npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dynz

v0.0.19

Published

A powerful TypeScript schema validation with advanced conditional logic, cross-field validation, and static type inference.

Readme

dynz

License: MIT CI NPM version Downloads

Dynz is a headless schema library that replaces scattered form logic with a single TypeScript definition any consumer can evaluate — a Frontend, a Backend, an LLM, or an external system. Conditions, validation rules, and derived values are expressed as data, not code, making schemas portable, testable, and channel-agnostic by design.

Features

  • Headless by design — no UI components, no rendering opinions. Bring your own renderer and wire it to the schema.
  • Serializable expression system — conditions, validation rules, and derived values are data structures, not JavaScript functions. Schemas survive a JSON round-trip and can be evaluated in any runtime.
  • Cross-field expressions — d.ref(), d.multiply(), d.add() and friends compose freely inside any rule slot, making inter-field dependencies first-class rather than bolted on via refine.
  • Runtime schema generation — schemas are plain TypeScript objects, designed to be generated by a NestJS service at request time based on user context, product state, or business rules.
  • Universal consumer support — the same schema drives a web UI, a mobile app, an AI agent via MCP, or a third-party integration without modification.

Installation

npm install dynz
# or
pnpm add dynz
# or
yarn add dynz

Quick Start

import * as d from "dynz";

// Define a schema
const userSchema = d.object({
  name: d.string().min(2),
  email: d.string().email(),
  password: d.string().min(6),
  passwordConfirm: d.string().equals(d.ref("password")),
});

// Send it to the external system (e.g. frontend)
return Response.json(userSchema);

// Get the response from the request
const schema = await response.json();

// Validate the response
const result = d.validate(schema, undefined, {
  name: "John Doe",
  email: "[email protected]",
  password: "MyPassword",
  passwordConfirm: "MyPassword",
});

if (result.success) {
  console.log("Valid data:", result.values);
} else {
  console.log("Validation errors:", result.errors);
}

Core Concepts

Schema Types

dynz supports comprehensive schema types for all common data structures:

import * as d from "dynz";

// String schema with validation rules
const nameSchema = d.string().min(2).max(5).regex("^[a-zA-Z\\s]+$");

// Number schema with constraints
const ageSchema = d.number().min(0).max(120);

// Object schema with nested fields
const userSchema = d.object({
  profile: d.object({
    name: d.string().min(1),
    bio: d.string().optional(),
  }),
});

// Array schema
const tagsSchema = d.array(d.string().min(1)).min(5).max(20);

Validation Rules

Extensive validation rules for precise data validation:

import * as d from "dynz";

const productSchema = d.object({
  name: d.string().min(1).max(100),
  email: d.string().email(),
  price: d.number().min(0),
  category: d.options(["electronics", "books", "clothing"]),
  sku: d.string().regex("^[A-Z]{3}-\\d{4}$"),
  quantity: d.string().isNumeric(),
});

Conditional Logic

dynz excels at dynamic validation based on other field values:

import * as d from "dynz";

const userSchema = d.object({
  accountType: d.options(["personal", "business"]),
  industry: d.options(["finance", "healthcare", "tech"]),

  // Included only for business accounts
  companyName: d
    .string()
    .min(2)
    .setIncluded(d.eq(d.ref("accountType"), "business")),

  // Different validation rules based on account type
  email: d
    .string()
    .email()
    .when(d.eq(d.ref("accountType"), "business"), (b) =>
      b.regex(
        "@company\\.com$",
        undefined,
        "Business accounts must use company email",
      ),
    ),

  // Complex conditional logic
  specialField: d
    .string()
    .setRequired(
      d.and(
        d.eq(d.ref("accountType"), "business"),
        d.or(
          d.eq(d.ref("industry"), "finance"),
          d.eq(d.ref("industry"), "healthcare"),
        ),
      ),
    ),
});

Cross-Field References

Reference other fields in validation rules:

import * as d from "dynz";

const signupSchema = d.object({
  password: d.string().min(8),
  confirmPassword: d.string().equals(d.ref("password"), "Passwords must match"),
  birthYear: d
    .date()
    .after(new Date(new Date().setFullYear(new Date().getFullYear() - 18))),
  graduatedAt: d
    .date()
    .after(d.ref("birthYear"), "Graduation date must be after birth year"),
});

Mutability Controls

Control when fields can be modified based on conditions:

import * as d from "dynz";

