This commit is contained in:
sinclair
2025-12-24 15:44:34 +09:00
commit 13d553220c
1047 changed files with 80931 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
/*--------------------------------------------------------------------------
@sinclair/typebox
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import * as Types from '@sinclair/typebox'
// -------------------------------------------------------------------
// Annotation
//
// Generates TypeScript Type Annotations from TypeBox types
// -------------------------------------------------------------------
/** Generates TypeScript Type Annotations from TypeBox types */
export namespace Annotation {
// -----------------------------------------------------------------
// Escape
// -----------------------------------------------------------------
function Escape(content: string) {
return content.replace(/'/g, "\\'")
}
// -----------------------------------------------------------------
// Types
// -----------------------------------------------------------------
function Intersect(schema: Types.TSchema[], references: Types.TSchema[]): string {
const [L, ...R] = schema
// prettier-ignore
return R.length === 0
? `${Visit(L, references)}`
: `${Visit(L, references)} & ${Intersect(R, references)}`
}
function Union(schema: Types.TSchema[], references: Types.TSchema[]): string {
const [L, ...R] = schema
// prettier-ignore
return R.length === 0
? `${Visit(L, references)}`
: `${Visit(L, references)} | ${Union(R, references)}`
}
function Tuple(schema: Types.TSchema[], references: Types.TSchema[]): string {
const [L, ...R] = schema
// prettier-ignore
return R.length > 0
? `${Visit(L, references)}, ${Tuple(R, references)}`
: ``
}
function Property(schema: Types.TProperties, K: string, references: Types.TSchema[]): string {
const TK = schema[K]
// prettier-ignore
return (
Types.TypeGuard.IsOptional(TK) && Types.TypeGuard.IsReadonly(TK) ? `readonly ${K}?: ${Visit(TK, references)}` :
Types.TypeGuard.IsReadonly(TK) ? `readonly ${K}: ${Visit(TK, references)}` :
Types.TypeGuard.IsOptional(TK) ? `${K}?: ${Visit(TK, references)}` :
`${K}: ${Visit(TK, references)}`
)
}
function Properties(schema: Types.TProperties, K: string[], references: Types.TSchema[]): string {
const [L, ...R] = K
// prettier-ignore
return R.length === 0
? `${Property(schema, L, references)}`
: `${Property(schema, L, references)}; ${Properties(schema, R, references)}`
}
function Parameters(schema: Types.TSchema[], I: number, references: Types.TSchema[]): string {
const [L, ...R] = schema
// prettier-ignore
return R.length === 0
? `param_${I}: ${Visit(L, references)}`
: `param_${I}: ${Visit(L, references)}, ${Parameters(R, I + 1, references)}`
}
function Literal(schema: Types.TLiteral, references: Types.TSchema[]): string {
return typeof schema.const === 'string' ? `'${Escape(schema.const)}'` : schema.const.toString()
}
function Record(schema: Types.TRecord, references: Types.TSchema[]): string {
// prettier-ignore
return (
Types.PatternBooleanExact in schema.patternProperties ? `Record<boolean, ${Visit(schema.patternProperties[Types.PatternBooleanExact], references)}>` :
Types.PatternNumberExact in schema.patternProperties ? `Record<number, ${Visit(schema.patternProperties[Types.PatternNumberExact], references)}>` :
Types.PatternStringExact in schema.patternProperties ? `Record<string, ${Visit(schema.patternProperties[Types.PatternStringExact], references)}>` :
`{}`
)
}
function TemplateLiteral(schema: Types.TTemplateLiteral, references: Types.TSchema[]) {
const E = Types.TemplateLiteralParseExact(schema.pattern)
if (!Types.IsTemplateLiteralExpressionFinite(E)) return 'string'
return [...Types.TemplateLiteralExpressionGenerate(E)].map((literal) => `'${Escape(literal)}'`).join(' | ')
}
function Visit(schema: Types.TSchema, references: Types.TSchema[]): string {
// prettier-ignore
return (
Types.TypeGuard.IsAny(schema) ? 'any' :
Types.TypeGuard.IsArray(schema) ? `${Visit(schema.items, references)}[]` :
Types.TypeGuard.IsAsyncIterator(schema) ? `AsyncIterableIterator<${Visit(schema.items, references)}>` :
Types.TypeGuard.IsBigInt(schema) ? `bigint` :
Types.TypeGuard.IsBoolean(schema) ? `boolean` :
Types.TypeGuard.IsConstructor(schema) ? `new (${Parameters(schema.parameter, 0, references)}) => ${Visit(schema.returns, references)}` :
Types.TypeGuard.IsDate(schema) ? 'Date' :
Types.TypeGuard.IsFunction(schema) ? `(${Parameters(schema.parameters, 0, references)}) => ${Visit(schema.returns, references)}` :
Types.TypeGuard.IsInteger(schema) ? 'number' :
Types.TypeGuard.IsIntersect(schema) ? `(${Intersect(schema.allOf, references)})` :
Types.TypeGuard.IsIterator(schema) ? `IterableIterator<${Visit(schema.items, references)}>` :
Types.TypeGuard.IsLiteral(schema) ? `${Literal(schema, references)}` :
Types.TypeGuard.IsNever(schema) ? `never` :
Types.TypeGuard.IsNull(schema) ? `null` :
Types.TypeGuard.IsNot(schema) ? 'unknown' :
Types.TypeGuard.IsNumber(schema) ? 'number' :
Types.TypeGuard.IsObject(schema) ? `{ ${Properties(schema.properties, Object.getOwnPropertyNames(schema.properties), references)} }` :
Types.TypeGuard.IsPromise(schema) ? `Promise<${Visit(schema.item, references)}>` :
Types.TypeGuard.IsRecord(schema) ? `${Record(schema, references)}` :
Types.TypeGuard.IsRef(schema) ? `${Visit(Types.Type.Deref(schema, references), references)}` :
Types.TypeGuard.IsString(schema) ? 'string' :
Types.TypeGuard.IsSymbol(schema) ? 'symbol' :
Types.TypeGuard.IsTemplateLiteral(schema) ? `${TemplateLiteral(schema, references)}` :
Types.TypeGuard.IsThis(schema) ? 'unknown' : // requires named interface
Types.TypeGuard.IsTuple(schema) ? `[${Tuple(schema.items || [], references)}]` :
Types.TypeGuard.IsUint8Array(schema) ? `Uint8Array` :
Types.TypeGuard.IsUndefined(schema) ? 'undefined' :
Types.TypeGuard.IsUnion(schema) ? `${Union(schema.anyOf, references)}` :
Types.TypeGuard.IsVoid(schema) ? `void` :
'unknown'
)
}
/** Generates a TypeScript annotation for the given schema */
export function Code(schema: Types.TSchema, references: Types.TSchema[] = []): string {
return Visit(schema, references)
}
}

View File

@@ -0,0 +1 @@
export * from './annotation'

View File

@@ -0,0 +1,350 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/collections
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import { TypeCheck, TypeCompiler, ValueError } from '@sinclair/typebox/compiler'
import { TSchema, Static, TypeBoxError } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
// ----------------------------------------------------------------
// TypeArrayError
// ----------------------------------------------------------------
export class TypeArrayError extends TypeBoxError {
constructor(message: string) {
super(`${message}`)
}
}
export class TypeArrayLengthError extends TypeBoxError {
constructor() {
super('arrayLength not a number')
}
}
// ----------------------------------------------------------------
// TypeArray<T>
// ----------------------------------------------------------------
export class TypeArray<T extends TSchema> implements Iterable<Static<T>> {
readonly #typeCheck: TypeCheck<T>
readonly #values: Static<T>[]
constructor(schema: T, arrayLength: number = 0) {
if (typeof arrayLength !== 'number') throw new TypeArrayLengthError()
this.#typeCheck = TypeCompiler.Compile(schema)
this.#values = new Array(arrayLength)
for (let i = 0; i < arrayLength; i++) {
this.#values[i] = Value.Create(schema)
}
}
// ---------------------------------------------------
// Indexer
// ---------------------------------------------------
/** Sets the value at the given index */
public set(index: number, item: Static<T>): void {
this.#assertIndexInBounds(index)
this.#assertItem(index, item)
this.#values[index] = item
}
// ---------------------------------------------------
// Array<T>
// ---------------------------------------------------
/** Iterator for values in this array */
public [Symbol.iterator](): IterableIterator<Static<T>> {
return this.#values[Symbol.iterator]() as IterableIterator<T>
}
/** Gets the value at the given index */
public at(index: number): Static<T> {
this.#assertIndexInBounds(index)
return this.#values[index] as T
}
/**
* Gets the length of the array. This is a number one higher than the highest index in the array.
*/
public get length(): number {
return this.#values.length
}
/**
* Returns a string representation of an array.
*/
public toString(): string {
return this.#values.toString()
}
/**
* Returns a string representation of an array. The elements are converted to string using their toLocaleString methods.
*/
public toLocaleString(): string {
return this.#values.toLocaleString()
}
/**
* Removes the last element from an array and returns it.
* If the array is empty, undefined is returned and the array is not modified.
*/
public pop(): Static<T> | undefined {
return this.#values.pop()
}
/**
* Appends new elements to the end of an array, and returns the new length of the array.
* @param items New elements to add to the array.
*/
public push(...items: Static<T>[]): number {
this.#assertItems(items)
return this.#values.push(...items)
}
/**
* Combines two or more arrays.
* This method returns a new array without modifying any existing arrays.
* @param items Additional arrays and/or items to add to the end of the array.
*/
public concat(...items: ConcatArray<Static<T>>[]): Static<T>[]
/**
* Combines two or more arrays.
* This method returns a new array without modifying any existing arrays.
* @param items Additional arrays and/or items to add to the end of the array.
*/
public concat(...items: (T | ConcatArray<Static<T>>)[]): Static<T>[] {
this.#assertItems(items)
return this.#values.concat(...items) as Static<T>[]
}
/**
* Adds all the elements of an array into a string, separated by the specified separator string.
* @param separator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
*/
public join(separator?: string): string {
return this.#values.join(separator)
}
/**
* Reverses the elements in an array in place.
* This method mutates the array and returns a reference to the same array.
*/
public reverse(): Static<T>[] {
return this.#values.reverse() as Static<T>[]
}
/**
* Removes the first element from an array and returns it.
* If the array is empty, undefined is returned and the array is not modified.
*/
public shift(): Static<T> | undefined {
return this.#values.shift() as Static<T> | undefined
}
/**
* Returns a copy of a section of an array.
* For both start and end, a negative index can be used to indicate an offset from the end of the array.
* For example, -2 refers to the second to last element of the array.
* @param start The beginning index of the specified portion of the array.
* If start is undefined, then the slice begins at index 0.
* @param end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'.
* If end is undefined, then the slice extends to the end of the array.
*/
public slice(start?: number, end?: number): Static<T>[] {
return this.#values.slice(start, end) as Static<T>[]
}
/**
* Sorts an array in place.
* This method mutates the array and returns a reference to the same array.
* @param compareFn Function used to determine the order of the elements. It is expected to return
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
* ```ts
* [11,2,22,1].sort((a, b) => a - b)
* ```
*/
public sort(compareFn?: (a: Static<T>, b: Static<T>) => number): this {
this.#values.sort(compareFn as any)
return this
}
/**
* Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements.
* @param start The zero-based location in the array from which to start removing elements.
* @param deleteCount The number of elements to remove.
* @returns An array containing the elements that were deleted.
*/
public splice(start: number, deleteCount?: number): Static<T>[]
/**
* Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements.
* @param start The zero-based location in the array from which to start removing elements.
* @param deleteCount The number of elements to remove.
* @param items Elements to insert into the array in place of the deleted elements.
* @returns An array containing the elements that were deleted.
*/
public splice(start: number, deleteCount: number, ...items: Static<T>[]): Static<T>[] {
this.#assertItems(items)
return this.#values.splice(start, deleteCount, items) as Static<T>[]
}
/**
* Inserts new elements at the start of an array, and returns the new length of the array.
* @param items Elements to insert at the start of the array.
*/
public unshift(...items: Static<T>[]): number {
this.#assertItems(items)
return this.#values.unshift(items)
}
/**
* Returns the index of the first occurrence of a value in an array, or -1 if it is not present.
* @param searchElement The value to locate in the array.
* @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0.
*/
public indexOf(searchElement: Static<T>, fromIndex?: number): number {
return this.#values.indexOf(searchElement, fromIndex)
}
/**
* Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present.
* @param searchElement The value to locate in the array.
* @param fromIndex The array index at which to begin searching backward. If fromIndex is omitted, the search starts at the last index in the array.
*/
public lastIndexOf(searchElement: Static<T>, fromIndex?: number): number {
return this.#values.lastIndexOf(searchElement, fromIndex)
}
/**
* Determines whether all the members of an array satisfy the specified test.
* @param predicate A function that accepts up to three arguments. The every method calls
* the predicate function for each element in the array until the predicate returns a value
* which is coercible to the Boolean value false, or until the end of the array.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
public every<S extends T>(predicate: (value: Static<T>, index: number, array: Static<T>[]) => value is Static<S>, thisArg?: any): this is Static<S>[]
/**
* Determines whether all the members of an array satisfy the specified test.
* @param predicate A function that accepts up to three arguments. The every method calls
* the predicate function for each element in the array until the predicate returns a value
* which is coercible to the Boolean value false, or until the end of the array.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
public every(predicate: (value: Static<T>, index: number, array: Static<T>[]) => unknown, thisArg?: any): boolean {
return this.#values.every(predicate as any, thisArg)
}
/**
* Determines whether the specified callback function returns true for any element of an array.
* @param predicate A function that accepts up to three arguments. The some method calls
* the predicate function for each element in the array until the predicate returns a value
* which is coercible to the Boolean value true, or until the end of the array.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
public some(predicate: (value: Static<T>, index: number, array: T[]) => unknown, thisArg?: any): boolean {
return this.#values.some(predicate as any, thisArg)
}
/**
* Performs the specified action for each element in an array.
* @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
public forEach(callbackfn: (value: Static<T>, index: number, array: Static<T>[]) => void, thisArg?: any): void {
return this.#values.forEach(callbackfn as any, thisArg)
}
/**
* Calls a defined callback function on each element of an array, and returns an array that contains the results.
* @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
public map<U>(callbackfn: (value: Static<T>, index: number, array: Static<T>[]) => U, thisArg?: any): U[] {
return this.#values.map(callbackfn as any, thisArg)
}
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
public filter<S extends T>(predicate: (value: Static<T>, index: number, array: Static<T>[]) => value is Static<S>, thisArg?: any): Static<S>[]
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
public filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[]
public filter(predicate: any, thisArg: any): any {
return this.#values.filter(predicate as any, thisArg)
}
/**
* Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
public reduce(callbackfn: (previousValue: Static<T>, currentValue: Static<T>, currentIndex: number, array: T[]) => Static<T>): Static<T>
public reduce(callbackfn: (previousValue: Static<T>, currentValue: Static<T>, currentIndex: number, array: T[]) => Static<T>, initialValue: Static<T>): Static<T>
/**
* Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
public reduce<U>(callbackfn: (previousValue: U, currentValue: Static<T>, currentIndex: number, array: Static<T>[]) => U, initialValue: U): U
public reduce(callbackfn: any, initialValue?: any): any {
return this.#values.reduce(callbackfn, initialValue)
}
/**
* Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
public reduceRight(callbackfn: (previousValue: Static<T>, currentValue: Static<T>, currentIndex: number, array: Static<T>[]) => Static<T>): Static<T>
public reduceRight(callbackfn: (previousValue: Static<T>, currentValue: Static<T>, currentIndex: number, array: Static<T>[]) => Static<T>, initialValue: T): Static<T>
/**
* Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
public reduceRight<U>(callbackfn: (previousValue: U, currentValue: Static<T>, currentIndex: number, array: Static<T>[]) => U, initialValue: U): U
public reduceRight(callbackfn: any, initialValue?: any): any {
return this.#values.reduceRight(callbackfn, initialValue)
}
// ---------------------------------------------------
// Assertions
// ---------------------------------------------------
#formatError(errors: ValueError[]) {
return errors.map((error) => `${error.message} ${error.path}`).join('. ')
}
/** Asserts the given values */
#assertIndexInBounds(index: number) {
if (index >= 0 && index < this.#values.length) return
throw new TypeArrayError(`Index ${index} is outside the bounds of this Array.`)
}
/** Asserts the given values */
#assertItem(index: number, item: unknown): asserts item is Static<T> {
if (this.#typeCheck.Check(item)) return
const message = this.#formatError([...this.#typeCheck.Errors(item)])
throw new TypeArrayError(`Item at Index ${index} is invalid. ${message}`)
}
/** Asserts the given values */
#assertItems(items: unknown[]): asserts items is Static<T>[] {
for (let i = 0; i < items.length; i++) {
this.#assertItem(i, items[i])
}
}
/** Creates a typed array from an existing array */
public static from<T extends TSchema>(schema: T, iterable: IterableIterator<Static<T>> | Array<Static<T>>): TypeArray<T> {
if (globalThis.Array.isArray(iterable)) {
const array = new TypeArray(schema, iterable.length)
for (let i = 0; i < iterable.length; i++) {
array.set(i, iterable[i])
}
return array
}
const array = new TypeArray(schema)
for (const value of iterable) {
array.push(value)
}
return array
}
}

