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.
#
ArraysThe 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']); // pass
Array schemas support all methods found on the ArraySchema
interface.
#
BlueprintsThe 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(),});
#
BooleansThe 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); // pass
Boolean schemas support all methods found on the
BooleanSchema
interface.
#
Class instancesThe 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()); // fail
Since 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.
#
CustomThe 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'); // fail
Custom schemas support all methods found on the
CustomSchema
interface.
#
DatesThe 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'); // pass
Date schemas support all methods found on the DateSchema
interface.
#
FunctionsThe func()
schema verifies a value is a function.
import { func } from 'optimal';
const funcSchema = func();
funcSchema.validate(() => {}); // passfuncSchema.validate(123); // fail
By 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
undefined
values, because, what would the default value of a function be?
#
IDsThe 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); // fail
ID schemas support all methods found on the NumberSchema
interface.
#
Lazy / recursiveThe 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
.
#
NumbersThe 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); // fail
Number schemas support all methods found on the
NumberSchema
interface.
#
ObjectsThe 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' }); // fail
Objects 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.
#
RecordsThe record()
schema is an alias for objects.
import { record } from 'optimal';
#
Regex patternsThe 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'); // fail
Regex schemas support all methods found on the
InstanceSchema
interface.
#
SchemasThe 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({}); // fail
#
ShapesThe 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 }); // fail
Shape schemas support all methods found on the ShapeSchema
interface.
#
StringsThe 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'); // fail
#
TuplesA 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]); // fail
Tuple 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.
#
UnionsThe 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' }); // pass
Unions 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.
#
UUIDsThe 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); // fail
By 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.