zmdbzero-maintenance data layer
Docs Benchmarks Anti-patterns OpenAPI
Docs / Validation and contracts

JSON SchemaSupported

The toJsonSchema function generates valid JSON Schema from a zmdb table declaration. This enables interoperability with tools that understand JSON Schema — validation libraries, API documentation systems, and code generation tools.

📝 Note

The generation is a build-time operation, driven by the declaration's IR. There is no runtime reflection.

Basic Generation#

import { toJsonSchema } from '@zmdb/schema/openapi';
import { schemaOf } from '@zmdb/core';
import type { Min, PrimaryKey, Serial, Sql, Table } from '@zmdb/core/tags';

interface User extends Table<'users'> {
  id: number & Sql<'integer'> & Serial & PrimaryKey;
  email: string & Sql<'text'>;
  age: number & Sql<'integer'> & Min<0>;
}

const userSchema = schemaOf<User>();
const jsonSchema = toJsonSchema(userSchema, 'entity');
// Generated JSON Schema:
{
  "type": "object",
  "properties": {
    "id": { "type": "integer" },
    "email": { "type": "string" },
    "age": { "type": "integer", "minimum": 0 }
  },
  "required": ["id", "email", "age"]
}

Schema Variants#

The second parameter controls which columns are included:

// Entity (response) — all columns including auto-increment
toJsonSchema(userSchema, 'entity');

// Create — excludes auto-increment columns
toJsonSchema(userSchema, 'create');
// { type: 'object', properties: { email: {...}, age: {...} }, required: ['email'] }

// Update — all columns optional
toJsonSchema(userSchema, 'update');
// { type: 'object', properties: { email: {...}, age: {...} }, required: [] }

// GET /list /search — same as entity (response)
toJsonSchema(userSchema, 'get');
toJsonSchema(userSchema, 'list');
toJsonSchema(userSchema, 'search');
❗ Important

The create variant omits Serial columns since those are generated by the database. The update variant marks all fields as optional since partial updates are allowed.

Tag to JSON Schema Mapping#

Validation tags map to JSON Schema keywords:

// Min<N>          -> minimum
// Max<N>          -> maximum
// MinLength<N>    -> minLength
// MaxLength<N>    -> maxLength
// Length<N>       -> maxLength
// Pattern<S>      -> pattern
// a literal union -> enum

There is no Enum tag: a literal union is how you say that, and TypeScript checks it everywhere a flag would not.

Generated schema includes these mappings:

interface Product extends Table<'products'> {
  name: string & Sql<'text'> & MinLength<1> & MaxLength<100>;
  price: number & Sql<'numeric'> & Min<0>;
  code: string & Sql<'text'> & Pattern<'^[A-Z]{3}$'>;
  status: 'active' | 'inactive';
}

const jsonSchema = toJsonSchema(schemaOf<Product>(), 'entity');
// {
//   "type": "object",
//   "properties": {
//     "name": { "type": "string", "minLength": 1, "maxLength": 100 },
//     "price": { "type": "number", "minimum": 0 },
//     "code": { "type": "string", "pattern": "^[A-Z]{3}$" },
//     "status": { "type": "string", "enum": ["active", "inactive"] }
//   },
//   "required": ["status", "name", "price", "code"]
// }

Nullable Handling#

Nullable columns become union types in JSON Schema:

interface Profile extends Table<'profiles'> {
  id: number & Sql<'integer'> & Serial & PrimaryKey;
  bio: (string & Sql<'text'>) | null; // nullable column
  avatar: string & Sql<'text'>; // required
}

const jsonSchema = toJsonSchema(schemaOf<Profile>(), 'entity');
// {
//   "properties": {
//     "bio": { "type": ["string", "null"] },  // union with null
//     "avatar": { "type": "string" }
//   },
//   "required": ["id", "avatar"]
// }

Generating OpenAPI Components#

Use toOpenApiComponents to generate a map of schemas for an entire API:

import { toOpenApiComponents } from '@zmdb/schema/openapi';

const schemas = toOpenApiComponents([schemaOf<User>(), schemaOf<Order>(), schemaOf<Product>()]);

// Returns: { schemas: { User: {...}, Order: {...}, Product: {...} } }
// Output:
// {
//   "schemas": {
//     "User": { "type": "object", "properties": {...}, "required": [...] },
//     "Order": { "type": "object", "properties": {...}, "required": [...] },
//     "Product": { "type": "object", "properties": {...}, "required": [...] }
//   }
// }
💡 Tip

The generated OpenAPI components can be directly merged into your OpenAPI spec's components.schemas field.

List and Search Envelopes#

For list/search responses, use toListSchema and toSearchSchema:

import { toListSchema, toSearchSchema } from '@zmdb/schema/openapi';

const listSchema = toListSchema(userSchema);
// {
//   "type": "object",
//   "properties": {
//     "items": { "type": "array", "items": <User schema> },
//     "total": { "type": "integer" },
//     "hasMore": { "type": "boolean" },
//     "cursor": { "type": "string" }
//   },
//   "required": ["hasMore", "items"]
// }

const searchSchema = toSearchSchema(userSchema);
// Similar to list, but each item includes "_score" for FTS ranking