Skip to main content
Individual guards call .parse() and fail on the first error. Schemas walk the entire structure and collect all errors with full path tracking.

Defining schemas

defineSchemas({ Name: guard })

Pass a map of names to guards. You can use any guard directly, or pass a plain object of guards (auto-wrapped as an object schema).
import { is, defineSchemas } from 'ts-chas/guard';

const schemas = defineSchemas({
  User: is.object({
    id:    is.string.uuid(),
    name:  is.string.min(1).max(100),
    email: is.string.trim().email,
    age:   is.number.int.gte(0).optional,
  }),
  // Plain record — equivalent to wrapping in is.object({ ... })
  Address: {
    street: is.string,
    city:   is.string,
    zip:    is.string.length(5),
  },
});

defineSchema(name, guard)

Creates a single named schema. Useful when you only need one.
import { is, defineSchema } from 'ts-chas/guard';

const UserSchema = defineSchema('User', is.object({
  name: is.string.min(1),
  age:  is.number.int.gte(0),
}));

Parsing and asserting

schema.parse(data)Result<T, GuardErr[]>

Validates data and returns Ok(T) on success or Err(GuardErr[]) containing every validation failure.
const result = schemas.User.parse(incomingData);

if (result.isOk()) {
  console.log(result.value.email); // string — typed
} else {
  for (const e of result.error) {
    console.log(`${e.path.join('.')}: ${e.message}`);
  }
}

schema.assert(data)T

Returns the validated value or throws AggregateGuardError with all collected errors.
try {
  const user = schemas.User.assert(req.body);
  // user is fully typed
} catch (e) {
  if (e instanceof AggregateGuardError) {
    console.log(e.errors);    // GuardErr[]
    console.log(e.format());  // { 'name': ['...'], 'address.zip': ['...'] }
  }
}

schema.is(data)boolean

Boolean type predicate — delegates to the underlying guard.
if (schemas.User.is(value)) {
  value; // typed as User
}

Type inference

InferSchema<typeof schemas.Name>

Extracts the validated output type from a schema.
import type { InferSchema } from 'ts-chas/guard';

type User = InferSchema<typeof schemas.User>;
// { id: string; name: string; email: string; age?: number }

typeof schemas.Name.$infer

Alternative syntax that avoids importing InferSchema.
type User = typeof schemas.User.$infer;

GuardErr

Each error in a Result<T, GuardErr[]> or AggregateGuardError.errors has this shape:
FieldTypeDescription
messagestringHuman-readable description of the failure
pathstring[]Full path from schema root, e.g. ['User', 'address', 'zip']
expectedstringExpected type or constraint, e.g. 'string'
actualstringActual type received, e.g. 'number'
schemastringName of the schema that produced the error
namestringInternal guard name, e.g. 'string.email'
import type { GuardErr } from 'ts-chas/guard';

const result = schemas.User.parse({ id: 'bad', name: '', email: 123 });
if (result.isErr()) {
  const errors: GuardErr[] = result.error;
  // errors[0].path     → ['User', 'id']
  // errors[0].message  → 'Value "bad" failed validation'
  // errors[0].expected → 'string'
}

AggregateGuardError

Thrown by schema.assert() when validation fails.
MemberTypeDescription
errorsGuardErr[]All collected validation errors
schemaNamestringName of the schema
format()() => Record<string, string[]>Returns a path-keyed map of messages
flatten()() => { path: string; message: string }[]Returns a flat array of { path, message }
import { AggregateGuardError } from 'ts-chas/guard';

try {
  schemas.User.assert(badData);
} catch (e) {
  if (e instanceof AggregateGuardError) {
    e.format();
    // {
    //   'name':        ['Value "" failed validation'],
    //   'address.zip': ['Expected string, but got number (12345)'],
    // }

    e.flatten();
    // [
    //   { path: 'name',        message: 'Value "" failed validation' },
    //   { path: 'address.zip', message: 'Expected string, but got number (12345)' },
    // ]
  }
}

formatErrors and flattenErrors

Standalone utilities for formatting GuardErr[] arrays outside of AggregateGuardError.
import { formatErrors, flattenErrors } from 'ts-chas/guard';

const result = schemas.User.parse(data);
if (result.isErr()) {
  formatErrors(result.error);
  // { 'email': ['Expected string, but got number (123)'] }

  flattenErrors(result.error);
  // [{ path: 'email', message: 'Expected string, but got number (123)' }]
}
The schema name prefix is stripped from all paths. Root-level errors (no nested path) use the key '_root'.

Complete example

import { is, defineSchemas, formatErrors, type InferSchema } from 'ts-chas/guard';

// 1. Define schemas
const schemas = defineSchemas({
  CreateUserRequest: is.object({
    name:     is.string.trim().min(1).max(100),
    email:    is.string.trim().toLowerCase().email.max(255),
    age:      is.number.int.between(0, 150).optional,
    role:     is.literal('admin', 'editor', 'viewer'),
    tags:     is.array(is.string.min(1)).max(10),
    address:  is.object({
      street: is.string.min(1),
      city:   is.string.min(1),
      zip:    is.string.regex(/^\d{5}(-\d{4})?$/),
    }),
  }),
});

// 2. Infer the type
type CreateUserRequest = InferSchema<typeof schemas.CreateUserRequest>;

// 3. Parse incoming data
function handleCreateUser(body: unknown) {
  const result = schemas.CreateUserRequest.parse(body);

  if (result.isErr()) {
    // Return structured validation errors to the client
    return {
      status: 400,
      errors: formatErrors(result.error),
      // {
      //   'email':          ['Expected string, but got number (123)'],
      //   'role':           ['Value "superuser" failed validation'],
      //   'address.zip':    ['Value "abc" failed validation'],
      // }
    };
  }

  const user = result.value;
  // user.email is string — already trimmed and lowercased
  // user.role  is 'admin' | 'editor' | 'viewer'
  return { status: 201, user };
}

// 4. Use assert when you want exceptions (e.g. in trusted internal code)
import { AggregateGuardError } from 'ts-chas/guard';

function mustBeValidUser(data: unknown): CreateUserRequest {
  try {
    return schemas.CreateUserRequest.assert(data);
  } catch (e) {
    if (e instanceof AggregateGuardError) {
      throw new Error(`Invalid user data: ${e.message}`);
    }
    throw e;
  }
}