Parse String Schemas
▶ Open in PlaygroundUse 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:
| Method | Effect |
|---|---|
.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 PlaygroundThe 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 dateNon-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.