RippleDB
RippleDB
Reference

@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 zod
npm install @rippledb/zod @rippledb/core zod
yarn add @rippledb/zod @rippledb/core zod

withZod() - 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:

SchemaDescription
hlcSchemaHLC timestamp format ({wallMs}:{counter}:{nodeId})
changeKindSchema'upsert' | 'delete'
changeSchemaGeneric change with unknown patch
pullRequestSchemaPull request body
pullResponseSchemaPull response body
appendRequestSchemaAppend request body
appendResultSchemaAppend 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" validates patch against the todos schema automatically): see GitHub issue #7.

On this page