View File

@@ -0,0 +1,31 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/collections
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
export * from './array'
export * from './map'
export * from './set'

156
example/collections/map.ts Normal file
View File

@@ -0,0 +1,156 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/collections
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import { TypeCheck, TypeCompiler, ValueError } from '@sinclair/typebox/compiler'
import { TSchema, Static, TypeBoxError } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
// ----------------------------------------------------------------
// TypeMapKeyError
// ----------------------------------------------------------------
export class TypeMapKeyError extends TypeBoxError {
constructor(message: string) {
super(`${message} for key`)
}
}
export class TypeMapValueError extends TypeBoxError {
constructor(key: unknown, message: string) {
super(`${message} for key ${JSON.stringify(key)}`)
}
}
// ----------------------------------------------------------------
// TypeMap<K, V>
// ----------------------------------------------------------------
// prettier-ignore
type TypeMapEntries<K extends TSchema, V extends TSchema> =
| Iterable<[Static<K>, Static<V>]>
| Array<[Static<K>, Static<V>]>
/** Runtime type checked Map collection */
export class TypeMap<K extends TSchema, V extends TSchema> {
readonly #keycheck: TypeCheck<K>
readonly #valuecheck: TypeCheck<V>
readonly #keys: Map<bigint, Static<K>>
readonly #values: Map<bigint, Static<V>>
/** Constructs a new HashMap of the given key and value types. */
constructor(key: K, value: V, entries: TypeMapEntries<K, V> = []) {
this.#keycheck = TypeCompiler.Compile(key)
this.#valuecheck = TypeCompiler.Compile(value)
this.#keys = new Map<bigint, Static<K>>()
this.#values = new Map<bigint, Static<V>>()
for (const [key, value] of entries) {
this.set(key, value)
}
}
/** Iterator for this TypeMap */
public *[Symbol.iterator](): IterableIterator<[Static<K>, Static<V>]> {
for (const [key, value] of this.#values) {
yield [this.#keys.get(key)!, value]
}
}
/** Iterator for the keys in this TypeMap */
public *keys(): IterableIterator<Static<K>> {
yield* this.#keys.values()
}
/** Iterator for the values in this TypeMap */
public *values(): IterableIterator<Static<V>> {
yield* this.#values.values()
}
/** Clears all entries in this map */
public clear(): void {
this.#values.clear()
this.#keys.clear()
}
/** Executes a provided function once per each key/value pair in the Map, in insertion order. */
public forEach(callbackfn: (value: Static<V>, key: Static<K>, map: TypeMap<K, V>) => void, thisArg?: any): void {
this.#values.forEach((value, key) => callbackfn(value, this.#keys.get(key)!, this))
}
/** @returns the number of elements in the TypeMap. */
public get size(): number {
return this.#values.size
}
/**
* @returns boolean indicating whether an element with the specified key exists or not.
*/
public has(key: Static<K>): boolean {
this.#assertKey(key)
return this.#values.has(this.#encodeKey(key))
}
/**
* Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.
* @returns Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
*/
public get(key: Static<K>): Static<V> | undefined {
this.#assertKey(key)
return this.#values.get(this.#encodeKey(key))!
}
/**
* Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
*/
public set(key: Static<K>, value: Static<V>) {
this.#assertKey(key)
this.#assertValue(key, value)
const encodedKey = this.#encodeKey(key)
this.#keys.set(encodedKey, key)
this.#values.set(encodedKey, value)
}
/**
* @returns true if an element in the Map existed and has been removed, or false if the element does not exist.
*/
public delete(key: Static<K>): boolean {
this.#assertKey(key)
const encodedKey = this.#encodeKey(key)
this.#keys.delete(encodedKey)
return this.#values.delete(encodedKey)
}
// ---------------------------------------------------
// Encoder
// ---------------------------------------------------
/** Encodes the key as a 64bit numeric */
#encodeKey(key: Static<K>) {
return Value.Hash(key)
}
// ---------------------------------------------------
// Assertions
// ---------------------------------------------------
#formatError(errors: ValueError[]) {
return errors
.map((error) => `${error.message} ${error.path}`)
.join('. ')
.trim()
}
/** Asserts the key matches the key schema */
#assertKey(key: unknown): asserts key is Static<K> {
if (this.#keycheck.Check(key)) return
throw new TypeMapKeyError(this.#formatError([...this.#keycheck.Errors(key)]))
}
/** Asserts the key matches the value schema */
#assertValue(key: Static<K>, value: unknown): asserts value is Static<V> {
if (this.#valuecheck.Check(value)) return
throw new TypeMapValueError(key, this.#formatError([...this.#valuecheck.Errors(value)]))
}
}

View File

@@ -0,0 +1,3 @@
# Collections
This example implements runtime type safe generic `Array`, `Map` and `Set` collection types using TypeBox types as the generic type arguments.

109
example/collections/set.ts Normal file
View File

@@ -0,0 +1,109 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/collections
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import { TypeCheck, TypeCompiler, ValueError } from '@sinclair/typebox/compiler'
import { TSchema, Static, TypeBoxError } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
// ----------------------------------------------------------------
// Errors
// ----------------------------------------------------------------
export class TypeSetError extends TypeBoxError {
constructor(message: string) {
super(`${message}`)
}
}
// ----------------------------------------------------------------
// TypeSet
// ----------------------------------------------------------------
/** Runtime type checked Set collection */
export class TypeSet<T extends TSchema> {
readonly #valuecheck: TypeCheck<T>
readonly values: Map<bigint, Static<T>>
constructor(schema: T, iterable: Array<T> | Iterable<T> = []) {
this.#valuecheck = TypeCompiler.Compile(schema)
this.values = new Map<bigint, Static<T>>()
for (const value of iterable) {
this.add(value)
}
}
/** Adds a value to this set */
public add(value: Static<T>): this {
this.#assertValue(value)
this.values.set(this.#encodeKey(value), value)
return this
}
/** Clears the values in this set */
public clear(): void {
this.values.clear()
}
/**
* Removes a specified value from the Set.
* @returns Returns true if an element in the Set existed and has been removed, or false if the element does not exist.
*/
public delete(value: Static<T>): boolean {
return this.values.delete(this.#encodeKey(value))
}
/** Executes a provided function once per each value in the Set object, in insertion order. */
public forEach(callbackfn: (value: Static<T>, value2: Static<T>, set: TypeSet<T>) => void, thisArg?: any): void {
this.values.forEach((value, value2) => callbackfn(value, value2, this), thisArg)
}
/**
* @returns a boolean indicating whether an element with the specified value exists in the Set or not.
*/
public has(value: Static<T>): boolean {
return this.values.has(this.#encodeKey(value))
}
/**
* @returns the number of (unique) elements in Set.
*/
public get size(): number {
return this.values.size
}
// ---------------------------------------------------
// Encoder
// ---------------------------------------------------
#encodeKey(value: Static<T>) {
return Value.Hash(value)
}
// ---------------------------------------------------
// Assertions
// ---------------------------------------------------
/** Formats errors */
#formatError(errors: ValueError[]) {
return errors
.map((error) => `${error.message} ${error.path}`)
.join('. ')
.trim()
}
/** Asserts the key matches the value schema */
#assertValue(value: unknown): asserts value is Static<T> {
if (this.#valuecheck.Check(value)) return
throw new TypeSetError(this.#formatError([...this.#valuecheck.Errors(value)]))
}
}

View File

@@ -0,0 +1,39 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
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.
---------------------------------------------------------------------------*/
import { IsDate } from './date'
import { IsTime } from './time'
const DATE_TIME_SEPARATOR = /t|\s/i
/**
* `[ajv-formats]` ISO8601 DateTime
* @example `2020-12-12T20:20:40+00:00`
*/
export function IsDateTime(value: string, strictTimeZone?: boolean): boolean {
const dateTime: string[] = value.split(DATE_TIME_SEPARATOR)
return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1], strictTimeZone)
}

44
example/formats/date.ts Normal file
View File

@@ -0,0 +1,44 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
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.
---------------------------------------------------------------------------*/
const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
function IsLeapYear(year: number): boolean {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
}
/**
* `[ajv-formats]` ISO8601 Date component
* @example `2020-12-12`
*/
export function IsDate(value: string): boolean {
const matches: string[] | null = DATE.exec(value)
if (!matches) return false
const year: number = +matches[1]
const month: number = +matches[2]
const day: number = +matches[3]
return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && IsLeapYear(year) ? 29 : DAYS[month])
}

