Contracts
The SDK and the backend agree on shapes by snapshotting both sides as JSON Schema and diffing on every CI run. The pipeline runs on every PR; drift breaks the build before it can reach a storefront.
The two sources of truth
| Side | Source | Snapshot |
|---|---|---|
| Backend | Server-derived types | contracts/server/*.json |
| SDK | zod schemas in @cimplify/sdk/testing | packages/sdk/contracts/ts/*.json |
Pipeline
# 1. Backend emits schemas from its typed models
bun run contracts:emit:server
# → writes Brand.json, Cart.json, CartItem.json, AddItemPayload.json,
# CheckoutBody.json, CheckoutResponse.json, …
# 2. SDK emits schemas from zod via z.toJSONSchema()
bun run contracts:emit
# → writes packages/sdk/contracts/ts/*, same filenames
# 3. Diff each pair; CI fails on any structural difference
bun run contracts:diffWhat the snapshot contains
Each emitted file is a standard JSON Schema with $id, description, version, and a $defs section for shared sub-shapes. Identical filenames on both sides are considered the same contract; that's how the differ pairs them.
AddItemPayload.json
Brand.json
Cart.json
CartItem.json
CartPricing.json
CheckoutBody.json
CheckoutResponse.json
VariantInfo.json
manifest.json # version pins per schemamanifest.json
The manifest pins each schema's version. Per-schema versioning lets a consumer pinned to an old SDK detect drift only on the contracts they care about, instead of a global SCHEMA_VERSION bump that false-positives on every release.
{
"generated_at": "2026-05-07T13:55:55Z",
"schema_count": 8,
"schemas": [
{ "name": "Brand", "file": "Brand.json", "version": "1.0.0" },
{ "name": "AddItemPayload", "file": "AddItemPayload.json", "version": "1.0.0" },
{ "name": "Cart", "file": "Cart.json", "version": "1.0.0" },
{ "name": "CartItem", "file": "CartItem.json", "version": "1.0.0" },
{ "name": "CheckoutBody", "file": "CheckoutBody.json", "version": "1.0.0" },
{ "name": "CheckoutResponse", "file": "CheckoutResponse.json", "version": "1.0.0" }
]
}Drift failure example
Suppose a backend PR renames billing_address to billing on CheckoutBody. The server snapshot updates; the zod schema in the SDK doesn't. CI prints:
✖ contract drift: CheckoutBody.json
+ properties.billing
- properties.billing_address
! version pin 1.0.0 → bump required
build failed (contract drift)Resolution path: update the SDK's zod schema, bump the per-schema version in both manifests, ship the SDK release, then merge the backend PR. The lockstep flow keeps live storefronts compatible with the backend they were built against.
Local diff
# From the SDK package, emit + diff in one step
bun run contracts:check
# Or from the workspace root, alongside the backend
bun run contracts:emit:server && bun run contracts:diffWhy JSON Schema
- Both sides have first-class generators (backend schema derive, zod 4's
z.toJSONSchema()); no hand-rolled DSL. - Diffable as text; reviewers see exactly which field, type, or constraint moved.
- Downstream tooling (OpenAPI generators, agent prompt assemblers, codegen) can consume the same artefacts.
Next
MSW transport
The same in-process Hono mock, exposed as MSW handlers. Use this transport when your tests need MSW's request interception: React Testing Library component tests, Playwright component runner, browser-mode vitest. One mock, two transports.
Products
Browse the active business’s catalogue. List endpoints accept rich filters; detail endpoints return a single product with its variants, add-ons, schedules, and deals.