Why @cleverbrush/schema?

One schema definition that drives your types, validation, API contracts, forms, and documentation — with zero duplication.

The Problem

In a typical TypeScript project, types and runtime validation live in separate worlds. You define a User interface in one file, then write validation rules in another — using Joi, Yup, Zod, or manual if checks. Over time these drift apart: the type says a field is required, but the validator allows undefined. Tests pass, but production breaks because the validation didn't match the type.

Then you need the same shape in your API contract, your form fields, your OpenAPI spec, and your object mapper. Each concern re-describes the same data shape — and each is another place for things to go wrong.

The Solution: Define Once, Use Everywhere

@cleverbrush/schema lets you define a schema once and derive everything from it:

  • TypeScript types — via InferType<typeof MySchema>, no duplicate interfaces
  • Runtime validation — with typed per-field error access
  • API contracts — define endpoints that reference schemas, generate typed clients automatically
  • React forms — headless form generation from schema PropertyDescriptors
  • Object mapping — type-safe mapping between two schemas with compile-time completeness checks
  • JSON Schema / OpenAPI — bidirectional conversion with toJsonSchema() and fromJsonSchema()
import { object, string, number, type InferType } from '@cleverbrush/schema';

// Define once
const UserSchema = object({
  name:  string().minLength(2).describe('Display name'),
  email: string().email().describe('Primary email'),
  age:   number().min(0).max(150)
});

// ── Types ─────────────────────────────────────────────
type User = InferType<typeof UserSchema>;
// { name: string; email: string; age: number }

// ── Validation ────────────────────────────────────────
const result = UserSchema.validate(untrustedInput);
if (!result.valid) {
  const emailErrors = result.getErrorsFor(u => u.email); // typed!
}

// ── The same schema powers mapper, react-form, JSON Schema, server endpoints…

One Schema, Many Consumers

The schema sits at the center of an ecosystem. Each companion package reads the schema's introspection data — no adapters, no code generation, no drift.

@cleverbrush/schema
@cleverbrush/mapperType-safe object mapping
@cleverbrush/react-formSchema-driven React forms
@cleverbrush/schema-jsonBidirectional JSON Schema
@cleverbrush/serverType-safe endpoints & auto-typed clients
@cleverbrush/server-openapiOpenAPI spec generation

What No Other Schema Library Offers

If you know Zod, you already know most of the API. These three capabilities are what set @cleverbrush/schema apart:

1. Type-safe Extension System

Add your own methods to schema builders — fully typed, autocomplete-ready. The built-in .email(), .url(), .uuid() methods use the same public extension API. No other schema library supports this.

Learn more about Extensions →

2. Generic / Parameterized Schemas

Create schema templates with type parameters using generic(). Apply them with .apply() to produce concrete schemas — with full introspection preserved. Competitors require plain functions that lose schema metadata.

Learn more about Generic Schemas →

3. PropertyDescriptors & Typed Field Navigation

Every schema exposes a typed property descriptor tree. Navigate fields with arrow functions like u => u.address.city — TypeScript verifies the path at compile time. This powers the mapper, react-form auto-generation, and typed error access. No magic strings, no runtime surprises.

Learn more about PropertyDescriptors →

Production Tested

Every form, every API response mapping, and every validation rule in cleverbrush.com/editor is powered by @cleverbrush/schema. It handles hundreds of schemas with nested objects, async validators, and custom error messages in production every day.

Next Steps