function buildSchema(user: { role: "admin" | "user" }) {
  return d.object({
    status: d.options(["draft", "published"]),

    // mutability as a constant, but dynamically generated
    title: d
      .string()
      .min(1)
      .setMutable(user.role === "admin"),

    // mutability as a predicate; resolved in the schema itself
    content: d.string().setMutable(d.eq(d.ref("status"), "draft")),

    // mutability as a constant
    createdAt: d.string().setMutable(false),
  });
}

Mutability on array schemas

You can also control mutability on inner schemas of an array. This allows you to add new elements or remove elements, but not to mutate elements on the same index.

import * as d from "dynz";

// The array is mutable, but each element is immutable
const schema = d.array(d.string().setMutable(false));

d.validate(schema, [], ["foo"]); // Validates successfully, because it's a new entry

d.validate(schema, ["foo"], []); // Validates successfully, because an entry is removed

d.validate(schema, ["foo"], ["bar"]); // Returns an error since 'foo' is mutated into 'bar'

Field Inclusion

Dynamically include or exclude fields:

import * as d from "dynz";

const registrationSchema = d.object({
  dietryRestrictions: d.boolean(),
  dietryDetail: d.string().setIncluded(d.eq(d.ref("dietryRestrictions"), true)),
});

Advanced Usage

Custom Rules

Create reusable custom validation logic:

import * as d from "dynz";

const passwordStrengthRule = d.custom("passwordStrength", {
  minScore: 4,
  requireSpecialChars: true,
});

const passwordStrengthRuleValidator: d.CustomRuleFunction = (value, params) => {
  // ... validation logic
};

// Use custom() to add arbitrary rules to a fluent chain
const strongPasswordSchema = d
  .string()
  .min(8)
  .custom("passwordStrength", { minScore: 4, requireSpecialChars: true });

d.validate(strongPasswordSchema, undefined, "myStrongPassword", {
  customRules: {
    passwordStrength: passwordStrengthRuleValidator,
  },
});

Complex Conditional Schemas

import * as d from "dynz";

const orderSchema = d.object({
  orderType: d.options(["standard", "express", "international"]),

  shippingMethod: d
    .options(["overnight", "same-day", "air", "sea"])
    .when(d.eq(d.ref("orderType"), "express"), (b) =>
      b.oneOf(["overnight", "same-day"]),
    )
    .when(d.eq(d.ref("orderType"), "international"), (b) =>
      b.oneOf(["air", "sea"]),
    )
    .setRequired(
      d.or(
        d.eq(d.ref("orderType"), "express"),
        d.eq(d.ref("orderType"), "international"),
      ),
    ),

  customsInfo: d
    .object({
      value: d.number().min(0),
      description: d.string().min(1),
    })
    .setIncluded(d.eq(d.ref("orderType"), "international")),
});

Mutable Conditions in Practice

import * as d from "dynz";

// Order management with status-based mutability
const orderSchema = d.object({
  orderStatus: d.options(["draft", "pending", "send"]).setMutable(false), // always immutable

  items: d
    .array(d.string())
    .setMutable(
      d.or(
        d.eq(d.ref("orderStatus"), "draft"),
        d.eq(d.ref("orderStatus"), "pending"),
      ),
    ),

  shippingAddress: d
    .string()
    .setMutable(
      d.or(
        d.eq(d.ref("orderStatus"), "draft"),
        d.eq(d.ref("orderStatus"), "pending"),
      ),
    ),
});

API Reference

Schema Builders

  • string() - String validation schema with fluent methods
  • number() - Number validation schema with fluent methods
  • boolean() - Boolean validation schema with fluent methods
  • object(fields) - Object schema with nested field schemas
  • array(schema) - Array schema with item validation
  • date() - Date validation schema with fluent methods
  • options(values) - Enum-like validation for predefined values
  • file() - File validation schema with fluent methods

Fluent Rule Methods

String schemas:

  • .min(value, code?) - Minimum string length
  • .max(value, code?) - Maximum string length
  • .email(code?) - Email format validation
  • .regex(pattern, flags?, code?) - Regular expression validation
  • .equals(value, code?) - Exact value matching
  • .oneOf(values, code?) - Must be one of specified values
  • .isNumeric(code?) - Numeric string validation

Number schemas:

  • .min(value, code?) - Minimum numeric value
  • .max(value, code?) - Maximum numeric value
  • .maxPrecision(value, code?) - Maximum decimal precision

Date schemas:

  • .before(value, code?) - Date must be before value
  • .after(value, code?) - Date must be after value
  • .minDate(value, code?) - Minimum date (inclusive)
  • .maxDate(value, code?) - Maximum date (inclusive)

Array schemas:

  • .min(value, code?) - Minimum array length
  • .max(value, code?) - Maximum array length

