mirror of
https://github.com/zoriya/typebox.git
synced 2025-12-06 06:46:10 +00:00
3364 lines
153 KiB
TypeScript
3364 lines
153 KiB
TypeScript
/*--------------------------------------------------------------------------
|
|
|
|
@sinclair/typebox
|
|
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, 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 IN
|
|
THE SOFTWARE.
|
|
|
|
---------------------------------------------------------------------------*/
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Symbols
|
|
// --------------------------------------------------------------------------
|
|
export const Transform = Symbol.for('TypeBox.Transform')
|
|
export const Readonly = Symbol.for('TypeBox.Readonly')
|
|
export const Optional = Symbol.for('TypeBox.Optional')
|
|
export const Hint = Symbol.for('TypeBox.Hint')
|
|
export const Kind = Symbol.for('TypeBox.Kind')
|
|
// --------------------------------------------------------------------------
|
|
// Patterns
|
|
// --------------------------------------------------------------------------
|
|
export const PatternBoolean = '(true|false)'
|
|
export const PatternNumber = '(0|[1-9][0-9]*)'
|
|
export const PatternString = '(.*)'
|
|
export const PatternBooleanExact = `^${PatternBoolean}$`
|
|
export const PatternNumberExact = `^${PatternNumber}$`
|
|
export const PatternStringExact = `^${PatternString}$`
|
|
// --------------------------------------------------------------------------
|
|
// Helpers
|
|
// --------------------------------------------------------------------------
|
|
export type TupleToIntersect<T extends any[]> = T extends [infer I] ? I : T extends [infer I, ...infer R] ? I & TupleToIntersect<R> : never
|
|
export type TupleToUnion<T extends any[]> = { [K in keyof T]: T[K] }[number]
|
|
export type UnionToIntersect<U> = (U extends unknown ? (arg: U) => 0 : never) extends (arg: infer I) => 0 ? I : never
|
|
export type UnionLast<U> = UnionToIntersect<U extends unknown ? (x: U) => 0 : never> extends (x: infer L) => 0 ? L : never
|
|
export type UnionToTuple<U, L = UnionLast<U>> = [U] extends [never] ? [] : [...UnionToTuple<Exclude<U, L>>, L]
|
|
export type Discard<T extends unknown[], D extends unknown> = T extends [infer L, ...infer R] ? (L extends D ? Discard<R, D> : [L, ...Discard<R, D>]) : []
|
|
export type Flat<T> = T extends [] ? [] : T extends [infer L] ? [...Flat<L>] : T extends [infer L, ...infer R] ? [...Flat<L>, ...Flat<R>] : [T]
|
|
export type Trim<T> = T extends `${' '}${infer U}` ? Trim<U> : T extends `${infer U}${' '}` ? Trim<U> : T
|
|
export type Assert<T, E> = T extends E ? T : never
|
|
export type Evaluate<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
|
|
export type Ensure<T> = T extends infer U ? U : never
|
|
// --------------------------------------------------------------------------
|
|
// Type Assertions
|
|
// --------------------------------------------------------------------------
|
|
export type AssertProperties<T> = T extends TProperties ? T : TProperties
|
|
export type AssertRest<T, E extends TSchema[] = TSchema[]> = T extends E ? T : []
|
|
export type AssertType<T, E extends TSchema = TSchema> = T extends E ? T : TNever
|
|
// --------------------------------------------------------------------------
|
|
// Modifiers
|
|
// --------------------------------------------------------------------------
|
|
export type TModifier = TOptional<TSchema> | TReadonly<TSchema>
|
|
export type TReadonly<T extends TSchema> = T & { [Readonly]: 'Readonly' }
|
|
export type TOptional<T extends TSchema> = T & { [Optional]: 'Optional' }
|
|
// --------------------------------------------------------------------------
|
|
// Readonly Unwrap
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type ReadonlyUnwrapType<T extends TSchema> =
|
|
T extends TReadonly<infer S> ? ReadonlyUnwrapType<S> :
|
|
T extends TOptional<infer S> ? TOptional<ReadonlyUnwrapType<S>> :
|
|
T
|
|
// prettier-ignore
|
|
export type ReadonlyUnwrapRest<T extends TSchema[]> = T extends [infer L, ...infer R]
|
|
? L extends TReadonly<infer S>
|
|
? [ReadonlyUnwrapType<AssertType<S>>, ...ReadonlyUnwrapRest<AssertRest<R>>]
|
|
: [L, ...ReadonlyUnwrapRest<AssertRest<R>>]
|
|
: []
|
|
// --------------------------------------------------------------------------
|
|
// Optional Unwrap
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type OptionalUnwrapType<T extends TSchema> =
|
|
T extends TReadonly<infer S> ? TReadonly<OptionalUnwrapType<S>> :
|
|
T extends TOptional<infer S> ? OptionalUnwrapType<S> :
|
|
T
|
|
// prettier-ignore
|
|
export type OptionalUnwrapRest<T extends TSchema[]> = T extends [infer L, ...infer R]
|
|
? L extends TOptional<infer S>
|
|
? [OptionalUnwrapType<AssertType<S>>, ...OptionalUnwrapRest<AssertRest<R>>]
|
|
: [L, ...OptionalUnwrapRest<AssertRest<R>>]
|
|
: []
|
|
// --------------------------------------------------------------------------
|
|
// IntersectType
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type IntersectOptional<T extends TSchema[]> = T extends [infer L, ...infer R]
|
|
? L extends TOptional<AssertType<L>>
|
|
? IntersectOptional<AssertRest<R>>
|
|
: false
|
|
: true
|
|
// prettier-ignore
|
|
export type IntersectResolve<T extends TSchema[], U = OptionalUnwrapRest<AssertRest<T>>> = IntersectOptional<AssertRest<T>> extends true
|
|
? TOptional<TIntersect<AssertRest<U>>>
|
|
: TIntersect<AssertRest<U>>
|
|
// prettier-ignore
|
|
export type IntersectType<T extends TSchema[]> =
|
|
T extends [] ? TNever :
|
|
T extends [TSchema] ? AssertType<T[0]> :
|
|
IntersectResolve<T>
|
|
// --------------------------------------------------------------------------
|
|
// UnionType
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type UnionOptional<T extends TSchema[]> = T extends [infer L, ...infer R]
|
|
? L extends (TOptional<AssertType<L>>)
|
|
? true
|
|
: UnionOptional<AssertRest<R>>
|
|
: false
|
|
// prettier-ignore
|
|
export type UnionResolve<T extends TSchema[], U = OptionalUnwrapRest<AssertRest<T>>> = UnionOptional<AssertRest<T>> extends true
|
|
? TOptional<TUnion<AssertRest<U>>>
|
|
: TUnion<AssertRest<U>>
|
|
// prettier-ignore
|
|
export type UnionType<T extends TSchema[]> =
|
|
T extends [] ? TNever :
|
|
T extends [TSchema] ? AssertType<T[0]> :
|
|
UnionResolve<T>
|
|
// --------------------------------------------------------------------------
|
|
// TSchema
|
|
// --------------------------------------------------------------------------
|
|
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
|
|
/** Optional annotation for readOnly */
|
|
readOnly?: boolean
|
|
/** Optional annotation for writeOnly */
|
|
writeOnly?: boolean
|
|
[prop: string]: any
|
|
}
|
|
export interface TKind {
|
|
[Kind]: string
|
|
}
|
|
export interface TSchema extends SchemaOptions, TKind {
|
|
[Readonly]?: string
|
|
[Optional]?: string
|
|
[Hint]?: string
|
|
params: unknown[]
|
|
static: unknown
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TAnySchema
|
|
// --------------------------------------------------------------------------
|
|
export type TAnySchema =
|
|
| TSchema
|
|
| TAny
|
|
| TArray
|
|
| TAsyncIterator
|
|
| TBigInt
|
|
| TBoolean
|
|
| TConstructor
|
|
| TDate
|
|
| TEnum
|
|
| TFunction
|
|
| TInteger
|
|
| TIntersect
|
|
| TIterator
|
|
| TLiteral
|
|
| TNot
|
|
| TNull
|
|
| TNumber
|
|
| TObject
|
|
| TPromise
|
|
| TRecord
|
|
| TRef
|
|
| TString
|
|
| TSymbol
|
|
| TTemplateLiteral
|
|
| TThis
|
|
| TTuple
|
|
| TUndefined
|
|
| TUnion
|
|
| TUint8Array
|
|
| TUnknown
|
|
| TVoid
|
|
// --------------------------------------------------------------------------
|
|
// TNumeric
|
|
// --------------------------------------------------------------------------
|
|
export interface NumericOptions<N extends number | bigint> extends SchemaOptions {
|
|
exclusiveMaximum?: N
|
|
exclusiveMinimum?: N
|
|
maximum?: N
|
|
minimum?: N
|
|
multipleOf?: N
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TAny
|
|
// --------------------------------------------------------------------------
|
|
export interface TAny extends TSchema {
|
|
[Kind]: 'Any'
|
|
static: any
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TArray
|
|
// --------------------------------------------------------------------------
|
|
export interface ArrayOptions extends SchemaOptions {
|
|
/** The minimum number of items in this array */
|
|
minItems?: number
|
|
/** The maximum number of items in this array */
|
|
maxItems?: number
|
|
/** Should this schema contain unique items */
|
|
uniqueItems?: boolean
|
|
/** A schema for which some elements should match */
|
|
contains?: TSchema
|
|
/** A minimum number of contains schema matches */
|
|
minContains?: number
|
|
/** A maximum number of contains schema matches */
|
|
maxContains?: number
|
|
}
|
|
export interface TArray<T extends TSchema = TSchema> extends TSchema, ArrayOptions {
|
|
[Kind]: 'Array'
|
|
static: Static<T, this['params']>[]
|
|
type: 'array'
|
|
items: T
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TAsyncIterator
|
|
// --------------------------------------------------------------------------
|
|
export interface TAsyncIterator<T extends TSchema = TSchema> extends TSchema {
|
|
[Kind]: 'AsyncIterator'
|
|
static: AsyncIterableIterator<Static<T, this['params']>>
|
|
type: 'AsyncIterator'
|
|
items: T
|
|
}
|
|
// -------------------------------------------------------------------------------
|
|
// TAwaited
|
|
// -------------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type TAwaitedRest<T extends TSchema[]> = T extends [infer L, ...infer R]
|
|
? [TAwaited<AssertType<L>>, ...TAwaitedRest<AssertRest<R>>]
|
|
: []
|
|
// prettier-ignore
|
|
export type TAwaited<T extends TSchema> =
|
|
T extends TIntersect<infer S> ? TIntersect<TAwaitedRest<S>> :
|
|
T extends TUnion<infer S> ? TUnion<TAwaitedRest<S>> :
|
|
T extends TPromise<infer S> ? TAwaited<S> :
|
|
T
|
|
// --------------------------------------------------------------------------
|
|
// TBigInt
|
|
// --------------------------------------------------------------------------
|
|
export interface TBigInt extends TSchema, NumericOptions<bigint> {
|
|
[Kind]: 'BigInt'
|
|
static: bigint
|
|
type: 'bigint'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TBoolean
|
|
// --------------------------------------------------------------------------
|
|
export interface TBoolean extends TSchema {
|
|
[Kind]: 'Boolean'
|
|
static: boolean
|
|
type: 'boolean'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TConstructorParameters
|
|
// --------------------------------------------------------------------------
|
|
export type TConstructorParameters<T extends TConstructor<TSchema[], TSchema>> = TTuple<T['parameters']>
|
|
// --------------------------------------------------------------------------
|
|
// TInstanceType
|
|
// --------------------------------------------------------------------------
|
|
export type TInstanceType<T extends TConstructor<TSchema[], TSchema>> = T['returns']
|
|
// --------------------------------------------------------------------------
|
|
// TComposite
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type TCompositeKeys<T extends TObject[]> = T extends [infer L, ...infer R]
|
|
? keyof Assert<L, TObject>['properties'] | TCompositeKeys<Assert<R, TObject[]>>
|
|
: never
|
|
// prettier-ignore
|
|
export type TCompositeIndex<T extends TIntersect<TObject[]>, K extends string[]> = K extends [infer L, ...infer R]
|
|
? { [_ in Assert<L, string>]: TIndexType<T, Assert<L, string>> } & TCompositeIndex<T, Assert<R, string[]>>
|
|
: {}
|
|
// prettier-ignore
|
|
export type TCompositeReduce<T extends TObject[]> = UnionToTuple<TCompositeKeys<T>> extends infer K
|
|
? Evaluate<TCompositeIndex<TIntersect<T>, Assert<K, string[]>>>
|
|
: {} // ^ indexed via intersection of T
|
|
// prettier-ignore
|
|
export type TComposite<T extends TObject[]> = TIntersect<T> extends TIntersect
|
|
? TObject<TCompositeReduce<T>>
|
|
: TObject<{}>
|
|
// --------------------------------------------------------------------------
|
|
// TConstructor
|
|
// --------------------------------------------------------------------------
|
|
export type TConstructorParameterArray<T extends readonly TSchema[], P extends unknown[]> = [...{ [K in keyof T]: Static<AssertType<T[K]>, P> }]
|
|
export interface TConstructor<T extends TSchema[] = TSchema[], U extends TSchema = TSchema> extends TSchema {
|
|
[Kind]: 'Constructor'
|
|
static: new (...param: TConstructorParameterArray<T, this['params']>) => Static<U, this['params']>
|
|
type: 'constructor'
|
|
parameters: T
|
|
returns: U
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TDate
|
|
// --------------------------------------------------------------------------
|
|
export interface DateOptions extends SchemaOptions {
|
|
/** The exclusive maximum timestamp value */
|
|
exclusiveMaximumTimestamp?: number
|
|
/** The exclusive minimum timestamp value */
|
|
exclusiveMinimumTimestamp?: number
|
|
/** The maximum timestamp value */
|
|
maximumTimestamp?: number
|
|
/** The minimum timestamp value */
|
|
minimumTimestamp?: number
|
|
/** The multiple of timestamp value */
|
|
multipleOfTimestamp?: number
|
|
}
|
|
export interface TDate extends TSchema, DateOptions {
|
|
[Kind]: 'Date'
|
|
static: Date
|
|
type: 'date'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TEnum
|
|
// --------------------------------------------------------------------------
|
|
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<T[keyof T]>[]
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TExtends
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type TExtends<L extends TSchema, R extends TSchema, T extends TSchema, U extends TSchema> =
|
|
(Static<L> extends Static<R> ? T : U) extends infer O ?
|
|
UnionToTuple<O> extends [infer X, infer Y] ? TUnion<[AssertType<X>, AssertType<Y>]> : AssertType<O>
|
|
: never
|
|
// --------------------------------------------------------------------------
|
|
// TExclude
|
|
// --------------------------------------------------------------------------
|
|
export type TExcludeTemplateLiteralResult<T extends string> = UnionType<AssertRest<UnionToTuple<{ [K in T]: TLiteral<K> }[T]>>>
|
|
export type TExcludeTemplateLiteral<T extends TTemplateLiteral, U extends TSchema> = Exclude<Static<T>, Static<U>> extends infer S ? TExcludeTemplateLiteralResult<Assert<S, string>> : never
|
|
// prettier-ignore
|
|
export type TExcludeArray<T extends TSchema[], U extends TSchema> = AssertRest<UnionToTuple<{
|
|
[K in keyof T]: Static<AssertType<T[K]>> extends Static<U> ? never : T[K]
|
|
}[number]>> extends infer R ? UnionType<AssertRest<R>> : never
|
|
// prettier-ignore
|
|
export type TExclude<T extends TSchema, U extends TSchema> =
|
|
T extends TTemplateLiteral ? TExcludeTemplateLiteral<T, U> :
|
|
T extends TUnion<infer S> ? TExcludeArray<S, U> :
|
|
T extends U ? TNever : T
|
|
// --------------------------------------------------------------------------
|
|
// TExtract
|
|
// --------------------------------------------------------------------------
|
|
export type TExtractTemplateLiteralResult<T extends string> = UnionType<AssertRest<UnionToTuple<{ [K in T]: TLiteral<K> }[T]>>>
|
|
export type TExtractTemplateLiteral<T extends TTemplateLiteral, U extends TSchema> = Extract<Static<T>, Static<U>> extends infer S ? TExtractTemplateLiteralResult<Assert<S, string>> : never
|
|
// prettier-ignore
|
|
export type TExtractArray<T extends TSchema[], U extends TSchema> = AssertRest<UnionToTuple<
|
|
{[K in keyof T]: Static<AssertType<T[K]>> extends Static<U> ? T[K] : never
|
|
}[number]>> extends infer R ? UnionType<AssertRest<R>> : never
|
|
// prettier-ignore
|
|
export type TExtract<T extends TSchema, U extends TSchema> =
|
|
T extends TTemplateLiteral ? TExtractTemplateLiteral<T, U> :
|
|
T extends TUnion<infer S> ? TExtractArray<S, U> :
|
|
T extends U ? T : T
|
|
// --------------------------------------------------------------------------
|
|
// TFunction
|
|
// --------------------------------------------------------------------------
|
|
export type TFunctionParameters<T extends TSchema[], P extends unknown[]> = [...{ [K in keyof T]: Static<AssertType<T[K]>, P> }]
|
|
export interface TFunction<T extends 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
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TIndex
|
|
// --------------------------------------------------------------------------
|
|
export type TIndexRest<T extends TSchema[], K extends TPropertyKey> = T extends [infer L, ...infer R] ? [TIndexType<AssertType<L>, K>, ...TIndexRest<AssertRest<R>, K>] : []
|
|
export type TIndexProperty<T extends TProperties, K extends TPropertyKey> = K extends keyof T ? [T[K]] : []
|
|
export type TIndexTuple<T extends TSchema[], K extends TPropertyKey> = K extends keyof T ? [T[K]] : []
|
|
// prettier-ignore
|
|
export type TIndexType<T extends TSchema, K extends TPropertyKey> =
|
|
T extends TRecursive<infer S> ? TIndexType<S, K> :
|
|
T extends TIntersect<infer S> ? IntersectType<AssertRest<Discard<Flat<TIndexRest<S, K>>, TNever>>> :
|
|
T extends TUnion<infer S> ? UnionType<AssertRest<Flat<TIndexRest<S, K>>>> :
|
|
T extends TObject<infer S> ? UnionType<AssertRest<Flat<TIndexProperty<S, K>>>> :
|
|
T extends TTuple<infer S> ? UnionType<AssertRest<Flat<TIndexTuple<S, K>>>> :
|
|
[]
|
|
// prettier-ignore
|
|
export type TIndexRestMany<T extends TSchema, K extends TPropertyKey[]> =
|
|
K extends [infer L, ...infer R] ? [TIndexType<T, Assert<L, TPropertyKey>>, ...TIndexRestMany<T, Assert<R, TPropertyKey[]>>] :
|
|
[]
|
|
// prettier-ignore
|
|
export type TIndex<T extends TSchema, K extends TPropertyKey[]> =
|
|
T extends TRecursive<infer S> ? TIndex<S, K> :
|
|
T extends TIntersect ? UnionType<Flat<TIndexRestMany<T, K>>> :
|
|
T extends TUnion ? UnionType<Flat<TIndexRestMany<T, K>>> :
|
|
T extends TObject ? UnionType<Flat<TIndexRestMany<T, K>>> :
|
|
T extends TTuple ? UnionType<Flat<TIndexRestMany<T, K>>> :
|
|
TNever
|
|
// --------------------------------------------------------------------------
|
|
// TIntrinsic
|
|
// --------------------------------------------------------------------------
|
|
export type TIntrinsicMode = 'Uppercase' | 'Lowercase' | 'Capitalize' | 'Uncapitalize'
|
|
// prettier-ignore
|
|
export type TIntrinsicTemplateLiteral<T extends TTemplateLiteralKind[], M extends TIntrinsicMode> =
|
|
M extends ('Lowercase' | 'Uppercase') ? T extends [infer L, ...infer R] ? [TIntrinsic<AssertType<L>, M>, ...TIntrinsicTemplateLiteral<AssertRest<R>, M>] : T :
|
|
M extends ('Capitalize' | 'Uncapitalize') ? T extends [infer L, ...infer R] ? [TIntrinsic<AssertType<L>, M>, ...R] : T :
|
|
T
|
|
// prettier-ignore
|
|
export type TIntrinsicLiteral<T, M extends TIntrinsicMode> =
|
|
T extends string ?
|
|
M extends 'Uncapitalize' ? Uncapitalize<T> :
|
|
M extends 'Capitalize' ? Capitalize<T> :
|
|
M extends 'Uppercase' ? Uppercase<T> :
|
|
M extends 'Lowercase' ? Lowercase<T> :
|
|
string
|
|
: T
|
|
// prettier-ignore
|
|
export type TIntrinsicRest<T extends TSchema[], M extends TIntrinsicMode> = T extends [infer L, ...infer R]
|
|
? [TIntrinsic<AssertType<L>, M>, ...TIntrinsicRest<AssertRest<R>, M>]
|
|
: []
|
|
// prettier-ignore
|
|
export type TIntrinsic<T extends TSchema, M extends TIntrinsicMode> =
|
|
T extends TTemplateLiteral<infer S> ? TTemplateLiteral<TIntrinsicTemplateLiteral<S, M>> :
|
|
T extends TUnion<infer S> ? TUnion<TIntrinsicRest<S, M>> :
|
|
T extends TLiteral<infer S> ? TLiteral<TIntrinsicLiteral<S, M>> :
|
|
T
|
|
// --------------------------------------------------------------------------
|
|
// TInteger
|
|
// --------------------------------------------------------------------------
|
|
export interface TInteger extends TSchema, NumericOptions<number> {
|
|
[Kind]: 'Integer'
|
|
static: number
|
|
type: 'integer'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TIntersect
|
|
// --------------------------------------------------------------------------
|
|
export type TUnevaluatedProperties = undefined | TSchema | boolean
|
|
export interface IntersectOptions extends SchemaOptions {
|
|
unevaluatedProperties?: TUnevaluatedProperties
|
|
}
|
|
export interface TIntersect<T extends TSchema[] = TSchema[]> extends TSchema, IntersectOptions {
|
|
[Kind]: 'Intersect'
|
|
static: TupleToIntersect<{ [K in keyof T]: Static<AssertType<T[K]>, this['params']> }>
|
|
type?: 'object'
|
|
allOf: [...T]
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TIterator
|
|
// --------------------------------------------------------------------------
|
|
export interface TIterator<T extends TSchema = TSchema> extends TSchema {
|
|
[Kind]: 'Iterator'
|
|
static: IterableIterator<Static<T, this['params']>>
|
|
type: 'Iterator'
|
|
items: T
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TKeyOf
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type TKeyOfProperties<T extends TSchema> = Discard<Static<T> extends infer S
|
|
? UnionToTuple<{[K in keyof S]: TLiteral<Assert<K, TLiteralValue>>}[keyof S]>
|
|
: [], undefined> // note: optional properties produce undefined types in tuple result. discard.
|
|
// prettier-ignore
|
|
export type TKeyOfIndicesArray<T extends TSchema[]> = UnionToTuple<keyof T & `${number}`>
|
|
// prettier-ignore
|
|
export type TKeyOfIndices<T extends TSchema[]> = AssertRest<TKeyOfIndicesArray<T> extends infer R ? {
|
|
[K in keyof R] : TLiteral<Assert<R[K], TLiteralValue>>
|
|
}: []>
|
|
// prettier-ignore
|
|
export type TKeyOf<T extends TSchema = TSchema> = (
|
|
T extends TRecursive<infer S> ? TKeyOfProperties<S> :
|
|
T extends TIntersect ? TKeyOfProperties<T> :
|
|
T extends TUnion ? TKeyOfProperties<T> :
|
|
T extends TObject ? TKeyOfProperties<T> :
|
|
T extends TTuple<infer K> ? TKeyOfIndices<K> :
|
|
T extends TArray ? [TNumber] :
|
|
T extends TRecord<infer K> ? [K] :
|
|
[]
|
|
) extends infer R ? UnionType<AssertRest<R>> : never
|
|
// --------------------------------------------------------------------------
|
|
// TLiteral
|
|
// --------------------------------------------------------------------------
|
|
export type TLiteralValue = boolean | number | string // | bigint - supported but variant disable due to potential numeric type conflicts
|
|
export type TLiteralBoolean = TLiteral<boolean>
|
|
export type TLiteralNumber = TLiteral<number>
|
|
export type TLiteralString = TLiteral<string>
|
|
export interface TLiteral<T extends TLiteralValue = TLiteralValue> extends TSchema {
|
|
[Kind]: 'Literal'
|
|
static: T
|
|
const: T
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TNever
|
|
// --------------------------------------------------------------------------
|
|
export interface TNever extends TSchema {
|
|
[Kind]: 'Never'
|
|
static: never
|
|
not: {}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TNot
|
|
// --------------------------------------------------------------------------
|
|
export interface TNot<T extends TSchema = TSchema> extends TSchema {
|
|
[Kind]: 'Not'
|
|
static: T extends TNot<infer U> ? Static<U> : unknown
|
|
not: T
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TNull
|
|
// --------------------------------------------------------------------------
|
|
export interface TNull extends TSchema {
|
|
[Kind]: 'Null'
|
|
static: null
|
|
type: 'null'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TNumber
|
|
// --------------------------------------------------------------------------
|
|
export interface TNumber extends TSchema, NumericOptions<number> {
|
|
[Kind]: 'Number'
|
|
static: number
|
|
type: 'number'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TObject
|
|
// --------------------------------------------------------------------------
|
|
export type ReadonlyOptionalPropertyKeys<T extends TProperties> = { [K in keyof T]: T[K] extends TReadonly<TSchema> ? (T[K] extends TOptional<T[K]> ? K : never) : never }[keyof T]
|
|
export type ReadonlyPropertyKeys<T extends TProperties> = { [K in keyof T]: T[K] extends TReadonly<TSchema> ? (T[K] extends TOptional<T[K]> ? never : K) : never }[keyof T]
|
|
export type OptionalPropertyKeys<T extends TProperties> = { [K in keyof T]: T[K] extends TOptional<TSchema> ? (T[K] extends TReadonly<T[K]> ? never : K) : never }[keyof T]
|
|
export type RequiredPropertyKeys<T extends TProperties> = keyof Omit<T, ReadonlyOptionalPropertyKeys<T> | ReadonlyPropertyKeys<T> | OptionalPropertyKeys<T>>
|
|
// prettier-ignore
|
|
export type PropertiesReducer<T extends TProperties, R extends Record<keyof any, unknown>> = Evaluate<(
|
|
Readonly<Partial<Pick<R, ReadonlyOptionalPropertyKeys<T>>>> &
|
|
Readonly<Pick<R, ReadonlyPropertyKeys<T>>> &
|
|
Partial<Pick<R, OptionalPropertyKeys<T>>> &
|
|
Required<Pick<R, RequiredPropertyKeys<T>>>
|
|
)>
|
|
// prettier-ignore
|
|
export type PropertiesReduce<T extends TProperties, P extends unknown[]> = PropertiesReducer<T, {
|
|
[K in keyof T]: Static<T[K], P>
|
|
}>
|
|
export type TPropertyKey = string | number
|
|
export type TProperties = Record<TPropertyKey, 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 type TAdditionalProperties = undefined | TSchema | boolean
|
|
export interface ObjectOptions extends SchemaOptions {
|
|
/** Additional property constraints for this object */
|
|
additionalProperties?: TAdditionalProperties
|
|
/** The minimum number of properties allowed on this object */
|
|
minProperties?: number
|
|
/** The maximum number of properties allowed on this object */
|
|
maxProperties?: number
|
|
}
|
|
export interface TObject<T extends TProperties = TProperties> extends TSchema, ObjectOptions {
|
|
[Kind]: 'Object'
|
|
static: PropertiesReduce<T, this['params']>
|
|
additionalProperties?: TAdditionalProperties
|
|
type: 'object'
|
|
properties: T
|
|
required?: string[]
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TOmit
|
|
// --------------------------------------------------------------------------
|
|
export type TOmitProperties<T extends TProperties, K extends keyof any> = Evaluate<AssertProperties<Omit<T, K>>>
|
|
export type TOmitRest<T extends TSchema[], K extends keyof any> = AssertRest<{ [K2 in keyof T]: TOmit<AssertType<T[K2]>, K> }>
|
|
// prettier-ignore
|
|
export type TOmit<T extends TSchema = TSchema, K extends keyof any = keyof any> =
|
|
T extends TRecursive<infer S> ? TRecursive<TOmit<S, K>> :
|
|
T extends TIntersect<infer S> ? TIntersect<TOmitRest<S, K>> :
|
|
T extends TUnion<infer S> ? TUnion<TOmitRest<S, K>> :
|
|
T extends TObject<infer S> ? TObject<TOmitProperties<S, K>> :
|
|
T
|
|
// --------------------------------------------------------------------------
|
|
// TParameters
|
|
// --------------------------------------------------------------------------
|
|
export type TParameters<T extends TFunction> = Ensure<TTuple<T['parameters']>>
|
|
// --------------------------------------------------------------------------
|
|
// TPartial
|
|
// --------------------------------------------------------------------------
|
|
export type TPartialObjectArray<T extends TObject[]> = AssertRest<{ [K in keyof T]: TPartial<AssertType<T[K], TObject>> }, TObject[]>
|
|
export type TPartialRest<T extends TSchema[]> = AssertRest<{ [K in keyof T]: TPartial<AssertType<T[K]>> }>
|
|
// prettier-ignore
|
|
export type TPartialProperties<T extends TProperties> = Evaluate<AssertProperties<{
|
|
[K in keyof T]: TOptional<T[K]>
|
|
}>>
|
|
// prettier-ignore
|
|
export type TPartial<T extends TSchema> =
|
|
T extends TRecursive<infer S> ? TRecursive<TPartial<S>> :
|
|
T extends TIntersect<infer S> ? TIntersect<TPartialRest<S>> :
|
|
T extends TUnion<infer S> ? TUnion<TPartialRest<S>> :
|
|
T extends TObject<infer S> ? TObject<TPartialProperties<S>> :
|
|
T
|
|
// --------------------------------------------------------------------------
|
|
// TPick
|
|
// --------------------------------------------------------------------------
|
|
// Note the key K will overlap for varying TProperties gathered via recursive union and intersect traversal. Because of this,
|
|
// we need to extract only keys assignable to T on K2. This behavior is only required for Pick only.
|
|
// prettier-ignore
|
|
export type TPickProperties<T extends TProperties, K extends keyof any> =
|
|
Pick<T, Assert<Extract<K, keyof T>, keyof T>> extends infer R ? ({
|
|
[K in keyof R]: AssertType<R[K]> extends TSchema ? R[K] : never
|
|
}): never
|
|
export type TPickRest<T extends TSchema[], K extends keyof any> = { [K2 in keyof T]: TPick<AssertType<T[K2]>, K> }
|
|
// prettier-ignore
|
|
export type TPick<T extends TSchema = TSchema, K extends keyof any = keyof any> =
|
|
T extends TRecursive<infer S> ? TRecursive<TPick<S, K>> :
|
|
T extends TIntersect<infer S> ? TIntersect<TPickRest<S, K>> :
|
|
T extends TUnion<infer S> ? TUnion<TPickRest<S, K>> :
|
|
T extends TObject<infer S> ? TObject<TPickProperties<S, K>> :
|
|
T
|
|
// --------------------------------------------------------------------------
|
|
// TPromise
|
|
// --------------------------------------------------------------------------
|
|
export interface TPromise<T extends TSchema = TSchema> extends TSchema {
|
|
[Kind]: 'Promise'
|
|
static: Promise<Static<T, this['params']>>
|
|
type: 'promise'
|
|
item: TSchema
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TRecord
|
|
// --------------------------------------------------------------------------
|
|
export type TRecordFromUnionLiteralString<K extends TLiteralString, T extends TSchema> = { [_ in K['const']]: T }
|
|
export type TRecordFromUnionLiteralNumber<K extends TLiteralNumber, T extends TSchema> = { [_ in K['const']]: T }
|
|
// prettier-ignore
|
|
export type TRecordFromUnionRest<K extends TSchema[], T extends TSchema> = K extends [infer L, ...infer R] ? (
|
|
L extends TUnion<infer S> ? TRecordFromUnionRest<S, T> & TRecordFromUnionRest<AssertRest<R>, T> :
|
|
L extends TLiteralString ? TRecordFromUnionLiteralString<L, T> & TRecordFromUnionRest<AssertRest<R>, T> :
|
|
L extends TLiteralNumber ? TRecordFromUnionLiteralNumber<L, T> & TRecordFromUnionRest<AssertRest<R>, T> :
|
|
{}) : {}
|
|
export type TRecordFromUnion<K extends TSchema[], T extends TSchema> = Ensure<TObject<AssertProperties<Evaluate<TRecordFromUnionRest<K, T>>>>>
|
|
export type TRecordFromTemplateLiteralKeyInfinite<T extends TSchema> = Ensure<TRecord<TString, T>>
|
|
export type TRecordFromTemplateLiteralKeyFinite<K extends TTemplateLiteral, T extends TSchema, I = Static<K>> = Ensure<TObject<Evaluate<{ [_ in Assert<I, string>]: T }>>>
|
|
// prettier-ignore
|
|
export type TRecordFromTemplateLiteralKey<K extends TTemplateLiteral, T extends TSchema> = IsTemplateLiteralFinite<K> extends false
|
|
? TRecordFromTemplateLiteralKeyInfinite<T>
|
|
: TRecordFromTemplateLiteralKeyFinite<K, T>
|
|
export type TRecordFromLiteralStringKey<K extends TLiteralString, T extends TSchema> = Ensure<TObject<{ [_ in K['const']]: T }>>
|
|
export type TRecordFromLiteralNumberKey<K extends TLiteralNumber, T extends TSchema> = Ensure<TObject<{ [_ in K['const']]: T }>>
|
|
export type TRecordFromStringKey<K extends TString, T extends TSchema> = Ensure<TRecord<K, T>>
|
|
export type TRecordFromNumberKey<K extends TNumber, T extends TSchema> = Ensure<TRecord<K, T>>
|
|
export type TRecordFromIntegerKey<K extends TInteger, T extends TSchema> = Ensure<TRecord<K, T>>
|
|
// prettier-ignore
|
|
export type TRecordResolve<K extends TSchema, T extends TSchema> =
|
|
K extends TUnion<infer S> ? TRecordFromUnion<S, T> :
|
|
K extends TTemplateLiteral ? TRecordFromTemplateLiteralKey<K, T> :
|
|
K extends TLiteralString ? TRecordFromLiteralStringKey<K, T> :
|
|
K extends TLiteralNumber ? TRecordFromLiteralNumberKey<K, T> :
|
|
K extends TString ? TRecordFromStringKey<K, T> :
|
|
K extends TNumber ? TRecordFromNumberKey<K, T> :
|
|
K extends TInteger ? TRecordFromIntegerKey<K, T> :
|
|
TNever
|
|
export interface TRecord<K extends TSchema = TSchema, T extends TSchema = TSchema> extends TSchema {
|
|
[Kind]: 'Record'
|
|
static: Record<Assert<Static<K>, string | number>, Static<T, this['params']>>
|
|
type: 'object'
|
|
patternProperties: { [pattern: string]: T }
|
|
additionalProperties: TAdditionalProperties
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TRecursive
|
|
// --------------------------------------------------------------------------
|
|
export interface TThis extends TSchema {
|
|
[Kind]: 'This'
|
|
static: this['params'][0]
|
|
$ref: string
|
|
}
|
|
export type TRecursiveReduce<T extends TSchema> = Static<T, [TRecursiveReduce<T>]>
|
|
export interface TRecursive<T extends TSchema> extends TSchema {
|
|
[Hint]: 'Recursive'
|
|
static: TRecursiveReduce<T>
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TRef
|
|
// --------------------------------------------------------------------------
|
|
export interface TRef<T extends TSchema = TSchema> extends TSchema {
|
|
[Kind]: 'Ref'
|
|
static: Static<T, this['params']>
|
|
$ref: string
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TRest
|
|
// --------------------------------------------------------------------------
|
|
export type TRest<T extends TSchema> = T extends TIntersect<infer R> ? R : T extends TUnion<infer R> ? R : T extends TTuple<infer R> ? R : []
|
|
// --------------------------------------------------------------------------
|
|
// TReturnType
|
|
// --------------------------------------------------------------------------
|
|
export type TReturnType<T extends TFunction> = T['returns']
|
|
// --------------------------------------------------------------------------
|
|
// TRequired
|
|
// --------------------------------------------------------------------------
|
|
export type TRequiredRest<T extends TSchema[]> = AssertRest<{ [K in keyof T]: TRequired<AssertType<T[K]>> }>
|
|
// prettier-ignore
|
|
export type TRequiredProperties<T extends TProperties> = Evaluate<AssertProperties<{
|
|
[K in keyof T]: T[K] extends TOptional<infer S> ? S : T[K]
|
|
}>>
|
|
// prettier-ignore
|
|
export type TRequired<T extends TSchema> =
|
|
T extends TRecursive<infer S> ? TRecursive<TRequired<S>> :
|
|
T extends TIntersect<infer S> ? TIntersect<TRequiredRest<S>> :
|
|
T extends TUnion<infer S> ? TUnion<TRequiredRest<S>> :
|
|
T extends TObject<infer S> ? TObject<TRequiredProperties<S>> :
|
|
T
|
|
// --------------------------------------------------------------------------
|
|
// TString
|
|
// --------------------------------------------------------------------------
|
|
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'
|
|
| ({} & string)
|
|
// prettier-ignore
|
|
export type StringContentEncodingOption =
|
|
| '7bit'
|
|
| '8bit'
|
|
| 'binary'
|
|
| 'quoted-printable'
|
|
| 'base64'
|
|
| ({} & string)
|
|
export interface StringOptions extends SchemaOptions {
|
|
/** The maximum string length */
|
|
maxLength?: number
|
|
/** The minimum string length */
|
|
minLength?: number
|
|
/** A regular expression pattern this string should match */
|
|
pattern?: string
|
|
/** A format this string should match */
|
|
format?: StringFormatOption
|
|
/** The content encoding for this string */
|
|
contentEncoding?: StringContentEncodingOption
|
|
/** The content media type for this string */
|
|
contentMediaType?: string
|
|
}
|
|
export interface TString extends TSchema, StringOptions {
|
|
[Kind]: 'String'
|
|
static: string
|
|
type: 'string'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TSymbol
|
|
// --------------------------------------------------------------------------
|
|
export type SymbolValue = string | number | undefined
|
|
export interface TSymbol extends TSchema, SchemaOptions {
|
|
[Kind]: 'Symbol'
|
|
static: symbol
|
|
type: 'symbol'
|
|
}
|
|
// -------------------------------------------------------------------------
|
|
// TTemplateLiteralParserDsl
|
|
// -------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type TTemplateLiteralDslParserUnionLiteral<T extends string> =
|
|
T extends `${infer L}|${infer R}` ? [TLiteral<Trim<L>>, ...TTemplateLiteralDslParserUnionLiteral<R>] :
|
|
T extends `${infer L}` ? [TLiteral<Trim<L>>] :
|
|
[]
|
|
export type TTemplateLiteralDslParserUnion<T extends string> = UnionType<TTemplateLiteralDslParserUnionLiteral<T>>
|
|
// prettier-ignore
|
|
export type TTemplateLiteralDslParserTerminal<T extends string> =
|
|
T extends 'boolean' ? TBoolean :
|
|
T extends 'bigint' ? TBigInt :
|
|
T extends 'number' ? TNumber :
|
|
T extends 'string' ? TString :
|
|
TTemplateLiteralDslParserUnion<T>
|
|
// prettier-ignore
|
|
export type TTemplateLiteralDslParserTemplate<T extends string> =
|
|
T extends `{${infer L}}${infer R}` ? [TTemplateLiteralDslParserTerminal<L>, ...TTemplateLiteralDslParserTemplate<R>] :
|
|
T extends `${infer L}$${infer R}` ? [TLiteral<L>, ...TTemplateLiteralDslParserTemplate<R>] :
|
|
T extends `${infer L}` ? [TLiteral<L>] :
|
|
[]
|
|
export type TTemplateLiteralDslParser<T extends string> = Ensure<TTemplateLiteral<Assert<TTemplateLiteralDslParserTemplate<T>, TTemplateLiteralKind[]>>>
|
|
// --------------------------------------------------------------------------
|
|
// TTemplateLiteral
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type IsTemplateLiteralFiniteCheck<T> =
|
|
T extends TTemplateLiteral<infer U> ? IsTemplateLiteralFiniteArray<Assert<U, TTemplateLiteralKind[]>> :
|
|
T extends TUnion<infer U> ? IsTemplateLiteralFiniteArray<Assert<U, TTemplateLiteralKind[]>> :
|
|
T extends TString ? false :
|
|
T extends TBoolean ? false :
|
|
T extends TNumber ? false :
|
|
T extends TInteger ? false :
|
|
T extends TBigInt ? false :
|
|
T extends TLiteral ? true :
|
|
false
|
|
// prettier-ignore
|
|
export type IsTemplateLiteralFiniteArray<T extends TTemplateLiteralKind[]> =
|
|
T extends [infer L, ...infer R] ? IsTemplateLiteralFiniteCheck<L> extends false ? false : IsTemplateLiteralFiniteArray<Assert<R, TTemplateLiteralKind[]>> :
|
|
true
|
|
export type IsTemplateLiteralFinite<T> = T extends TTemplateLiteral<infer U> ? IsTemplateLiteralFiniteArray<U> : false
|
|
export type TTemplateLiteralKind = TUnion | TLiteral | TInteger | TTemplateLiteral | TNumber | TBigInt | TString | TBoolean | TNever
|
|
// prettier-ignore
|
|
export type TTemplateLiteralConst<T, Acc extends string> =
|
|
T extends TUnion<infer U> ? { [K in keyof U]: TTemplateLiteralUnion<Assert<[U[K]], TTemplateLiteralKind[]>, Acc> }[number] :
|
|
T extends TTemplateLiteral ? `${Static<T>}` :
|
|
T extends TLiteral<infer U> ? `${U}` :
|
|
T extends TString ? `${string}` :
|
|
T extends TNumber ? `${number}` :
|
|
T extends TBigInt ? `${bigint}` :
|
|
T extends TBoolean ? `${boolean}` :
|
|
never
|
|
// prettier-ignore
|
|
export type TTemplateLiteralUnion<T extends TTemplateLiteralKind[], Acc extends string = ''> =
|
|
T extends [infer L, ...infer R] ? `${TTemplateLiteralConst<L, Acc>}${TTemplateLiteralUnion<Assert<R, TTemplateLiteralKind[]>, Acc>}` :
|
|
Acc
|
|
export type TTemplateLiteralKeyRest<T extends TTemplateLiteral> = Assert<UnionToTuple<Static<T>>, TPropertyKey[]>
|
|
export interface TTemplateLiteral<T extends TTemplateLiteralKind[] = TTemplateLiteralKind[]> extends TSchema {
|
|
[Kind]: 'TemplateLiteral'
|
|
static: TTemplateLiteralUnion<T>
|
|
type: 'string'
|
|
pattern: string // todo: it may be possible to infer this pattern
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TTransform
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type DecodeStaticProperties<T extends TProperties> = {
|
|
[K in keyof T]: DecodeStaticType<T[K]>
|
|
}
|
|
// prettier-ignore
|
|
export type DecodeStaticRest<T extends TSchema[]> = T extends [infer L, ...infer R]
|
|
? [DecodeStaticType<AssertType<L>>, ...DecodeStaticRest<AssertRest<R>>]
|
|
: []
|
|
// prettier-ignore
|
|
export type DecodeStaticType<T extends TSchema> =
|
|
T extends TTransform<infer _, infer R> ? TUnsafe<R> :
|
|
T extends TArray<infer S> ? TArray<DecodeStaticType<S>> :
|
|
T extends TAsyncIterator<infer S> ? TAsyncIterator<DecodeStaticType<S>> :
|
|
T extends TConstructor<infer P, infer R> ? TConstructor<AssertRest<DecodeStaticRest<P>>, DecodeStaticType<R>> :
|
|
T extends TFunction<infer P, infer R> ? TFunction<AssertRest<DecodeStaticRest<P>>, DecodeStaticType<R>> :
|
|
T extends TIntersect<infer S> ? TIntersect<AssertRest<DecodeStaticRest<S>>> :
|
|
T extends TIterator<infer S> ? TIterator<DecodeStaticType<S>> :
|
|
T extends TNot<infer S> ? TNot<DecodeStaticType<S>> :
|
|
T extends TObject<infer S> ? TObject<Evaluate<DecodeStaticProperties<S>>> :
|
|
T extends TPromise<infer S> ? TPromise<DecodeStaticType<S>> :
|
|
T extends TRecord<infer K, infer S> ? TRecord<K, DecodeStaticType<S>> :
|
|
T extends TRecursive<infer S> ? TRecursive<DecodeStaticType<S>> :
|
|
T extends TRef<infer S> ? TRef<DecodeStaticType<S>> :
|
|
T extends TTuple<infer S> ? TTuple<AssertRest<DecodeStaticRest<S>>> :
|
|
T extends TUnion<infer S> ? TUnion<AssertRest<DecodeStaticRest<S>>> :
|
|
T
|
|
export type TransformFunction<T = any, U = any> = (value: T) => U
|
|
export interface TransformOptions<I extends TSchema = TSchema, O extends unknown = unknown> {
|
|
Decode: TransformFunction<StaticDecode<I>, O>
|
|
Encode: TransformFunction<O, StaticDecode<I>>
|
|
}
|
|
export type TTransformResolve<T extends TSchema, P extends unknown[] = []> = T extends TTransform<infer _, infer S> ? S : Static<T, P>
|
|
export interface TTransform<I extends TSchema = TSchema, O extends unknown = unknown> extends TSchema {
|
|
static: TTransformResolve<I, this['params']>
|
|
[Transform]: TransformOptions<I, O>
|
|
[key: string]: any
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TTuple
|
|
// --------------------------------------------------------------------------
|
|
export type TTupleRest<T extends TSchema[], P extends unknown[]> = T extends [infer L, ...infer R] ? [Static<AssertType<L>, P>, ...TTupleRest<AssertRest<R>, P>] : []
|
|
export interface TTuple<T extends TSchema[] = TSchema[]> extends TSchema {
|
|
[Kind]: 'Tuple'
|
|
static: TTupleRest<T, this['params']>
|
|
type: 'array'
|
|
items?: T
|
|
additionalItems?: false
|
|
minItems: number
|
|
maxItems: number
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TUndefined
|
|
// --------------------------------------------------------------------------
|
|
export interface TUndefined extends TSchema {
|
|
[Kind]: 'Undefined'
|
|
static: undefined
|
|
type: 'undefined'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TUnionLiteral
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type TLiteralUnionReduce<T extends TLiteral<string | number>[]> =
|
|
T extends [infer L, ...infer R] ? [Assert<L, TLiteral<string | number>>['const'], ...TLiteralUnionReduce<Assert<R, TLiteral<string | number>[]>>] :
|
|
[]
|
|
// prettier-ignore
|
|
export type TUnionLiteralKeyRest<T extends TUnion<TLiteral<string | number>[]>> =
|
|
T extends TUnion<infer S> ? TLiteralUnionReduce<Assert<S, TLiteral<string | number>[]>> :
|
|
[]
|
|
// --------------------------------------------------------------------------
|
|
// TUnion
|
|
// --------------------------------------------------------------------------
|
|
// prettier-ignore
|
|
export type TUnionTemplateLiteral<T extends TTemplateLiteral, S extends string = Static<T>> = Ensure<UnionType<Assert<UnionToTuple<{[K in S]: TLiteral<K>}[S]>,TLiteral[]>>>
|
|
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
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TUint8Array
|
|
// --------------------------------------------------------------------------
|
|
export interface Uint8ArrayOptions extends SchemaOptions {
|
|
maxByteLength?: number
|
|
minByteLength?: number
|
|
}
|
|
export interface TUint8Array extends TSchema, Uint8ArrayOptions {
|
|
[Kind]: 'Uint8Array'
|
|
static: Uint8Array
|
|
type: 'uint8array'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TUnknown
|
|
// --------------------------------------------------------------------------
|
|
export interface TUnknown extends TSchema {
|
|
[Kind]: 'Unknown'
|
|
static: unknown
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TUnsafe
|
|
// --------------------------------------------------------------------------
|
|
export interface UnsafeOptions extends SchemaOptions {
|
|
[Kind]?: string
|
|
}
|
|
export interface TUnsafe<T> extends TSchema {
|
|
[Kind]: string
|
|
static: T
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TVoid
|
|
// --------------------------------------------------------------------------
|
|
export interface TVoid extends TSchema {
|
|
[Kind]: 'Void'
|
|
static: void
|
|
type: 'void'
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Static<T>
|
|
// --------------------------------------------------------------------------
|
|
/** Creates the decoded static form for a TypeBox type */
|
|
export type StaticDecode<T extends TSchema, P extends unknown[] = []> = Static<DecodeStaticType<T>, P>
|
|
/** Creates the encoded static form for a TypeBox type */
|
|
export type StaticEncode<T extends TSchema, P extends unknown[] = []> = Static<T, P>
|
|
/** Creates the static type for a TypeBox type */
|
|
export type Static<T extends TSchema, P extends unknown[] = []> = (T & { params: P })['static']
|
|
// --------------------------------------------------------------------------
|
|
// TypeRegistry
|
|
// --------------------------------------------------------------------------
|
|
export type TypeRegistryValidationFunction<TSchema> = (schema: TSchema, value: unknown) => boolean
|
|
/** A registry for user defined types */
|
|
export namespace TypeRegistry {
|
|
const map = new Map<string, TypeRegistryValidationFunction<any>>()
|
|
/** Returns the entries in this registry */
|
|
export function Entries() {
|
|
return new Map(map)
|
|
}
|
|
/** Clears all user defined types */
|
|
export function Clear() {
|
|
return map.clear()
|
|
}
|
|
/** Deletes a registered type */
|
|
export function Delete(kind: string) {
|
|
return map.delete(kind)
|
|
}
|
|
/** Returns true if this registry contains this kind */
|
|
export function Has(kind: string) {
|
|
return map.has(kind)
|
|
}
|
|
/** Sets a validation function for a user defined type */
|
|
export function Set<TSchema = unknown>(kind: string, func: TypeRegistryValidationFunction<TSchema>) {
|
|
map.set(kind, func)
|
|
}
|
|
/** Gets a custom validation function for a user defined type */
|
|
export function Get(kind: string) {
|
|
return map.get(kind)
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TypeBoxError
|
|
// --------------------------------------------------------------------------
|
|
export class TypeBoxError extends Error {
|
|
constructor(message: string) {
|
|
super(message)
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TypeRegistry
|
|
// --------------------------------------------------------------------------
|
|
export type FormatRegistryValidationFunction = (value: string) => boolean
|
|
/** A registry for user defined string formats */
|
|
export namespace FormatRegistry {
|
|
const map = new Map<string, FormatRegistryValidationFunction>()
|
|
/** Returns the entries in this registry */
|
|
export function Entries() {
|
|
return new Map(map)
|
|
}
|
|
/** Clears all user defined string formats */
|
|
export function Clear() {
|
|
return map.clear()
|
|
}
|
|
/** Deletes a registered format */
|
|
export function Delete(format: string) {
|
|
return map.delete(format)
|
|
}
|
|
/** Returns true if the user defined string format exists */
|
|
export function Has(format: string) {
|
|
return map.has(format)
|
|
}
|
|
/** Sets a validation function for a user defined string format */
|
|
export function Set(format: string, func: FormatRegistryValidationFunction) {
|
|
map.set(format, func)
|
|
}
|
|
/** Gets a validation function for a user defined string format */
|
|
export function Get(format: string) {
|
|
return map.get(format)
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// ValueGuard
|
|
// --------------------------------------------------------------------------
|
|
/** Provides functions to type guard raw JavaScript values */
|
|
export namespace ValueGuard {
|
|
/** Returns true if this value is an array */
|
|
export function IsArray(value: unknown): value is unknown[] {
|
|
return Array.isArray(value)
|
|
}
|
|
/** Returns true if this value is bigint */
|
|
export function IsBigInt(value: unknown): value is bigint {
|
|
return typeof value === 'bigint'
|
|
}
|
|
/** Returns true if this value is a boolean */
|
|
export function IsBoolean(value: unknown): value is boolean {
|
|
return typeof value === 'boolean'
|
|
}
|
|
/** Returns true if this value is null */
|
|
export function IsNull(value: unknown): value is null {
|
|
return value === null
|
|
}
|
|
/** Returns true if this value is number */
|
|
export function IsNumber(value: unknown): value is number {
|
|
return typeof value === 'number'
|
|
}
|
|
/** Returns true if this value is an object */
|
|
export function IsObject(value: unknown): value is Record<PropertyKey, unknown> {
|
|
return typeof value === 'object' && value !== null
|
|
}
|
|
/** Returns true if this value is string */
|
|
export function IsString(value: unknown): value is string {
|
|
return typeof value === 'string'
|
|
}
|
|
/** Returns true if this value is undefined */
|
|
export function IsUndefined(value: unknown): value is undefined {
|
|
return value === undefined
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TypeGuard
|
|
// --------------------------------------------------------------------------
|
|
export class TypeGuardUnknownTypeError extends TypeBoxError {}
|
|
/** Provides functions to test if JavaScript values are TypeBox types */
|
|
export namespace TypeGuard {
|
|
function IsPattern(value: unknown): value is string {
|
|
try {
|
|
new RegExp(value as string)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
function IsControlCharacterFree(value: unknown): value is string {
|
|
if (!ValueGuard.IsString(value)) return false
|
|
for (let i = 0; i < value.length; i++) {
|
|
const code = value.charCodeAt(i)
|
|
if ((code >= 7 && code <= 13) || code === 27 || code === 127) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
function IsAdditionalProperties(value: unknown): value is TAdditionalProperties {
|
|
return IsOptionalBoolean(value) || TSchema(value)
|
|
}
|
|
function IsOptionalBigInt(value: unknown): value is bigint | undefined {
|
|
return ValueGuard.IsUndefined(value) || ValueGuard.IsBigInt(value)
|
|
}
|
|
function IsOptionalNumber(value: unknown): value is number | undefined {
|
|
return ValueGuard.IsUndefined(value) || ValueGuard.IsNumber(value)
|
|
}
|
|
function IsOptionalBoolean(value: unknown): value is boolean | undefined {
|
|
return ValueGuard.IsUndefined(value) || ValueGuard.IsBoolean(value)
|
|
}
|
|
function IsOptionalString(value: unknown): value is string | undefined {
|
|
return ValueGuard.IsUndefined(value) || ValueGuard.IsString(value)
|
|
}
|
|
function IsOptionalPattern(value: unknown): value is string | undefined {
|
|
return ValueGuard.IsUndefined(value) || (ValueGuard.IsString(value) && IsControlCharacterFree(value) && IsPattern(value))
|
|
}
|
|
function IsOptionalFormat(value: unknown): value is string | undefined {
|
|
return ValueGuard.IsUndefined(value) || (ValueGuard.IsString(value) && IsControlCharacterFree(value))
|
|
}
|
|
function IsOptionalSchema(value: unknown): value is boolean | undefined {
|
|
return ValueGuard.IsUndefined(value) || TSchema(value)
|
|
}
|
|
/** Returns true if the given value is TAny */
|
|
export function TAny(schema: unknown): schema is TAny {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Any') &&
|
|
IsOptionalString(schema.$id)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TArray */
|
|
export function TArray(schema: unknown): schema is TArray {
|
|
return (
|
|
TKindOf(schema, 'Array') &&
|
|
schema.type === 'array' &&
|
|
IsOptionalString(schema.$id) &&
|
|
TSchema(schema.items) &&
|
|
IsOptionalNumber(schema.minItems) &&
|
|
IsOptionalNumber(schema.maxItems) &&
|
|
IsOptionalBoolean(schema.uniqueItems) &&
|
|
IsOptionalSchema(schema.contains) &&
|
|
IsOptionalNumber(schema.minContains) &&
|
|
IsOptionalNumber(schema.maxContains)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TAsyncIterator */
|
|
export function TAsyncIterator(schema: unknown): schema is TAsyncIterator {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'AsyncIterator') &&
|
|
schema.type === 'AsyncIterator' &&
|
|
IsOptionalString(schema.$id) &&
|
|
TSchema(schema.items)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TBigInt */
|
|
export function TBigInt(schema: unknown): schema is TBigInt {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'BigInt') &&
|
|
schema.type === 'bigint' &&
|
|
IsOptionalString(schema.$id) &&
|
|
IsOptionalBigInt(schema.exclusiveMaximum) &&
|
|
IsOptionalBigInt(schema.exclusiveMinimum) &&
|
|
IsOptionalBigInt(schema.maximum) &&
|
|
IsOptionalBigInt(schema.minimum) &&
|
|
IsOptionalBigInt(schema.multipleOf)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TBoolean */
|
|
export function TBoolean(schema: unknown): schema is TBoolean {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Boolean') &&
|
|
schema.type === 'boolean' &&
|
|
IsOptionalString(schema.$id)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TConstructor */
|
|
export function TConstructor(schema: unknown): schema is TConstructor {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Constructor') &&
|
|
schema.type === 'constructor' &&
|
|
IsOptionalString(schema.$id) &&
|
|
ValueGuard.IsArray(schema.parameters) &&
|
|
schema.parameters.every(schema => TSchema(schema)) &&
|
|
TSchema(schema.returns)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TDate */
|
|
export function TDate(schema: unknown): schema is TDate {
|
|
return (
|
|
TKindOf(schema, 'Date') &&
|
|
schema.type === 'Date' &&
|
|
IsOptionalString(schema.$id) &&
|
|
IsOptionalNumber(schema.exclusiveMaximumTimestamp) &&
|
|
IsOptionalNumber(schema.exclusiveMinimumTimestamp) &&
|
|
IsOptionalNumber(schema.maximumTimestamp) &&
|
|
IsOptionalNumber(schema.minimumTimestamp) &&
|
|
IsOptionalNumber(schema.multipleOfTimestamp)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TFunction */
|
|
export function TFunction(schema: unknown): schema is TFunction {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Function') &&
|
|
schema.type === 'function' &&
|
|
IsOptionalString(schema.$id) &&
|
|
ValueGuard.IsArray(schema.parameters) &&
|
|
schema.parameters.every(schema => TSchema(schema)) &&
|
|
TSchema(schema.returns)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TInteger */
|
|
export function TInteger(schema: unknown): schema is TInteger {
|
|
return (
|
|
TKindOf(schema, 'Integer') &&
|
|
schema.type === 'integer' &&
|
|
IsOptionalString(schema.$id) &&
|
|
IsOptionalNumber(schema.exclusiveMaximum) &&
|
|
IsOptionalNumber(schema.exclusiveMinimum) &&
|
|
IsOptionalNumber(schema.maximum) &&
|
|
IsOptionalNumber(schema.minimum) &&
|
|
IsOptionalNumber(schema.multipleOf)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TIntersect */
|
|
export function TIntersect(schema: unknown): schema is TIntersect {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Intersect') &&
|
|
(ValueGuard.IsString(schema.type) && schema.type !== 'object' ? false : true) &&
|
|
ValueGuard.IsArray(schema.allOf) &&
|
|
schema.allOf.every(schema => TSchema(schema) && !TTransform(schema)) &&
|
|
IsOptionalString(schema.type) &&
|
|
(IsOptionalBoolean(schema.unevaluatedProperties) || IsOptionalSchema(schema.unevaluatedProperties)) &&
|
|
IsOptionalString(schema.$id)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TIterator */
|
|
export function TIterator(schema: unknown): schema is TIterator {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Iterator') &&
|
|
schema.type === 'Iterator' &&
|
|
IsOptionalString(schema.$id) &&
|
|
TSchema(schema.items)
|
|
)
|
|
}
|
|
/** Returns true if the given value is a TKind with the given name. */
|
|
export function TKindOf<T extends string>(schema: unknown, kind: T): schema is Record<PropertyKey, unknown> & { [Kind]: T } {
|
|
return TKind(schema) && schema[Kind] === kind
|
|
}
|
|
/** Returns true if the given value is TKind */
|
|
export function TKind(schema: unknown): schema is Record<PropertyKey, unknown> & { [Kind]: string } {
|
|
return ValueGuard.IsObject(schema) && Kind in schema && ValueGuard.IsString(schema[Kind])
|
|
}
|
|
/** Returns true if the given value is TLiteral<string> */
|
|
export function TLiteralString(schema: unknown): schema is TLiteral<string> {
|
|
return TLiteral(schema) && ValueGuard.IsString(schema.const)
|
|
}
|
|
/** Returns true if the given value is TLiteral<number> */
|
|
export function TLiteralNumber(schema: unknown): schema is TLiteral<number> {
|
|
return TLiteral(schema) && ValueGuard.IsNumber(schema.const)
|
|
}
|
|
/** Returns true if the given value is TLiteral<boolean> */
|
|
export function TLiteralBoolean(schema: unknown): schema is TLiteral<boolean> {
|
|
return TLiteral(schema) && ValueGuard.IsBoolean(schema.const)
|
|
}
|
|
/** Returns true if the given value is TLiteral */
|
|
export function TLiteral(schema: unknown): schema is TLiteral {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Literal') &&
|
|
IsOptionalString(schema.$id) && (
|
|
ValueGuard.IsBoolean(schema.const) ||
|
|
ValueGuard.IsNumber(schema.const) ||
|
|
ValueGuard.IsString(schema.const)
|
|
)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TNever */
|
|
export function TNever(schema: unknown): schema is TNever {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Never') &&
|
|
ValueGuard.IsObject(schema.not) &&
|
|
Object.getOwnPropertyNames(schema.not).length === 0
|
|
)
|
|
}
|
|
/** Returns true if the given value is TNot */
|
|
export function TNot(schema: unknown): schema is TNot {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Not') &&
|
|
TSchema(schema.not)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TNull */
|
|
export function TNull(schema: unknown): schema is TNull {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Null') &&
|
|
schema.type === 'null' &&
|
|
IsOptionalString(schema.$id)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TNumber */
|
|
export function TNumber(schema: unknown): schema is TNumber {
|
|
return (
|
|
TKindOf(schema, 'Number') &&
|
|
schema.type === 'number' &&
|
|
IsOptionalString(schema.$id) &&
|
|
IsOptionalNumber(schema.exclusiveMaximum) &&
|
|
IsOptionalNumber(schema.exclusiveMinimum) &&
|
|
IsOptionalNumber(schema.maximum) &&
|
|
IsOptionalNumber(schema.minimum) &&
|
|
IsOptionalNumber(schema.multipleOf)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TObject */
|
|
export function TObject(schema: unknown): schema is TObject {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Object') &&
|
|
schema.type === 'object' &&
|
|
IsOptionalString(schema.$id) &&
|
|
ValueGuard.IsObject(schema.properties) &&
|
|
IsAdditionalProperties(schema.additionalProperties) &&
|
|
IsOptionalNumber(schema.minProperties) &&
|
|
IsOptionalNumber(schema.maxProperties) &&
|
|
Object.entries(schema.properties).every(([key, schema]) => IsControlCharacterFree(key) && TSchema(schema))
|
|
)
|
|
}
|
|
/** Returns true if the given value is TPromise */
|
|
export function TPromise(schema: unknown): schema is TPromise {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Promise') &&
|
|
schema.type === 'Promise' &&
|
|
IsOptionalString(schema.$id) &&
|
|
TSchema(schema.item)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TRecord */
|
|
export function TRecord(schema: unknown): schema is TRecord {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Record') &&
|
|
schema.type === 'object' &&
|
|
IsOptionalString(schema.$id) &&
|
|
IsAdditionalProperties(schema.additionalProperties) &&
|
|
ValueGuard.IsObject(schema.patternProperties) &&
|
|
((schema: Record<PropertyKey, unknown>) => {
|
|
const keys = Object.getOwnPropertyNames(schema.patternProperties)
|
|
return (
|
|
keys.length === 1 &&
|
|
IsPattern(keys[0]) &&
|
|
ValueGuard.IsObject(schema.patternProperties) &&
|
|
TSchema(schema.patternProperties[keys[0]])
|
|
)
|
|
})(schema)
|
|
)
|
|
}
|
|
/** Returns true if this value is TRecursive */
|
|
export function TRecursive(schema: unknown): schema is { [Hint]: 'Recursive' } {
|
|
return ValueGuard.IsObject(schema) && Hint in schema && schema[Hint] === 'Recursive'
|
|
}
|
|
/** Returns true if the given value is TRef */
|
|
export function TRef(schema: unknown): schema is TRef {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Ref') &&
|
|
IsOptionalString(schema.$id) &&
|
|
ValueGuard.IsString(schema.$ref)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TString */
|
|
export function TString(schema: unknown): schema is TString {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'String') &&
|
|
schema.type === 'string' &&
|
|
IsOptionalString(schema.$id) &&
|
|
IsOptionalNumber(schema.minLength) &&
|
|
IsOptionalNumber(schema.maxLength) &&
|
|
IsOptionalPattern(schema.pattern) &&
|
|
IsOptionalFormat(schema.format)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TSymbol */
|
|
export function TSymbol(schema: unknown): schema is TSymbol {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Symbol') &&
|
|
schema.type === 'symbol' &&
|
|
IsOptionalString(schema.$id)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TTemplateLiteral */
|
|
export function TTemplateLiteral(schema: unknown): schema is TTemplateLiteral {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'TemplateLiteral') &&
|
|
schema.type === 'string' &&
|
|
ValueGuard.IsString(schema.pattern) &&
|
|
schema.pattern[0] === '^' &&
|
|
schema.pattern[schema.pattern.length - 1] === '$'
|
|
)
|
|
}
|
|
/** Returns true if the given value is TThis */
|
|
export function TThis(schema: unknown): schema is TThis {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'This') &&
|
|
IsOptionalString(schema.$id) &&
|
|
ValueGuard.IsString(schema.$ref)
|
|
)
|
|
}
|
|
/** Returns true of this value is TTransform */
|
|
export function TTransform(schema: unknown): schema is { [Transform]: TransformOptions } {
|
|
return ValueGuard.IsObject(schema) && Transform in schema
|
|
}
|
|
/** Returns true if the given value is TTuple */
|
|
export function TTuple(schema: unknown): schema is TTuple {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Tuple') &&
|
|
schema.type === 'array' &&
|
|
IsOptionalString(schema.$id) &&
|
|
ValueGuard.IsNumber(schema.minItems) &&
|
|
ValueGuard.IsNumber(schema.maxItems) &&
|
|
schema.minItems === schema.maxItems &&
|
|
(( // empty
|
|
ValueGuard.IsUndefined(schema.items) &&
|
|
ValueGuard.IsUndefined(schema.additionalItems) &&
|
|
schema.minItems === 0
|
|
) || (
|
|
ValueGuard.IsArray(schema.items) &&
|
|
schema.items.every(schema => TSchema(schema))
|
|
))
|
|
)
|
|
}
|
|
/** Returns true if the given value is TUndefined */
|
|
export function TUndefined(schema: unknown): schema is TUndefined {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Undefined') &&
|
|
schema.type === 'undefined' &&
|
|
IsOptionalString(schema.$id)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TUnion<Literal<string | number>[]> */
|
|
export function TUnionLiteral(schema: unknown): schema is TUnion<TLiteral[]> {
|
|
return TUnion(schema) && schema.anyOf.every((schema) => TLiteralString(schema) || TLiteralNumber(schema))
|
|
}
|
|
/** Returns true if the given value is TUnion */
|
|
export function TUnion(schema: unknown): schema is TUnion {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Union') &&
|
|
IsOptionalString(schema.$id) &&
|
|
ValueGuard.IsObject(schema) &&
|
|
ValueGuard.IsArray(schema.anyOf) &&
|
|
schema.anyOf.every(schema => TSchema(schema))
|
|
)
|
|
}
|
|
/** Returns true if the given value is TUint8Array */
|
|
export function TUint8Array(schema: unknown): schema is TUint8Array {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Uint8Array') &&
|
|
schema.type === 'Uint8Array' &&
|
|
IsOptionalString(schema.$id) &&
|
|
IsOptionalNumber(schema.minByteLength) &&
|
|
IsOptionalNumber(schema.maxByteLength)
|
|
)
|
|
}
|
|
/** Returns true if the given value is TUnknown */
|
|
export function TUnknown(schema: unknown): schema is TUnknown {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Unknown') &&
|
|
IsOptionalString(schema.$id)
|
|
)
|
|
}
|
|
/** Returns true if the given value is a raw TUnsafe */
|
|
export function TUnsafe(schema: unknown): schema is TUnsafe<unknown> {
|
|
return TKindOf(schema, 'Unsafe')
|
|
}
|
|
/** Returns true if the given value is TVoid */
|
|
export function TVoid(schema: unknown): schema is TVoid {
|
|
// prettier-ignore
|
|
return (
|
|
TKindOf(schema, 'Void') &&
|
|
schema.type === 'void' &&
|
|
IsOptionalString(schema.$id)
|
|
)
|
|
}
|
|
/** Returns true if this value has a Readonly symbol */
|
|
export function TReadonly<T extends TSchema>(schema: T): schema is TReadonly<T> {
|
|
return ValueGuard.IsObject(schema) && schema[Readonly] === 'Readonly'
|
|
}
|
|
/** Returns true if this value has a Optional symbol */
|
|
export function TOptional<T extends TSchema>(schema: T): schema is TOptional<T> {
|
|
return ValueGuard.IsObject(schema) && schema[Optional] === 'Optional'
|
|
}
|
|
/** Returns true if the given value is TSchema */
|
|
export function TSchema(schema: unknown): schema is TSchema {
|
|
return (
|
|
ValueGuard.IsObject(schema) &&
|
|
(TAny(schema) ||
|
|
TArray(schema) ||
|
|
TBoolean(schema) ||
|
|
TBigInt(schema) ||
|
|
TAsyncIterator(schema) ||
|
|
TConstructor(schema) ||
|
|
TDate(schema) ||
|
|
TFunction(schema) ||
|
|
TInteger(schema) ||
|
|
TIntersect(schema) ||
|
|
TIterator(schema) ||
|
|
TLiteral(schema) ||
|
|
TNever(schema) ||
|
|
TNot(schema) ||
|
|
TNull(schema) ||
|
|
TNumber(schema) ||
|
|
TObject(schema) ||
|
|
TPromise(schema) ||
|
|
TRecord(schema) ||
|
|
TRef(schema) ||
|
|
TString(schema) ||
|
|
TSymbol(schema) ||
|
|
TTemplateLiteral(schema) ||
|
|
TThis(schema) ||
|
|
TTuple(schema) ||
|
|
TUndefined(schema) ||
|
|
TUnion(schema) ||
|
|
TUint8Array(schema) ||
|
|
TUnknown(schema) ||
|
|
TUnsafe(schema) ||
|
|
TVoid(schema) ||
|
|
(TKind(schema) && TypeRegistry.Has(schema[Kind] as any)))
|
|
)
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// ExtendsUndefined
|
|
// --------------------------------------------------------------------------
|
|
/** Fast undefined check used for properties of type undefined */
|
|
export namespace ExtendsUndefined {
|
|
export function Check(schema: TSchema): boolean {
|
|
return schema[Kind] === 'Intersect'
|
|
? (schema as TIntersect).allOf.every((schema) => Check(schema))
|
|
: schema[Kind] === 'Union'
|
|
? (schema as TUnion).anyOf.some((schema) => Check(schema))
|
|
: schema[Kind] === 'Undefined'
|
|
? true
|
|
: schema[Kind] === 'Not'
|
|
? !Check(schema.not)
|
|
: false
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TypeExtends
|
|
// --------------------------------------------------------------------------
|
|
export class TypeExtendsError extends TypeBoxError {}
|
|
export enum TypeExtendsResult {
|
|
Union,
|
|
True,
|
|
False,
|
|
}
|
|
export namespace TypeExtends {
|
|
// --------------------------------------------------------------------------
|
|
// IntoBooleanResult
|
|
// --------------------------------------------------------------------------
|
|
function IntoBooleanResult(result: TypeExtendsResult) {
|
|
return result === TypeExtendsResult.False ? result : TypeExtendsResult.True
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Throw
|
|
// --------------------------------------------------------------------------
|
|
function Throw(message: string): never {
|
|
throw new TypeExtendsError(message)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// StructuralRight
|
|
// --------------------------------------------------------------------------
|
|
function IsStructuralRight(right: TSchema): boolean {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TNever(right) ||
|
|
TypeGuard.TIntersect(right) ||
|
|
TypeGuard.TUnion(right) ||
|
|
TypeGuard.TUnknown(right) ||
|
|
TypeGuard.TAny(right)
|
|
)
|
|
}
|
|
function StructuralRight(left: TSchema, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TNever(right) ? TNeverRight(left, right) :
|
|
TypeGuard.TIntersect(right) ? TIntersectRight(left, right) :
|
|
TypeGuard.TUnion(right) ? TUnionRight(left, right) :
|
|
TypeGuard.TUnknown(right) ? TUnknownRight(left, right) :
|
|
TypeGuard.TAny(right) ? TAnyRight(left, right) :
|
|
Throw('StructuralRight')
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Any
|
|
// --------------------------------------------------------------------------
|
|
function TAnyRight(left: TSchema, right: TAny) {
|
|
return TypeExtendsResult.True
|
|
}
|
|
function TAny(left: TAny, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TIntersect(right) ? TIntersectRight(left, right) :
|
|
(TypeGuard.TUnion(right) && right.anyOf.some((schema) => TypeGuard.TAny(schema) || TypeGuard.TUnknown(schema))) ? TypeExtendsResult.True :
|
|
TypeGuard.TUnion(right) ? TypeExtendsResult.Union :
|
|
TypeGuard.TUnknown(right) ? TypeExtendsResult.True :
|
|
TypeGuard.TAny(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.Union
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Array
|
|
// --------------------------------------------------------------------------
|
|
function TArrayRight(left: TSchema, right: TArray) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TUnknown(left) ? TypeExtendsResult.False :
|
|
TypeGuard.TAny(left) ?TypeExtendsResult.Union :
|
|
TypeGuard.TNever(left) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
function TArray(left: TArray, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TObject(right) && IsObjectArrayLike(right) ? TypeExtendsResult.True :
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
!TypeGuard.TArray(right) ? TypeExtendsResult.False :
|
|
IntoBooleanResult(Visit(left.items, right.items))
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// AsyncIterator
|
|
// --------------------------------------------------------------------------
|
|
function TAsyncIterator(left: TAsyncIterator, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
!TypeGuard.TAsyncIterator(right) ? TypeExtendsResult.False :
|
|
IntoBooleanResult(Visit(left.items, right.items))
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// BigInt
|
|
// --------------------------------------------------------------------------
|
|
function TBigInt(left: TBigInt, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TBigInt(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Boolean
|
|
// --------------------------------------------------------------------------
|
|
function TBooleanRight(left: TSchema, right: TBoolean) {
|
|
return TypeGuard.TLiteral(left) && ValueGuard.IsBoolean(left.const) ? TypeExtendsResult.True : TypeGuard.TBoolean(left) ? TypeExtendsResult.True : TypeExtendsResult.False
|
|
}
|
|
function TBoolean(left: TBoolean, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TBoolean(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Constructor
|
|
// --------------------------------------------------------------------------
|
|
function TConstructor(left: TConstructor, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
!TypeGuard.TConstructor(right) ? TypeExtendsResult.False :
|
|
left.parameters.length > right.parameters.length ? TypeExtendsResult.False :
|
|
(!left.parameters.every((schema, index) => IntoBooleanResult(Visit(right.parameters[index], schema)) === TypeExtendsResult.True)) ? TypeExtendsResult.False :
|
|
IntoBooleanResult(Visit(left.returns, right.returns))
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Date
|
|
// --------------------------------------------------------------------------
|
|
function TDate(left: TDate, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TDate(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Function
|
|
// --------------------------------------------------------------------------
|
|
function TFunction(left: TFunction, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
!TypeGuard.TFunction(right) ? TypeExtendsResult.False :
|
|
left.parameters.length > right.parameters.length ? TypeExtendsResult.False :
|
|
(!left.parameters.every((schema, index) => IntoBooleanResult(Visit(right.parameters[index], schema)) === TypeExtendsResult.True)) ? TypeExtendsResult.False :
|
|
IntoBooleanResult(Visit(left.returns, right.returns))
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Integer
|
|
// --------------------------------------------------------------------------
|
|
function TIntegerRight(left: TSchema, right: TInteger) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TLiteral(left) && ValueGuard.IsNumber(left.const) ? TypeExtendsResult.True :
|
|
TypeGuard.TNumber(left) || TypeGuard.TInteger(left) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
function TInteger(left: TInteger, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TInteger(right) || TypeGuard.TNumber(right) ? TypeExtendsResult.True :
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Intersect
|
|
// --------------------------------------------------------------------------
|
|
function TIntersectRight(left: TSchema, right: TIntersect): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return right.allOf.every((schema) => Visit(left, schema) === TypeExtendsResult.True)
|
|
? TypeExtendsResult.True
|
|
: TypeExtendsResult.False
|
|
}
|
|
function TIntersect(left: TIntersect, right: TSchema) {
|
|
// prettier-ignore
|
|
return left.allOf.some((schema) => Visit(schema, right) === TypeExtendsResult.True)
|
|
? TypeExtendsResult.True
|
|
: TypeExtendsResult.False
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Iterator
|
|
// --------------------------------------------------------------------------
|
|
function TIterator(left: TIterator, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
!TypeGuard.TIterator(right) ? TypeExtendsResult.False :
|
|
IntoBooleanResult(Visit(left.items, right.items))
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Literal
|
|
// --------------------------------------------------------------------------
|
|
function TLiteral(left: TLiteral, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TLiteral(right) && right.const === left.const ? TypeExtendsResult.True :
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TString(right) ? TStringRight(left, right) :
|
|
TypeGuard.TNumber(right) ? TNumberRight(left, right) :
|
|
TypeGuard.TInteger(right) ? TIntegerRight(left, right) :
|
|
TypeGuard.TBoolean(right) ? TBooleanRight(left, right) :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Never
|
|
// --------------------------------------------------------------------------
|
|
function TNeverRight(left: TSchema, right: TNever) {
|
|
return TypeExtendsResult.False
|
|
}
|
|
function TNever(left: TNever, right: TSchema) {
|
|
return TypeExtendsResult.True
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Not
|
|
// --------------------------------------------------------------------------
|
|
function UnwrapTNot<T extends TNot>(schema: T): TUnknown | TNot['not'] {
|
|
let [current, depth]: [TSchema, number] = [schema, 0]
|
|
while (true) {
|
|
if (!TypeGuard.TNot(current)) break
|
|
current = current.not
|
|
depth += 1
|
|
}
|
|
return depth % 2 === 0 ? current : Type.Unknown()
|
|
}
|
|
function TNot(left: TSchema, right: TSchema) {
|
|
// TypeScript has no concept of negated types, and attempts to correctly check the negated
|
|
// type at runtime would put TypeBox at odds with TypeScripts ability to statically infer
|
|
// the type. Instead we unwrap to either unknown or T and continue evaluating.
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TNot(left) ? Visit(UnwrapTNot(left), right) :
|
|
TypeGuard.TNot(right) ? Visit(left, UnwrapTNot(right)) :
|
|
Throw('Invalid fallthrough for Not')
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Null
|
|
// --------------------------------------------------------------------------
|
|
function TNull(left: TNull, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TNull(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Number
|
|
// --------------------------------------------------------------------------
|
|
function TNumberRight(left: TSchema, right: TNumber) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TLiteralNumber(left) ? TypeExtendsResult.True :
|
|
TypeGuard.TNumber(left) || TypeGuard.TInteger(left) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
function TNumber(left: TNumber, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TInteger(right) || TypeGuard.TNumber(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Object
|
|
// --------------------------------------------------------------------------
|
|
function IsObjectPropertyCount(schema: TObject, count: number) {
|
|
return Object.getOwnPropertyNames(schema.properties).length === count
|
|
}
|
|
function IsObjectStringLike(schema: TObject) {
|
|
return IsObjectArrayLike(schema)
|
|
}
|
|
function IsObjectSymbolLike(schema: TObject) {
|
|
// prettier-ignore
|
|
return IsObjectPropertyCount(schema, 0) || (
|
|
IsObjectPropertyCount(schema, 1) && 'description' in schema.properties && TypeGuard.TUnion(schema.properties.description) && schema.properties.description.anyOf.length === 2 && ((
|
|
TypeGuard.TString(schema.properties.description.anyOf[0]) &&
|
|
TypeGuard.TUndefined(schema.properties.description.anyOf[1])
|
|
) || (
|
|
TypeGuard.TString(schema.properties.description.anyOf[1]) &&
|
|
TypeGuard.TUndefined(schema.properties.description.anyOf[0])
|
|
))
|
|
)
|
|
}
|
|
function IsObjectNumberLike(schema: TObject) {
|
|
return IsObjectPropertyCount(schema, 0)
|
|
}
|
|
function IsObjectBooleanLike(schema: TObject) {
|
|
return IsObjectPropertyCount(schema, 0)
|
|
}
|
|
function IsObjectBigIntLike(schema: TObject) {
|
|
return IsObjectPropertyCount(schema, 0)
|
|
}
|
|
function IsObjectDateLike(schema: TObject) {
|
|
return IsObjectPropertyCount(schema, 0)
|
|
}
|
|
function IsObjectUint8ArrayLike(schema: TObject) {
|
|
return IsObjectArrayLike(schema)
|
|
}
|
|
function IsObjectFunctionLike(schema: TObject) {
|
|
const length = Type.Number()
|
|
return IsObjectPropertyCount(schema, 0) || (IsObjectPropertyCount(schema, 1) && 'length' in schema.properties && IntoBooleanResult(Visit(schema.properties['length'], length)) === TypeExtendsResult.True)
|
|
}
|
|
function IsObjectConstructorLike(schema: TObject) {
|
|
return IsObjectPropertyCount(schema, 0)
|
|
}
|
|
function IsObjectArrayLike(schema: TObject) {
|
|
const length = Type.Number()
|
|
return IsObjectPropertyCount(schema, 0) || (IsObjectPropertyCount(schema, 1) && 'length' in schema.properties && IntoBooleanResult(Visit(schema.properties['length'], length)) === TypeExtendsResult.True)
|
|
}
|
|
function IsObjectPromiseLike(schema: TObject) {
|
|
const then = Type.Function([Type.Any()], Type.Any())
|
|
return IsObjectPropertyCount(schema, 0) || (IsObjectPropertyCount(schema, 1) && 'then' in schema.properties && IntoBooleanResult(Visit(schema.properties['then'], then)) === TypeExtendsResult.True)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Property
|
|
// --------------------------------------------------------------------------
|
|
function Property(left: TSchema, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
Visit(left, right) === TypeExtendsResult.False ? TypeExtendsResult.False :
|
|
TypeGuard.TOptional(left) && !TypeGuard.TOptional(right) ? TypeExtendsResult.False :
|
|
TypeExtendsResult.True
|
|
)
|
|
}
|
|
function TObjectRight(left: TSchema, right: TObject) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TUnknown(left) ? TypeExtendsResult.False :
|
|
TypeGuard.TAny(left) ? TypeExtendsResult.Union : (
|
|
TypeGuard.TNever(left) ||
|
|
(TypeGuard.TLiteralString(left) && IsObjectStringLike(right)) ||
|
|
(TypeGuard.TLiteralNumber(left) && IsObjectNumberLike(right)) ||
|
|
(TypeGuard.TLiteralBoolean(left) && IsObjectBooleanLike(right)) ||
|
|
(TypeGuard.TSymbol(left) && IsObjectSymbolLike(right)) ||
|
|
(TypeGuard.TBigInt(left) && IsObjectBigIntLike(right)) ||
|
|
(TypeGuard.TString(left) && IsObjectStringLike(right)) ||
|
|
(TypeGuard.TSymbol(left) && IsObjectSymbolLike(right)) ||
|
|
(TypeGuard.TNumber(left) && IsObjectNumberLike(right)) ||
|
|
(TypeGuard.TInteger(left) && IsObjectNumberLike(right)) ||
|
|
(TypeGuard.TBoolean(left) && IsObjectBooleanLike(right)) ||
|
|
(TypeGuard.TUint8Array(left) && IsObjectUint8ArrayLike(right)) ||
|
|
(TypeGuard.TDate(left) && IsObjectDateLike(right)) ||
|
|
(TypeGuard.TConstructor(left) && IsObjectConstructorLike(right)) ||
|
|
(TypeGuard.TFunction(left) && IsObjectFunctionLike(right))
|
|
) ? TypeExtendsResult.True :
|
|
(TypeGuard.TRecord(left) && TypeGuard.TString(RecordKey(left))) ? (() => {
|
|
// When expressing a Record with literal key values, the Record is converted into a Object with
|
|
// the Hint assigned as `Record`. This is used to invert the extends logic.
|
|
return right[Hint] === 'Record' ? TypeExtendsResult.True : TypeExtendsResult.False
|
|
})() :
|
|
(TypeGuard.TRecord(left) && TypeGuard.TNumber(RecordKey(left))) ? (() => {
|
|
return IsObjectPropertyCount(right, 0)
|
|
? TypeExtendsResult.True
|
|
: TypeExtendsResult.False
|
|
})() :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
function TObject(left: TObject, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
!TypeGuard.TObject(right) ? TypeExtendsResult.False :
|
|
(() => {
|
|
for (const key of Object.getOwnPropertyNames(right.properties)) {
|
|
if (!(key in left.properties)) return TypeExtendsResult.False
|
|
if (Property(left.properties[key], right.properties[key]) === TypeExtendsResult.False) {
|
|
return TypeExtendsResult.False
|
|
}
|
|
}
|
|
return TypeExtendsResult.True
|
|
})()
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Promise
|
|
// --------------------------------------------------------------------------
|
|
function TPromise(left: TPromise, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) && IsObjectPromiseLike(right) ? TypeExtendsResult.True :
|
|
!TypeGuard.TPromise(right) ? TypeExtendsResult.False :
|
|
IntoBooleanResult(Visit(left.item, right.item))
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Record
|
|
// --------------------------------------------------------------------------
|
|
function RecordKey(schema: TRecord) {
|
|
// prettier-ignore
|
|
return (
|
|
PatternNumberExact in schema.patternProperties ? Type.Number() :
|
|
PatternStringExact in schema.patternProperties ? Type.String() :
|
|
Throw('Unknown record key pattern')
|
|
)
|
|
}
|
|
function RecordValue(schema: TRecord) {
|
|
// prettier-ignore
|
|
return (
|
|
PatternNumberExact in schema.patternProperties ? schema.patternProperties[PatternNumberExact] :
|
|
PatternStringExact in schema.patternProperties ? schema.patternProperties[PatternStringExact] :
|
|
Throw('Unable to get record value schema')
|
|
)
|
|
}
|
|
function TRecordRight(left: TSchema, right: TRecord) {
|
|
const [Key, Value] = [RecordKey(right), RecordValue(right)]
|
|
// prettier-ignore
|
|
return (
|
|
(TypeGuard.TLiteralString(left) && TypeGuard.TNumber(Key) && IntoBooleanResult(Visit(left, Value)) === TypeExtendsResult.True) ? TypeExtendsResult.True :
|
|
TypeGuard.TUint8Array(left) && TypeGuard.TNumber(Key) ? Visit(left, Value) :
|
|
TypeGuard.TString(left) && TypeGuard.TNumber(Key) ? Visit(left, Value) :
|
|
TypeGuard.TArray(left) && TypeGuard.TNumber(Key) ? Visit(left, Value) :
|
|
TypeGuard.TObject(left) ? (() => {
|
|
for (const key of Object.getOwnPropertyNames(left.properties)) {
|
|
if (Property(Value, left.properties[key]) === TypeExtendsResult.False) {
|
|
return TypeExtendsResult.False
|
|
}
|
|
}
|
|
return TypeExtendsResult.True
|
|
})() :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
function TRecord(left: TRecord, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
!TypeGuard.TRecord(right) ? TypeExtendsResult.False :
|
|
Visit(RecordValue(left), RecordValue(right))
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// String
|
|
// --------------------------------------------------------------------------
|
|
function TStringRight(left: TSchema, right: TString) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TLiteral(left) && ValueGuard.IsString(left.const) ? TypeExtendsResult.True :
|
|
TypeGuard.TString(left) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
function TString(left: TString, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TString(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Symbol
|
|
// --------------------------------------------------------------------------
|
|
function TSymbol(left: TSymbol, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TSymbol(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TemplateLiteral
|
|
// --------------------------------------------------------------------------
|
|
function TTemplateLiteral(left: TSchema, right: TSchema) {
|
|
// TemplateLiteral types are resolved to either unions for finite expressions or string
|
|
// for infinite expressions. Here we call to TemplateLiteralResolver to resolve for
|
|
// either type and continue evaluating.
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TTemplateLiteral(left) ? Visit(TemplateLiteralResolver.Resolve(left), right) :
|
|
TypeGuard.TTemplateLiteral(right) ? Visit(left, TemplateLiteralResolver.Resolve(right)) :
|
|
Throw('Invalid fallthrough for TemplateLiteral')
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Tuple
|
|
// --------------------------------------------------------------------------
|
|
function IsArrayOfTuple(left: TTuple, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TArray(right) &&
|
|
left.items !== undefined &&
|
|
left.items.every((schema) => Visit(schema, right.items) === TypeExtendsResult.True)
|
|
)
|
|
}
|
|
function TTupleRight(left: TSchema, right: TTuple) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TNever(left) ? TypeExtendsResult.True :
|
|
TypeGuard.TUnknown(left) ? TypeExtendsResult.False :
|
|
TypeGuard.TAny(left) ? TypeExtendsResult.Union :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
function TTuple(left: TTuple, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) && IsObjectArrayLike(right) ? TypeExtendsResult.True :
|
|
TypeGuard.TArray(right) && IsArrayOfTuple(left, right) ? TypeExtendsResult.True :
|
|
!TypeGuard.TTuple(right) ? TypeExtendsResult.False :
|
|
(ValueGuard.IsUndefined(left.items) && !ValueGuard.IsUndefined(right.items)) || (!ValueGuard.IsUndefined(left.items) && ValueGuard.IsUndefined(right.items)) ? TypeExtendsResult.False :
|
|
(ValueGuard.IsUndefined(left.items) && !ValueGuard.IsUndefined(right.items)) ? TypeExtendsResult.True :
|
|
left.items!.every((schema, index) => Visit(schema, right.items![index]) === TypeExtendsResult.True) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Uint8Array
|
|
// --------------------------------------------------------------------------
|
|
function TUint8Array(left: TUint8Array, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TUint8Array(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Undefined
|
|
// --------------------------------------------------------------------------
|
|
function TUndefined(left: TUndefined, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
IsStructuralRight(right) ? StructuralRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TRecord(right) ? TRecordRight(left, right) :
|
|
TypeGuard.TVoid(right) ? VoidRight(left, right) :
|
|
TypeGuard.TUndefined(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Union
|
|
// --------------------------------------------------------------------------
|
|
function TUnionRight(left: TSchema, right: TUnion): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return right.anyOf.some((schema) => Visit(left, schema) === TypeExtendsResult.True)
|
|
? TypeExtendsResult.True
|
|
: TypeExtendsResult.False
|
|
}
|
|
function TUnion(left: TUnion, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return left.anyOf.every((schema) => Visit(schema, right) === TypeExtendsResult.True)
|
|
? TypeExtendsResult.True
|
|
: TypeExtendsResult.False
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Unknown
|
|
// --------------------------------------------------------------------------
|
|
function TUnknownRight(left: TSchema, right: TUnknown) {
|
|
return TypeExtendsResult.True
|
|
}
|
|
function TUnknown(left: TUnknown, right: TSchema) {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TNever(right) ? TNeverRight(left, right) :
|
|
TypeGuard.TIntersect(right) ? TIntersectRight(left, right) :
|
|
TypeGuard.TUnion(right) ? TUnionRight(left, right) :
|
|
TypeGuard.TAny(right) ? TAnyRight(left, right) :
|
|
TypeGuard.TString(right) ? TStringRight(left, right) :
|
|
TypeGuard.TNumber(right) ? TNumberRight(left, right) :
|
|
TypeGuard.TInteger(right) ? TIntegerRight(left, right) :
|
|
TypeGuard.TBoolean(right) ? TBooleanRight(left, right) :
|
|
TypeGuard.TArray(right) ? TArrayRight(left, right) :
|
|
TypeGuard.TTuple(right) ? TTupleRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TUnknown(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
)
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Void
|
|
// --------------------------------------------------------------------------
|
|
function VoidRight(left: TSchema, right: TVoid) {
|
|
// prettier-ignore
|
|
return TypeGuard.TUndefined(left) ? TypeExtendsResult.True :
|
|
TypeGuard.TUndefined(left) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
}
|
|
function TVoid(left: TVoid, right: TSchema) {
|
|
// prettier-ignore
|
|
return TypeGuard.TIntersect(right) ? TIntersectRight(left, right) :
|
|
TypeGuard.TUnion(right) ? TUnionRight(left, right) :
|
|
TypeGuard.TUnknown(right) ? TUnknownRight(left, right) :
|
|
TypeGuard.TAny(right) ? TAnyRight(left, right) :
|
|
TypeGuard.TObject(right) ? TObjectRight(left, right) :
|
|
TypeGuard.TVoid(right) ? TypeExtendsResult.True :
|
|
TypeExtendsResult.False
|
|
}
|
|
function Visit(left: TSchema, right: TSchema): TypeExtendsResult {
|
|
// prettier-ignore
|
|
return (
|
|
// resolvable
|
|
(TypeGuard.TTemplateLiteral(left) || TypeGuard.TTemplateLiteral(right)) ? TTemplateLiteral(left, right) :
|
|
(TypeGuard.TNot(left) || TypeGuard.TNot(right)) ? TNot(left, right) :
|
|
// standard
|
|
TypeGuard.TAny(left) ? TAny(left, right) :
|
|
TypeGuard.TArray(left) ? TArray(left, right) :
|
|
TypeGuard.TBigInt(left) ? TBigInt(left, right) :
|
|
TypeGuard.TBoolean(left) ? TBoolean(left, right) :
|
|
TypeGuard.TAsyncIterator(left) ? TAsyncIterator(left, right) :
|
|
TypeGuard.TConstructor(left) ? TConstructor(left, right) :
|
|
TypeGuard.TDate(left) ? TDate(left, right) :
|
|
TypeGuard.TFunction(left) ? TFunction(left, right) :
|
|
TypeGuard.TInteger(left) ? TInteger(left, right) :
|
|
TypeGuard.TIntersect(left) ? TIntersect(left, right) :
|
|
TypeGuard.TIterator(left) ? TIterator(left, right) :
|
|
TypeGuard.TLiteral(left) ? TLiteral(left, right) :
|
|
TypeGuard.TNever(left) ? TNever(left, right) :
|
|
TypeGuard.TNull(left) ? TNull(left, right) :
|
|
TypeGuard.TNumber(left) ? TNumber(left, right) :
|
|
TypeGuard.TObject(left) ? TObject(left, right) :
|
|
TypeGuard.TRecord(left) ? TRecord(left, right) :
|
|
TypeGuard.TString(left) ? TString(left, right) :
|
|
TypeGuard.TSymbol(left) ? TSymbol(left, right) :
|
|
TypeGuard.TTuple(left) ? TTuple(left, right) :
|
|
TypeGuard.TPromise(left) ? TPromise(left, right) :
|
|
TypeGuard.TUint8Array(left) ? TUint8Array(left, right) :
|
|
TypeGuard.TUndefined(left) ? TUndefined(left, right) :
|
|
TypeGuard.TUnion(left) ? TUnion(left, right) :
|
|
TypeGuard.TUnknown(left) ? TUnknown(left, right) :
|
|
TypeGuard.TVoid(left) ? TVoid(left, right) :
|
|
Throw(`Unknown left type operand '${left[Kind]}'`)
|
|
)
|
|
}
|
|
export function Extends(left: TSchema, right: TSchema): TypeExtendsResult {
|
|
return Visit(left, right)
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TypeClone
|
|
// --------------------------------------------------------------------------
|
|
/** Specialized Clone for Types */
|
|
export namespace TypeClone {
|
|
function ObjectType(value: Record<keyof any, unknown>) {
|
|
const clonedProperties = Object.getOwnPropertyNames(value).reduce((acc, key) => ({ ...acc, [key]: Visit(value[key]) }), {})
|
|
const clonedSymbols = Object.getOwnPropertySymbols(value).reduce((acc, key) => ({ ...acc, [key]: Visit(value[key as any]) }), {})
|
|
return { ...clonedProperties, ...clonedSymbols }
|
|
}
|
|
function ArrayType(value: unknown[]) {
|
|
return (value as any).map((value: unknown) => Visit(value as any))
|
|
}
|
|
function Visit(value: unknown): any {
|
|
// prettier-ignore
|
|
return ValueGuard.IsArray(value) ? ArrayType(value) :
|
|
ValueGuard.IsObject(value) ? ObjectType(value) :
|
|
value
|
|
}
|
|
/** Clones a Rest */
|
|
export function Rest<T extends TSchema[]>(schemas: [...T]): T {
|
|
return schemas.map((schema) => Type(schema)) as T
|
|
}
|
|
/** Clones a Type */
|
|
export function Type<T extends TSchema>(schema: T, options: SchemaOptions = {}): T {
|
|
return { ...Visit(schema), ...options }
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// IndexedAccessor
|
|
// --------------------------------------------------------------------------
|
|
export namespace IndexedAccessor {
|
|
function OptionalUnwrap(schema: TSchema[]): TSchema[] {
|
|
return schema.map((schema) => {
|
|
const { [Optional]: _, ...clone } = TypeClone.Type(schema)
|
|
return clone
|
|
})
|
|
}
|
|
function IsIntersectOptional(schema: TSchema[]): boolean {
|
|
return schema.every((schema) => TypeGuard.TOptional(schema))
|
|
}
|
|
function IsUnionOptional(schema: TSchema[]): boolean {
|
|
return schema.some((schema) => TypeGuard.TOptional(schema))
|
|
}
|
|
function ResolveIntersect(schema: TIntersect): TSchema {
|
|
return IsIntersectOptional(schema.allOf) ? Type.Optional(Type.Intersect(OptionalUnwrap(schema.allOf))) : schema
|
|
}
|
|
function ResolveUnion(schema: TUnion): TSchema {
|
|
return IsUnionOptional(schema.anyOf) ? Type.Optional(Type.Union(OptionalUnwrap(schema.anyOf))) : schema
|
|
}
|
|
function ResolveOptional(schema: TSchema) {
|
|
// prettier-ignore
|
|
return schema[Kind] === 'Intersect' ? ResolveIntersect(schema as TIntersect) :
|
|
schema[Kind] === 'Union' ? ResolveUnion(schema as TUnion) :
|
|
schema
|
|
}
|
|
function TIntersect(schema: TIntersect, key: string): TSchema {
|
|
const resolved = schema.allOf.reduce((acc, schema) => {
|
|
const indexed = Visit(schema, key)
|
|
return indexed[Kind] === 'Never' ? acc : [...acc, indexed]
|
|
}, [] as TSchema[])
|
|
return ResolveOptional(Type.Intersect(resolved))
|
|
}
|
|
function TUnion(schema: TUnion, key: string): TSchema {
|
|
const resolved = schema.anyOf.map((schema) => Visit(schema, key))
|
|
return ResolveOptional(Type.Union(resolved))
|
|
}
|
|
function TObject(schema: TObject, key: string): TSchema {
|
|
const property = schema.properties[key]
|
|
return ValueGuard.IsUndefined(property) ? Type.Never() : Type.Union([property])
|
|
}
|
|
function TTuple(schema: TTuple, key: string): TSchema {
|
|
const items = schema.items
|
|
if (ValueGuard.IsUndefined(items)) return Type.Never()
|
|
const element = items[key as any as number] //
|
|
if (ValueGuard.IsUndefined(element)) return Type.Never()
|
|
return element
|
|
}
|
|
function Visit(schema: TSchema, key: string): TSchema {
|
|
// prettier-ignore
|
|
return schema[Kind] === 'Intersect' ? TIntersect(schema as TIntersect, key) :
|
|
schema[Kind] === 'Union' ? TUnion(schema as TUnion, key) :
|
|
schema[Kind] === 'Object' ? TObject(schema as TObject, key) :
|
|
schema[Kind] === 'Tuple' ? TTuple(schema as TTuple, key) :
|
|
Type.Never()
|
|
}
|
|
export function Resolve(schema: TSchema, keys: TPropertyKey[], options: SchemaOptions = {}): TSchema {
|
|
const resolved = keys.map((key) => Visit(schema, key.toString()))
|
|
return ResolveOptional(Type.Union(resolved, options))
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// Intrinsic
|
|
// --------------------------------------------------------------------------
|
|
export namespace Intrinsic {
|
|
function Uncapitalize(value: string): string {
|
|
const [first, rest] = [value.slice(0, 1), value.slice(1)]
|
|
return `${first.toLowerCase()}${rest}`
|
|
}
|
|
function Capitalize(value: string): string {
|
|
const [first, rest] = [value.slice(0, 1), value.slice(1)]
|
|
return `${first.toUpperCase()}${rest}`
|
|
}
|
|
function Uppercase(value: string): string {
|
|
return value.toUpperCase()
|
|
}
|
|
function Lowercase(value: string): string {
|
|
return value.toLowerCase()
|
|
}
|
|
function IntrinsicTemplateLiteral(schema: TTemplateLiteral, mode: TIntrinsicMode) {
|
|
// note: template literals require special runtime handling as they are encoded in string patterns.
|
|
// This diverges from the mapped type which would otherwise map on the template literal kind.
|
|
const expression = TemplateLiteralParser.ParseExact(schema.pattern)
|
|
const finite = TemplateLiteralFinite.Check(expression)
|
|
if (!finite) return { ...schema, pattern: IntrinsicLiteral(schema.pattern, mode) } as any
|
|
const strings = [...TemplateLiteralGenerator.Generate(expression)]
|
|
const literals = strings.map((value) => Type.Literal(value))
|
|
const mapped = IntrinsicRest(literals as any, mode)
|
|
const union = Type.Union(mapped)
|
|
return Type.TemplateLiteral([union])
|
|
}
|
|
function IntrinsicLiteral(value: TLiteralValue, mode: TIntrinsicMode) {
|
|
// prettier-ignore
|
|
return typeof value === 'string' ? (
|
|
mode === 'Uncapitalize' ? Uncapitalize(value) :
|
|
mode === 'Capitalize' ? Capitalize(value) :
|
|
mode === 'Uppercase' ? Uppercase(value) :
|
|
mode === 'Lowercase' ? Lowercase(value) :
|
|
value) : value.toString()
|
|
}
|
|
function IntrinsicRest(schema: TSchema[], mode: TIntrinsicMode): TSchema[] {
|
|
if (schema.length === 0) return []
|
|
const [L, ...R] = schema
|
|
return [Map(L, mode), ...IntrinsicRest(R, mode)]
|
|
}
|
|
function Visit(schema: TSchema, mode: TIntrinsicMode) {
|
|
// prettier-ignore
|
|
return TypeGuard.TTemplateLiteral(schema) ? IntrinsicTemplateLiteral(schema, mode) :
|
|
TypeGuard.TUnion(schema) ? Type.Union(IntrinsicRest(schema.anyOf, mode)) :
|
|
TypeGuard.TLiteral(schema) ? Type.Literal(IntrinsicLiteral(schema.const, mode)) :
|
|
schema
|
|
}
|
|
/** Applies an intrinsic string manipulation to the given type. */
|
|
export function Map<T extends TSchema, M extends TIntrinsicMode>(schema: T, mode: M): TIntrinsic<T, M> {
|
|
return Visit(schema, mode)
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// ObjectMap
|
|
// --------------------------------------------------------------------------
|
|
export namespace ObjectMap {
|
|
function TIntersect(schema: TIntersect, callback: (object: TObject) => TObject) {
|
|
// prettier-ignore
|
|
return Type.Intersect(schema.allOf.map((inner) => Visit(inner, callback)), { ...schema })
|
|
}
|
|
function TUnion(schema: TUnion, callback: (object: TObject) => TObject) {
|
|
// prettier-ignore
|
|
return Type.Union(schema.anyOf.map((inner) => Visit(inner, callback)), { ...schema })
|
|
}
|
|
function TObject(schema: TObject, callback: (object: TObject) => TObject) {
|
|
return callback(schema)
|
|
}
|
|
function Visit(schema: TSchema, callback: (object: TObject) => TObject): TSchema {
|
|
// There are cases where users need to map objects with unregistered kinds. Using a TypeGuard here would
|
|
// prevent sub schema mapping as unregistered kinds will not pass TSchema checks. This is notable in the
|
|
// case of TObject where unregistered property kinds cause the TObject check to fail. As mapping is only
|
|
// used for composition, we use explicit checks instead.
|
|
// prettier-ignore
|
|
return (
|
|
schema[Kind] === 'Intersect' ? TIntersect(schema as TIntersect, callback) :
|
|
schema[Kind] === 'Union' ? TUnion(schema as TUnion, callback) :
|
|
schema[Kind] === 'Object' ? TObject(schema as TObject, callback) :
|
|
schema
|
|
)
|
|
}
|
|
export function Map<T = TSchema>(schema: TSchema, callback: (object: TObject) => TObject, options: SchemaOptions): T {
|
|
return { ...Visit(TypeClone.Type(schema), callback), ...options } as unknown as T
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// KeyResolver
|
|
// --------------------------------------------------------------------------
|
|
export interface KeyResolverOptions {
|
|
includePatterns: boolean
|
|
}
|
|
export namespace KeyResolver {
|
|
function UnwrapPattern(key: string) {
|
|
return key[0] === '^' && key[key.length - 1] === '$' ? key.slice(1, key.length - 1) : key
|
|
}
|
|
function TIntersect(schema: TIntersect, options: KeyResolverOptions): string[] {
|
|
return schema.allOf.reduce((acc, schema) => [...acc, ...Visit(schema, options)], [] as string[])
|
|
}
|
|
function TUnion(schema: TUnion, options: KeyResolverOptions): string[] {
|
|
const sets = schema.anyOf.map((inner) => Visit(inner, options))
|
|
return [...sets.reduce((set, outer) => outer.map((key) => (sets.every((inner) => inner.includes(key)) ? set.add(key) : set))[0], new Set<string>())]
|
|
}
|
|
function TObject(schema: TObject, options: KeyResolverOptions): string[] {
|
|
return Object.getOwnPropertyNames(schema.properties)
|
|
}
|
|
function TRecord(schema: TRecord, options: KeyResolverOptions): string[] {
|
|
return options.includePatterns ? Object.getOwnPropertyNames(schema.patternProperties) : []
|
|
}
|
|
function Visit(schema: TSchema, options: KeyResolverOptions): string[] {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TIntersect(schema) ? TIntersect(schema, options) :
|
|
TypeGuard.TUnion(schema) ? TUnion(schema, options) :
|
|
TypeGuard.TObject(schema) ? TObject(schema, options) :
|
|
TypeGuard.TRecord(schema) ? TRecord(schema, options) :
|
|
[]
|
|
)
|
|
}
|
|
/** Resolves an array of keys in this schema */
|
|
export function ResolveKeys(schema: TSchema, options: KeyResolverOptions): string[] {
|
|
return [...new Set(Visit(schema, options))]
|
|
}
|
|
/** Resolves a regular expression pattern matching all keys in this schema */
|
|
export function ResolvePattern(schema: TSchema): string {
|
|
const keys = ResolveKeys(schema, { includePatterns: true })
|
|
const pattern = keys.map((key) => `(${UnwrapPattern(key)})`)
|
|
return `^(${pattern.join('|')})$`
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// KeyArrayResolver
|
|
// --------------------------------------------------------------------------
|
|
export class KeyArrayResolverError extends TypeBoxError {}
|
|
export namespace KeyArrayResolver {
|
|
/** Resolves an array of string[] keys from the given schema or array type. */
|
|
export function Resolve(schema: TSchema | string[]): string[] {
|
|
// prettier-ignore
|
|
return Array.isArray(schema) ? schema :
|
|
TypeGuard.TUnionLiteral(schema) ? schema.anyOf.map((schema) => schema.const.toString()) :
|
|
TypeGuard.TLiteral(schema) ? [schema.const as string] :
|
|
TypeGuard.TTemplateLiteral(schema) ? (() => {
|
|
const expression = TemplateLiteralParser.ParseExact(schema.pattern)
|
|
if (!TemplateLiteralFinite.Check(expression)) throw new KeyArrayResolverError('Cannot resolve keys from infinite template expression')
|
|
return [...TemplateLiteralGenerator.Generate(expression)]
|
|
})() : []
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// UnionResolver
|
|
// --------------------------------------------------------------------------
|
|
export namespace UnionResolver {
|
|
function* TUnion(union: TUnion): IterableIterator<TSchema> {
|
|
for (const schema of union.anyOf) {
|
|
if (schema[Kind] === 'Union') {
|
|
yield* TUnion(schema as TUnion)
|
|
} else {
|
|
yield schema
|
|
}
|
|
}
|
|
}
|
|
/** Returns a resolved union with interior unions flattened */
|
|
export function Resolve(union: TUnion): TUnion {
|
|
return Type.Union([...TUnion(union)], { ...union })
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TemplateLiteralPattern
|
|
// --------------------------------------------------------------------------
|
|
export class TemplateLiteralPatternError extends TypeBoxError {}
|
|
export namespace TemplateLiteralPattern {
|
|
function Throw(message: string): never {
|
|
throw new TemplateLiteralPatternError(message)
|
|
}
|
|
function Escape(value: string) {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
}
|
|
function Visit(schema: TSchema, acc: string): string {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TTemplateLiteral(schema) ? schema.pattern.slice(1, schema.pattern.length - 1) :
|
|
TypeGuard.TUnion(schema) ? `(${schema.anyOf.map((schema) => Visit(schema, acc)).join('|')})` :
|
|
TypeGuard.TNumber(schema) ? `${acc}${PatternNumber}` :
|
|
TypeGuard.TInteger(schema) ? `${acc}${PatternNumber}` :
|
|
TypeGuard.TBigInt(schema) ? `${acc}${PatternNumber}` :
|
|
TypeGuard.TString(schema) ? `${acc}${PatternString}` :
|
|
TypeGuard.TLiteral(schema) ? `${acc}${Escape(schema.const.toString())}` :
|
|
TypeGuard.TBoolean(schema) ? `${acc}${PatternBoolean}` :
|
|
Throw(`Unexpected Kind '${schema[Kind]}'`)
|
|
)
|
|
}
|
|
export function Create(kinds: TTemplateLiteralKind[]): string {
|
|
return `^${kinds.map((schema) => Visit(schema, '')).join('')}\$`
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------------------
|
|
// TemplateLiteralResolver
|
|
// --------------------------------------------------------------------------------------
|
|
export namespace TemplateLiteralResolver {
|
|
/** Resolves a template literal as a TUnion */
|
|
export function Resolve(template: TTemplateLiteral): TString | TUnion | TLiteral {
|
|
const expression = TemplateLiteralParser.ParseExact(template.pattern)
|
|
if (!TemplateLiteralFinite.Check(expression)) return Type.String()
|
|
const literals = [...TemplateLiteralGenerator.Generate(expression)].map((value) => Type.Literal(value))
|
|
return Type.Union(literals)
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------------------
|
|
// TemplateLiteralParser
|
|
// --------------------------------------------------------------------------------------
|
|
export class TemplateLiteralParserError extends TypeBoxError {}
|
|
export namespace TemplateLiteralParser {
|
|
export type Expression = And | Or | Const
|
|
export type Const = { type: 'const'; const: string }
|
|
export type And = { type: 'and'; expr: Expression[] }
|
|
export type Or = { type: 'or'; expr: Expression[] }
|
|
function IsNonEscaped(pattern: string, index: number, char: string) {
|
|
return pattern[index] === char && pattern.charCodeAt(index - 1) !== 92
|
|
}
|
|
function IsOpenParen(pattern: string, index: number) {
|
|
return IsNonEscaped(pattern, index, '(')
|
|
}
|
|
function IsCloseParen(pattern: string, index: number) {
|
|
return IsNonEscaped(pattern, index, ')')
|
|
}
|
|
function IsSeparator(pattern: string, index: number) {
|
|
return IsNonEscaped(pattern, index, '|')
|
|
}
|
|
function IsGroup(pattern: string) {
|
|
if (!(IsOpenParen(pattern, 0) && IsCloseParen(pattern, pattern.length - 1))) return false
|
|
let count = 0
|
|
for (let index = 0; index < pattern.length; index++) {
|
|
if (IsOpenParen(pattern, index)) count += 1
|
|
if (IsCloseParen(pattern, index)) count -= 1
|
|
if (count === 0 && index !== pattern.length - 1) return false
|
|
}
|
|
return true
|
|
}
|
|
function InGroup(pattern: string) {
|
|
return pattern.slice(1, pattern.length - 1)
|
|
}
|
|
function IsPrecedenceOr(pattern: string) {
|
|
let count = 0
|
|
for (let index = 0; index < pattern.length; index++) {
|
|
if (IsOpenParen(pattern, index)) count += 1
|
|
if (IsCloseParen(pattern, index)) count -= 1
|
|
if (IsSeparator(pattern, index) && count === 0) return true
|
|
}
|
|
return false
|
|
}
|
|
function IsPrecedenceAnd(pattern: string) {
|
|
for (let index = 0; index < pattern.length; index++) {
|
|
if (IsOpenParen(pattern, index)) return true
|
|
}
|
|
return false
|
|
}
|
|
function Or(pattern: string): Expression {
|
|
let [count, start] = [0, 0]
|
|
const expressions: Expression[] = []
|
|
for (let index = 0; index < pattern.length; index++) {
|
|
if (IsOpenParen(pattern, index)) count += 1
|
|
if (IsCloseParen(pattern, index)) count -= 1
|
|
if (IsSeparator(pattern, index) && count === 0) {
|
|
const range = pattern.slice(start, index)
|
|
if (range.length > 0) expressions.push(Parse(range))
|
|
start = index + 1
|
|
}
|
|
}
|
|
const range = pattern.slice(start)
|
|
if (range.length > 0) expressions.push(Parse(range))
|
|
if (expressions.length === 0) return { type: 'const', const: '' }
|
|
if (expressions.length === 1) return expressions[0]
|
|
return { type: 'or', expr: expressions }
|
|
}
|
|
function And(pattern: string): Expression {
|
|
function Group(value: string, index: number): [number, number] {
|
|
if (!IsOpenParen(value, index)) throw new TemplateLiteralParserError(`TemplateLiteralParser: Index must point to open parens`)
|
|
let count = 0
|
|
for (let scan = index; scan < value.length; scan++) {
|
|
if (IsOpenParen(value, scan)) count += 1
|
|
if (IsCloseParen(value, scan)) count -= 1
|
|
if (count === 0) return [index, scan]
|
|
}
|
|
throw new TemplateLiteralParserError(`TemplateLiteralParser: Unclosed group parens in expression`)
|
|
}
|
|
function Range(pattern: string, index: number): [number, number] {
|
|
for (let scan = index; scan < pattern.length; scan++) {
|
|
if (IsOpenParen(pattern, scan)) return [index, scan]
|
|
}
|
|
return [index, pattern.length]
|
|
}
|
|
const expressions: Expression[] = []
|
|
for (let index = 0; index < pattern.length; index++) {
|
|
if (IsOpenParen(pattern, index)) {
|
|
const [start, end] = Group(pattern, index)
|
|
const range = pattern.slice(start, end + 1)
|
|
expressions.push(Parse(range))
|
|
index = end
|
|
} else {
|
|
const [start, end] = Range(pattern, index)
|
|
const range = pattern.slice(start, end)
|
|
if (range.length > 0) expressions.push(Parse(range))
|
|
index = end - 1
|
|
}
|
|
}
|
|
// prettier-ignore
|
|
return (expressions.length === 0) ? { type: 'const', const: '' } :
|
|
(expressions.length === 1) ? expressions[0] :
|
|
{ type: 'and', expr: expressions }
|
|
}
|
|
/** Parses a pattern and returns an expression tree */
|
|
export function Parse(pattern: string): Expression {
|
|
// prettier-ignore
|
|
return IsGroup(pattern) ? Parse(InGroup(pattern)) :
|
|
IsPrecedenceOr(pattern) ? Or(pattern) :
|
|
IsPrecedenceAnd(pattern) ? And(pattern) :
|
|
{ type: 'const', const: pattern }
|
|
}
|
|
/** Parses a pattern and strips forward and trailing ^ and $ */
|
|
export function ParseExact(pattern: string): Expression {
|
|
return Parse(pattern.slice(1, pattern.length - 1))
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------------------
|
|
// TemplateLiteralFinite
|
|
// --------------------------------------------------------------------------------------
|
|
export class TemplateLiteralFiniteError extends TypeBoxError {}
|
|
export namespace TemplateLiteralFinite {
|
|
function Throw(message: string): never {
|
|
throw new TemplateLiteralFiniteError(message)
|
|
}
|
|
function IsNumber(expression: TemplateLiteralParser.Expression): boolean {
|
|
// prettier-ignore
|
|
return (
|
|
expression.type === 'or' &&
|
|
expression.expr.length === 2 &&
|
|
expression.expr[0].type === 'const' &&
|
|
expression.expr[0].const === '0' &&
|
|
expression.expr[1].type === 'const' &&
|
|
expression.expr[1].const === '[1-9][0-9]*'
|
|
)
|
|
}
|
|
function IsBoolean(expression: TemplateLiteralParser.Expression): boolean {
|
|
// prettier-ignore
|
|
return (
|
|
expression.type === 'or' &&
|
|
expression.expr.length === 2 &&
|
|
expression.expr[0].type === 'const' &&
|
|
expression.expr[0].const === 'true' &&
|
|
expression.expr[1].type === 'const' &&
|
|
expression.expr[1].const === 'false'
|
|
)
|
|
}
|
|
function IsString(expression: TemplateLiteralParser.Expression) {
|
|
return expression.type === 'const' && expression.const === '.*'
|
|
}
|
|
export function Check(expression: TemplateLiteralParser.Expression): boolean {
|
|
// prettier-ignore
|
|
return IsBoolean(expression) ? true :
|
|
IsNumber(expression) || IsString(expression) ? false :
|
|
(expression.type === 'and') ? expression.expr.every((expr) => Check(expr)) :
|
|
(expression.type === 'or') ? expression.expr.every((expr) => Check(expr)) :
|
|
(expression.type === 'const') ? true :
|
|
Throw(`Unknown expression type`)
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------------------
|
|
// TemplateLiteralGenerator
|
|
// --------------------------------------------------------------------------------------
|
|
export class TemplateLiteralGeneratorError extends TypeBoxError {}
|
|
export namespace TemplateLiteralGenerator {
|
|
function* Reduce(buffer: string[][]): IterableIterator<string> {
|
|
if (buffer.length === 1) return yield* buffer[0]
|
|
for (const left of buffer[0]) {
|
|
for (const right of Reduce(buffer.slice(1))) {
|
|
yield `${left}${right}`
|
|
}
|
|
}
|
|
}
|
|
function* And(expression: TemplateLiteralParser.And): IterableIterator<string> {
|
|
return yield* Reduce(expression.expr.map((expr) => [...Generate(expr)]))
|
|
}
|
|
function* Or(expression: TemplateLiteralParser.Or): IterableIterator<string> {
|
|
for (const expr of expression.expr) yield* Generate(expr)
|
|
}
|
|
function* Const(expression: TemplateLiteralParser.Const): IterableIterator<string> {
|
|
return yield expression.const
|
|
}
|
|
export function* Generate(expression: TemplateLiteralParser.Expression): IterableIterator<string> {
|
|
// prettier-ignore
|
|
return (
|
|
expression.type === 'and' ? yield* And(expression) :
|
|
expression.type === 'or' ? yield* Or(expression) :
|
|
expression.type === 'const' ? yield* Const(expression) :
|
|
(() => { throw new TemplateLiteralGeneratorError('Unknown expression') })()
|
|
)
|
|
}
|
|
}
|
|
// ---------------------------------------------------------------------
|
|
// TemplateLiteralDslParser
|
|
// ---------------------------------------------------------------------
|
|
export namespace TemplateLiteralDslParser {
|
|
function* ParseUnion(template: string): IterableIterator<TTemplateLiteralKind> {
|
|
const trim = template.trim().replace(/"|'/g, '')
|
|
// prettier-ignore
|
|
return (
|
|
trim === 'boolean' ? yield Type.Boolean() :
|
|
trim === 'number' ? yield Type.Number() :
|
|
trim === 'bigint' ? yield Type.BigInt() :
|
|
trim === 'string' ? yield Type.String() :
|
|
yield (() => {
|
|
const literals = trim.split('|').map((literal) => Type.Literal(literal.trim()))
|
|
return (
|
|
literals.length === 0 ? Type.Never() :
|
|
literals.length === 1 ? literals[0] :
|
|
Type.Union(literals)
|
|
)
|
|
})()
|
|
)
|
|
}
|
|
function* ParseTerminal(template: string): IterableIterator<TTemplateLiteralKind> {
|
|
if (template[1] !== '{') {
|
|
const L = Type.Literal('$')
|
|
const R = ParseLiteral(template.slice(1))
|
|
return yield* [L, ...R]
|
|
}
|
|
for (let i = 2; i < template.length; i++) {
|
|
if (template[i] === '}') {
|
|
const L = ParseUnion(template.slice(2, i))
|
|
const R = ParseLiteral(template.slice(i + 1))
|
|
return yield* [...L, ...R]
|
|
}
|
|
}
|
|
yield Type.Literal(template)
|
|
}
|
|
function* ParseLiteral(template: string): IterableIterator<TTemplateLiteralKind> {
|
|
for (let i = 0; i < template.length; i++) {
|
|
if (template[i] === '$') {
|
|
const L = Type.Literal(template.slice(0, i))
|
|
const R = ParseTerminal(template.slice(i))
|
|
return yield* [L, ...R]
|
|
}
|
|
}
|
|
yield Type.Literal(template)
|
|
}
|
|
export function Parse(template_dsl: string): TTemplateLiteralKind[] {
|
|
return [...ParseLiteral(template_dsl)]
|
|
}
|
|
}
|
|
// ---------------------------------------------------------------------
|
|
// TransformBuilder
|
|
// ---------------------------------------------------------------------
|
|
export class TransformDecodeBuilder<T extends TSchema> {
|
|
constructor(private readonly schema: T) {}
|
|
public Decode<U extends unknown, D extends TransformFunction<StaticDecode<T>, U>>(decode: D): TransformEncodeBuilder<T, D> {
|
|
return new TransformEncodeBuilder(this.schema, decode)
|
|
}
|
|
}
|
|
export class TransformEncodeBuilder<T extends TSchema, D extends TransformFunction> {
|
|
constructor(private readonly schema: T, private readonly decode: D) {}
|
|
public Encode<E extends TransformFunction<ReturnType<D>, StaticDecode<T>>>(encode: E): TTransform<T, ReturnType<D>> {
|
|
const schema = TypeClone.Type(this.schema)
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TTransform(schema) ? (() => {
|
|
const Encode = (value: unknown) => schema[Transform].Encode(encode(value as any))
|
|
const Decode = (value: unknown) => this.decode(schema[Transform].Decode(value))
|
|
const Codec = { Encode: Encode, Decode: Decode }
|
|
return { ...schema, [Transform]: Codec }
|
|
})() : (() => {
|
|
const Codec = { Decode: this.decode, Encode: encode }
|
|
return { ...schema, [Transform]: Codec }
|
|
})()
|
|
) as TTransform<T, ReturnType<D>>
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// TypeOrdinal: Used for auto $id generation
|
|
// --------------------------------------------------------------------------
|
|
let TypeOrdinal = 0
|
|
// --------------------------------------------------------------------------
|
|
// TypeBuilder
|
|
// --------------------------------------------------------------------------
|
|
export class TypeBuilderError extends TypeBoxError {}
|
|
export class TypeBuilder {
|
|
/** `[Internal]` Creates a schema without `static` and `params` types */
|
|
protected Create<T>(schema: Omit<T, 'static' | 'params'>): T {
|
|
return schema as any
|
|
}
|
|
/** `[Internal]` Throws a TypeBuilder error with the given message */
|
|
protected Throw(message: string): never {
|
|
throw new TypeBuilderError(message)
|
|
}
|
|
/** `[Internal]` Discards a property key from the given schema */
|
|
protected Discard(schema: TSchema, key: PropertyKey): TSchema {
|
|
const { [key as any]: _, ...rest } = schema
|
|
return rest as TSchema
|
|
}
|
|
/** `[Json]` Omits compositing symbols from this schema */
|
|
public Strict<T extends TSchema>(schema: T): T {
|
|
return JSON.parse(JSON.stringify(schema))
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// JsonTypeBuilder
|
|
// --------------------------------------------------------------------------
|
|
export class JsonTypeBuilder extends TypeBuilder {
|
|
// ------------------------------------------------------------------------
|
|
// Modifiers
|
|
// ------------------------------------------------------------------------
|
|
/** `[Json]` Creates a Readonly and Optional property */
|
|
public ReadonlyOptional<T extends TSchema>(schema: T): TReadonly<TOptional<T>> {
|
|
return this.Readonly(this.Optional(schema))
|
|
}
|
|
/** `[Json]` Creates a Readonly property */
|
|
public Readonly<T extends TSchema>(schema: T): TReadonly<T> {
|
|
return { ...TypeClone.Type(schema), [Readonly]: 'Readonly' }
|
|
}
|
|
/** `[Json]` Creates an Optional property */
|
|
public Optional<T extends TSchema>(schema: T): TOptional<T> {
|
|
return { ...TypeClone.Type(schema), [Optional]: 'Optional' }
|
|
}
|
|
// ------------------------------------------------------------------------
|
|
// Types
|
|
// ------------------------------------------------------------------------
|
|
/** `[Json]` Creates an Any type */
|
|
public Any(options: SchemaOptions = {}): TAny {
|
|
return this.Create({ ...options, [Kind]: 'Any' })
|
|
}
|
|
/** `[Json]` Creates an Array type */
|
|
public Array<T extends TSchema>(schema: T, options: ArrayOptions = {}): TArray<T> {
|
|
return this.Create({ ...options, [Kind]: 'Array', type: 'array', items: TypeClone.Type(schema) })
|
|
}
|
|
/** `[Json]` Creates a Boolean type */
|
|
public Boolean(options: SchemaOptions = {}): TBoolean {
|
|
return this.Create({ ...options, [Kind]: 'Boolean', type: 'boolean' })
|
|
}
|
|
/** `[Json]` Intrinsic function to Capitalize LiteralString types */
|
|
public Capitalize<T extends TSchema>(schema: T, options: SchemaOptions = {}): TIntrinsic<T, 'Capitalize'> {
|
|
return { ...Intrinsic.Map(TypeClone.Type(schema), 'Capitalize'), ...options }
|
|
}
|
|
/** `[Json]` Creates a Composite object type */
|
|
public Composite<T extends TObject[]>(objects: [...T], options?: ObjectOptions): TComposite<T> {
|
|
const intersect: any = Type.Intersect(objects, {})
|
|
const keys = KeyResolver.ResolveKeys(intersect, { includePatterns: false })
|
|
const properties = keys.reduce((acc, key) => ({ ...acc, [key]: Type.Index(intersect, [key]) }), {} as TProperties)
|
|
return Type.Object(properties, options) as TComposite<T>
|
|
}
|
|
/** `[Json]` Creates a Enum type */
|
|
public Enum<T extends Record<string, string | number>>(item: T, options: SchemaOptions = {}): TEnum<T> {
|
|
// prettier-ignore
|
|
const values = Object.getOwnPropertyNames(item).filter((key) => isNaN(key as any)).map((key) => item[key]) as T[keyof T][]
|
|
// prettier-ignore
|
|
const anyOf = values.map((value) => ValueGuard.IsString(value)
|
|
? { [Kind]: 'Literal', type: 'string' as const, const: value }
|
|
: { [Kind]: 'Literal', type: 'number' as const, const: value }
|
|
)
|
|
return this.Create({ ...options, [Kind]: 'Union', anyOf })
|
|
}
|
|
/** `[Json]` Creates a Conditional type */
|
|
public Extends<L extends TSchema, R extends TSchema, T extends TSchema, U extends TSchema>(left: L, right: R, trueType: T, falseType: U, options: SchemaOptions = {}): TExtends<L, R, T, U> {
|
|
switch (TypeExtends.Extends(left, right)) {
|
|
case TypeExtendsResult.Union:
|
|
return this.Union([TypeClone.Type(trueType, options), TypeClone.Type(falseType, options)]) as any as TExtends<L, R, T, U>
|
|
case TypeExtendsResult.True:
|
|
return TypeClone.Type(trueType, options) as unknown as TExtends<L, R, T, U>
|
|
case TypeExtendsResult.False:
|
|
return TypeClone.Type(falseType, options) as unknown as TExtends<L, R, T, U>
|
|
}
|
|
}
|
|
/** `[Json]` Constructs a type by excluding from unionType all union members that are assignable to excludedMembers */
|
|
public Exclude<L extends TSchema, R extends TSchema>(unionType: L, excludedMembers: R, options: SchemaOptions = {}): TExclude<L, R> {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TTemplateLiteral(unionType) ? this.Exclude(TemplateLiteralResolver.Resolve(unionType), excludedMembers, options) :
|
|
TypeGuard.TTemplateLiteral(excludedMembers) ? this.Exclude(unionType, TemplateLiteralResolver.Resolve(excludedMembers), options) :
|
|
TypeGuard.TUnion(unionType) ? (() => {
|
|
const narrowed = unionType.anyOf.filter((inner) => TypeExtends.Extends(inner, excludedMembers) === TypeExtendsResult.False)
|
|
return (narrowed.length === 1 ? TypeClone.Type(narrowed[0], options) : this.Union(narrowed, options)) as TExclude<L, R>
|
|
})() :
|
|
TypeExtends.Extends(unionType, excludedMembers) !== TypeExtendsResult.False ? this.Never(options) :
|
|
TypeClone.Type(unionType, options)
|
|
) as TExclude<L, R>
|
|
}
|
|
/** `[Json]` Constructs a type by extracting from type all union members that are assignable to union */
|
|
public Extract<L extends TSchema, R extends TSchema>(type: L, union: R, options: SchemaOptions = {}): TExtract<L, R> {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TTemplateLiteral(type) ? this.Extract(TemplateLiteralResolver.Resolve(type), union, options) :
|
|
TypeGuard.TTemplateLiteral(union) ? this.Extract(type, TemplateLiteralResolver.Resolve(union), options) :
|
|
TypeGuard.TUnion(type) ? (() => {
|
|
const narrowed = type.anyOf.filter((inner) => TypeExtends.Extends(inner, union) !== TypeExtendsResult.False)
|
|
return (narrowed.length === 1 ? TypeClone.Type(narrowed[0], options) : this.Union(narrowed, options))
|
|
})() :
|
|
TypeExtends.Extends(type, union) !== TypeExtendsResult.False ? TypeClone.Type(type, options) :
|
|
this.Never(options)
|
|
) as TExtract<L, R>
|
|
}
|
|
/** `[Json]` Returns an Indexed property type for the given keys */
|
|
public Index<T extends TArray, K extends TNumber>(schema: T, keys: K, options?: SchemaOptions): AssertType<T['items']>
|
|
/** `[Json]` Returns an Indexed property type for the given keys */
|
|
public Index<T extends TTuple, K extends (keyof Static<T>)[]>(schema: T, keys: [...K], options?: SchemaOptions): TIndex<T, Assert<K, TPropertyKey[]>>
|
|
/** `[Json]` Returns an Indexed property type for the given keys */
|
|
public Index<T extends TTuple, K extends TNumber>(schema: T, keys: K, options?: SchemaOptions): UnionType<AssertRest<T['items']>>
|
|
/** `[Json]` Returns an Indexed property type for the given keys */
|
|
public Index<T extends TSchema, K extends TTemplateLiteral>(schema: T, keys: K, options?: SchemaOptions): TIndex<T, TTemplateLiteralKeyRest<K>>
|
|
/** `[Json]` Returns an Indexed property type for the given keys */
|
|
public Index<T extends TSchema, K extends TLiteral<TPropertyKey>>(schema: T, keys: K, options?: SchemaOptions): TIndex<T, [K['const']]>
|
|
/** `[Json]` Returns an Indexed property type for the given keys */
|
|
public Index<T extends TSchema, K extends (keyof Static<T>)[]>(schema: T, keys: [...K], options?: SchemaOptions): TIndex<T, Assert<K, TPropertyKey[]>>
|
|
/** `[Json]` Returns an Indexed property type for the given keys */
|
|
public Index<T extends TSchema, K extends TUnion<TLiteral<TPropertyKey>[]>>(schema: T, keys: K, options?: SchemaOptions): TIndex<T, TUnionLiteralKeyRest<K>>
|
|
/** `[Json]` Returns an Indexed property type for the given keys */
|
|
public Index<T extends TSchema, K extends TSchema>(schema: T, key: K, options?: SchemaOptions): TSchema
|
|
/** `[Json]` Returns an Indexed property type for the given keys */
|
|
public Index(schema: TSchema, unresolved: any, options: SchemaOptions = {}): any {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TArray(schema) && TypeGuard.TNumber(unresolved) ? (() => {
|
|
return TypeClone.Type(schema.items, options)
|
|
})() :
|
|
TypeGuard.TTuple(schema) && TypeGuard.TNumber(unresolved) ? (() => {
|
|
const items = ValueGuard.IsUndefined(schema.items) ? [] : schema.items
|
|
const cloned = items.map((schema) => TypeClone.Type(schema))
|
|
return this.Union(cloned, options)
|
|
})() : (() => {
|
|
const keys = KeyArrayResolver.Resolve(unresolved)
|
|
const clone = TypeClone.Type(schema)
|
|
return IndexedAccessor.Resolve(clone, keys, options)
|
|
})()
|
|
)
|
|
}
|
|
/** `[Json]` Creates an Integer type */
|
|
public Integer(options: NumericOptions<number> = {}): TInteger {
|
|
return this.Create({ ...options, [Kind]: 'Integer', type: 'integer' })
|
|
}
|
|
/** `[Json]` Creates an Intersect type */
|
|
public Intersect(allOf: [], options?: SchemaOptions): TNever
|
|
/** `[Json]` Creates an Intersect type */
|
|
public Intersect<T extends [TSchema]>(allOf: [...T], options?: SchemaOptions): T[0]
|
|
/** `[Json]` Creates an Intersect type */
|
|
public Intersect<T extends TSchema[]>(allOf: [...T], options?: IntersectOptions): TIntersect<T>
|
|
/** `[Json]` Creates an Intersect type */
|
|
public Intersect(allOf: TSchema[], options: IntersectOptions = {}) {
|
|
if (allOf.length === 0) return Type.Never()
|
|
if (allOf.length === 1) return TypeClone.Type(allOf[0], options)
|
|
if (allOf.some((schema) => TypeGuard.TTransform(schema))) this.Throw('Cannot intersect transform types')
|
|
const objects = allOf.every((schema) => TypeGuard.TObject(schema))
|
|
const cloned = TypeClone.Rest(allOf)
|
|
// prettier-ignore
|
|
const clonedUnevaluatedProperties = TypeGuard.TSchema(options.unevaluatedProperties)
|
|
? { unevaluatedProperties: TypeClone.Type(options.unevaluatedProperties) }
|
|
: {}
|
|
return options.unevaluatedProperties === false || TypeGuard.TSchema(options.unevaluatedProperties) || objects
|
|
? this.Create({ ...options, ...clonedUnevaluatedProperties, [Kind]: 'Intersect', type: 'object', allOf: cloned })
|
|
: this.Create({ ...options, ...clonedUnevaluatedProperties, [Kind]: 'Intersect', allOf: cloned })
|
|
}
|
|
/** `[Json]` Creates a KeyOf type */
|
|
public KeyOf<T extends TSchema>(schema: T, options: SchemaOptions = {}): TKeyOf<T> {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TRecord(schema) ? (() => {
|
|
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
|
|
return (
|
|
pattern === PatternNumberExact ? this.Number(options) :
|
|
pattern === PatternStringExact ? this.String(options) :
|
|
this.Throw('Unable to resolve key type from Record key pattern')
|
|
)
|
|
})() :
|
|
TypeGuard.TTuple(schema) ? (() => {
|
|
const items = ValueGuard.IsUndefined(schema.items) ? [] : schema.items
|
|
const literals = items.map((_, index) => Type.Literal(index.toString()))
|
|
return this.Union(literals, options)
|
|
})() :
|
|
TypeGuard.TArray(schema) ? (() => {
|
|
return this.Number(options)
|
|
})() : (() => {
|
|
const keys = KeyResolver.ResolveKeys(schema, { includePatterns: false })
|
|
if (keys.length === 0) return this.Never(options) as TKeyOf<T>
|
|
const literals = keys.map((key) => this.Literal(key))
|
|
return this.Union(literals, options)
|
|
})()
|
|
) as unknown as TKeyOf<T>
|
|
}
|
|
/** `[Json]` 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' })
|
|
}
|
|
/** `[Json]` Intrinsic function to Lowercase LiteralString types */
|
|
public Lowercase<T extends TSchema>(schema: T, options: SchemaOptions = {}): TIntrinsic<T, 'Lowercase'> {
|
|
return { ...Intrinsic.Map(TypeClone.Type(schema), 'Lowercase'), ...options }
|
|
}
|
|
/** `[Json]` Creates a Never type */
|
|
public Never(options: SchemaOptions = {}): TNever {
|
|
return this.Create({ ...options, [Kind]: 'Never', not: {} })
|
|
}
|
|
/** `[Json]` Creates a Not type */
|
|
public Not<T extends TSchema>(schema: T, options?: SchemaOptions): TNot<T> {
|
|
return this.Create({ ...options, [Kind]: 'Not', not: TypeClone.Type(schema) })
|
|
}
|
|
/** `[Json]` Creates a Null type */
|
|
public Null(options: SchemaOptions = {}): TNull {
|
|
return this.Create({ ...options, [Kind]: 'Null', type: 'null' })
|
|
}
|
|
/** `[Json]` Creates a Number type */
|
|
public Number(options: NumericOptions<number> = {}): TNumber {
|
|
return this.Create({ ...options, [Kind]: 'Number', type: 'number' })
|
|
}
|
|
/** `[Json]` Creates an Object type */
|
|
public Object<T extends TProperties>(properties: T, options: ObjectOptions = {}): TObject<T> {
|
|
const propertyKeys = Object.getOwnPropertyNames(properties)
|
|
const optionalKeys = propertyKeys.filter((key) => TypeGuard.TOptional(properties[key]))
|
|
const requiredKeys = propertyKeys.filter((name) => !optionalKeys.includes(name))
|
|
const clonedAdditionalProperties = TypeGuard.TSchema(options.additionalProperties) ? { additionalProperties: TypeClone.Type(options.additionalProperties) } : {}
|
|
const clonedProperties = propertyKeys.reduce((acc, key) => ({ ...acc, [key]: TypeClone.Type(properties[key]) }), {} as TProperties)
|
|
return requiredKeys.length > 0
|
|
? this.Create({ ...options, ...clonedAdditionalProperties, [Kind]: 'Object', type: 'object', properties: clonedProperties, required: requiredKeys })
|
|
: this.Create({ ...options, ...clonedAdditionalProperties, [Kind]: 'Object', type: 'object', properties: clonedProperties })
|
|
}
|
|
/** `[Json]` Constructs a type whose keys are omitted from the given type */
|
|
public Omit<T extends TSchema, K extends (keyof Static<T>)[]>(schema: T, keys: readonly [...K], options?: SchemaOptions): TOmit<T, K[number]>
|
|
/** `[Json]` Constructs a type whose keys are omitted from the given type */
|
|
public Omit<T extends TSchema, K extends TUnion<TLiteral<string>[]>>(schema: T, keys: K, options?: SchemaOptions): TOmit<T, TUnionLiteralKeyRest<K>[number]>
|
|
/** `[Json]` Constructs a type whose keys are omitted from the given type */
|
|
public Omit<T extends TSchema, K extends TLiteral<string>>(schema: T, key: K, options?: SchemaOptions): TOmit<T, K['const']>
|
|
/** `[Json]` Constructs a type whose keys are omitted from the given type */
|
|
public Omit<T extends TSchema, K extends TTemplateLiteral>(schema: T, key: K, options?: SchemaOptions): TOmit<T, TTemplateLiteralKeyRest<K>[number]>
|
|
/** `[Json]` Constructs a type whose keys are omitted from the given type */
|
|
public Omit<T extends TSchema, K extends TNever>(schema: T, key: K, options?: SchemaOptions): TOmit<T, never>
|
|
/** `[Json]` Constructs a type whose keys are omitted from the given type */
|
|
public Omit(schema: TSchema, unresolved: any, options: SchemaOptions = {}): any {
|
|
const keys = KeyArrayResolver.Resolve(unresolved)
|
|
// prettier-ignore
|
|
return ObjectMap.Map(this.Discard(TypeClone.Type(schema), Transform), (object) => {
|
|
if (ValueGuard.IsArray(object.required)) {
|
|
object.required = object.required.filter((key: string) => !keys.includes(key as any))
|
|
if (object.required.length === 0) delete object.required
|
|
}
|
|
for (const key of Object.getOwnPropertyNames(object.properties)) {
|
|
if (keys.includes(key as any)) delete object.properties[key]
|
|
}
|
|
return this.Create(object)
|
|
}, options)
|
|
}
|
|
/** `[Json]` Constructs a type where all properties are optional */
|
|
public Partial<T extends TSchema>(schema: T, options: ObjectOptions = {}): TPartial<T> {
|
|
// prettier-ignore
|
|
return ObjectMap.Map(this.Discard(TypeClone.Type(schema), Transform), (object) => {
|
|
const properties = Object.getOwnPropertyNames(object.properties).reduce((acc, key) => {
|
|
return { ...acc, [key]: this.Optional(object.properties[key]) }
|
|
}, {} as TProperties)
|
|
return this.Object(properties, this.Discard(object, 'required') /* object used as options to retain other constraints */)
|
|
}, options)
|
|
}
|
|
/** `[Json]` Constructs a type whose keys are picked from the given type */
|
|
public Pick<T extends TSchema, K extends (keyof Static<T>)[]>(schema: T, keys: readonly [...K], options?: SchemaOptions): TPick<T, K[number]>
|
|
/** `[Json]` Constructs a type whose keys are picked from the given type */
|
|
public Pick<T extends TSchema, K extends TUnion<TLiteral<string>[]>>(schema: T, keys: K, options?: SchemaOptions): TPick<T, TUnionLiteralKeyRest<K>[number]>
|
|
/** `[Json]` Constructs a type whose keys are picked from the given type */
|
|
public Pick<T extends TSchema, K extends TLiteral<string>>(schema: T, key: K, options?: SchemaOptions): TPick<T, K['const']>
|
|
/** `[Json]` Constructs a type whose keys are picked from the given type */
|
|
public Pick<T extends TSchema, K extends TTemplateLiteral>(schema: T, key: K, options?: SchemaOptions): TPick<T, TTemplateLiteralKeyRest<K>[number]>
|
|
/** `[Json]` Constructs a type whose keys are picked from the given type */
|
|
public Pick<T extends TSchema, K extends TNever>(schema: T, key: K, options?: SchemaOptions): TPick<T, never>
|
|
/** `[Json]` Constructs a type whose keys are picked from the given type */
|
|
public Pick(schema: TSchema, unresolved: any, options: SchemaOptions = {}): any {
|
|
const keys = KeyArrayResolver.Resolve(unresolved)
|
|
// prettier-ignore
|
|
return ObjectMap.Map(this.Discard(TypeClone.Type(schema), Transform), (object) => {
|
|
if (ValueGuard.IsArray(object.required)) {
|
|
object.required = object.required.filter((key: any) => keys.includes(key))
|
|
if (object.required.length === 0) delete object.required
|
|
}
|
|
for (const key of Object.getOwnPropertyNames(object.properties)) {
|
|
if (!keys.includes(key as any)) delete object.properties[key]
|
|
}
|
|
return this.Create(object)
|
|
}, options)
|
|
}
|
|
/** `[Json]` Creates a Record type */
|
|
public Record<K extends TSchema, T extends TSchema>(key: K, schema: T, options: ObjectOptions = {}): TRecordResolve<K, T> {
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TTemplateLiteral(key) ? (() => {
|
|
const expression = TemplateLiteralParser.ParseExact(key.pattern)
|
|
// prettier-ignore
|
|
return TemplateLiteralFinite.Check(expression)
|
|
? (this.Object([...TemplateLiteralGenerator.Generate(expression)].reduce((acc, key) => ({ ...acc, [key]: TypeClone.Type(schema) }), {} as TProperties), options))
|
|
: this.Create<any>({ ...options, [Kind]: 'Record', type: 'object', patternProperties: { [key.pattern]: TypeClone.Type(schema) }})
|
|
})() :
|
|
TypeGuard.TUnion(key) ? (() => {
|
|
const union = UnionResolver.Resolve(key)
|
|
if (TypeGuard.TUnionLiteral(union)) {
|
|
const properties = union.anyOf.reduce((acc: any, literal: any) => ({ ...acc, [literal.const]: TypeClone.Type(schema) }), {} as TProperties)
|
|
return this.Object(properties, { ...options, [Hint]: 'Record' })
|
|
} else this.Throw('Record key of type union contains non-literal types')
|
|
})() :
|
|
TypeGuard.TLiteral(key) ? (() => {
|
|
// prettier-ignore
|
|
return (ValueGuard.IsString(key.const) || ValueGuard.IsNumber(key.const))
|
|
? this.Object({ [key.const]: TypeClone.Type(schema) }, options)
|
|
: this.Throw('Record key of type literal is not of type string or number')
|
|
})() :
|
|
TypeGuard.TInteger(key) || TypeGuard.TNumber(key) ? (() => {
|
|
return this.Create<any>({ ...options, [Kind]: 'Record', type: 'object', patternProperties: { [PatternNumberExact]: TypeClone.Type(schema) } })
|
|
})() :
|
|
TypeGuard.TString(key) ? (() => {
|
|
const pattern = ValueGuard.IsUndefined(key.pattern) ? PatternStringExact : key.pattern
|
|
return this.Create<any>({ ...options, [Kind]: 'Record', type: 'object', patternProperties: { [pattern]: TypeClone.Type(schema) } })
|
|
})() :
|
|
this.Never()
|
|
)
|
|
}
|
|
/** `[Json]` Creates a Recursive type */
|
|
public Recursive<T extends TSchema>(callback: (thisType: TThis) => T, options: SchemaOptions = {}): TRecursive<T> {
|
|
if (ValueGuard.IsUndefined(options.$id)) (options as any).$id = `T${TypeOrdinal++}`
|
|
const thisType = callback({ [Kind]: 'This', $ref: `${options.$id}` } as any)
|
|
thisType.$id = options.$id
|
|
return this.Create({ ...options, [Hint]: 'Recursive', ...thisType } as any)
|
|
}
|
|
/** `[Json]` Creates a Ref type. The referenced type must contain a $id */
|
|
public Ref<T extends TSchema>(schema: T, options?: SchemaOptions): TRef<T>
|
|
/** `[Json]` Creates a Ref type. */
|
|
public Ref<T extends TSchema>($ref: string, options?: SchemaOptions): TRef<T>
|
|
/** `[Json]` Creates a Ref type. */
|
|
public Ref(unresolved: TSchema | string, options: SchemaOptions = {}) {
|
|
if (ValueGuard.IsString(unresolved)) return this.Create({ ...options, [Kind]: 'Ref', $ref: unresolved })
|
|
if (ValueGuard.IsUndefined(unresolved.$id)) this.Throw('Reference target type must specify an $id')
|
|
return this.Create({ ...options, [Kind]: 'Ref', $ref: unresolved.$id! })
|
|
}
|
|
/** `[Json]` Constructs a type where all properties are required */
|
|
public Required<T extends TSchema>(schema: T, options: SchemaOptions = {}): TRequired<T> {
|
|
// prettier-ignore
|
|
return ObjectMap.Map(this.Discard(TypeClone.Type(schema), Transform), (object) => {
|
|
const properties = Object.getOwnPropertyNames(object.properties).reduce((acc, key) => {
|
|
return { ...acc, [key]: this.Discard(object.properties[key], Optional) as TSchema }
|
|
}, {} as TProperties)
|
|
return this.Object(properties, object /* object used as options to retain other constraints */)
|
|
}, options)
|
|
}
|
|
/** `[Json]` Extracts interior Rest elements from Tuple, Intersect and Union types */
|
|
public Rest<T extends TSchema>(schema: T): TRest<T> {
|
|
return (
|
|
TypeGuard.TTuple(schema) && !ValueGuard.IsUndefined(schema.items) ? TypeClone.Rest(schema.items) : TypeGuard.TIntersect(schema) ? TypeClone.Rest(schema.allOf) : TypeGuard.TUnion(schema) ? TypeClone.Rest(schema.anyOf) : []
|
|
) as TRest<T>
|
|
}
|
|
/** `[Json]` Creates a String type */
|
|
public String(options: StringOptions = {}): TString {
|
|
return this.Create({ ...options, [Kind]: 'String', type: 'string' })
|
|
}
|
|
/** `[Json]` Creates a TemplateLiteral type from template dsl string */
|
|
public TemplateLiteral<T extends string>(templateDsl: T, options?: SchemaOptions): TTemplateLiteralDslParser<T>
|
|
/** `[Json]` Creates a TemplateLiteral type */
|
|
public TemplateLiteral<T extends TTemplateLiteralKind[]>(kinds: [...T], options?: SchemaOptions): TTemplateLiteral<T>
|
|
/** `[Json]` Creates a TemplateLiteral type */
|
|
public TemplateLiteral(unresolved: unknown, options: SchemaOptions = {}) {
|
|
// prettier-ignore
|
|
const pattern = ValueGuard.IsString(unresolved)
|
|
? TemplateLiteralPattern.Create(TemplateLiteralDslParser.Parse(unresolved))
|
|
: TemplateLiteralPattern.Create(unresolved as TTemplateLiteralKind[])
|
|
return this.Create({ ...options, [Kind]: 'TemplateLiteral', type: 'string', pattern })
|
|
}
|
|
/** `[Json]` Creates a Transform type */
|
|
public Transform<I extends TSchema>(schema: I): TransformDecodeBuilder<I> {
|
|
return new TransformDecodeBuilder(schema)
|
|
}
|
|
/** `[Json]` Creates a Tuple type */
|
|
public Tuple<T extends TSchema[]>(items: [...T], options: SchemaOptions = {}): TTuple<T> {
|
|
const [additionalItems, minItems, maxItems] = [false, items.length, items.length]
|
|
const clonedItems = TypeClone.Rest(items)
|
|
// prettier-ignore
|
|
const schema = (items.length > 0 ?
|
|
{ ...options, [Kind]: 'Tuple', type: 'array', items: clonedItems, additionalItems, minItems, maxItems } :
|
|
{ ...options, [Kind]: 'Tuple', type: 'array', minItems, maxItems }) as any
|
|
return this.Create(schema)
|
|
}
|
|
/** `[Json]` Intrinsic function to Uncapitalize LiteralString types */
|
|
public Uncapitalize<T extends TSchema>(schema: T, options: SchemaOptions = {}): TIntrinsic<T, 'Uncapitalize'> {
|
|
return { ...Intrinsic.Map(TypeClone.Type(schema), 'Uncapitalize'), ...options }
|
|
}
|
|
/** `[Json]` Creates a Union type */
|
|
public Union(anyOf: [], options?: SchemaOptions): TNever
|
|
/** `[Json]` Creates a Union type */
|
|
public Union<T extends [TSchema]>(anyOf: [...T], options?: SchemaOptions): T[0]
|
|
/** `[Json]` Creates a Union type */
|
|
public Union<T extends TSchema[]>(anyOf: [...T], options?: SchemaOptions): TUnion<T>
|
|
/** `[Json-Experimental]` Converts a TemplateLiteral into a Union */
|
|
public Union<T extends TTemplateLiteral>(template: T): TUnionTemplateLiteral<T>
|
|
/** `[Json]` Creates a Union type */
|
|
public Union(union: TSchema[] | TTemplateLiteral, options: SchemaOptions = {}) {
|
|
// prettier-ignore
|
|
return TypeGuard.TTemplateLiteral(union)
|
|
? TemplateLiteralResolver.Resolve(union)
|
|
: (() => {
|
|
const anyOf = union
|
|
if (anyOf.length === 0) return this.Never(options)
|
|
if (anyOf.length === 1) return this.Create(TypeClone.Type(anyOf[0], options))
|
|
const clonedAnyOf = TypeClone.Rest(anyOf)
|
|
return this.Create({ ...options, [Kind]: 'Union', anyOf: clonedAnyOf })
|
|
})()
|
|
}
|
|
/** `[Json]` Creates an Unknown type */
|
|
public Unknown(options: SchemaOptions = {}): TUnknown {
|
|
return this.Create({ ...options, [Kind]: 'Unknown' })
|
|
}
|
|
/** `[Json]` Creates a Unsafe type that will infers as the generic argument T */
|
|
public Unsafe<T>(options: UnsafeOptions = {}): TUnsafe<T> {
|
|
return this.Create({ ...options, [Kind]: options[Kind] || 'Unsafe' })
|
|
}
|
|
/** `[Json]` Intrinsic function to Uppercase LiteralString types */
|
|
public Uppercase<T extends TSchema>(schema: T, options: SchemaOptions = {}): TIntrinsic<T, 'Uppercase'> {
|
|
return { ...Intrinsic.Map(TypeClone.Type(schema), 'Uppercase'), ...options }
|
|
}
|
|
}
|
|
// --------------------------------------------------------------------------
|
|
// JavaScriptTypeBuilder
|
|
// --------------------------------------------------------------------------
|
|
export class JavaScriptTypeBuilder extends JsonTypeBuilder {
|
|
/** `[JavaScript]` Creates a AsyncIterator type */
|
|
public AsyncIterator<T extends TSchema>(items: T, options: SchemaOptions = {}): TAsyncIterator<T> {
|
|
return this.Create({ ...options, [Kind]: 'AsyncIterator', type: 'AsyncIterator', items: TypeClone.Type(items) })
|
|
}
|
|
/** `[JavaScript]` Constructs a type by recursively unwrapping Promise types */
|
|
public Awaited<T extends TSchema>(schema: T, options: SchemaOptions = {}): TAwaited<T> {
|
|
// prettier-ignore
|
|
const Unwrap = (rest: TSchema[]): TSchema[] => rest.length > 0 ? (() => {
|
|
const [L, ...R] = rest
|
|
return [this.Awaited(L), ...Unwrap(R)]
|
|
})() : rest
|
|
// prettier-ignore
|
|
return (
|
|
TypeGuard.TIntersect(schema) ? Type.Intersect(Unwrap(schema.allOf)) :
|
|
TypeGuard.TUnion(schema) ? Type.Union(Unwrap(schema.anyOf)) :
|
|
TypeGuard.TPromise(schema) ? this.Awaited(schema.item) :
|
|
TypeClone.Type(schema, options)
|
|
) as TAwaited<T>
|
|
}
|
|
/** `[JavaScript]` Creates a BigInt type */
|
|
public BigInt(options: NumericOptions<bigint> = {}): TBigInt {
|
|
return this.Create({ ...options, [Kind]: 'BigInt', type: 'bigint' })
|
|
}
|
|
/** `[JavaScript]` Extracts the ConstructorParameters from the given Constructor type */
|
|
public ConstructorParameters<T extends TConstructor<any[], any>>(schema: T, options: SchemaOptions = {}): TConstructorParameters<T> {
|
|
return this.Tuple([...schema.parameters], { ...options })
|
|
}
|
|
/** `[JavaScript]` Creates a Constructor type */
|
|
public Constructor<T extends TSchema[], U extends TSchema>(parameters: [...T], returns: U, options?: SchemaOptions): TConstructor<T, U> {
|
|
const [clonedParameters, clonedReturns] = [TypeClone.Rest(parameters), TypeClone.Type(returns)]
|
|
return this.Create({ ...options, [Kind]: 'Constructor', type: 'constructor', parameters: clonedParameters, returns: clonedReturns })
|
|
}
|
|
/** `[JavaScript]` Creates a Date type */
|
|
public Date(options: DateOptions = {}): TDate {
|
|
return this.Create({ ...options, [Kind]: 'Date', type: 'Date' })
|
|
}
|
|
/** `[JavaScript]` Creates a Function type */
|
|
public Function<T extends TSchema[], U extends TSchema>(parameters: [...T], returns: U, options?: SchemaOptions): TFunction<T, U> {
|
|
const [clonedParameters, clonedReturns] = [TypeClone.Rest(parameters), TypeClone.Type(returns)]
|
|
return this.Create({ ...options, [Kind]: 'Function', type: 'function', parameters: clonedParameters, returns: clonedReturns })
|
|
}
|
|
/** `[JavaScript]` Extracts the InstanceType from the given Constructor type */
|
|
public InstanceType<T extends TConstructor<any[], any>>(schema: T, options: SchemaOptions = {}): TInstanceType<T> {
|
|
return TypeClone.Type(schema.returns, options)
|
|
}
|
|
/** `[JavaScript]` Creates an Iterator type */
|
|
public Iterator<T extends TSchema>(items: T, options: SchemaOptions = {}): TIterator<T> {
|
|
return this.Create({ ...options, [Kind]: 'Iterator', type: 'Iterator', items: TypeClone.Type(items) })
|
|
}
|
|
/** `[JavaScript]` Extracts the Parameters from the given Function type */
|
|
public Parameters<T extends TFunction<any[], any>>(schema: T, options: SchemaOptions = {}): TParameters<T> {
|
|
return this.Tuple(schema.parameters, { ...options })
|
|
}
|
|
/** `[JavaScript]` Creates a Promise type */
|
|
public Promise<T extends TSchema>(item: T, options: SchemaOptions = {}): TPromise<T> {
|
|
return this.Create({ ...options, [Kind]: 'Promise', type: 'Promise', item: TypeClone.Type(item) })
|
|
}
|
|
/** `[JavaScript]` Creates a String type from a Regular Expression pattern */
|
|
public RegExp(pattern: string, options?: SchemaOptions): TString
|
|
/** `[JavaScript]` Creates a String type from a Regular Expression */
|
|
public RegExp(regex: RegExp, options?: SchemaOptions): TString
|
|
/** `[Extended]` Creates a String type */
|
|
public RegExp(unresolved: string | RegExp, options: SchemaOptions = {}) {
|
|
const pattern = ValueGuard.IsString(unresolved) ? unresolved : unresolved.source
|
|
return this.Create({ ...options, [Kind]: 'String', type: 'string', pattern })
|
|
}
|
|
/**
|
|
* @deprecated Use `Type.RegExp`
|
|
*/
|
|
public RegEx(regex: RegExp, options: SchemaOptions = {}): TString {
|
|
return this.RegExp(regex, options)
|
|
}
|
|
/** `[JavaScript]` Extracts the ReturnType from the given Function type */
|
|
public ReturnType<T extends TFunction<any[], any>>(schema: T, options: SchemaOptions = {}): TReturnType<T> {
|
|
return TypeClone.Type(schema.returns, options)
|
|
}
|
|
/** `[JavaScript]` Creates a Symbol type */
|
|
public Symbol(options?: SchemaOptions): TSymbol {
|
|
return this.Create({ ...options, [Kind]: 'Symbol', type: 'symbol' })
|
|
}
|
|
/** `[JavaScript]` Creates a Undefined type */
|
|
public Undefined(options: SchemaOptions = {}): TUndefined {
|
|
return this.Create({ ...options, [Kind]: 'Undefined', type: 'undefined' })
|
|
}
|
|
/** `[JavaScript]` Creates a Uint8Array type */
|
|
public Uint8Array(options: Uint8ArrayOptions = {}): TUint8Array {
|
|
return this.Create({ ...options, [Kind]: 'Uint8Array', type: 'Uint8Array' })
|
|
}
|
|
/** `[JavaScript]` Creates a Void type */
|
|
public Void(options: SchemaOptions = {}): TVoid {
|
|
return this.Create({ ...options, [Kind]: 'Void', type: 'void' })
|
|
}
|
|
}
|
|
/** Json Type Builder with Static Resolution for TypeScript */
|
|
export const JsonType = new JsonTypeBuilder()
|
|
/** JavaScript Type Builder with Static Resolution for TypeScript */
|
|
export const Type = new JavaScriptTypeBuilder()
|