Skip to main content
Module

x/typebox/src/typebox.ts

JSON Schema Type Builder with Static Type Resolution for TypeScript
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
/*--------------------------------------------------------------------------
@sinclair/typebox
The MIT License (MIT)
Copyright (c) 2022 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included inall copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE.
---------------------------------------------------------------------------*/
// --------------------------------------------------------------------------// Symbols// --------------------------------------------------------------------------
export const Kind = Symbol.for('TypeBox.Kind')export const Modifier = Symbol.for('TypeBox.Modifier')
// --------------------------------------------------------------------------// Modifiers// --------------------------------------------------------------------------
export type TModifier = TReadonlyOptional<TSchema> | TOptional<TSchema> | TReadonly<TSchema>
export type TReadonly<T extends TSchema> = T & { [Modifier]: 'Readonly' }
export type TOptional<T extends TSchema> = T & { [Modifier]: 'Optional' }
export type TReadonlyOptional<T extends TSchema> = T & { [Modifier]: 'ReadonlyOptional' }
// --------------------------------------------------------------------------// Schema// --------------------------------------------------------------------------
export interface DesignType { type: string [props: string]: any}
export interface SchemaOptions { $schema?: string /** Id for this schema */ $id?: string /** Title of this schema */ title?: string /** Description of this schema */ description?: string /** Default value for this schema */ default?: any /** Example values matching this schema. */ examples?: any [prop: string]: any}
export interface TSchema extends SchemaOptions { [Kind]: string [Modifier]?: string params: unknown[] static: unknown}
// --------------------------------------------------------------------------// TAnySchema// --------------------------------------------------------------------------
export type TAnySchema = | TSchema | TAny | TArray | TBoolean | TConstructor | TEnum | TFunction | TInteger | TLiteral | TNull | TNumber | TObject | TPromise | TRecord | TSelf | TRef | TString | TTuple | TUndefined | TUnion | TUint8Array | TUnknown | TVoid
// --------------------------------------------------------------------------// TNumeric// --------------------------------------------------------------------------
export interface NumericOptions extends SchemaOptions { exclusiveMaximum?: number exclusiveMinimum?: number maximum?: number minimum?: number multipleOf?: number}
export type TNumeric = TInteger | TNumber
// --------------------------------------------------------------------------// Any// --------------------------------------------------------------------------
export interface TAny extends TSchema { [Kind]: 'Any' static: any}
// --------------------------------------------------------------------------// Array// --------------------------------------------------------------------------
export interface ArrayOptions extends SchemaOptions { uniqueItems?: boolean minItems?: number maxItems?: number}
export interface TArray<T extends TSchema = TSchema> extends TSchema, ArrayOptions { [Kind]: 'Array' static: Array<Static<T, this['params']>> type: 'array' items: T}
// --------------------------------------------------------------------------// Boolean// --------------------------------------------------------------------------
export interface TBoolean extends TSchema { [Kind]: 'Boolean' static: boolean type: 'boolean'}
// --------------------------------------------------------------------------// Constructor// --------------------------------------------------------------------------
export type TContructorParameters<T extends readonly TSchema[], P extends unknown[]> = [...{ [K in keyof T]: T[K] extends TSchema ? Static<T[K], P> : never }]
export interface TConstructor<T extends TSchema[] = TSchema[], U extends TSchema = TSchema> extends TSchema { [Kind]: 'Constructor' static: new (...param: TContructorParameters<T, this['params']>) => Static<U, this['params']> type: 'constructor' parameters: T returns: U}
// --------------------------------------------------------------------------// Enum// --------------------------------------------------------------------------
export interface TEnumOption<T> { type: 'number' | 'string' const: T}
export interface TEnum<T extends Record<string, string | number> = Record<string, string | number>> extends TSchema { [Kind]: 'Union' static: T[keyof T] anyOf: TLiteral<string | number>[]}
// --------------------------------------------------------------------------// Function// --------------------------------------------------------------------------
export type TFunctionParameters<T extends readonly TSchema[], P extends unknown[]> = [...{ [K in keyof T]: T[K] extends TSchema ? Static<T[K], P> : never }]
export interface TFunction<T extends readonly TSchema[] = TSchema[], U extends TSchema = TSchema> extends TSchema { [Kind]: 'Function' static: (...param: TFunctionParameters<T, this['params']>) => Static<U, this['params']> type: 'function' parameters: T returns: U}
// --------------------------------------------------------------------------// Integer// --------------------------------------------------------------------------
export interface TInteger extends TSchema, NumericOptions { [Kind]: 'Integer' static: number type: 'integer'}
// --------------------------------------------------------------------------// Intersect// --------------------------------------------------------------------------
export type IntersectEvaluate<T extends readonly TSchema[], P extends unknown[]> = { [K in keyof T]: T[K] extends TSchema ? Static<T[K], P> : never }
export type IntersectReduce<I extends unknown, T extends readonly any[]> = T extends [infer A, ...infer B] ? IntersectReduce<I & A, B> : I extends object ? I : {}
export interface TIntersect<T extends TObject[] = TObject[]> extends TObject { static: IntersectReduce<unknown, IntersectEvaluate<T, this['params']>> properties: Record<keyof IntersectReduce<unknown, IntersectEvaluate<T, this['params']>>, TSchema>}
// --------------------------------------------------------------------------// KeyOf: Implemented by way of Union<TLiteral<string>>// --------------------------------------------------------------------------
type UnionToIntersect<U> = (U extends unknown ? (arg: U) => 0 : never) extends (arg: infer I) => 0 ? I : never
type UnionLast<U> = UnionToIntersect<U extends unknown ? (x: U) => 0 : never> extends (x: infer L) => 0 ? L : never
type UnionToTuple<U, L = UnionLast<U>> = [U] extends [never] ? [] : [...UnionToTuple<Exclude<U, L>>, L]
export type TKeyOf<T extends TObject> = { [K in ObjectPropertyKeys<T>]: TLiteral<K> } extends infer R ? UnionToTuple<R[keyof R]> : never
// --------------------------------------------------------------------------// Literal// --------------------------------------------------------------------------
export type TLiteralValue = string | number | boolean
export interface TLiteral<T extends TLiteralValue = TLiteralValue> extends TSchema { [Kind]: 'Literal' static: T const: T}
// --------------------------------------------------------------------------// Null// --------------------------------------------------------------------------
export interface TNull extends TSchema { [Kind]: 'Null' static: null type: 'null'}
// --------------------------------------------------------------------------// Number// --------------------------------------------------------------------------
export interface TNumber extends TSchema, NumericOptions { [Kind]: 'Number' static: number type: 'number'}
// --------------------------------------------------------------------------// Object// --------------------------------------------------------------------------
export type ReadonlyOptionalPropertyKeys<T extends TProperties> = { [K in keyof T]: T[K] extends TReadonlyOptional<TSchema> ? K : never }[keyof T]
export type ReadonlyPropertyKeys<T extends TProperties> = { [K in keyof T]: T[K] extends TReadonly<TSchema> ? K : never }[keyof T]
export type OptionalPropertyKeys<T extends TProperties> = { [K in keyof T]: T[K] extends TOptional<TSchema> ? K : never }[keyof T]
export type RequiredPropertyKeys<T extends TProperties> = keyof Omit<T, ReadonlyOptionalPropertyKeys<T> | ReadonlyPropertyKeys<T> | OptionalPropertyKeys<T>>
export type PropertiesReduce<T extends TProperties, P extends unknown[]> = { readonly [K in ReadonlyOptionalPropertyKeys<T>]?: Static<T[K], P> } & { readonly [K in ReadonlyPropertyKeys<T>]: Static<T[K], P> } & { [K in OptionalPropertyKeys<T>]?: Static<T[K], P>} & { [K in RequiredPropertyKeys<T>]: Static<T[K], P> } extends infer R ? { [K in keyof R]: R[K] } : never
export type TRecordProperties<K extends TUnion<TLiteral[]>, T extends TSchema> = Static<K> extends string ? { [X in Static<K>]: T } : never
export interface TProperties { [key: string]: TSchema}
export type ObjectProperties<T> = T extends TObject<infer U> ? U : never
export type ObjectPropertyKeys<T> = T extends TObject<infer U> ? keyof U : never
export interface ObjectOptions extends SchemaOptions { additionalProperties?: boolean minProperties?: number maxProperties?: number}
export interface TObject<T extends TProperties = TProperties> extends TSchema, ObjectOptions { [Kind]: 'Object' static: PropertiesReduce<T, this['params']> type: 'object' properties: T required?: string[]}
// --------------------------------------------------------------------------// Omit// --------------------------------------------------------------------------
export interface TOmit<T extends TObject, Properties extends ObjectPropertyKeys<T>[]> extends TObject, ObjectOptions { static: Omit<Static<T, this['params']>, Properties[number]> properties: T extends TObject ? Omit<T['properties'], Properties[number]> : never}
// --------------------------------------------------------------------------// Partial// --------------------------------------------------------------------------
export interface TPartial<T extends TObject> extends TObject { static: Partial<Static<T, this['params']>>}
// --------------------------------------------------------------------------// Pick// --------------------------------------------------------------------------
export interface TPick<T extends TObject, Properties extends ObjectPropertyKeys<T>[]> extends TObject, ObjectOptions { static: Pick<Static<T, this['params']>, Properties[number]> properties: ObjectProperties<T>}
// --------------------------------------------------------------------------// Promise// --------------------------------------------------------------------------
export interface TPromise<T extends TSchema = TSchema> extends TSchema { [Kind]: 'Promise' static: Promise<Static<T, this['params']>> type: 'promise' item: TSchema}
// --------------------------------------------------------------------------// Record// --------------------------------------------------------------------------
export type TRecordKey = TString | TNumber | TUnion<TLiteral<any>[]>
export interface TRecord<K extends TRecordKey = TRecordKey, T extends TSchema = TSchema> extends TSchema { [Kind]: 'Record' static: Record<Static<K>, Static<T, this['params']>> type: 'object' patternProperties: { [pattern: string]: T } additionalProperties: false}
// --------------------------------------------------------------------------// Rec// --------------------------------------------------------------------------
export interface TSelf extends TSchema { [Kind]: 'Self' static: this['params'][0] $ref: string}
export type TRecursiveReduce<T extends TSchema> = Static<T, [TRecursiveReduce<T>]>
export interface TRecursive<T extends TSchema> extends TSchema { static: TRecursiveReduce<T>}
// --------------------------------------------------------------------------// Ref// --------------------------------------------------------------------------
export interface TRef<T extends TSchema = TSchema> extends TSchema { [Kind]: 'Ref' static: Static<T, this['params']> $ref: string}
// --------------------------------------------------------------------------// Required// --------------------------------------------------------------------------
export interface TRequired<T extends TObject | TRef<TObject>> extends TObject { static: Required<Static<T, this['params']>>}
// --------------------------------------------------------------------------// String// --------------------------------------------------------------------------
export type StringFormatOption = | 'date-time' | 'time' | 'date' | 'email' | 'idn-email' | 'hostname' | 'idn-hostname' | 'ipv4' | 'ipv6' | 'uri' | 'uri-reference' | 'iri' | 'uuid' | 'iri-reference' | 'uri-template' | 'json-pointer' | 'relative-json-pointer' | 'regex'
export interface StringOptions<TFormat extends string> extends SchemaOptions { minLength?: number maxLength?: number pattern?: string format?: TFormat contentEncoding?: '7bit' | '8bit' | 'binary' | 'quoted-printable' | 'base64' contentMediaType?: string}
export interface TString extends TSchema, StringOptions<string> { [Kind]: 'String' static: string type: 'string'}
// --------------------------------------------------------------------------// Tuple// --------------------------------------------------------------------------
export interface TTuple<T extends TSchema[] = TSchema[]> extends TSchema { [Kind]: 'Tuple' static: { [K in keyof T]: T[K] extends TSchema ? Static<T[K], this['params']> : T[K] } type: 'array' items?: T additionalItems?: false minItems: number maxItems: number}
// --------------------------------------------------------------------------// Undefined// --------------------------------------------------------------------------
export interface TUndefined extends TSchema { [Kind]: 'Undefined' specialized: 'Undefined' static: undefined type: 'object'}
// --------------------------------------------------------------------------// Union// --------------------------------------------------------------------------
export interface TUnion<T extends TSchema[] = TSchema[]> extends TSchema { [Kind]: 'Union' static: { [K in keyof T]: T[K] extends TSchema ? Static<T[K], this['params']> : never }[number] anyOf: T}
// -------------------------------------------------------------------------// Uint8Array// -------------------------------------------------------------------------
export interface Uint8ArrayOptions extends SchemaOptions { maxByteLength?: number minByteLength?: number}
export interface TUint8Array extends TSchema, Uint8ArrayOptions { [Kind]: 'Uint8Array' static: Uint8Array specialized: 'Uint8Array' type: 'object'}
// --------------------------------------------------------------------------// Unknown// --------------------------------------------------------------------------
export interface TUnknown extends TSchema { [Kind]: 'Unknown' static: unknown}
// --------------------------------------------------------------------------// Unsafe// --------------------------------------------------------------------------
export interface UnsafeOptions extends SchemaOptions { [Kind]?: string}
export interface TUnsafe<T> extends TSchema { [Kind]: string static: T}
// --------------------------------------------------------------------------// Void// --------------------------------------------------------------------------
export interface TVoid extends TSchema { [Kind]: 'Void' static: void type: 'null'}
// --------------------------------------------------------------------------// Static<T>// --------------------------------------------------------------------------
/** Creates a static type from a TypeBox type */export type Static<T extends TSchema, P extends unknown[] = []> = (T & { params: P })['static']
// --------------------------------------------------------------------------// TypeBuilder// --------------------------------------------------------------------------
let TypeOrdinal = 0
export class TypeBuilder { // ---------------------------------------------------------------------- // Modifiers // ----------------------------------------------------------------------
/** Creates a readonly optional property */ public ReadonlyOptional<T extends TSchema>(item: T): TReadonlyOptional<T> { return { [Modifier]: 'ReadonlyOptional', ...item } }
/** Creates a readonly property */ public Readonly<T extends TSchema>(item: T): TReadonly<T> { return { [Modifier]: 'Readonly', ...item } }
/** Creates a optional property */ public Optional<T extends TSchema>(item: T): TOptional<T> { return { [Modifier]: 'Optional', ...item } }
// ---------------------------------------------------------------------- // Types // ----------------------------------------------------------------------
/** Creates a any type */ public Any(options: SchemaOptions = {}): TAny { return this.Create({ ...options, [Kind]: 'Any' }) }
/** Creates a array type */ public Array<T extends TSchema>(items: T, options: ArrayOptions = {}): TArray<T> { return this.Create({ ...options, [Kind]: 'Array', type: 'array', items }) }
/** Creates a boolean type */ public Boolean(options: SchemaOptions = {}): TBoolean { return this.Create({ ...options, [Kind]: 'Boolean', type: 'boolean' }) }
/** Creates a constructor type */ public Constructor<T extends TSchema[], U extends TSchema>(parameters: [...T], returns: U, options: SchemaOptions = {}): TConstructor<T, U> { return this.Create({ ...options, [Kind]: 'Constructor', type: 'constructor', parameters, returns }) }
/** Creates a enum type */ public Enum<T extends Record<string, string | number>>(item: T, options: SchemaOptions = {}): TEnum<T> { const values = Object.keys(item) .filter((key) => isNaN(key as any)) .map((key) => item[key]) as T[keyof T][] const anyOf = values.map((value) => (typeof value === 'string' ? { [Kind]: 'Literal', type: 'string' as const, const: value } : { [Kind]: 'Literal', type: 'number' as const, const: value })) return this.Create({ ...options, [Kind]: 'Union', anyOf }) }
/** Creates a function type */ public Function<T extends readonly TSchema[], U extends TSchema>(parameters: [...T], returns: U, options: SchemaOptions = {}): TFunction<T, U> { return this.Create({ ...options, [Kind]: 'Function', type: 'function', parameters, returns }) }
/** Creates a integer type */ public Integer(options: NumericOptions = {}): TInteger { return this.Create({ ...options, [Kind]: 'Integer', type: 'integer' }) }
/** Creates a intersect type. */ public Intersect<T extends TObject[]>(objects: [...T], options: ObjectOptions = {}): TIntersect<T> { const isOptional = (schema: TSchema) => (schema[Modifier] && schema[Modifier] === 'Optional') || schema[Modifier] === 'ReadonlyOptional' const [required, optional] = [new Set<string>(), new Set<string>()] for (const object of objects) { for (const [key, schema] of Object.entries(object.properties)) { if (isOptional(schema)) optional.add(key) } } for (const object of objects) { for (const key of Object.keys(object.properties)) { if (!optional.has(key)) required.add(key) } } const properties = {} as Record<string, any> for (const object of objects) { for (const [key, schema] of Object.entries(object.properties)) { properties[key] = properties[key] === undefined ? schema : { [Kind]: 'Union', anyOf: [properties[key], { ...schema }] } } } if (required.size > 0) { return this.Create({ ...options, [Kind]: 'Object', type: 'object', properties, required: [...required] }) } else { return this.Create({ ...options, [Kind]: 'Object', type: 'object', properties }) } }
/** Creates a keyof type */ public KeyOf<T extends TObject>(object: T, options: SchemaOptions = {}): TUnion<TKeyOf<T>> { const items = Object.keys(object.properties).map((key) => this.Create({ ...options, [Kind]: 'Literal', type: 'string', const: key })) return this.Create({ ...options, [Kind]: 'Union', anyOf: items }) }
/** Creates a literal type. */ public Literal<T extends TLiteralValue>(value: T, options: SchemaOptions = {}): TLiteral<T> { return this.Create({ ...options, [Kind]: 'Literal', const: value, type: typeof value as 'string' | 'number' | 'boolean' }) }
/** Creates a null type */ public Null(options: SchemaOptions = {}): TNull { return this.Create({ ...options, [Kind]: 'Null', type: 'null' }) }
/** Creates a number type */ public Number(options: NumericOptions = {}): TNumber { return this.Create({ ...options, [Kind]: 'Number', type: 'number' }) }
/** Creates an object type with the given properties */ public Object<T extends TProperties>(properties: T, options: ObjectOptions = {}): TObject<T> { const property_names = Object.keys(properties) const optional = property_names.filter((name) => { const property = properties[name] as TModifier const modifier = property[Modifier] return modifier && (modifier === 'Optional' || modifier === 'ReadonlyOptional') }) const required = property_names.filter((name) => !optional.includes(name)) if (required.length > 0) { return this.Create({ ...options, [Kind]: 'Object', type: 'object', properties, required }) } else { return this.Create({ ...options, [Kind]: 'Object', type: 'object', properties }) } }
/** Creates a new object whose properties are omitted from the given object */ public Omit<T extends TObject, Properties extends Array<ObjectPropertyKeys<T>>>(schema: T, keys: [...Properties], options: ObjectOptions = {}): TOmit<T, Properties> { const next = { ...this.Clone(schema), ...options } next.required = next.required ? next.required.filter((key: string) => !keys.includes(key as any)) : undefined for (const key of Object.keys(next.properties)) { if (keys.includes(key as any)) delete next.properties[key] } return this.Create(next) }
/** Creates an object type whose properties are all optional */ public Partial<T extends TObject>(schema: T, options: ObjectOptions = {}): TPartial<T> { const next = { ...(this.Clone(schema) as T), ...options } delete next.required for (const key of Object.keys(next.properties)) { const property = next.properties[key] const modifer = property[Modifier] switch (modifer) { case 'ReadonlyOptional': property[Modifier] = 'ReadonlyOptional' break case 'Readonly': property[Modifier] = 'ReadonlyOptional' break case 'Optional': property[Modifier] = 'Optional' break default: property[Modifier] = 'Optional' break } } return this.Create(next) }
/** Creates a new object whose properties are picked from the given object */ public Pick<T extends TObject, Properties extends Array<ObjectPropertyKeys<T>>>(schema: T, keys: [...Properties], options: ObjectOptions = {}): TPick<T, Properties> { const next = { ...this.Clone(schema), ...options } next.required = next.required ? next.required.filter((key: any) => keys.includes(key)) : undefined for (const key of Object.keys(next.properties)) { if (!keys.includes(key as any)) delete next.properties[key] } return this.Create(next) }
/** Creates a promise type. This type cannot be represented in schema. */ public Promise<T extends TSchema>(item: T, options: SchemaOptions = {}): TPromise<T> { return this.Create({ ...options, [Kind]: 'Promise', type: 'promise', item }) }
/** Creates an object whose properties are derived from the given string literal union. */ public Record<K extends TUnion<TLiteral[]>, T extends TSchema>(key: K, schema: T, options?: ObjectOptions): TObject<TRecordProperties<K, T>>
/** Creates a record type */ public Record<K extends TString | TNumber, T extends TSchema>(key: K, schema: T, options?: ObjectOptions): TRecord<K, T>
/** Creates a record type */ public Record(key: any, value: any, options: ObjectOptions = {}) { // If string literal union return TObject with properties extracted from union. if (key[Kind] === 'Union') { return this.Object( key.anyOf.reduce((acc: any, literal: any) => { return { ...acc, [literal.const]: value } }, {}), { ...options }, ) } // otherwise return TRecord with patternProperties const pattern = key[Kind] === 'Number' ? '^(0|[1-9][0-9]*)$' : key[Kind] === 'String' && key.pattern ? key.pattern : '^.*$' return this.Create({ ...options, [Kind]: 'Record', type: 'object', patternProperties: { [pattern]: value }, additionalProperties: false, }) }
/** Creates a recursive object type */ public Recursive<T extends TSchema>(callback: (self: TSelf) => T, options: SchemaOptions = {}): TRecursive<T> { if (options.$id === undefined) options.$id = `type-${TypeOrdinal++}` const self = callback({ [Kind]: 'Self', $ref: `${options.$id}` } as any) self.$id = options.$id return this.Create({ ...options, ...self } as any) }
/** Creates a reference schema */ public Ref<T extends TSchema>(schema: T, options: SchemaOptions = {}): TRef<T> { if (schema.$id === undefined) throw Error('Type.Ref: Referenced schema must specify an $id') return this.Create({ ...options, [Kind]: 'Ref', $ref: schema.$id! }) }
/** Creates a string type from a regular expression */ public RegEx(regex: RegExp, options: SchemaOptions = {}): TString { return this.Create({ ...options, [Kind]: 'String', type: 'string', pattern: regex.source }) }
/** Creates an object type whose properties are all required */ public Required<T extends TObject>(schema: T, options: SchemaOptions = {}): TRequired<T> { const next = { ...(this.Clone(schema) as T), ...options } next.required = Object.keys(next.properties) for (const key of Object.keys(next.properties)) { const property = next.properties[key] const modifier = property[Modifier] switch (modifier) { case 'ReadonlyOptional': property[Modifier] = 'Readonly' break case 'Readonly': property[Modifier] = 'Readonly' break case 'Optional': delete property[Modifier] break default: delete property[Modifier] break } } return this.Create(next) }
/** Removes Kind and Modifier symbol property keys from this schema */ public Strict<T extends TSchema>(schema: T): T { return JSON.parse(JSON.stringify(schema)) }
/** Creates a string type */ public String<TCustomFormatOption extends string>(options: StringOptions<StringFormatOption | TCustomFormatOption> = {}): TString { return this.Create({ ...options, [Kind]: 'String', type: 'string' }) }
/** Creates a tuple type */ public Tuple<T extends TSchema[]>(items: [...T], options: SchemaOptions = {}): TTuple<T> { const additionalItems = false const minItems = items.length const maxItems = items.length const schema = (items.length > 0 ? { ...options, [Kind]: 'Tuple', type: 'array', items, additionalItems, minItems, maxItems } : { ...options, [Kind]: 'Tuple', type: 'array', minItems, maxItems }) as any return this.Create(schema) }
/** Creates a undefined type */ public Undefined(options: SchemaOptions = {}): TUndefined { return this.Create({ ...options, [Kind]: 'Undefined', type: 'object', specialized: 'Undefined' }) }
/** Creates a union type */ public Union<T extends TSchema[]>(items: [...T], options: SchemaOptions = {}): TUnion<T> { return this.Create({ ...options, [Kind]: 'Union', anyOf: items }) }
/** Creates a Uint8Array type */ public Uint8Array(options: Uint8ArrayOptions = {}): TUint8Array { return this.Create({ ...options, [Kind]: 'Uint8Array', type: 'object', specialized: 'Uint8Array' }) }
/** Creates an unknown type */ public Unknown(options: SchemaOptions = {}): TUnknown { return this.Create({ ...options, [Kind]: 'Unknown' }) }
/** Creates a user defined schema that infers as type T */ public Unsafe<T>(options: UnsafeOptions = {}): TUnsafe<T> { return this.Create({ ...options, [Kind]: options[Kind] || 'Unsafe' }) }
/** Creates a void type */ public Void(options: SchemaOptions = {}): TVoid { return this.Create({ ...options, [Kind]: 'Void', type: 'null' }) }
/** Use this function to return TSchema with static and params omitted */ protected Create<T>(schema: Omit<T, 'static' | 'params'>): T { return schema as any }
/** Clones the given value */ protected Clone(value: any): any { const isObject = (object: any): object is Record<string | symbol, any> => typeof object === 'object' && object !== null && !Array.isArray(object) const isArray = (object: any): object is any[] => typeof object === 'object' && object !== null && Array.isArray(object) if (isObject(value)) { return Object.keys(value).reduce( (acc, key) => ({ ...acc, [key]: this.Clone(value[key]), }), Object.getOwnPropertySymbols(value).reduce( (acc, key) => ({ ...acc, [key]: this.Clone(value[key]), }), {}, ), ) } else if (isArray(value)) { return value.map((item: any) => this.Clone(item)) } else { return value } }}
/** JSON Schema Type Builder with Static Type Resolution for TypeScript */export const Type = new TypeBuilder()