zmdbzero-maintenance data layer
Docs Benchmarks Anti-patterns OpenAPI
Docs / Ecosystem integrations

TypeBoxSupported

TypeBox is the closest neighbour zmdb has, because it targets JSON Schema. That makes the interop genuinely useful rather than just a migration path: zmdb _emits_ JSON Schema, and TypeBox _is_ JSON Schema.

// TypeBox
const User = Type.Object({ id: Type.Number(), email: Type.String() });
type User = Static<typeof User>;
const check = TypeCompiler.Compile(User); // codegen via new Function
check.Check(body);

// zmdb
interface User {
  id: number;
  email: string;
}
is<User>(body);

The new Function difference#

This is the substantive one. TypeCompiler.Compile generates a function with new Function, which is fast and needs runtime code generation. zmdb's validators contain no new Function and no eval anywhere in the packages — the descriptor is a literal produced at build time.

TypeBox TypeCompilerzmdb
Codegenat runtime, new Functionat build time, transformer
Strict CSP (script-src without unsafe-eval)noyes
Cloudflare Workers / edgegenerally noyes
Cold-start costcompile per processnone
Steady-state speedvery fastcomparable

TypeBox's uncompiled Value.Check avoids codegen but is much slower. So the choice under a strict CSP is between slow TypeBox and fast zmdb, which is the case where this actually matters. See JIT vs AOT.

Real interop: zmdb schema → JSON Schema → TypeBox#

toJsonSchema emits JSON Schema, and TypeBox consumes JSON Schema by construction — so a declared table can validate through TypeBox, ajv, or anything else in that ecosystem:

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

const createSchema = toJsonSchema(posts, 'create'); // omits serial, respects defaults
const components = toOpenApiComponents([users, posts]);
import Ajv from 'ajv';
const validate = new Ajv().compile(createSchema);

The variants are entity | create | update | get | list | search, so you get the right shape per operation rather than one schema you narrow by hand. See OpenAPI Schemas.

This is the pattern for the dynamic case too: where you need a runtime-defined validator, generate JSON Schema from your data and hand it to ajv, rather than trying to make a type parameter dynamic.

Going the other way#

There is no JSON-Schema-to-declaration importer. If you have JSON Schema as your source of truth — a shared API contract, say — you have two options:

Mapping the API#

TypeBoxzmdb
Value.Check(S, x) / check.Check(x)is<T>(x)
Value.Assertassert<T>(x)
Value.Errors(S, x)validate<T>(x).errors
Value.Clean(S, x)validateObject(x, 'strip')
Value.Convert(S, x)coerce
Type.Union([...])union
Type.Union with a literal tagdiscriminated
additionalProperties: falsevalidateObject(x, 'strict')
Static<typeof S>the type itself

When to stay on TypeBox#

And the canary, as ever#

it('the transformer is running', () => {
  expect(is<{ id: number }>({ id: 'x' })).toBe(false);
});

TypeBox fails loudly when misconfigured. zmdb fails open. If you are replacing one with the other, this test is what keeps the swap accurate.

---

See also: OpenAPI Schemas · JIT vs AOT · Zod