Parse String Schemas

▶ Open in Playground

Use parseString(objectSchema, templateFn) to validate a string against a template pattern and parse it into a strongly-typed object. The template expression uses a tagged-template syntax with type-safe property selectors — you get full IntelliSense in the selector lambdas.

import { parseString, object, string, number, type InferType } from '@cleverbrush/schema';

const RouteSchema = parseString(
    object({ userId: string().uuid(), id: number().coerce() }),
    $t => $t`/orders/${t => t.id}/${t => t.userId}`
);

type Route = InferType<typeof RouteSchema>;
// { userId: string; id: number }

const result = RouteSchema.validate('/orders/42/550e8400-e29b-41d4-a716-446655440000');
// result.valid === true
// result.object === { id: 42, userId: '550e8400-...' }

Nested Objects

Navigate deep properties via t => t.parent.child. The builder automatically constructs the nested result object:

const schema = parseString(
    object({
        order: object({ id: number().coerce() }),
        user:  object({ name: string() })
    }),
    $t => $t`/orders/${t => t.order.id}/by/${t => t.user.name}`
);
// InferType → { order: { id: number }; user: { name: string } }

schema.validate('/orders/42/by/Alice');
// { valid: true, object: { order: { id: 42 }, user: { name: 'Alice' } } }

Coercion

Captured segments are always strings. Property schemas handle their own coercion — use .coerce() on number(), boolean(), or date() to convert from strings:

const LogEntry = parseString(
    object({
        level:   string(),
        ts:      date().coerce(),
        message: string()
    }),
    $t => $t`[${t => t.level}] ${t => t.ts} ${t => t.message}`
);

Error Messages

Errors include the property path for easy debugging. Use { doNotStopOnFirstError: true } to collect all segment errors at once:

const result = RouteSchema.validate('/orders/abc/bad-uuid');
// result.errors[0].message → "id: expected an integer number"

Fluent Methods

All standard modifiers work and preserve type inference:

MethodEffect
.optional()Accepts undefined and null
.nullable()Accepts null
.default(value)Falls back to value when undefined
.brand(name)Adds a type-level brand
.readonly()Marks result as Readonly<T>

Coercion

▶ Open in Playground

The number(), boolean(), and date() builders each have a .coerce() method that adds a preprocessor to convert string values to the target type. This is especially useful with parse-string schemas, but also works standalone for URL parameters, form inputs, or any other string source.

import { number, boolean, date } from '@cleverbrush/schema';

// number().coerce() — uses Number(value)
number().coerce().validate('42');    // { valid: true, object: 42 }
number().coerce().validate('hello'); // { valid: false } — NaN fails

// boolean().coerce() — "true" → true, "false" → false
boolean().coerce().validate('true');  // { valid: true, object: true }
boolean().coerce().validate('yes');   // { valid: false } — unrecognized

// date().coerce() — new Date(value) if valid
date().coerce().validate('2024-01-15'); // { valid: true, object: Date }
date().coerce().validate('nope');       // { valid: false } — invalid date

Non-string values pass through unchanged, so .coerce() is safe to chain even when the input might already be the correct type. All three methods return a new immutable schema instance.