Integration contracts
How a generated validation layer keeps forms, server code, and the database schema in sync when the schema changes.
New Mistflow apps ship with a contracts/ directory: a validation layer derived from the
database schema. It exists so that when the schema changes, everything that depends on the
schema changes with it, in one place instead of five.
The problem it solves
Say a habit tracker has a habits table. Three places depend on its shape:
- Server code that inserts habits
- A form that collects the fields
- A list view that displays them
Someone adds a reminderTime column. Without a contract layer, every one of those has to
be found and updated by hand. The form does not know about the new field. The server code
still rejects it. The list view is fine until something filters on the new column and gets
back the wrong shape.
One source of truth, several places manually kept in step with it. That is drift.
What the directory does
db/
schema.ts the database tables, the source of truth
contracts/
habit.ts validation schemas derived from db/schema.ts
index.ts re-exportscontracts/habit.ts:
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
import { z } from 'zod';
import { habits } from '@/db/schema';
export const HabitRow = createSelectSchema(habits);
export const HabitInsert = createInsertSchema(habits, {
name: z.string().min(1).max(80),
targetDaysPerWeek: z.number().int().min(1).max(7),
});
export const HabitUpdate = HabitInsert.partial();
export type Habit = z.infer<typeof HabitRow>;
export type NewHabit = z.infer<typeof HabitInsert>;Everything that touches the shape imports from contracts/ instead of redefining it:
- Forms validate against
HabitInsert - Server code parses input with
HabitInsertbefore touching the database - API routes validate request bodies with the same schema
- List views type their data as
Habit[]
Now adding reminderTime is one edit to db/schema.ts. The derived schemas pick up the
new column, type inference updates, and the compiler points at every place that assumed
the old shape.
What the AI is told to do with it
The stack methodology loaded into every build says:
- Never write validation schemas inline for stored entities. Use
contracts/ - When adding a column, edit
db/schema.tsfirst. Everything else follows - Import the insert schema from
contracts/before writing server code. Do not hand-roll validation - If a new field is required, update the form's defaults
Existing contracts are also surfaced as context during implementation, so the editor sees what is already defined before generating anything new.
Older projects
Projects created before contracts existed keep working without them. Asking your editor for project state surfaces any mismatch between the schema and the contracts, so it can regenerate them or add missing entities before continuing.
Contracts are optional. But once the layer exists, a schema change is a one-line edit rather than a hunt through the codebase, which is the entire point.