Pragmatic Ontology-Driven Design for TypeScript. Define your domain once, generate everything.
TypeScript + Zod instead of RDF/OWL/SHACL. Same goals: centralized knowledge, grounded reasoning, zero inference. One contract definition produces D1 migrations, Hono route handlers, typed SDK clients, OpenAPI specs, and test fixtures.
npm install @stackbilt/contracts zodDefine a contract:
import { z } from 'zod';
import { defineContract } from '@stackbilt/contracts';
const TaskContract = defineContract({
name: 'Task',
version: '1.0.0',
description: 'A work item with lifecycle management',
schema: z.object({
id: z.string().uuid(),
userId: z.string(),
title: z.string().min(1).max(200),
status: z.enum(['open', 'in_progress', 'done']),
createdAt: z.string().datetime(),
}),
operations: {
create: {
input: z.object({
userId: z.string(),
title: z.string().min(1).max(200),
}),
output: 'self',
emits: ['task.created'],
},
complete: {
input: z.object({ id: z.string().uuid() }),
output: 'self',
transition: { from: 'in_progress', to: 'done' },
emits: ['task.completed'],
},
},
states: {
field: 'status',
initial: 'open',
transitions: {
open: { start: 'in_progress' },
in_progress: { complete: 'done' },
done: {},
},
},
surfaces: {
api: {
basePath: '/api/tasks',
routes: {
create: { method: 'POST', path: '/' },
list: { method: 'GET', path: '/' },
get: { method: 'GET', path: '/:id' },
complete: { method: 'POST', path: '/:id/complete' },
},
},
db: {
table: 'tasks',
indexes: ['idx_task_user(user_id, status)'],
},
},
authority: {
create: { requires: 'authenticated' },
list: { requires: 'public' },
get: { requires: 'public' },
complete: { requires: 'owner', ownerField: 'userId' },
},
});Generate everything from that single definition:
import {
generateSQL,
generateRoutes,
generateSDK,
generateOpenAPI,
generateTests,
} from '@stackbilt/contracts/generators';
const sql = generateSQL(TaskContract); // CREATE TABLE DDL
const routes = generateRoutes(TaskContract); // Hono handler bodies
const sdk = generateSDK(TaskContract); // Typed fetch client
const openapi = generateOpenAPI(TaskContract); // OpenAPI 3.1 spec
const tests = generateTests(TaskContract); // Vitest fixturesA contract is a single source of truth for a domain entity:
| Field | Purpose |
|---|---|
schema |
Entity shape (Zod schema) |
operations |
Valid actions with typed input/output |
states |
State machine transitions (optional) |
surfaces |
API routes + DB table mapping |
authority |
Per-operation access control |
invariants |
Runtime business rules (optional) |
version |
Semver for schema evolution |
Pure declaration factory. No side effects. Returns the definition with full type inference.
import { defineContract } from '@stackbilt/contracts';
const MyContract = defineContract({
name: 'MyEntity',
version: '1.0.0',
description: '...',
schema: z.object({ ... }),
operations: { ... },
surfaces: { ... },
authority: { ... },
});Cross-contract foreign key reference. Generators use the metadata for JOIN clauses and referential integrity.
import { ref } from '@stackbilt/contracts';
schema: z.object({
userId: ref(UserContract, 'id'), // FK to users.id
})Contract inheritance. Creates a new contract with merged fields. Base contract remains untouched.
import { extend } from '@stackbilt/contracts';
const AdminTaskContract = extend(TaskContract, {
name: 'AdminTask',
operations: {
forceClose: {
input: z.object({ id: z.string().uuid(), reason: z.string() }),
output: 'self',
transition: { from: ['open', 'in_progress'], to: 'done' },
},
},
authority: {
forceClose: { requires: 'role', roles: ['admin'] },
},
});All generators take a ContractDefinition and return a string (or object for OpenAPI).
Emits CREATE TABLE DDL for D1 (SQLite).
Options:
dropFirst— prependDROP TABLE IF EXISTSifNotExists— addIF NOT EXISTS(default:true)tableName— override table name
Output includes:
- Column types mapped from Zod (string -> TEXT, number.int -> INTEGER, number -> REAL, boolean -> INTEGER, enum -> TEXT with CHECK, array/object -> TEXT as JSON)
- NOT NULL for non-optional fields
- PRIMARY KEY detection
- CHECK constraints for enum fields
- UNIQUE constraints from
surfaces.db.uniqueColumns - DEFAULT values from
surfaces.db.columnOverrides - Foreign key comments from
ref()calls - Index definitions from
surfaces.db.indexes
Emits Hono HTTP handler bodies with D1 operations.
Options:
contractImport— import path for the contractappVar— Hono app variable nameincludeAuthImports— include auth middleware importsincludeEventImports— include event helper imports when operations declareemitseventImport— import path foremitContractEventeventSinkExpression— generated handler expression that resolves the caller-provided event sink
Output includes:
- Per-route handlers with try/catch error handling
- Input validation via
safeParse() - CRUD operations (INSERT, SELECT, UPDATE, DELETE)
ref()-awareLEFT JOINclauses for generated get/list SELECTs- State transition guards (checks current state before allowing transition, returns 409 on invalid state)
- Authority middleware (
requireAuth,requireOwner,requireRole) - Optional
emitContractEvent()calls after successful operations that declareemits - Proper HTTP status codes (201, 400, 404, 409, 500)
- Response envelope:
{ data }or{ error: { code, message } }
Emits a typed fetch client class.
Options:
contractImport— import pathclassName— class name override
Output includes:
- Client class with
baseUrl+headersconstructor - Per-operation methods with typed input/output
- Path parameter interpolation
- Error handling with descriptive messages
Returns an OpenAPI 3.1.0 specification object.
Options:
title— API title overrideversion— API version overrideserverUrl— server URL
Output includes:
- Paths from
surfaces.api.routes(:id->{id}conversion) - Request/response schemas from operations
- Security schemes for authenticated routes
- Component schemas for entity + operation inputs
Emits Vitest test fixtures and state machine validation tests.
Options:
contractImport— import pathrouteImport— import path for generated routesrouteExportName— exported generated route object nameincludeRouteIntegrationTests— include handler-level integration tests when an API surface exists
Output includes:
- Valid fixture generation with sensible defaults per type
- Enum validation tests (pass/fail for each value)
- State transition tests (allowed and blocked transitions)
- Handler-level integration harness for generated Hono routes
The Zod schema walker extracts metadata for code generation. Works with both Zod v3 and v4.
import { extractColumns, extractEnums, toSnakeCase } from '@stackbilt/contracts/introspect';
const columns = extractColumns(MyContract.schema);
// [{ name: 'id', sqlType: 'TEXT', nullable: false, isPrimaryKey: true, ... }, ...]
const enums = extractEnums(MyContract.schema);
// { status: ['open', 'in_progress', 'done'] }
toSnakeCase('createdAt'); // 'created_at'This package includes generic graded-run and benchmark/eval report schemas for private product eval suites that need shared validation without leaking product evidence. GradedRunReportSchema is the package-level canonical shape for measured case runs; benchmark reports compose the same aggregate count and latency primitives.
import {
GradedRunReportSchema,
BenchmarkReportSchema,
PublicBenchmarkSummarySchema,
} from '@stackbilt/contracts';
const gradedRun = GradedRunReportSchema.parse({
schemaVersion: 'graded-run-report.v1',
run: {
runId: '123e4567-e89b-12d3-a456-426614174000',
runName: 'classifier-eval',
runner: 'eval-runner@1.0.0',
dataset: 'fixtures/classifier.jsonl',
generatedAt: '2026-05-29T10:00:00.000Z',
environment: 'ci',
},
counts: {
total: 4,
passed: 3,
failed: 1,
errored: 0,
skipped: 0,
},
passRate: 0.75,
latency: {
unit: 'ms',
min: 8,
max: 80,
mean: 31,
p50: 24,
p90: 48,
p95: 62,
},
});
const report = BenchmarkReportSchema.parse({
schemaVersion: 'benchmark-report.v1',
run: {
runId: '123e4567-e89b-12d3-a456-426614174000',
benchmarkName: 'synthetic-routing-benchmark',
benchmarkVersion: '1.0.0',
generatedAt: '2026-05-29T10:00:00.000Z',
environment: 'ci',
},
counts: {
total: 4,
passed: 3,
failed: 1,
errored: 0,
skipped: 0,
},
latency: {
unit: 'ms',
min: 8,
max: 80,
mean: 31,
p50: 24,
p95: 62,
p99: 77,
},
});
const publicSummary = PublicBenchmarkSummarySchema.parse({
schemaVersion: 'public-benchmark-summary.v1',
benchmarkName: report.run.benchmarkName,
benchmarkVersion: report.run.benchmarkVersion,
generatedAt: report.run.generatedAt,
counts: report.counts,
latency: report.latency,
});Product-specific eval evidence belongs in private repos. Do not place golden cases, scorer weights, private thresholds, endpoint configuration, raw prompts, raw outputs, competitor transcripts, or product-specific corpus rows in this package. Public summaries should contain aggregate metrics and opaque artifact digests only.
interface ColumnDef {
name: string; // snake_case column name
sqlType: string; // TEXT | INTEGER | REAL
nullable: boolean;
defaultValue: string | null;
checkConstraint: string | null; // SQL CHECK for enums
isPrimaryKey: boolean;
isRef: boolean; // foreign key via ref()
refTable: string | null;
refField: string | null;
}Contracts can declare state machines that enforce valid transitions:
states: {
field: 'status', // which schema field holds state
initial: 'draft', // state for new entities
transitions: {
draft: { publish: 'published', archive: 'archived', delete: null },
published: { archive: 'archived' },
archived: {}, // terminal state
},
}nullas a target means deletion (terminal)- Empty
{}means no valid transitions (terminal state) - Route generator emits guards that check current state and return 409 on invalid transitions
Four authorization levels per operation:
authority: {
list: { requires: 'public' }, // no auth
create: { requires: 'authenticated' }, // any logged-in user
update: { requires: 'owner', ownerField: 'userId' }, // entity owner only
admin: { requires: 'role', roles: ['admin'] }, // specific roles
}Route generator emits the corresponding middleware calls.
Runtime business rules that guard operations:
invariants: [
{
name: 'min_ingredients',
description: 'Published recipes need at least one ingredient',
check: (entity) => {
const e = entity as { status: string; ingredients: string[] };
if (e.status === 'published' && e.ingredients.length === 0) {
return 'Published recipes need at least one ingredient';
}
return true;
},
appliesTo: ['publish'],
},
]- Return
trueto pass, or a string error message to reject appliesTolists which operations trigger the check- Enforcement is delegated to your application code
See src/examples/recipe.contract.ts for a complete contract demonstrating schema, operations, state machine, surfaces, authority, and invariants.
import { RecipeContract } from '@stackbilt/contracts/examples';
import { generateSQL, generateRoutes } from '@stackbilt/contracts/generators';
console.log(generateSQL(RecipeContract));
console.log(generateRoutes(RecipeContract));- Contracts are the source of truth. Schema, API, database, auth, and business rules in one place.
- Generate, don't handwrite. CRUD handlers, SQL migrations, SDKs, and tests derived from contracts.
- Zod over RDF. Same ontological rigor, developer-friendly, no external tooling required.
- Extend over modify.
extend()creates new contracts from existing ones without mutation. - Surfaces decouple shape from deployment. Same contract serves different API frameworks and databases.
- Authority is declarative. Per-operation access rules, generated as middleware.
- State machines are optional but powerful. Transitions enforce valid lifecycle changes at the route level.
Apache-2.0