35
example/formats/email.ts Normal file
View File

@@ -0,0 +1,35 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
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.
---------------------------------------------------------------------------*/
const Email = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i
/**
* `[ajv-formats]` Internet Email Address [RFC 5321, section 4.1.2.](http://tools.ietf.org/html/rfc5321#section-4.1.2)
* @example `user@domain.com`
*/
export function IsEmail(value: string): boolean {
return Email.test(value)
}

36
example/formats/index.ts Normal file
View File

@@ -0,0 +1,36 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
export * from './date-time'
export * from './date'
export * from './email'
export * from './ipv4'
export * from './ipv6'
export * from './time'
export * from './url'
export * from './uuid'

35
example/formats/ipv4.ts Normal file
View File

@@ -0,0 +1,35 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
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.
---------------------------------------------------------------------------*/
const IPv4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/
/**
* `[ajv-formats]` IPv4 address according to dotted-quad ABNF syntax as defined in [RFC 2673, section 3.2](http://tools.ietf.org/html/rfc2673#section-3.2)
* @example `192.168.0.1`
*/
export function IsIPv4(value: string): boolean {
return IPv4.test(value)
}

36
example/formats/ipv6.ts Normal file
View File

@@ -0,0 +1,36 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
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.
---------------------------------------------------------------------------*/
const IPv6 =
/^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i
/**
* `[ajv-formats]` IPv6 address as defined in [RFC 2373, section 2.2](http://tools.ietf.org/html/rfc2373#section-2.2).
* @example `2001:0db8:85a3:0000:0000:8a2e:0370:7334`
*/
export function IsIPv6(value: string): boolean {
return IPv6.test(value)
}

48
example/formats/time.ts Normal file
View File

@@ -0,0 +1,48 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
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.
---------------------------------------------------------------------------*/
const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i
/**
* `[ajv-formats]` ISO8601 Time component
* @example `20:20:39+00:00`
*/
export function IsTime(value: string, strictTimeZone?: boolean): boolean {
const matches: string[] | null = TIME.exec(value)
if (!matches) return false
const hr: number = +matches[1]
const min: number = +matches[2]
const sec: number = +matches[3]
const tz: string | undefined = matches[4]
const tzSign: number = matches[5] === '-' ? -1 : 1
const tzH: number = +(matches[6] || 0)
const tzM: number = +(matches[7] || 0)
if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false
if (hr <= 23 && min <= 59 && sec < 60) return true
const utcMin = min - tzM * tzSign
const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0)
return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61
}

36
example/formats/url.ts Normal file
View File

@@ -0,0 +1,36 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
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.
---------------------------------------------------------------------------*/
const Url =
/^(?:https?|wss?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu
/**
* `[ajv-formats:deprecated]` A uniform resource locator as defined in [RFC 1738](https://www.rfc-editor.org/rfc/rfc1738)
* @example `http://domain.com`
*/
export function IsUrl(value: string) {
return Url.test(value)
}

35
example/formats/uuid.ts Normal file
View File

@@ -0,0 +1,35 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
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.
---------------------------------------------------------------------------*/
const Uuid = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i
/**
* `[ajv-formats]` A Universally Unique Identifier as defined by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122).
* @example `9aa8a673-8590-4db2-9830-01755844f7c1`
*/
export function IsUuid(value: string): boolean {
return Uuid.test(value)
}

49
example/index.ts Normal file
View File

@@ -0,0 +1,49 @@
import { TypeSystem } from '@sinclair/typebox/system'
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { Value, ValuePointer } from '@sinclair/typebox/value'
import { Type, TypeGuard, Kind, Static, TSchema } from '@sinclair/typebox'
import { Syntax } from '@sinclair/typebox/syntax'
// -----------------------------------------------------------
// Create: Type
// -----------------------------------------------------------
const T = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
})
type T = Static<typeof T>
console.log(T)
// -----------------------------------------------------------
// Syntax: Type
// -----------------------------------------------------------
const S = Syntax({ T }, `{ x: T, y: T, z: T }`)
type S = Static<typeof S>
// -----------------------------------------------------------
// Create: Value
// -----------------------------------------------------------
const V = Value.Create(T)
console.log(V)
// -----------------------------------------------------------
// Compile: Type
// -----------------------------------------------------------
const C = TypeCompiler.Compile(T)
console.log(C.Code())
// -----------------------------------------------------------
// Check: Value
// -----------------------------------------------------------
console.log(C.Check(V))

View File

@@ -0,0 +1,96 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import { Static, Kind, TSchema, TObject, SchemaOptions, CreateType, TLiteral, TypeRegistry, ValueGuard, KindGuard, TUnion } from '@sinclair/typebox'
import { GetErrorFunction, SetErrorFunction } from 'src/errors/function'
import { Value } from '@sinclair/typebox/value'
// ------------------------------------------------------------------
// DiscriminatedUnionError
// ------------------------------------------------------------------
const errorFunction = GetErrorFunction()
// prettier-ignore
SetErrorFunction((parameter) => {
if (parameter.schema[Kind] !== 'DiscriminatedUnion') {
return errorFunction(parameter)
}
const union = parameter.schema as TDiscriminatedUnion
// Try generate error when value matches known discriminator literal
if (ValueGuard.IsObject(parameter.value) && union.discriminator in parameter.value) {
const variant = parameter.schema.anyOf.find((variant: TSchema) => union.discriminator in variant.properties
&& (variant.properties[union.discriminator] as TLiteral).const ===
(parameter.value as Record<PropertyKey, unknown>)[union.discriminator])
if (KindGuard.IsSchema(variant)) {
const literal = variant.properties[union.discriminator]
return `Invalid value for DiscriminatedUnion variant '${literal.const}'`
}
}
// Return generic error containing possible discriminator types.
const options = union.anyOf.map(object => object.properties[union.discriminator].const) as string[]
return `Expected value of ${options.map(option => `'${option}'`).join(', ')} for DiscriminatedUnion`
})
// ------------------------------------------------------------------
// TDiscriminatedUnionObject
//
// Constructs a base TObject type requiring 1 discriminator property
// ------------------------------------------------------------------
// prettier-ignore
type TDiscriminatedUnionProperties<Discriminator extends string> = {
[_ in Discriminator]: TLiteral
}
// prettier-ignore
type TDiscriminatedUnionObject<Discriminator extends string> = TObject<TDiscriminatedUnionProperties<Discriminator>>
// ------------------------------------------------------------------
// DiscriminatedUnion
// ------------------------------------------------------------------
// prettier-ignore
TypeRegistry.Set('DiscriminatedUnion', (schema: TDiscriminatedUnion, value) => {
return schema.anyOf.some(variant => Value.Check(variant, [], value))
})
// prettier-ignore
export interface TDiscriminatedUnion<Discriminator extends string = string, Types extends TObject[] = TObject[]> extends TSchema {
[Kind]: 'DiscriminatedUnion'
static: Static<TUnion<Types>>
discriminator: Discriminator
anyOf: Types
}
/** Creates a DiscriminatedUnion. */
// prettier-ignore
export function DiscriminatedUnion<Discriminator extends string, Types extends TDiscriminatedUnionObject<Discriminator>[]>(
discriminator: Discriminator, types: [...Types], options?: SchemaOptions
): TDiscriminatedUnion<Discriminator, Types> {
return CreateType({ [Kind]: 'DiscriminatedUnion', anyOf: types, discriminator }, options) as never
}

View File

