Dynz.

Type inference

Derive TypeScript types from your schema with SchemaValues — no duplication, no drift.

One of dynz's core guarantees is that your TypeScript types and your runtime validation stay in sync. The SchemaValues<T> utility type derives the exact value type directly from the schema definition.

SchemaValues<T>

import * as d from 'dynz';

const schema = d.object({
  name:  d.string().min(1),
  age:   d.number().min(0),
  email: d.string().email().optional(),
  tags:  d.array(d.string()),
});

type User = d.SchemaValues<typeof schema>;
// {
//   name:  string;
//   age:   number;
//   email: string | undefined;
//   tags:  string[];
// }

The type is derived — you never write it by hand. Change the schema, and the type changes automatically.

Required and optional fields

Fields are required by default. .optional() (or .setRequired(false)) makes the field optional in the inferred type:

const schema = d.object({
  title:    d.string(),           // required → string
  subtitle: d.string().optional(), // optional → string | undefined
});

type Post = d.SchemaValues<typeof schema>;
// { title: string; subtitle?: string | undefined }

When setRequired receives a predicate rather than false, the field becomes T | undefined because its requirement depends on runtime values:

const schema = d.object({
  plan:    d.options(['free', 'pro']),
  coupon:  d.string().setRequired(d.eq(d.ref('plan'), 'pro')),
});

type Subscription = d.SchemaValues<typeof schema>;
// { plan: 'free' | 'pro'; coupon: string | undefined }
//                                              ↑ unknown at compile time

Options and enums

d.options() infers the union of all allowed values when you use as const:

const schema = d.object({
  status: d.options(['draft', 'published', 'archived'] as const),
  rating: d.options([1, 2, 3, 4, 5] as const),
});

type Article = d.SchemaValues<typeof schema>;
// {
//   status: 'draft' | 'published' | 'archived';
//   rating: 1 | 2 | 3 | 4 | 5;
// }

Nested objects and arrays

Nesting is fully typed at every depth:

const schema = d.object({
  user: d.object({
    profile: d.object({
      name:   d.string(),
      avatar: d.file().optional(),
    }),
    roles: d.array(d.options(['admin', 'editor', 'viewer'] as const)),
  }),
});

type Data = d.SchemaValues<typeof schema>;
// {
//   user: {
//     profile: {
//       name:   string;
//       avatar: File | undefined;
//     };
//     roles: Array<'admin' | 'editor' | 'viewer'>;
//   };
// }

Discriminated unions

Each member of a discriminated union produces its own branch:

const schema = d.discriminatedUnion('method', [
  { method: 'card',  cardNumber: d.string(), cvv: d.string() },
  { method: 'paypal', paypalEmail: d.string().email() },
]);

type Payment = d.SchemaValues<typeof schema>;
// { method: 'card';   cardNumber: string; cvv: string }
// | { method: 'paypal'; paypalEmail: string }

Using the inferred type

Once you have the inferred type, use it anywhere you'd use a manually written interface:

const schema = d.object({
  name:  d.string().min(1),
  email: d.string().email(),
});

type User = d.SchemaValues<typeof schema>;

// Use in function signatures
function createUser(data: User) { /* ... */ }

// Use in React components
function UserForm({ defaultValues }: { defaultValues?: Partial<User> }) { /* ... */ }

// Use in API route handlers
export async function POST(req: Request) {
  const body = await req.json();
  const result = await d.validate(schema, undefined, body);

  if (result.success) {
    // result.values is typed as User
    await db.users.create(result.values);
  }
}

Type-safe validation results

validate() is generic — the result type matches the schema:

const result = await d.validate(schema, undefined, body);

if (result.success) {
  // result.values: User  ← fully typed
  result.values.name  // string ✓
  result.values.email // string ✓
}

Sharing types across packages

Since SchemaValues<T> is a utility type, the schema itself is the shared source. Define it once (e.g. in a shared package), and derive both the runtime validation and the TypeScript type from it:

// packages/shared/src/schemas.ts
import * as d from 'dynz';

export const userSchema = d.object({
  name:  d.string().min(1),
  email: d.string().email(),
});

export type User = d.SchemaValues<typeof userSchema>;

// packages/api/src/handler.ts
import { userSchema, type User } from '@my-org/shared/schemas';

// packages/web/src/forms/UserForm.tsx
import { userSchema, type User } from '@my-org/shared/schemas';

Frontend and backend stay in sync automatically. Changing the schema propagates to both.

On this page