node-comfortv2.0.0

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 }
GuideExplanations and examples for nc.schema.
Read the guide →

Functions

nc.schema.string()

string(options?: { message?: Message; } | undefined): StringSchema

A string schema.

Parameters

NameTypeDescription
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): NumberSchema

A number. NaN and Infinity are refused.

Parameters

NameTypeDescription
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): BooleanSchema

A boolean schema.

Parameters

NameType
optionsoptional{ message?: Message }

Returns BooleanSchema

nc.schema.bigint()

bigint(options?: { message?: Message; } | undefined): BigIntSchema

A bigint schema.

Parameters

NameType
optionsoptional{ message?: Message }

Returns BigIntSchema

nc.schema.date()

date(options?: { message?: Message; } | undefined): DateSchema

A valid Date.

Parameters

NameType
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

NameType
valueL

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

NameType
valuesreadonly 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

NameType
itemSchema<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

NameType
shapeS

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

NameType
optionsU

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

NameType
itemsT

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

NameType
valuesSchema<V>
keysoptionalSchema<string>

Returns RecordSchema<V>

Example

s.record(s.number());                                // Record<string, number>
s.record(s.boolean(), s.string().regex(/^[a-z]+$/)); // keys checked too

nc.schema.lazy()

lazy<T>(getter: () => Schema<T>): LazySchema<T>

A schema defined later, for recursive data like trees.

Parameters

NameType
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

NameType
ctorC
messageoptionalMessage

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

NameType
test((value: unknown) => value is T) | ((value: unknown) => boolean)
messageoptionalMessage

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

Schemas 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 Schema

The 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.

PropertyTypeDescription
string() => StringSchemaA string schema that turns any value into a string first.
number() => NumberSchemaA number schema that converts strings like "42", booleans and dates first.
boolean() => BooleanSchemaA boolean schema that understands "true", "1", "yes", "on" and their opposites.
date() => DateSchemaA date schema that converts ISO strings and timestamps.
bigint() => BigIntSchemaA bigint schema that converts numeric strings and integers.

Infer

The type a schema produces.

type Infer = S extends Schema<infer T> ? T : never

Message

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.

type ObjectOutput = Simplify<{ [K in keyof S as undefined extends Infer<S[K]> ? never : K]: Infer<S[K]>; } & { [K in keyof S as undefined extends Infer<S[K]> ? K : never]?: Infer<S[K]>; }>

ParseContext

PropertyTypeDescription
issuesValidationIssue[]
path(string | number)[]

RefineOptions

Options for refine().

PropertyTypeDescription
messageoptionalMessageDefaults to "Invalid value".
pathoptional(string | number)[]Where to report the issue, like ["confirmPassword"] for a cross-field check.
codeoptionalstringDefaults 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; }
node-comfort v2.0.0View the source