Schema Generation
Purpose: How Zod schemas are generated from PostgreSQL types
Last Updated: 2024-11-28
Overview
The schema generation layer converts PostgreSQL type metadata into Zod validation schemas, providing runtime type safety in addition to compile-time TypeScript types.
For detailed builder implementations: See Schema Builders
Architecture
TypeCatalog (type metadata)
↓ processed by
Schema Builders (Enum, Domain, Composite, Array, Range)
↓ generate
Zod Schemas (TypeScript code)
↓ used for
Runtime Validation
Builder Types
| Builder | PostgreSQL Type | Zod Output | Example |
|---|---|---|---|
| EnumBuilder | ENUM |
z.enum([...]) |
status AS ENUM ('active', 'inactive') |
| DomainBuilder | DOMAIN |
z.string().refine(...) |
email AS text CHECK (...) |
| CompositeBuilder | Composite type | z.object({...}) |
user_record AS (id bigint, ...) |
| ArrayBuilder | Array type | z.array(...) |
text[], integer[] |
| RangeBuilder | Range type | z.object({...}) |
daterange, int4range |
Detailed implementations: Schema Builders
Generation Process
Steps: Initialize builders → Topological sort → Generate schemas → Emit TypeScript
// 1. Initialize builders for each type category
const builders = [
new EnumBuilder(typeCatalog),
new DomainBuilder(typeCatalog),
new CompositeBuilder(typeCatalog),
new ArrayBuilder(typeCatalog),
new RangeBuilder(typeCatalog)
];
// 2. Sort types by dependencies
const sortedTypes = typeCatalog.topologicalSort();
// 3. Generate schemas
for (const type of sortedTypes) {
const builder = builders.find(b => b.canHandle(type));
if (builder) emit(builder.build(type));
}
Output Example:
export const statusSchema = z.enum(['active', 'inactive']);
export type Status = z.infer<typeof statusSchema>;
Base Type Mappings
Common PostgreSQL to Zod mappings:
| PostgreSQL | Zod Schema | TypeScript Type |
|---|---|---|
integer |
z.number().int() |
number |
bigint |
z.bigint() |
bigint |
text |
z.string() |
string |
boolean |
z.boolean() |
boolean |
timestamp |
z.string().datetime() |
string |
jsonb |
z.unknown() |
unknown |
uuid |
z.string().uuid() |
string |
text[] |
z.array(z.string()) |
string[] |
Complete mapping table: Schema Builders
Nullable and Optional
| SQL Pattern | Zod Schema | TypeScript Type |
|---|---|---|
RETURNS bigint (can be NULL) |
z.bigint().nullable() |
bigint | null |
p_name text DEFAULT 'x' |
z.string().optional() |
string | undefined |
p_name text DEFAULT NULL |
z.string().nullable().optional() |
string | null | undefined |
Nested Types
Composites: Dependencies generated first, then referenced by name
// address first, then user_with_address
export const addressSchema = z.object({ street: z.string(), city: z.string() });
export const userWithAddressSchema = z.object({ id: z.bigint(), address: addressSchema });
Arrays: integer[][] → z.array(z.array(z.number().int()))
Runtime Validation
Input: inputSchema.parse(params) - Throws ZodError if invalid
Output: outputSchema.parse(result) - Ensures database returns expected shape
// Input validation
const validated = inputSchema.parse(params); // ZodError on failure
// Output validation
return userSchema.parse(result.rows[0]); // ZodError on unexpected data
Error Handling
import { ZodError } from 'zod';
try {
await createUser(client, { p_email: 'invalid' });
} catch (error) {
if (error instanceof ZodError) {
// error.errors: [{ code, message, path, validation }]
console.error('Validation failed:', error.errors);
}
}
Code Organization
src/__generated__/graph/
├── types.ts # Schemas and inferred types
├── functions.ts # Generated function wrappers
└── index.ts # Re-exports
// types.ts
export const nodeTypeSchema = z.enum([...]);
export type NodeType = z.infer<typeof nodeTypeSchema>;
Performance
Schema Reuse: Define once, reference everywhere
Lazy Evaluation: z.lazy(() => schema) for circular references
// Reuse
export const emailSchema = z.string().email();
export const userSchema = z.object({ email: emailSchema });
// Circular
export const nodeSchema: z.ZodType = z.lazy(() =>
z.object({ id: z.bigint(), children: z.array(nodeSchema) })
);
Advanced Patterns
Circular References: Schema Builders - Advanced Patterns
Custom Validators: Schema Builders - Advanced Patterns
Error Handling: Schema Builders - Error Handling
Testing
describe('userSchema', () => {
it('validates valid user', () => {
expect(() => userSchema.parse({ id: 1n, email: 'user@example.com' })).not.toThrow();
});
it('rejects invalid user', () => {
expect(() => userSchema.parse({ id: 1n, email: 'invalid' })).toThrow(ZodError);
});
});
More examples: Schema Builders - Testing
Quick Reference
| Need | See |
|---|---|
| Builder implementations | Schema Builders |
| Type mappings | Type System |
| Using schemas | Function Wrappers |
| Type discovery | Introspection |
Related Documentation
Core Documentation
- Schema Builders - Detailed implementations
- Type System - PostgreSQL ↔ TypeScript mappings
- Function Wrappers - Using generated schemas
- Introspection - Type discovery process
Reference
- Quick Reference - Common tasks
- CLI Reference - Running codegen
- Examples - Code samples
Key Takeaway: Schema builders convert PostgreSQL types to Zod schemas, providing runtime type safety. For detailed builder implementations, see Schema Builders.