Spec-driven validation + typing
Define a single operation with SchemaModel, generate validation, and keep your existing handler logic.
What you'll build
- One command spec with explicit input/output models.
- Validation + typing without rewriting your service layer.
- Clear acceptance scenarios for regression safety.
1) Define the spec
Create src/contracts/contact-create.operation.ts:
src/contracts/contact-create.operation.ts
import { defineCommand } from "@contractspec/lib.contracts/operations";
import { SchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
const ContactInput = new SchemaModel({
name: "ContactInput",
fields: {
email: { type: ScalarTypeEnum.Email(), isOptional: false },
firstName: { type: ScalarTypeEnum.NonEmptyString(), isOptional: false },
lastName: { type: ScalarTypeEnum.NonEmptyString(), isOptional: false },
},
});
const ContactOutput = new SchemaModel({
name: "ContactOutput",
fields: {
id: { type: ScalarTypeEnum.String(), isOptional: false },
email: { type: ScalarTypeEnum.Email(), isOptional: false },
createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
},
});
export const ContactCreateCommand = defineCommand({
meta: {
key: "contact.create",
version: "1.0.0",
description: "Create a CRM contact",
owners: ["@sales"],
tags: ["crm", "contacts"],
},
io: {
input: ContactInput,
output: ContactOutput,
},
policy: {
auth: "user",
},
acceptance: {
scenarios: [
{
key: "create-contact",
given: ["User is authenticated"],
when: ["ContactCreateCommand executes"],
then: ["Contact is persisted", "Email is validated"],
},
],
},
});2) Wire the handler
Keep your existing code. Just ensure the handler returns the output shape defined above.
src/handlers/contact-create.ts
import { ContactCreateCommand } from "@/contracts/contact-create.operation";
export async function handleContactCreate(
input: (typeof ContactCreateCommand)["io"]["input"],
ctx: { userId: string }
) {
// Your existing persistence logic
return {
id: "contact_123",
email: input.email,
createdAt: new Date().toISOString(),
};
}3) Validate
spec-validation
contractspec validate src/contracts/contact-create.operation.tsExpected output: Validation passed.
Example package
The CRM Pipeline example includes real specs, handlers, and presentations for contact + deal flows.
crm-example
# Build + validate the CRM pipeline example
cd packages/examples/crm-pipeline
bun install
bun run build
bun run validateNeed shared validation policies?
Studio lets teams enforce validation policies and review changes before they ship.
Join Studio waitlist