Skip to content

Schema Builders

TypeScript-first schema definitions for z0 entities, facts, and configs.

Prerequisites: core-concepts.md


z0 provides a fluent TypeScript API for defining schemas. Instead of writing JSON manifests, define your domain model using TypeScript with full type inference.

import { z0 } from '@z0-app/sdk';
// Define an entity
const Account = z0.entity('account', {
email: z0.string().indexed(),
balance: z0.number().default(0),
status: z0.enum(['active', 'suspended', 'closed']),
}).facts({
deposit: { data: { amount: z0.number(), reference: z0.string().optional() } },
withdrawal: { data: { amount: z0.number() } },
});
// Type inference works automatically
type AccountFields = z0.infer<typeof Account>;
// => { email: string; balance: number; status: 'active' | 'suspended' | 'closed' }

z0 has three core primitives. Each has a corresponding schema builder:

PrimitiveBuilderPurpose
Entityz0.entity()Identity/state container with fields and facts
Factz0.fact()Immutable event with typed data
Configz0.config()Versioned configuration with scope

Entities are the core state containers. Define fields and the facts that can modify them.

const User = z0.entity('user', {
email: z0.string().indexed(),
name: z0.string(),
createdAt: z0.date(),
});
const Account = z0.entity('account', {
balance: z0.number().default(0),
status: z0.enum(['active', 'frozen']),
}).facts({
deposit: {
data: {
amount: z0.number(),
reference: z0.string().optional()
}
},
withdrawal: {
data: {
amount: z0.number()
}
},
freeze: {
data: {
reason: z0.string()
}
},
}).description('Financial account with balance tracking');

Generate Zod validators for runtime validation:

const validators = z0.entityToValidators(Account);
// Validate field data
const result = validators.fields.safeParse({ balance: 100, status: 'active' });
if (!result.success) {
console.error(result.error);
}
// Validate fact data
const factResult = validators.validateFactData('deposit', { amount: 50 });

For standalone facts not tied to a specific entity definition:

const AuditEvent = z0.fact('audit', {
action: z0.string(),
userId: z0.string(),
resourceId: z0.string(),
metadata: z0.object({
ip: z0.string(),
userAgent: z0.string().optional(),
}),
}).subtype('security')
.description('Security audit trail event');
// Type inference
type AuditData = z0.infer<typeof AuditEvent>;
// => { action: string; userId: string; resourceId: string; metadata: { ip: string; userAgent?: string } }
// Validation
const validators = z0.factSchemaToValidators(AuditEvent);
const result = validators.validate({
action: 'login',
userId: 'user_123',
resourceId: 'session_456',
metadata: { ip: '192.168.1.1' }
});
MethodDescription
.subtype(string)Set the fact subtype (e.g., ‘manual’, ‘recurring’)
.description(string)Add human-readable description

Configuration schemas define settings that can vary by scope:

const RateLimits = z0.config('rate_limits', 'security', {
maxRequestsPerMinute: z0.number().default(100),
windowMs: z0.number().default(60000),
blockDurationMs: z0.number().default(300000),
}).scope('tenant')
.description('Per-tenant API rate limiting configuration');
// Type inference
type RateLimitSettings = z0.infer<typeof RateLimits>;
// => { maxRequestsPerMinute: number; windowMs: number; blockDurationMs: number }
// Validation
const validators = z0.configToValidators(RateLimits);
const result = validators.validate({
maxRequestsPerMinute: 50,
windowMs: 30000,
blockDurationMs: 600000
});
ScopeDescription
tenantConfiguration specific to a single tenant
platformConfiguration shared across all tenants
globalSystem-wide configuration
MethodDescription
.scope(scope)Set default scope (‘tenant’, ‘platform’, ‘global’)
.description(string)Add human-readable description

z0.string() // String values
z0.number() // Numeric values
z0.boolean() // Boolean values
z0.date() // Date/timestamp values
z0.enum(['active', 'inactive', 'pending'])
// Type inferred as: 'active' | 'inactive' | 'pending'
z0.object({
street: z0.string(),
city: z0.string(),
zip: z0.string(),
})
// Stored as JSON in SQLite

Chain modifiers to customize field behavior:

z0.string()
.optional() // Field is not required
.default('pending') // Default value
.indexed() // Create generated column for queries
.description('...') // Human-readable description
ModifierDescription
.optional()Field can be undefined
.required()Field must be present (default)
.default(value)Default value when not provided
.indexed()Generate SQLite column for efficient queries
.indexed(storage)Use custom storage key for the index
.description(desc)Documentation string