Property Setters (all schema types)

  • .setRequired(value) - Mark as required (boolean or predicate)
  • .optional() - Shorthand for .setRequired(false)
  • .setMutable(value) - Control field mutability (boolean or predicate)
  • .setIncluded(value) - Control field inclusion (boolean or predicate)
  • .setPrivate(value) - Mark as private/masked
  • .setCoerce(value) - Enable automatic type coercion
  • .setDefault(value) - Set default value

Conditional Rules

  • .when(predicate, callback) - Apply rules conditionally

Predicates

Predicates return a boolean and are used wherever a schema accepts a conditional expression: .setRequired(), .setMutable(), .setIncluded(), and .when().

Comparison:

  • eq(left, right) - Equals (===)
  • neq(left, right) - Not equals (!==)
  • gt(left, right) - Greater than (>)
  • gte(left, right) - Greater than or equal (>=)
  • lt(left, right) - Less than (<)
  • lte(left, right) - Less than or equal (<=)
  • matches(value, pattern, flags?) - Regex pattern matching

Collection:

  • isIn(value, array) - Value is contained in array
  • isNotIn(value, array) - Value is not contained in array

Logical combinators:

  • and(...predicates) - All predicates must be true
  • or(...predicates) - At least one predicate must be true

Transformers

Transformers compute a value and can be used as an input to any predicate or validation rule.

Arithmetic:

  • sum(...values) - Add values together
  • sub(...values) - Subtract values left to right
  • multiply(...values) - Multiply values together
  • divide(...values) - Divide values left to right
  • min(...values) - Smallest of the given values
  • max(...values) - Largest of the given values

Math:

  • ceil(value) - Round up to nearest integer
  • floor(value) - Round down to nearest integer
  • sin(value) - Sine
  • cos(value) - Cosine
  • tan(value) - Tangent

Utility:

  • age(dateValue) - Age in years derived from a date value
  • size(value) - Length of a string or array, size in bytes of a file
  • lookup(value, table) - Map a value through a lookup table

Helpers

  • ref(path) - Reference another field's value
  • v(value) - Wrap a static/constant value

Validation

  • validate(schema, currentValues?, newValues, options?) - Main validation function
  • validateMutable option - Check field mutability constraints (defaults to true)
  • customRules option - Provide custom rule implementations

Type Safety

dynz provides excellent TypeScript integration:

import * as d from "dynz";

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

// Inferred type: { name: string; age?: number; tags: string[] }
type UserData = d.SchemaValues<typeof schema>;

// Type-safe validation results
const result = d.validate(schema, undefined, {
  name: "John",
  tags: ["dynz"],
});

if (result.success) {
  // result.values is properly typed as UserData
  console.log(result.values.name); // ✅ Type-safe access
}

Examples

Check out the /examples directory for complete working examples:

  • Next.js Example - React forms with dynz schemas

Advanced Features

Complex Business Logic

import * as d from "dynz";

const loanApplicationSchema = d.object({
  applicantType: d.options(["individual", "business"]),

  income: d
    .number()
    .min(0)
    .setIncluded(d.eq(d.ref("applicantType"), "individual")),

  businessRevenue: d
    .number()
    .min(0)
    .setIncluded(d.eq(d.ref("applicantType"), "business")),

  loanAmount: d
    .number()
    .min(1000)
    // Can't exceed annual income for individuals
    .when(d.eq(d.ref("applicantType"), "individual"), (b) =>
      b.max(d.ref("income")),
    )
    // Can't exceed annual revenue for businesses
    .when(d.eq(d.ref("applicantType"), "business"), (b) =>
      b.max(d.ref("businessRevenue")),
    ),
});

Workflow Management

import * as d from "dynz";

const documentWorkflowSchema = d.object({
  content: d
    .string()
    .setMutable(
      d.and(
        d.eq(d.ref("status"), "draft"),
        d.or(
          d.eq(d.ref("isOwner"), true),
          d.eq(d.ref("hasEditPermission"), true),
        ),
      ),
    ),

  status: d
    .options(["draft", "review", "approved", "published"])
    .setMutable(
      d.or(
        d.and(
          d.eq(d.ref("currentStatus"), "draft"),
          d.eq(d.ref("isOwner"), true),
        ),
        d.and(
          d.eq(d.ref("currentStatus"), "review"),
          d.eq(d.ref("userRole"), "reviewer"),
        ),
        d.eq(d.ref("userRole"), "admin"),
      ),
    ),
});

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

MIT © dynz

Related Packages

  • More framework integrations coming soon...

Built with ❤️ for type-safe validation