@rippledb/zod
Zod schemas for runtime validation of RippleDB payloads
@rippledb/zod
Runtime validation helpers for RippleDB types using Zod.
This is useful for:
- Auto-generating Zod schemas from your schema descriptor
- Validating server request bodies (
pull,append) - Validating events/messages between systems
- Validating persisted JSON blobs
Installation
pnpm add @rippledb/zod @rippledb/core zodnpm install @rippledb/zod @rippledb/core zodyarn add @rippledb/zod @rippledb/core zodwithZod() - Auto-Generated Schemas
The withZod() function automatically generates Zod schemas from your field descriptors:
import { defineSchema, s, InferSchema } from "@rippledb/core";
import { withZod } from "@rippledb/zod";
const schema = defineSchema({
todos: {
id: s.string(),
title: s.string(),
done: s.boolean(),
status: s.enum(["pending", "active", "done"]),
notes: s.string().optional(),
},
users: {
id: s.string(),
name: s.string(),
email: s.string(),
},
});
// Wrap with auto-generated Zod schemas
const schemaWithZod = withZod(schema);
// Validate data
const todo = schemaWithZod.zod.todos.parse({
id: "1",
title: "Buy milk",
done: false,
status: "pending",
});
// Safe parsing
const result = schemaWithZod.zod.users.safeParse(untrustedInput);
if (result.success) {
console.log(result.data);
} else {
console.error(result.error);
}Custom Refinements
Override specific fields with custom Zod schemas for additional validation.
Inline Overrides
Pass overrides directly to withZod() - you get full autocomplete for entity and field names:
import { z } from "zod";
const schemaWithRefinements = withZod(schema, {
todos: {
title: z.string().min(1).max(100),
},
users: {
email: z.string().email(),
name: z.string().min(2),
},
});
// Now email validation is enforced
schemaWithRefinements.zod.users.parse({
id: "1",
name: "Jo", // Valid (length >= 2)
email: "not-an-email", // ❌ Throws ZodError
});Typed External Overrides
If you want to define overrides separately (e.g., in another file), use the ZodOverrides type:
import { z } from "zod";
import { withZod, ZodOverrides } from "@rippledb/zod";
// ZodOverrides expects the raw schema type, not SchemaDescriptor
// Use schema['schema'] to access the underlying descriptor
const myOverrides: ZodOverrides<(typeof schema)["schema"]> = {
todos: {
title: z.string().min(1).max(100),
},
users: {
email: z.string().email(),
},
};
// Pass to withZod later
const schemaWithRefinements = withZod(schema, myOverrides);Both patterns provide full autocomplete for valid entity and field names. Use inline for simple cases, typed external when overrides need to be shared or organized separately.
generateZodSchemas()
If you only need the Zod schemas without wrapping the descriptor:
import { generateZodSchemas } from "@rippledb/zod";
const zodSchemas = generateZodSchemas(schema);
// Use directly
zodSchemas.todos.parse(data);
zodSchemas.users.parse(data);Protocol Schemas
Pre-built schemas for RippleDB protocol types:
| Schema | Description |
|---|---|
hlcSchema | HLC timestamp format ({wallMs}:{counter}:{nodeId}) |
changeKindSchema | 'upsert' | 'delete' |
changeSchema | Generic change with unknown patch |
pullRequestSchema | Pull request body |
pullResponseSchema | Pull response body |
appendRequestSchema | Append request body |
appendResultSchema | Append result body |
Typed Change Schema
For typed change validation:
import { z } from "zod";
import { createChangeSchema } from "@rippledb/zod";
const todoPatchSchema = z.object({
id: z.string(),
title: z.string(),
done: z.boolean().optional(),
});
const todoChangeSchema = createChangeSchema(todoPatchSchema);Roadmap
- Per-entity patch validation (so
entity: "todos"validatespatchagainst thetodosschema automatically): see GitHub issue #7.