Indexed fields create generated columns in SQLite for efficient querying:

const User = z0.entity('user', {
email: z0.string().indexed(), // Creates ix_s_1 column
age: z0.number().indexed(), // Creates ix_n_1 column
status: z0.enum(['a', 'b']).indexed(), // Creates ix_s_2 column
});

The storage keys are auto-generated but can be customized:

z0.string().indexed('email_idx')

The z0.infer<> helper extracts TypeScript types from schemas:

// Entity fields
const Account = z0.entity('account', {
email: z0.string(),
balance: z0.number(),
});
type AccountData = z0.infer<typeof Account>;
// => { email: string; balance: number }
// Standalone fact
const Deposit = z0.fact('deposit', {
amount: z0.number(),
reference: z0.string().optional(),
});
type DepositData = z0.infer<typeof Deposit>;
// => { amount: number; reference?: string }
// Config settings
const Settings = z0.config('settings', 'app', {
darkMode: z0.boolean().default(false),
});
type SettingsData = z0.infer<typeof Settings>;
// => { darkMode: boolean }

Convert entity schemas to DomainManifest for the registry:

import { z0 } from '@z0-app/sdk';
// Register entities
z0.registerEntity(Account);
z0.registerEntity(User);
// Build manifest
const manifest = z0.buildManifest({
name: 'my-app',
version: '1.0.0',
ledgers: {
account: AccountLedger,
user: UserLedger,
},
});
// Or for a single entity
const simpleManifest = z0.entityToManifest(Account, {
name: 'my-app',
version: '1.0.0',
ledger: AccountLedger,
});

After boot, freeze the schema registry to prevent runtime tampering:

const platform = z0.boot(env, manifest);
z0.freezeSchemas(); // No more schema registrations allowed
// Check if frozen
if (z0.isSchemaFrozen()) {
// Safe to proceed
}

import { z0 } from '@z0-app/sdk';
// =========================================
// Define Domain Schema
// =========================================
// Entity with facts
const Account = z0.entity('account', {
email: z0.string().indexed(),
balance: z0.number().default(0),
status: z0.enum(['active', 'suspended', 'closed']).indexed(),
profile: z0.object({
firstName: z0.string(),
lastName: z0.string(),
avatar: z0.string().optional(),
}),
}).facts({
deposit: { data: { amount: z0.number(), reference: z0.string().optional() } },
withdrawal: { data: { amount: z0.number() } },
statusChange: { data: { newStatus: z0.string(), reason: z0.string() } },
}).description('User financial account');
// Standalone fact for cross-entity events
const Transfer = z0.fact('transfer', {
fromAccountId: z0.string(),
toAccountId: z0.string(),
amount: z0.number(),
reference: z0.string(),
}).description('Account-to-account transfer');
// Config for rate limiting
const RateLimits = z0.config('rate_limits', 'security', {
maxTransfersPerDay: z0.number().default(10),
maxTransferAmount: z0.number().default(10000),
}).scope('tenant');
// =========================================
// Type Inference (compile-time)
// =========================================
type AccountFields = z0.infer<typeof Account>;
type TransferData = z0.infer<typeof Transfer>;
type RateLimitSettings = z0.infer<typeof RateLimits>;
// =========================================
// Runtime Validation
// =========================================
const accountValidators = z0.entityToValidators(Account);
const transferValidators = z0.factSchemaToValidators(Transfer);
const rateLimitValidators = z0.configToValidators(RateLimits);
// Validate incoming data
function processDeposit(data: unknown) {
const result = accountValidators.validateFactData('deposit', data);
if (!result.success) {
throw new Error('Invalid deposit data');
}
return result.data; // Typed as { amount: number; reference?: string }
}
// =========================================
// Build Manifest
// =========================================
z0.registerEntity(Account);
const manifest = z0.buildManifest({
name: 'banking-app',
version: '1.0.0',
ledgers: {
account: AccountLedger,
},
});
// Boot and freeze
const platform = z0.boot(env, manifest);
z0.freezeSchemas();

ConceptAPI
Define entityz0.entity(name, fields).facts({...})
Define factz0.fact(type, data).subtype(...).description(...)
Define configz0.config(type, category, settings).scope(...)
Infer typesz0.infer<typeof Schema>
Validate entityz0.entityToValidators(entity)
Validate factz0.factSchemaToValidators(fact)
Validate configz0.configToValidators(config)
Build manifestz0.buildManifest({ name, version, ledgers })
Freeze registryz0.freezeSchemas()

The schema builder API provides compile-time type safety and runtime validation from a single source of truth.