Dynz.

Getting started

Build a real schema, validate data, and see how dynz works end to end.

This guide walks through building a registration form schema from scratch. By the end you'll understand the three things dynz does: define, validate, and react.

1. Define a schema

A schema describes the shape and rules of your data. Use the fluent builders to compose it:

import * as d from 'dynz';

const registrationSchema = d.object({
  email:           d.string().email(),
  password:        d.string().min(8),
  confirmPassword: d.string().equals(d.ref('password'), 'Passwords must match'),
  plan:            d.options(['free', 'pro', 'enterprise']),
  companyName:     d.string().min(2)
                    .setIncluded(d.eq(d.ref('plan'), 'enterprise')),
});

A few things to notice:

  • d.ref('password') references another field's value. The confirmPassword rule evaluates against the live value of password — not a snapshot.
  • companyName has setIncluded(...) with a predicate. The field only exists in the schema when plan === 'enterprise'. When it isn't included, submitting a value for it is an error.
  • The whole thing is a plain object — no closures, no hidden state.

2. Validate data

Pass a schema, optional current values, and new values to validate():

const result = await d.validate(registrationSchema, undefined, {
  email:           'alice@example.com',
  password:        'hunter42!',
  confirmPassword: 'hunter42!',
  plan:            'free',
});

if (result.success) {
  console.log(result.values);
  // { email: 'alice@example.com', password: 'hunter42!', ... }
} else {
  console.log(result.errors);
  // [{ path: '$.email', code: 'email', message: '...' }]
}

The second argument (undefined here) is the current value — used to enforce mutability rules. Pass undefined for creates, pass the existing record for updates.

Handling errors

Each error object has a path, code, and message:

if (!result.success) {
  for (const error of result.errors) {
    console.log(error.path);    // '$.confirmPassword'
    console.log(error.code);    // 'equals'
    console.log(error.message); // 'A required value is missing...'
  }
}

Use error.path to map errors back to specific fields in your UI.

3. Send it over the wire

Schemas are plain objects — serialize them with JSON.stringify and restore them with JSON.parse. This means you can generate schemas on the server and evaluate them anywhere:

// Server — generate and send
import * as d from 'dynz';

export async function GET(req: Request) {
  const schema = buildRegistrationSchema(req);
  return Response.json(schema);
}

// Client — receive and validate
const schema = await fetch('/api/schema').then(r => r.json());
const result = await d.validate(schema, undefined, formValues);

Because schemas are data, not code, the client doesn't need to import any schema definitions. The schema is the API contract.

4. Infer TypeScript types

Use SchemaValues to derive the exact TypeScript type for your schema's output:

import * as d from 'dynz';

const registrationSchema = d.object({
  email:    d.string().email(),
  password: d.string().min(8),
  plan:     d.options(['free', 'pro', 'enterprise'] as const),
});

type Registration = d.SchemaValues<typeof registrationSchema>;
// {
//   email:    string;
//   password: string;
//   plan:     'free' | 'pro' | 'enterprise';
// }

Optional fields (setRequired(false) or .optional()) become field?: T | undefined automatically.

Next steps

On this page