Schemas
A schema is a factory function that returns a fluent and typed builder interface. This builder can be used to chain validation rules, define a default value, parse values, and more!
All schemas support methods found on the CommonCriterias
interface.
Arrays#
The array() schema verifies a value is an array, or an array of a
specific type. For undefined values, an empty array ([]) is returned, which can be customized with
the 1st argument.
import { array, string } from 'optimal';
const anyArraySchema = array();
anyArraySchema.validate([]); // passanyArraySchema.validate([1, 2, 3]); // passanyArraySchema.validate(['a', 'b', 'c']); // pass
const stringArraySchema = array(['foo']).of(string()).notEmpty();
stringArraySchema.validate([]); // failstringArraySchema.validate([1, 2, 3]); // failstringArraySchema.validate(['a', 'b', 'c']); // passArray schemas support all methods found on the ArraySchema
interface.
Blueprints#
The blueprint() schema verifies a value is an object that maps
properties to schema instances. This schema is useful for composition based APIs.
import { blueprint } from 'optimal';
blueprint().validate({    name: string(),    type: number(),});Booleans#
The bool() schema verifies a value is a boolean. For undefined
values, false is returned, which can be customized with the 1st argument.
import { bool } from 'optimal';
const anyBoolSchema = bool();
anyBoolSchema.validate(true); // passanyBoolSchema.validate(false); // passanyBoolSchema.validate(123); // fail
const falsyBoolSchema = bool().onlyFalse();
falsyBoolSchema.validate(true); // failfalsyBoolSchema.validate(false); // passBoolean schemas support all methods found on the
BooleanSchema interface.
Class instances#
The instance() schema verifies a value is an instance of a
specific class (when using of()), or simply an instance of any class (by default). This schema is
nullable by default and may return null.
import { instance } from 'optimal';
class Foo {}class Bar {}
const anyClassSchema = instance();
anyClassSchema.validate(new Foo()); // passanyClassSchema.validate(new Bar()); // pass
const fooSchema = instance().of(Foo);
fooSchema.validate(new Foo()); // passfooSchema.validate(new Bar()); // failSince instanceof checks are problematic across realms or when dealing with dual-package hazards,
an optional loose argument can be enabled as the 2nd argument on of(). This will compare
constructor names, which is brittle, but unblocks certain scenarios.
const looseFooSchema = instance().of(Foo, true);Instance schemas support all methods found on the
InstanceSchema interface.
Custom#
The custom() schema verifies a value based on a user-provided
callback. This callback receives the current value to validate, an object path, and validation
options (which includes any root and current objects).
A default value is required as the 2nd argument.
import path from 'path';import { custom } from 'optimal';
const absPathSchema = custom((value) => {    if (!path.isAbsolute(value)) {        throw new Error('Path must be absolute.');    }}, process.cwd());
absPathSchema.validate('/absolute/path'); // passabsPathSchema.validate('../relative/path'); // failCustom schemas support all methods found on the
CustomSchema interface.
Dates#
The date() schema verifies a value is date-like, which supports
Date objects, an ISO-8601 string, or a UNIX timestamp. Regardless of the input value, a Date
object is always returned as the output value. For undefined values, a new Date is returned.
import { date } from 'optimal';
const dateSchema = date();
dateSchema.validate(new Date()); // passdateSchema.validate(1632450940763); // passdateSchema.validate('2021-09-24T02:32:31.610Z'); // passDate schemas support all methods found on the DateSchema
interface.
Functions#
The func() schema verifies a value is a function.
import { func } from 'optimal';
const funcSchema = func();
funcSchema.validate(() => {}); // passfuncSchema.validate(123); // failBy default this schema has no default value (returns undefined), regardless of undefinable state,
but this can be customized with the 1st argument. However, because of our
lazy default values, the "default function" must be returned with
another function.
import { func } from 'optimal';
function noop() {}
// Incorrectfunc(noop);
// Correctfunc(() => noop);Function schemas support all methods found on the
FunctionSchema interface.
Functions are special when it comes to handling
undefinedvalues, because, what would the default value of a function be?
IDs#
The id() schema verifies a value is an auto-incrementing ID, most
commonly used for database records. All IDs must be a positive integer, and will not accept 0,
null, or undefined.
import { id } from 'optimal';
const idSchema = id();
idSchema.validate(123); // passidSchema.validate(0); // failidSchema.validate(-123); // failidSchema.validate(null); // failID schemas support all methods found on the NumberSchema
interface.
Lazy / recursive#
The lazy() schema is useful for declaring deferred evaluation or
recursive schemas. When using this pattern, the lazy element must declare a default value, and
must never be required.
import { lazy, LazySchema, number, shape } from 'optimal';
interface Node {    id: number;    child?: Node | null;}
const node: LazySchema<Node> = shape({    id: number(),    child: lazy(() => node, null).nullable(),});Because of a limitation in TypeScript, the return type cannot be statically inferred, so you'll need to type the schema variable directly with
LazySchema.
Numbers#
The number() schema verifies a value is a number. For undefined
values, a 0 is returned, which can be customized with the 1st argument.
import { number } from 'optimal';
const anyNumberSchema = number();
anyNumberSchema.validate(123); // passanyNumberSchema.validate('abc'); // fail
const intGteNumberSchema = number(100).int().gte(100);
intGteNumberSchema.validate(150); // passintGteNumberSchema.validate(50); // failintGteNumberSchema.validate(200.25); // failNumber schemas support all methods found on the
NumberSchema interface.
Objects#
The object() schema verifies a value is a plain object or an
indexed object with all values of a specific type. For undefined values, an empty object ({}) is
returned, which can be customized with the 1st argument.
import { object, number } from 'optimal';
const anyObjectSchema = object();
anyObjectSchema.validate({}); // passanyObjectSchema.validate({ foo: 123 }); // passanyObjectSchema.validate({ bar: 'abc' }); // pass
const numberObjectSchema = object().of(number());
numberObjectSchema.validate({ foo: 123 }); // passnumberObjectSchema.validate({ bar: 'abc' }); // failObjects can also define schemas for keys. For example, say we only want underscored names.
object().keysOf(string().snakeCase());Object schemas support all methods found on the
ObjectSchema interface.
Records#
The record() schema is an alias for objects.
import { record } from 'optimal';Regex patterns#
The regex() schema verifies a value is an instance of RegExp.
This schema is nullable by default and may return null.
import { regex } from 'optimal';
const regexSchema = regex();
regexSchema.validate(/foo/); // passregexSchema.validate(new RegExp('bar')); // passregexSchema.validate('baz'); // failRegex schemas support all methods found on the
InstanceSchema interface.
Schemas#
The schema() schema verifies a value is a schema instance. This is
useful for composing blueprints.
import { number, schema } from 'optimal';
const anySchema = schema();
anySchema.validate(number()); // passanySchema.validate({}); // failShapes#
The shape() schema verifies a value matches a explicit object
shape, defined as a blueprint mapping properties to schemas. For undefined values, defaults to the
structure of the shape and cannot be customized.
import { bool, shape, string } from 'optimal';
const imageSchema = shape({    name: string().notEmpty().required(),    path: string().required(),    type: string('png'),    relative: bool(),});
imageSchema.validate({    name: 'Image',    path: '/some/path/image.png',    type: 'png',    relative: false,}); // pass
imageSchema.validate({ name: 'Invalid', size: 123 }); // failShape schemas support all methods found on the ShapeSchema
interface.
Strings#
The string() schema verifies a value is a string. For undefined
values, an empty string ('') is returned, which can be customized with the 1st argument.
import { string } from 'optimal';
const anyStringSchema = string();
anyStringSchema.validate(''); // passanyStringSchema.validate('abc'); // pass
const fileTypeSchema = string('js').oneOf(['js', 'ts', 'css', 'html']);
fileTypeSchema.validate('js'); // passfileTypeSchema.validate('png'); // failTuples#
A tuple is an array-like structure with a defined set of items, each with their own unique type. The
tuple() schema will validate each item and return an array of the
same length and types. Defaults to the structure of the tuple and cannot be customized.
import { number, string tuple } from 'optimal';
type Item = [number, string]; // ID, name
const itemTuple = tuple<Item>([number().gt(0).required(), string().notEmpty()]);
itemTuple.validate([]); // failitemTuple.validate([123]); // passitemTuple.validate([123, 'abc']); // passitemTuple.validate([123, 'abc', true]); // failTuple schemas support all methods found on the TupleSchema
interface.
When using TypeScript, a generic type is required for schemas to type correctly. Furthermore, the schema only supports a max length of 5 items.
Unions#
The union() schema verifies a value against a list of possible
values. All unions require a default value as the 1st argument, as we need a value to fallback to.
import { array, string, shape, union } from 'optimal';
type EntryPoint = string | string[] | { path: string };
const entryPointSchema = union<EntryPoint>('./src/index.ts').of([    string(),    array(string()),    shape({        path: string(),    }),]);
entryPointSchema.validate('./some/path'); // passentryPointSchema.validate(['./some/path', './another/path']); // passentryPointSchema.validate({ path: './some/path' }); // passUnions support multiple schemas of the same type in unison, and the first one that passes validation will be used.
import { number, string, object, union } from 'optimal';
const objectOfNumberOrString = union<Record<string, number> | Record<string, string>>({}).of([    object(number()),    object(string()),]);Unions also support objects and shapes in unison. However, when using this approach, be sure that shapes are listed first so that they validate their shape early and exit the validation process.
import { number, string, object, union } from 'optimal';
const shapeOrObject = union<{ path: string } | Record<string, number>>({}).of([    shape({        path: string(),    }),    object(number()),]);Union schemas support all methods found on the UnionSchema
interface.
When using TypeScript, the type cannot be inferred automatically, so defaults to
unknown. This can be overridden by explicitly defining the generic, as seen in the examples above.
UUIDs#
The uuid() schema verifies a value is a universally unique
identifier, most commonly used for database records. All UUIDs must be align with the
specification, and will not accept an
empty string (""), null, or undefined.
import { uuid } from 'optimal';
const uuidSchema = uuid();
uuidSchema.validate('e023d5bd-5c1b-3b47-8646-cacb8b8e3634'); // passuuidSchema.validate(''); // failuuidSchema.validate(null); // failBy default the schema will validate all UUID versions, but this can be customized with the 1st argument.
const v4UuidSchema = uuid(4);UUID schemas support all methods found on the StringSchema
interface.