nc.schema
Validate data with schemas, in the spirit of zod. Describe your data once and get both a runtime check with clear messages and the TypeScript type, Infer<typeof schema>. Schemas never change: .min() or .optional() return a new one.
const { schema } = require("@ix-xs/node-comfort");
import { string } from "@ix-xs/node-comfort/schema";const User = s.object({
name: s.string().trim().min(2),
email: s.string().email().toLowerCase(),
age: s.number().int().min(18).optional(),
role: s.enum(["admin", "user"]).default("user"),
});
const user = User.parse(req.body); // typed, or a ValidationError listing every issue
const result = User.safeParse(input); // { success, data } or { success, error }nc.schema.Functions
nc.schema.string()
string(options?: { message?: Message; } | undefined): StringSchemaA string schema.
Parameters
| Name | Type | Description |
|---|---|---|
optionsoptional | { message?: Message } | Message when the value isn't a string. |
Returns StringSchema
Example
s.string().trim().min(1).max(100);
s.string().email();nc.schema.number()
number(options?: { message?: Message; } | undefined): NumberSchemaA number. NaN and Infinity are refused.
Parameters
| Name | Type | Description |
|---|---|---|
optionsoptional | { message?: Message } | Message when the value isn't a number. |
Returns NumberSchema
Example
s.number().int().min(0).max(120);nc.schema.boolean()
boolean(options?: { message?: Message; } | undefined): BooleanSchemaA boolean schema.
Parameters
| Name | Type |
|---|---|
optionsoptional | { message?: Message } |
Returns BooleanSchema
nc.schema.bigint()
bigint(options?: { message?: Message; } | undefined): BigIntSchemaA bigint schema.
Parameters
| Name | Type |
|---|---|
optionsoptional | { message?: Message } |
Returns BigIntSchema
nc.schema.date()
date(options?: { message?: Message; } | undefined): DateSchemaA valid Date.
Parameters
| Name | Type |
|---|---|
optionsoptional | { message?: Message } |
Returns DateSchema
Example
s.date().min("2024-01-01");nc.schema.literal()
literal<L extends string | number | boolean | bigint | null | undefined>(value: L): LiteralSchema<L>Exactly this value.
Parameters
| Name | Type |
|---|---|
value | L |
Returns LiteralSchema<L>
Example
s.object({ type: s.literal("circle"), radius: s.number() });nc.schema.enum()
enum<V extends string | number>(values: readonly V[], options?: { message?: Message; } | undefined): LiteralSchema<V>One of these values. The type is their union.
Parameters
| Name | Type |
|---|---|
values | readonly V[] |
optionsoptional | { message?: Message } |
Returns LiteralSchema<V>
Example
const Role = s.enum(["admin", "editor", "viewer"]); // Schema<"admin" | "editor" | "viewer">
Role.options; // ["admin", "editor", "viewer"]nc.schema.array()
array<T>(item: Schema<T>): ArraySchema<T>An array whose items all match item.
Parameters
| Name | Type |
|---|---|
item | Schema<T> |
Returns ArraySchema<T>
Example
s.array(s.string()).min(1).max(10).unique();nc.schema.object()
object<S extends Shape>(shape: S): ObjectSchema<S>An object with known keys. Unknown keys are dropped; .strict() rejects them and .passthrough() keeps them.
Parameters
| Name | Type |
|---|---|
shape | S |
Returns ObjectSchema<S>
Example
const Address = s.object({ street: s.string(), zip: s.string().regex(/^\d{5}$/), city: s.string() });nc.schema.union()
union<U extends ReadonlyArray<Schema<any>>>(options: U): UnionSchema<Infer<U[number]>>Matches any of these schemas; the first that fits wins.
Parameters
| Name | Type |
|---|---|
options | U |
Returns UnionSchema<Infer<U[number]>>
Example
s.union([s.string(), s.number()]); // Schema<string | number>nc.schema.tuple()
tuple<T extends [Schema<any>, ...Schema<any>[]] | []>(items: T): TupleSchema<{ -readonly [K in keyof T]: Infer<T[K]>; }>A fixed-length array with one schema per position.
Parameters
| Name | Type |
|---|---|
items | T |
Returns TupleSchema<{ -readonly [K in keyof T]: Infer<T[K]> }>
Example
s.tuple([s.number(), s.number()]); // Schema<[number, number]>nc.schema.record()
record<V>(values: Schema<V>, keys?: Schema<string>): RecordSchema<V>An object with any keys whose values match a schema, like a dictionary.
Parameters
| Name | Type |
|---|---|
values | Schema<V> |
keysoptional | Schema<string> |
Returns RecordSchema<V>
Example
s.record(s.number()); // Record<string, number>
s.record(s.boolean(), s.string().regex(/^[a-z]+$/)); // keys checked toonc.schema.lazy()
lazy<T>(getter: () => Schema<T>): LazySchema<T>A schema defined later, for recursive data like trees.
Parameters
| Name | Type |
|---|---|
getter | () => Schema<T> |
Returns LazySchema<T>
Example
const Category = s.object({
name: s.string(),
children: s.lazy(() => s.array(Category)).default([]),
});nc.schema.instanceOf()
instanceOf<C extends new (...args: any[]) => any>(ctor: C, message?: Message): CustomSchema<InstanceType<C>>An instance of a class.
Parameters
| Name | Type |
|---|---|
ctor | C |
messageoptional | Message |
Returns CustomSchema<InstanceType<C>>
Example
s.instanceOf(URL);nc.schema.custom()
custom<T = unknown>(test: ((value: unknown) => value is T) | ((value: unknown) => boolean), message?: Message): CustomSchema<T>A schema from any test, usually a type guard.
Parameters
| Name | Type |
|---|---|
test | ((value: unknown) => value is T) | ((value: unknown) => boolean) |
messageoptional | Message |
Returns CustomSchema<T>
Example
const Color = s.custom(nc.isHexColor, "Expected a hex color");nc.schema.any()
any(): Schema<any>Anything, typed as any.
Returns Schema<any>
nc.schema.unknown()
unknown(): Schema<unknown>Anything, typed as unknown.
Returns Schema<unknown>
nc.schema.coerceproperty
coerce: CoerceBuildersSchemas that convert their input before checking it. Query strings, form fields and environment variables are always strings, so this is what you want for them.
nc.schema.Schemaproperty
Schema: typeof SchemaThe base of every schema.
Types
Import any of them in TypeScript with import type { CoerceBuilders } from "@ix-xs/node-comfort", or in JavaScript with import("@ix-xs/node-comfort").CoerceBuilders.
CoerceBuilders
The s.coerce builders.
| Property | Type | Description |
|---|---|---|
string | () => StringSchema | A string schema that turns any value into a string first. |
number | () => NumberSchema | A number schema that converts strings like "42", booleans and dates first. |
boolean | () => BooleanSchema | A boolean schema that understands "true", "1", "yes", "on" and their opposites. |
date | () => DateSchema | A date schema that converts ISO strings and timestamps. |
bigint | () => BigIntSchema | A bigint schema that converts numeric strings and integers. |
Infer
The type a schema produces.
type Infer = S extends Schema<infer T> ? T : neverMessage
An error message: a string, or a function that gets the invalid value.
type Message = string | ((value: any) => string)ObjectOutput
The type of an object schema. Keys that accept undefined become optional.
ParseContext
| Property | Type | Description |
|---|---|---|
issues | ValidationIssue[] | |
path | (string | number)[] |
RefineOptions
Options for refine().
| Property | Type | Description |
|---|---|---|
messageoptional | Message | Defaults to "Invalid value". |
pathoptional | (string | number)[] | Where to report the issue, like ["confirmPassword"] for a cross-field check. |
codeoptional | string | Defaults to "custom". |
SafeParseResult
What safeParse() returns.
type SafeParseResult = { success: true; data: T; error?: undefined; } | { success: false; data?: undefined; error: ValidationError; }Shape
An object of schemas, as passed to object().
type Shape = Record<string, Schema<any>>Simplify
Flattens a type so editors show it nicely.
type Simplify = { [K in keyof T]: T[K]; } & {}Step
A step in a schema: a check, a transformation or a refinement.
type Step = { kind: "check"; test: (value: any) => boolean; code: string; message: Message; details?: Record<string, unknown>; } | { kind: "transform"; fn: (value: any) => any; } | { kind: "refine"; test: (value: any) => boolean; options: RefineOptions; }