Documentation Index
Fetch the complete documentation index at: https://docs.cbrock.dev/llms.txt
Use this file to discover all available pages before exploring further.
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:
| Field | Type | Description |
|---|
message | string | Human-readable description of the failure |
path | string[] | Full path from schema root, e.g. ['User', 'address', 'zip'] |
expected | string | Expected type or constraint, e.g. 'string' |
actual | string | Actual type received, e.g. 'number' |
schema | string | Name of the schema that produced the error |
name | string | Internal 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.
| Member | Type | Description |
|---|
errors | GuardErr[] | All collected validation errors |
schemaName | string | Name 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)' },
// ]
}
}
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;
}
}