@@ -0,0 +1,251 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import * as Type from '@sinclair/typebox'
// ------------------------------------------------------------------
// Schematics
// ------------------------------------------------------------------
const IsExact = (value: unknown, expect: unknown) => value === expect
const IsSValue = (value: unknown): value is SValue => Type.ValueGuard.IsString(value) || Type.ValueGuard.IsNumber(value) || Type.ValueGuard.IsBoolean(value)
const IsSEnum = (value: unknown): value is SEnum => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.enum) && value.enum.every((value) => IsSValue(value))
const IsSAllOf = (value: unknown): value is SAllOf => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.allOf)
const IsSAnyOf = (value: unknown): value is SAnyOf => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.anyOf)
const IsSOneOf = (value: unknown): value is SOneOf => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.oneOf)
const IsSTuple = (value: unknown): value is STuple => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'array') && Type.ValueGuard.IsArray(value.items)
const IsSArray = (value: unknown): value is SArray => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'array') && !Type.ValueGuard.IsArray(value.items) && Type.ValueGuard.IsObject(value.items)
const IsSConst = (value: unknown): value is SConst => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsObject(value['const'])
const IsSString = (value: unknown): value is SString => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'string')
const IsSRef = (value: unknown): value is SRef => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsString(value.$ref)
const IsSNumber = (value: unknown): value is SNumber => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'number')
const IsSInteger = (value: unknown): value is SInteger => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'integer')
const IsSBoolean = (value: unknown): value is SBoolean => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'boolean')
const IsSNull = (value: unknown): value is SBoolean => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'null')
const IsSProperties = (value: unknown): value is SProperties => Type.ValueGuard.IsObject(value)
// prettier-ignore
const IsSObject = (value: unknown): value is SObject => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'object') && IsSProperties(value.properties) && (value.required === undefined || Type.ValueGuard.IsArray(value.required) && value.required.every((value: unknown) => Type.ValueGuard.IsString(value)))
type SValue = string | number | boolean
type SEnum = Readonly<{ enum: readonly SValue[] }>
type SAllOf = Readonly<{ allOf: readonly unknown[] }>
type SAnyOf = Readonly<{ anyOf: readonly unknown[] }>
type SOneOf = Readonly<{ oneOf: readonly unknown[] }>
type SProperties = Record<PropertyKey, unknown>
type SObject = Readonly<{ type: 'object'; properties: SProperties; required?: readonly string[] }>
type STuple = Readonly<{ type: 'array'; items: readonly unknown[] }>
type SArray = Readonly<{ type: 'array'; items: unknown }>
type SConst = Readonly<{ const: SValue }>
type SRef = Readonly<{ $ref: string }>
type SString = Readonly<{ type: 'string' }>
type SNumber = Readonly<{ type: 'number' }>
type SInteger = Readonly<{ type: 'integer' }>
type SBoolean = Readonly<{ type: 'boolean' }>
type SNull = Readonly<{ type: 'null' }>
// ------------------------------------------------------------------
// FromRest
// ------------------------------------------------------------------
// prettier-ignore
type TFromRest<T extends readonly unknown[], Acc extends Type.TSchema[] = []> = (
T extends readonly [infer L extends unknown, ...infer R extends unknown[]]
? TFromSchema<L> extends infer S extends Type.TSchema
? TFromRest<R, [...Acc, S]>
: TFromRest<R, [...Acc]>
: Acc
)
function FromRest<T extends readonly unknown[]>(T: T): TFromRest<T> {
return T.map((L) => FromSchema(L)) as never
}
// ------------------------------------------------------------------
// FromEnumRest
// ------------------------------------------------------------------
// prettier-ignore
type TFromEnumRest<T extends readonly SValue[], Acc extends Type.TSchema[] = []> = (
T extends readonly [infer L extends SValue, ...infer R extends SValue[]]
? TFromEnumRest<R, [...Acc, Type.TLiteral<L>]>
: Acc
)
function FromEnumRest<T extends readonly SValue[]>(T: T): TFromEnumRest<T> {
return T.map((L) => Type.Literal(L)) as never
}
// ------------------------------------------------------------------
// AllOf
// ------------------------------------------------------------------
// prettier-ignore
type TFromAllOf<T extends SAllOf> = (
TFromRest<T['allOf']> extends infer Rest extends Type.TSchema[]
? Type.TIntersectEvaluated<Rest>
: Type.TNever
)
function FromAllOf<T extends SAllOf>(T: T): TFromAllOf<T> {
return Type.IntersectEvaluated(FromRest(T.allOf), T)
}
// ------------------------------------------------------------------
// AnyOf
// ------------------------------------------------------------------
// prettier-ignore
type TFromAnyOf<T extends SAnyOf> = (
TFromRest<T['anyOf']> extends infer Rest extends Type.TSchema[]
? Type.TUnionEvaluated<Rest>
: Type.TNever
)
function FromAnyOf<T extends SAnyOf>(T: T): TFromAnyOf<T> {
return Type.UnionEvaluated(FromRest(T.anyOf), T)
}
// ------------------------------------------------------------------
// OneOf
// ------------------------------------------------------------------
// prettier-ignore
type TFromOneOf<T extends SOneOf> = (
TFromRest<T['oneOf']> extends infer Rest extends Type.TSchema[]
? Type.TUnionEvaluated<Rest>
: Type.TNever
)
function FromOneOf<T extends SOneOf>(T: T): TFromOneOf<T> {
return Type.UnionEvaluated(FromRest(T.oneOf), T)
}
// ------------------------------------------------------------------
// Enum
// ------------------------------------------------------------------
// prettier-ignore
type TFromEnum<T extends SEnum> = (
TFromEnumRest<T['enum']> extends infer Elements extends Type.TSchema[]
? Type.TUnionEvaluated<Elements>
: Type.TNever
)
function FromEnum<T extends SEnum>(T: T): TFromEnum<T> {
return Type.UnionEvaluated(FromEnumRest(T.enum))
}
// ------------------------------------------------------------------
// Tuple
// ------------------------------------------------------------------
// prettier-ignore
type TFromTuple<T extends STuple> = (
TFromRest<T['items']> extends infer Elements extends Type.TSchema[]
? Type.TTuple<Elements>
: Type.TTuple<[]>
)
// prettier-ignore
function FromTuple<T extends STuple>(T: T): TFromTuple<T> {
return Type.Tuple(FromRest(T.items), T) as never
}
// ------------------------------------------------------------------
// Array
// ------------------------------------------------------------------
// prettier-ignore
type TFromArray<T extends SArray> = (
TFromSchema<T['items']> extends infer Items extends Type.TSchema
? Type.TArray<Items>
: Type.TArray<Type.TUnknown>
)
// prettier-ignore
function FromArray<T extends SArray>(T: T): TFromArray<T> {
return Type.Array(FromSchema(T.items), T) as never
}
// ------------------------------------------------------------------
// Const
// ------------------------------------------------------------------
// prettier-ignore
type TFromConst<T extends SConst> = (
Type.Ensure<Type.TLiteral<T['const']>>
)
function FromConst<T extends SConst>(T: T) {
return Type.Literal(T.const, T)
}
// ------------------------------------------------------------------
// Ref
// ------------------------------------------------------------------
// prettier-ignore
type TFromRef<T extends SRef> = (
Type.Ensure<Type.TRef<T['$ref']>>
)
function FromRef<T extends SRef>(T: T) {
return Type.Ref(T['$ref'])
}
// ------------------------------------------------------------------
// Object
// ------------------------------------------------------------------
type TFromPropertiesIsOptional<K extends PropertyKey, R extends string | unknown> = unknown extends R ? true : K extends R ? false : true
// prettier-ignore
type TFromProperties<T extends SProperties, R extends string | unknown> = Type.Evaluate<{
-readonly [K in keyof T]: TFromPropertiesIsOptional<K, R> extends true
? Type.TOptional<TFromSchema<T[K]>>
: TFromSchema<T[K]>
}>
// prettier-ignore
type TFromObject<T extends SObject> = (
TFromProperties<T['properties'], Exclude<T['required'], undefined>[number]> extends infer Properties extends Type.TProperties
? Type.TObject<Properties>
: Type.TObject<{}>
)
function FromObject<T extends SObject>(T: T): TFromObject<T> {
const properties = globalThis.Object.getOwnPropertyNames(T.properties).reduce((Acc, K) => {
return { ...Acc, [K]: T.required && T.required.includes(K) ? FromSchema(T.properties[K]) : Type.Optional(FromSchema(T.properties[K])) }
}, {} as Type.TProperties)
return Type.Object(properties, T) as never
}
// ------------------------------------------------------------------
// FromSchema
// ------------------------------------------------------------------
// prettier-ignore
export type TFromSchema<T> = (
T extends SAllOf ? TFromAllOf<T> :
T extends SAnyOf ? TFromAnyOf<T> :
T extends SOneOf ? TFromOneOf<T> :
T extends SEnum ? TFromEnum<T> :
T extends SObject ? TFromObject<T> :
T extends STuple ? TFromTuple<T> :
T extends SArray ? TFromArray<T> :
T extends SConst ? TFromConst<T> :
T extends SRef ? TFromRef<T> :
T extends SString ? Type.TString :
T extends SNumber ? Type.TNumber :
T extends SInteger ? Type.TInteger :
T extends SBoolean ? Type.TBoolean :
T extends SNull ? Type.TNull :
Type.TUnknown
)
/** Parses a TypeBox type from raw JsonSchema */
export function FromSchema<T>(T: T): TFromSchema<T> {
// prettier-ignore
return (
IsSAllOf(T) ? FromAllOf(T) :
IsSAnyOf(T) ? FromAnyOf(T) :
IsSOneOf(T) ? FromOneOf(T) :
IsSEnum(T) ? FromEnum(T) :
IsSObject(T) ? FromObject(T) :
IsSTuple(T) ? FromTuple(T) :
IsSArray(T) ? FromArray(T) :
IsSConst(T) ? FromConst(T) :
IsSRef(T) ? FromRef(T) :
IsSString(T) ? Type.String(T) :
IsSNumber(T) ? Type.Number(T) :
IsSInteger(T) ? Type.Integer(T) :
IsSBoolean(T) ? Type.Boolean(T) :
IsSNull(T) ? Type.Null(T) :
Type.Unknown(T || {})
) as never
}

View File

@@ -0,0 +1,35 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
export * from './discriminated-union'
export * from './from-schema'
export * from './options'
export * from './partial-deep'
export * from './recursive-map'
export * from './union-enum'
export * from './union-oneof'

View File

@@ -0,0 +1,40 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import { TSchema, CloneType } from '@sinclair/typebox'
// prettier-ignore
export type TOptions<Type extends TSchema, Options extends Record<PropertyKey, unknown>> = (
Type & Options
)
/** `[Prototype]` Augments a schema with additional generics aware properties */
// prettier-ignore
export function Options<Type extends TSchema, Options extends Record<PropertyKey, unknown>>(type: Type, options: Options): TOptions<Type, Options> {
return CloneType(type, options) as never
}

View File

@@ -0,0 +1,68 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import { TypeGuard, Type, TSchema, TIntersect, TUnion, TObject, TPartial, TProperties, Evaluate } from '@sinclair/typebox'
// -------------------------------------------------------------------------------------
// TPartialDeepProperties
// -------------------------------------------------------------------------------------
export type TPartialDeepProperties<T extends TProperties> = {
[K in keyof T]: TPartialDeep<T[K]>
}
function PartialDeepProperties<T extends TProperties>(properties: T): TPartialDeepProperties<T> {
return Object.getOwnPropertyNames(properties).reduce((acc, key) => {
return {...acc, [key]: PartialDeep(properties[key])}
}, {}) as never
}
// -------------------------------------------------------------------------------------
// TPartialDeepRest
// -------------------------------------------------------------------------------------
export type TPartialDeepRest<T extends TSchema[], Acc extends TSchema[] = []> = (
T extends [infer L extends TSchema, ...infer R extends TSchema[]]
? TPartialDeepRest<R, [...Acc, TPartialDeep<L>]>
: Acc
)
function PartialDeepRest<T extends TSchema[]>(rest: [...T]): TPartialDeepRest<T> {
return rest.map(schema => PartialDeep(schema)) as never
}
// -------------------------------------------------------------------------------------
// TPartialDeep
// -------------------------------------------------------------------------------------
export type TPartialDeep<T extends TSchema> =
T extends TIntersect<infer S> ? TIntersect<TPartialDeepRest<S>> :
T extends TUnion<infer S> ? TUnion<TPartialDeepRest<S>> :
T extends TObject<infer S> ? TPartial<TObject<Evaluate<TPartialDeepProperties<S>>>> :
T
export function PartialDeep<T extends TSchema>(schema: T): TPartialDeep<T> {
return (
TypeGuard.IsIntersect(schema) ? Type.Intersect(PartialDeepRest(schema.allOf)) :
TypeGuard.IsUnion(schema) ? Type.Union(PartialDeepRest(schema.anyOf)) :
TypeGuard.IsObject(schema) ? Type.Partial(Type.Object(PartialDeepProperties(schema.properties))) :
schema
) as never
}

View File

