Dynz.

Serialization

Send dynz schemas over the wire, store them in a database, or generate them at request time on the server.

A dynz schema is a plain JavaScript object — no functions, no closures, no hidden state. This means you can serialize it to JSON and reconstruct it anywhere. The schema itself is the API contract.

serialize(schema)

The serialize helper is a thin wrapper around JSON.stringify:

import { serialize } from 'dynz';

const json = serialize(schema);
// '{"type":"object","fields":{"email":{"type":"string","rules":[...]},...}}'

You can also use JSON.stringify directly — the result is identical. serialize exists as a named export to make the intent explicit.

Sending a schema from server to client

The recommended pattern is to generate the schema on the server (where you know the user's role, plan, and feature flags) and send it to the client as JSON:

// app/api/schema/route.ts
import * as d from 'dynz';

export async function GET(req: Request) {
  const user = await getSessionUser(req);

  const schema = d.object({
    title:   d.string().min(1).max(200),
    content: d.string().min(1),

    // Only admins can set the featured flag
    featured: d.boolean()
      .setIncluded(user.role === 'admin'),

    // Publish date is only required for pro plans
    publishAt: d.date()
      .setRequired(user.plan === 'pro')
      .optional(),
  });

  return Response.json(schema);
}
// app/posts/new/page.tsx
const schema = await fetch('/api/schema').then(r => r.json());

// Use as-is with validate() — it works on the parsed JSON object
const result = await d.validate(schema, undefined, formData);

The client imports validate from dynz but doesn't import the schema. The schema is the data received from the server. No code-generation or type generation step needed.

Storing schemas in a database

Because schemas are JSON, you can store them in a jsonb column (Postgres), a document store, or any key-value store:

// Save a schema
await db.forms.create({
  id: 'contact-form',
  schema: JSON.stringify(contactSchema),
});

// Load and use it
const row = await db.forms.findById('contact-form');
const schema = JSON.parse(row.schema);
const result = await d.validate(schema, undefined, submission);

This enables use cases like:

  • Dynamic forms — build a form builder UI that saves schemas and renders them without deployments
  • Versioned validation — store the schema version that was active when a record was created, and replay validation against the original rules
  • Multi-tenant rules — store different schemas per tenant, each with different fields and constraints

What survives serialization

Everything in a dynz schema is serializable:

FeatureSerializes?Example
Schema types{ type: 'string' }
Rules{ type: 'min_length', min: 8 }
Static predicatesd.eq(d.ref('plan'), 'pro')
Cross-field referencesd.ref('startDate')
Transformersd.multiply(d.ref('qty'), d.ref('price'))
Conditional rulesd.when(predicate, ...)
Custom rule names + params{ type: 'custom', name: 'profanity' }
Custom rule implementationsFunctions are not serializable

Custom rule implementations are intentionally excluded — they're environment-specific and injected at validate() time via schemaOptions.customRules.

Round-trip example

import * as d from 'dynz';

const original = d.object({
  email: d.string().email(),
  plan:  d.options(['free', 'pro']),
  seats: d.number().min(1)
            .setIncluded(d.eq(d.ref('plan'), 'pro')),
});

// Serialize
const json = JSON.stringify(original);

// Deserialize
const restored = JSON.parse(json);

// Validate against the restored schema — identical behavior
const result = await d.validate(restored, undefined, {
  email: 'alice@example.com',
  plan:  'pro',
  seats: 5,
});

console.log(result.success); // true

On this page