UNPKG

@sinclair/typebox

Version:

Json Schema Type Builder with Static Type Resolution for TypeScript

629 lines (585 loc) 126 kB
<div align='center'> <h1>TypeBox</h1> <p>Json Schema Type Builder with Static Type Resolution for TypeScript</p> <img src="https://github.com/sinclairzx81/typebox/blob/master/typebox.png?raw=true" /> <br /> <br /> [![npm version](https://badge.fury.io/js/%40sinclair%2Ftypebox.svg)](https://badge.fury.io/js/%40sinclair%2Ftypebox) [![Downloads](https://img.shields.io/npm/dm/%40sinclair%2Ftypebox.svg)](https://www.npmjs.com/package/%40sinclair%2Ftypebox) [![Build](https://github.com/sinclairzx81/typebox/actions/workflows/build.yml/badge.svg)](https://github.com/sinclairzx81/typebox/actions/workflows/build.yml) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) </div> <a name="Install"></a> ## Install ```bash $ npm install @sinclair/typebox --save ``` ## Example ```typescript import { Type, type Static } from '@sinclair/typebox' const T = Type.Object({ // const T = { x: Type.Number(), // type: 'object', y: Type.Number(), // required: ['x', 'y', 'z'], z: Type.Number() // properties: { }) // x: { type: 'number' }, // y: { type: 'number' }, // z: { type: 'number' } // } // } type T = Static<typeof T> // type T = { // x: number, // y: number, // z: number // } ``` <a name="Overview"></a> ## Overview TypeBox is a runtime type builder that creates in-memory Json Schema objects that infer as TypeScript types. The schematics produced by this library are designed to match the static type checking rules of the TypeScript compiler. TypeBox offers a unified type that can be statically checked by TypeScript and runtime asserted using standard Json Schema validation. This library is designed to allow Json Schema to compose similar to how types compose within TypeScript's type system. It can be used as a simple tool to build up complex schematics or integrated into REST and RPC services to help validate data received over the wire. License MIT ## Contents - [Install](#install) - [Overview](#overview) - [Usage](#usage) - [Types](#types) - [Json](#types-json) - [JavaScript](#types-javascript) - [Import](#types-import) - [Options](#types-options) - [Properties](#types-properties) - [Generics](#types-generics) - [References](#types-references) - [Recursive](#types-recursive) - [Template Literal](#types-template-literal) - [Indexed](#types-indexed) - [Mapped](#types-mapped) - [Conditional](#types-conditional) - [Transform](#types-transform) - [Guard](#types-guard) - [Unsafe](#types-unsafe) - [Strict](#types-strict) - [Values](#values) - [Assert](#values-assert) - [Create](#values-create) - [Clone](#values-clone) - [Check](#values-check) - [Convert](#values-convert) - [Default](#values-default) - [Clean](#values-clean) - [Cast](#values-cast) - [Decode](#values-decode) - [Encode](#values-decode) - [Parse](#values-parse) - [Equal](#values-equal) - [Hash](#values-hash) - [Diff](#values-diff) - [Patch](#values-patch) - [Errors](#values-errors) - [Mutate](#values-mutate) - [Pointer](#values-pointer) - [TypeRegistry](#typeregistry) - [Type](#typeregistry-type) - [Format](#typeregistry-format) - [TypeCheck](#typecheck) - [Ajv](#typecheck-ajv) - [TypeCompiler](#typecheck-typecompiler) - [TypeSystem](#typesystem) - [Policies](#typesystem-policies) - [Error Function](#error-function) - [Workbench](#workbench) - [Codegen](#codegen) - [Ecosystem](#ecosystem) - [Benchmark](#benchmark) - [Compile](#benchmark-compile) - [Validate](#benchmark-validate) - [Compression](#benchmark-compression) - [Contribute](#contribute) <a name="usage"></a> ## Usage The following shows general usage. ```typescript import { Type, type Static } from '@sinclair/typebox' //-------------------------------------------------------------------------------------------- // // Let's say you have the following type ... // //-------------------------------------------------------------------------------------------- type T = { id: string, name: string, timestamp: number } //-------------------------------------------------------------------------------------------- // // ... you can express this type in the following way. // //-------------------------------------------------------------------------------------------- const T = Type.Object({ // const T = { id: Type.String(), // type: 'object', name: Type.String(), // properties: { timestamp: Type.Integer() // id: { }) // type: 'string' // }, // name: { // type: 'string' // }, // timestamp: { // type: 'integer' // } // }, // required: [ // 'id', // 'name', // 'timestamp' // ] // } //-------------------------------------------------------------------------------------------- // // ... then infer back to the original static type this way. // //-------------------------------------------------------------------------------------------- type T = Static<typeof T> // type T = { // id: string, // name: string, // timestamp: number // } //-------------------------------------------------------------------------------------------- // // ... or use the type to parse JavaScript values. // //-------------------------------------------------------------------------------------------- import { Value } from '@sinclair/typebox/value' const R = Value.Parse(T, value) // const R: { // id: string, // name: string, // timestamp: number // } ``` <a name='types'></a> ## Types TypeBox types are Json Schema fragments that compose into more complex types. Each fragment is structured such that any Json Schema compliant validator can runtime assert a value the same way TypeScript will statically assert a type. TypeBox offers a set of Json Types which are used to create Json Schema compliant schematics as well as a JavaScript type set used to create schematics for constructs native to JavaScript. <a name='types-json'></a> ### Json Types The following table lists the supported Json types. These types are fully compatible with the Json Schema Draft 7 specification. ```typescript ┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐ │ TypeBox │ TypeScript │ Json Schema │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Any() │ type T = any │ const T = { } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Unknown() │ type T = unknown │ const T = { } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.String() │ type T = string │ const T = { │ │ │ │ type: 'string' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Number() │ type T = number │ const T = { │ │ │ │ type: 'number' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Integer() │ type T = number │ const T = { │ │ │ │ type: 'integer' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Boolean() │ type T = boolean │ const T = { │ │ │ │ type: 'boolean' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Null() │ type T = null │ const T = { │ │ │ │ type: 'null' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Literal(42) │ type T = 42 │ const T = { │ │ │ │ const: 42, │ │ │ │ type: 'number' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Array( │ type T = number[] │ const T = { │ │ Type.Number() │ │ type: 'array', │ │ ) │ │ items: { │ │ │ │ type: 'number' │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Object({ │ type T = { │ const T = { │ │ x: Type.Number(), │ x: number, │ type: 'object', │ │ y: Type.Number() │ y: number │ required: ['x', 'y'], │ │ }) │ } │ properties: { │ │ │ │ x: { │ │ │ │ type: 'number' │ │ │ │ }, │ │ │ │ y: { │ │ │ │ type: 'number' │ │ │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Tuple([ │ type T = [number, number] │ const T = { │ │ Type.Number(), │ │ type: 'array', │ │ Type.Number() │ │ items: [{ │ │ ]) │ │ type: 'number' │ │ │ │ }, { │ │ │ │ type: 'number' │ │ │ │ }], │ │ │ │ additionalItems: false, │ │ │ │ minItems: 2, │ │ │ │ maxItems: 2 │ │ │ │ } │ │ │ │ │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ enum Foo { │ enum Foo { │ const T = { │ │ A, │ A, │ anyOf: [{ │ │ B │ B │ type: 'number', │ │ } │ } │ const: 0 │ │ │ │ }, { │ │ const T = Type.Enum(Foo) │ type T = Foo │ type: 'number', │ │ │ │ const: 1 │ │ │ │ }] │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Const({ │ type T = { │ const T = { │ │ x: 1, │ readonly x: 1, │ type: 'object', │ │ y: 2, │ readonly y: 2 │ required: ['x', 'y'], │ │ } as const) │ } │ properties: { │ │ │ │ x: { │ │ │ │ type: 'number', │ │ │ │ const: 1 │ │ │ │ }, │ │ │ │ y: { │ │ │ │ type: 'number', │ │ │ │ const: 2 │ │ │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.KeyOf( │ type T = keyof { │ const T = { │ │ Type.Object({ │ x: number, │ anyOf: [{ │ │ x: Type.Number(), │ y: number │ type: 'string', │ │ y: Type.Number() │ } │ const: 'x' │ │ }) │ │ }, { │ │ ) │ │ type: 'string', │ │ │ │ const: 'y' │ │ │ │ }] │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Union([ │ type T = string | number │ const T = { │ │ Type.String(), │ │ anyOf: [{ │ │ Type.Number() │ │ type: 'string' │ │ ]) │ │ }, { │ │ │ │ type: 'number' │ │ │ │ }] │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Intersect([ │ type T = { │ const T = { │ │ Type.Object({ │ x: number │ allOf: [{ │ │ x: Type.Number() │ } & { │ type: 'object', │ │ }), │ y: number │ required: ['x'], │ │ Type.Object({ │ } │ properties: { │ │ y: Type.Number() │ │ x: { │ │ ]) │ │ type: 'number' │ │ ]) │ │ } │ │ │ │ } │ │ │ │ }, { │ │ │ │ type: 'object', | │ │ │ required: ['y'], │ │ │ │ properties: { │ │ │ │ y: { │ │ │ │ type: 'number' │ │ │ │ } │ │ │ │ } │ │ │ │ }] │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Composite([ │ type T = { │ const T = { │ │ Type.Object({ │ x: number, │ type: 'object', │ │ x: Type.Number() │ y: number │ required: ['x', 'y'], │ │ }), │ } │ properties: { │ │ Type.Object({ │ │ x: { │ │ y: Type.Number() │ │ type: 'number' │ │ }) │ │ }, │ │ ]) │ │ y: { │ │ │ │ type: 'number' │ │ │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Never() │ type T = never │ const T = { │ │ │ │ not: {} │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Not( | type T = unknown │ const T = { │ │ Type.String() │ │ not: { │ │ ) │ │ type: 'string' │ │ │ │ } │ │ │ │ } │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Extends( │ type T = │ const T = { │ │ Type.String(), │ string extends number │ const: false, │ │ Type.Number(), │ ? true │ type: 'boolean' │ │ Type.Literal(true), │ : false │ } │ │ Type.Literal(false) │ │ │ │ ) │ │ │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Extract( │ type T = Extract< │ const T = { │ │ Type.Union([ │ string | number, │ type: 'string' │ │ Type.String(), │ string │ } │ │ Type.Number(), │ > │ │ │ ]), │ │ │ │ Type.String() │ │ │ │ ) │ │ │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Exclude( │ type T = Exclude< │ const T = { │ │ Type.Union([ │ string | number, │ type: 'number' │ │ Type.String(), │ string │ } │ │ Type.Number(), │ > │ │ │ ]), │ │ │ │ Type.String() │ │ │ │ ) │ │ │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Mapped( │ type T = { │ const T = { │ │ Type.Union([ │ [_ in 'x' | 'y'] : number │ type: 'object', │ │ Type.Literal('x'), │ } │ required: ['x', 'y'], │ │ Type.Literal('y') │ │ properties: { │ │ ]), │ │ x: { │ │ () => Type.Number() │ │ type: 'number' │ │ ) │ │ }, │ │ │ │ y: { │ │ │ │ type: 'number' │ │ │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const U = Type.Union([ │ type U = 'open' | 'close' │ const T = { │ │ Type.Literal('open'), │ │ type: 'string', │ │ Type.Literal('close') │ type T = `on${U}` │ pattern: '^on(open|close)$' │ │ ]) │ │ } │ │ │ │ │ │ const T = Type │ │ │ │ .TemplateLiteral([ │ │ │ │ Type.Literal('on'), │ │ │ │ U │ │ │ │ ]) │ │ │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Record( │ type T = Record< │ const T = { │ │ Type.String(), │ string, │ type: 'object', │ │ Type.Number() │ number │ patternProperties: { │ │ ) │ > │ '^.*$': { │ │ │ │ type: 'number' │ │ │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Partial( │ type T = Partial<{ │ const T = { │ │ Type.Object({ │ x: number, │ type: 'object', │ │ x: Type.Number(), │ y: number │ properties: { │ │ y: Type.Number() | }> │ x: { │ │ }) │ │ type: 'number' │ │ ) │ │ }, │ │ │ │ y: { │ │ │ │ type: 'number' │ │ │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Required( │ type T = Required<{ │ const T = { │ │ Type.Object({ │ x?: number, │ type: 'object', │ │ x: Type.Optional( │ y?: number │ required: ['x', 'y'], │ │ Type.Number() | }> │ properties: { │ │ ), │ │ x: { │ │ y: Type.Optional( │ │ type: 'number' │ │ Type.Number() │ │ }, │ │ ) │ │ y: { │ │ }) │ │ type: 'number' │ │ ) │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Pick( │ type T = Pick<{ │ const T = { │ │ Type.Object({ │ x: number, │ type: 'object', │ │ x: Type.Number(), │ y: number │ required: ['x'], │ │ y: Type.Number() │ }, 'x'> │ properties: { │ │ }), ['x'] | │ x: { │ │ ) │ │ type: 'number' │ │ │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Omit( │ type T = Omit<{ │ const T = { │ │ Type.Object({ │ x: number, │ type: 'object', │ │ x: Type.Number(), │ y: number │ required: ['y'], │ │ y: Type.Number() │ }, 'x'> │ properties: { │ │ }), ['x'] | │ y: { │ │ ) │ │ type: 'number' │ │ │ │ } │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Index( │ type T = { │ const T = { │ │ Type.Object({ │ x: number, │ type: 'number' │ │ x: Type.Number(), │ y: string │ } │ │ y: Type.String() │ }['x'] │ │ │ }), ['x'] │ │ │ │ ) │ │ │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const A = Type.Tuple([ │ type A = [0, 1] │ const T = { │ │ Type.Literal(0), │ type B = [2, 3] │ type: 'array', │ │ Type.Literal(1) │ type T = [ │ items: [ │ │ ]) │ ...A, │ { const: 0 }, │ │ const B = Type.Tuple([ │ ...B │ { const: 1 }, │ | Type.Literal(2), │ ] │ { const: 2 }, │ | Type.Literal(3) │ │ { const: 3 } │ │ ]) │ │ ], │ │ const T = Type.Tuple([ │ │ additionalItems: false, │ | ...Type.Rest(A), │ │ minItems: 4, │ | ...Type.Rest(B) │ │ maxItems: 4 │ │ ]) │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Uncapitalize( │ type T = Uncapitalize< │ const T = { │ │ Type.Literal('Hello') │ 'Hello' │ type: 'string', │ │ ) │ > │ const: 'hello' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Capitalize( │ type T = Capitalize< │ const T = { │ │ Type.Literal('hello') │ 'hello' │ type: 'string', │ │ ) │ > │ const: 'Hello' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Uppercase( │ type T = Uppercase< │ const T = { │ │ Type.Literal('hello') │ 'hello' │ type: 'string', │ │ ) │ > │ const: 'HELLO' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Lowercase( │ type T = Lowercase< │ const T = { │ │ Type.Literal('HELLO') │ 'HELLO' │ type: 'string', │ │ ) │ > │ const: 'hello' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Object({ │ type T = { │ const R = { │ │ x: Type.Number(), │ x: number, │ $ref: 'T' │ │ y: Type.Number() │ y: number │ } │ │ }, { $id: 'T' }) | } │ │ │ │ │ │ │ const R = Type.Ref(T) │ type R = T │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘ ``` <a name='types-javascript'></a> ### JavaScript Types TypeBox provides an extended type set that can be used to create schematics for common JavaScript constructs. These types can not be used with any standard Json Schema validator; but can be used to frame schematics for interfaces that may receive Json validated data. JavaScript types are prefixed with the `[JavaScript]` jsdoc comment for convenience. The following table lists the supported types. ```typescript ┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐ │ TypeBox │ TypeScript │ Extended Schema │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Constructor([ │ type T = new ( │ const T = { │ │ Type.String(), │ arg0: string, │ type: 'Constructor', │ │ Type.Number() │ arg0: number │ parameters: [{ │ │ ], Type.Boolean()) │ ) => boolean │ type: 'string' │ │ │ │ }, { │ │ │ │ type: 'number' │ │ │ │ }], │ │ │ │ returns: { │ │ │ │ type: 'boolean' │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Function([ │ type T = ( │ const T = { │ | Type.String(), │ arg0: string, │ type: 'Function', │ │ Type.Number() │ arg1: number │ parameters: [{ │ │ ], Type.Boolean()) │ ) => boolean │ type: 'string' │ │ │ │ }, { │ │ │ │ type: 'number' │ │ │ │ }], │ │ │ │ returns: { │ │ │ │ type: 'boolean' │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Promise( │ type T = Promise<string> │ const T = { │ │ Type.String() │ │ type: 'Promise', │ │ ) │ │ item: { │ │ │ │ type: 'string' │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = │ type T = │ const T = { │ │ Type.AsyncIterator( │ AsyncIterableIterator< │ type: 'AsyncIterator', │ │ Type.String() │ string │ items: { │ │ ) │ > │ type: 'string' │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Iterator( │ type T = │ const T = { │ │ Type.String() │ IterableIterator<string> │ type: 'Iterator', │ │ ) │ │ items: { │ │ │ │ type: 'string' │ │ │ │ } │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.RegExp(/abc/i) │ type T = string │ const T = { │ │ │ │ type: 'RegExp' │ │ │ │ source: 'abc' │ │ │ │ flags: 'i' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Uint8Array() │ type T = Uint8Array │ const T = { │ │ │ │ type: 'Uint8Array' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Date() │ type T = Date │ const T = { │ │ │ │ type: 'Date' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Undefined() │ type T = undefined │ const T = { │ │ │ │ type: 'undefined' │ │ │ │ } │ │ │ │ │ ├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤ │ const T = Type.Symbol() │ type T = symbol │ const T = { │ │ │ │ type: 'symbol'