@@ -0,0 +1,119 @@
# TypeBox Prototypes
TypeBox prototypes are a set of types that are either under consideration for inclusion into the library, or have been requested by users but cannot be added to the library either due to complexity, using schematics that fall outside the supported TypeBox or should be expressed by users via advanced type composition.
## PartialDeep
Maps the given schema as deep partial, making all properties and sub properties optional. This type is asked for on occation, but as there is no TypeScript equivalent and because this type is typically handled through end user type mapping, this type is left out of TypeBox.
```typescript
import { PartialDeep } from './prototypes'
const T = Type.Object({
x: Type.Object({
x: Type.Number(),
y: Type.Number()
}),
y: Type.Object({
x: Type.Number(),
y: Type.Number()
})
})
const P = PartialDeep(T)
type P = Static<typeof P> // type P = {
// x?: {
// x?: number,
// y?: number
// },
// y?: {
// x?: number,
// y?: number
// },
// }
```
## UnionEnum
Creates an `enum` union string schema representation. This type is often requested by OpenAPI users, particularily for documentation presentation. As TypeBox standardizes on `anyOf` for all unions, this type is generally at odds with TypeBox's internal representation. Some considerations for internally remapping this type into a `anyOf` through composition have been considered (and would be feasible), but as TypeScript doesn't have multiple representations for unions, neither should TypeBox, making this type an unlikely candidate.
```typescript
import { UnionEnum } from './prototypes'
const T = UnionEnum(['A', 'B', 'C']) // const T = {
// enum: ['A', 'B', 'C']
// }
type T = Static<typeof T> // type T = 'A' | 'B' | 'C'
```
## UnionOneOf
Creates a `oneOf` union representation. This type is often requested by users looking for discriminated union support (which is not formally supported by JSON Schema). TypeBox omits this type as `oneOf` has the potential to create illogical schematics where values match more than one sub schema (making type inference extremely difficult). TypeBox preferences users explicitly narrowing on a overlapping union post type check, making `anyOf` the ideal representation, leaving the `oneOf` type an unlikely candidate for inclusion in the library.
```typescript
import { UnionOneOf } from './prototypes'
const T = UnionOneOf([ // const T = {
Type.Literal('A'), // oneOf: [
Type.Literal('B'), // { const: 'A' },
Type.Literal('C') // { const: 'B' },
]) // { const: 'C' },
// ]
// }
type T = Static<typeof T> // type T = 'A' | 'B' | 'C'
```
## Options
By default, TypeBox does not represent arbituary options as generics aware properties. However, there are cases where having options observable to the type system can be useful, for example conditionally mapping schematics based on custom metadata. The Options function makes user defined options generics aware.
```typescript
import { Options } from './prototypes'
const A = Options(Type.String(), { foo: 1 }) // Options<TString, { foo: number }>
type A = typeof A extends { foo: number } ? true : false // true: foo property is observable to the type system
```
## Recursive Map
The Recursive Map type enables deep structural remapping of a type and it's internal constituents. This type accepts a TSchema type and a mapping type function (expressed via HKT). The HKT is applied when traversing the type and it's interior. The mapping HKT can apply conditional tests to each visited type to remap into a new form. The following augments a schematic via Options, and conditionally remaps any schema with an default annotation to make it optional.
```typescript
import { Type, TOptional, Static, TSchema } from '@sinclair/typebox'
import { TRecursiveMap, TMappingType, Options } from './prototypes'
// ------------------------------------------------------------------
// StaticDefault
// ------------------------------------------------------------------
export interface StaticDefaultMapping extends TMappingType {
output: (
this['input'] extends TSchema // if input schematic contains an default
? this['input'] extends { default: unknown } // annotation, remap it to be optional,
? TOptional<this['input']> // otherwise just return the schema as is.
: this['input']
: this['input']
)
}
export type StaticDefault<Type extends TSchema> = (
Static<TRecursiveMap<Type, StaticDefaultMapping>>
)
// ------------------------------------------------------------------
// Usage
// ------------------------------------------------------------------
const T = Type.Object({
x: Options(Type.String(), { default: 'hello' }),
y: Type.String()
})
type T = StaticDefault<typeof T> // { x?: string, y: string }
type S = Static<typeof T> // { x: string, y: string }

View File

@@ -0,0 +1,153 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import * as Types from '@sinclair/typebox'
// ------------------------------------------------------------------
// Mapping: Functions and Type
// ------------------------------------------------------------------
export type TMappingFunction = (schema: Types.TSchema) => Types.TSchema
export interface TMappingType {
input: unknown
output: unknown
}
// ------------------------------------------------------------------
// Record Parameters
// ------------------------------------------------------------------
function GetRecordPattern(record: Types.TRecord): string {
return globalThis.Object.getOwnPropertyNames(record.patternProperties)[0]
}
function GetRecordKey(record: Types.TRecord): Types.TSchema {
const pattern = GetRecordPattern(record)
return (
pattern === Types.PatternStringExact ? Types.String() :
pattern === Types.PatternNumberExact ? Types.Number() :
pattern === Types.PatternBooleanExact ? Types.Boolean() :
Types.String({ pattern })
)
}
function GetRecordValue(record: Types.TRecord): Types.TSchema {
return record.patternProperties[GetRecordPattern(record)]
}
// ------------------------------------------------------------------
// Traversal
// ------------------------------------------------------------------
// prettier-ignore
type TApply<Type extends Types.TSchema, Func extends TMappingType,
Mapped = (Func & { input: Type })['output'],
Result = Mapped extends Types.TSchema ? Mapped : never
> = Result
// prettier-ignore
type TFromProperties<Properties extends Types.TProperties, Func extends TMappingType, Result extends Types.TProperties = {
[Key in keyof Properties]: TRecursiveMap<Properties[Key], Func>
}> = Result
function FromProperties(properties: Types.TProperties, func: TMappingFunction): Types.TProperties {
return globalThis.Object.getOwnPropertyNames(properties).reduce((result, key) => {
return {...result, [key]: RecursiveMap(properties[key], func) }
}, {})
}
// prettier-ignore
type TFromRest<Types extends Types.TSchema[], Func extends TMappingType, Result extends Types.TSchema[] = []> = (
Types extends [infer Left extends Types.TSchema, ...infer Right extends Types.TSchema[]]
? TFromRest<Right, Func, [...Result, TRecursiveMap<Left, Func>]>
: Result
)
function FromRest(types: Types.TSchema[], func: TMappingFunction): Types.TSchema[] {
return types.map(type => RecursiveMap(type, func))
}
// prettier-ignore
type TFromType<Type extends Types.TSchema, Func extends TMappingType, Result extends Types.TSchema = (
TApply<Type, Func>
)> = Result
function FromType(type: Types.TSchema, func: TMappingFunction): Types.TSchema {
return func(type)
}
// ------------------------------------------------------------------
// TRecursiveMap<Type, Mapping>
// ------------------------------------------------------------------
/** `[Prototype]` Applies a deep recursive map across the given type and sub types. */
// prettier-ignore
export type TRecursiveMap<Type extends Types.TSchema, Func extends TMappingType,
// Maps the Exterior Type
Exterior extends Types.TSchema = TFromType<Type, Func>,
// Maps the Interior Parameterized Types
Interior extends Types.TSchema = (
Exterior extends Types.TConstructor<infer Parameters extends Types.TSchema[], infer ReturnType extends Types.TSchema> ? Types.TConstructor<TFromRest<Parameters, Func>, TFromType<ReturnType, Func>> :
Exterior extends Types.TFunction<infer Parameters extends Types.TSchema[], infer ReturnType extends Types.TSchema> ? Types.TFunction<TFromRest<Parameters, Func>, TFromType<ReturnType, Func>> :
Exterior extends Types.TIntersect<infer Types extends Types.TSchema[]> ? Types.TIntersect<TFromRest<Types, Func>> :
Exterior extends Types.TUnion<infer Types extends Types.TSchema[]> ? Types.TUnion<TFromRest<Types, Func>> :
Exterior extends Types.TTuple<infer Types extends Types.TSchema[]> ? Types.TTuple<TFromRest<Types, Func>> :
Exterior extends Types.TArray<infer Type extends Types.TSchema> ? Types.TArray<TFromType<Type, Func>>:
Exterior extends Types.TAsyncIterator<infer Type extends Types.TSchema> ? Types.TAsyncIterator<TFromType<Type, Func>> :
Exterior extends Types.TIterator<infer Type extends Types.TSchema> ? Types.TIterator<TFromType<Type, Func>> :
Exterior extends Types.TPromise<infer Type extends Types.TSchema> ? Types.TPromise<TFromType<Type, Func>> :
Exterior extends Types.TObject<infer Properties extends Types.TProperties> ? Types.TObject<TFromProperties<Properties, Func>> :
Exterior extends Types.TRecord<infer Key extends Types.TSchema, infer Value extends Types.TSchema> ? Types.TRecordOrObject<TFromType<Key, Func>, TFromType<Value, Func>> :
Exterior
),
// Modifiers Derived from Exterior Type Mapping
IsOptional extends number = Exterior extends Types.TOptional<Types.TSchema> ? 1 : 0,
IsReadonly extends number = Exterior extends Types.TReadonly<Types.TSchema> ? 1 : 0,
Result extends Types.TSchema = (
[IsReadonly, IsOptional] extends [1, 1] ? Types.TReadonlyOptional<Interior> :
[IsReadonly, IsOptional] extends [0, 1] ? Types.TOptional<Interior> :
[IsReadonly, IsOptional] extends [1, 0] ? Types.TReadonly<Interior> :
Interior
)
> = Result
/** `[Prototype]` Applies a deep recursive map across the given type and sub types. */
// prettier-ignore
export function RecursiveMap(type: Types.TSchema, func: TMappingFunction): Types.TSchema {
// Maps the Exterior Type
const exterior = Types.CloneType(FromType(type, func), type)
// Maps the Interior Parameterized Types
const interior = (
Types.KindGuard.IsConstructor(type) ? Types.Constructor(FromRest(type.parameters, func), FromType(type.returns, func), exterior) :
Types.KindGuard.IsFunction(type) ? Types.Function(FromRest(type.parameters, func), FromType(type.returns, func), exterior) :
Types.KindGuard.IsIntersect(type) ? Types.Intersect(FromRest(type.allOf, func), exterior) :
Types.KindGuard.IsUnion(type) ? Types.Union(FromRest(type.anyOf, func), exterior) :
Types.KindGuard.IsTuple(type) ? Types.Tuple(FromRest(type.items || [], func), exterior) :
Types.KindGuard.IsArray(type) ? Types.Array(FromType(type.items, func), exterior) :
Types.KindGuard.IsAsyncIterator(type) ? Types.AsyncIterator(FromType(type.items, func), exterior) :
Types.KindGuard.IsIterator(type) ? Types.Iterator(FromType(type.items, func), exterior) :
Types.KindGuard.IsPromise(type) ? Types.Promise(FromType(type.items, func), exterior) :
Types.KindGuard.IsObject(type) ? Types.Object(FromProperties(type.properties, func), exterior) :
Types.KindGuard.IsRecord(type) ? Types.Record(FromType(GetRecordKey(type), func), FromType(GetRecordValue(type), func), exterior) :
Types.CloneType(exterior, exterior)
)
// Modifiers Derived from Exterior Type Mapping
const isOptional = Types.KindGuard.IsOptional(exterior)
const isReadonly = Types.KindGuard.IsOptional(exterior)
return (
isOptional && isReadonly ? Types.ReadonlyOptional(interior) :
isOptional ? Types.Optional(interior) :
isReadonly ? Types.Readonly(interior) :
interior
)
}

View File

@@ -0,0 +1,49 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import { TypeRegistry, Kind, TSchema, SchemaOptions } from '@sinclair/typebox'
// -------------------------------------------------------------------------------------
// TUnionEnum
// -------------------------------------------------------------------------------------
export interface TUnionEnum<T extends (string | number)[]> extends TSchema {
[Kind]: 'UnionEnum'
static: T[number]
enum: T
}
// -------------------------------------------------------------------------------------
// UnionEnum
// -------------------------------------------------------------------------------------
/** `[Experimental]` Creates a Union type with a `enum` schema representation */
export function UnionEnum<T extends (string | number)[]>(values: [...T], options: SchemaOptions = {}) {
function UnionEnumCheck(schema: TUnionEnum<(string | number)[]>, value: unknown) {
return (typeof value === 'string' || typeof value === 'number') && schema.enum.includes(value)
}
if (!TypeRegistry.Has('UnionEnum')) TypeRegistry.Set('UnionEnum', UnionEnumCheck)
return { ...options, [Kind]: 'UnionEnum', enum: values } as TUnionEnum<T>
}

View File

@@ -0,0 +1,50 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import { TypeRegistry, Kind, Static, TSchema, SchemaOptions } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
// -------------------------------------------------------------------------------------
// TUnionOneOf
// -------------------------------------------------------------------------------------
export interface TUnionOneOf<T extends TSchema[]> extends TSchema {
[Kind]: 'UnionOneOf'
static: { [K in keyof T]: Static<T[K]> }[number]
oneOf: T
}
// -------------------------------------------------------------------------------------
// UnionOneOf
// -------------------------------------------------------------------------------------
/** `[Experimental]` Creates a Union type with a `oneOf` schema representation */
export function UnionOneOf<T extends TSchema[]>(oneOf: [...T], options: SchemaOptions = {}) {
function UnionOneOfCheck(schema: TUnionOneOf<TSchema[]>, value: unknown) {
return 1 === schema.oneOf.reduce((acc: number, schema: any) => (Value.Check(schema, value) ? acc + 1 : acc), 0)
}
if (!TypeRegistry.Has('UnionOneOf')) TypeRegistry.Set('UnionOneOf', UnionOneOfCheck)
return { ...options, [Kind]: 'UnionOneOf', oneOf } as TUnionOneOf<T>
}

29
example/standard/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/standard-schema
The MIT License (MIT)
Copyright (c) 2024 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.
---------------------------------------------------------------------------*/
export * from './standard'

View File

@@ -0,0 +1,33 @@
### Standard Schema
Reference implementation of [Standard Schema](https://github.com/standard-schema/standard-schema) for TypeBox.
### Example
The following example augments a TypeBox schema with the required `~standard` interface. The `~standard` interface is applied via non-enumerable configuration enabling the schematics to continue to be used with strict compliant validators such as Ajv that would otherwise reject the non-standard `~standard` keyword.
```typescript
import { StandardSchema } from './standard'
import { Type } from '@sinclair/typebox'
const T = StandardSchema(Type.Object({ // const A = {
x: Type.Number(), // (non-enumerable) '~standard': {
y: Type.Number(), // version: 1,
z: Type.Number(), // vendor: 'TypeBox',
})) // validate: [Function: validate]
// },
// type: 'object',
// properties: {
// x: { type: 'number', [Symbol(TypeBox.Kind)]: 'Number' },
// y: { type: 'number', [Symbol(TypeBox.Kind)]: 'Number' },
// z: { type: 'number', [Symbol(TypeBox.Kind)]: 'Number' }
// },
// required: [ 'x', 'y', 'z' ],
// [Symbol(TypeBox.Kind)]: 'Object'
// }
const R = T['~standard'].validate({ x: 1, y: 2, z: 3 }) // const R = {
// value: { x: 1, y: 2, z: 3 },
// issues: []
// }
```

View File

@@ -0,0 +1,85 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/standard-schema
The MIT License (MIT)
Copyright (c) 2024 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.
---------------------------------------------------------------------------*/
import { AssertError, Value, ValueError, ValueErrorType } from '@sinclair/typebox/value'
import { TSchema, StaticDecode, CloneType } from '@sinclair/typebox'
// ------------------------------------------------------------------
// StandardSchema
// ------------------------------------------------------------------
interface StandardResult<Output> {
value: Output
issues: ValueError[]
}
interface StandardSchema<Input = unknown, Output = Input> {
readonly "~standard": StandardSchemaProperties<Input, Output>
}
interface StandardSchemaProperties<Input = unknown, Output = Input> {
readonly version: 1
readonly vendor: 'TypeBox'
readonly validate: (value: unknown) => StandardResult<Output>
readonly types?: undefined
}
// ------------------------------------------------------------------
// Issues
// ------------------------------------------------------------------
// prettier-ignore
function CreateIssues(schema: TSchema, value: unknown, error: unknown): ValueError[] {
const isAssertError = error instanceof AssertError ? error : undefined
return !isAssertError
? [{errors: [], message: 'Unknown error', path: '/', type: ValueErrorType.Kind, schema, value }]
: [...isAssertError.Errors()]
}
// ------------------------------------------------------------------
// Validate
// ------------------------------------------------------------------
// prettier-ignore
function CreateValidator<Type extends TSchema>(schema: Type, references: TSchema[]): (value: unknown) => StandardResult<StaticDecode<Type>> {
return (value: unknown): StandardResult<StaticDecode<Type>> => {
try {
return { value: Value.Parse(schema, references, value), issues: [] }
} catch (error) {
return { value: undefined, issues: CreateIssues(schema, value, error) }
}
}
}
// ------------------------------------------------------------------
// StandardSchema
// ------------------------------------------------------------------
/** Augments a TypeBox type with the `~standard` validation interface. */
export type TStandardSchema<Input = unknown, Output = Input> = (
Input & StandardSchema<Input, Output>
)
/** Augments a TypeBox type with the `~standard` validation interface. */
export function StandardSchema<Type extends TSchema>(schema: Type, references: TSchema[] = []): TStandardSchema<Type, StaticDecode<Type>> {
const standard = { version: 1, vendor: 'TypeBox', validate: CreateValidator(schema, references) }
return Object.defineProperty(CloneType(schema), "~standard", {
enumerable: false,
value: standard
}) as never
}

29
example/typedef/index.ts Normal file
View File

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/typedef
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
export * from './typedef'

266
example/typedef/readme.md Normal file
View File

@@ -0,0 +1,266 @@
# TypeDef
TypeBox is considering support for the JSON Type Definition [RFC8927](https://www.rfc-editor.org/rfc/rfc8927) specification in future releases. This specification is similar to JSON Schema but provides a constrained type representation that enables schematics to map more naturally to [nominal type systems](https://en.wikipedia.org/wiki/Nominal_type_system) as well as offering type primitives such as `int8`, `uint32` or `float32`. JSON Type Definition can be useful in applications that need to express and share data structures in a way that can be understood by a wide range of programming languages outside of JavaScript.
License MIT
## Contents
- [Usage](#Usage)
- [Types](#Types)
- [Unions](#Unions)
- [Check](#Check)
## Usage
TypeBox currently doesn't publish TypeDef as part of the mainline package. However the TypeDef functionality is written to be a standalone module you can copy into your project. You will also need `@sinclair/typebox` installed. You can obtain the `typedef` module from `example/typedef/typedef.ts` contained within this repository.
```typescript
import { Type, Static } from './typedef'
const T = Type.Struct({ // const T = {
x: Type.Float32(), // properties: {
y: Type.Float32(), // x: { type: 'float32' },
z: Type.Float32() // y: { type: 'float32' },
}) // z: { type: 'float32' }
// }
// }
type T = Static<typeof T> // type T = {
// x: number,
// y: number,
// z: number
// }
```
## Types
The following types are supported by the typedef module. Please note these types are not compatible with the JSON Schema specification and should not be combined with the standard TypeBox types.
```typescript
TypeBox TypeScript JSON Type Definition
const T = Type.Boolean() type T = boolean const T = {
type: 'boolean'
}
const T = Type.String() type T = string const T = {
type: 'string'
}
const T = Type.Float32() type T = number const T = {
type: 'float32'
}
const T = Type.Float64() type T = number const T = {
type: 'float64'
}
const T = Type.Int8() type T = number const T = {
type: 'int8'
}
const T = Type.Int16() type T = number const T = {
type: 'int16'
}
const T = Type.Int32() type T = number const T = {
type: 'int32'
}
const T = Type.Uint8() type T = number const T = {
type: 'uint8'
}
const T = Type.Uint16() type T = number const T = {
type: 'uint16'
}
const T = Type.Uint32() type T = number const T = {
type: 'uint32'
}
const T = Type.Timestamp() type T = number const T = {
type: 'timestamp'
}
const T = Type.Struct([ type T = { const T = {
x: Type.Float32(), x: number, properties: {
y: Type.Float32(), y: number x: number,
]) } y: number
}
}
const T = Type.Array( type T = number[] const T = {
Type.Float32() elements: {
) type: 'float32'
}
}
const T = Type.Record( type T = Record< const T = {
Type.Float32() string, values: {
) number type: 'float32'
> }
}
const T = Type.Enum([ type T = 'A' | 'B' | 'C' const T = {
'A', 'B', 'C' enum: [
]) 'A',
'B',
'C'
]
}
const T = Type.Union([ type T = { const T = {
Type.Struct({ kind: '0', discriminator: 'kind',
x: Type.Float32() x: number mapping: {
}), } | { '0': {
Type.Struct({ kind: '1' properties: {
y: Type.Float32() y: number x: {
]) } type: 'float32'
], 'kind') }
}
},
'1': { |
properties: {
y: {
type: 'float32'
}
}
}
}
}
```
## Unions
TypeBox supports JSON Type Definition discriminated unions with `Type.Union`. This type works similar its JSON Schema counterpart, but can only accept types of `Type.Struct` and will infer each struct with an additional named `discriminator` field. The representation for discriminated unions are also quite different, where instead of `anyOf` or `oneOf`, a set of `mapping` properties are used for each sub type.
```typescript
const Vector2 = Type.Struct({ // const Vector2 = {
x: Type.Float32(), // properties: {
y: Type.Float32() // x: { type: 'float32' },
}) // y: { type: 'float32' }
// }
// }
const Vector3 = Type.Struct({ // const Vector3 = {
x: Type.Float32(), // properties: {
y: Type.Float32(), // x: { type: 'float32' },
z: Type.Float32() // y: { type: 'float32' },
}) // z: { type: 'float32' }
// }
// }
const Vector4 = Type.Struct({ // const Vector4 = {
x: Type.Float32(), // properties: {
y: Type.Float32(), // x: { type: 'float32' },
z: Type.Float32(), // y: { type: 'float32' },
w: Type.Float32() // z: { type: 'float32' },
}) // w: { type: 'float32' }
// }
// }
const T = Type.Union([ // const T = {
Vector2, // discriminator: 'type',
Vector3, // mapping: {
Vector4 // 0: {
]) // properties: {
// x: { type: 'float32' },
// y: { type: 'float32' }
// }
// },
// 1: {
// properties: {
// x: { type: 'float32' },
// y: { type: 'float32' },
// z: { type: 'float32' }
// }
// },
// 2: {
// properties: {
// x: { type: 'float32' },
// y: { type: 'float32' },
// z: { type: 'float32' }
// }
// }
// }
// }
type T = Static<typeof T> // type T = {
// type: '0',
// x: number,
// y: number
// } | {
// type: '1',
// x: number,
// y: number,
// y: number
// } | {
// type: '2',
// x: number,
// y: number,
// y: number,
// w: number
// }
```
To type check a value matching the above union, the value will need to contain the discriminator property `type` with a value matching one of the sub type `mapping` keys. The inference type shown above can be a good reference point to understand the structure of the expected value. Nominal type systems will use the discriminator to an expected target type.
The following are examples of valid and invalid union data.
```typescript
const V = { x: 1, y: 1 } // invalid Vector2
const V = { type: '0', x: 1, y: 1 } // valid Vector2
const V = { type: '0', x: 1, y: 1, z: 1 } // invalid Vector2
const V = { type: '1', x: 1, y: 1, z: 1 } // valid Vector3
```
## Check
TypeDef types are partially supported with the `TypeCompiler` and `Value` checking modules through the extensible type system in TypeBox. Please note these types are not optimized for JIT performance and do not provide deep error reporting support. For more fully featured validation support consider Ajv. Documentation of Ajv support can be found [here](https://ajv.js.org/json-type-definition.html).
The following is TypeDef used with TypeBox's type checking infrastructure.
```typescript
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { Value } from '@sinclair/typebox/value'
const T = Type.Struct({
x: Type.Float32(),
y: Type.Float32(),
z: Type.Float32()
})
const V = {
x: 1,
y: 2,
z: 3
}
const R1 = TypeCompiler.Compile(T).Check(V) // true
const R2 = Value.Check(T, V) // true
```

619
example/typedef/typedef.ts Normal file
View File

@@ -0,0 +1,619 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/typedef
The MIT License (MIT)
Copyright (c) 2017-2025 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.
---------------------------------------------------------------------------*/
import { SetErrorFunction, DefaultErrorFunction } from '@sinclair/typebox/errors'
import * as Types from '@sinclair/typebox/type'
// --------------------------------------------------------------------------
// Metadata
// --------------------------------------------------------------------------
export interface Metadata {
[name: string]: any
}
// --------------------------------------------------------------------------
// TArray
// --------------------------------------------------------------------------
export interface TArray<T extends Types.TSchema = Types.TSchema> extends Types.TSchema {
[Types.Kind]: 'TypeDef:Array'
static: Types.Static<T, this['params']>[]
elements: T
}
// --------------------------------------------------------------------------
// TBoolean
// --------------------------------------------------------------------------
export interface TBoolean extends Types.TSchema {
[Types.Kind]: 'TypeDef:Boolean'
static: 'boolean'
type: 'boolean'
}
// --------------------------------------------------------------------------
// TUnion
// --------------------------------------------------------------------------
export type InferUnion<T extends TStruct[], D extends string, Index = string> =
T extends [infer L extends TStruct, ...infer R extends TStruct[]]
? Types.Evaluate<{ [_ in D]: Index } & Types.Static<L>> | InferUnion<R, D, Types.TIncrement<Types.Assert<Index, string>>>
: never
export interface TUnion<T extends TStruct[] = TStruct[], D extends string = string> extends Types.TSchema {
[Types.Kind]: 'TypeDef:Union'
static: InferUnion<T, D, '0'>
discriminator: D,
mapping: T
}
// --------------------------------------------------------------------------
// TEnum
// --------------------------------------------------------------------------
export interface TEnum<T extends string[] = string[]> extends Types.TSchema {
[Types.Kind]: 'TypeDef:Enum'
static: T[number]
enum: [...T]
}
// --------------------------------------------------------------------------
// TFloat32
// --------------------------------------------------------------------------
export interface TFloat32 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Float32'
type: 'float32'
static: number
}
// --------------------------------------------------------------------------
// TFloat64
// --------------------------------------------------------------------------
export interface TFloat64 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Float64'
type: 'float64'
static: number
}
// --------------------------------------------------------------------------
// TInt8
// --------------------------------------------------------------------------
export interface TInt8 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Int8'
type: 'int8'
static: number
}
// --------------------------------------------------------------------------
// TInt16
// --------------------------------------------------------------------------
export interface TInt16 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Int16'
type: 'int16'
static: number
}
// --------------------------------------------------------------------------
// TInt32
// --------------------------------------------------------------------------
export interface TInt32 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Int32'
type: 'int32'
static: number
}
// --------------------------------------------------------------------------
// TUint8
// --------------------------------------------------------------------------
export interface TUint8 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Uint8'
type: 'uint8'
static: number
}
// --------------------------------------------------------------------------
// TUint16
// --------------------------------------------------------------------------
export interface TUint16 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Uint16'
type: 'uint16'
static: number
}
// --------------------------------------------------------------------------
// TUint32
// --------------------------------------------------------------------------
export interface TUint32 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Uint32'
type: 'uint32'
static: number
}
// --------------------------------------------------------------------------
// TProperties
// --------------------------------------------------------------------------
export type TFields = Record<string, Types.TSchema>
// --------------------------------------------------------------------------
// TRecord
// --------------------------------------------------------------------------
export interface TRecord<T extends Types.TSchema = Types.TSchema> extends Types.TSchema {
[Types.Kind]: 'TypeDef:Record'
static: Record<string, Types.Static<T, this['params']>>
values: T
}
// --------------------------------------------------------------------------
// TString
// --------------------------------------------------------------------------
export interface TString extends Types.TSchema {
[Types.Kind]: 'TypeDef:String'
type: 'string'
static: string
}
// --------------------------------------------------------------------------
// TStruct
// --------------------------------------------------------------------------
// used for structural type inference
type OptionalKeys<T extends TFields> = { [K in keyof T]: T[K] extends (Types.TOptional<T[K]>) ? T[K] : never }
type RequiredKeys<T extends TFields> = { [K in keyof T]: T[K] extends (Types.TOptional<T[K]>) ? never : T[K] }
// static inference
type ReadonlyOptionalPropertyKeys<T extends TFields> = { [K in keyof T]: T[K] extends Types.TReadonly<Types.TSchema> ? (T[K] extends Types.TOptional<T[K]> ? K : never) : never }[keyof T]
type ReadonlyPropertyKeys<T extends TFields> = { [K in keyof T]: T[K] extends Types.TReadonly<Types.TSchema> ? (T[K] extends Types.TOptional<T[K]> ? never : K) : never }[keyof T]
type OptionalPropertyKeys<T extends TFields> = { [K in keyof T]: T[K] extends Types.TOptional<Types.TSchema> ? (T[K] extends Types.TReadonly<T[K]> ? never : K) : never }[keyof T]
type RequiredPropertyKeys<T extends TFields> = keyof Omit<T, ReadonlyOptionalPropertyKeys<T> | ReadonlyPropertyKeys<T> | OptionalPropertyKeys<T>>
// prettier-ignore
type StructStaticProperties<T extends TFields, R extends Record<keyof any, unknown>> = Types.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 StructStatic<T extends TFields, P extends unknown[]> = StructStaticProperties<T, {
[K in keyof T]: Static<T[K], P>
}>
export interface StructMetadata extends Metadata {
additionalProperties?: boolean
}
export interface TStruct<T extends TFields = TFields> extends Types.TSchema, StructMetadata {
[Types.Kind]: 'TypeDef:Struct'
static: StructStatic<T, this['params']>
optionalProperties: { [K in Types.Assert<OptionalKeys<T>, keyof T>]: T[K] }
properties: { [K in Types.Assert<RequiredKeys<T>, keyof T>]: T[K] }
}
// --------------------------------------------------------------------------
// TTimestamp
// --------------------------------------------------------------------------
export interface TTimestamp extends Types.TSchema {
[Types.Kind]: 'TypeDef:Timestamp'
type: 'timestamp'
static: string
}
// --------------------------------------------------------------------------
// Static
// --------------------------------------------------------------------------
export type Static<T extends Types.TSchema, P extends unknown[] = []> = Types.Static<T, P>
// --------------------------------------------------------------------------
// TimestampFormat
// --------------------------------------------------------------------------
export namespace TimestampFormat {
const DATE_TIME_SEPARATOR = /t|\s/i
const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i
const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
function IsLeapYear(year: number): boolean {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
}
function IsDate(str: string): boolean {
const matches: string[] | null = DATE.exec(str)
if (!matches) return false
const year: number = +matches[1]
const month: number = +matches[2]
const day: number = +matches[3]
return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && IsLeapYear(year) ? 29 : DAYS[month])
}
function IsTime(str: string, strictTimeZone?: boolean): boolean {
const matches: string[] | null = TIME.exec(str)
if (!matches) return false
const hr: number = +matches[1]
const min: number = +matches[2]
const sec: number = +matches[3]
const tz: string | undefined = matches[4]
const tzSign: number = matches[5] === '-' ? -1 : 1
const tzH: number = +(matches[6] || 0)
const tzM: number = +(matches[7] || 0)
if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false
if (hr <= 23 && min <= 59 && sec < 60) return true
const utcMin = min - tzM * tzSign
const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0)
return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61
}
function IsDateTime(value: string, strictTimeZone?: boolean): boolean {
const dateTime: string[] = value.split(DATE_TIME_SEPARATOR)
return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1], strictTimeZone)
}
export function Check(value: string): boolean {
return IsDateTime(value)
}
}
// --------------------------------------------------------------------------
// ValueCheck
// --------------------------------------------------------------------------
export class ValueCheckError extends Types.TypeBoxError {
constructor(public readonly schema: Types.TSchema) {
super('Unknown type')
}
}
export namespace ValueCheck {
// ------------------------------------------------------------------------
// Guards
// ------------------------------------------------------------------------
function IsObject(value: unknown): value is Record<keyof any, any> {
return typeof value === 'object' && value !== null && !globalThis.Array.isArray(value)
}
function IsArray(value: unknown): value is unknown[] {
return globalThis.Array.isArray(value)
}
function IsString(value: unknown): value is string {
return typeof value === 'string'
}
function IsInt(value: unknown, min: number, max: number): value is number {
return typeof value === 'number' && globalThis.Number.isInteger(value) && value >= min && value < max
}
// ------------------------------------------------------------------------
// Types
// ------------------------------------------------------------------------
function Array(schema: TArray, value: unknown): boolean {
return IsArray(value) && value.every(value => Visit(schema.elements, value))
}
function Boolean(schema: TBoolean, value: unknown): boolean {
return typeof value === 'boolean'
}
function Enum(schema: TEnum, value: unknown): boolean {
return typeof value === 'string' && schema.enum.includes(value)
}
function Float32(schema: TFloat32, value: unknown): boolean {
return typeof value === 'number'
}
function Float64(schema: TFloat64, value: unknown): boolean {
return typeof value === 'number'
}
function Int8(schema: TInt8, value: unknown): boolean {
return IsInt(value, -128, 127)
}
function Int16(schema: TInt16, value: unknown): boolean {
return IsInt(value, -32_768, 32_767)
}
function Int32(schema: TInt32, value: unknown): boolean {
return IsInt(value, -2_147_483_648, 2_147_483_647)
}
function Uint8(schema: TUint8, value: unknown): boolean {
return IsInt(value, 0, 255)
}
function Uint16(schema: TUint16, value: unknown): boolean {
return IsInt(value, 0, 65535)
}
function Uint32(schema: TUint32, value: unknown): boolean {
return IsInt(value, 0, 4_294_967_295)
}
function Record(schema: TRecord, value: unknown): boolean {
return IsObject(value) && globalThis.Object.getOwnPropertyNames(value).every(key => Visit(schema.values, value[key]))
}
function String(schema: TString, value: unknown): boolean {
return typeof value === 'string'
}
function Struct(schema: TStruct, value: unknown, descriminator?: string): boolean {
if (!IsObject(value)) return false
const optionalKeys = schema.optionalProperties === undefined ? [] : globalThis.Object.getOwnPropertyNames(schema.optionalProperties)
const requiredKeys = schema.properties === undefined ? [] : globalThis.Object.getOwnPropertyNames(schema.properties)
const unknownKeys = globalThis.Object.getOwnPropertyNames(value)
for (const requiredKey of requiredKeys) {
if (!(requiredKey in value)) return false
const requiredProperty = value[requiredKey]
const requiredSchema = (schema as any).properties[requiredKey]
if (!Visit(requiredSchema, requiredProperty)) return false
}
for (const optionalKey of optionalKeys) {
if (!(optionalKey in value)) continue
const optionalProperty = value[optionalKey]
const optionalSchema = (schema as any).properties[optionalKey]
if (!Visit(optionalSchema, optionalProperty)) return false
}
if (schema.additionalProperties === true) return true
const knownKeys = [...optionalKeys, ...requiredKeys]
for (const unknownKey of unknownKeys) if (!knownKeys.includes(unknownKey) && (descriminator !== undefined && unknownKey !== descriminator)) return false
for (const knownKey of knownKeys) if (!unknownKeys.includes(knownKey)) return false
return true
}
function Timestamp(schema: TString, value: unknown): boolean {
return IsString(value) && TimestampFormat.Check(value)
}
function Union(schema: TUnion, value: unknown): boolean {
if (!IsObject(value)) return false
if (!(schema.discriminator in value)) return false
if (!IsString(value[schema.discriminator])) return false
if (!(value[schema.discriminator] in schema.mapping)) return false
const struct = schema.mapping[value[schema.discriminator]] as TStruct
return Struct(struct, value, schema.discriminator)
}
function Visit(schema: Types.TSchema, value: unknown): boolean {
const anySchema = schema as any
switch (anySchema[Types.Kind]) {
case 'TypeDef:Array': return Array(anySchema, value)
case 'TypeDef:Boolean': return Boolean(anySchema, value)
case 'TypeDef:Union': return Union(anySchema, value)
case 'TypeDef:Enum': return Enum(anySchema, value)
case 'TypeDef:Float32': return Float32(anySchema, value)
case 'TypeDef:Float64': return Float64(anySchema, value)
case 'TypeDef:Int8': return Int8(anySchema, value)
case 'TypeDef:Int16': return Int16(anySchema, value)
case 'TypeDef:Int32': return Int32(anySchema, value)
case 'TypeDef:Uint8': return Uint8(anySchema, value)
case 'TypeDef:Uint16': return Uint16(anySchema, value)
case 'TypeDef:Uint32': return Uint32(anySchema, value)
case 'TypeDef:Record': return Record(anySchema, value)
case 'TypeDef:String': return String(anySchema, value)
case 'TypeDef:Struct': return Struct(anySchema, value)
case 'TypeDef:Timestamp': return Timestamp(anySchema, value)
default: throw new ValueCheckError(anySchema)
}
}
export function Check<T extends Types.TSchema>(schema: T, value: unknown): value is Types.Static<T> {
return Visit(schema, value)
}
}
// --------------------------------------------------------------------------
// TypeGuard
// --------------------------------------------------------------------------
export namespace TypeGuard {
// ------------------------------------------------------------------------
// Guards
// ------------------------------------------------------------------------
function IsObject(value: unknown): value is Record<keyof any, unknown> {
return typeof value === 'object'
}
function IsArray(value: unknown): value is unknown[] {
return globalThis.Array.isArray(value)
}
function IsOptionalBoolean(value: unknown): value is boolean | undefined {
return IsBoolean(value) || value === undefined
}
function IsBoolean(value: unknown): value is boolean {
return typeof value === 'boolean'
}
function IsString(value: unknown): value is string {
return typeof value === 'string'
}
// ------------------------------------------------------------------------
// Types
// ------------------------------------------------------------------------
export function TArray(schema: unknown): schema is TArray {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Array' && TSchema(schema['elements'])
}
export function TBoolean(schema: unknown): schema is TBoolean {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Boolean' && schema['type'] === 'boolean'
}
export function TUnion(schema: unknown): schema is TUnion {
if(!(IsObject(schema) && schema[Types.Kind] === 'TypeDef:Union' && IsString(schema['discriminator']) && IsObject(schema['mapping']))) return false
return globalThis.Object.getOwnPropertyNames(schema['mapping']).every(key => TSchema((schema['mapping'] as any)[key]))
}
export function TEnum(schema: unknown): schema is TEnum {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Enum' && IsArray(schema['enum']) && schema['enum'].every(item => IsString(item))
}
export function TFloat32(schema: unknown): schema is TFloat32 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Float32' && schema['type'] === 'float32'
}
export function TFloat64(schema: unknown): schema is TFloat64 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Float64' && schema['type'] === 'float64'
}
export function TInt8(schema: unknown): schema is TInt8 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Int8' && schema['type'] === 'int8'
}
export function TInt16(schema: unknown): schema is TInt16 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Int16' && schema['type'] === 'int16'
}
export function TInt32(schema: unknown): schema is TInt32 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Int32' && schema['type'] === 'int32'
}
export function TUint8(schema: unknown): schema is TUint8 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Uint8' && schema['type'] === 'uint8'
}
export function TUint16(schema: unknown): schema is TUint16 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Uint16' && schema['type'] === 'uint16'
}
export function TUint32(schema: unknown): schema is TUint32 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Uint32' && schema['type'] === 'uint32'
}
export function TRecord(schema: unknown): schema is TRecord {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Record' && TSchema(schema['values'])
}
export function TString(schema: unknown): schema is TString {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:String' && schema['type'] === 'string'
}
export function TStruct(schema: unknown): schema is TStruct {
if(!(IsObject(schema) && schema[Types.Kind] === 'TypeDef:Struct' && IsOptionalBoolean(schema['additionalProperties']))) return false
const optionalProperties = schema['optionalProperties']
const requiredProperties = schema['properties']
const optionalCheck = optionalProperties === undefined || IsObject(optionalProperties) && globalThis.Object.getOwnPropertyNames(optionalProperties).every(key => TSchema(optionalProperties[key]))
const requiredCheck = requiredProperties === undefined || IsObject(requiredProperties) && globalThis.Object.getOwnPropertyNames(requiredProperties).every(key => TSchema(requiredProperties[key]))
return optionalCheck && requiredCheck
}
export function TTimestamp(schema: unknown): schema is TTimestamp {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Timestamp' && schema['type'] === 'timestamp'
}
export function TKind(schema: unknown): schema is Types.TKind {
return IsObject(schema) && Types.Kind in schema && typeof (schema as any)[Types.Kind] === 'string' // TS 4.1.5: any required for symbol indexer
}
export function TSchema(schema: unknown): schema is Types.TSchema {
// prettier-ignore
return (
TArray(schema) ||
TBoolean(schema) ||
TUnion(schema) ||
TEnum(schema) ||
TFloat32(schema) ||
TFloat64(schema) ||
TInt8(schema) ||
TInt16(schema) ||
TInt32(schema) ||
TUint8(schema) ||
TUint16(schema) ||
TUint32(schema) ||
TRecord(schema) ||
TString(schema) ||
TStruct(schema) ||
TTimestamp(schema) ||
(TKind(schema) && Types.TypeRegistry.Has(schema[Types.Kind]))
)
}
}
// --------------------------------------------------------------------------
// TypeRegistry
// --------------------------------------------------------------------------
Types.TypeRegistry.Set<TArray>('TypeDef:Array', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TBoolean>('TypeDef:Boolean', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TUnion>('TypeDef:Union', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TInt8>('TypeDef:Int8', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TInt16>('TypeDef:Int16', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TInt32>('TypeDef:Int32', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TUint8>('TypeDef:Uint8', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TUint16>('TypeDef:Uint16', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TUint32>('TypeDef:Uint32', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TRecord>('TypeDef:Record', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TString>('TypeDef:String', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TStruct>('TypeDef:Struct', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TTimestamp>('TypeDef:Timestamp', (schema, value) => ValueCheck.Check(schema, value))
// --------------------------------------------------------------------------
// TypeSystemErrorFunction
// --------------------------------------------------------------------------
SetErrorFunction((error) => {
switch(error.schema[Types.Kind]) {
case 'TypeDef:Array': return 'Expected Array'
case 'TypeDef:Boolean': return 'Expected Boolean'
case 'TypeDef:Union': return 'Expected Union'
case 'TypeDef:Int8': return 'Expected Int8'
case 'TypeDef:Int16': return 'Expected Int16'
case 'TypeDef:Int32': return 'Expected Int32'
case 'TypeDef:Uint8': return 'Expected Uint8'
case 'TypeDef:Uint16': return 'Expected Uint16'
case 'TypeDef:Uint32': return 'Expected Uint32'
case 'TypeDef:Record': return 'Expected Record'
case 'TypeDef:String': return 'Expected String'
case 'TypeDef:Struct': return 'Expected Struct'
case 'TypeDef:Timestamp': return 'Expected Timestamp'
}
return DefaultErrorFunction(error)
})
// --------------------------------------------------------------------------
// TypeDefBuilder
// --------------------------------------------------------------------------
export class TypeDefBuilder {
// ------------------------------------------------------------------------
// Core
// ------------------------------------------------------------------------
protected Create(schema: Record<PropertyKey, any>, metadata: Record<keyof any, any>): any {
const keys = globalThis.Object.getOwnPropertyNames(metadata)
return keys.length > 0 ? { ...schema, metadata: { ...metadata } } : { ...schema }
}
/** [Standard] Removes compositing symbols from this schema */
public Strict<T extends Types.TSchema>(schema: T): T {
return JSON.parse(JSON.stringify(schema)) as T
}
// ------------------------------------------------------------------------
// Modifiers
// ------------------------------------------------------------------------
/** `[Standard]` Creates an Optional property */
public Optional<T extends Types.TSchema>(schema: T): Types.TOptional<T> {
return this.Optional(schema)
}
/** `[Standard]` Creates a Readonly property */
public Readonly<T extends Types.TSchema>(schema: T): Types.TReadonly<T> {
return this.Readonly(schema)
}
// ------------------------------------------------------------------------
// Types
// ------------------------------------------------------------------------
/** [Standard] Creates a Array type */
public Array<T extends Types.TSchema>(elements: T, metadata: Metadata = {}): TArray<T> {
return this.Create({ [Types.Kind]: 'TypeDef:Array', elements }, metadata)
}
/** [Standard] Creates a Boolean type */
public Boolean(metadata: Metadata = {}): TBoolean {
return this.Create({ [Types.Kind]: 'TypeDef:Boolean', type: 'boolean' }, metadata)
}
/** [Standard] Creates a Enum type */
public Enum<T extends string[]>(values: [...T], metadata: Metadata = {}): TEnum<T> {
return this.Create({[Types.Kind]: 'TypeDef:Enum', enum: values }, metadata )
}
/** [Standard] Creates a Float32 type */
public Float32(metadata: Metadata = {}): TFloat32 {
return this.Create({ [Types.Kind]: 'TypeDef:Float32', type: 'float32' }, metadata)
}
/** [Standard] Creates a Float64 type */
public Float64(metadata: Metadata = {}): TFloat64 {
return this.Create({ [Types.Kind]: 'TypeDef:Float64', type: 'float64' }, metadata)
}
/** [Standard] Creates a Int8 type */
public Int8(metadata: Metadata = {}): TInt8 {
return this.Create({ [Types.Kind]: 'TypeDef:Int8', type: 'int8' }, metadata)
}
/** [Standard] Creates a Int16 type */
public Int16(metadata: Metadata = {}): TInt16 {
return this.Create({ [Types.Kind]: 'TypeDef:Int16', type: 'int16' }, metadata)
}
/** [Standard] Creates a Int32 type */
public Int32(metadata: Metadata = {}): TInt32 {
return this.Create({ [Types.Kind]: 'TypeDef:Int32', type: 'int32' }, metadata)
}
/** [Standard] Creates a Uint8 type */
public Uint8(metadata: Metadata = {}): TUint8 {
return this.Create({ [Types.Kind]: 'TypeDef:Uint8', type: 'uint8' }, metadata)
}
/** [Standard] Creates a Uint16 type */
public Uint16(metadata: Metadata = {}): TUint16 {
return this.Create({ [Types.Kind]: 'TypeDef:Uint16', type: 'uint16' }, metadata)
}
/** [Standard] Creates a Uint32 type */
public Uint32(metadata: Metadata = {}): TUint32 {
return this.Create({ [Types.Kind]: 'TypeDef:Uint32', type: 'uint32' }, metadata)
}
/** [Standard] Creates a Record type */
public Record<T extends Types.TSchema>(values: T, metadata: Metadata = {}): TRecord<T> {
return this.Create({ [Types.Kind]: 'TypeDef:Record', values },metadata)
}
/** [Standard] Creates a String type */
public String(metadata: Metadata = {}): TString {
return this.Create({ [Types.Kind]: 'TypeDef:String', type: 'string' }, metadata)
}
/** [Standard] Creates a Struct type */
public Struct<T extends TFields>(fields: T, metadata: StructMetadata = {}): TStruct<T> {
const optionalProperties = globalThis.Object.getOwnPropertyNames(fields).reduce((acc, key) => (Types.TypeGuard.IsOptional(fields[key]) ? { ...acc, [key]: fields[key] } : { ...acc }), {} as TFields)
const properties = globalThis.Object.getOwnPropertyNames(fields).reduce((acc, key) => (Types.TypeGuard.IsOptional(fields[key]) ? { ...acc } : { ...acc, [key]: fields[key] }), {} as TFields)
const optionalObject = globalThis.Object.getOwnPropertyNames(optionalProperties).length > 0 ? { optionalProperties: optionalProperties } : {}
const requiredObject = globalThis.Object.getOwnPropertyNames(properties).length === 0 ? {} : { properties: properties }
return this.Create({ [Types.Kind]: 'TypeDef:Struct', ...requiredObject, ...optionalObject }, metadata)
}
/** [Standard] Creates a Union type */
public Union<T extends TStruct<TFields>[], D extends string = 'type'>(structs: [...T], discriminator?: D): TUnion<T, D> {
discriminator = (discriminator || 'type') as D
if (structs.length === 0) throw new Error('TypeDefBuilder: Union types must contain at least one struct')
const mapping = structs.reduce((acc, current, index) => ({ ...acc, [index.toString()]: current }), {})
return this.Create({ [Types.Kind]: 'TypeDef:Union', discriminator, mapping }, {})
}
/** [Standard] Creates a Timestamp type */
public Timestamp(metadata: Metadata = {}): TTimestamp {
return this.Create({ [Types.Kind]: 'TypeDef:Timestamp', type: 'timestamp' }, metadata)
}
}
/** JSON Type Definition Type Builder */
export const Type = new TypeDefBuilder()