mirror of
https://github.com/zoriya/typebox.git
synced 2025-12-05 22:36:12 +00:00
Revision 0.26.0 (#346)
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -20,4 +20,4 @@ jobs:
|
||||
- name: Build Library
|
||||
run: npm run build
|
||||
- name: Test Library
|
||||
run: npm run test
|
||||
run: npm run test
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
target
|
||||
dist
|
||||
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -3,6 +3,6 @@
|
||||
"node_modules": true,
|
||||
"package-lock.json": true
|
||||
},
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"editor.suggest.showStatusBar": false
|
||||
"editor.suggest.showStatusBar": false,
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { Conditional } from '@sinclair/typebox/conditional'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
const T = Conditional.Extends(Type.String(), Type.String(), Type.String(), Type.String())
|
||||
@@ -1,4 +0,0 @@
|
||||
import { Custom } from 'src/custom/index'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
Custom.Set('custom', (value) => true)
|
||||
3
benchmark/compression/module/typebox-errors.ts
Normal file
3
benchmark/compression/module/typebox-errors.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { ValueErrorType } from '@sinclair/typebox/errors'
|
||||
|
||||
console.log(ValueErrorType)
|
||||
@@ -1,4 +0,0 @@
|
||||
import { Format } from 'src/format'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
Format.Set('custom', (value) => true)
|
||||
@@ -1,4 +0,0 @@
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
const T = TypeGuard.TSchema(Type.String())
|
||||
@@ -1,3 +0,0 @@
|
||||
import { ValueHash } from '@sinclair/typebox/hash'
|
||||
|
||||
const H = ValueHash.Create(1)
|
||||
@@ -1,42 +1,40 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
export namespace Cases {
|
||||
export const Number = Type.Number()
|
||||
export const Literal_String = Type.Literal('hello')
|
||||
|
||||
export const String = Type.String()
|
||||
export const Literal_Number = Type.Literal(1)
|
||||
|
||||
export const Boolean = Type.Boolean()
|
||||
export const Literal_Boolean = Type.Literal(true)
|
||||
|
||||
export const Null = Type.Null()
|
||||
export const Primitive_Number = Type.Number()
|
||||
|
||||
export const RegEx = Type.RegEx(/foo/, { default: 'foo' })
|
||||
export const Primitive_String = Type.String()
|
||||
|
||||
export const ObjectA = Type.Object({
|
||||
p0: Type.String(),
|
||||
p1: Type.Number(),
|
||||
p2: Type.Number(),
|
||||
p3: Type.Array(Type.Number(), { minItems: 4 }),
|
||||
p4: Type.Object({
|
||||
x: Type.Number(),
|
||||
y: Type.Number(),
|
||||
z: Type.Number(),
|
||||
}),
|
||||
p5: Type.Object({
|
||||
a: Type.String(),
|
||||
b: Type.String(),
|
||||
c: Type.String(),
|
||||
export const Primitive_String_Pattern = Type.RegEx(/foo/, { default: 'foo' })
|
||||
|
||||
export const Primitive_Boolean = Type.Boolean()
|
||||
|
||||
export const Primitive_Null = Type.Null()
|
||||
|
||||
export const Object_Unconstrained = Type.Object({
|
||||
number: Type.Number(),
|
||||
negNumber: Type.Number(),
|
||||
maxNumber: Type.Number(),
|
||||
string: Type.String(),
|
||||
longString: Type.String(),
|
||||
boolean: Type.Boolean(),
|
||||
deeplyNested: Type.Object({
|
||||
foo: Type.String(),
|
||||
num: Type.Number(),
|
||||
bool: Type.Boolean(),
|
||||
}),
|
||||
})
|
||||
|
||||
export const ObjectB = Type.Object(ObjectA.properties, {
|
||||
export const Object_Constrained = Type.Object(Object_Unconstrained.properties, {
|
||||
additionalProperties: false,
|
||||
})
|
||||
|
||||
export const Tuple = Type.Tuple([Type.String(), Type.Number(), Type.Boolean()])
|
||||
|
||||
export const Union = Type.Union([Type.Object({ x: Type.Number(), y: Type.Number() }), Type.Object({ a: Type.String(), b: Type.String() })], { default: { a: 'a', b: 'b' } })
|
||||
|
||||
export const Recursive = Type.Recursive(
|
||||
export const Object_Recursive = Type.Recursive(
|
||||
(Recursive) =>
|
||||
Type.Object({
|
||||
id: Type.String(),
|
||||
@@ -75,9 +73,32 @@ export namespace Cases {
|
||||
},
|
||||
)
|
||||
|
||||
export const Vector4 = Type.Tuple([Type.Number(), Type.Number(), Type.Number(), Type.Number()])
|
||||
// prettier-ignore
|
||||
export const Tuple_Primitive = Type.Tuple([
|
||||
Type.String(),
|
||||
Type.Number(),
|
||||
Type.Boolean()
|
||||
])
|
||||
// prettier-ignore
|
||||
export const Tuple_Object = Type.Tuple([
|
||||
Type.Object({ x: Type.Number(), y: Type.Number() }),
|
||||
Type.Object({ a: Type.String(), b: Type.String() })
|
||||
])
|
||||
// prettier-ignore
|
||||
export const Composite_Intersect = Type.Intersect([
|
||||
Type.Object({ x: Type.Number(), y: Type.Number() }),
|
||||
Type.Object({ a: Type.String(), b: Type.String() })
|
||||
], { default: { x: 1, y: 2, a: 'a', b: 'b' } })
|
||||
|
||||
export const Matrix4 = Type.Array(Type.Array(Type.Number()), {
|
||||
// prettier-ignore
|
||||
export const Composite_Union = Type.Union([
|
||||
Type.Object({ x: Type.Number(), y: Type.Number() }),
|
||||
Type.Object({ a: Type.String(), b: Type.String() })
|
||||
], { default: { a: 'a', b: 'b' } })
|
||||
|
||||
export const Math_Vector4 = Type.Tuple([Type.Number(), Type.Number(), Type.Number(), Type.Number()])
|
||||
|
||||
export const Math_Matrix4 = Type.Array(Type.Array(Type.Number()), {
|
||||
default: [
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
@@ -86,29 +107,27 @@ export namespace Cases {
|
||||
],
|
||||
})
|
||||
|
||||
export const Literal_String = Type.Literal('foo')
|
||||
export const Array_Primitive_Number = Type.Array(Type.Number(), { minItems: 4 })
|
||||
|
||||
export const Literal_Number = Type.Literal(1)
|
||||
export const Array_Primitive_String = Type.Array(Type.String(), { minItems: 4 })
|
||||
|
||||
export const Literal_Boolean = Type.Literal(true)
|
||||
export const Array_Primitive_Boolean = Type.Array(Type.Boolean(), { minItems: 4 })
|
||||
|
||||
export const Array_Number = Type.Array(Type.Number(), { minItems: 16 })
|
||||
export const Array_Object_Unconstrained = Type.Array(Object_Unconstrained, { minItems: 4 })
|
||||
|
||||
export const Array_String = Type.Array(Type.String(), { minItems: 16 })
|
||||
export const Array_Object_Constrained = Type.Array(Object_Constrained, { minItems: 4 })
|
||||
|
||||
export const Array_Boolean = Type.Array(Type.Boolean(), { minItems: 16 })
|
||||
export const Array_Object_Recursive = Type.Array(Object_Recursive, { minItems: 4 })
|
||||
|
||||
export const Array_ObjectA = Type.Array(ObjectA, { minItems: 16 })
|
||||
export const Array_Tuple_Primitive = Type.Array(Tuple_Primitive, { minItems: 4 })
|
||||
|
||||
export const Array_ObjectB = Type.Array(ObjectB, { minItems: 16 })
|
||||
export const Array_Tuple_Object = Type.Array(Tuple_Object, { minItems: 4 })
|
||||
|
||||
export const Array_Tuple = Type.Array(Tuple, { minItems: 16 })
|
||||
export const Array_Composite_Intersect = Type.Array(Composite_Intersect, { minItems: 4 })
|
||||
|
||||
export const Array_Union = Type.Array(Union, { minItems: 16 })
|
||||
export const Array_Composite_Union = Type.Array(Composite_Union, { minItems: 4 })
|
||||
|
||||
export const Array_Recursive = Type.Array(Recursive, { minItems: 16 })
|
||||
export const Array_Math_Vector4 = Type.Array(Math_Vector4, { minItems: 4 })
|
||||
|
||||
export const Array_Vector4 = Type.Array(Vector4, { minItems: 16 })
|
||||
|
||||
export const Array_Matrix4 = Type.Array(Matrix4, { minItems: 16 })
|
||||
export const Array_Math_Matrix4 = Type.Array(Math_Matrix4, { minItems: 4 })
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Cases } from './cases'
|
||||
import { Benchmark } from './benchmark'
|
||||
import { TypeCompiler } from '@sinclair/typebox/compiler'
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
import { TSchema } from '@sinclair/typebox'
|
||||
import { TSchema, TypeGuard } from '@sinclair/typebox'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
import Ajv from 'ajv'
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Cases } from './cases'
|
||||
import { Benchmark } from './benchmark'
|
||||
import { TypeCompiler } from '@sinclair/typebox/compiler'
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
import { TSchema } from '@sinclair/typebox'
|
||||
import { TSchema, TypeGuard } from '@sinclair/typebox'
|
||||
import Ajv from 'ajv'
|
||||
|
||||
const ajv = new Ajv() // ensure single instance
|
||||
|
||||
export namespace CompileBenchmark {
|
||||
function Measure<T extends TSchema>(type: string, schema: T) {
|
||||
const iterations = 2000
|
||||
const iterations = 1000
|
||||
console.log('CompileBenchmark.Measure(', type, ')')
|
||||
// -------------------------------------------------------------------------------
|
||||
// Note: Ajv caches schemas by reference. To ensure we measure actual
|
||||
@@ -28,7 +27,7 @@ export namespace CompileBenchmark {
|
||||
// track duplicate $id (resulting in compile error). It is not possible to ammend
|
||||
// recursive $id's without potentially biasing results, so we omit on this case.
|
||||
// -------------------------------------------------------------------------------
|
||||
if (type === 'Recursive' || type === 'Array_Recursive') continue
|
||||
if (type === 'Object_Recursive' || type === 'Array_Object_Recursive') continue
|
||||
|
||||
yield Measure(type, schema)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export function present(results: Result[]) {
|
||||
if (result.value) {
|
||||
return {
|
||||
...acc,
|
||||
[result.type.padStart(16, ' ')]: {
|
||||
[result.type.padEnd(26, ' ')]: {
|
||||
Iterations: result.compiler.iterations,
|
||||
ValueCheck: `${result.value.completed} ms`.padStart(10),
|
||||
Ajv: `${result.ajv.completed} ms`.padStart(10),
|
||||
@@ -20,7 +20,7 @@ export function present(results: Result[]) {
|
||||
} else {
|
||||
return {
|
||||
...acc,
|
||||
[result.type.padStart(16, ' ')]: {
|
||||
[result.type.padEnd(26, ' ')]: {
|
||||
Iterations: result.compiler.iterations,
|
||||
Ajv: `${result.ajv.completed} ms`.padStart(10),
|
||||
TypeCompiler: `${result.compiler.completed} ms`.padStart(10),
|
||||
|
||||
410
changelog.md
410
changelog.md
@@ -1,410 +0,0 @@
|
||||
## [0.25.24](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.23)
|
||||
|
||||
Updates:
|
||||
|
||||
- [331](https://github.com/sinclairzx81/typebox/pull/331) Implements an additional check specific to property types of `required & undefined`. This to ensure the property key exists when the property value extends `undefined`.
|
||||
- [331](https://github.com/sinclairzx81/typebox/pull/331) Documentation updates for Ajv and TypeCompiler
|
||||
|
||||
Additional:
|
||||
|
||||
- [331](https://github.com/sinclairzx81/typebox/pull/331) Remove unusued recursive code paths for create and cast.
|
||||
|
||||
## [0.25.23](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.23)
|
||||
|
||||
Updates:
|
||||
|
||||
- [324](https://github.com/sinclairzx81/typebox/pull/324) TypeScript Language Service now presents JSDoc comments when inferring static object properties. (IntelliSense)
|
||||
- [325](https://github.com/sinclairzx81/typebox/pull/325) Additional property inference optimizations.
|
||||
|
||||
Additional:
|
||||
|
||||
- Huge thank you to Github user [stevezhu](https://github.com/stevezhu) for these excellent contributions.
|
||||
|
||||
|
||||
## [0.25.22](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.22)
|
||||
|
||||
Updates:
|
||||
|
||||
- [323](https://github.com/sinclairzx81/typebox/pull/323) adds compiler support for UTF-16 (unicode) characters for schema identifiers.
|
||||
|
||||
## [0.25.18](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.18)
|
||||
|
||||
Updates:
|
||||
|
||||
- [307](https://github.com/sinclairzx81/typebox/pull/307) implements date conversion when casting values with `Value.Cast(Type.Date(), ...)`. Castable values include numbers (interpretted as timestamps) and iso8601 string values. Uncastable values will result in dates with values of `1970-01-01T00:00:00.000Z`. This version also includes more robust checks for Dates initialized with invalid values.
|
||||
|
||||
## [0.25.11](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.11)
|
||||
|
||||
Updates:
|
||||
|
||||
- [286](https://github.com/sinclairzx81/typebox/pull/286) implements a FNV1A-64 non cryptographic hashing function in TypeBox. This function should not be used in place of cryptographic hashing functions, rather it's purpose is to provide relatively fast, hashing mechanism to assist with checks for arrays with uniqueItems constraints, specifically for cases where the array may contains reference values (such as objects, arrays, Dates and Uint8Array). This function is provided via `Value.Hash()` for convenience as the hash may be useful to generate a numeric identifier for values (with some considerations to React array rendering in absence of key or identifier)
|
||||
|
||||
## [0.25.10](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.10)
|
||||
|
||||
Updates:
|
||||
|
||||
- [283](https://github.com/sinclairzx81/typebox/pull/283) Updates the custom type validator callback signature to accept a schema instance. The schema instance may include additional constraints (such as options) that may be used during the validation process. `Custom.Set('<Kind>', (schema, value) => { ... })`.
|
||||
|
||||
## [0.25.9](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.9)
|
||||
|
||||
Updates:
|
||||
|
||||
- [282](https://github.com/sinclairzx81/typebox/pull/282) TypeBox now supports custom types. These types require the user to specify a custom `[Kind]` string on the type. Custom types can be registered via `Custom.Set('<Kind>', (value) => { ... })` which allow the TypeCompiler and Value API's to make use of user defined validation logic.
|
||||
|
||||
## [0.25.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- [271](https://github.com/sinclairzx81/typebox/pull/271) Adds a new non-standard `Type.Date()` type. This type joins the existing `Type.UInt8Array()` as a promoted extended type used to represent core JavaScript primitives. It's inclusion was prompted by end user requirements to validate Date objects prior to writing them to Date supported API's and where serialization of the Date object is handled internally by the API.
|
||||
|
||||
- [271](https://github.com/sinclairzx81/typebox/pull/271) Redesign of Extended Type representations. Extended types been updated to provide external validators (such as Ajv) additional standard proporties to use when defining the custom schema. These properties are `instanceOf` (used for validating a class `object` instances), and `typeOf` (when validating `value` types). Information on configuring Ajv for these properties can be found in the Ajv section of the TypeBox readme.
|
||||
|
||||
## [0.24.49](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.49)
|
||||
|
||||
Updates:
|
||||
|
||||
- [264](https://github.com/sinclairzx81/typebox/pull/264) TypeBox now provides preliminary support for non-boolean `additionalProperties`. This allows existing `TObject` schemas to be augmented with additional properties of a known type.
|
||||
|
||||
Additional:
|
||||
|
||||
- TypeBox provides an additional reference `codegen` module for generating raw JSON Schema from TypeScript types via the TS compiler API. This generator may be used in future tooling.
|
||||
|
||||
## [0.24.44](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.44)
|
||||
|
||||
Updates:
|
||||
- [189](https://github.com/sinclairzx81/typebox/pull/189) Both `Value.Error(T, value)` and `TypeCheck.Error(value)` now return an iterator for validation errors.
|
||||
- [191](https://github.com/sinclairzx81/typebox/pull/191) TypeBox now provides a `TypeGuard` API that can be used to check the structural validity of TypeBox type. The TypeGuard can be used in reflection / code generation scenarios to resolve the appropriate inner `TSchema` type while traversing a outer type.
|
||||
- [197](https://github.com/sinclairzx81/typebox/pull/197) TypeBox now implements conditional runtime type mapping. This functionality is offered as seperate import for the `0.24.0` release but may be provided as standard type in later releases. This API enables `type T = Foo extends Bar ? true : false` conditional checks to be implemented at runtime. This API also provides the `Exclude` and `Extract` utility types which are implemented through conditional types in TypeScript.
|
||||
- [199](https://github.com/sinclairzx81/typebox/pull/199) TypeBox now provides better support for varidiac function and constructor signatures. Currently Variadics are mapped through `Tuple` types.
|
||||
- [200](https://github.com/sinclairzx81/typebox/pull/200) The types `TPick` and `TOmit` now support types of `TUnion<TLiteral<string>[]>` to be used to select properties. Additionally, `KeyOf` now returns `TUnion<TLiteral<string>[]>`, allowing `KeyOf` schemas to be passed to `TPick` and `TOmit`.
|
||||
- [214](https://github.com/sinclairzx81/typebox/pull/214) TypeBox now provides better support for i18n. To achieve this, TypeBox includes fixed mappable error codes on the `ValueError` type. These codes can be used by external implementors to create localized error messages. TypeBox may include localized error codes as an optional import in future releases.
|
||||
- [288](https://github.com/sinclairzx81/typebox/pull/228) TypeBox now allows users to implement custom string validator formats. These formats are internally shared between the `Value` and `TypeCompiler` API's. TypeBox does not currently provide any built in formats, however the standard expected set (email, uuid, uri, etc) may be provided via optional import (inline with ajv-formats usage)
|
||||
- [229](https://github.com/sinclairzx81/typebox/pull/229) The `Value.Cast()` function now implements automatic coersion of string, number and boolean types.
|
||||
- [231](https://github.com/sinclairzx81/typebox/pull/231) TypeBox provides a new `Value.Diff<T>()` and `Value.Patch<T>()` utility API for JavaScript values. This API is intended to provide a basis for the efficient transmission of state updates across a network. This API can diff any JavaScript value (typed or untyped) but is recommended to be used in conjunction with a formal static type.
|
||||
- [236](https://github.com/sinclairzx81/typebox/pull/236) TypeBox now implements the `TNever` type. This type is analogous to TypeScript's `never` type and is used in instances a composition results in a non-reconcilable type. Currently this type is implemented for empty `TUnion<[]>` types only. Future releases may utilize this type for planned updates to `TIntersect` (for example `string & number` resolves to `never`)
|
||||
- [241](https://github.com/sinclairzx81/typebox/pull/241) [247](https://github.com/sinclairzx81/typebox/pull/247) TypeBox now exposes a ValuePointer API that can be used to mutate a value via an RFC6901 JSON Pointer. Previously this functionality was internally used by `Value.Diff()` and `Value.Patch()` functions but is now offered as an optional import for implementations that need to update values manually through pointer references.
|
||||
|
||||
Additional:
|
||||
|
||||
- This project now includes two reference code generation utilities that can be used in custom build tooling. The first is `TypeScriptCodeGen` which will remap TypeScript `interface` and `type` definitions to TypeBox types. The second is `TypeBoxCodeGen` which will map existing TypeBox types into TypeScript type definitions. These implementations are not expected to be part of the TypeBox package, but users are free to clone and enhance them in their existing tool chains. Reference implementations can be found https://github.com/sinclairzx81/typebox/tree/master/codegen
|
||||
|
||||
|
||||
|
||||
## [0.24.15](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.15)
|
||||
|
||||
Added:
|
||||
- `Conditional.Extends(...)` This enables TypeBox to conditionally map types inline with TypeScripts structural equivelence checks. Tested against TypeScript 4.7.4.
|
||||
- `Conditional.Extract(...)` Which analogs TypeScripts `Extract<...>` utility type. Additional information [here](https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union)
|
||||
- `Conditional.Exclude(...)` Which analogs TypeScripts `Exclude<...>` utility type. Additional information [here](https://www.typescriptlang.org/docs/handbook/utility-types.html#excludeuniontype-excludedmembers)
|
||||
- `Type.Parameters(...)` Returns the parameters of a `TFunction` as a `TTuple`
|
||||
- `Type.ReturnType(...)` Returns the return type schema of a `TFunction`
|
||||
- `Type.ConstructorParameters(...)` Returns the parameters of a `TConstructor` as a `TTuple`
|
||||
- `Type.InstanceType(...)` Returns the instance type schema of a `TConstructor`
|
||||
|
||||
## [0.24.8](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.8)
|
||||
|
||||
Added:
|
||||
- `Value.Cast(T, value)` structurally casts a value into another form while retaining information within the original value.
|
||||
- `Value.Check(T, value)` provides slow dynamic type checking for values. For performance, one should consider the `TypeCompiler` or `Ajv` validator.
|
||||
- `Value.Errors(T, value)` returns an iterable iterator errors found in a given value.
|
||||
|
||||
## [0.24.6](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.6)
|
||||
|
||||
Added:
|
||||
|
||||
- TypeBox now offers a `TypeGuard` module for structurally checking TypeBox schematics. This module can be used in runtime type reflection scenarios where it's helpful to test a schema is of a particular form. This module can be imported under the `@sinclair/typebox/guard` import path.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
|
||||
const T: any = {} // T is any
|
||||
|
||||
const { type } = T // unsafe: type is any
|
||||
|
||||
if(TypeGuard.TString(T)) {
|
||||
|
||||
const { type } = T // safe: type is 'string'
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## [0.24.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.0)
|
||||
|
||||
Changes:
|
||||
|
||||
- The `kind` and `modifier` keywords are now expressed as symbol keys. This change allows AJV to leverage TypeBox schemas directly without explicit configuration of `kind` and `modifier` in strict mode.
|
||||
- `Type.Intersect([...])` now returns a composite `TObject` instead of a `allOf` schema representation. This change allows intersected types to be leveraged in calls to `Omit`, `Pick`, `Partial`, `Required`.
|
||||
- `Type.Void(...)` now generates a `{ type: null }` schema representation. This is principally used for RPC implementations where a RPC target function needs to respond with a serializable value for `void` return.
|
||||
- `Type.Rec(...)` renamed to `Type.Recursive(...)` and now supports non-mutual recursive type inference.
|
||||
|
||||
Added:
|
||||
|
||||
- `Type.Unsafe<T>(...)`. This type enables custom schema representations whose static type is informed by generic type T.
|
||||
- `Type.Uint8Array(...)`. This is a non-standard schema that can be configured on AJV to enable binary buffer range validation.
|
||||
- Added optional extended `design` property on all schema options. This property can be used to specify design time metadata when rendering forms.
|
||||
|
||||
Compiler:
|
||||
|
||||
- TypeBox now provides an optional experimental type compiler that can be used to validate types without AJV. This compiler is not a standard JSON schema compiler and will only compile TypeBox's known schema representations. For full JSON schema validation, AJV should still be the preference. This compiler is a work in progress.
|
||||
|
||||
Value:
|
||||
|
||||
- TypeBox now provides a value generator that can generate default values from TypeBox types.
|
||||
|
||||
Breaking Changes:
|
||||
|
||||
- `Type.Intersect(...)` is constrained to accept types of `TObject` only.
|
||||
- `Type.Namespace(...)` has been removed.
|
||||
- The types `TUnion`, `TEnum`, `KeyOf` and `TLiteral<TString>[]` are all now expressed via `allOf`. For Open API users, Please consider `Type.Unsafe()` to express `enum` string union representations. Documentation on using `Type.Unsafe()` can be found [here](https://github.com/sinclairzx81/typebox#Unsafe-Types)
|
||||
|
||||
## [0.23.3](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.3)
|
||||
|
||||
Updates:
|
||||
|
||||
- Fix: Rename BoxKind to NamespaceKind
|
||||
|
||||
## [0.23.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.1)
|
||||
|
||||
Updates:
|
||||
|
||||
- The `Type.KeyOf(...)` type can now accept references of `Type.Ref(TObject)`
|
||||
|
||||
## [0.23.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- The types `Type.Namespace(...)` and `Type.Ref(...)` are promoted to `Standard`.
|
||||
- TypeBox now includes an additional type named `TRef<...>` that is returned on calls to `Type.Ref(...)`. The `TRef<...>` includes a new `RefKind` symbol for introspection of the reference type.
|
||||
- TypeBox now maintains an internal dictionary of all schemas passed that contain an `$id` property. This dictionary is checked whenever a user attempts to reference a type and will throw if attempting to reference a target schema with no `$id`.
|
||||
- The types `Type.Partial(...)`, `Type.Required(...)`, `Type.Omit()` and `Type.Pick(...)` now support reference types. Note that when using these functions with references, TypeBox will replicate the source schema and apply the nessasary modifiers to the replication.
|
||||
|
||||
## [0.22.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.22.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- The type `TSchema` is now expressed as an HKT compatible interface. All types now extend the `TSchema` interface and are themselves also expressed as interfaces. This work was undertaken to explore recursive type aliases in future releases.
|
||||
- The phantom property `_infer` has been renamed to `$static`. Callers should not interact with this property as it will always be `undefined` and used exclusively for optimizing type inference in TypeScript 4.5 and above.
|
||||
- TypeBox re-adds the feature to deeply introspect schema properties. This feature was temporarily removed on the `0.21.0` update to resolve deep instantiation errors on TypeScript 4.5.
|
||||
- The `Type.Box(...)` and `Type.Rec(...)` functions internally rename the property `definitions` to `$defs` inline with JSON schema draft 2019-09 conventions. Reference [here](https://opis.io/json-schema/2.x/definitions.html).
|
||||
|
||||
## [0.21.2](https://www.npmjs.com/package/@sinclair/typebox/v/0.21.2)
|
||||
|
||||
Updates:
|
||||
|
||||
- TypeBox now correctly infers for nested union and intersect types.
|
||||
|
||||
Before
|
||||
|
||||
```typescript
|
||||
const A = Type.Object({ a: Type.String() })
|
||||
const B = Type.Object({ b: Type.String() })
|
||||
const C = Type.Object({ c: Type.String() })
|
||||
const T = Type.Intersect([A, Type.Union([B, C])])
|
||||
|
||||
// type T = { a: string } & { b: string } & { c: string }
|
||||
```
|
||||
After
|
||||
|
||||
```typescript
|
||||
const A = Type.Object({ a: Type.String() })
|
||||
const B = Type.Object({ b: Type.String() })
|
||||
const C = Type.Object({ c: Type.String() })
|
||||
const T = Type.Intersect([A, Type.Union([B, C])])
|
||||
|
||||
// type T = { a: string } & ({ b: string } | { c: string })
|
||||
```
|
||||
|
||||
## [0.21.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.21.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- TypeBox static inference has been updated inline with additional inference constraints added in TypeScript 4.5. All types now include a phantom `_infer` property which contains the inference TS type for a given schema. The type of this property is inferred at the construction of the schema, and referenced directly via `Static<T>`.
|
||||
- `Type.Box(...)` has been renamed to `Type.Namespace(...)` to draw an analogy with XML's `xmlns` XSD types.
|
||||
|
||||
## [0.20.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.20.1)
|
||||
|
||||
Updates:
|
||||
|
||||
- TypeBox mandates TypeScript compiler version `4.3.5` and above.
|
||||
|
||||
## [0.20.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.20.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- Function `Type.Rec(...)` signature change.
|
||||
- Minor documentation updates.
|
||||
|
||||
Notes:
|
||||
|
||||
The `Type.Rec(...)` function signature has been changed to allow passing the `$id` as a custom option. This is to align `Type.Rec(...)` with other functions that accept `$id` as an option. `Type.Rec(...)` can work with or without an explicit `$id`, but it is recommend to specify one if the recursive type is nested in an outer schema.
|
||||
|
||||
```typescript
|
||||
const Node = Type.Rec(Self => Type.Object({
|
||||
id: Type.String(),
|
||||
nodes: Type.Array(Self)
|
||||
}), { $id: 'Node' })
|
||||
```
|
||||
|
||||
## [0.19.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.19.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- Function `Type.Box(...)` removes `$id` parameter as first argument.
|
||||
- Function `Type.Ref(...)` is now overloaded to support referencing `Type.Box(...)` and `TSchema`.
|
||||
|
||||
Notes:
|
||||
|
||||
This update changes the signature of `Type.Box(...)` and removes the explicit `$id` passing on the first parameter. The `$id` must be passed as an option if the caller wants to reference that type.
|
||||
|
||||
```typescript
|
||||
const T = Type.String({ $id: 'T' })
|
||||
|
||||
const B = Type.Box({ T }, { $id: 'B' })
|
||||
|
||||
const R1 = Type.Ref(T) // const R1 = { $ref: 'T' }
|
||||
|
||||
const R2 = Type.Ref(B, 'T') // const R2 = { $ref: 'B#/definitions/T' }
|
||||
```
|
||||
|
||||
## [0.18.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.18.1)
|
||||
|
||||
- Function `Type.Enum(...)` now expressed with `anyOf`. This to remove the `allowUnionTypes` configuration required to use `enum` with in AJV strict.
|
||||
- Function `Type.Rec(...)` now takes a required `$id` as the first parameter.
|
||||
- Function `Type.Strict(...)` no longer includes a `$schema`. Callers can now optionally pass `CustomOptions` on `Type.Strict(...)`
|
||||
|
||||
## [0.18.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.18.0)
|
||||
|
||||
Changes:
|
||||
|
||||
- Function `Type.Intersect(...)` is now implemented with `allOf` and constrained with `unevaluatedProperties` (draft `2019-09`)
|
||||
- Function `Type.Dict(...)` has been deprecated and replaced with `Type.Record(...)`.
|
||||
- Function `Type.Strict(...)` now includes the `$schema` property referencing the `2019-09` draft.
|
||||
|
||||
### Type.Intersect(...)
|
||||
|
||||
TypeBox now targets JSON schema draft `2019-09` for expressing `Type.Intersect(...)`. This is now expressed via `allOf` with additionalProperties constrained with `unevaluatedProperties`. Note that `unevaluatedProperties` is a feature of the `2019-09` specification.
|
||||
|
||||
### Type.Record(K, V)
|
||||
|
||||
TypeBox has deprecated `Type.Dict(...)` in favor of the more generic `Type.Record(...)`. Where as `Type.Dict(...)` was previously expressed with `additionalProperties: { ... }`, `Type.Record(...)` is expressed with `patternProperties` and supports both `string` and `number` indexer keys. Additionally, `Type.Record(...)` supports string union arguments. This is analogous to TypeScript's utility record type `Record<'a' | 'b' | 'c', T>`.
|
||||
|
||||
## [0.17.7](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.7)
|
||||
|
||||
Changes:
|
||||
|
||||
- Added optional `$id` argument on `Type.Rec()`.
|
||||
- Documentation updates.
|
||||
|
||||
## [0.17.6](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.6)
|
||||
|
||||
Changes:
|
||||
|
||||
- Added `Type.Rec(...)` function.
|
||||
|
||||
Notes:
|
||||
|
||||
This update introduces the `Type.Rec()` function for enabling Recursive Types. Please note that due to current inference limitations in TypeScript, TypeBox is unable to infer the type and resolves inner types to `any`.
|
||||
|
||||
This functionality enables for complex self referential schemas to be composed. The following creates a binary expression syntax node with the expression self referential for left and right oprands.
|
||||
|
||||
```typescript
|
||||
const Operator = Type.Union([
|
||||
Type.Literal('+'),
|
||||
Type.Literal('-'),
|
||||
Type.Literal('/'),
|
||||
Type.Literal('*')
|
||||
])
|
||||
|
||||
type Expression = Static<typeof Expression>
|
||||
|
||||
// Defines a self referencing type.
|
||||
const Expression = Type.Rec(Self => Type.Object({
|
||||
left: Type.Union([Self, Type.Number()]),
|
||||
right: Type.Union([Self, Type.Number()]),
|
||||
operator: Operator
|
||||
}))
|
||||
|
||||
function evaluate(expression: Expression): number {
|
||||
const left = typeof expression.left !== 'number'
|
||||
? evaluate(expression.left as Expression) // assert as Expression
|
||||
: expression.left
|
||||
const right = typeof expression.right !== 'number'
|
||||
? evaluate(expression.right as Expression) // assert as Expression
|
||||
: expression.right
|
||||
switch(expression.operator) {
|
||||
case '+': return left + right
|
||||
case '-': return left - right
|
||||
case '*': return left * right
|
||||
case '/': return left / right
|
||||
}
|
||||
}
|
||||
|
||||
const result = evaluate({
|
||||
left: {
|
||||
left: 10,
|
||||
operator: '*',
|
||||
right: 4,
|
||||
},
|
||||
operator: '+',
|
||||
right: 2,
|
||||
}) // -> 42
|
||||
```
|
||||
|
||||
This functionality is flagged as `EXPERIMENTAL` and awaits community feedback.
|
||||
|
||||
## [0.17.4](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.4)
|
||||
|
||||
Changes:
|
||||
|
||||
- Added `Type.Box()` and `Type.Ref()` functions.
|
||||
|
||||
Notes:
|
||||
|
||||
This update provides the `Type.Box()` function to enable common related schemas to grouped under a common namespace; typically expressed as a `URI`. This functionality is primarily geared towards allowing one to define a common set of domain objects that may be shared across application domains running over a network. The `Type.Box()` is intended to be an analog to `XML` `xmlns` namespacing.
|
||||
|
||||
The `Type.Ref()` function is limited to referencing from boxes only. The following is an example.
|
||||
|
||||
```typescript
|
||||
// Domain objects for the fruit service.
|
||||
const Fruit = Type.Box('https://fruit.domain.com', {
|
||||
Apple: Type.Object({ ... }),
|
||||
Orange: Type.Object({ ... }),
|
||||
})
|
||||
|
||||
// An order referencing types of the fruit service.
|
||||
const Order = Type.Object({
|
||||
id: Type.String(),
|
||||
quantity: Type.Number(),
|
||||
item: Type.Union([
|
||||
Type.Ref(Fruit, 'Apple'),
|
||||
Type.Ref(Fruit, 'Orange')
|
||||
])
|
||||
})
|
||||
```
|
||||
> Note: As of this release, the `Type.Omit()`, `Type.Pick()`, `Type.Partial()`, `Type.Readonly()` and `Type.Intersect()` functions do not work with Reference Types. This may change in later revisions.
|
||||
|
||||
For validation using `Ajv`, its possible to apply the `Box` directly as a schema.
|
||||
|
||||
```typescript
|
||||
ajv.addSchema(Fruit) // makes all boxed types known to Ajv
|
||||
```
|
||||
|
||||
This functionality is flagged as `EXPERIMENTAL` and awaits community feedback.
|
||||
|
||||
## [0.17.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.1)
|
||||
|
||||
- Remove default `additionalProperties: false` constraint from all object schemas.
|
||||
|
||||
This update removes the `additionalProperties: false` constraint on all object schemas. This constraint was introduced on `0.16.x` but has resulted in significant downstream problems composing schemas whose types `intersect`. This is due to a JSON schema design principle where constraints should only be added (never removed), and that intersection types may require removal of the `additionalProperties` constraint in some cases, this had resulted in some ambiguity with respect to how TypeBox should handle such intersections.
|
||||
|
||||
This update can also be seen as a precursor towards TypeBox potentially leveraging `unevaluatedProperties` for type intersection in future releases. Implementors should take note that in order to constrain the schema to known properties, one should apply the `additionalProperties: false` as the second argument to `Type.Object({...})`.
|
||||
|
||||
```typescript
|
||||
const T = Type.Object({
|
||||
a: Type.String(),
|
||||
b: Type.Number()
|
||||
}, {
|
||||
additionalProperties: false
|
||||
})
|
||||
15
changelog/0.17.1.md
Normal file
15
changelog/0.17.1.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## [0.17.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.1)
|
||||
|
||||
- Remove default `additionalProperties: false` constraint from all object schemas.
|
||||
|
||||
This update removes the `additionalProperties: false` constraint on all object schemas. This constraint was introduced on `0.16.x` but has resulted in significant downstream problems composing schemas whose types `intersect`. This is due to a JSON schema design principle where constraints should only be added (never removed), and that intersection types may require removal of the `additionalProperties` constraint in some cases, this had resulted in some ambiguity with respect to how TypeBox should handle such intersections.
|
||||
|
||||
This update can also be seen as a precursor towards TypeBox potentially leveraging `unevaluatedProperties` for type intersection in future releases. Implementers should take note that in order to constrain the schema to known properties, one should apply the `additionalProperties: false` as the second argument to `Type.Object({...})`.
|
||||
|
||||
```typescript
|
||||
const T = Type.Object({
|
||||
a: Type.String(),
|
||||
b: Type.Number()
|
||||
}, {
|
||||
additionalProperties: false
|
||||
})
|
||||
38
changelog/0.17.4.md
Normal file
38
changelog/0.17.4.md
Normal file
@@ -0,0 +1,38 @@
|
||||
## [0.17.4](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.4)
|
||||
|
||||
Changes:
|
||||
|
||||
- Added `Type.Box()` and `Type.Ref()` functions.
|
||||
|
||||
Notes:
|
||||
|
||||
This update provides the `Type.Box()` function to enable common related schemas to grouped under a common namespace; typically expressed as a `URI`. This functionality is primarily geared towards allowing one to define a common set of domain objects that may be shared across application domains running over a network. The `Type.Box()` is intended to be an analog to `XML` `xmlns` namespaces.
|
||||
|
||||
The `Type.Ref()` function is limited to referencing from boxes only. The following is an example.
|
||||
|
||||
```typescript
|
||||
// Domain objects for the fruit service.
|
||||
const Fruit = Type.Box('https://fruit.domain.com', {
|
||||
Apple: Type.Object({ ... }),
|
||||
Orange: Type.Object({ ... }),
|
||||
})
|
||||
|
||||
// An order referencing types of the fruit service.
|
||||
const Order = Type.Object({
|
||||
id: Type.String(),
|
||||
quantity: Type.Number(),
|
||||
item: Type.Union([
|
||||
Type.Ref(Fruit, 'Apple'),
|
||||
Type.Ref(Fruit, 'Orange')
|
||||
])
|
||||
})
|
||||
```
|
||||
> Note: As of this release, the `Type.Omit()`, `Type.Pick()`, `Type.Partial()`, `Type.Readonly()` and `Type.Intersect()` functions do not work with Reference Types. This may change in later revisions.
|
||||
|
||||
For validation using `Ajv`, its possible to apply the `Box` directly as a schema.
|
||||
|
||||
```typescript
|
||||
ajv.addSchema(Fruit) // makes all boxed types known to Ajv
|
||||
```
|
||||
|
||||
This functionality is flagged as `EXPERIMENTAL` and awaits community feedback.
|
||||
56
changelog/0.17.6.md
Normal file
56
changelog/0.17.6.md
Normal file
@@ -0,0 +1,56 @@
|
||||
## [0.17.6](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.6)
|
||||
|
||||
Changes:
|
||||
|
||||
- Added `Type.Rec(...)` function.
|
||||
|
||||
Notes:
|
||||
|
||||
This update introduces the `Type.Rec()` function for enabling Recursive Types. Please note that due to current inference limitations in TypeScript, TypeBox is unable to infer the type and resolves inner types to `any`.
|
||||
|
||||
This functionality enables for complex self referential schemas to be composed. The following creates a binary expression syntax node with the expression self referential for left and right operands.
|
||||
|
||||
```typescript
|
||||
const Operator = Type.Union([
|
||||
Type.Literal('+'),
|
||||
Type.Literal('-'),
|
||||
Type.Literal('/'),
|
||||
Type.Literal('*')
|
||||
])
|
||||
|
||||
type Expression = Static<typeof Expression>
|
||||
|
||||
// Defines a self referencing type.
|
||||
const Expression = Type.Rec(Self => Type.Object({
|
||||
left: Type.Union([Self, Type.Number()]),
|
||||
right: Type.Union([Self, Type.Number()]),
|
||||
operator: Operator
|
||||
}))
|
||||
|
||||
function evaluate(expression: Expression): number {
|
||||
const left = typeof expression.left !== 'number'
|
||||
? evaluate(expression.left as Expression) // assert as Expression
|
||||
: expression.left
|
||||
const right = typeof expression.right !== 'number'
|
||||
? evaluate(expression.right as Expression) // assert as Expression
|
||||
: expression.right
|
||||
switch(expression.operator) {
|
||||
case '+': return left + right
|
||||
case '-': return left - right
|
||||
case '*': return left * right
|
||||
case '/': return left / right
|
||||
}
|
||||
}
|
||||
|
||||
const result = evaluate({
|
||||
left: {
|
||||
left: 10,
|
||||
operator: '*',
|
||||
right: 4,
|
||||
},
|
||||
operator: '+',
|
||||
right: 2,
|
||||
}) // -> 42
|
||||
```
|
||||
|
||||
This functionality is flagged as `EXPERIMENTAL` and awaits community feedback.
|
||||
22
changelog/0.18.0.md
Normal file
22
changelog/0.18.0.md
Normal file
@@ -0,0 +1,22 @@
|
||||
## [0.18.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.18.0)
|
||||
|
||||
Changes:
|
||||
|
||||
- Function `Type.Intersect(...)` is now implemented with `allOf` and constrained with `unevaluatedProperties` (draft `2019-09`)
|
||||
- Function `Type.Dict(...)` has been deprecated and replaced with `Type.Record(...)`.
|
||||
- Function `Type.Strict(...)` now includes the `$schema` property referencing the `2019-09` draft.
|
||||
|
||||
### Type.Intersect(...)
|
||||
|
||||
TypeBox now targets JSON schema draft `2019-09` for expressing `Type.Intersect(...)`. This is now expressed via `allOf` with additionalProperties constrained with `unevaluatedProperties`. Note that `unevaluatedProperties` is a feature of the `2019-09` specification.
|
||||
|
||||
### Type.Record(K, V)
|
||||
|
||||
TypeBox has deprecated `Type.Dict(...)` in favor of the more generic `Type.Record(...)`. Where as `Type.Dict(...)` was previously expressed with `additionalProperties: { ... }`, `Type.Record(...)` is expressed with `patternProperties` and supports both `string` and `number` indexer keys. Additionally, `Type.Record(...)` supports string union arguments. This is analogous to TypeScript's utility record type `Record<'a' | 'b' | 'c', T>`.
|
||||
|
||||
## [0.17.7](https://www.npmjs.com/package/@sinclair/typebox/v/0.17.7)
|
||||
|
||||
Changes:
|
||||
|
||||
- Added optional `$id` argument on `Type.Rec()`.
|
||||
- Documentation updates.
|
||||
5
changelog/0.18.1.md
Normal file
5
changelog/0.18.1.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## [0.18.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.18.1)
|
||||
|
||||
- Function `Type.Enum(...)` now expressed with `anyOf`. This to remove the `allowUnionTypes` configuration required to use `enum` with in AJV strict.
|
||||
- Function `Type.Rec(...)` now takes a required `$id` as the first parameter.
|
||||
- Function `Type.Strict(...)` no longer includes a `$schema`. Callers can now optionally pass `CustomOptions` on `Type.Strict(...)`
|
||||
20
changelog/0.19.0.md
Normal file
20
changelog/0.19.0.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## [0.19.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.19.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- Function `Type.Box(...)` removes `$id` parameter as first argument.
|
||||
- Function `Type.Ref(...)` is now overloaded to support referencing `Type.Box(...)` and `TSchema`.
|
||||
|
||||
Notes:
|
||||
|
||||
This update changes the signature of `Type.Box(...)` and removes the explicit `$id` passing on the first parameter. The `$id` must be passed as an option if the caller wants to reference that type.
|
||||
|
||||
```typescript
|
||||
const T = Type.String({ $id: 'T' })
|
||||
|
||||
const B = Type.Box({ T }, { $id: 'B' })
|
||||
|
||||
const R1 = Type.Ref(T) // const R1 = { $ref: 'T' }
|
||||
|
||||
const R2 = Type.Ref(B, 'T') // const R2 = { $ref: 'B#/definitions/T' }
|
||||
```
|
||||
17
changelog/0.20.0.md
Normal file
17
changelog/0.20.0.md
Normal file
@@ -0,0 +1,17 @@
|
||||
## [0.20.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.20.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- Function `Type.Rec(...)` signature change.
|
||||
- Minor documentation updates.
|
||||
|
||||
Notes:
|
||||
|
||||
The `Type.Rec(...)` function signature has been changed to allow passing the `$id` as a custom option. This is to align `Type.Rec(...)` with other functions that accept `$id` as an option. `Type.Rec(...)` can work with or without an explicit `$id`, but it is recommend to specify one if the recursive type is nested in an outer schema.
|
||||
|
||||
```typescript
|
||||
const Node = Type.Rec(Self => Type.Object({
|
||||
id: Type.String(),
|
||||
nodes: Type.Array(Self)
|
||||
}), { $id: 'Node' })
|
||||
```
|
||||
5
changelog/0.20.1.md
Normal file
5
changelog/0.20.1.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## [0.20.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.20.1)
|
||||
|
||||
Updates:
|
||||
|
||||
- TypeBox mandates TypeScript compiler version `4.3.5` and above.
|
||||
7
changelog/0.21.0.md
Normal file
7
changelog/0.21.0.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## [0.21.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.21.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- TypeBox static inference has been updated inline with additional inference constraints added in TypeScript 4.5. All types now include a phantom `_infer` property which contains the inference TS type for a given schema. The type of this property is inferred at the construction of the schema, and referenced directly via `Static<T>`.
|
||||
- `Type.Box(...)` has been renamed to `Type.Namespace(...)` to draw an analogy with XML's `xmlns` XSD types.
|
||||
|
||||
26
changelog/0.21.2.md
Normal file
26
changelog/0.21.2.md
Normal file
@@ -0,0 +1,26 @@
|
||||
## [0.21.2](https://www.npmjs.com/package/@sinclair/typebox/v/0.21.2)
|
||||
|
||||
Updates:
|
||||
|
||||
- TypeBox now correctly infers for nested union and intersect types.
|
||||
|
||||
Before
|
||||
|
||||
```typescript
|
||||
const A = Type.Object({ a: Type.String() })
|
||||
const B = Type.Object({ b: Type.String() })
|
||||
const C = Type.Object({ c: Type.String() })
|
||||
const T = Type.Intersect([A, Type.Union([B, C])])
|
||||
|
||||
// type T = { a: string } & { b: string } & { c: string }
|
||||
```
|
||||
After
|
||||
|
||||
```typescript
|
||||
const A = Type.Object({ a: Type.String() })
|
||||
const B = Type.Object({ b: Type.String() })
|
||||
const C = Type.Object({ c: Type.String() })
|
||||
const T = Type.Intersect([A, Type.Union([B, C])])
|
||||
|
||||
// type T = { a: string } & ({ b: string } | { c: string })
|
||||
```
|
||||
8
changelog/0.22.0.md
Normal file
8
changelog/0.22.0.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## [0.22.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.22.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- The type `TSchema` is now expressed as an HKT compatible interface. All types now extend the `TSchema` interface and are themselves also expressed as interfaces. This work was undertaken to explore recursive type aliases in future releases.
|
||||
- The phantom property `_infer` has been renamed to `$static`. Callers should not interact with this property as it will always be `undefined` and used exclusively for optimizing type inference in TypeScript 4.5 and above.
|
||||
- TypeBox re-adds the feature to deeply introspect schema properties. This feature was temporarily removed on the `0.21.0` update to resolve deep instantiation errors on TypeScript 4.5.
|
||||
- The `Type.Box(...)` and `Type.Rec(...)` functions internally rename the property `definitions` to `$defs` inline with JSON schema draft 2019-09 conventions. Reference [here](https://opis.io/json-schema/2.x/definitions.html).
|
||||
8
changelog/0.23.0.md
Normal file
8
changelog/0.23.0.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## [0.23.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- The types `Type.Namespace(...)` and `Type.Ref(...)` are promoted to `Standard`.
|
||||
- TypeBox now includes an additional type named `TRef<...>` that is returned on calls to `Type.Ref(...)`. The `TRef<...>` includes a new `RefKind` symbol for introspection of the reference type.
|
||||
- TypeBox now maintains an internal dictionary of all schemas passed that contain an `$id` property. This dictionary is checked whenever a user attempts to reference a type and will throw if attempting to reference a target schema with no `$id`.
|
||||
- The types `Type.Partial(...)`, `Type.Required(...)`, `Type.Omit()` and `Type.Pick(...)` now support reference types. Note that when using these functions with references, TypeBox will replicate the source schema and apply the necessary modifiers to the replication.
|
||||
5
changelog/0.23.1.md
Normal file
5
changelog/0.23.1.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## [0.23.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.1)
|
||||
|
||||
Updates:
|
||||
|
||||
- The `Type.KeyOf(...)` type can now accept references of `Type.Ref(TObject)`
|
||||
5
changelog/0.23.3.md
Normal file
5
changelog/0.23.3.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## [0.23.3](https://www.npmjs.com/package/@sinclair/typebox/v/0.23.3)
|
||||
|
||||
Updates:
|
||||
|
||||
- Fix: Rename BoxKind to NamespaceKind
|
||||
28
changelog/0.24.0.md
Normal file
28
changelog/0.24.0.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## [0.24.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.0)
|
||||
|
||||
Changes:
|
||||
|
||||
- The `kind` and `modifier` keywords are now expressed as symbol keys. This change allows AJV to leverage TypeBox schemas directly without explicit configuration of `kind` and `modifier` in strict mode.
|
||||
- `Type.Intersect([...])` now returns a composite `TObject` instead of a `allOf` schema representation. This change allows intersected types to be leveraged in calls to `Omit`, `Pick`, `Partial`, `Required`.
|
||||
- `Type.Void(...)` now generates a `{ type: null }` schema representation. This is principally used for RPC implementations where a RPC target function needs to respond with a serializable value for `void` return.
|
||||
- `Type.Rec(...)` renamed to `Type.Recursive(...)` and now supports non-mutual recursive type inference.
|
||||
|
||||
Added:
|
||||
|
||||
- `Type.Unsafe<T>(...)`. This type enables custom schema representations whose static type is informed by generic type T.
|
||||
- `Type.Uint8Array(...)`. This is a non-standard schema that can be configured on AJV to enable binary buffer range validation.
|
||||
- Added optional extended `design` property on all schema options. This property can be used to specify design time metadata when rendering forms.
|
||||
|
||||
Compiler:
|
||||
|
||||
- TypeBox now provides an optional experimental type compiler that can be used to validate types without AJV. This compiler is not a standard JSON schema compiler and will only compile TypeBox's known schema representations. For full JSON schema validation, AJV should still be the preference. This compiler is a work in progress.
|
||||
|
||||
Value:
|
||||
|
||||
- TypeBox now provides a value generator that can generate default values from TypeBox types.
|
||||
|
||||
Breaking Changes:
|
||||
|
||||
- `Type.Intersect(...)` is constrained to accept types of `TObject` only.
|
||||
- `Type.Namespace(...)` has been removed.
|
||||
- The types `TUnion`, `TEnum`, `KeyOf` and `TLiteral<TString>[]` are all now expressed via `allOf`. For Open API users, Please consider `Type.Unsafe()` to express `enum` string union representations. Documentation on using `Type.Unsafe()` can be found [here](https://github.com/sinclairzx81/typebox#Unsafe-Types)
|
||||
10
changelog/0.24.15.md
Normal file
10
changelog/0.24.15.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## [0.24.15](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.15)
|
||||
|
||||
Added:
|
||||
- `Conditional.Extends(...)` This enables TypeBox to conditionally map types inline with TypeScripts structural equivalence checks. Tested against TypeScript 4.7.4.
|
||||
- `Conditional.Extract(...)` Which analogs TypeScripts `Extract<...>` utility type. Additional information [here](https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union)
|
||||
- `Conditional.Exclude(...)` Which analogs TypeScripts `Exclude<...>` utility type. Additional information [here](https://www.typescriptlang.org/docs/handbook/utility-types.html#excludeuniontype-excludedmembers)
|
||||
- `Type.Parameters(...)` Returns the parameters of a `TFunction` as a `TTuple`
|
||||
- `Type.ReturnType(...)` Returns the return type schema of a `TFunction`
|
||||
- `Type.ConstructorParameters(...)` Returns the parameters of a `TConstructor` as a `TTuple`
|
||||
- `Type.InstanceType(...)` Returns the instance type schema of a `TConstructor`
|
||||
18
changelog/0.24.44.md
Normal file
18
changelog/0.24.44.md
Normal file
@@ -0,0 +1,18 @@
|
||||
## [0.24.44](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.44)
|
||||
|
||||
Updates:
|
||||
- [189](https://github.com/sinclairzx81/typebox/pull/189) Both `Value.Error(T, value)` and `TypeCheck.Error(value)` now return an iterator for validation errors.
|
||||
- [191](https://github.com/sinclairzx81/typebox/pull/191) TypeBox now provides a `TypeGuard` API that can be used to check the structural validity of TypeBox type. The TypeGuard can be used in reflection / code generation scenarios to resolve the appropriate inner `TSchema` type while traversing a outer type.
|
||||
- [197](https://github.com/sinclairzx81/typebox/pull/197) TypeBox now implements conditional runtime type mapping. This functionality is offered as separate import for the `0.24.0` release but may be provided as standard type in later releases. This API enables `type T = Foo extends Bar ? true : false` conditional checks to be implemented at runtime. This API also provides the `Exclude` and `Extract` utility types which are implemented through conditional types in TypeScript.
|
||||
- [199](https://github.com/sinclairzx81/typebox/pull/199) TypeBox now provides better support for variadic function and constructor signatures. Currently variadic types are mapped as `Tuple` types.
|
||||
- [200](https://github.com/sinclairzx81/typebox/pull/200) The types `TPick` and `TOmit` now support types of `TUnion<TLiteral<string>[]>` to be used to select properties. Additionally, `KeyOf` now returns `TUnion<TLiteral<string>[]>`, allowing `KeyOf` schemas to be passed to `TPick` and `TOmit`.
|
||||
- [214](https://github.com/sinclairzx81/typebox/pull/214) TypeBox now provides better support for i18n. To achieve this, TypeBox includes fixed mappable error codes on the `ValueError` type. These codes can be used by external implementers to create localized error messages. TypeBox may include localized error codes as an optional import in future releases.
|
||||
- [288](https://github.com/sinclairzx81/typebox/pull/228) TypeBox now allows users to implement custom string validator formats. These formats are internally shared between the `Value` and `TypeCompiler` API's. TypeBox does not currently provide any built in formats, however the standard expected set (email, uuid, uri, etc) may be provided via optional import (inline with ajv-formats usage)
|
||||
- [229](https://github.com/sinclairzx81/typebox/pull/229) The `Value.Cast()` function now implements automatic coercion of string, number and Boolean types.
|
||||
- [231](https://github.com/sinclairzx81/typebox/pull/231) TypeBox provides a new `Value.Diff<T>()` and `Value.Patch<T>()` utility API for JavaScript values. This API is intended to provide a basis for the efficient transmission of state updates across a network. This API can diff any JavaScript value (typed or untyped) but is recommended to be used in conjunction with a formal static type.
|
||||
- [236](https://github.com/sinclairzx81/typebox/pull/236) TypeBox now implements the `TNever` type. This type is analogous to TypeScript's `never` type and is used in instances a composition results in a non-reconcilable type. Currently this type is implemented for empty `TUnion<[]>` types only. Future releases may utilize this type for planned updates to `TIntersect` (for example `string & number` resolves to `never`)
|
||||
- [241](https://github.com/sinclairzx81/typebox/pull/241) [247](https://github.com/sinclairzx81/typebox/pull/247) TypeBox now exposes a ValuePointer API that can be used to mutate a value via an RFC6901 JSON Pointer. Previously this functionality was internally used by `Value.Diff()` and `Value.Patch()` functions but is now offered as an optional import for implementations that need to update values manually through pointer references.
|
||||
|
||||
Additional:
|
||||
|
||||
- This project now includes two reference code generation utilities that can be used in custom build tooling. The first is `TypeScriptCodeGen` which will remap TypeScript `interface` and `type` definitions to TypeBox types. The second is `TypeBoxCodeGen` which will map existing TypeBox types into TypeScript type definitions. These implementations are not expected to be part of the TypeBox package, but users are free to clone and enhance them in their existing tool chains. Reference implementations can be found https://github.com/sinclairzx81/typebox/tree/master/codegen
|
||||
9
changelog/0.24.49.md
Normal file
9
changelog/0.24.49.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## [0.24.49](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.49)
|
||||
|
||||
Updates:
|
||||
|
||||
- [264](https://github.com/sinclairzx81/typebox/pull/264) TypeBox now provides preliminary support for non Boolean `additionalProperties`. This allows existing `TObject` schemas to be augmented with additional properties of a known type.
|
||||
|
||||
Additional:
|
||||
|
||||
- TypeBox provides an additional reference `codegen` module for generating raw JSON Schema from TypeScript types via the TS compiler API. This generator may be used in future tooling.
|
||||
20
changelog/0.24.6.md
Normal file
20
changelog/0.24.6.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## [0.24.6](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.6)
|
||||
|
||||
Added:
|
||||
|
||||
- TypeBox now offers a `TypeGuard` module for structurally checking TypeBox schematics. This module can be used in runtime type reflection scenarios where it's helpful to test a schema is of a particular form. This module can be imported under the `@sinclair/typebox/guard` import path.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
|
||||
const T: any = {} // T is any
|
||||
|
||||
const { type } = T // unsafe: type is any
|
||||
|
||||
if(TypeGuard.TString(T)) {
|
||||
|
||||
const { type } = T // safe: type is 'string'
|
||||
}
|
||||
```
|
||||
6
changelog/0.24.8.md
Normal file
6
changelog/0.24.8.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## [0.24.8](https://www.npmjs.com/package/@sinclair/typebox/v/0.24.8)
|
||||
|
||||
Added:
|
||||
- `Value.Cast(T, value)` structurally casts a value into another form while retaining information within the original value.
|
||||
- `Value.Check(T, value)` provides slow dynamic type checking for values. For performance, one should consider the `TypeCompiler` or `Ajv` validator.
|
||||
- `Value.Errors(T, value)` returns an iterator of errors found in a given value.
|
||||
7
changelog/0.25.0.md
Normal file
7
changelog/0.25.0.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## [0.25.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.0)
|
||||
|
||||
Updates:
|
||||
|
||||
- [271](https://github.com/sinclairzx81/typebox/pull/271) Adds a new non-standard `Type.Date()` type. This type joins the existing `Type.UInt8Array()` as a promoted extended type used to represent core JavaScript primitives. It's inclusion was prompted by end user requirements to validate Date objects prior to writing them to Date supported API's and where serialization of the Date object is handled internally by the API.
|
||||
|
||||
- [271](https://github.com/sinclairzx81/typebox/pull/271) Redesign of Extended Type representations. Extended types been updated to provide external validators (such as Ajv) additional standard proporties to use when defining the custom schema. These properties are `instanceOf` (used for validating a class `object` instances), and `typeOf` (when validating `value` types). Information on configuring AJV for these properties can be found in the AJV section of the TypeBox readme.
|
||||
5
changelog/0.25.10.md
Normal file
5
changelog/0.25.10.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## [0.25.10](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.10)
|
||||
|
||||
Updates:
|
||||
|
||||
- [283](https://github.com/sinclairzx81/typebox/pull/283) Updates the custom type validator callback signature to accept a schema instance. The schema instance may include additional constraints (such as options) that may be used during the validation process. `Custom.Set('<Kind>', (schema, value) => { ... })`.
|
||||
5
changelog/0.25.11.md
Normal file
5
changelog/0.25.11.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## [0.25.11](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.11)
|
||||
|
||||
Updates:
|
||||
|
||||
- [286](https://github.com/sinclairzx81/typebox/pull/286) implements a FNV1A-64 non cryptographic hashing function in TypeBox. This function should not be used in place of cryptographic hashing functions, rather it's purpose is to provide relatively fast, hashing mechanism to assist with checks for arrays with uniqueItems constraints, specifically for cases where the array may contains reference values (such as objects, arrays, Dates and Uint8Array). This function is provided via `Value.Hash()` for convenience as the hash may be useful to generate a numeric identifier for values (with some considerations to React array rendering in absence of key or identifier)
|
||||
5
changelog/0.25.18.md
Normal file
5
changelog/0.25.18.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## [0.25.18](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.18)
|
||||
|
||||
Updates:
|
||||
|
||||
- [307](https://github.com/sinclairzx81/typebox/pull/307) implements date conversion when casting values with `Value.Cast(Type.Date(), ...)`. Castable values include numbers (interpretted as timestamps) and iso8601 string values. UNCASTABLE values will result in dates with values of `1970-01-01T00:00:00.000Z`. This version also includes more robust checks for Dates initialized with invalid values.
|
||||
5
changelog/0.25.22.md
Normal file
5
changelog/0.25.22.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## [0.25.22](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.22)
|
||||
|
||||
Updates:
|
||||
|
||||
- [323](https://github.com/sinclairzx81/typebox/pull/323) adds compiler support for UTF-16 (unicode) characters for schema identifiers.
|
||||
10
changelog/0.25.23.md
Normal file
10
changelog/0.25.23.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## [0.25.23](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.23)
|
||||
|
||||
Updates:
|
||||
|
||||
- [324](https://github.com/sinclairzx81/typebox/pull/324) TypeScript Language Service now presents JSDoc comments when inferring static object properties. (IntelliSense)
|
||||
- [325](https://github.com/sinclairzx81/typebox/pull/325) Additional property inference optimizations.
|
||||
|
||||
Additional:
|
||||
|
||||
- Huge thank you to GITHUB user [stevezhu](https://github.com/stevezhu) for these excellent contributions.
|
||||
10
changelog/0.25.24.md
Normal file
10
changelog/0.25.24.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## [0.25.24](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.23)
|
||||
|
||||
Updates:
|
||||
|
||||
- [331](https://github.com/sinclairzx81/typebox/pull/331) Implements an additional check specific to property types of `required & undefined`. This to ensure the property key exists when the property value extends `undefined`.
|
||||
- [331](https://github.com/sinclairzx81/typebox/pull/331) Documentation updates for AJV and TypeCompiler
|
||||
|
||||
Additional:
|
||||
|
||||
- [331](https://github.com/sinclairzx81/typebox/pull/331) Remove unused recursive code paths for create and cast.
|
||||
5
changelog/0.25.9.md
Normal file
5
changelog/0.25.9.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## [0.25.9](https://www.npmjs.com/package/@sinclair/typebox/v/0.25.9)
|
||||
|
||||
Updates:
|
||||
|
||||
- [282](https://github.com/sinclairzx81/typebox/pull/282) TypeBox now supports custom types. These types require the user to specify a custom `[Kind]` string on the type. Custom types can be registered via `Custom.Set('<Kind>', (value) => { ... })` which allow the TypeCompiler and Value API's to make use of user defined validation logic.
|
||||
394
changelog/0.26.0.md
Normal file
394
changelog/0.26.0.md
Normal file
@@ -0,0 +1,394 @@
|
||||
## [0.26.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.26.0)
|
||||
|
||||
## Overview
|
||||
|
||||
TypeBox now provides "runtime conditional types" (formally the `Conditional` module) as standard on `Type.*`. Additional updates in this revision include automatic union and intersection unwrap, universal support for utility types, several ergonomic enhancements and additional options for framework integrators. This revision also carries out a number an internal refactorings to reduce the amount of submodule imports.
|
||||
|
||||
Revision 0.26.0 is a milestone release for the TypeBox project and requires a minor semver update.
|
||||
|
||||
## Contents
|
||||
|
||||
- Enhancements
|
||||
- [Standard Type Builder](#Standard-Type-Builder)
|
||||
- [Automatic Unwrap for Union and Intersect](#Automatic-Unwrap-for-Union-and-Intersect)
|
||||
- [Intersect and Union now Compose](#Intersect-and-Union-now-Compose)
|
||||
- [Runtime Conditional Types](#Runtime-Conditional-Types)
|
||||
- [Value Convert](#Value-Convert)
|
||||
- [Error Iterator](#Error-Iterator)
|
||||
- [Codegen without JIT](#Codegen-without-JIT)
|
||||
- [Standard Type (Composite)](#Standard-Type-Composite)
|
||||
- [Standard Type (Not)](#Standard-Type-Not)
|
||||
- [Extended Type (BigInt)](#Extended-Type-BigInt)
|
||||
- [Extended Type (Symbol)](#Extended-Type-Symbol)
|
||||
- Breaking
|
||||
- [Minimum TypeScript Version](#Minimum-TypeScript-Version)
|
||||
- [Intersect Schema Representation](#Intersect-Schema-Representation)
|
||||
- [Never Schema Representation](#Never-Schema-Representation)
|
||||
- [Value Cast and Convert](#Value-Cast-and-Convert)
|
||||
- [Moved TypeGuard Module](#Moved-TypeGuard-Module)
|
||||
- [Format Renamed to FormatRegistry](#Format-Renamed-to-FormatRegistry)
|
||||
- [Custom Renamed to TypeRegistry](#Custom-Renamed-to-TypeRegistry)
|
||||
|
||||
<a name="Standard-Type-Builder"></a>
|
||||
|
||||
## Standard Type Builder
|
||||
|
||||
Revision 0.26.0 exports a new type builder called `StandardType`. This builder only allows for the construction JSON Schema compliant types by omitting all Extended types.
|
||||
|
||||
```typescript
|
||||
import { StandardType as Type, Static } from '@sinclair/typebox'
|
||||
|
||||
const T = Type.Date() // error: no such function
|
||||
```
|
||||
|
||||
<a name="Automatic-Unwrap-for-Union-and-Intersect"></a>
|
||||
|
||||
## Automatic Unwrap for Union and Intersect
|
||||
|
||||
Revision 0.26.0 will automatically unwrap unions and intersections for the following cases.
|
||||
|
||||
```typescript
|
||||
const T1 = Type.Union([Type.String(), Type.Number()]) // TUnion<[TString, TNumber]>
|
||||
|
||||
const T2 = Type.Union([Type.String()]) // TString
|
||||
|
||||
const T3 = Type.Union([]) // TNever
|
||||
```
|
||||
|
||||
<a name="Intersect-and-Union-now-Compose"></a>
|
||||
|
||||
## Intersect and Union now Compose
|
||||
|
||||
Revision 0.26.0 re-enables support for union and intersection type composition. These types are also made compatible with `Pick`, `Omit`, `Partial`, `Required` and `KeyOf` utility types.
|
||||
|
||||
```typescript
|
||||
const A = Type.Object({ type: Type.Literal('A') })
|
||||
const B = Type.Object({ type: Type.Literal('B') })
|
||||
const C = Type.Object({ type: Type.Literal('C') })
|
||||
|
||||
const Union = Type.Union([A, B, C])
|
||||
|
||||
const Extended = Type.Object({
|
||||
x: Type.Number(),
|
||||
y: Type.Number(),
|
||||
z: Type.Number()
|
||||
})
|
||||
|
||||
const T = Type.Intersect([Union, Extended]) // type T = ({
|
||||
// type: "A";
|
||||
// } | {
|
||||
// type: "B";
|
||||
// } | {
|
||||
// type: "C";
|
||||
// }) & {
|
||||
// x: number;
|
||||
// y: number;
|
||||
// z: number;
|
||||
// }
|
||||
|
||||
const K = Type.KeyOf(T) // type K = "type" | "x" | "y" | "z"
|
||||
|
||||
const P = Type.Pick(T, ['type', 'x']) // type P = ({
|
||||
// type: "A";
|
||||
// } | {
|
||||
// type: "B";
|
||||
// } | {
|
||||
// type: "C";
|
||||
// }) & {
|
||||
// x: number;
|
||||
// }
|
||||
|
||||
const O = Type.Partial(P) // type O = ({
|
||||
// type?: "A" | undefined;
|
||||
// } | {
|
||||
// type?: "B" | undefined;
|
||||
// } | {
|
||||
// type?: "C" | undefined;
|
||||
// }) & {
|
||||
// x?: number | undefined;
|
||||
// }
|
||||
```
|
||||
|
||||
<a name="Runtime-Conditional-Types"></a>
|
||||
|
||||
## Runtime Conditional Types
|
||||
|
||||
Revision 0.26.0 adds the runtime conditional types `Extends`, `Extract` and `Exclude` as standard.
|
||||
|
||||
#### TypeScript
|
||||
|
||||
```typescript
|
||||
type T0 = string extends number ? true : false
|
||||
// ^ false
|
||||
type T1 = Extract<string | number, number>
|
||||
// ^ number
|
||||
type T2 = Exclude<string | number, number>
|
||||
// ^ string
|
||||
```
|
||||
|
||||
#### TypeBox
|
||||
```typescript
|
||||
const T0 = Type.Extends(Type.String(), Type.Number(), Type.Literal(true), Type.Literal(false))
|
||||
// ^ TLiteral<false>
|
||||
const T1 = Type.Extract(Type.Union([Type.String(), Type.Number()]), Type.Number())
|
||||
// ^ TNumber
|
||||
const T2 = Type.Exclude(Type.Union([Type.String(), Type.Number()]), Type.Number())
|
||||
// ^ TString<string>
|
||||
```
|
||||
|
||||
<a name="Value-Convert"></a>
|
||||
|
||||
## Value Convert
|
||||
|
||||
Revision 0.26.0 adds a new `Convert` function to the `Value.*` module. This function will perform a type coercion for any value mismatched to its type if a reasonable conversion is possible.
|
||||
|
||||
```typescript
|
||||
const T = Type.Number()
|
||||
|
||||
const A = Value.Convert(T, '42') // const A: unknown = 42 - ... Convert(...) will return `unknown`
|
||||
|
||||
const B = Value.Check(T, A) // const B = true - ... so should be checked
|
||||
```
|
||||
|
||||
<a name="Error-Iterator"></a>
|
||||
|
||||
## Error Iterator
|
||||
|
||||
Revision 0.26.0 now returns a `ValueErrorIterator` for `.Errors(...)`. This iterator provides a utility function to obtain the first error only. To obtain all errors, continue to use `for-of` enumeration or array spread syntax.
|
||||
|
||||
```typescript
|
||||
const T = Type.Number()
|
||||
|
||||
const First = Value.Errors(T, 'foo').First() // const First = { path: '', message: 'Expected number', ... }
|
||||
|
||||
const All = [...Value.Errors(T, 'foo')] // const All = [{ path: '', message: 'Expected number', ... }]
|
||||
```
|
||||
|
||||
|
||||
<a name="Codegen-without-JIT"></a>
|
||||
|
||||
## Codegen without JIT
|
||||
|
||||
Revision 0.26.0 adds a `.Code()` function to the `TypeCompiler` to enable code generation without JIT evaluation.
|
||||
|
||||
```typescript
|
||||
import { TypeCompiler } from '@sinclair/typebox/compiler'
|
||||
|
||||
const T = Type.Object({
|
||||
x: Type.Number(),
|
||||
y: Type.Number()
|
||||
})
|
||||
|
||||
const C = TypeCompiler.Code(T) // return function check(value) {
|
||||
// return (
|
||||
// (typeof value === 'object' && value !== null) &&
|
||||
// !Array.isArray(value) &&
|
||||
// typeof value.x === 'number' &&
|
||||
// Number.isFinite(value.x) &&
|
||||
// typeof value.y === 'number' &&
|
||||
// Number.isFinite(value.y)
|
||||
// )
|
||||
// }
|
||||
```
|
||||
|
||||
<a name="Standard-Type-Not"></a>
|
||||
|
||||
## Standard Type (Not)
|
||||
|
||||
Revision 0.26.0 introduces the `Not` standard type. This type allows for the inversion of assertion logic which can be useful to narrow for broader types.
|
||||
|
||||
#### Example 1
|
||||
|
||||
```typescript
|
||||
const T = Type.Not(Type.String({ pattern: 'A|B|C' }), Type.String())
|
||||
|
||||
Value.Check(T, 'A') // false
|
||||
Value.Check(T, 'B') // false
|
||||
Value.Check(T, 'C') // false
|
||||
Value.Check(T, 'D') // true
|
||||
```
|
||||
|
||||
#### Example 2
|
||||
|
||||
```typescript
|
||||
const Even = Type.Number({ multipleOf: 2 })
|
||||
const Odd = Type.Not(Even, Type.Number())
|
||||
|
||||
Value.Check(Even, 0) // true
|
||||
Value.Check(Even, 1) // false
|
||||
Value.Check(Even, 2) // true
|
||||
|
||||
Value.Check(Odd, 0) // false
|
||||
Value.Check(Odd, 1) // true
|
||||
Value.Check(Odd, 2) // false
|
||||
```
|
||||
|
||||
<a name="Extended-Type-Composite"></a>
|
||||
|
||||
## Standard Type (Composite)
|
||||
|
||||
Revision 0.26.0 includes a new `Composite` standard type. This type will combine an array of `TObject[]` into a `TObject` by taking a union of any overlapping properties.
|
||||
|
||||
```typescript
|
||||
const A = Type.Object({ type: Type.Literal('A') })
|
||||
|
||||
const B = Type.Object({ type: Type.Literal('B') })
|
||||
|
||||
const C = Type.Object({ type: Type.Literal('C'), value: Type.Number() })
|
||||
|
||||
const T = Type.Composite([A, B, C]) // type T = {
|
||||
// type: 'A' | 'B' | 'C'
|
||||
// value: number
|
||||
// }
|
||||
```
|
||||
|
||||
<a name="Extended-Type-Symbol"></a>
|
||||
|
||||
## Extended Type (Symbol)
|
||||
|
||||
Revision 0.26.0 provides provisional support for `Symbol` type validation.
|
||||
|
||||
```typescript
|
||||
const T = Type.Symbol()
|
||||
|
||||
Value.Check(A, Symbol('Foo')) // true
|
||||
```
|
||||
|
||||
<a name="Extended-Type-BigInt"></a>
|
||||
|
||||
## Extended Type (BigInt)
|
||||
|
||||
Revision 0.26.0 provides provisional support for `BigInt` type validation.
|
||||
|
||||
```typescript
|
||||
const T = Type.BigInt({ minimum: 10n })
|
||||
|
||||
Value.Check(B, 1_000_000n) // true
|
||||
```
|
||||
|
||||
<a name="Breaking"></a>
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
The following are breaking changed in Revision 0.26.0
|
||||
|
||||
<a name="Minimum-TypeScript-Version"></a>
|
||||
|
||||
## Minimum TypeScript Version
|
||||
|
||||
Revision 0.26.0 requires a minimum recommended TypeScript version of `4.2.3`. Version `4.1.5` is no longer supported.
|
||||
|
||||
<a name="Intersect-Schema-Representation"></a>
|
||||
|
||||
## Intersect Schema Representation
|
||||
|
||||
Revision 0.26.0 changes the schema representation for `Intersect`. Revision 0.25.0 would construct a composite `object` type, in 0.26.0, `Intersect` is expressed as `anyOf`. If upgrading, consider using `Type.Composite(...)` to return backwards compatible representations.
|
||||
|
||||
#### Intersect 0.25.0
|
||||
|
||||
```typescript
|
||||
const T = Type.Intersect([ // const U = {
|
||||
Type.Object({ // type: 'object',
|
||||
x: Type.Number(), // required: ['x', 'y'],
|
||||
}), // properties: {
|
||||
Type.Object({ // x: {
|
||||
y: Type.Number(), // type: 'number'
|
||||
}) // },
|
||||
]) // y: {
|
||||
// type: 'number'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
```
|
||||
#### Intersect 0.26.0
|
||||
|
||||
```typescript
|
||||
const T = Type.Intersect([ // const U = {
|
||||
Type.Object({ // type: 'object',
|
||||
x: Type.Number(), // allOf: [{
|
||||
}), // type: 'object',
|
||||
Type.Object({ // required: [ 'x' ],
|
||||
y: Type.Number(), // properties: {
|
||||
}) // x: { type: 'number' }
|
||||
]) // }
|
||||
// }, {
|
||||
// type: 'object',
|
||||
// required: ['y'],
|
||||
// properties: {
|
||||
// y: { type: 'number' }
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
```
|
||||
|
||||
<a name="Never-Schema-Representation"></a>
|
||||
|
||||
## Never Schema Representation
|
||||
|
||||
Revision 0.26.0 simplifies the representation for `TNever`. Previous versions of TypeBox used an illogical intersection of Boolean constants via `allOf`. In 0.26.0, `never` is expressed as a `not` schema of type `any`.
|
||||
|
||||
#### Intersect 0.25.0
|
||||
|
||||
```typescript
|
||||
const T = Type.Never() // const T = {
|
||||
// allOf: [
|
||||
// { type: 'boolean', const: true }
|
||||
// { type: 'boolean', const: false }
|
||||
// ]
|
||||
// }
|
||||
```
|
||||
#### Intersect 0.26.0
|
||||
|
||||
```typescript
|
||||
const T = Type.Never() // const T = { not: {} }
|
||||
```
|
||||
|
||||
<a name="Value-Cast-and-Convert"></a>
|
||||
|
||||
## Value Cast and Convert
|
||||
|
||||
Revision 0.26.0 removes the `Cast` functions ability to coerce values. Use the new `Convert` function prior to `Cast`.
|
||||
|
||||
```typescript
|
||||
const T = Type.Number()
|
||||
|
||||
const V = Value.Cast(T, '42') // const V = 42 - 0.25.0 coerces to 42
|
||||
|
||||
const V = Value.Cast(T, Value.Convert(T, '42')) // const V = 42 - 0.26.0 convert then cast
|
||||
```
|
||||
|
||||
<a name="Movied TypeGuard Module"></a>
|
||||
|
||||
## Moved TypeGuard Module
|
||||
|
||||
The `TypeGuard` is now imported via the `@sinclair/typebox` module. This move is due to the TypeBox compositor internally using the guard when constructing types.
|
||||
|
||||
```typescript
|
||||
import { TypeGuard } from '@sinclair/typebox/guard' // 0.25.0
|
||||
|
||||
import { TypeGuard } from '@sinclair/typebox' // 0.26.0
|
||||
```
|
||||
|
||||
<a name="Format-Renamed-to-FormatRegistry"></a>
|
||||
|
||||
## Format Renamed to FormatRegistry
|
||||
|
||||
The `Format` module has been renamed to `FormatRegistry` and moved to the `typebox.ts` module.
|
||||
|
||||
```typescript
|
||||
import { Format } from '@sinclair/typebox/format' // 0.25.0
|
||||
|
||||
import { FormatRegistry } from '@sinclair/typebox' // 0.26.0
|
||||
```
|
||||
|
||||
<a name="Custom-Renamed-to-TypeRegistry"></a>
|
||||
|
||||
## Custom Renamed to TypeRegistry
|
||||
|
||||
The `Format` module has been renamed to `FormatRegistry` and moved to the `typebox.ts` module.
|
||||
|
||||
```typescript
|
||||
import { Custom } from '@sinclair/typebox/format' // 0.25.0
|
||||
|
||||
import { TypeRegistry } from '@sinclair/typebox' // 0.26.0
|
||||
```
|
||||
@@ -1,53 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/codegen
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { TSchema } from '@sinclair/typebox'
|
||||
import { TypeBoxCodegen } from './typebox'
|
||||
import { TypeScriptCodegen } from './typescript'
|
||||
import { JsonSchemaCodegen } from './jsonschema'
|
||||
import { ZodCodegen, ZodCodegenOptions } from './zod'
|
||||
|
||||
export namespace Codegen {
|
||||
/** Generates TypeScript type definitions from TypeBox types */
|
||||
export function TypeScript(schema: TSchema, references: TSchema[] = []): string {
|
||||
return TypeScriptCodegen.Generate(schema, references)
|
||||
}
|
||||
/** Generates Zod type definitions from TypeBox types */
|
||||
export function Zod(schema: TSchema, references: TSchema[] = [], options: ZodCodegenOptions = { imports: true, exports: false }): string {
|
||||
return ZodCodegen.Generate(schema, references, options)
|
||||
}
|
||||
/** Generates TypeBox type definitions from TypeScript code */
|
||||
export function TypeBox(code: string): string {
|
||||
return TypeBoxCodegen.Generate(code)
|
||||
}
|
||||
|
||||
/** Generates JsonSchema definitions from TypeScript code */
|
||||
export function JsonSchema(code: string): string {
|
||||
return JsonSchemaCodegen.Generate(code)
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,7 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export * from './codegen'
|
||||
export { TypeBoxToTypeScript } from './typebox-to-typescript'
|
||||
export { TypeBoxToZod } from './typebox-to-zod'
|
||||
export { TypeScriptToJsonSchema } from './typescript-to-json-schema'
|
||||
export { TypeScriptToTypeBox } from './typescript-to-typebox'
|
||||
|
||||
@@ -27,39 +27,35 @@ THE SOFTWARE.
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { Formatter } from './formatter'
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
import * as Types from '@sinclair/typebox'
|
||||
|
||||
export namespace TypeScriptCodegen {
|
||||
export namespace TypeBoxToTypeScript {
|
||||
function Any(schema: Types.TAny) {
|
||||
return 'any'
|
||||
}
|
||||
|
||||
function Array(schema: Types.TArray) {
|
||||
const items = Visit(schema.items)
|
||||
return `Array<${items}>`
|
||||
}
|
||||
|
||||
function Boolean(schema: Types.TBoolean) {
|
||||
return 'boolean'
|
||||
}
|
||||
|
||||
function Constructor(schema: Types.TConstructor) {
|
||||
const params = schema.parameters.map((param) => Visit(param)).join(', ')
|
||||
const returns = Visit(schema.returns)
|
||||
return `new (${params}) => ${returns}`
|
||||
}
|
||||
|
||||
function Function(schema: Types.TFunction) {
|
||||
const params = schema.parameters.map((param) => Visit(param)).join(', ')
|
||||
const returns = Visit(schema.returns)
|
||||
return `(${params}) => ${returns}`
|
||||
}
|
||||
|
||||
function Integer(schema: Types.TInteger) {
|
||||
return 'number'
|
||||
}
|
||||
|
||||
function Intersect(schema: Types.TIntersect) {
|
||||
return `(${schema.allOf.map((schema) => Visit(schema)).join(' & ')})`
|
||||
}
|
||||
function Literal(schema: Types.TLiteral) {
|
||||
if (typeof schema.const === 'string') {
|
||||
return `'${schema.const}'`
|
||||
@@ -67,23 +63,18 @@ export namespace TypeScriptCodegen {
|
||||
return `${schema.const}`
|
||||
}
|
||||
}
|
||||
|
||||
function Never(schema: Types.TNever) {
|
||||
return 'never'
|
||||
}
|
||||
|
||||
function Null(schema: Types.TNull) {
|
||||
return 'null'
|
||||
}
|
||||
|
||||
function String(schema: Types.TString) {
|
||||
return 'string'
|
||||
}
|
||||
|
||||
function Number(schema: Types.TNumber) {
|
||||
return 'number'
|
||||
}
|
||||
|
||||
function Object(schema: Types.TObject) {
|
||||
const properties: string = globalThis.Object.entries(schema.properties)
|
||||
.map(([key, value]) => {
|
||||
@@ -92,12 +83,10 @@ export namespace TypeScriptCodegen {
|
||||
.join(',\n')
|
||||
return `{\n${properties}\n}`
|
||||
}
|
||||
|
||||
function Promise(schema: Types.TPromise) {
|
||||
const item = Visit(schema.item)
|
||||
return `Promise<${item}>`
|
||||
}
|
||||
|
||||
function Record(schema: Types.TRecord) {
|
||||
for (const [key, value] of globalThis.Object.entries(schema.patternProperties)) {
|
||||
const type = Visit(value)
|
||||
@@ -107,93 +96,85 @@ export namespace TypeScriptCodegen {
|
||||
return `Record<string, ${type}>`
|
||||
}
|
||||
}
|
||||
throw Error('TypeScriptCodeGen: Unreachable')
|
||||
throw Error('TypeBoxToTypeScript: Unreachable')
|
||||
}
|
||||
|
||||
function Ref(schema: Types.TRef) {
|
||||
return schema.$ref
|
||||
}
|
||||
|
||||
function Self(schema: Types.TSelf) {
|
||||
return schema.$ref
|
||||
}
|
||||
|
||||
function Tuple(schema: Types.TTuple) {
|
||||
if (schema.items === undefined) return `[]`
|
||||
const items = schema.items.map((schema) => Visit(schema)).join(', ')
|
||||
return `[${items}]`
|
||||
}
|
||||
|
||||
function UInt8Array(schema: Types.TUint8Array) {
|
||||
return `Uint8Array`
|
||||
}
|
||||
|
||||
function Undefined(schema: Types.TUndefined) {
|
||||
return `undefined`
|
||||
}
|
||||
|
||||
function Union(schema: Types.TUnion) {
|
||||
return schema.anyOf.map((schema) => Visit(schema)).join(' | ')
|
||||
return `${schema.anyOf.map((schema) => Visit(schema)).join(' | ')}`
|
||||
}
|
||||
|
||||
function Unknown(schema: Types.TUnknown) {
|
||||
return `unknown`
|
||||
}
|
||||
|
||||
function Void(schema: Types.TVoid) {
|
||||
return `void`
|
||||
}
|
||||
|
||||
function Visit(schema: Types.TSchema): string {
|
||||
if (TypeGuard.TAny(schema)) {
|
||||
if (Types.TypeGuard.TAny(schema)) {
|
||||
return Any(schema)
|
||||
} else if (TypeGuard.TArray(schema)) {
|
||||
} else if (Types.TypeGuard.TArray(schema)) {
|
||||
return Array(schema)
|
||||
} else if (TypeGuard.TBoolean(schema)) {
|
||||
} else if (Types.TypeGuard.TBoolean(schema)) {
|
||||
return Boolean(schema)
|
||||
} else if (TypeGuard.TConstructor(schema)) {
|
||||
} else if (Types.TypeGuard.TConstructor(schema)) {
|
||||
return Constructor(schema)
|
||||
} else if (TypeGuard.TFunction(schema)) {
|
||||
} else if (Types.TypeGuard.TFunction(schema)) {
|
||||
return Function(schema)
|
||||
} else if (TypeGuard.TInteger(schema)) {
|
||||
} else if (Types.TypeGuard.TInteger(schema)) {
|
||||
return Integer(schema)
|
||||
} else if (TypeGuard.TLiteral(schema)) {
|
||||
} else if (Types.TypeGuard.TIntersect(schema)) {
|
||||
return Intersect(schema)
|
||||
} else if (Types.TypeGuard.TLiteral(schema)) {
|
||||
return Literal(schema)
|
||||
} else if (TypeGuard.TNever(schema)) {
|
||||
} else if (Types.TypeGuard.TNever(schema)) {
|
||||
return Never(schema)
|
||||
} else if (TypeGuard.TNull(schema)) {
|
||||
} else if (Types.TypeGuard.TNull(schema)) {
|
||||
return Null(schema)
|
||||
} else if (TypeGuard.TNumber(schema)) {
|
||||
} else if (Types.TypeGuard.TNumber(schema)) {
|
||||
return Number(schema)
|
||||
} else if (TypeGuard.TObject(schema)) {
|
||||
} else if (Types.TypeGuard.TObject(schema)) {
|
||||
return Object(schema)
|
||||
} else if (TypeGuard.TPromise(schema)) {
|
||||
} else if (Types.TypeGuard.TPromise(schema)) {
|
||||
return Promise(schema)
|
||||
} else if (TypeGuard.TRecord(schema)) {
|
||||
} else if (Types.TypeGuard.TRecord(schema)) {
|
||||
return Record(schema)
|
||||
} else if (TypeGuard.TRef(schema)) {
|
||||
} else if (Types.TypeGuard.TRef(schema)) {
|
||||
return Ref(schema)
|
||||
} else if (TypeGuard.TSelf(schema)) {
|
||||
} else if (Types.TypeGuard.TSelf(schema)) {
|
||||
return Self(schema)
|
||||
} else if (TypeGuard.TString(schema)) {
|
||||
} else if (Types.TypeGuard.TString(schema)) {
|
||||
return String(schema)
|
||||
} else if (TypeGuard.TTuple(schema)) {
|
||||
} else if (Types.TypeGuard.TTuple(schema)) {
|
||||
return Tuple(schema)
|
||||
} else if (TypeGuard.TUint8Array(schema)) {
|
||||
} else if (Types.TypeGuard.TUint8Array(schema)) {
|
||||
return UInt8Array(schema)
|
||||
} else if (TypeGuard.TUndefined(schema)) {
|
||||
} else if (Types.TypeGuard.TUndefined(schema)) {
|
||||
return Undefined(schema)
|
||||
} else if (TypeGuard.TUnion(schema)) {
|
||||
} else if (Types.TypeGuard.TUnion(schema)) {
|
||||
return Union(schema)
|
||||
} else if (TypeGuard.TUnknown(schema)) {
|
||||
} else if (Types.TypeGuard.TUnknown(schema)) {
|
||||
return Unknown(schema)
|
||||
} else if (TypeGuard.TVoid(schema)) {
|
||||
} else if (Types.TypeGuard.TVoid(schema)) {
|
||||
return Void(schema)
|
||||
} else {
|
||||
throw Error('TypeScriptCodeGen: Unknown type')
|
||||
throw Error('TypeBoxToTypeScript: Unknown type')
|
||||
}
|
||||
}
|
||||
|
||||
/** Generates TypeScript code from TypeBox types */
|
||||
export function Generate(schema: Types.TSchema, references: Types.TSchema[] = []) {
|
||||
const result: string[] = []
|
||||
@@ -27,24 +27,29 @@ THE SOFTWARE.
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { Formatter } from './formatter'
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
import * as Types from '@sinclair/typebox'
|
||||
|
||||
export class ZodNonReferentialType extends Error {
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class TypeBoxToZodNonReferentialType extends Error {
|
||||
constructor(message: string) {
|
||||
super(`ZodNonReferentialType: ${message}`)
|
||||
super(`TypeBoxToZod: ${message}`)
|
||||
}
|
||||
}
|
||||
export class ZodUnsupportedType extends Error {
|
||||
export class TypeBoxToZodUnsupportedType extends Error {
|
||||
constructor(message: string) {
|
||||
super(`ZodUnsupportedType: ${message}`)
|
||||
super(`TypeBoxToZod: ${message}`)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Transform
|
||||
// --------------------------------------------------------------------------
|
||||
export interface ZodCodegenOptions {
|
||||
imports: boolean
|
||||
exports: boolean
|
||||
}
|
||||
export namespace ZodCodegen {
|
||||
export namespace TypeBoxToZod {
|
||||
function Any(schema: Types.TAny) {
|
||||
return `z.any()`
|
||||
}
|
||||
@@ -56,7 +61,7 @@ export namespace ZodCodegen {
|
||||
return `z.boolean()`
|
||||
}
|
||||
function Constructor(schema: Types.TConstructor): string {
|
||||
throw new ZodUnsupportedType(`TConstructor`)
|
||||
throw new TypeBoxToZodUnsupportedType(`TConstructor`)
|
||||
}
|
||||
function Function(schema: Types.TFunction) {
|
||||
const params = schema.parameters.map((param) => Visit(param)).join(`, `)
|
||||
@@ -73,6 +78,17 @@ export namespace ZodCodegen {
|
||||
if (schema.multipleOf !== undefined) buffer.push(`.multipleOf(${schema.multipleOf})`)
|
||||
return buffer.join(``)
|
||||
}
|
||||
function Intersect(schema: Types.TIntersect) {
|
||||
// note: Zod only supports binary intersection. While correct, this is partially at odds with TypeScript's
|
||||
// ability to distribute across (A & B & C). This code reduces intersection to binary ops.
|
||||
function reduce(rest: Types.TSchema[]): string {
|
||||
if (rest.length === 0) throw Error('Expected at least one intersect type')
|
||||
if (rest.length === 1) return Visit(rest[0])
|
||||
const [left, right] = [rest[0], rest.slice(1)]
|
||||
return `z.intersection(${Visit(left)}, ${reduce(right)})`
|
||||
}
|
||||
return reduce(schema.allOf)
|
||||
}
|
||||
function Literal(schema: Types.TLiteral) {
|
||||
if (typeof schema.const === `string`) {
|
||||
return `z.literal('${schema.const}')`
|
||||
@@ -124,19 +140,19 @@ export namespace ZodCodegen {
|
||||
for (const [key, value] of globalThis.Object.entries(schema.patternProperties)) {
|
||||
const type = Visit(value)
|
||||
if (key === `^(0|[1-9][0-9]*)$`) {
|
||||
throw new ZodUnsupportedType(`TRecord<TNumber, TUnknown>`)
|
||||
throw new TypeBoxToZodUnsupportedType(`TRecord<TNumber, TUnknown>`)
|
||||
} else {
|
||||
return `z.record(${type})`
|
||||
}
|
||||
}
|
||||
throw Error(`TypeScriptCodeGen: Unreachable`)
|
||||
throw Error(`TypeBoxToZod: Unreachable`)
|
||||
}
|
||||
function Ref(schema: Types.TRef) {
|
||||
if (!reference_map.has(schema.$ref!)) throw new ZodNonReferentialType(schema.$ref!)
|
||||
if (!reference_map.has(schema.$ref!)) throw new TypeBoxToZodNonReferentialType(schema.$ref!)
|
||||
return schema.$ref
|
||||
}
|
||||
function Self(schema: Types.TSelf) {
|
||||
if (!reference_map.has(schema.$ref!)) throw new ZodNonReferentialType(schema.$ref!)
|
||||
if (!reference_map.has(schema.$ref!)) throw new TypeBoxToZodNonReferentialType(schema.$ref!)
|
||||
recursive_set.add(schema.$ref)
|
||||
return schema.$ref
|
||||
}
|
||||
@@ -146,7 +162,7 @@ export namespace ZodCodegen {
|
||||
return `z.tuple([${items}])`
|
||||
}
|
||||
function UInt8Array(schema: Types.TUint8Array): string {
|
||||
throw new ZodUnsupportedType(`TUint8Array`)
|
||||
throw new TypeBoxToZodUnsupportedType(`TUint8Array`)
|
||||
}
|
||||
function Undefined(schema: Types.TUndefined) {
|
||||
return `z.undefined()`
|
||||
@@ -162,52 +178,54 @@ export namespace ZodCodegen {
|
||||
}
|
||||
function Visit(schema: Types.TSchema): string {
|
||||
if (schema.$id !== undefined) reference_map.set(schema.$id, schema)
|
||||
if (TypeGuard.TAny(schema)) {
|
||||
if (Types.TypeGuard.TAny(schema)) {
|
||||
return Any(schema)
|
||||
} else if (TypeGuard.TArray(schema)) {
|
||||
} else if (Types.TypeGuard.TArray(schema)) {
|
||||
return Array(schema)
|
||||
} else if (TypeGuard.TBoolean(schema)) {
|
||||
} else if (Types.TypeGuard.TBoolean(schema)) {
|
||||
return Boolean(schema)
|
||||
} else if (TypeGuard.TConstructor(schema)) {
|
||||
} else if (Types.TypeGuard.TConstructor(schema)) {
|
||||
return Constructor(schema)
|
||||
} else if (TypeGuard.TFunction(schema)) {
|
||||
} else if (Types.TypeGuard.TFunction(schema)) {
|
||||
return Function(schema)
|
||||
} else if (TypeGuard.TInteger(schema)) {
|
||||
} else if (Types.TypeGuard.TInteger(schema)) {
|
||||
return Integer(schema)
|
||||
} else if (TypeGuard.TLiteral(schema)) {
|
||||
} else if (Types.TypeGuard.TIntersect(schema)) {
|
||||
return Intersect(schema)
|
||||
} else if (Types.TypeGuard.TLiteral(schema)) {
|
||||
return Literal(schema)
|
||||
} else if (TypeGuard.TNever(schema)) {
|
||||
} else if (Types.TypeGuard.TNever(schema)) {
|
||||
return Never(schema)
|
||||
} else if (TypeGuard.TNull(schema)) {
|
||||
} else if (Types.TypeGuard.TNull(schema)) {
|
||||
return Null(schema)
|
||||
} else if (TypeGuard.TNumber(schema)) {
|
||||
} else if (Types.TypeGuard.TNumber(schema)) {
|
||||
return Number(schema)
|
||||
} else if (TypeGuard.TObject(schema)) {
|
||||
} else if (Types.TypeGuard.TObject(schema)) {
|
||||
return Object(schema)
|
||||
} else if (TypeGuard.TPromise(schema)) {
|
||||
} else if (Types.TypeGuard.TPromise(schema)) {
|
||||
return Promise(schema)
|
||||
} else if (TypeGuard.TRecord(schema)) {
|
||||
} else if (Types.TypeGuard.TRecord(schema)) {
|
||||
return Record(schema)
|
||||
} else if (TypeGuard.TRef(schema)) {
|
||||
} else if (Types.TypeGuard.TRef(schema)) {
|
||||
return Ref(schema)
|
||||
} else if (TypeGuard.TSelf(schema)) {
|
||||
} else if (Types.TypeGuard.TSelf(schema)) {
|
||||
return Self(schema)
|
||||
} else if (TypeGuard.TString(schema)) {
|
||||
} else if (Types.TypeGuard.TString(schema)) {
|
||||
return String(schema)
|
||||
} else if (TypeGuard.TTuple(schema)) {
|
||||
} else if (Types.TypeGuard.TTuple(schema)) {
|
||||
return Tuple(schema)
|
||||
} else if (TypeGuard.TUint8Array(schema)) {
|
||||
} else if (Types.TypeGuard.TUint8Array(schema)) {
|
||||
return UInt8Array(schema)
|
||||
} else if (TypeGuard.TUndefined(schema)) {
|
||||
} else if (Types.TypeGuard.TUndefined(schema)) {
|
||||
return Undefined(schema)
|
||||
} else if (TypeGuard.TUnion(schema)) {
|
||||
} else if (Types.TypeGuard.TUnion(schema)) {
|
||||
return Union(schema)
|
||||
} else if (TypeGuard.TUnknown(schema)) {
|
||||
} else if (Types.TypeGuard.TUnknown(schema)) {
|
||||
return Unknown(schema)
|
||||
} else if (TypeGuard.TVoid(schema)) {
|
||||
} else if (Types.TypeGuard.TVoid(schema)) {
|
||||
return Void(schema)
|
||||
} else {
|
||||
throw Error(`TypeScriptCodeGen: Unknown type`)
|
||||
throw Error(`TypeBoxToZod: Unknown type`)
|
||||
}
|
||||
}
|
||||
const reference_map = new Map<string, Types.TSchema>()
|
||||
@@ -222,7 +240,7 @@ export namespace ZodCodegen {
|
||||
// initialize root schematic and reference map
|
||||
if (schema.$id === undefined) schema.$id = `T_Generated`
|
||||
for (const reference of references) {
|
||||
if (reference.$id === undefined) throw new ZodNonReferentialType(JSON.stringify(reference))
|
||||
if (reference.$id === undefined) throw new TypeBoxToZodNonReferentialType(JSON.stringify(reference))
|
||||
reference_map.set(reference.$id, reference)
|
||||
}
|
||||
// render-code: Imports required for the generated code
|
||||
@@ -29,32 +29,32 @@ THE SOFTWARE.
|
||||
import { Formatter } from './formatter'
|
||||
import * as ts from 'typescript'
|
||||
|
||||
export class NonExpressable extends Error {
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class TypeScriptToJsonSchemaNonExpressable extends Error {
|
||||
constructor(type: string) {
|
||||
super(`JsonSchemaCodeGen: Cannot express syntax type '${type}'`)
|
||||
super(`TypeScriptToJsonSchema: Cannot express syntax type '${type}'`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Generates JsonSchema from TypeScript interface and type definitions */
|
||||
export namespace JsonSchemaCodegen {
|
||||
// --------------------------------------------------------------------------
|
||||
// Transform
|
||||
// --------------------------------------------------------------------------
|
||||
export namespace TypeScriptToJsonSchema {
|
||||
function isReadonlyProperty(node: ts.PropertySignature): boolean {
|
||||
return node.modifiers !== undefined && node.modifiers.find((modifier) => modifier.getText() === 'readonly') !== undefined
|
||||
}
|
||||
|
||||
function isOptionalProperty(node: ts.PropertySignature) {
|
||||
return node.questionToken !== undefined
|
||||
}
|
||||
|
||||
function isExport(node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDeclaration): boolean {
|
||||
return node.modifiers !== undefined && node.modifiers.find((modifier) => modifier.getText() === 'export') !== undefined
|
||||
}
|
||||
|
||||
function* SourceFile(node: ts.SourceFile): IterableIterator<string> {
|
||||
for (const next of node.getChildren()) {
|
||||
yield* Visit(next)
|
||||
}
|
||||
}
|
||||
|
||||
function* PropertySignature(node: ts.PropertySignature): IterableIterator<string> {
|
||||
const [readonly, optional] = [isReadonlyProperty(node), isOptionalProperty(node)]
|
||||
const type = Collect(node.type)
|
||||
@@ -68,7 +68,6 @@ export namespace JsonSchemaCodegen {
|
||||
return yield `${node.name.getText()}: ${type}`
|
||||
}
|
||||
}
|
||||
|
||||
function* ArrayTypeNode(node: ts.ArrayTypeNode): IterableIterator<string> {
|
||||
const type = Collect(node.elementType)
|
||||
yield `{
|
||||
@@ -76,7 +75,6 @@ export namespace JsonSchemaCodegen {
|
||||
items: ${type}
|
||||
}`
|
||||
}
|
||||
|
||||
function* TupleTypeNode(node: ts.TupleTypeNode): IterableIterator<string> {
|
||||
const types = node.elements.map((type) => Collect(type)).join(',\n')
|
||||
yield `{
|
||||
@@ -86,7 +84,6 @@ export namespace JsonSchemaCodegen {
|
||||
maxItems: ${node.elements.length}
|
||||
}`
|
||||
}
|
||||
|
||||
function* UnionTypeNode(node: ts.UnionTypeNode): IterableIterator<string> {
|
||||
const types = node.types.map((type) => Collect(type)).join(',\n')
|
||||
yield `{
|
||||
@@ -95,7 +92,6 @@ export namespace JsonSchemaCodegen {
|
||||
]
|
||||
}`
|
||||
}
|
||||
|
||||
function* IntersectionTypeNode(node: ts.IntersectionTypeNode): IterableIterator<string> {
|
||||
const types = node.types.map((type) => Collect(type)).join(',\n')
|
||||
yield `{
|
||||
@@ -104,23 +100,18 @@ export namespace JsonSchemaCodegen {
|
||||
]
|
||||
}`
|
||||
}
|
||||
|
||||
function* TypeOperatorNode(node: ts.TypeOperatorNode): IterableIterator<string> {
|
||||
throw new NonExpressable('TypeOperatorNode')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeOperatorNode')
|
||||
}
|
||||
|
||||
function* Parameter(node: ts.ParameterDeclaration): IterableIterator<string> {
|
||||
yield Collect(node.type)
|
||||
}
|
||||
|
||||
function* FunctionTypeNode(node: ts.FunctionTypeNode): IterableIterator<string> {
|
||||
throw new NonExpressable('FunctionTypeNode')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('FunctionTypeNode')
|
||||
}
|
||||
|
||||
function* ConstructorTypeNode(node: ts.ConstructorTypeNode): IterableIterator<string> {
|
||||
throw new NonExpressable('ConstructorTypeNode')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('ConstructorTypeNode')
|
||||
}
|
||||
|
||||
function* EnumMember(node: ts.EnumMember): IterableIterator<string> {
|
||||
if (node.initializer) {
|
||||
return yield `{
|
||||
@@ -131,7 +122,6 @@ export namespace JsonSchemaCodegen {
|
||||
const: '${node.name.getText()}'
|
||||
}`
|
||||
}
|
||||
|
||||
function* EnumDeclaration(node: ts.EnumDeclaration): IterableIterator<string> {
|
||||
const exports = isExport(node) ? 'export ' : ''
|
||||
const name = node.name.getText()
|
||||
@@ -142,7 +132,6 @@ export namespace JsonSchemaCodegen {
|
||||
]
|
||||
}`
|
||||
}
|
||||
|
||||
function* InterfaceDeclaration(node: ts.InterfaceDeclaration): IterableIterator<string> {
|
||||
if (node.typeParameters) {
|
||||
const exports = isExport(node) ? 'export ' : ''
|
||||
@@ -206,11 +195,11 @@ export namespace JsonSchemaCodegen {
|
||||
}
|
||||
|
||||
function* RestTypeNode(node: ts.RestTypeNode): IterableIterator<string> {
|
||||
throw new NonExpressable('RestTypeNode')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('RestTypeNode')
|
||||
}
|
||||
|
||||
function* ConditionalTypeNode(node: ts.ConditionalTypeNode): IterableIterator<string> {
|
||||
throw new NonExpressable('ConditionalTypeNode')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('ConditionalTypeNode')
|
||||
}
|
||||
|
||||
function* TypeReferenceNode(node: ts.TypeReferenceNode): IterableIterator<string> {
|
||||
@@ -222,31 +211,31 @@ export namespace JsonSchemaCodegen {
|
||||
items: ${args}
|
||||
}`
|
||||
} else if (name === 'Record') {
|
||||
throw new NonExpressable('TypeReferenceNode:Record')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Record')
|
||||
} else if (name === 'Partial') {
|
||||
throw new NonExpressable('TypeReferenceNode:Partial')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Partial')
|
||||
} else if (name === 'Uint8Array') {
|
||||
throw new NonExpressable('TypeReferenceNode:Uint8Array')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Uint8Array')
|
||||
} else if (name === 'Required') {
|
||||
throw new NonExpressable('TypeReferenceNode:Required')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Required')
|
||||
} else if (name === 'Omit') {
|
||||
throw new NonExpressable('TypeReferenceNode:Omit')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Omit')
|
||||
} else if (name === 'Pick') {
|
||||
throw new NonExpressable('TypeReferenceNode:Pick')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Pick')
|
||||
} else if (name === 'Promise') {
|
||||
throw new NonExpressable('TypeReferenceNode:Promise')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Promise')
|
||||
} else if (name === 'ReturnType') {
|
||||
throw new NonExpressable('TypeReferenceNode:ReturnType')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:ReturnType')
|
||||
} else if (name === 'InstanceType') {
|
||||
throw new NonExpressable('TypeReferenceNode:InstanceType')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:InstanceType')
|
||||
} else if (name === 'Parameters') {
|
||||
throw new NonExpressable('TypeReferenceNode:Parameters')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Parameters')
|
||||
} else if (name === 'ConstructorParameters') {
|
||||
throw new NonExpressable('TypeReferenceNode:ConstructorParameters')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:ConstructorParameters')
|
||||
} else if (name === 'Exclude') {
|
||||
throw new NonExpressable('TypeReferenceNode:Exclude')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Exclude')
|
||||
} else if (name === 'Extract') {
|
||||
throw new NonExpressable('TypeReferenceNode:Extract')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('TypeReferenceNode:Extract')
|
||||
} else {
|
||||
return yield `${name}${args}`
|
||||
}
|
||||
@@ -345,7 +334,7 @@ export namespace JsonSchemaCodegen {
|
||||
} else if (ts.isConditionalTypeNode(node)) {
|
||||
return yield* ConditionalTypeNode(node)
|
||||
} else if (node.kind === ts.SyntaxKind.KeyOfKeyword) {
|
||||
throw new NonExpressable('KeyOfKeyword')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('KeyOfKeyword')
|
||||
} else if (node.kind === ts.SyntaxKind.NumberKeyword) {
|
||||
return yield `{
|
||||
type: 'number'
|
||||
@@ -359,7 +348,7 @@ export namespace JsonSchemaCodegen {
|
||||
type: 'boolean'
|
||||
}`
|
||||
} else if (node.kind === ts.SyntaxKind.UndefinedKeyword) {
|
||||
throw new NonExpressable('UndefinedKeyword')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('UndefinedKeyword')
|
||||
} else if (node.kind === ts.SyntaxKind.UnknownKeyword) {
|
||||
return yield `{
|
||||
|
||||
@@ -386,7 +375,7 @@ export namespace JsonSchemaCodegen {
|
||||
type: 'null'
|
||||
}`
|
||||
} else if (node.kind === ts.SyntaxKind.VoidKeyword) {
|
||||
throw new NonExpressable('KeyOfKeyword')
|
||||
throw new TypeScriptToJsonSchemaNonExpressable('KeyOfKeyword')
|
||||
} else if (node.kind === ts.SyntaxKind.EndOfFileToken) {
|
||||
return
|
||||
} else if (node.kind === ts.SyntaxKind.SyntaxList) {
|
||||
@@ -29,26 +29,29 @@ THE SOFTWARE.
|
||||
import { Formatter } from './formatter'
|
||||
import * as ts from 'typescript'
|
||||
|
||||
/** Generates TypeBox types from TypeScript interface and type definitions */
|
||||
export namespace TypeBoxCodegen {
|
||||
// --------------------------------------------------------------------------
|
||||
// Transform
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/** Generates TypeBox types from TypeScript code */
|
||||
export namespace TypeScriptToTypeBox {
|
||||
function isReadonlyProperty(node: ts.PropertySignature): boolean {
|
||||
return node.modifiers !== undefined && node.modifiers.find((modifier) => modifier.getText() === 'readonly') !== undefined
|
||||
}
|
||||
|
||||
function isOptionalProperty(node: ts.PropertySignature) {
|
||||
return node.questionToken !== undefined
|
||||
}
|
||||
|
||||
function isExport(node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDeclaration): boolean {
|
||||
function isExport(node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDeclaration | ts.ModuleDeclaration): boolean {
|
||||
return node.modifiers !== undefined && node.modifiers.find((modifier) => modifier.getText() === 'export') !== undefined
|
||||
}
|
||||
|
||||
function isNamespace(node: ts.ModuleDeclaration) {
|
||||
return node.flags === ts.NodeFlags.Namespace
|
||||
}
|
||||
function* SourceFile(node: ts.SourceFile): IterableIterator<string> {
|
||||
for (const next of node.getChildren()) {
|
||||
yield* Visit(next)
|
||||
}
|
||||
}
|
||||
|
||||
function* PropertySignature(node: ts.PropertySignature): IterableIterator<string> {
|
||||
const [readonly, optional] = [isReadonlyProperty(node), isOptionalProperty(node)]
|
||||
const type = Collect(node.type)
|
||||
@@ -62,50 +65,41 @@ export namespace TypeBoxCodegen {
|
||||
return yield `${node.name.getText()}: ${type}`
|
||||
}
|
||||
}
|
||||
|
||||
function* ArrayTypeNode(node: ts.ArrayTypeNode): IterableIterator<string> {
|
||||
const type = Collect(node.elementType)
|
||||
yield `Type.Array(${type})`
|
||||
}
|
||||
|
||||
function* TupleTypeNode(node: ts.TupleTypeNode): IterableIterator<string> {
|
||||
const types = node.elements.map((type) => Collect(type)).join(',\n')
|
||||
yield `Type.Tuple([\n${types}\n])`
|
||||
}
|
||||
|
||||
function* UnionTypeNode(node: ts.UnionTypeNode): IterableIterator<string> {
|
||||
const types = node.types.map((type) => Collect(type)).join(',\n')
|
||||
yield `Type.Union([\n${types}\n])`
|
||||
}
|
||||
|
||||
function* IntersectionTypeNode(node: ts.IntersectionTypeNode): IterableIterator<string> {
|
||||
const types = node.types.map((type) => Collect(type)).join(',\n')
|
||||
yield `Type.Intersect([\n${types}\n])`
|
||||
}
|
||||
|
||||
function* TypeOperatorNode(node: ts.TypeOperatorNode): IterableIterator<string> {
|
||||
if (node.operator === ts.SyntaxKind.KeyOfKeyword) {
|
||||
const type = Collect(node.type)
|
||||
yield `Type.KeyOf(${type})`
|
||||
}
|
||||
}
|
||||
|
||||
function* Parameter(node: ts.ParameterDeclaration): IterableIterator<string> {
|
||||
yield Collect(node.type)
|
||||
}
|
||||
|
||||
function* FunctionTypeNode(node: ts.FunctionTypeNode): IterableIterator<string> {
|
||||
const parameters = node.parameters.map((param) => Collect(param)).join(', ')
|
||||
const returns = Collect(node.type)
|
||||
yield `Type.Function([${parameters}], ${returns})`
|
||||
}
|
||||
|
||||
function* ConstructorTypeNode(node: ts.ConstructorTypeNode): IterableIterator<string> {
|
||||
const parameters = node.parameters.map((param) => Collect(param)).join(', ')
|
||||
const returns = Collect(node.type)
|
||||
yield `Type.Constructor([${parameters}], ${returns})`
|
||||
}
|
||||
|
||||
function* EnumDeclaration(node: ts.EnumDeclaration): IterableIterator<string> {
|
||||
const exports = isExport(node) ? 'export ' : ''
|
||||
const name = node.name.getText()
|
||||
@@ -114,7 +108,6 @@ export namespace TypeBoxCodegen {
|
||||
const type = `${exports}const ${name} = Type.Enum(${name}Enum)`
|
||||
yield [enumType, '', type].join('\n')
|
||||
}
|
||||
|
||||
function* InterfaceDeclaration(node: ts.InterfaceDeclaration): IterableIterator<string> {
|
||||
useImports = true
|
||||
if (node.typeParameters) {
|
||||
@@ -135,7 +128,6 @@ export namespace TypeBoxCodegen {
|
||||
yield `${staticDeclaration}\n${typeDeclaration}`
|
||||
}
|
||||
}
|
||||
|
||||
function* TypeAliasDeclaration(node: ts.TypeAliasDeclaration): IterableIterator<string> {
|
||||
useImports = true
|
||||
if (node.typeParameters) {
|
||||
@@ -156,28 +148,22 @@ export namespace TypeBoxCodegen {
|
||||
yield `${staticDeclaration}\n${typeDeclaration}`
|
||||
}
|
||||
}
|
||||
|
||||
function* TypeParameterDeclaration(node: ts.TypeParameterDeclaration): IterableIterator<string> {
|
||||
yield node.name.getText()
|
||||
}
|
||||
|
||||
function* ParenthesizedTypeNode(node: ts.ParenthesizedTypeNode): IterableIterator<string> {
|
||||
yield Collect(node.type)
|
||||
}
|
||||
|
||||
function* RestTypeNode(node: ts.RestTypeNode): IterableIterator<string> {
|
||||
yield `Type.Rest()`
|
||||
}
|
||||
|
||||
function* ConditionalTypeNode(node: ts.ConditionalTypeNode): IterableIterator<string> {
|
||||
useConditional = true
|
||||
const checkType = Collect(node.checkType)
|
||||
const extendsType = Collect(node.extendsType)
|
||||
const trueType = Collect(node.trueType)
|
||||
const falseType = Collect(node.falseType)
|
||||
yield `Conditional.Extends(${checkType}, ${extendsType}, ${trueType}, ${falseType})`
|
||||
yield `Type.Extends(${checkType}, ${extendsType}, ${trueType}, ${falseType})`
|
||||
}
|
||||
|
||||
function* TypeReferenceNode(node: ts.TypeReferenceNode): IterableIterator<string> {
|
||||
const name = node.typeName.getText()
|
||||
const args = node.typeArguments ? `(${node.typeArguments.map((type) => Collect(type)).join(', ')})` : ''
|
||||
@@ -206,41 +192,46 @@ export namespace TypeBoxCodegen {
|
||||
} else if (name === 'ConstructorParameters') {
|
||||
return yield `Type.ConstructorParameters${args}`
|
||||
} else if (name === 'Exclude') {
|
||||
return yield `Conditional.Exclude${args}`
|
||||
return yield `Type.Exclude${args}`
|
||||
} else if (name === 'Extract') {
|
||||
return yield `Conditional.Extract${args}`
|
||||
return yield `Type.Extract${args}`
|
||||
} else {
|
||||
return yield `${name}${args}`
|
||||
}
|
||||
}
|
||||
|
||||
function* TypeLiteralNode(node: ts.TypeLiteralNode): IterableIterator<string> {
|
||||
const members = node.members.map((member) => Collect(member)).join(',\n')
|
||||
yield `Type.Object({\n${members}\n})`
|
||||
}
|
||||
|
||||
function* LiteralTypeNode(node: ts.LiteralTypeNode): IterableIterator<string> {
|
||||
const text = node.getText()
|
||||
if (text === 'null') return yield `Type.Null()`
|
||||
yield `Type.Literal(${node.getText()})`
|
||||
}
|
||||
|
||||
function* ModuleDeclaration(node: ts.ModuleDeclaration): IterableIterator<string> {
|
||||
const export_specifier = isExport(node) ? 'export ' : ''
|
||||
const module_specifier = isNamespace(node) ? 'namespace' : 'module'
|
||||
yield `${export_specifier}${module_specifier} ${node.name.getText()} {`
|
||||
yield* Visit(node.body)
|
||||
yield `}`
|
||||
}
|
||||
function* ModuleBlock(node: ts.ModuleBlock): IterableIterator<string> {
|
||||
for (const statement of node.statements) {
|
||||
yield* Visit(statement)
|
||||
}
|
||||
}
|
||||
function* FunctionDeclaration(node: ts.FunctionDeclaration): IterableIterator<string> {
|
||||
yield node.getText()
|
||||
}
|
||||
|
||||
function* ClassDeclaration(node: ts.ClassDeclaration): IterableIterator<string> {
|
||||
yield node.getText()
|
||||
}
|
||||
|
||||
function Collect(node: ts.Node | undefined): string {
|
||||
return `${[...Visit(node)].join('')}`
|
||||
}
|
||||
|
||||
function CollectNewLine(node: ts.Node | undefined): string {
|
||||
return [...Visit(node)].join('\n\n')
|
||||
}
|
||||
|
||||
function* Visit(node: ts.Node | undefined): IterableIterator<string> {
|
||||
if (node === undefined) return
|
||||
if (ts.isSourceFile(node)) {
|
||||
@@ -265,6 +256,10 @@ export namespace TypeBoxCodegen {
|
||||
return yield* TypeLiteralNode(node)
|
||||
} else if (ts.isLiteralTypeNode(node)) {
|
||||
return yield* LiteralTypeNode(node)
|
||||
} else if (ts.isModuleDeclaration(node)) {
|
||||
return yield* ModuleDeclaration(node)
|
||||
} else if (ts.isModuleBlock(node)) {
|
||||
return yield* ModuleBlock(node)
|
||||
} else if (ts.isArrayTypeNode(node)) {
|
||||
return yield* ArrayTypeNode(node)
|
||||
} else if (ts.isTupleTypeNode(node)) {
|
||||
@@ -287,6 +282,8 @@ export namespace TypeBoxCodegen {
|
||||
return yield* ClassDeclaration(node)
|
||||
} else if (ts.isConditionalTypeNode(node)) {
|
||||
return yield* ConditionalTypeNode(node)
|
||||
} else if (node.kind === ts.SyntaxKind.ExportKeyword) {
|
||||
return yield `export`
|
||||
} else if (node.kind === ts.SyntaxKind.KeyOfKeyword) {
|
||||
return yield `Type.KeyOf()`
|
||||
} else if (node.kind === ts.SyntaxKind.NumberKeyword) {
|
||||
@@ -319,22 +316,16 @@ export namespace TypeBoxCodegen {
|
||||
return yield node.getText()
|
||||
}
|
||||
}
|
||||
|
||||
let useImports = false
|
||||
let useConditional = false
|
||||
let useGenerics = false
|
||||
|
||||
/** Generates TypeBox types from TypeScript interface and type definitions */
|
||||
export function Generate(typescriptCode: string) {
|
||||
useImports = false
|
||||
useConditional = false
|
||||
useGenerics = false
|
||||
|
||||
const source = ts.createSourceFile('code.ts', typescriptCode, ts.ScriptTarget.ESNext, true)
|
||||
const typeDeclarations = CollectNewLine(source)
|
||||
const importStatments: string[] = []
|
||||
if (useImports) {
|
||||
if (useConditional) importStatments.push(`import { Conditional } from '@sinclair/typebox/conditional'`)
|
||||
if (useGenerics) importStatments.push(`import { Type, Static, TSchema } from '@sinclair/typebox'`)
|
||||
if (!useGenerics) importStatments.push(`import { Type, Static } from '@sinclair/typebox'`)
|
||||
}
|
||||
109
example/experimental/experimental.ts
Normal file
109
example/experimental/experimental.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/experimental
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as Types from '@sinclair/typebox'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// TReadonlyObject
|
||||
// -------------------------------------------------------------------------------------
|
||||
export type TReadonlyArray<T extends Types.TSchema[]> = Types.Assert<{ [K in keyof T]: TReadonlyObject<Types.Assert<T[K], Types.TSchema>> }, Types.TSchema[]>
|
||||
// prettier-ignore
|
||||
export type TReadonlyProperties<T extends Types.TProperties> = Types.Evaluate<Types.Assert<{
|
||||
[K in keyof T]:
|
||||
T[K] extends Types.TReadonlyOptional<infer U> ? Types.TReadonlyOptional<U> :
|
||||
T[K] extends Types.TReadonly<infer U> ? Types.TReadonly<U> :
|
||||
T[K] extends Types.TOptional<infer U> ? Types.TReadonlyOptional<U> :
|
||||
Types.TReadonly<T[K]>
|
||||
}, Types.TProperties>>
|
||||
// prettier-ignore
|
||||
export type TReadonlyObject<T extends Types.TSchema> =
|
||||
T extends Types.TIntersect<infer S> ? Types.TIntersect<TReadonlyArray<S>> :
|
||||
T extends Types.TUnion<infer S> ? Types.TUnion<TReadonlyArray<S>> :
|
||||
T extends Types.TObject<infer S> ? Types.TObject<TReadonlyProperties<S>> :
|
||||
Types.TReadonly<T>
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// TUnionEnum
|
||||
// -------------------------------------------------------------------------------------
|
||||
export interface TUnionEnum<T extends (string | number)[]> extends Types.TSchema {
|
||||
[Types.Kind]: 'UnionEnum'
|
||||
static: T[number]
|
||||
enum: T
|
||||
}
|
||||
// -------------------------------------------------------------------------------------
|
||||
// UnionOneOf
|
||||
// -------------------------------------------------------------------------------------
|
||||
export interface UnionOneOf<T extends Types.TSchema[]> extends Types.TSchema {
|
||||
[Types.Kind]: 'UnionOneOf'
|
||||
static: { [K in keyof T]: Types.Static<T[K]> }[number]
|
||||
oneOf: T
|
||||
}
|
||||
// -------------------------------------------------------------------------------------
|
||||
// ExperimentalTypeBuilder
|
||||
// -------------------------------------------------------------------------------------
|
||||
export class ExperimentalTypeBuilder extends Types.ExtendedTypeBuilder {
|
||||
/** `[Experimental]` Remaps a Intersect, Union or Object as readonly */
|
||||
public ReadonlyObject<T extends Types.TSchema>(schema: T): TReadonlyObject<T> {
|
||||
function Apply(property: Types.TSchema): any {
|
||||
// prettier-ignore
|
||||
switch (property[Types.Modifier]) {
|
||||
case 'ReadonlyOptional': property[Types.Modifier] = 'ReadonlyOptional'; break
|
||||
case 'Readonly': property[Types.Modifier] = 'Readonly'; break
|
||||
case 'Optional': property[Types.Modifier] = 'ReadonlyOptional'; break
|
||||
default: property[Types.Modifier] = 'Readonly'; break
|
||||
}
|
||||
return property
|
||||
}
|
||||
// prettier-ignore
|
||||
return (Types.TypeGuard.TIntersect(schema) || Types.TypeGuard.TUnion(schema) || Types.TypeGuard.TObject(schema))
|
||||
? Types.ObjectMap.Map<TReadonlyObject<T>>(schema, (schema) => {
|
||||
globalThis.Object.keys(schema.properties).forEach(key => Apply(schema.properties[key]))
|
||||
return schema
|
||||
}, {}) : Apply(schema)
|
||||
}
|
||||
/** `[Experimental]` Creates a Union type with a `enum` schema representation */
|
||||
public UnionEnum<T extends (string | number)[]>(values: [...T], options: Types.SchemaOptions = {}) {
|
||||
function UnionEnumCheck(schema: TUnionEnum<(string | number)[]>, value: unknown) {
|
||||
return (typeof value === 'string' || typeof value === 'number') && schema.enum.includes(value)
|
||||
}
|
||||
if (!Types.TypeRegistry.Has('UnionEnum')) Types.TypeRegistry.Set('UnionEnum', UnionEnumCheck)
|
||||
return { ...options, [Types.Kind]: 'UnionEnum', enum: values } as TUnionEnum<T>
|
||||
}
|
||||
/** `[Experimental]` Creates a Union type with a `oneOf` schema representation */
|
||||
public UnionOneOf<T extends Types.TSchema[]>(oneOf: [...T], options: Types.SchemaOptions = {}) {
|
||||
function UnionOneOfCheck(schema: UnionOneOf<Types.TSchema[]>, value: unknown) {
|
||||
return 1 === schema.oneOf.reduce((acc: number, schema: any) => (Value.Check(schema, value) ? acc + 1 : acc), 0)
|
||||
}
|
||||
if (!Types.TypeRegistry.Has('UnionOneOf')) Types.TypeRegistry.Set('UnionOneOf', UnionOneOfCheck)
|
||||
return { ...options, [Types.Kind]: 'UnionOneOf', oneOf } as UnionOneOf<T>
|
||||
}
|
||||
}
|
||||
|
||||
export const Type = new ExperimentalTypeBuilder()
|
||||
@@ -1,6 +1,6 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/hash
|
||||
@sinclair/typebox/extensions
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -26,4 +26,4 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export * from './hash'
|
||||
export * from './experimental'
|
||||
30
example/experimental/readme.md
Normal file
30
example/experimental/readme.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# ExperimentalTypeBuilder
|
||||
|
||||
An experimental TypeBox type builder with additional custom types.
|
||||
|
||||
## Overview
|
||||
|
||||
The TypeBox TypeBuilder classes are designed to be extended with user defined types. Instances where you may wish to do this are if your application is dependent on custom schematics and/or non-JSON serializable values (an example of which might be a Mongo's `ObjectId` or other such non-serializable value)
|
||||
|
||||
## Application Type Builder
|
||||
|
||||
The following shows creating a simple `ApplicationTypeBuilder` with additional types `Nullable` and `StringEnum`. These types are fairly common in OpenAPI implementations.
|
||||
|
||||
```typescript
|
||||
import { StandardTypeBuilder, Static, TSchema } from '@sinclair/typebox'
|
||||
|
||||
export class ApplicationTypeBuilder extends StandardTypeBuilder { // only JSON Schema types
|
||||
public Nullable<T extends TSchema>(schema: T) {
|
||||
return this.Unsafe<Static<T> | null>({ ...schema, nullable: true })
|
||||
}
|
||||
public StringEnum<T extends string[]>(values: [...T]) {
|
||||
return this.Unsafe<T[number]>({ type: 'string', enum: values })
|
||||
}
|
||||
}
|
||||
|
||||
export const Type = new ApplicationTypeBuilder() // re-export!
|
||||
```
|
||||
|
||||
## Experimental Type Builder
|
||||
|
||||
The `experimental.ts` file provided with this example shows advanced usage by creating complex types for potential inclusion in the TypeBox library in later revisions. It is offered for reference, experimentation and is open to contributor submission.
|
||||
@@ -1,32 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/extensions
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export * from './intersect-allof'
|
||||
export * from './readonly-object'
|
||||
export * from './union-oneof'
|
||||
export * from './union-enum'
|
||||
@@ -1,60 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/extensions
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { Kind, TSchema, SchemaOptions, IntersectReduce, IntersectEvaluate } from '@sinclair/typebox'
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
import { Custom } from '@sinclair/typebox/custom'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
function IntersectAllOfCheck(schema: IntersectAllOf<TSchema[]>, value: unknown) {
|
||||
if (!schema.allOf.every((schema) => Value.Check(schema, value))) return false
|
||||
if (schema.unevaluatedProperties === undefined || schema.unevaluatedProperties === true) return true
|
||||
const valueKeys = typeof value === 'object' && value !== null ? globalThis.Object.keys(value) : []
|
||||
const knownKeys = schema.allOf.reduce((set, schema) => {
|
||||
if (!TypeGuard.TObject(schema)) return set
|
||||
Object.keys(schema.properties).forEach((key) => set.add(key))
|
||||
return set
|
||||
}, new Set<string>())
|
||||
return valueKeys.every((key) => knownKeys.has(key))
|
||||
}
|
||||
|
||||
export interface IntersectAllOfOptions extends SchemaOptions {
|
||||
unevaluatedProperties?: boolean
|
||||
}
|
||||
|
||||
export interface IntersectAllOf<T extends TSchema[]> extends TSchema, IntersectAllOfOptions {
|
||||
[Kind]: 'IntersectAllOf'
|
||||
static: IntersectReduce<unknown, IntersectEvaluate<T, []>>
|
||||
allOf: T
|
||||
}
|
||||
|
||||
/** Creates a Intersect type with a `allOf` schema representation */
|
||||
export function IntersectAllOf<T extends TSchema[]>(allOf: [...T], options: IntersectAllOfOptions = {}) {
|
||||
if (!Custom.Has('IntersectAllOf')) Custom.Set('IntersectAllOf', IntersectAllOfCheck)
|
||||
return { ...options, [Kind]: 'IntersectAllOf', allOf } as IntersectAllOf<T>
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/extensions
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { Type, TSchema, TObject, TProperties, Modifier, TReadonly, TReadonlyOptional, TOptional } from '@sinclair/typebox'
|
||||
|
||||
// prettier-ignore
|
||||
export type TReadonlyObject<T extends TObject> = TObject<{
|
||||
[K in keyof T['properties']]:
|
||||
T['properties'][K] extends TReadonlyOptional<infer U> ? TReadonlyOptional<U> :
|
||||
T['properties'][K] extends TReadonly<infer U> ? TReadonly<U> :
|
||||
T['properties'][K] extends TOptional<infer U> ? TReadonlyOptional<U> :
|
||||
TReadonly<T['properties'][K]>
|
||||
}>
|
||||
|
||||
/** Remaps all properties of an object to be readonly */
|
||||
export function ReadonlyObject<T extends TObject>(schema: T): TReadonlyObject<T> {
|
||||
return Type.Object(
|
||||
Object.keys(schema.properties).reduce((acc, key) => {
|
||||
const property = schema.properties[key] as TSchema
|
||||
const modifier = property[Modifier]
|
||||
// prettier-ignore
|
||||
const mapped = (
|
||||
(modifier === 'ReadonlyOptional') ? { ...property, [Modifier]: 'ReadonlyOptional' } :
|
||||
(modifier === 'Readonly') ? { ...property, [Modifier]: 'Readonly' } :
|
||||
(modifier === 'Optional') ? { ...property, [Modifier]: 'ReadonlyOptional' } :
|
||||
({...property, [Modifier]: 'Readonly' })
|
||||
)
|
||||
return { ...acc, [key]: mapped }
|
||||
}, {} as TProperties),
|
||||
) as TReadonlyObject<T>
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/extensions
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { Kind, TSchema, SchemaOptions } from '@sinclair/typebox'
|
||||
import { Custom } from '@sinclair/typebox/custom'
|
||||
|
||||
function UnionEnumCheck(schema: TUnionEnum<(string | number)[]>, value: unknown) {
|
||||
return (typeof value === 'string' || typeof value === 'number') && schema.enum.includes(value)
|
||||
}
|
||||
|
||||
export interface TUnionEnum<T extends (string | number)[]> extends TSchema {
|
||||
[Kind]: 'UnionEnum'
|
||||
static: T[number]
|
||||
enum: T
|
||||
}
|
||||
|
||||
/** Creates a Union type with a `enum` schema representation */
|
||||
export function UnionEnum<T extends (string | number)[]>(values: [...T], options: SchemaOptions = {}) {
|
||||
if (!Custom.Has('UnionEnum')) Custom.Set('UnionEnum', UnionEnumCheck)
|
||||
return { ...options, [Kind]: 'UnionEnum', enum: values } as TUnionEnum<T>
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/extensions
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { Kind, TSchema, SchemaOptions, Static } from '@sinclair/typebox'
|
||||
import { Custom } from '@sinclair/typebox/custom'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
function UnionOneOfCheck(schema: UnionOneOf<TSchema[]>, value: unknown) {
|
||||
return 1 === schema.oneOf.reduce((acc: number, schema: any) => (Value.Check(schema, value) ? acc + 1 : acc), 0)
|
||||
}
|
||||
|
||||
export interface UnionOneOf<T extends TSchema[]> extends TSchema {
|
||||
[Kind]: 'UnionOneOf'
|
||||
static: { [K in keyof T]: Static<T[K]> }[number]
|
||||
oneOf: T
|
||||
}
|
||||
|
||||
/** Creates a Union type with a `oneOf` schema representation */
|
||||
export function UnionOneOf<T extends TSchema[]>(oneOf: [...T], options: SchemaOptions = {}) {
|
||||
if (!Custom.Has('UnionOneOf')) Custom.Set('UnionOneOf', UnionOneOfCheck)
|
||||
return { ...options, [Kind]: 'UnionOneOf', oneOf } as UnionOneOf<T>
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import { Format } from '@sinclair/typebox/format'
|
||||
|
||||
/** Configures TypeBox for additional string formats */
|
||||
export namespace AdditionalFormats {
|
||||
// -------------------------------------------------------------------------------------------
|
||||
// These formats are ported from the ajv-formats library. All credit goes to the original
|
||||
// authors with the original code located at the following location.
|
||||
//
|
||||
// https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
const UUID = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i
|
||||
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]
|
||||
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)$/
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
// leap second
|
||||
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)
|
||||
}
|
||||
|
||||
function IsEmail(value: string) {
|
||||
return EMAIL.test(value)
|
||||
}
|
||||
|
||||
function IsUuid(value: string) {
|
||||
return UUID.test(value)
|
||||
}
|
||||
|
||||
function IsUrl(value: string) {
|
||||
return URL.test(value)
|
||||
}
|
||||
|
||||
function IsIPv6(value: string) {
|
||||
return IPV6.test(value)
|
||||
}
|
||||
|
||||
function IsIPv4(value: string) {
|
||||
return IPV4.test(value)
|
||||
}
|
||||
|
||||
/** Configures additional string formats */
|
||||
export function Configure(): void {
|
||||
Format.Set('date-time', (value) => IsDateTime(value, true))
|
||||
Format.Set('date', (value) => IsDate(value))
|
||||
Format.Set('time', (value) => IsTime(value))
|
||||
Format.Set('email', (value) => IsEmail(value))
|
||||
Format.Set('uuid', (value) => IsUuid(value))
|
||||
Format.Set('url', (value) => IsUrl(value))
|
||||
Format.Set('ipv6', (value) => IsIPv6(value))
|
||||
Format.Set('ipv4', (value) => IsIPv4(value))
|
||||
//
|
||||
// optional: add additional formats
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
export * from './additional'
|
||||
export * from './standard'
|
||||
|
||||
43
example/formats/readme.md
Normal file
43
example/formats/readme.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# String Formats
|
||||
|
||||
TypeBox does not implement any string formats by default. However it is possible to register user defined formats using the `FormatRegistry`. Once registered, the format becomes available to both `Value` and `TypeCompiler` modules.
|
||||
|
||||
## FormatRegistry
|
||||
|
||||
The following shows basic usage of the format registry
|
||||
|
||||
```typescript
|
||||
import { Type, FormatRegistry } from '@sinclair/typebox'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
// Register the 'foo-only' format. The format checks for 'foo' only.
|
||||
FormatRegistry.Set('foo-only', value => value === 'foo')
|
||||
|
||||
const T = Type.String({ format: 'foo-only' })
|
||||
|
||||
// Validate
|
||||
Value.Check(T, 'foo') // true
|
||||
Value.Check(T, 'bar') // false
|
||||
```
|
||||
|
||||
## Standard Formats
|
||||
|
||||
The `standard.ts` file provided with this example implements several standard string formats.
|
||||
|
||||
```typescript
|
||||
import './standard'
|
||||
```
|
||||
|
||||
The following formats are implemented by `standard.ts`
|
||||
|
||||
| Format | Description |
|
||||
| --- | --- |
|
||||
| `email` | Internet email address, see [RFC 5321, section 4.1.2.](http://tools.ietf.org/html/rfc5321#section-4.1.2) |
|
||||
| `date-time` | ISO8601 DateTime. example, `2018-11-13T20:20:39+00:00` |
|
||||
| `date` | ISO8601 Date component, for example, `2018-11-13` |
|
||||
| `time` | ISO8601 Time component, for example, `20:20:39+00:00` |
|
||||
| `uuid` | A Universally Unique Identifier as defined by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122). |
|
||||
| `url` | A uniform resource locator as defined in [RFC 1738](https://www.rfc-editor.org/rfc/rfc1738)
|
||||
| `ipv4` | 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). |
|
||||
| `ipv6` | IPv6 address, as defined in [RFC 2373, section 2.2](http://tools.ietf.org/html/rfc2373#section-2.2). |
|
||||
|
||||
74
example/formats/standard.ts
Normal file
74
example/formats/standard.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { FormatRegistry } from '@sinclair/typebox'
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
// Format Registration
|
||||
// -------------------------------------------------------------------------------------------
|
||||
FormatRegistry.Set('date-time', (value) => IsDateTime(value, true))
|
||||
FormatRegistry.Set('date', (value) => IsDate(value))
|
||||
FormatRegistry.Set('time', (value) => IsTime(value))
|
||||
FormatRegistry.Set('email', (value) => IsEmail(value))
|
||||
FormatRegistry.Set('uuid', (value) => IsUuid(value))
|
||||
FormatRegistry.Set('url', (value) => IsUrl(value))
|
||||
FormatRegistry.Set('ipv6', (value) => IsIPv6(value))
|
||||
FormatRegistry.Set('ipv4', (value) => IsIPv4(value))
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
// https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
const UUID = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i
|
||||
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]
|
||||
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)$/
|
||||
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
|
||||
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
|
||||
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
|
||||
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)
|
||||
}
|
||||
function IsEmail(value: string) {
|
||||
return EMAIL.test(value)
|
||||
}
|
||||
function IsUuid(value: string) {
|
||||
return UUID.test(value)
|
||||
}
|
||||
function IsUrl(value: string) {
|
||||
return URL.test(value)
|
||||
}
|
||||
function IsIPv6(value: string) {
|
||||
return IPV6.test(value)
|
||||
}
|
||||
function IsIPv4(value: string) {
|
||||
return IPV4.test(value)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { Codegen } from '@sinclair/typebox/codegen'
|
||||
import { TypeSystem } from '@sinclair/typebox/system'
|
||||
import { TypeCompiler } from '@sinclair/typebox/compiler'
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
import { Conditional } from '@sinclair/typebox/conditional'
|
||||
import { Format } from '@sinclair/typebox/format'
|
||||
import { Custom } from '@sinclair/typebox/custom'
|
||||
import { Value, ValuePointer } from '@sinclair/typebox/value'
|
||||
import { Type, Kind, Static, TSchema } from '@sinclair/typebox'
|
||||
import { Type, TypeGuard, Kind, Static, TSchema } from '@sinclair/typebox'
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Create: Type
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Using TypeBox with TRPC
|
||||
|
||||
To use TypeBox with TRPC, you will need to wrap types passed to procedures within a TRPC compatible assertion function. Once wrapped, TypeBox can provide TRPC auto type inference for procedures, enhanced runtime checking performance and well as providing options for enabling publishable procedure schematics based on the JSON Schema specification.
|
||||
To use TypeBox with TRPC, you will need to wrap types passed to procedures within a TRPC compatible assertion function. Once wrapped, TypeBox can provide TRPC auto type inference for procedures, enhanced runtime checking performance as well as provide options to enabling publishable procedure schematics based on the JSON Schema specification.
|
||||
|
||||
## Contents
|
||||
|
||||
@@ -57,11 +57,11 @@ import { TSchema } from '@sinclair/typebox'
|
||||
import { TypeCompiler } from '@sinclair/typebox/compiler'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
|
||||
export function RpcType<T extends TSchema>(schema: T, references: TSchema[] = []) {
|
||||
const check = TypeCompiler.Compile(schema, references)
|
||||
export function RpcType<T extends TSchema>(schema: T) {
|
||||
const check = TypeCompiler.Compile(schema)
|
||||
return (value: unknown) => {
|
||||
if (check.Check(value)) return value
|
||||
const { path, message } = [...check.Errors(value)][0]
|
||||
const { path, message } = check.Errors(value).First()!
|
||||
throw new TRPCError({ message: `${message} for ${path}`, code: 'BAD_REQUEST' })
|
||||
}
|
||||
}
|
||||
@@ -78,10 +78,10 @@ import { Value } from '@sinclair/typebox/value'
|
||||
import { TSchema } from '@sinclair/typebox'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
|
||||
export function RpcType<T extends TSchema>(schema: T, references: TSchema[] = []) {
|
||||
export function RpcType<T extends TSchema>(schema: T) {
|
||||
return (value: unknown) => {
|
||||
if (Value.Check(schema, value)) return value
|
||||
const { path, message } = [...Value.Errors(schema, references, value)][0]
|
||||
const { path, message } = check.Errors(value).First()!
|
||||
throw new TRPCError({ message: `${message} for ${path}`, code: 'BAD_REQUEST' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/extensions
|
||||
@sinclair/typebox/typedef
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
|
||||
@@ -1,100 +1,81 @@
|
||||
# JSON Type Definition
|
||||
# TypeDef
|
||||
|
||||
JSON Type Definition (JTD) specification is an alternative schema specification to JSON Schema that provides better support for nominal type systems. TypeBox doesn't provide JTD support by default, but can be expressed through unsafe types and validated with Ajv.
|
||||
TypeBox may offer support for [RPC8927](https://www.rfc-editor.org/rfc/rfc8927) JSON Type Definition in future revisions of the library. This specification is much simpler than JSON Schema but can be useful when describing schematics that need to be shared with nominal type languages.
|
||||
|
||||
This example provides a reference implementation for JSON Type Definition.
|
||||
## Usage
|
||||
|
||||
## TypeDef
|
||||
|
||||
Refer to the `typedef.ts` file in this directory for a reference implementation of the JSON Type Definition type builder.
|
||||
The file `typedef.ts` provided with this example contains the provisional implementation for RPC8927.
|
||||
|
||||
```typescript
|
||||
import { Static } from '@sinclair/typebox'
|
||||
import { TypeDef } from './typedef'
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
import { Type, Static } from './typedef'
|
||||
|
||||
const T = Type.Struct('T', { // 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,
|
||||
// z: number,
|
||||
// y: number
|
||||
// }
|
||||
|
||||
const R = Value.Check(T, { x: 1, y: 2, z: 3 }) // const R = true
|
||||
```
|
||||
|
||||
## Properties
|
||||
### Unions
|
||||
|
||||
The JSON Type Definition has a different representation for unions and is primarily orientated towards discriminated unions. To use unions, you will need to name each struct on the first argument. TypeBox will take care of producing the union representation and static type.
|
||||
|
||||
```typescript
|
||||
// ------------------------------------------------------------------------
|
||||
// PropertiesType
|
||||
//
|
||||
// https://jsontypedef.com/docs/jtd-in-5-minutes/#properties-schemas
|
||||
//
|
||||
// ------------------------------------------------------------------------
|
||||
import { Type, Static } from './typedef'
|
||||
|
||||
export type PropertiesType = Static<typeof PropertiesType>
|
||||
export const PropertiesType = TypeDef.Properties({
|
||||
x: TypeDef.Float32(),
|
||||
y: TypeDef.Float32(),
|
||||
z: TypeDef.Float32(),
|
||||
})
|
||||
```
|
||||
|
||||
## Values
|
||||
|
||||
```typescript
|
||||
// ------------------------------------------------------------------------
|
||||
// ValuesType
|
||||
//
|
||||
// https://jsontypedef.com/docs/jtd-in-5-minutes/#values-schemas
|
||||
//
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
export type ValuesType = Static<typeof ValuesType>
|
||||
export const ValuesType = TypeDef.Values(TypeDef.Float64())
|
||||
```
|
||||
|
||||
## Enum
|
||||
|
||||
```typescript
|
||||
// ------------------------------------------------------------------------
|
||||
// EnumType
|
||||
//
|
||||
// https://jsontypedef.com/docs/jtd-in-5-minutes/#enum-schemas
|
||||
//
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
export type EnumType = Static<typeof EnumType>
|
||||
export const EnumType = TypeDef.Enum(['FOO', 'BAR', 'BAZ'])
|
||||
```
|
||||
|
||||
## Elements
|
||||
|
||||
```typescript
|
||||
// ------------------------------------------------------------------------
|
||||
// ElementsType
|
||||
//
|
||||
// https://jsontypedef.com/docs/jtd-in-5-minutes/#elements-schemas
|
||||
//
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
export type ElementsType = Static<typeof ElementsType>
|
||||
export const ElementsType = TypeDef.Elements(PropertiesType)
|
||||
```
|
||||
|
||||
## Union
|
||||
|
||||
```typescript
|
||||
// ------------------------------------------------------------------------
|
||||
// UnionType
|
||||
//
|
||||
// https://jsontypedef.com/docs/jtd-in-5-minutes/#discriminator-schemas
|
||||
//
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
export type UnionType = Static<typeof UnionType>
|
||||
export const UnionType = TypeDef.Union('eventType', {
|
||||
USER_CREATED: TypeDef.Properties({
|
||||
id: TypeDef.String(),
|
||||
}),
|
||||
USER_PAYMENT_PLAN_CHANGED: TypeDef.Properties({
|
||||
id: TypeDef.String(),
|
||||
plan: TypeDef.Enum(['FREE', 'PAID']),
|
||||
}),
|
||||
USER_DELETED: TypeDef.Properties({
|
||||
id: TypeDef.String(),
|
||||
softDelete: TypeDef.Boolean(),
|
||||
}),
|
||||
})
|
||||
const Vector2 = Type.Struct('Vector2', { // const Vector2 = {
|
||||
x: Type.Float32(), // properties: {
|
||||
y: Type.Float32(), // x: { type: 'float32' },
|
||||
}) // y: { type: 'float32' }
|
||||
// }
|
||||
// }
|
||||
|
||||
const Vector3 = Type.Struct('Vector3', { // const Vector3 = {
|
||||
x: Type.Float32(), // properties: {
|
||||
y: Type.Float32(), // x: { type: 'float32' },
|
||||
z: Type.Float32() // y: { type: 'float32' },
|
||||
}) // z: { type: 'float32' }
|
||||
// }
|
||||
// }
|
||||
|
||||
const Union = Type.Union('type', [ // const Union = {
|
||||
Vector2, // discriminator: 'type',
|
||||
Vector3 // mapping: {
|
||||
]) // Vector2: {
|
||||
// properties: {
|
||||
// x: { type: 'float32' },
|
||||
// y: { type: 'float32' },
|
||||
// }
|
||||
// },
|
||||
// Vector3: {
|
||||
// properties: {
|
||||
// x: { type: 'float32' },
|
||||
// y: { type: 'float32' },
|
||||
// z: { type: 'float32' }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
type Union = Static<typeof Union> // type Union = {
|
||||
// type: 'Vector2'
|
||||
// x: number
|
||||
// y: number
|
||||
// } | {
|
||||
// type: 'Vector3'
|
||||
// x: number
|
||||
// y: number
|
||||
// z: number
|
||||
// }
|
||||
```
|
||||
|
||||
@@ -26,63 +26,378 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { Type, Static, TUnsafe } from '@sinclair/typebox'
|
||||
export { type Static, TSchema, PropertiesReduce, TReadonly, TReadonlyOptional, TOptional } from '@sinclair/typebox'
|
||||
import * as Types from '@sinclair/typebox'
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// TypeDef Namespace
|
||||
//
|
||||
// https://jsontypedef.com/docs/jtd-in-5-minutes/
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
export type StaticTypeDefUnion<D extends string, M extends Record<string, TUnsafe<any>>> = { [K in keyof M]: { [P in D]: K } & Static<M[K]> }[keyof M]
|
||||
|
||||
export namespace TypeDef {
|
||||
export function Boolean() {
|
||||
return Type.Unsafe<boolean>({ type: 'boolean' })
|
||||
}
|
||||
export function String() {
|
||||
return Type.Unsafe<string>({ type: 'string' })
|
||||
}
|
||||
export function TimeStamp() {
|
||||
return Type.Unsafe<string>({ type: 'timestamp' })
|
||||
}
|
||||
export function Float32() {
|
||||
return Type.Unsafe<number>({ type: 'float32' })
|
||||
}
|
||||
export function Float64() {
|
||||
return Type.Unsafe<number>({ type: 'float64' })
|
||||
}
|
||||
export function Int8() {
|
||||
return Type.Unsafe<number>({ type: 'int8' })
|
||||
}
|
||||
export function Uint8() {
|
||||
return Type.Unsafe<number>({ type: 'uint8' })
|
||||
}
|
||||
export function Int16() {
|
||||
return Type.Unsafe<number>({ type: 'int16' })
|
||||
}
|
||||
export function Uint16() {
|
||||
return Type.Unsafe<number>({ type: 'uint16' })
|
||||
}
|
||||
export function Int32() {
|
||||
return Type.Unsafe<number>({ type: 'int32' })
|
||||
}
|
||||
export function Uint32() {
|
||||
return Type.Unsafe<number>({ type: 'uint32' })
|
||||
}
|
||||
export function Enum<T extends string[]>(values: [...T]) {
|
||||
return Type.Unsafe<T[number]>({ enum: values })
|
||||
}
|
||||
export function Elements<T extends TUnsafe<any>>(element: T) {
|
||||
return Type.Unsafe<Array<Static<T>>>({ elements: element })
|
||||
}
|
||||
export function Properties<T extends Record<string, TUnsafe<any>>>(properties: T) {
|
||||
return Type.Unsafe<{ [K in keyof T]: Static<T[K]> }>({ properties })
|
||||
}
|
||||
export function Values<V extends TUnsafe<any>>(values: V) {
|
||||
return Type.Unsafe<Record<string, Static<V>>>({ values })
|
||||
}
|
||||
export function Union<D extends string, M extends Record<string, TUnsafe<any>>>(discriminator: D, mapping: M) {
|
||||
return Type.Unsafe<StaticTypeDefUnion<D, M>>({ discriminator, mapping })
|
||||
// --------------------------------------------------------------------------
|
||||
// Symbols
|
||||
// --------------------------------------------------------------------------
|
||||
export const Name = Symbol.for('TypeBox:Name')
|
||||
// --------------------------------------------------------------------------
|
||||
// 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'
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// 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'
|
||||
static: string
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// TStruct
|
||||
// --------------------------------------------------------------------------
|
||||
type OptionalKeys<T extends TFields> = { [K in keyof T]: T[K] extends (Types.TReadonlyOptional<T[K]> | Types.TOptional<T[K]>) ? T[K] : never }
|
||||
type RequiredKeys<T extends TFields> = { [K in keyof T]: T[K] extends (Types.TReadonlyOptional<T[K]> | Types.TOptional<T[K]>) ? never : T[K] }
|
||||
export interface StructOptions {
|
||||
additionalProperties?: boolean
|
||||
}
|
||||
export interface TStruct<D extends string = string, T extends TFields = TFields> extends Types.TSchema, StructOptions {
|
||||
[Name]: D
|
||||
[Types.Kind]: 'TypeDef:Struct'
|
||||
static: Types.PropertiesReduce<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'
|
||||
static: number
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// TUnion
|
||||
// --------------------------------------------------------------------------
|
||||
export interface TUnion<D extends string = string, T extends TStruct[] = TStruct[]> extends Types.TSchema {
|
||||
[Types.Kind]: 'TypeDef:Union'
|
||||
static: Types.Evaluate<{ [K in keyof T]: { [key in D]: T[K][typeof Name] } & Types.Static<T[K]> }[number]>
|
||||
discriminator: D,
|
||||
mapping: T
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// TypeRegistry
|
||||
// --------------------------------------------------------------------------
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Array', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Boolean', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Int8', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Int16', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Int32', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Uint8', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Uint16', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Uint32', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Record', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:String', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Struct', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Timestamp', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
Types.TypeRegistry.Set<TArray>('TypeDef:Union', (schema, value) => TypeDefCheck.Check(schema, value))
|
||||
// --------------------------------------------------------------------------
|
||||
// TypeDefCheck
|
||||
// --------------------------------------------------------------------------
|
||||
export class TypeDefCheckUnionTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('TypeDefCheck: Unknown type')
|
||||
}
|
||||
}
|
||||
export namespace TypeDefCheck {
|
||||
// ------------------------------------------------------------------------
|
||||
// Guards
|
||||
// ------------------------------------------------------------------------
|
||||
function IsObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
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): boolean {
|
||||
const optionalKeys = schema.optionalProperties === undefined ? [] : globalThis.Object.getOwnPropertyNames(schema.optionalProperties)
|
||||
const requiredKeys = schema.properties === undefined ? [] : globalThis.Object.getOwnPropertyNames(schema.properties)
|
||||
if(!(IsObject(value) &&
|
||||
optionalKeys.every(key => key in value ? Visit((schema.optionalProperties as any)[key], value[key]) : true) &&
|
||||
requiredKeys.every(key => key in value && Visit(((schema as any).properties[key] as any), value[key])))) return false
|
||||
if(schema.additionalProperties === true) return true
|
||||
const unknownKeys = globalThis.Object.getOwnPropertyNames(value)
|
||||
return unknownKeys.every(key => optionalKeys.includes(key) || requiredKeys.includes(key))
|
||||
}
|
||||
function Timestamp(schema: TString, value: unknown): boolean {
|
||||
return IsInt(value, 0, Number.MAX_SAFE_INTEGER)
|
||||
}
|
||||
function Union(schema: TUnion, value: unknown): boolean {
|
||||
return IsObject(value) &&
|
||||
schema.discriminator in value &&
|
||||
IsString(value[schema.discriminator]) &&
|
||||
value[schema.discriminator] as any in schema.mapping &&
|
||||
Visit(schema.mapping[value[schema.discriminator] as any], value)
|
||||
}
|
||||
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: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)
|
||||
case 'TypeDef:Union': return Union(anySchema, value)
|
||||
default: throw new TypeDefCheckUnionTypeError(anySchema)
|
||||
}
|
||||
}
|
||||
export function Check(schema: Types.TSchema, value: unknown): boolean {
|
||||
return Visit(schema, value)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// TypeDefTypeBuilder
|
||||
// --------------------------------------------------------------------------
|
||||
export class TypeDefTypeBuilder extends Types.TypeBuilder {
|
||||
// ------------------------------------------------------------------------
|
||||
// Modifiers
|
||||
// ------------------------------------------------------------------------
|
||||
/** `[Modifier]` Creates a Optional property */
|
||||
public Optional<T extends Types.TSchema>(schema: T): Types.TOptional<T> {
|
||||
return { [Types.Modifier]: 'Optional', ...Types.TypeClone.Clone(schema, {}) }
|
||||
}
|
||||
/** `[Modifier]` Creates a ReadonlyOptional property */
|
||||
public ReadonlyOptional<T extends Types.TSchema>(schema: T): Types.TReadonlyOptional<T> {
|
||||
return { [Types.Modifier]: 'ReadonlyOptional', ...Types.TypeClone.Clone(schema, {}) }
|
||||
}
|
||||
/** `[Modifier]` Creates a Readonly object or property */
|
||||
public Readonly<T extends Types.TSchema>(schema: T): Types.TReadonly<T> {
|
||||
return { [Types.Modifier]: 'Readonly', ...schema }
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
// Modifiers
|
||||
// ------------------------------------------------------------------------
|
||||
/** `[Standard]` Creates a TypeDef Array type */
|
||||
public Array<T extends Types.TSchema>(elements: T): TArray<T> {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Array', elements })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Boolean type */
|
||||
public Boolean(): TBoolean {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Boolean', type: 'boolean' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Enum type */
|
||||
public Enum<T extends string[]>(values: [...T]): TEnum<T> {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Enum', enum: values })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Float32 type */
|
||||
public Float32(): TFloat32 {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Float32', type: 'float32' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Float64 type */
|
||||
public Float64(): TFloat64 {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Float64', type: 'float64' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Int8 type */
|
||||
public Int8(): TInt8 {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Int8', type: 'int8' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Int16 type */
|
||||
public Int16(): TInt16 {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Int16', type: 'int16' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Int32 type */
|
||||
public Int32(): TInt32 {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Int32', type: 'int32' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Uint8 type */
|
||||
public Uint8(): TUint8 {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Uint8', type: 'uint8' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Uint16 type */
|
||||
public Uint16(): TUint16 {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Uint16', type: 'uint16' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Uint32 type */
|
||||
public Uint32(): TUint32 {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Uint32', type: 'uint32' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Record type */
|
||||
public Record<T extends Types.TSchema>(values: T): TRecord<T> {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Record',values })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef String type */
|
||||
public String(): TString {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:String',type: 'string' })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Struct type */
|
||||
public Struct<N extends string, T extends TFields>(name: N, fields: T, options?: StructOptions): TStruct<N, T> {
|
||||
const optionalProperties = globalThis.Object.getOwnPropertyNames(fields).reduce((acc, key) => (Types.TypeGuard.TOptional(fields[key]) || Types.TypeGuard.TReadonlyOptional(fields[key]) ? { ...acc, [key]: fields[key] } : { ...acc }), {} as TFields)
|
||||
const properties = globalThis.Object.getOwnPropertyNames(fields).reduce((acc, key) => (Types.TypeGuard.TOptional(fields[key]) || Types.TypeGuard.TReadonlyOptional(fields[key]) ? {... acc } : { ...acc, [key]: fields[key] }), {} as TFields)
|
||||
const optionalPropertiesObject = globalThis.Object.getOwnPropertyNames(optionalProperties).length > 0 ? { optionalProperties: optionalProperties } : {}
|
||||
const propertiesObject = globalThis.Object.getOwnPropertyNames(properties).length === 0 ? {} : { properties: properties }
|
||||
return this.Create({ ...options, [Types.Kind]: 'TypeDef:Struct', [Name]: name, ...propertiesObject, ...optionalPropertiesObject })
|
||||
}
|
||||
/** `[Standard]` Creates a TypeDef Timestamp type */
|
||||
public Timestamp(): TTimestamp {
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Timestamp', type: 'timestamp' })
|
||||
}
|
||||
|
||||
/** `[Standard]` Creates a TypeDef Discriminated Union type */
|
||||
public Union<D extends string, T extends TStruct<string, TFields>[]>(discriminator: D, objects: [...T]): TUnion<D, T> {
|
||||
if(objects.length === 0) throw new Error('TypeDefTypeBuilder: Union types must have at least one object')
|
||||
const exists = objects.every(object => typeof object[Name] === 'string')
|
||||
if(!exists) throw new Error('TypeDefTypeBuilder: All union objects MUST have a descriminator')
|
||||
const unique = objects.reduce((set, current) => set.add(current[Name]), new Set<string>())
|
||||
if(unique.size !== objects.length) throw new Error('TypeDefTypeBuilder: All union objects MUST unique descriminator strings')
|
||||
const mapping = objects.reduce((acc, current) => ({ ...acc, [current[Name]]: current }), {})
|
||||
return this.Create({ [Types.Kind]: 'TypeDef:Union', discriminator, mapping })
|
||||
}
|
||||
}
|
||||
|
||||
/** JSON Type Definition Type Builder */
|
||||
export const Type = new TypeDefTypeBuilder()
|
||||
|
||||
|
||||
16
hammer.mjs
16
hammer.mjs
@@ -1,11 +1,9 @@
|
||||
import { compression, measurement } from './benchmark'
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// Clean
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
export async function clean() {
|
||||
await folder('target').delete()
|
||||
}
|
||||
@@ -13,15 +11,13 @@ export async function clean() {
|
||||
// -------------------------------------------------------------------------------
|
||||
// Format
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
export async function format() {
|
||||
await shell('prettier --no-semi --single-quote --print-width 240 --trailing-comma all --write src test example benchmark codegen')
|
||||
await shell('prettier --no-semi --single-quote --print-width 240 --trailing-comma all --write src test example/index.ts benchmark codegen')
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// Start
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
export async function start(example = 'index') {
|
||||
await shell(`hammer run example/${example}.ts --dist target/example/${example}`)
|
||||
}
|
||||
@@ -29,34 +25,27 @@ export async function start(example = 'index') {
|
||||
// -------------------------------------------------------------------------------
|
||||
// Benchmark
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
export async function benchmark() {
|
||||
await compression()
|
||||
await measurement()
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// Test
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
export async function test_static() {
|
||||
await shell(`tsc -p test/static/tsconfig.json --noEmit --strict`)
|
||||
}
|
||||
|
||||
export async function test_runtime(filter) {
|
||||
await shell(`hammer build ./test/runtime/index.ts --dist target/test/runtime --platform node`)
|
||||
await shell(`mocha target/test/runtime/index.js -g "${filter}"`)
|
||||
}
|
||||
|
||||
export async function test(filter = '') {
|
||||
await test_static()
|
||||
await test_runtime(filter)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// Build
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
export async function build(target = 'target/build') {
|
||||
await test()
|
||||
await folder(target).delete()
|
||||
@@ -66,12 +55,9 @@ export async function build(target = 'target/build') {
|
||||
await folder(target).add('license')
|
||||
await shell(`cd ${target} && npm pack`)
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Publish
|
||||
// -------------------------------------------------------------
|
||||
|
||||
export async function publish(otp, target = 'target/build') {
|
||||
const { version } = JSON.parse(readFileSync('package.json', 'utf8'))
|
||||
await shell(`cd ${target} && npm publish sinclair-typebox-${version}.tgz --access=public --otp ${otp}`)
|
||||
|
||||
20
package-lock.json
generated
20
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@sinclair/typebox",
|
||||
"version": "0.25.21",
|
||||
"version": "0.26.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@sinclair/typebox",
|
||||
"version": "0.25.21",
|
||||
"version": "0.26.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@sinclair/hammer": "^0.17.1",
|
||||
@@ -18,7 +18,7 @@
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^5.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
@@ -1410,16 +1410,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
|
||||
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
|
||||
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
"node": ">=12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
@@ -2442,9 +2442,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
|
||||
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
|
||||
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
|
||||
"dev": true
|
||||
},
|
||||
"uri-js": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sinclair/typebox",
|
||||
"version": "0.25.24",
|
||||
"version": "0.26.0",
|
||||
"description": "JSONSchema Type Builder with Static Type Resolution for TypeScript",
|
||||
"keywords": [
|
||||
"typescript",
|
||||
@@ -14,12 +14,7 @@
|
||||
"types": "./typebox.d.ts",
|
||||
"exports": {
|
||||
"./compiler": "./compiler/index.js",
|
||||
"./conditional": "./conditional/index.js",
|
||||
"./custom": "./custom/index.js",
|
||||
"./errors": "./errors/index.js",
|
||||
"./format": "./format/index.js",
|
||||
"./guard": "./guard/index.js",
|
||||
"./hash": "./hash/index.js",
|
||||
"./system": "./system/index.js",
|
||||
"./value": "./value/index.js",
|
||||
".": "./typebox.js"
|
||||
@@ -47,6 +42,6 @@
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^5.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
656
readme.md
656
readme.md
@@ -35,7 +35,7 @@ import { Static, Type } from 'npm:@sinclair/typebox'
|
||||
import { Static, Type } from 'https://esm.sh/@sinclair/typebox'
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
@@ -62,32 +62,33 @@ type T = Static<typeof T> // type T = {
|
||||
|
||||
## Overview
|
||||
|
||||
TypeBox is a type builder library that creates in-memory JSON Schema objects that can be statically inferred as TypeScript types. The schemas produced by this library are designed to match the static type checking rules of the TypeScript compiler. TypeBox enables one to create a unified type that can be statically checked by TypeScript and runtime asserted using standard JSON Schema validation.
|
||||
TypeBox is a runtime type builder that creates in-memory JSON Schema objects that can be statically inferred as TypeScript types. The schemas produced by this library are designed to match the static type assertion rules of the TypeScript compiler. TypeBox enables one to create a unified type that can be statically checked by TypeScript and runtime asserted using standard JSON Schema validation.
|
||||
|
||||
This library is designed to enable JSON schema to compose with the same flexibility as TypeScript's type system. It can be used either as a simple tool to build up complex schemas or integrated into REST and RPC services to help validate data received over the wire.
|
||||
This library is designed to enable JSON schema to compose with the same flexibility as TypeScript's type system. It can be used as a simple tool to build up complex schemas or integrated into REST or RPC services to help validate data received over the wire.
|
||||
|
||||
License MIT
|
||||
|
||||
## Contents
|
||||
- [Install](#install)
|
||||
- [Overview](#overview)
|
||||
- [Example](#Example)
|
||||
- [Usage](#usage)
|
||||
- [Types](#types)
|
||||
- [Standard](#types-standard)
|
||||
- [Extended](#types-extended)
|
||||
- [Modifiers](#types-modifiers)
|
||||
- [Options](#types-options)
|
||||
- [Reference](#types-reference)
|
||||
- [Generics](#types-generics)
|
||||
- [References](#types-references)
|
||||
- [Recursive](#types-recursive)
|
||||
- [Generic](#types-generic)
|
||||
- [Conditional](#types-conditional)
|
||||
- [Unsafe](#types-unsafe)
|
||||
- [Guards](#types-guards)
|
||||
- [Unsafe](#types-unsafe)
|
||||
- [Strict](#types-strict)
|
||||
- [Values](#values)
|
||||
- [Create](#values-create)
|
||||
- [Clone](#values-clone)
|
||||
- [Check](#values-check)
|
||||
- [Convert](#values-convert)
|
||||
- [Cast](#values-cast)
|
||||
- [Equal](#values-equal)
|
||||
- [Hash](#values-hash)
|
||||
@@ -101,20 +102,20 @@ License MIT
|
||||
- [TypeSystem](#typecheck)
|
||||
- [Types](#typesystem-types)
|
||||
- [Formats](#typesystem-formats)
|
||||
- [Policies](#typesystem-policies)
|
||||
- [Benchmark](#benchmark)
|
||||
- [Compile](#benchmark-compile)
|
||||
- [Validate](#benchmark-validate)
|
||||
- [Compression](#benchmark-compression)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
<a name="Example"></a>
|
||||
<a name="usage"></a>
|
||||
|
||||
## Example
|
||||
## Usage
|
||||
|
||||
The following demonstrates TypeBox's general usage.
|
||||
The following shows general usage.
|
||||
|
||||
```typescript
|
||||
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
@@ -173,9 +174,11 @@ type T = Static<typeof T> // type T = {
|
||||
//
|
||||
//--------------------------------------------------------------------------------------------
|
||||
|
||||
function receive(value: T) { // ... as a Type
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
if(JSON.validate(T, value)) { // ... as a Schema
|
||||
function receive(value: T) { // ... as a Static Type
|
||||
|
||||
if(Value.Check(T, value)) { // ... as a JSON Schema
|
||||
|
||||
// ok...
|
||||
}
|
||||
@@ -186,13 +189,13 @@ function receive(value: T) { // ... as a Type
|
||||
|
||||
## Types
|
||||
|
||||
TypeBox provides a set of functions that allow you to compose JSON Schema similar to how you would compose static types with TypeScript. Each function creates a JSON schema fragment which can compose into more complex types. The schemas produced by TypeBox can be passed directly to any JSON Schema compliant validator, or used to reflect runtime metadata for a type.
|
||||
TypeBox types are JSON schema fragments that can be composed into more complex types. Each fragment is structured such that a JSON schema compliant validator can runtime assert a value the same way TypeScript will statically assert a type. TypeBox provides a set of Standard types which are used create JSON schema compliant schematics as well as an Extended type set used to create schematics for constructs native to JavaScript.
|
||||
|
||||
<a name='types-standard'></a>
|
||||
|
||||
### Standard
|
||||
### Standard Types
|
||||
|
||||
The following table lists the Standard TypeBox types.
|
||||
The following table lists the Standard TypeBox types. These types are fully compatible with the JSON Schema Draft 6 specification.
|
||||
|
||||
```typescript
|
||||
┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐
|
||||
@@ -230,12 +233,6 @@ The following table lists the Standard TypeBox types.
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.RegEx(/foo/) │ type T = string │ const T = { │
|
||||
│ │ │ type: 'string', │
|
||||
│ │ │ pattern: 'foo' │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Literal(42) │ type T = 42 │ const T = { │
|
||||
│ │ │ const: 42, │
|
||||
│ │ │ type: 'number' │
|
||||
@@ -252,15 +249,14 @@ The following table lists the Standard TypeBox types.
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Object({ │ type T = { │ const T = { │
|
||||
│ x: Type.Number(), │ x: number, │ type: 'object', │
|
||||
│ y: Type.Number() │ y: number │ properties: { │
|
||||
│ }) │ } │ x: { │
|
||||
│ │ │ type: 'number' │
|
||||
│ │ │ }, │
|
||||
│ │ │ y: { │
|
||||
│ │ │ type: 'number' │
|
||||
│ │ │ } │
|
||||
│ │ │ }, │
|
||||
│ │ │ required: ['x', 'y'] │
|
||||
│ y: Type.Number() │ y: number │ required: ['x', 'y'], │
|
||||
│ }) │ } │ properties: { │
|
||||
│ │ │ x: { │
|
||||
│ │ │ type: 'number' │
|
||||
│ │ │ }, { │
|
||||
│ │ │ type: 'number' │
|
||||
│ │ │ } │
|
||||
│ │ │ } │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
@@ -310,13 +306,36 @@ The following table lists the Standard TypeBox types.
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Intersect([ │ type T = { │ const T = { │
|
||||
│ Type.Object({ │ x: number │ type: 'object', │
|
||||
│ x: Type.Number() │ } & { │ properties: { │
|
||||
│ }), │ y: number │ x: { │
|
||||
│ Type.Object({ │ } │ type: 'number' │
|
||||
│ y: Type.Number() │ │ }, │
|
||||
│ }) │ │ y: { │
|
||||
│ ]) │ │ type: 'number' │
|
||||
│ Type.Object({ │ x: number │ allOf: [{ │
|
||||
│ x: Type.Number() │ } & { │ type: 'object', │
|
||||
│ }), │ y: number │ required: ['x'], │
|
||||
│ Type.Object({ │ } │ properties: { │
|
||||
│ y: Type.Number() │ │ x: { │
|
||||
│ ]) │ │ type: 'number' │
|
||||
│ ]) │ │ } │
|
||||
│ │ │ } │
|
||||
│ │ │ }, { │
|
||||
│ │ │ type: 'object', |
|
||||
│ │ │ required: ['y'], │
|
||||
│ │ │ properties: { │
|
||||
│ │ │ y: { │
|
||||
│ │ │ type: 'number' │
|
||||
│ │ │ } │
|
||||
│ │ │ }] │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Composite([ │ type T = { │ const T = { │
|
||||
│ Type.Object({ │ x: number | string │ type: 'object', │
|
||||
│ x: Type.Number() │ y: number │ properties: { │
|
||||
│ }), │ } │ x: { │
|
||||
│ Type.Object({ │ │ anyOf: [ │
|
||||
│ x: Type.String() │ │ { type: 'number' }, │
|
||||
│ y: Type.Number() │ │ { type: 'string' } │
|
||||
│ }) │ │ ] │
|
||||
│ ]) │ │ }, │
|
||||
│ │ │ y: { │
|
||||
│ │ │ type: 'number' │
|
||||
│ │ │ } │
|
||||
│ │ │ }, │
|
||||
│ │ │ required: ['x', 'y'] │
|
||||
@@ -324,16 +343,51 @@ The following table lists the Standard TypeBox types.
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Never() │ type T = never │ const T = { │
|
||||
│ │ │ allOf: [{ │
|
||||
│ │ │ type: 'boolean', │
|
||||
│ │ │ const: false │
|
||||
│ │ │ not: {} │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Not( | type T = string │ const T = { │
|
||||
| Type.Union([ │ │ allOf: [{ │
|
||||
│ Type.Literal('x'), │ │ not: { │
|
||||
│ Type.Literal('y'), │ │ anyOf: [ │
|
||||
│ Type.Literal('z') │ │ { const: 'x' }, │
|
||||
│ ]), │ │ { const: 'y' }, │
|
||||
│ Type.String() │ │ { const: 'z' } │
|
||||
│ ) │ │ ] │
|
||||
│ │ │ } │
|
||||
│ │ │ }, { │
|
||||
│ │ │ type: 'boolean', │
|
||||
│ │ │ const: true │
|
||||
│ │ │ type: 'string' │
|
||||
│ │ │ }] │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Extends( │ type T = │ const T = { │
|
||||
│ Type.String(), │ string extends number │ const: false, │
|
||||
│ Type.Number(), │ true : false │ type: 'boolean' │
|
||||
│ Type.Literal(true), │ │ } │
|
||||
│ Type.Literal(false) │ │ │
|
||||
│ ) │ │ │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Extract( │ type T = Extract< │ const T = { │
|
||||
│ Type.Union([ │ string | number, │ type: 'string' │
|
||||
│ Type.String(), │ string │ } │
|
||||
│ Type.Number(), │ > │ │
|
||||
│ ]), │ │ │
|
||||
│ Type.String() │ │ │
|
||||
│ ) │ │ │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Exclude( │ type T = Exclude< │ const T = { │
|
||||
│ Type.Union([ │ string | number, │ type: 'number' │
|
||||
│ Type.String(), │ string │ } │
|
||||
│ Type.Number(), │ > │ │
|
||||
│ ]), │ │ │
|
||||
│ Type.String() │ │ │
|
||||
│ ) │ │ │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Record( │ type T = Record< │ const T = { │
|
||||
│ Type.String(), │ string, │ type: 'object', │
|
||||
│ Type.Number() │ number, │ patternProperties: { │
|
||||
@@ -359,15 +413,15 @@ The following table lists the Standard TypeBox types.
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Required( │ type T = Required<{ │ const T = { │
|
||||
│ Type.Object({ │ x?: number, │ type: 'object', │
|
||||
│ x: Type.Optional( │ y?: number │ properties: { │
|
||||
│ Type.Number() | }> │ x: { │
|
||||
│ ), │ │ type: 'number' │
|
||||
│ y: Type.Optional( │ │ }, │
|
||||
│ Type.Number() │ │ y: { │
|
||||
│ ) │ │ type: 'number' │
|
||||
│ }) │ │ } │
|
||||
│ ) │ │ }, │
|
||||
│ │ │ required: ['x', 'y'] │
|
||||
│ x: Type.Optional( │ y?: number │ required: ['x', 'y'], │
|
||||
│ Type.Number() | }> │ properties: { │
|
||||
│ ), │ │ x: { │
|
||||
│ y: Type.Optional( │ │ type: 'number' │
|
||||
│ Type.Number() │ │ }, │
|
||||
│ ) │ │ y: { │
|
||||
│ }) │ │ type: 'number' │
|
||||
│ ) │ │ } │
|
||||
│ │ │ } │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
@@ -392,14 +446,25 @@ The following table lists the Standard TypeBox types.
|
||||
│ │ │ required: ['y'] │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const A = Type.Object({ │ type A = { │ const T = { │
|
||||
│ x: Type.Number(), │ x: number, │ $ref: 'A' │
|
||||
│ y: Type.Number() │ y: number │ } │
|
||||
│ }, { $id: 'T' }) | } │ │
|
||||
│ │ │ │
|
||||
│ const T = Type.Ref(A) │ type T = A │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
└────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘
|
||||
```
|
||||
|
||||
<a name='types-extended'></a>
|
||||
|
||||
### Extended
|
||||
### Extended Types
|
||||
|
||||
TypeBox provides a set of extended types that can be used to express schematics for core JavaScript constructs and primitives. Extended types are not valid JSON Schema and will not validate using typical validation. These types however can be used to frame JSON schema and describe callable RPC interfaces that may receive JSON validated data.
|
||||
TypeBox provides several extended types that can be used to produce schematics for common JavaScript constructs. These types can not be used with standard JSON schema validators; but are useful to help frame schematics for RPC interfaces that may receive JSON validated data. Extended types are prefixed with the `[Extended]` doc comment for convenience. The following table lists the supported types.
|
||||
|
||||
```typescript
|
||||
┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐
|
||||
@@ -462,6 +527,24 @@ TypeBox provides a set of extended types that can be used to express schematics
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.RegEx(/foo/) │ type T = string │ const T = { │
|
||||
│ │ │ type: 'string', │
|
||||
│ │ │ pattern: 'foo' │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Symbol() │ type T = symbol │ const T = { │
|
||||
│ │ │ type: 'null', │
|
||||
│ │ │ typeOf: 'Symbol' │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.BigInt() │ type T = bigint │ const T = { │
|
||||
│ │ │ type: 'null', │
|
||||
│ │ │ typeOf: 'BigInt' │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Type.Void() │ type T = void │ const T = { │
|
||||
│ │ │ type: 'null' │
|
||||
│ │ │ typeOf: 'Void' │
|
||||
@@ -474,7 +557,7 @@ TypeBox provides a set of extended types that can be used to express schematics
|
||||
|
||||
### Modifiers
|
||||
|
||||
TypeBox provides modifiers that can be applied to an objects properties. This allows for `optional` and `readonly` to be applied to that property. The following table illustates how they map between TypeScript and JSON Schema.
|
||||
TypeBox provides modifiers that allow schema properties to be statically inferred as `readonly` or `optional`. The following table shows the supported modifiers and how they map between TypeScript and JSON Schema.
|
||||
|
||||
```typescript
|
||||
┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐
|
||||
@@ -484,9 +567,9 @@ TypeBox provides modifiers that can be applied to an objects properties. This al
|
||||
│ const T = Type.Object({ │ type T = { │ const T = { │
|
||||
│ name: Type.Optional( │ name?: string │ type: 'object', │
|
||||
│ Type.String() │ } │ properties: { │
|
||||
│ ) │ │ name: { │
|
||||
│ }) │ │ type: 'string' │
|
||||
│ │ │ } │
|
||||
│ ) │ │ name: { │
|
||||
│ }) │ │ type: 'string' │
|
||||
│ │ │ } │
|
||||
│ │ │ } │
|
||||
│ │ │ } │
|
||||
│ │ │ │
|
||||
@@ -518,24 +601,96 @@ TypeBox provides modifiers that can be applied to an objects properties. This al
|
||||
|
||||
### Options
|
||||
|
||||
You can pass additional JSON schema options on the last argument of any given type. The following are some examples.
|
||||
You can pass JSON Schema options on the last argument of any type. Option hints specific to each type are provided for convenience.
|
||||
|
||||
```typescript
|
||||
// string must be an email
|
||||
const T = Type.String({ format: 'email' })
|
||||
// String must be an email
|
||||
const T = Type.String({ // const T = {
|
||||
format: 'email' // type: 'string',
|
||||
}) // format: 'email'
|
||||
// }
|
||||
|
||||
// number must be a multiple of 2
|
||||
const T = Type.Number({ multipleOf: 2 })
|
||||
// Mumber must be a multiple of 2
|
||||
const T = Type.Number({ // const T = {
|
||||
multipleOf: 2 // type: 'number',
|
||||
}) // multipleOf: 2
|
||||
// }
|
||||
|
||||
// Array must have at least 5 integer values
|
||||
const T = Type.Array(Type.Integer(), { // const T = {
|
||||
minItems: 5 // type: 'array',
|
||||
}) // minItems: 5,
|
||||
// items: {
|
||||
// type: 'integer'
|
||||
// }
|
||||
// }
|
||||
|
||||
// array must have at least 5 integer values
|
||||
const T = Type.Array(Type.Integer(), { minItems: 5 })
|
||||
```
|
||||
|
||||
<a name='types-reference'></a>
|
||||
<a name='types-generics'></a>
|
||||
|
||||
### Reference
|
||||
### Generic Types
|
||||
|
||||
Use `Type.Ref(...)` to create referenced types. The target type must specify an `$id`.
|
||||
Generic types can be created with generic functions constrained to type `TSchema`. The following creates a generic `Vector<T>` type.
|
||||
|
||||
```typescript
|
||||
import { Type, Static, TSchema } from '@sinclair/typebox'
|
||||
|
||||
const Vector = <T extends TSchema>(t: T) => Type.Object({ x: t, y: t, z: t })
|
||||
|
||||
const NumberVector = Vector(Type.Number()) // const NumberVector = {
|
||||
// type: 'object',
|
||||
// required: ['x', 'y', 'z'],
|
||||
// properties: {
|
||||
// x: { type: 'number' },
|
||||
// y: { type: 'number' },
|
||||
// z: { type: 'number' }
|
||||
// }
|
||||
// }
|
||||
|
||||
type NumberVector = Static<typeof NumberVector> // type NumberVector = {
|
||||
// x: number,
|
||||
// y: number,
|
||||
// z: number
|
||||
// }
|
||||
|
||||
const BooleanVector = Vector(Type.Boolean()) // const BooleanVector = {
|
||||
// type: 'object',
|
||||
// required: ['x', 'y', 'z'],
|
||||
// properties: {
|
||||
// x: { type: 'boolean' },
|
||||
// y: { type: 'boolean' },
|
||||
// z: { type: 'boolean' }
|
||||
// }
|
||||
// }
|
||||
|
||||
type BooleanVector = Static<typeof BooleanVector> // type BooleanVector = {
|
||||
// x: boolean,
|
||||
// y: boolean,
|
||||
// z: boolean
|
||||
// }
|
||||
```
|
||||
|
||||
The following creates a generic `Nullable<T>` type.
|
||||
|
||||
```typescript
|
||||
const Nullable = <T extends TSchema>(schema: T) => Type.Union([schema, Type.Null()])
|
||||
|
||||
const T = Nullable(Type.String()) // const T = {
|
||||
// anyOf: [
|
||||
// { type: 'string' },
|
||||
// { type: 'null' }
|
||||
// ]
|
||||
// }
|
||||
|
||||
type T = Static<typeof T> // type T = string | null
|
||||
```
|
||||
|
||||
<a name='types-references'></a>
|
||||
|
||||
### Reference Types
|
||||
|
||||
Reference types are supported with `Type.Ref(...)`. The target type must specify a valid `$id`.
|
||||
|
||||
```typescript
|
||||
const T = Type.String({ $id: 'T' }) // const T = {
|
||||
@@ -550,9 +705,9 @@ const R = Type.Ref(T) // const R = {
|
||||
|
||||
<a name='types-recursive'></a>
|
||||
|
||||
### Recursive
|
||||
### Recursive Types
|
||||
|
||||
Use `Type.Recursive(...)` to create recursive types.
|
||||
Recursive types are supported with `Type.Recursive(...)`.
|
||||
|
||||
```typescript
|
||||
const Node = Type.Recursive(Node => Type.Object({ // const Node = {
|
||||
@@ -581,102 +736,41 @@ type Node = Static<typeof Node> // type Node = {
|
||||
// }
|
||||
|
||||
function test(node: Node) {
|
||||
const id = node.nodes[0].nodes[0] // id is string
|
||||
.nodes[0].nodes[0]
|
||||
.id
|
||||
const id = node.nodes[0].nodes[0].id // id is string
|
||||
}
|
||||
```
|
||||
|
||||
<a name='types-generic'></a>
|
||||
|
||||
### Generic
|
||||
|
||||
Use functions to create generic types. The following creates a generic `Nullable<T>` type.
|
||||
|
||||
```typescript
|
||||
import { Type, Static, TSchema } from '@sinclair/typebox'
|
||||
|
||||
const Nullable = <T extends TSchema>(type: T) => Type.Union([type, Type.Null()])
|
||||
|
||||
const T = Nullable(Type.String()) // const T = {
|
||||
// anyOf: [{
|
||||
// type: 'string'
|
||||
// }, {
|
||||
// type: 'null'
|
||||
// }]
|
||||
// }
|
||||
|
||||
type T = Static<typeof T> // type T = string | null
|
||||
|
||||
const U = Nullable(Type.Number()) // const U = {
|
||||
// anyOf: [{
|
||||
// type: 'number'
|
||||
// }, {
|
||||
// type: 'null'
|
||||
// }]
|
||||
// }
|
||||
|
||||
type U = Static<typeof U> // type U = number | null
|
||||
```
|
||||
|
||||
<a name='types-conditional'></a>
|
||||
|
||||
### Conditional
|
||||
### Conditional Types
|
||||
|
||||
Use the conditional module to create [Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html). This module implements TypeScript's structural equivalence checks to enable TypeBox types to be conditionally inferred at runtime. This module also provides the [Extract](https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union) and [Exclude](https://www.typescriptlang.org/docs/handbook/utility-types.html#excludeuniontype-excludedmembers) utility types which are expressed as conditional types in TypeScript.
|
||||
Conditional types are supported with `Extends`, `Exclude` and `Extract`.
|
||||
|
||||
The conditional module is provided as an optional import.
|
||||
**TypeScript**
|
||||
|
||||
```typescript
|
||||
import { Conditional } from '@sinclair/typebox/conditional'
|
||||
type T0 = string extends number ? true : false
|
||||
// ^ false
|
||||
type T1 = Extract<string | number, number>
|
||||
// ^ number
|
||||
type T2 = Exclude<string | number, number>
|
||||
// ^ string
|
||||
```
|
||||
The following table shows the TypeBox mappings between TypeScript and JSON schema.
|
||||
|
||||
**TypeBox**
|
||||
```typescript
|
||||
┌────────────────────────────────┬─────────────────────────────┬────────────────────────────────┐
|
||||
│ TypeBox │ TypeScript │ JSON Schema │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Conditional.Extends( │ type T = │ const T = { │
|
||||
│ Type.String(), │ string extends number │ const: false, │
|
||||
│ Type.Number(), │ true : false │ type: 'boolean' │
|
||||
│ Type.Literal(true), │ │ } │
|
||||
│ Type.Literal(false) │ │ │
|
||||
│ ) │ │ │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Conditional.Extract( │ type T = Extract< │ const T = { │
|
||||
│ Type.Union([ │ 'a' | 'b' | 'c', │ anyOf: [{ │
|
||||
│ Type.Literal('a'), │ 'a' | 'f' │ const: 'a' │
|
||||
│ Type.Literal('b'), │ > │ type: 'string' │
|
||||
│ Type.Literal('c') │ │ }] │
|
||||
│ ]), │ │ } │
|
||||
│ Type.Union([ │ │ │
|
||||
│ Type.Literal('a'), │ │ │
|
||||
│ Type.Literal('f') │ │ │
|
||||
│ ]) │ │ │
|
||||
│ ) │ │ │
|
||||
│ │ │ │
|
||||
├────────────────────────────────┼─────────────────────────────┼────────────────────────────────┤
|
||||
│ const T = Conditional.Exclude( │ type T = Exclude< │ const T = { │
|
||||
│ Type.Union([ │ 'a' | 'b' | 'c', │ anyOf: [{ │
|
||||
│ Type.Literal('a'), │ 'a' │ const: 'b', │
|
||||
│ Type.Literal('b'), │ > │ type: 'string' │
|
||||
│ Type.Literal('c') │ │ }, { │
|
||||
│ ]), │ │ const: 'c', │
|
||||
│ Type.Union([ │ │ type: 'string' │
|
||||
│ Type.Literal('a') │ │ }] │
|
||||
│ ]) │ │ } │
|
||||
│ ) │ │ │
|
||||
│ │ │ │
|
||||
└────────────────────────────────┴─────────────────────────────┴────────────────────────────────┘
|
||||
const T0 = Type.Extends(Type.String(), Type.Number(), Type.Literal(true), Type.Literal(false))
|
||||
// ^ TLiteral<false>
|
||||
const T1 = Type.Extract(Type.Union([Type.String(), Type.Number()]), Type.Number())
|
||||
// ^ TNumber
|
||||
const T2 = Type.Exclude(Type.Union([Type.String(), Type.Number()]), Type.Number())
|
||||
// ^ TString<string>
|
||||
```
|
||||
|
||||
<a name='types-unsafe'></a>
|
||||
|
||||
### Unsafe
|
||||
|
||||
Use `Type.Unsafe(...)` to create custom schemas with user defined inference rules.
|
||||
Use `Type.Unsafe(...)` to create custom schematics with user defined inference rules.
|
||||
|
||||
```typescript
|
||||
const T = Type.Unsafe<string>({ type: 'number' }) // const T = {
|
||||
@@ -686,16 +780,12 @@ const T = Type.Unsafe<string>({ type: 'number' }) // const T = {
|
||||
type T = Static<typeof T> // type T = string
|
||||
```
|
||||
|
||||
This function can be used to create custom schemas for validators that require specific schema representations. An example of this might be OpenAPI's `nullable` and `enum` schemas which are not provided by TypeBox. The following demonstrates using `Type.Unsafe(...)` to create these types.
|
||||
The `Type.Unsafe(...)` type can be useful to express specific OpenAPI schema representations.
|
||||
|
||||
```typescript
|
||||
import { Type, Static, TSchema } from '@sinclair/typebox'
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Nullable<T>
|
||||
//
|
||||
//--------------------------------------------------------------------------------------------
|
||||
|
||||
function Nullable<T extends TSchema>(schema: T) {
|
||||
return Type.Unsafe<Static<T> | null>({ ...schema, nullable: true })
|
||||
@@ -708,12 +798,7 @@ const T = Nullable(Type.String()) // const T = {
|
||||
|
||||
type T = Static<typeof T> // type T = string | null
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
//
|
||||
// StringEnum<string[]>
|
||||
//
|
||||
//--------------------------------------------------------------------------------------------
|
||||
|
||||
function StringEnum<T extends string[]>(values: [...T]) {
|
||||
return Type.Unsafe<T[number]>({ type: 'string', enum: values })
|
||||
@@ -730,10 +815,10 @@ type T = Static<typeof T> // type T = 'A' | 'B' | 'C'
|
||||
|
||||
### Guards
|
||||
|
||||
Use the guard module to test if values are TypeBox types.
|
||||
TypeBox provides a `TypeGuard` module that can be used for reflection and asserting values as types.
|
||||
|
||||
```typescript
|
||||
import { TypeGuard } from '@sinclair/typebox/guard'
|
||||
import { Type, TypeGuard } from '@sinclair/typebox'
|
||||
|
||||
const T = Type.String()
|
||||
|
||||
@@ -747,7 +832,7 @@ if(TypeGuard.TString(T)) {
|
||||
|
||||
### Strict
|
||||
|
||||
TypeBox schemas contain the `Kind` and `Modifier` symbol properties. These properties are provided to enable runtime type reflection on schemas, as well as helping TypeBox internally compose types. These properties are not strictly valid JSON schema; so in some cases it may be desirable to omit them. TypeBox provides a `Type.Strict()` function that will omit these properties if necessary.
|
||||
TypeBox schemas contain the `Kind` and `Modifier` symbol properties. These properties are used for type composition and reflection. These properties are not strictly valid JSON schema; so in some cases it may be desirable to omit them. TypeBox provides a `Type.Strict()` function that will omit these properties if necessary.
|
||||
|
||||
```typescript
|
||||
const T = Type.Object({ // const T = {
|
||||
@@ -776,7 +861,7 @@ const U = Type.Strict(T) // const U = {
|
||||
|
||||
## Values
|
||||
|
||||
TypeBox includes an optional values module that can be used to perform common operations on JavaScript values. This module enables one to create, check and cast values from types. It also provides functionality to check equality, clone and diff and patch JavaScript values. The value module is provided as an optional import.
|
||||
TypeBox provides an optional utility module that can be used to perform common operations on JavaScript values. This module includes functionality to create, check and cast values from types as well as check equality, clone, diff and patch JavaScript values. This module is provided via optional import.
|
||||
|
||||
```typescript
|
||||
import { Value } from '@sinclair/typebox/value'
|
||||
@@ -786,7 +871,7 @@ import { Value } from '@sinclair/typebox/value'
|
||||
|
||||
### Create
|
||||
|
||||
Use the Create function to create a value from a TypeBox type. TypeBox will use default values if specified.
|
||||
Use the Create function to create a value from a type. TypeBox will use default values if specified.
|
||||
|
||||
```typescript
|
||||
const T = Type.Object({ x: Type.Number(), y: Type.Number({ default: 42 }) })
|
||||
@@ -816,6 +901,20 @@ const T = Type.Object({ x: Type.Number() })
|
||||
const R = Value.Check(T, { x: 1 }) // const R = true
|
||||
```
|
||||
|
||||
<a name='values-convert'></a>
|
||||
|
||||
### Convert
|
||||
|
||||
Use the Convert function to convert a value into its target type if a reasonable conversion is possible.
|
||||
|
||||
```typescript
|
||||
const T = Type.Object({ x: Type.Number(), y: Type.Number() })
|
||||
|
||||
const R1 = Value.Convert(T, { x: '3.14' }) // const R1 = { x: 3.14 }
|
||||
|
||||
const R2 = Value.Convert(T, { x: 'not a number' }) // const R2 = { x: 'not a number' }
|
||||
```
|
||||
|
||||
<a name='values-cast'></a>
|
||||
|
||||
### Cast
|
||||
@@ -918,7 +1017,7 @@ const R = [...Value.Errors(T, { x: '42' })] // const R = [{
|
||||
|
||||
### Pointer
|
||||
|
||||
Use ValuePointer to perform mutable updates on existing values using [RFC6901](https://www.rfc-editor.org/rfc/rfc6901) Json Pointers.
|
||||
Use ValuePointer to perform mutable updates on existing values using [RFC6901](https://www.rfc-editor.org/rfc/rfc6901) JSON Pointers.
|
||||
|
||||
```typescript
|
||||
import { ValuePointer } from '@sinclair/typebox/value'
|
||||
@@ -933,15 +1032,15 @@ ValuePointer.Set(A, '/z', 1) // const A = { x: 1, y: 1,
|
||||
|
||||
## TypeCheck
|
||||
|
||||
TypeBox constructs JSON Schema draft 6 compliant schematics and can be used with any validator that supports this specification. In JavaScript, an ideal validator to use is Ajv which supports draft 6 as well as more recent revisions to the specification. In addition to Ajv, TypeBox provides an optional built in type compiler which can offer faster runtime type compilation, as well as providing high performance data validation for TypeBox types only.
|
||||
TypeBox types target JSON Schema draft 6 so are compatible with any validator that supports this specification. TypeBox also provides a built in type checking compiler designed specifically for high performance compilation and value assertion.
|
||||
|
||||
The following sections detail using these validators.
|
||||
The following sections detail using Ajv and TypeBox's compiler infrastructure.
|
||||
|
||||
<a name='typecheck-ajv'></a>
|
||||
|
||||
## Ajv
|
||||
|
||||
The following shows the recommended setup for Ajv.
|
||||
The following shows the recommended setup for Ajv.
|
||||
|
||||
```bash
|
||||
$ npm install ajv ajv-formats --save
|
||||
@@ -982,7 +1081,7 @@ const R = C({ x: 1, y: 2, z: 3 }) // const R = true
|
||||
|
||||
### TypeCompiler
|
||||
|
||||
The TypeCompiler is a Just-In-Time (JIT) runtime compiler that can be used to convert TypeBox types into fast validation routines. This compiler is specifically tuned for fast compilation and validation for TypeBox types only.
|
||||
The TypeBox TypeCompiler is a high performance JIT compiler that transforms TypeBox types into optimized JavaScript validation routines. The compiler is tuned for fast compilation as well as fast value assertion. It is designed to serve as a validation backend that can be integrated into larger applications; but can also be used as a general purpose validator.
|
||||
|
||||
The TypeCompiler is provided as an optional import.
|
||||
|
||||
@@ -1002,7 +1101,7 @@ const C = TypeCompiler.Compile(Type.Object({ // const C: TypeCheck<TObje
|
||||
const R = C.Check({ x: 1, y: 2, z: 3 }) // const R = true
|
||||
```
|
||||
|
||||
Use `Errors(...)` to generate diagnostics for a value. The `Errors(...)` function will run an exhaustive check across the value and yield any error found. For performance, this function should only be called after failed `Check(...)`.
|
||||
Use the `Errors(...)` function to produce diagnostic errors for a value. The `Errors(...)` function will return an iterator that if enumerated; will perform an exhaustive check across the entire value and yield any error found. For performance, this function should only be called after failed `Check(...)`. Applications may also choose to yield only the first value to avoid exhaustive error generation.
|
||||
|
||||
```typescript
|
||||
const C = TypeCompiler.Compile(Type.Object({ // const C: TypeCheck<TObject<{
|
||||
@@ -1047,7 +1146,7 @@ console.log(C.Code()) // return function check(va
|
||||
|
||||
## TypeSystem
|
||||
|
||||
TypeBox provides an extensible TypeSystem module that enables developers to define additional types above and beyond the built in type set. This module also allows developers to define custom string formats as well as override certain type checking behaviours.
|
||||
The TypeBox TypeSystem module provides functionality to define types above and beyond the Standard and Extended type sets as well as control various assertion polices. Configurations made to the TypeSystem module are observed by both `TypeCompiler` and `Value` modules.
|
||||
|
||||
The TypeSystem module is provided as an optional import.
|
||||
|
||||
@@ -1059,73 +1158,64 @@ import { TypeSystem } from '@sinclair/typebox/system'
|
||||
|
||||
### Types
|
||||
|
||||
Use the `CreateType(...)` function to specify custom type. This function will return a type factory function that can be used to construct the type. The following creates and registers a BigNumber type which will statically infer as `bigint`.
|
||||
Use the `Type(...)` function to create a custom type. This function will return a type factory function that can be used to construct the type. The following creates a Point type.
|
||||
|
||||
```typescript
|
||||
//--------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Use TypeSystem.CreateType(...) to define and return a type factory function
|
||||
//
|
||||
//--------------------------------------------------------------------------------------------
|
||||
type PointOptions = { } // The Type Options
|
||||
|
||||
type BigNumberOptions = { minimum?: bigint; maximum?: bigint }
|
||||
type PointType = { x: number, y: number } // The Static<T> Type
|
||||
|
||||
const BigNumber = TypeSystem.CreateType<bigint, BigNumberOptions>(
|
||||
'BigNumber',
|
||||
(options, value) => {
|
||||
if (typeof value !== 'bigint') return false
|
||||
if (options.maximum !== undefined && value > options.maximum) return false
|
||||
if (options.minimum !== undefined && value < options.minimum) return false
|
||||
return true
|
||||
}
|
||||
)
|
||||
const Point = TypeSystem.Type<PointType, PointOptions>('Point', (options, value) => {
|
||||
return (
|
||||
typeof value === 'object' && value !== null &&
|
||||
typeof value.x === 'number' &&
|
||||
typeof value.y === 'number'
|
||||
)
|
||||
})
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Use the custom type like any other type
|
||||
//
|
||||
//--------------------------------------------------------------------------------------------
|
||||
const T = Point()
|
||||
|
||||
const T = BigNumber({ minimum: 10n, maximum: 20n }) // const T = {
|
||||
// minimum: 10n,
|
||||
// maximum: 20n,
|
||||
// [Symbol(TypeBox.Kind)]: 'BigNumber'
|
||||
// }
|
||||
type T = Static<typeof T> // type T = { x: number, y: number }
|
||||
|
||||
const C = TypeCompiler.Compile(T)
|
||||
const X = C.Check(15n) // const X = true
|
||||
const Y = C.Check(5n) // const Y = false
|
||||
const Z = C.Check(25n) // const Z = false
|
||||
const R = Value.Check(T, { x: 1, y: 2 }) // const R = true
|
||||
```
|
||||
|
||||
<a name='typesystem-formats'></a>
|
||||
|
||||
### Formats
|
||||
|
||||
Use the `CreateFormat(...)` function to specify user defined string formats. The following creates a custom string format that checks for lowercase.
|
||||
Use the `Format(...)` function to create a custom string format. The following creates a custom string format that checks for lowercase strings.
|
||||
|
||||
```typescript
|
||||
//--------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Use TypeSystem.CreateFormat(...) to define a custom string format
|
||||
//
|
||||
//--------------------------------------------------------------------------------------------
|
||||
TypeSystem.Format('lowercase', value => value === value.toLowerCase()) // format should be lowercase
|
||||
|
||||
TypeSystem.CreateFormat('lowercase', value => value === value.toLowerCase())
|
||||
|
||||
//--------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Use the format by creating string types with the 'format' option
|
||||
//
|
||||
//--------------------------------------------------------------------------------------------
|
||||
|
||||
const T = Type.String({ format: 'lowercase' })
|
||||
const T = Type.String({ format: 'lowercase' })
|
||||
|
||||
const A = Value.Check(T, 'action') // const A = true
|
||||
|
||||
const B = Value.Check(T, 'ACTION') // const B = false
|
||||
```
|
||||
|
||||
<a name='typesystem-policies'></a>
|
||||
|
||||
### Policies
|
||||
|
||||
TypeBox validates using JSON Schema assertion policies by default. It is possible to override these policies and have TypeBox assert using TypeScript policies. The following overrides are available.
|
||||
|
||||
```typescript
|
||||
// Allow arrays to validate as object types (default is false)
|
||||
//
|
||||
// const A: {} = [] - allowed in TS
|
||||
|
||||
TypeSystem.AllowArrayObjects = true
|
||||
|
||||
// Allow numeric values to be NaN or + or - Infinity (default is false)
|
||||
//
|
||||
// const A: number = NaN - allowed in TS
|
||||
|
||||
TypeSystem.AllowNaN = true
|
||||
```
|
||||
|
||||
<a name='benchmark'></a>
|
||||
|
||||
## Benchmark
|
||||
@@ -1141,33 +1231,37 @@ For additional comparative benchmarks, please refer to [typescript-runtime-type-
|
||||
This benchmark measures compilation performance for varying types. You can review this benchmark [here](https://github.com/sinclairzx81/typebox/blob/master/benchmark/measurement/module/compile.ts).
|
||||
|
||||
```typescript
|
||||
┌──────────────────┬────────────┬──────────────┬──────────────┬──────────────┐
|
||||
│ (index) │ Iterations │ Ajv │ TypeCompiler │ Performance │
|
||||
├──────────────────┼────────────┼──────────────┼──────────────┼──────────────┤
|
||||
│ Number │ 2000 │ ' 451 ms' │ ' 16 ms' │ ' 28.19 x' │
|
||||
│ String │ 2000 │ ' 338 ms' │ ' 14 ms' │ ' 24.14 x' │
|
||||
│ Boolean │ 2000 │ ' 297 ms' │ ' 13 ms' │ ' 22.85 x' │
|
||||
│ Null │ 2000 │ ' 265 ms' │ ' 8 ms' │ ' 33.13 x' │
|
||||
│ RegEx │ 2000 │ ' 492 ms' │ ' 18 ms' │ ' 27.33 x' │
|
||||
│ ObjectA │ 2000 │ ' 2744 ms' │ ' 55 ms' │ ' 49.89 x' │
|
||||
│ ObjectB │ 2000 │ ' 3005 ms' │ ' 44 ms' │ ' 68.30 x' │
|
||||
│ Tuple │ 2000 │ ' 1283 ms' │ ' 26 ms' │ ' 49.35 x' │
|
||||
│ Union │ 2000 │ ' 1263 ms' │ ' 27 ms' │ ' 46.78 x' │
|
||||
│ Vector4 │ 2000 │ ' 1622 ms' │ ' 23 ms' │ ' 70.52 x' │
|
||||
│ Matrix4 │ 2000 │ ' 888 ms' │ ' 12 ms' │ ' 74.00 x' │
|
||||
│ Literal_String │ 2000 │ ' 344 ms' │ ' 14 ms' │ ' 24.57 x' │
|
||||
│ Literal_Number │ 2000 │ ' 389 ms' │ ' 8 ms' │ ' 48.63 x' │
|
||||
│ Literal_Boolean │ 2000 │ ' 374 ms' │ ' 9 ms' │ ' 41.56 x' │
|
||||
│ Array_Number │ 2000 │ ' 710 ms' │ ' 12 ms' │ ' 59.17 x' │
|
||||
│ Array_String │ 2000 │ ' 739 ms' │ ' 9 ms' │ ' 82.11 x' │
|
||||
│ Array_Boolean │ 2000 │ ' 732 ms' │ ' 7 ms' │ ' 104.57 x' │
|
||||
│ Array_ObjectA │ 2000 │ ' 3733 ms' │ ' 42 ms' │ ' 88.88 x' │
|
||||
│ Array_ObjectB │ 2000 │ ' 3602 ms' │ ' 42 ms' │ ' 85.76 x' │
|
||||
│ Array_Tuple │ 2000 │ ' 2204 ms' │ ' 20 ms' │ ' 110.20 x' │
|
||||
│ Array_Union │ 2000 │ ' 1533 ms' │ ' 24 ms' │ ' 63.88 x' │
|
||||
│ Array_Vector4 │ 2000 │ ' 2263 ms' │ ' 21 ms' │ ' 107.76 x' │
|
||||
│ Array_Matrix4 │ 2000 │ ' 1576 ms' │ ' 14 ms' │ ' 112.57 x' │
|
||||
└──────────────────┴────────────┴──────────────┴──────────────┴──────────────┘
|
||||
┌────────────────────────────┬────────────┬──────────────┬──────────────┬──────────────┐
|
||||
│ (index) │ Iterations │ Ajv │ TypeCompiler │ Performance │
|
||||
├────────────────────────────┼────────────┼──────────────┼──────────────┼──────────────┤
|
||||
│ Literal_String │ 1000 │ ' 260 ms' │ ' 8 ms' │ ' 32.50 x' │
|
||||
│ Literal_Number │ 1000 │ ' 198 ms' │ ' 4 ms' │ ' 49.50 x' │
|
||||
│ Literal_Boolean │ 1000 │ ' 185 ms' │ ' 5 ms' │ ' 37.00 x' │
|
||||
│ Primitive_Number │ 1000 │ ' 176 ms' │ ' 9 ms' │ ' 19.56 x' │
|
||||
│ Primitive_String │ 1000 │ ' 161 ms' │ ' 9 ms' │ ' 17.89 x' │
|
||||
│ Primitive_String_Pattern │ 1000 │ ' 215 ms' │ ' 12 ms' │ ' 17.92 x' │
|
||||
│ Primitive_Boolean │ 1000 │ ' 133 ms' │ ' 5 ms' │ ' 26.60 x' │
|
||||
│ Primitive_Null │ 1000 │ ' 143 ms' │ ' 8 ms' │ ' 17.88 x' │
|
||||
│ Object_Unconstrained │ 1000 │ ' 1181 ms' │ ' 38 ms' │ ' 31.08 x' │
|
||||
│ Object_Constrained │ 1000 │ ' 1168 ms' │ ' 32 ms' │ ' 36.50 x' │
|
||||
│ Tuple_Primitive │ 1000 │ ' 557 ms' │ ' 16 ms' │ ' 34.81 x' │
|
||||
│ Tuple_Object │ 1000 │ ' 1119 ms' │ ' 17 ms' │ ' 65.82 x' │
|
||||
│ Composite_Intersect │ 1000 │ ' 569 ms' │ ' 22 ms' │ ' 25.86 x' │
|
||||
│ Composite_Union │ 1000 │ ' 513 ms' │ ' 23 ms' │ ' 22.30 x' │
|
||||
│ Math_Vector4 │ 1000 │ ' 802 ms' │ ' 10 ms' │ ' 80.20 x' │
|
||||
│ Math_Matrix4 │ 1000 │ ' 395 ms' │ ' 12 ms' │ ' 32.92 x' │
|
||||
│ Array_Primitive_Number │ 1000 │ ' 282 ms' │ ' 8 ms' │ ' 35.25 x' │
|
||||
│ Array_Primitive_String │ 1000 │ ' 321 ms' │ ' 5 ms' │ ' 64.20 x' │
|
||||
│ Array_Primitive_Boolean │ 1000 │ ' 364 ms' │ ' 5 ms' │ ' 72.80 x' │
|
||||
│ Array_Object_Unconstrained │ 1000 │ ' 1573 ms' │ ' 18 ms' │ ' 87.39 x' │
|
||||
│ Array_Object_Constrained │ 1000 │ ' 1270 ms' │ ' 20 ms' │ ' 63.50 x' │
|
||||
│ Array_Tuple_Primitive │ 1000 │ ' 973 ms' │ ' 18 ms' │ ' 54.06 x' │
|
||||
│ Array_Tuple_Object │ 1000 │ ' 1253 ms' │ ' 16 ms' │ ' 78.31 x' │
|
||||
│ Array_Composite_Intersect │ 1000 │ ' 927 ms' │ ' 20 ms' │ ' 46.35 x' │
|
||||
│ Array_Composite_Union │ 1000 │ ' 1123 ms' │ ' 16 ms' │ ' 70.19 x' │
|
||||
│ Array_Math_Vector4 │ 1000 │ ' 1068 ms' │ ' 10 ms' │ ' 106.80 x' │
|
||||
│ Array_Math_Matrix4 │ 1000 │ ' 488 ms' │ ' 7 ms' │ ' 69.71 x' │
|
||||
└────────────────────────────┴────────────┴──────────────┴──────────────┴──────────────┘
|
||||
```
|
||||
|
||||
<a name='benchmark-validate'></a>
|
||||
@@ -1177,35 +1271,39 @@ This benchmark measures compilation performance for varying types. You can revie
|
||||
This benchmark measures validation performance for varying types. You can review this benchmark [here](https://github.com/sinclairzx81/typebox/blob/master/benchmark/measurement/module/check.ts).
|
||||
|
||||
```typescript
|
||||
┌──────────────────┬────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
|
||||
│ (index) │ Iterations │ ValueCheck │ Ajv │ TypeCompiler │ Performance │
|
||||
├──────────────────┼────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
|
||||
│ Number │ 1000000 │ ' 30 ms' │ ' 7 ms' │ ' 6 ms' │ ' 1.17 x' │
|
||||
│ String │ 1000000 │ ' 23 ms' │ ' 21 ms' │ ' 11 ms' │ ' 1.91 x' │
|
||||
│ Boolean │ 1000000 │ ' 22 ms' │ ' 21 ms' │ ' 10 ms' │ ' 2.10 x' │
|
||||
│ Null │ 1000000 │ ' 27 ms' │ ' 20 ms' │ ' 10 ms' │ ' 2.00 x' │
|
||||
│ RegEx │ 1000000 │ ' 163 ms' │ ' 47 ms' │ ' 38 ms' │ ' 1.24 x' │
|
||||
│ ObjectA │ 1000000 │ ' 654 ms' │ ' 41 ms' │ ' 24 ms' │ ' 1.71 x' │
|
||||
│ ObjectB │ 1000000 │ ' 1173 ms' │ ' 59 ms' │ ' 41 ms' │ ' 1.44 x' │
|
||||
│ Tuple │ 1000000 │ ' 124 ms' │ ' 24 ms' │ ' 17 ms' │ ' 1.41 x' │
|
||||
│ Union │ 1000000 │ ' 332 ms' │ ' 26 ms' │ ' 16 ms' │ ' 1.63 x' │
|
||||
│ Recursive │ 1000000 │ ' 3129 ms' │ ' 412 ms' │ ' 102 ms' │ ' 4.04 x' │
|
||||
│ Vector4 │ 1000000 │ ' 147 ms' │ ' 26 ms' │ ' 13 ms' │ ' 2.00 x' │
|
||||
│ Matrix4 │ 1000000 │ ' 576 ms' │ ' 41 ms' │ ' 28 ms' │ ' 1.46 x' │
|
||||
│ Literal_String │ 1000000 │ ' 51 ms' │ ' 21 ms' │ ' 10 ms' │ ' 2.10 x' │
|
||||
│ Literal_Number │ 1000000 │ ' 47 ms' │ ' 21 ms' │ ' 11 ms' │ ' 1.91 x' │
|
||||
│ Literal_Boolean │ 1000000 │ ' 47 ms' │ ' 21 ms' │ ' 10 ms' │ ' 2.10 x' │
|
||||
│ Array_Number │ 1000000 │ ' 490 ms' │ ' 33 ms' │ ' 18 ms' │ ' 1.83 x' │
|
||||
│ Array_String │ 1000000 │ ' 502 ms' │ ' 31 ms' │ ' 25 ms' │ ' 1.24 x' │
|
||||
│ Array_Boolean │ 1000000 │ ' 465 ms' │ ' 33 ms' │ ' 27 ms' │ ' 1.22 x' │
|
||||
│ Array_ObjectA │ 1000000 │ ' 15463 ms' │ ' 2470 ms' │ ' 2052 ms' │ ' 1.20 x' │
|
||||
│ Array_ObjectB │ 1000000 │ ' 18047 ms' │ ' 2497 ms' │ ' 2348 ms' │ ' 1.06 x' │
|
||||
│ Array_Tuple │ 1000000 │ ' 1958 ms' │ ' 99 ms' │ ' 77 ms' │ ' 1.29 x' │
|
||||
│ Array_Union │ 1000000 │ ' 5348 ms' │ ' 254 ms' │ ' 89 ms' │ ' 2.85 x' │
|
||||
│ Array_Recursive │ 1000000 │ ' 54643 ms' │ ' 8870 ms' │ ' 1158 ms' │ ' 7.66 x' │
|
||||
│ Array_Vector4 │ 1000000 │ ' 2724 ms' │ ' 105 ms' │ ' 48 ms' │ ' 2.19 x' │
|
||||
│ Array_Matrix4 │ 1000000 │ ' 13821 ms' │ ' 437 ms' │ ' 266 ms' │ ' 1.64 x' │
|
||||
└──────────────────┴────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
|
||||
┌────────────────────────────┬────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
|
||||
│ (index) │ Iterations │ ValueCheck │ Ajv │ TypeCompiler │ Performance │
|
||||
├────────────────────────────┼────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
|
||||
│ Literal_String │ 1000000 │ ' 26 ms' │ ' 6 ms' │ ' 6 ms' │ ' 1.00 x' │
|
||||
│ Literal_Number │ 1000000 │ ' 21 ms' │ ' 19 ms' │ ' 11 ms' │ ' 1.73 x' │
|
||||
│ Literal_Boolean │ 1000000 │ ' 19 ms' │ ' 18 ms' │ ' 10 ms' │ ' 1.80 x' │
|
||||
│ Primitive_Number │ 1000000 │ ' 24 ms' │ ' 19 ms' │ ' 11 ms' │ ' 1.73 x' │
|
||||
│ Primitive_String │ 1000000 │ ' 26 ms' │ ' 17 ms' │ ' 10 ms' │ ' 1.70 x' │
|
||||
│ Primitive_String_Pattern │ 1000000 │ ' 159 ms' │ ' 45 ms' │ ' 37 ms' │ ' 1.22 x' │
|
||||
│ Primitive_Boolean │ 1000000 │ ' 23 ms' │ ' 17 ms' │ ' 10 ms' │ ' 1.70 x' │
|
||||
│ Primitive_Null │ 1000000 │ ' 23 ms' │ ' 18 ms' │ ' 10 ms' │ ' 1.80 x' │
|
||||
│ Object_Unconstrained │ 1000000 │ ' 809 ms' │ ' 35 ms' │ ' 30 ms' │ ' 1.17 x' │
|
||||
│ Object_Constrained │ 1000000 │ ' 1060 ms' │ ' 56 ms' │ ' 45 ms' │ ' 1.24 x' │
|
||||
│ Object_Recursive │ 1000000 │ ' 4965 ms' │ ' 397 ms' │ ' 100 ms' │ ' 3.97 x' │
|
||||
│ Tuple_Primitive │ 1000000 │ ' 159 ms' │ ' 22 ms' │ ' 16 ms' │ ' 1.38 x' │
|
||||
│ Tuple_Object │ 1000000 │ ' 658 ms' │ ' 31 ms' │ ' 27 ms' │ ' 1.15 x' │
|
||||
│ Composite_Intersect │ 1000000 │ ' 695 ms' │ ' 26 ms' │ ' 22 ms' │ ' 1.18 x' │
|
||||
│ Composite_Union │ 1000000 │ ' 503 ms' │ ' 24 ms' │ ' 19 ms' │ ' 1.26 x' │
|
||||
│ Math_Vector4 │ 1000000 │ ' 259 ms' │ ' 22 ms' │ ' 14 ms' │ ' 1.57 x' │
|
||||
│ Math_Matrix4 │ 1000000 │ ' 1007 ms' │ ' 40 ms' │ ' 29 ms' │ ' 1.38 x' │
|
||||
│ Array_Primitive_Number │ 1000000 │ ' 262 ms' │ ' 23 ms' │ ' 17 ms' │ ' 1.35 x' │
|
||||
│ Array_Primitive_String │ 1000000 │ ' 241 ms' │ ' 27 ms' │ ' 24 ms' │ ' 1.13 x' │
|
||||
│ Array_Primitive_Boolean │ 1000000 │ ' 141 ms' │ ' 23 ms' │ ' 20 ms' │ ' 1.15 x' │
|
||||
│ Array_Object_Unconstrained │ 1000000 │ ' 4976 ms' │ ' 70 ms' │ ' 67 ms' │ ' 1.04 x' │
|
||||
│ Array_Object_Constrained │ 1000000 │ ' 5234 ms' │ ' 143 ms' │ ' 120 ms' │ ' 1.19 x' │
|
||||
│ Array_Object_Recursive │ 1000000 │ ' 19605 ms' │ ' 1909 ms' │ ' 350 ms' │ ' 5.45 x' │
|
||||
│ Array_Tuple_Primitive │ 1000000 │ ' 706 ms' │ ' 39 ms' │ ' 32 ms' │ ' 1.22 x' │
|
||||
│ Array_Tuple_Object │ 1000000 │ ' 2951 ms' │ ' 67 ms' │ ' 63 ms' │ ' 1.06 x' │
|
||||
│ Array_Composite_Intersect │ 1000000 │ ' 2969 ms' │ ' 49 ms' │ ' 44 ms' │ ' 1.11 x' │
|
||||
│ Array_Composite_Union │ 1000000 │ ' 2191 ms' │ ' 77 ms' │ ' 41 ms' │ ' 1.88 x' │
|
||||
│ Array_Math_Vector4 │ 1000000 │ ' 1164 ms' │ ' 41 ms' │ ' 25 ms' │ ' 1.64 x' │
|
||||
│ Array_Math_Matrix4 │ 1000000 │ ' 4903 ms' │ ' 115 ms' │ ' 99 ms' │ ' 1.16 x' │
|
||||
└────────────────────────────┴────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
|
||||
```
|
||||
|
||||
<a name='benchmark-compression'></a>
|
||||
@@ -1218,15 +1316,11 @@ The following table lists esbuild compiled and minified sizes for each TypeBox m
|
||||
┌──────────────────────┬────────────┬────────────┬─────────────┐
|
||||
│ (index) │ Compiled │ Minified │ Compression │
|
||||
├──────────────────────┼────────────┼────────────┼─────────────┤
|
||||
│ typebox/compiler │ ' 65.4 kb' │ ' 32.2 kb' │ '2.03 x' │
|
||||
│ typebox/conditional │ ' 45.5 kb' │ ' 18.6 kb' │ '2.45 x' │
|
||||
│ typebox/custom │ ' 0.6 kb' │ ' 0.2 kb' │ '2.61 x' │
|
||||
│ typebox/format │ ' 0.6 kb' │ ' 0.2 kb' │ '2.66 x' │
|
||||
│ typebox/guard │ ' 23.8 kb' │ ' 11.4 kb' │ '2.08 x' │
|
||||
│ typebox/hash │ ' 4.2 kb' │ ' 1.8 kb' │ '2.30 x' │
|
||||
│ typebox/system │ ' 14.0 kb' │ ' 7.1 kb' │ '1.96 x' │
|
||||
│ typebox/value │ ' 90.0 kb' │ ' 41.8 kb' │ '2.15 x' │
|
||||
│ typebox │ ' 12.0 kb' │ ' 6.4 kb' │ '1.89 x' │
|
||||
│ typebox/compiler │ '108.8 kb' │ ' 48.9 kb' │ '2.23 x' │
|
||||
│ typebox/errors │ ' 93.2 kb' │ ' 41.5 kb' │ '2.24 x' │
|
||||
│ typebox/system │ ' 60.0 kb' │ ' 24.6 kb' │ '2.43 x' │
|
||||
│ typebox/value │ '153.5 kb' │ ' 66.7 kb' │ '2.30 x' │
|
||||
│ typebox │ ' 58.7 kb' │ ' 24.1 kb' │ '2.43 x' │
|
||||
└──────────────────────┴────────────┴────────────┴─────────────┘
|
||||
```
|
||||
|
||||
|
||||
@@ -26,37 +26,28 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { ValueErrors, ValueError } from '../errors/index'
|
||||
import { TypeSystem } from '../system/index'
|
||||
import { TypeExtends, TypeGuard } from '../guard/index'
|
||||
import { Format } from '../format/index'
|
||||
import { Custom } from '../custom/index'
|
||||
import { ValueHash } from '../hash/index'
|
||||
import * as Types from '../typebox'
|
||||
import { ValueErrors, ValueErrorIterator } from '../errors/index'
|
||||
import { TypeSystem } from '../system/index'
|
||||
import { ValueHash } from '../value/hash'
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CheckFunction
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export type CheckFunction = (value: unknown) => boolean
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// TypeCheck
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export class TypeCheck<T extends Types.TSchema> {
|
||||
constructor(private readonly schema: T, private readonly references: Types.TSchema[], private readonly checkFunc: CheckFunction, private readonly code: string) {}
|
||||
|
||||
/** Returns the generated validation code used to validate this type. */
|
||||
/** Returns the generated assertion code used to validate this type. */
|
||||
public Code(): string {
|
||||
return this.code
|
||||
}
|
||||
|
||||
/** Returns an iterator for each error in this value. */
|
||||
public Errors(value: unknown): IterableIterator<ValueError> {
|
||||
public Errors(value: unknown): ValueErrorIterator {
|
||||
return ValueErrors.Errors(this.schema, this.references, value)
|
||||
}
|
||||
|
||||
/** Returns true if the value matches the compiled type. */
|
||||
public Check(value: unknown): value is Types.Static<T> {
|
||||
return this.checkFunc(value)
|
||||
@@ -79,7 +70,6 @@ namespace Character {
|
||||
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Identifier
|
||||
// -------------------------------------------------------------------
|
||||
@@ -97,7 +87,6 @@ namespace Identifier {
|
||||
return buffer.join('').replace(/__/g, '_')
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// MemberExpression
|
||||
// -------------------------------------------------------------------
|
||||
@@ -122,369 +111,386 @@ export namespace MemberExpression {
|
||||
return !Check(key) ? `${object}['${key}']` : `${object}.${key}`
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// TypeCompiler
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export class TypeCompilerUnknownTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('TypeCompiler: Unknown type')
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeCompilerDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef) {
|
||||
super(`TypeCompiler: Unable to dereference schema with $id '${schema.$ref}'`)
|
||||
}
|
||||
}
|
||||
export class TypeCompilerTypeGuardError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('TypeCompiler: Preflight validation check failed to guard for the given schema')
|
||||
}
|
||||
}
|
||||
/** Compiles Types for Runtime Type Checking */
|
||||
export namespace TypeCompiler {
|
||||
// -------------------------------------------------------------------
|
||||
// Guards
|
||||
// -------------------------------------------------------------------
|
||||
function IsNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && !globalThis.isNaN(value)
|
||||
function IsBigInt(value: unknown): value is bigint {
|
||||
return typeof value === 'bigint'
|
||||
}
|
||||
function IsNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && globalThis.Number.isFinite(value)
|
||||
}
|
||||
function IsString(value: unknown): value is string {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// Overrides
|
||||
// -------------------------------------------------------------------
|
||||
function IsNumberCheck(value: string): string {
|
||||
return !TypeSystem.AllowNaN ? `(typeof ${value} === 'number' && Number.isFinite(${value}))` : `typeof ${value} === 'number'`
|
||||
}
|
||||
function IsObjectCheck(value: string): string {
|
||||
return !TypeSystem.AllowArrayObjects ? `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}))` : `(typeof ${value} === 'object' && ${value} !== null)`
|
||||
}
|
||||
function IsRecordCheck(value: string): string {
|
||||
return !TypeSystem.AllowArrayObjects
|
||||
? `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}) && !(${value} instanceof Date) && !(${value} instanceof Uint8Array))`
|
||||
: `(typeof ${value} === 'object' && ${value} !== null && !(${value} instanceof Date) && !(${value} instanceof Uint8Array))`
|
||||
}
|
||||
function IsVoidCheck(value: string): string {
|
||||
return TypeSystem.AllowVoidNull ? `(${value} === undefined || ${value} === null)` : `${value} === undefined`
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Types
|
||||
// -------------------------------------------------------------------
|
||||
function* Any(schema: Types.TAny, value: string): IterableIterator<string> {
|
||||
yield '(true)'
|
||||
function* Any(schema: Types.TAny, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield 'true'
|
||||
}
|
||||
|
||||
function* Array(schema: Types.TArray, value: string): IterableIterator<string> {
|
||||
const expression = CreateExpression(schema.items, 'value')
|
||||
yield `(Array.isArray(${value}) && ${value}.every(value => ${expression}))`
|
||||
if (IsNumber(schema.minItems)) yield `(${value}.length >= ${schema.minItems})`
|
||||
if (IsNumber(schema.maxItems)) yield `(${value}.length <= ${schema.maxItems})`
|
||||
function* Array(schema: Types.TArray, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const expression = CreateExpression(schema.items, references, 'value')
|
||||
yield `Array.isArray(${value}) && ${value}.every(value => ${expression})`
|
||||
if (IsNumber(schema.minItems)) yield `${value}.length >= ${schema.minItems}`
|
||||
if (IsNumber(schema.maxItems)) yield `${value}.length <= ${schema.maxItems}`
|
||||
if (schema.uniqueItems === true) yield `((function() { const set = new Set(); for(const element of ${value}) { const hashed = hash(element); if(set.has(hashed)) { return false } else { set.add(hashed) } } return true })())`
|
||||
}
|
||||
|
||||
function* Boolean(schema: Types.TBoolean, value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'boolean')`
|
||||
function* BigInt(schema: Types.TBigInt, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'bigint')`
|
||||
if (IsBigInt(schema.multipleOf)) yield `(${value} % BigInt(${schema.multipleOf})) === 0`
|
||||
if (IsBigInt(schema.exclusiveMinimum)) yield `${value} > BigInt(${schema.exclusiveMinimum})`
|
||||
if (IsBigInt(schema.exclusiveMaximum)) yield `${value} < BigInt(${schema.exclusiveMaximum})`
|
||||
if (IsBigInt(schema.minimum)) yield `${value} >= BigInt(${schema.minimum})`
|
||||
if (IsBigInt(schema.maximum)) yield `${value} <= BigInt(${schema.maximum})`
|
||||
}
|
||||
|
||||
function* Constructor(schema: Types.TConstructor, value: string): IterableIterator<string> {
|
||||
yield* Visit(schema.returns, `${value}.prototype`)
|
||||
function* Boolean(schema: Types.TBoolean, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `typeof ${value} === 'boolean'`
|
||||
}
|
||||
|
||||
function* Date(schema: Types.TDate, value: string): IterableIterator<string> {
|
||||
yield `(${value} instanceof Date) && !isNaN(${value}.getTime())`
|
||||
if (IsNumber(schema.exclusiveMinimumTimestamp)) yield `(${value}.getTime() > ${schema.exclusiveMinimumTimestamp})`
|
||||
if (IsNumber(schema.exclusiveMaximumTimestamp)) yield `(${value}.getTime() < ${schema.exclusiveMaximumTimestamp})`
|
||||
if (IsNumber(schema.minimumTimestamp)) yield `(${value}.getTime() >= ${schema.minimumTimestamp})`
|
||||
if (IsNumber(schema.maximumTimestamp)) yield `(${value}.getTime() <= ${schema.maximumTimestamp})`
|
||||
function* Constructor(schema: Types.TConstructor, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield* Visit(schema.returns, references, `${value}.prototype`)
|
||||
}
|
||||
|
||||
function* Function(schema: Types.TFunction, value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'function')`
|
||||
function* Date(schema: Types.TDate, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(${value} instanceof Date) && Number.isFinite(${value}.getTime())`
|
||||
if (IsNumber(schema.exclusiveMinimumTimestamp)) yield `${value}.getTime() > ${schema.exclusiveMinimumTimestamp}`
|
||||
if (IsNumber(schema.exclusiveMaximumTimestamp)) yield `${value}.getTime() < ${schema.exclusiveMaximumTimestamp}`
|
||||
if (IsNumber(schema.minimumTimestamp)) yield `${value}.getTime() >= ${schema.minimumTimestamp}`
|
||||
if (IsNumber(schema.maximumTimestamp)) yield `${value}.getTime() <= ${schema.maximumTimestamp}`
|
||||
}
|
||||
|
||||
function* Integer(schema: Types.TInteger, value: string): IterableIterator<string> {
|
||||
function* Function(schema: Types.TFunction, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `typeof ${value} === 'function'`
|
||||
}
|
||||
function* Integer(schema: Types.TInteger, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'number' && Number.isInteger(${value}))`
|
||||
if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf} === 0)`
|
||||
if (IsNumber(schema.exclusiveMinimum)) yield `(${value} > ${schema.exclusiveMinimum})`
|
||||
if (IsNumber(schema.exclusiveMaximum)) yield `(${value} < ${schema.exclusiveMaximum})`
|
||||
if (IsNumber(schema.minimum)) yield `(${value} >= ${schema.minimum})`
|
||||
if (IsNumber(schema.maximum)) yield `(${value} <= ${schema.maximum})`
|
||||
if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0`
|
||||
if (IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}`
|
||||
if (IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}`
|
||||
if (IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}`
|
||||
if (IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}`
|
||||
}
|
||||
|
||||
function* Literal(schema: Types.TLiteral, value: string): IterableIterator<string> {
|
||||
function* Intersect(schema: Types.TIntersect, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
if (schema.unevaluatedProperties === undefined) {
|
||||
const expressions = schema.allOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value))
|
||||
yield `${expressions.join(' && ')}`
|
||||
} else if (schema.unevaluatedProperties === false) {
|
||||
// prettier-ignore
|
||||
const schemaKeys = Types.KeyResolver.Resolve(schema).map((key) => `'${key}'`).join(', ')
|
||||
const expressions = schema.allOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value))
|
||||
const expression1 = `Object.getOwnPropertyNames(${value}).every(key => [${schemaKeys}].includes(key))`
|
||||
yield `${expressions.join(' && ')} && ${expression1}`
|
||||
} else if (typeof schema.unevaluatedProperties === 'object') {
|
||||
// prettier-ignore
|
||||
const schemaKeys = Types.KeyResolver.Resolve(schema).map((key) => `'${key}'`).join(', ')
|
||||
const expressions = schema.allOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value))
|
||||
const expression1 = CreateExpression(schema.unevaluatedProperties, references, 'value[key]')
|
||||
const expression2 = `Object.getOwnPropertyNames(${value}).every(key => [${schemaKeys}].includes(key) || ${expression1})`
|
||||
yield `${expressions.join(' && ')} && ${expression2}`
|
||||
}
|
||||
}
|
||||
function* Literal(schema: Types.TLiteral, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
if (typeof schema.const === 'number' || typeof schema.const === 'boolean') {
|
||||
yield `(${value} === ${schema.const})`
|
||||
yield `${value} === ${schema.const}`
|
||||
} else {
|
||||
yield `(${value} === '${schema.const}')`
|
||||
yield `${value} === '${schema.const}'`
|
||||
}
|
||||
}
|
||||
|
||||
function* Never(schema: Types.TNever, value: string): IterableIterator<string> {
|
||||
yield `(false)`
|
||||
function* Never(schema: Types.TNever, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `false`
|
||||
}
|
||||
function* Not(schema: Types.TNot, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const left = CreateExpression(schema.allOf[0].not, references, value)
|
||||
const right = CreateExpression(schema.allOf[1], references, value)
|
||||
yield `!${left} && ${right}`
|
||||
}
|
||||
function* Null(schema: Types.TNull, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `${value} === null`
|
||||
}
|
||||
function* Number(schema: Types.TNumber, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield IsNumberCheck(value)
|
||||
if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0`
|
||||
if (IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}`
|
||||
if (IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}`
|
||||
if (IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}`
|
||||
if (IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}`
|
||||
}
|
||||
|
||||
function* Null(schema: Types.TNull, value: string): IterableIterator<string> {
|
||||
yield `(${value} === null)`
|
||||
}
|
||||
|
||||
function* Number(schema: Types.TNumber, value: string): IterableIterator<string> {
|
||||
if (TypeSystem.AllowNaN) {
|
||||
yield `(typeof ${value} === 'number')`
|
||||
} else {
|
||||
yield `(typeof ${value} === 'number' && !isNaN(${value}))`
|
||||
}
|
||||
if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf} === 0)`
|
||||
if (IsNumber(schema.exclusiveMinimum)) yield `(${value} > ${schema.exclusiveMinimum})`
|
||||
if (IsNumber(schema.exclusiveMaximum)) yield `(${value} < ${schema.exclusiveMaximum})`
|
||||
if (IsNumber(schema.minimum)) yield `(${value} >= ${schema.minimum})`
|
||||
if (IsNumber(schema.maximum)) yield `(${value} <= ${schema.maximum})`
|
||||
}
|
||||
|
||||
function* Object(schema: Types.TObject, value: string): IterableIterator<string> {
|
||||
if (TypeSystem.AllowArrayObjects) {
|
||||
yield `(typeof ${value} === 'object' && ${value} !== null)`
|
||||
} else {
|
||||
yield `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}))`
|
||||
}
|
||||
if (IsNumber(schema.minProperties)) yield `(Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties})`
|
||||
if (IsNumber(schema.maxProperties)) yield `(Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties})`
|
||||
const propertyKeys = globalThis.Object.getOwnPropertyNames(schema.properties)
|
||||
if (schema.additionalProperties === false) {
|
||||
// Optimization: If the property key length matches the required keys length
|
||||
// then we only need check that the values property key length matches that
|
||||
// of the property key length. This is because exhaustive testing for values
|
||||
// will occur in subsequent property tests.
|
||||
if (schema.required && schema.required.length === propertyKeys.length) {
|
||||
yield `(Object.getOwnPropertyNames(${value}).length === ${propertyKeys.length})`
|
||||
function* Object(schema: Types.TObject, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield IsObjectCheck(value)
|
||||
if (!TypeSystem.AllowArrayObjects) yield `!Array.isArray(${value})`
|
||||
if (IsNumber(schema.minProperties)) yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}`
|
||||
if (IsNumber(schema.maxProperties)) yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}`
|
||||
const schemaKeys = globalThis.Object.getOwnPropertyNames(schema.properties)
|
||||
for (const schemaKey of schemaKeys) {
|
||||
const memberExpression = MemberExpression.Encode(value, schemaKey)
|
||||
const property = schema.properties[schemaKey]
|
||||
if (schema.required && schema.required.includes(schemaKey)) {
|
||||
yield* Visit(property, references, memberExpression)
|
||||
if (Types.ExtendsUndefined.Check(property)) yield `('${schemaKey}' in ${value})`
|
||||
} else {
|
||||
const keys = `[${propertyKeys.map((key) => `'${key}'`).join(', ')}]`
|
||||
yield `(Object.getOwnPropertyNames(${value}).every(key => ${keys}.includes(key)))`
|
||||
const expression = CreateExpression(property, references, memberExpression)
|
||||
yield `('${schemaKey}' in ${value} ? ${expression} : true)`
|
||||
}
|
||||
}
|
||||
if (TypeGuard.TSchema(schema.additionalProperties)) {
|
||||
const expression = CreateExpression(schema.additionalProperties, 'value[key]')
|
||||
const keys = `[${propertyKeys.map((key) => `'${key}'`).join(', ')}]`
|
||||
if (schema.additionalProperties === false) {
|
||||
if (schema.required && schema.required.length === schemaKeys.length) {
|
||||
yield `Object.getOwnPropertyNames(${value}).length === ${schemaKeys.length}`
|
||||
} else {
|
||||
const keys = `[${schemaKeys.map((key) => `'${key}'`).join(', ')}]`
|
||||
yield `Object.getOwnPropertyNames(${value}).every(key => ${keys}.includes(key))`
|
||||
}
|
||||
}
|
||||
if (typeof schema.additionalProperties === 'object') {
|
||||
const expression = CreateExpression(schema.additionalProperties, references, 'value[key]')
|
||||
const keys = `[${schemaKeys.map((key) => `'${key}'`).join(', ')}]`
|
||||
yield `(Object.getOwnPropertyNames(${value}).every(key => ${keys}.includes(key) || ${expression}))`
|
||||
}
|
||||
for (const propertyKey of propertyKeys) {
|
||||
const memberExpression = MemberExpression.Encode(value, propertyKey)
|
||||
const propertySchema = schema.properties[propertyKey]
|
||||
if (schema.required && schema.required.includes(propertyKey)) {
|
||||
yield* Visit(propertySchema, memberExpression)
|
||||
if (TypeExtends.Undefined(propertySchema)) {
|
||||
yield `('${propertyKey}' in ${value})`
|
||||
}
|
||||
} else {
|
||||
const expression = CreateExpression(propertySchema, memberExpression)
|
||||
yield `(${memberExpression} === undefined ? true : (${expression}))`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function* Promise(schema: Types.TPromise<any>, value: string): IterableIterator<string> {
|
||||
function* Promise(schema: Types.TPromise<any>, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof value === 'object' && typeof ${value}.then === 'function')`
|
||||
}
|
||||
|
||||
function* Record(schema: Types.TRecord<any, any>, value: string): IterableIterator<string> {
|
||||
if (TypeSystem.AllowArrayObjects) {
|
||||
yield `(typeof ${value} === 'object' && ${value} !== null && !(${value} instanceof Date))`
|
||||
} else {
|
||||
yield `(typeof ${value} === 'object' && ${value} !== null && !(${value} instanceof Date) && !Array.isArray(${value}))`
|
||||
}
|
||||
function* Record(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield IsRecordCheck(value)
|
||||
const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0]
|
||||
const local = PushLocal(`new RegExp(/${keyPattern}/)`)
|
||||
yield `(Object.getOwnPropertyNames(${value}).every(key => ${local}.test(key)))`
|
||||
const expression = CreateExpression(valueSchema, 'value')
|
||||
yield `(Object.values(${value}).every(value => ${expression}))`
|
||||
const expression = CreateExpression(valueSchema, references, 'value')
|
||||
yield `Object.values(${value}).every(value => ${expression})`
|
||||
}
|
||||
|
||||
function* Ref(schema: Types.TRef<any>, value: string): IterableIterator<string> {
|
||||
function* Ref(schema: Types.TRef<any>, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new TypeCompilerDereferenceError(schema)
|
||||
const target = references[index]
|
||||
// Reference: If we have seen this reference before we can just yield and return
|
||||
// the function call. If this isn't the case we defer to visit to generate and
|
||||
// set the function for subsequent passes. Consider for refactor.
|
||||
if (state_local_function_names.has(schema.$ref)) return yield `(${CreateFunctionName(schema.$ref)}(${value}))`
|
||||
if (!state_reference_map.has(schema.$ref)) throw Error(`TypeCompiler.Ref: Cannot de-reference schema with $id '${schema.$ref}'`)
|
||||
const reference = state_reference_map.get(schema.$ref)!
|
||||
yield* Visit(reference, value)
|
||||
if (state_local_function_names.has(schema.$ref)) return yield `${CreateFunctionName(schema.$ref)}(${value})`
|
||||
yield* Visit(target, references, value)
|
||||
}
|
||||
|
||||
function* Self(schema: Types.TSelf, value: string): IterableIterator<string> {
|
||||
function* Self(schema: Types.TSelf, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const func = CreateFunctionName(schema.$ref)
|
||||
yield `(${func}(${value}))`
|
||||
yield `${func}(${value})`
|
||||
}
|
||||
|
||||
function* String(schema: Types.TString, value: string): IterableIterator<string> {
|
||||
function* String(schema: Types.TString, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'string')`
|
||||
if (IsNumber(schema.minLength)) yield `(${value}.length >= ${schema.minLength})`
|
||||
if (IsNumber(schema.maxLength)) yield `(${value}.length <= ${schema.maxLength})`
|
||||
if (IsNumber(schema.minLength)) yield `${value}.length >= ${schema.minLength}`
|
||||
if (IsNumber(schema.maxLength)) yield `${value}.length <= ${schema.maxLength}`
|
||||
if (schema.pattern !== undefined) {
|
||||
const local = PushLocal(`${new RegExp(schema.pattern)};`)
|
||||
yield `(${local}.test(${value}))`
|
||||
yield `${local}.test(${value})`
|
||||
}
|
||||
if (schema.format !== undefined) {
|
||||
yield `(format('${schema.format}', ${value}))`
|
||||
yield `format('${schema.format}', ${value})`
|
||||
}
|
||||
}
|
||||
|
||||
function* Tuple(schema: Types.TTuple<any[]>, value: string): IterableIterator<string> {
|
||||
function* Symbol(schema: Types.TSymbol, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(typeof ${value} === 'symbol')`
|
||||
}
|
||||
function* Tuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `(Array.isArray(${value}))`
|
||||
if (schema.items === undefined) return yield `(${value}.length === 0)`
|
||||
if (schema.items === undefined) return yield `${value}.length === 0`
|
||||
yield `(${value}.length === ${schema.maxItems})`
|
||||
for (let i = 0; i < schema.items.length; i++) {
|
||||
const expression = CreateExpression(schema.items[i], `${value}[${i}]`)
|
||||
yield `(${expression})`
|
||||
const expression = CreateExpression(schema.items[i], references, `${value}[${i}]`)
|
||||
yield `${expression}`
|
||||
}
|
||||
}
|
||||
|
||||
function* Undefined(schema: Types.TUndefined, value: string): IterableIterator<string> {
|
||||
yield `(${value} === undefined)`
|
||||
function* Undefined(schema: Types.TUndefined, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `${value} === undefined`
|
||||
}
|
||||
|
||||
function* Union(schema: Types.TUnion<any[]>, value: string): IterableIterator<string> {
|
||||
const expressions = schema.anyOf.map((schema: Types.TSchema) => CreateExpression(schema, value))
|
||||
function* Union(schema: Types.TUnion<any[]>, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const expressions = schema.anyOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value))
|
||||
yield `(${expressions.join(' || ')})`
|
||||
}
|
||||
|
||||
function* Uint8Array(schema: Types.TUint8Array, value: string): IterableIterator<string> {
|
||||
yield `(${value} instanceof Uint8Array)`
|
||||
function* Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield `${value} instanceof Uint8Array`
|
||||
if (IsNumber(schema.maxByteLength)) yield `(${value}.length <= ${schema.maxByteLength})`
|
||||
if (IsNumber(schema.minByteLength)) yield `(${value}.length >= ${schema.minByteLength})`
|
||||
}
|
||||
|
||||
function* Unknown(schema: Types.TUnknown, value: string): IterableIterator<string> {
|
||||
yield '(true)'
|
||||
function* Unknown(schema: Types.TUnknown, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield 'true'
|
||||
}
|
||||
|
||||
function* Void(schema: Types.TVoid, value: string): IterableIterator<string> {
|
||||
yield `(${value} === null)`
|
||||
function* Void(schema: Types.TVoid, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
yield IsVoidCheck(value)
|
||||
}
|
||||
|
||||
function* UserDefined(schema: Types.TSchema, value: string): IterableIterator<string> {
|
||||
function* UserDefined(schema: Types.TSchema, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const schema_key = `schema_key_${state_remote_custom_types.size}`
|
||||
state_remote_custom_types.set(schema_key, schema)
|
||||
yield `(custom('${schema[Types.Kind]}', '${schema_key}', ${value}))`
|
||||
yield `custom('${schema[Types.Kind]}', '${schema_key}', ${value})`
|
||||
}
|
||||
|
||||
function* Visit<T extends Types.TSchema>(schema: T, value: string): IterableIterator<string> {
|
||||
function* Visit<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: string): IterableIterator<string> {
|
||||
const references_ = IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
// Reference: Referenced schemas can originate from either additional schemas
|
||||
// or inline in the schema itself. Ideally the recursive path should align to
|
||||
// reference path. Consider for refactor.
|
||||
if (schema.$id && !state_local_function_names.has(schema.$id)) {
|
||||
if (IsString(schema.$id) && !state_local_function_names.has(schema.$id)) {
|
||||
state_local_function_names.add(schema.$id)
|
||||
const name = CreateFunctionName(schema.$id)
|
||||
const body = CreateFunction(name, schema, 'value')
|
||||
const body = CreateFunction(name, schema, references, 'value')
|
||||
PushFunction(body)
|
||||
yield `(${name}(${value}))`
|
||||
yield `${name}(${value})`
|
||||
return
|
||||
}
|
||||
const anySchema = schema as any
|
||||
switch (anySchema[Types.Kind]) {
|
||||
switch (schema_[Types.Kind]) {
|
||||
case 'Any':
|
||||
return yield* Any(anySchema, value)
|
||||
return yield* Any(schema_, references_, value)
|
||||
case 'Array':
|
||||
return yield* Array(anySchema, value)
|
||||
return yield* Array(schema_, references_, value)
|
||||
case 'BigInt':
|
||||
return yield* BigInt(schema_, references_, value)
|
||||
case 'Boolean':
|
||||
return yield* Boolean(anySchema, value)
|
||||
return yield* Boolean(schema_, references_, value)
|
||||
case 'Constructor':
|
||||
return yield* Constructor(anySchema, value)
|
||||
return yield* Constructor(schema_, references_, value)
|
||||
case 'Date':
|
||||
return yield* Date(anySchema, value)
|
||||
return yield* Date(schema_, references_, value)
|
||||
case 'Function':
|
||||
return yield* Function(anySchema, value)
|
||||
return yield* Function(schema_, references_, value)
|
||||
case 'Integer':
|
||||
return yield* Integer(anySchema, value)
|
||||
return yield* Integer(schema_, references_, value)
|
||||
case 'Intersect':
|
||||
return yield* Intersect(schema_, references_, value)
|
||||
case 'Literal':
|
||||
return yield* Literal(anySchema, value)
|
||||
return yield* Literal(schema_, references_, value)
|
||||
case 'Never':
|
||||
return yield* Never(anySchema, value)
|
||||
return yield* Never(schema_, references_, value)
|
||||
case 'Not':
|
||||
return yield* Not(schema_, references_, value)
|
||||
case 'Null':
|
||||
return yield* Null(anySchema, value)
|
||||
return yield* Null(schema_, references_, value)
|
||||
case 'Number':
|
||||
return yield* Number(anySchema, value)
|
||||
return yield* Number(schema_, references_, value)
|
||||
case 'Object':
|
||||
return yield* Object(anySchema, value)
|
||||
return yield* Object(schema_, references_, value)
|
||||
case 'Promise':
|
||||
return yield* Promise(anySchema, value)
|
||||
return yield* Promise(schema_, references_, value)
|
||||
case 'Record':
|
||||
return yield* Record(anySchema, value)
|
||||
return yield* Record(schema_, references_, value)
|
||||
case 'Ref':
|
||||
return yield* Ref(anySchema, value)
|
||||
return yield* Ref(schema_, references_, value)
|
||||
case 'Self':
|
||||
return yield* Self(anySchema, value)
|
||||
return yield* Self(schema_, references_, value)
|
||||
case 'String':
|
||||
return yield* String(anySchema, value)
|
||||
return yield* String(schema_, references_, value)
|
||||
case 'Symbol':
|
||||
return yield* Symbol(schema_, references_, value)
|
||||
case 'Tuple':
|
||||
return yield* Tuple(anySchema, value)
|
||||
return yield* Tuple(schema_, references_, value)
|
||||
case 'Undefined':
|
||||
return yield* Undefined(anySchema, value)
|
||||
return yield* Undefined(schema_, references_, value)
|
||||
case 'Union':
|
||||
return yield* Union(anySchema, value)
|
||||
return yield* Union(schema_, references_, value)
|
||||
case 'Uint8Array':
|
||||
return yield* Uint8Array(anySchema, value)
|
||||
return yield* Uint8Array(schema_, references_, value)
|
||||
case 'Unknown':
|
||||
return yield* Unknown(anySchema, value)
|
||||
return yield* Unknown(schema_, references_, value)
|
||||
case 'Void':
|
||||
return yield* Void(anySchema, value)
|
||||
return yield* Void(schema_, references_, value)
|
||||
default:
|
||||
if (!Custom.Has(anySchema[Types.Kind])) throw new TypeCompilerUnknownTypeError(schema)
|
||||
return yield* UserDefined(anySchema, value)
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new TypeCompilerUnknownTypeError(schema)
|
||||
return yield* UserDefined(schema_, references_, value)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Compiler State
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const state_reference_map = new Map<string, Types.TSchema>() // tracks schemas with identifiers
|
||||
const state_local_variables = new Set<string>() // local variables and functions
|
||||
const state_local_function_names = new Set<string>() // local function names used call ref validators
|
||||
const state_remote_custom_types = new Map<string, unknown>() // remote custom types used during compilation
|
||||
|
||||
function ResetCompiler() {
|
||||
state_reference_map.clear()
|
||||
state_local_variables.clear()
|
||||
state_local_function_names.clear()
|
||||
state_remote_custom_types.clear()
|
||||
}
|
||||
|
||||
function AddReferences(schemas: Types.TSchema[] = []) {
|
||||
for (const schema of schemas) {
|
||||
if (!schema.$id) throw new Error(`TypeCompiler: Referenced schemas must specify an $id.`)
|
||||
if (state_reference_map.has(schema.$id)) throw new Error(`TypeCompiler: Duplicate schema $id found for '${schema.$id}'`)
|
||||
state_reference_map.set(schema.$id, schema)
|
||||
}
|
||||
function CreateExpression(schema: Types.TSchema, references: Types.TSchema[], value: string): string {
|
||||
return `(${[...Visit(schema, references, value)].join(' && ')})`
|
||||
}
|
||||
|
||||
function CreateExpression(schema: Types.TSchema, value: string): string {
|
||||
return `(${[...Visit(schema, value)].join(' && ')})`
|
||||
}
|
||||
|
||||
function CreateFunctionName($id: string) {
|
||||
return `check_${Identifier.Encode($id)}`
|
||||
}
|
||||
|
||||
function CreateFunction(name: string, schema: Types.TSchema, value: string): string {
|
||||
const expression = [...Visit(schema, value)].map((condition) => ` ${condition}`).join(' &&\n')
|
||||
function CreateFunction(name: string, schema: Types.TSchema, references: Types.TSchema[], value: string): string {
|
||||
const expression = [...Visit(schema, references, value)].map((condition) => ` ${condition}`).join(' &&\n')
|
||||
return `function ${name}(value) {\n return (\n${expression}\n )\n}`
|
||||
}
|
||||
|
||||
function PushFunction(functionBody: string) {
|
||||
state_local_variables.add(functionBody)
|
||||
}
|
||||
|
||||
function PushLocal(expression: string) {
|
||||
const local = `local_${state_local_variables.size}`
|
||||
state_local_variables.add(`const ${local} = ${expression}`)
|
||||
return local
|
||||
}
|
||||
|
||||
function GetLocals() {
|
||||
return [...state_local_variables.values()]
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Compile
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
function Build<T extends Types.TSchema>(schema: T, references: Types.TSchema[] = []): string {
|
||||
function Build<T extends Types.TSchema>(schema: T, references: Types.TSchema[]): string {
|
||||
ResetCompiler()
|
||||
AddReferences(references)
|
||||
const check = CreateFunction('check', schema, 'value')
|
||||
const check = CreateFunction('check', schema, references, 'value')
|
||||
const locals = GetLocals()
|
||||
return `${locals.join('\n')}\nreturn ${check}`
|
||||
}
|
||||
|
||||
/** Returns the generated assertion code used to validate this type. */
|
||||
export function Code<T extends Types.TSchema>(schema: T, references: Types.TSchema[] = []) {
|
||||
if (!Types.TypeGuard.TSchema(schema)) throw new TypeCompilerTypeGuardError(schema)
|
||||
for (const schema of references) if (!Types.TypeGuard.TSchema(schema)) throw new TypeCompilerTypeGuardError(schema)
|
||||
return Build(schema, references)
|
||||
}
|
||||
/** Compiles the given type for runtime type checking. This compiler only accepts known TypeBox types non-inclusive of unsafe types. */
|
||||
export function Compile<T extends Types.TSchema>(schema: T, references: Types.TSchema[] = []): TypeCheck<T> {
|
||||
TypeGuard.Assert(schema, references)
|
||||
const code = Build(schema, references)
|
||||
const code = Code(schema, references)
|
||||
const custom_schemas = new Map(state_remote_custom_types)
|
||||
const compiledFunction = globalThis.Function('custom', 'format', 'hash', code)
|
||||
const checkFunction = compiledFunction(
|
||||
(kind: string, schema_key: string, value: unknown) => {
|
||||
if (!Custom.Has(kind) || !custom_schemas.has(schema_key)) return false
|
||||
if (!Types.TypeRegistry.Has(kind) || !custom_schemas.has(schema_key)) return false
|
||||
const schema = custom_schemas.get(schema_key)!
|
||||
const func = Custom.Get(kind)!
|
||||
const func = Types.TypeRegistry.Get(kind)!
|
||||
return func(schema, value)
|
||||
},
|
||||
(format: string, value: string) => {
|
||||
if (!Format.Has(format)) return false
|
||||
const func = Format.Get(format)!
|
||||
if (!Types.FormatRegistry.Has(format)) return false
|
||||
const func = Types.FormatRegistry.Get(format)!
|
||||
return func(value)
|
||||
},
|
||||
(value: unknown) => {
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/conditional
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as Types from '../typebox'
|
||||
import { Structural, StructuralResult } from './structural'
|
||||
import { TypeGuard } from '../guard/index'
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Extends
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
export type TExtends<L extends Types.TSchema, R extends Types.TSchema, T extends Types.TSchema, U extends Types.TSchema> = Types.Static<L> extends Types.Static<R> ? T : U
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Exclude
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
export interface TExclude<T extends Types.TUnion, U extends Types.TUnion> extends Types.TUnion<any[]> {
|
||||
static: Exclude<Types.Static<T, this['params']>, Types.Static<U, this['params']>>
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Extract
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
export interface TExtract<T extends Types.TSchema, U extends Types.TUnion> extends Types.TUnion<any[]> {
|
||||
static: Extract<Types.Static<T, this['params']>, Types.Static<U, this['params']>>
|
||||
}
|
||||
|
||||
/** Conditional type mapping for TypeBox types */
|
||||
export namespace Conditional {
|
||||
/** (Experimental) Creates a conditional expression type */
|
||||
export function Extends<L extends Types.TSchema, R extends Types.TSchema, T extends Types.TSchema, U extends Types.TSchema>(left: L, right: R, ok: T, fail: U): TExtends<L, R, T, U> {
|
||||
switch (Structural.Check(left, right)) {
|
||||
case StructuralResult.Union:
|
||||
return Types.Type.Union([Clone(ok), Clone(fail)]) as any as TExtends<L, R, T, U>
|
||||
case StructuralResult.True:
|
||||
return Clone(ok)
|
||||
case StructuralResult.False:
|
||||
return Clone(fail)
|
||||
}
|
||||
}
|
||||
|
||||
/** (Experimental) Constructs a type by excluding from UnionType all union members that are assignable to ExcludedMembers. */
|
||||
export function Exclude<T extends Types.TUnion, U extends Types.TUnion>(unionType: T, excludedMembers: U, options: Types.SchemaOptions = {}): TExclude<T, U> {
|
||||
const anyOf = unionType.anyOf
|
||||
.filter((schema) => {
|
||||
const check = Structural.Check(schema, excludedMembers)
|
||||
return !(check === StructuralResult.True || check === StructuralResult.Union)
|
||||
})
|
||||
.map((schema) => Clone(schema))
|
||||
return { ...options, [Types.Kind]: 'Union', anyOf } as any
|
||||
}
|
||||
|
||||
/** (Experimental) Constructs a type by extracting from Type all union members that are assignable to Union. */
|
||||
export function Extract<T extends Types.TSchema, U extends Types.TUnion>(type: T, union: U, options: Types.SchemaOptions = {}): TExtract<T, U> {
|
||||
if (TypeGuard.TUnion(type)) {
|
||||
const anyOf = type.anyOf.filter((schema: Types.TSchema) => Structural.Check(schema, union) === StructuralResult.True).map((schema: Types.TSchema) => Clone(schema))
|
||||
return { ...options, [Types.Kind]: 'Union', anyOf } as any
|
||||
} else {
|
||||
const anyOf = union.anyOf.filter((schema) => Structural.Check(type, schema) === StructuralResult.True).map((schema) => Clone(schema))
|
||||
return { ...options, [Types.Kind]: 'Union', anyOf } as any
|
||||
}
|
||||
}
|
||||
|
||||
function Clone(value: any): any {
|
||||
const isObject = (object: any): object is Record<string | symbol, any> => typeof object === 'object' && object !== null && !Array.isArray(object)
|
||||
const isArray = (object: any): object is any[] => typeof object === 'object' && object !== null && Array.isArray(object)
|
||||
if (isObject(value)) {
|
||||
return Object.keys(value).reduce(
|
||||
(acc, key) => ({
|
||||
...acc,
|
||||
[key]: Clone(value[key]),
|
||||
}),
|
||||
Object.getOwnPropertySymbols(value).reduce(
|
||||
(acc, key) => ({
|
||||
...acc,
|
||||
[key]: Clone(value[key]),
|
||||
}),
|
||||
{},
|
||||
),
|
||||
)
|
||||
} else if (isArray(value)) {
|
||||
return value.map((item: any) => Clone(item))
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/conditional
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export * from './conditional'
|
||||
export * from './structural'
|
||||
@@ -1,579 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/conditional
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as Types from '../typebox'
|
||||
import { TypeGuard } from '../guard'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// StructuralResult
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
export enum StructuralResult {
|
||||
Union,
|
||||
True,
|
||||
False,
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Structural
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/** Performs structural equivalence checks against TypeBox types. */
|
||||
export namespace Structural {
|
||||
const referenceMap = new Map<string, Types.TAnySchema>()
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Rules
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
function AnyUnknownOrCustomRule(right: Types.TSchema) {
|
||||
// https://github.com/microsoft/TypeScript/issues/40049
|
||||
if (TypeGuard.TUnion(right) && right.anyOf.some((schema: Types.TSchema) => schema[Types.Kind] === 'Any' || schema[Types.Kind] === 'Unknown')) return true
|
||||
if (TypeGuard.TUnknown(right)) return true
|
||||
if (TypeGuard.TAny(right)) return true
|
||||
if (TypeGuard.TUserDefined(right)) throw Error(`Structural: Cannot structurally compare custom type '${right[Types.Kind]}'`)
|
||||
return false
|
||||
}
|
||||
|
||||
function ObjectRightRule(left: Types.TAnySchema, right: Types.TObject) {
|
||||
// type A = boolean extends {} ? 1 : 2 // additionalProperties: false
|
||||
// type B = boolean extends object ? 1 : 2 // additionalProperties: true
|
||||
const additionalProperties = right.additionalProperties
|
||||
const propertyLength = globalThis.Object.keys(right.properties).length
|
||||
return additionalProperties === false && propertyLength === 0
|
||||
}
|
||||
|
||||
function UnionRightRule(left: Types.TAnySchema, right: Types.TUnion): StructuralResult {
|
||||
const result = right.anyOf.some((right: Types.TSchema) => Visit(left, right) !== StructuralResult.False)
|
||||
return result ? StructuralResult.True : StructuralResult.False
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Records
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
function RecordPattern(schema: Types.TRecord) {
|
||||
return globalThis.Object.keys(schema.patternProperties)[0] as string
|
||||
}
|
||||
|
||||
function RecordNumberOrStringKey(schema: Types.TRecord) {
|
||||
const pattern = RecordPattern(schema)
|
||||
return pattern === '^.*$' || pattern === '^(0|[1-9][0-9]*)$'
|
||||
}
|
||||
|
||||
function RecordValue(schema: Types.TRecord) {
|
||||
const pattern = RecordPattern(schema)
|
||||
return schema.patternProperties[pattern]
|
||||
}
|
||||
|
||||
function RecordKey(schema: Types.TRecord) {
|
||||
const pattern = RecordPattern(schema)
|
||||
if (pattern === '^.*$') {
|
||||
return Types.Type.String()
|
||||
} else if (pattern === '^(0|[1-9][0-9]*)$') {
|
||||
return Types.Type.Number()
|
||||
} else {
|
||||
const keys = pattern.slice(1, pattern.length - 1).split('|')
|
||||
const schemas = keys.map((key) => (isNaN(+key) ? Types.Type.Literal(key) : Types.Type.Literal(parseFloat(key))))
|
||||
return Types.Type.Union(schemas)
|
||||
}
|
||||
}
|
||||
|
||||
function PropertyMap(schema: Types.TObject | Types.TRecord) {
|
||||
const comparable = new Map<string, Types.TSchema>()
|
||||
if (TypeGuard.TRecord(schema)) {
|
||||
const propertyPattern = RecordPattern(schema as Types.TRecord)
|
||||
if (propertyPattern === '^.*$' || propertyPattern === '^(0|[1-9][0-9]*)$') throw Error('Cannot extract record properties without property constraints')
|
||||
const propertySchema = schema.patternProperties[propertyPattern] as Types.TSchema
|
||||
const propertyKeys = propertyPattern.slice(1, propertyPattern.length - 1).split('|')
|
||||
propertyKeys.forEach((propertyKey) => {
|
||||
comparable.set(propertyKey, propertySchema)
|
||||
})
|
||||
} else {
|
||||
globalThis.Object.entries(schema.properties).forEach(([propertyKey, propertySchema]) => {
|
||||
comparable.set(propertyKey, propertySchema as Types.TSchema)
|
||||
})
|
||||
}
|
||||
return comparable
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Indexable
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
function Indexable<Left extends Types.TAnySchema, Right extends Types.TAnySchema>(left: Left, right: Types.TSchema): StructuralResult {
|
||||
if (TypeGuard.TUnion(right)) {
|
||||
return StructuralResult.False
|
||||
} else {
|
||||
return Visit(left, right)
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Checks
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
function Any(left: Types.TAny, right: Types.TSchema): StructuralResult {
|
||||
return AnyUnknownOrCustomRule(right) ? StructuralResult.True : StructuralResult.Union
|
||||
}
|
||||
|
||||
function Array(left: Types.TArray, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right)) {
|
||||
if (right.properties['length'] !== undefined && right.properties['length'][Types.Kind] === 'Number') return StructuralResult.True
|
||||
if (globalThis.Object.keys(right.properties).length === 0) return StructuralResult.True
|
||||
return StructuralResult.False
|
||||
} else if (!TypeGuard.TArray(right)) {
|
||||
return StructuralResult.False
|
||||
} else if (left.items === undefined && right.items !== undefined) {
|
||||
return StructuralResult.False
|
||||
} else if (left.items !== undefined && right.items === undefined) {
|
||||
return StructuralResult.False
|
||||
} else if (left.items === undefined && right.items === undefined) {
|
||||
return StructuralResult.False
|
||||
} else {
|
||||
const result = Visit(left.items, right.items) !== StructuralResult.False
|
||||
return result ? StructuralResult.True : StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Boolean(left: Types.TBoolean, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TBoolean(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
return UnionRightRule(left, right)
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Constructor(left: Types.TConstructor, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right) && globalThis.Object.keys(right.properties).length === 0) {
|
||||
return StructuralResult.True
|
||||
} else if (!TypeGuard.TConstructor(right)) {
|
||||
return StructuralResult.False
|
||||
} else if (right.parameters.length < left.parameters.length) {
|
||||
return StructuralResult.False
|
||||
} else {
|
||||
if (Visit(left.returns, right.returns) === StructuralResult.False) {
|
||||
return StructuralResult.False
|
||||
}
|
||||
for (let i = 0; i < left.parameters.length; i++) {
|
||||
const result = Visit(right.parameters[i], left.parameters[i])
|
||||
if (result === StructuralResult.False) return StructuralResult.False
|
||||
}
|
||||
return StructuralResult.True
|
||||
}
|
||||
}
|
||||
|
||||
function Date(left: Types.TDate, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TRecord(right)) {
|
||||
return StructuralResult.False
|
||||
} else if (TypeGuard.TDate(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
return UnionRightRule(left, right)
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Function(left: Types.TFunction, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right)) {
|
||||
if (right.properties['length'] !== undefined && right.properties['length'][Types.Kind] === 'Number') return StructuralResult.True
|
||||
if (globalThis.Object.keys(right.properties).length === 0) return StructuralResult.True
|
||||
return StructuralResult.False
|
||||
} else if (!TypeGuard.TFunction(right)) {
|
||||
return StructuralResult.False
|
||||
} else if (right.parameters.length < left.parameters.length) {
|
||||
return StructuralResult.False
|
||||
} else if (Visit(left.returns, right.returns) === StructuralResult.False) {
|
||||
return StructuralResult.False
|
||||
} else {
|
||||
for (let i = 0; i < left.parameters.length; i++) {
|
||||
const result = Visit(right.parameters[i], left.parameters[i])
|
||||
if (result === StructuralResult.False) return StructuralResult.False
|
||||
}
|
||||
return StructuralResult.True
|
||||
}
|
||||
}
|
||||
|
||||
function Integer(left: Types.TInteger, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TInteger(right) || TypeGuard.TNumber(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
return UnionRightRule(left, right)
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Literal(left: Types.TLiteral, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TRecord(right)) {
|
||||
if (typeof left.const === 'string') {
|
||||
return Indexable(left, RecordValue(right as Types.TRecord))
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
} else if (TypeGuard.TLiteral(right) && left.const === right.const) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TString(right) && typeof left.const === 'string') {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TNumber(right) && typeof left.const === 'number') {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TInteger(right) && typeof left.const === 'number') {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TBoolean(right) && typeof left.const === 'boolean') {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
return UnionRightRule(left, right)
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Number(left: Types.TNumber, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TNumber(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TInteger(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
return UnionRightRule(left, right)
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Null(left: Types.TNull, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TNull(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
return UnionRightRule(left, right)
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Properties(left: Map<string, Types.TSchema>, right: Map<string, Types.TSchema>) {
|
||||
if (right.size > left.size) return StructuralResult.False
|
||||
if (![...right.keys()].every((rightKey) => left.has(rightKey))) return StructuralResult.False
|
||||
for (const rightKey of right.keys()) {
|
||||
const leftProp = left.get(rightKey)!
|
||||
const rightProp = right.get(rightKey)!
|
||||
if (Visit(leftProp, rightProp) === StructuralResult.False) {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
return StructuralResult.True
|
||||
}
|
||||
|
||||
function Object(left: Types.TObject, right: Types.TAnySchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right)) {
|
||||
return Properties(PropertyMap(left), PropertyMap(right))
|
||||
} else if (TypeGuard.TRecord(right)) {
|
||||
if (!RecordNumberOrStringKey(right as Types.TRecord)) {
|
||||
return Properties(PropertyMap(left), PropertyMap(right))
|
||||
} else {
|
||||
return StructuralResult.True
|
||||
}
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Promise(left: Types.TPromise, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right)) {
|
||||
if (ObjectRightRule(left, right) || globalThis.Object.keys(right.properties).length === 0) {
|
||||
return StructuralResult.True
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
} else if (!TypeGuard.TPromise(right)) {
|
||||
return StructuralResult.False
|
||||
} else {
|
||||
const result = Visit(left.item, right.item) !== StructuralResult.False
|
||||
return result ? StructuralResult.True : StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Record(left: Types.TRecord, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right)) {
|
||||
if (RecordPattern(left) === '^.*$' && right[Types.Hint] === 'Record') {
|
||||
return StructuralResult.True
|
||||
} else if (RecordPattern(left) === '^.*$') {
|
||||
return StructuralResult.False
|
||||
} else {
|
||||
return globalThis.Object.keys(right.properties).length === 0 ? StructuralResult.True : StructuralResult.False
|
||||
}
|
||||
} else if (TypeGuard.TRecord(right)) {
|
||||
if (!RecordNumberOrStringKey(left as Types.TRecord) && !RecordNumberOrStringKey(right as Types.TRecord)) {
|
||||
return Properties(PropertyMap(left), PropertyMap(right))
|
||||
} else if (RecordNumberOrStringKey(left as Types.TRecord) && !RecordNumberOrStringKey(right as Types.TRecord)) {
|
||||
const leftKey = RecordKey(left as Types.TRecord)
|
||||
const rightKey = RecordKey(right as Types.TRecord)
|
||||
if (Visit(rightKey, leftKey) === StructuralResult.False) {
|
||||
return StructuralResult.False
|
||||
} else {
|
||||
return StructuralResult.True
|
||||
}
|
||||
} else {
|
||||
return StructuralResult.True
|
||||
}
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Ref(left: Types.TRef, right: Types.TSchema): StructuralResult {
|
||||
if (!referenceMap.has(left.$ref)) throw Error(`Cannot locate referenced $id '${left.$ref}'`)
|
||||
const resolved = referenceMap.get(left.$ref)!
|
||||
return Visit(resolved, right)
|
||||
}
|
||||
|
||||
function Self(left: Types.TSelf, right: Types.TSchema): StructuralResult {
|
||||
if (!referenceMap.has(left.$ref)) throw Error(`Cannot locate referenced self $id '${left.$ref}'`)
|
||||
const resolved = referenceMap.get(left.$ref)!
|
||||
return Visit(resolved, right)
|
||||
}
|
||||
|
||||
function String(left: Types.TString, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TRecord(right)) {
|
||||
return Indexable(left, RecordValue(right))
|
||||
} else if (TypeGuard.TString(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
return UnionRightRule(left, right)
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Tuple(left: Types.TTuple, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right)) {
|
||||
const result = ObjectRightRule(left, right) || globalThis.Object.keys(right.properties).length === 0
|
||||
return result ? StructuralResult.True : StructuralResult.False
|
||||
} else if (TypeGuard.TRecord(right)) {
|
||||
return Indexable(left, RecordValue(right))
|
||||
} else if (TypeGuard.TArray(right)) {
|
||||
if (right.items === undefined) {
|
||||
return StructuralResult.False
|
||||
} else if (TypeGuard.TUnion(right.items) && left.items) {
|
||||
const result = left.items.every((left: Types.TSchema) => UnionRightRule(left, right.items as Types.TUnion) !== StructuralResult.False)
|
||||
return result ? StructuralResult.True : StructuralResult.False
|
||||
} else if (TypeGuard.TAny(right.items)) {
|
||||
return StructuralResult.True
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
if (!TypeGuard.TTuple(right)) return StructuralResult.False
|
||||
if (left.items === undefined && right.items === undefined) return StructuralResult.True
|
||||
if (left.items === undefined && right.items !== undefined) return StructuralResult.False
|
||||
if (left.items !== undefined && right.items === undefined) return StructuralResult.False
|
||||
if (left.items === undefined && right.items === undefined) return StructuralResult.True
|
||||
if (left.minItems !== right.minItems || left.maxItems !== right.maxItems) return StructuralResult.False
|
||||
for (let i = 0; i < left.items!.length; i++) {
|
||||
if (Visit(left.items![i], right.items![i]) === StructuralResult.False) return StructuralResult.False
|
||||
}
|
||||
return StructuralResult.True
|
||||
}
|
||||
|
||||
function Uint8Array(left: Types.TUint8Array, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TObject(right) && ObjectRightRule(left, right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TRecord(right)) {
|
||||
return Indexable(left, RecordValue(right as Types.TRecord))
|
||||
} else if (TypeGuard.TUint8Array(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
return UnionRightRule(left, right)
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Undefined(left: Types.TUndefined, right: Types.TSchema): StructuralResult {
|
||||
if (AnyUnknownOrCustomRule(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUndefined(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TVoid(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
return UnionRightRule(left, right)
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Union(left: Types.TUnion, right: Types.TSchema): StructuralResult {
|
||||
if (left.anyOf.some((left: Types.TSchema) => TypeGuard.TAny(left))) {
|
||||
return StructuralResult.Union
|
||||
} else if (TypeGuard.TUnion(right)) {
|
||||
const result = left.anyOf.every((left: Types.TSchema) => right.anyOf.some((right: Types.TSchema) => Visit(left, right) !== StructuralResult.False))
|
||||
return result ? StructuralResult.True : StructuralResult.False
|
||||
} else {
|
||||
const result = left.anyOf.every((left: Types.TSchema) => Visit(left, right) !== StructuralResult.False)
|
||||
return result ? StructuralResult.True : StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Unknown(left: Types.TUnknown, right: Types.TSchema): StructuralResult {
|
||||
if (TypeGuard.TUnion(right)) {
|
||||
const result = right.anyOf.some((right: Types.TSchema) => TypeGuard.TAny(right) || TypeGuard.TUnknown(right))
|
||||
return result ? StructuralResult.True : StructuralResult.False
|
||||
} else if (TypeGuard.TAny(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnknown(right)) {
|
||||
return StructuralResult.True
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
function Void(left: Types.TVoid, right: Types.TSchema): StructuralResult {
|
||||
if (TypeGuard.TUnion(right)) {
|
||||
const result = right.anyOf.some((right: Types.TSchema) => TypeGuard.TAny(right) || TypeGuard.TUnknown(right))
|
||||
return result ? StructuralResult.True : StructuralResult.False
|
||||
} else if (TypeGuard.TAny(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TUnknown(right)) {
|
||||
return StructuralResult.True
|
||||
} else if (TypeGuard.TVoid(right)) {
|
||||
return StructuralResult.True
|
||||
} else {
|
||||
return StructuralResult.False
|
||||
}
|
||||
}
|
||||
|
||||
let recursionDepth = 0
|
||||
function Visit<Left extends Types.TAnySchema, Right extends Types.TAnySchema>(left: Left, right: Types.TSchema): StructuralResult {
|
||||
recursionDepth += 1
|
||||
if (recursionDepth >= 1000) return StructuralResult.True
|
||||
if (left.$id !== undefined) referenceMap.set(left.$id!, left)
|
||||
if (right.$id !== undefined) referenceMap.set(right.$id!, right)
|
||||
const resolvedRight = right[Types.Kind] === 'Self' ? referenceMap.get(right.$ref)! : right
|
||||
if (TypeGuard.TAny(left)) {
|
||||
return Any(left, resolvedRight)
|
||||
} else if (TypeGuard.TArray(left)) {
|
||||
return Array(left, resolvedRight)
|
||||
} else if (TypeGuard.TBoolean(left)) {
|
||||
return Boolean(left, resolvedRight)
|
||||
} else if (TypeGuard.TConstructor(left)) {
|
||||
return Constructor(left, resolvedRight)
|
||||
} else if (TypeGuard.TDate(left)) {
|
||||
return Date(left, resolvedRight)
|
||||
} else if (TypeGuard.TFunction(left)) {
|
||||
return Function(left, resolvedRight)
|
||||
} else if (TypeGuard.TInteger(left)) {
|
||||
return Integer(left, resolvedRight)
|
||||
} else if (TypeGuard.TLiteral(left)) {
|
||||
return Literal(left, resolvedRight)
|
||||
} else if (TypeGuard.TNull(left)) {
|
||||
return Null(left, resolvedRight)
|
||||
} else if (TypeGuard.TNumber(left)) {
|
||||
return Number(left, resolvedRight)
|
||||
} else if (TypeGuard.TObject(left)) {
|
||||
return Object(left, resolvedRight)
|
||||
} else if (TypeGuard.TPromise(left)) {
|
||||
return Promise(left, resolvedRight)
|
||||
} else if (TypeGuard.TRecord(left)) {
|
||||
return Record(left, resolvedRight)
|
||||
} else if (TypeGuard.TRef(left)) {
|
||||
return Ref(left, resolvedRight)
|
||||
} else if (TypeGuard.TSelf(left)) {
|
||||
return Self(left, resolvedRight)
|
||||
} else if (TypeGuard.TString(left)) {
|
||||
return String(left, resolvedRight)
|
||||
} else if (TypeGuard.TTuple(left)) {
|
||||
return Tuple(left, resolvedRight)
|
||||
} else if (TypeGuard.TUndefined(left)) {
|
||||
return Undefined(left, resolvedRight)
|
||||
} else if (TypeGuard.TUint8Array(left)) {
|
||||
return Uint8Array(left, resolvedRight)
|
||||
} else if (TypeGuard.TUnion(left)) {
|
||||
return Union(left, resolvedRight)
|
||||
} else if (TypeGuard.TUnknown(left)) {
|
||||
return Unknown(left, resolvedRight)
|
||||
} else if (TypeGuard.TVoid(left)) {
|
||||
return Void(left, resolvedRight)
|
||||
} else if (TypeGuard.TUserDefined(left)) {
|
||||
throw Error(`Structural: Cannot structurally compare custom type '${left[Types.Kind]}'`)
|
||||
} else {
|
||||
throw Error(`Structural: Unknown left operand '${left[Types.Kind]}'`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Structurally tests if the left schema extends the right. */
|
||||
export function Check(left: Types.TSchema, right: Types.TSchema): StructuralResult {
|
||||
referenceMap.clear()
|
||||
recursionDepth = 0
|
||||
return Visit(left, right)
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/custom
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export type CustomValidationFunction<TSchema> = (schema: TSchema, value: unknown) => boolean
|
||||
|
||||
/** Provides functions to create user defined types */
|
||||
export namespace Custom {
|
||||
const customs = new Map<string, CustomValidationFunction<any>>()
|
||||
|
||||
/** Clears all user defined types */
|
||||
export function Clear() {
|
||||
return customs.clear()
|
||||
}
|
||||
|
||||
/** Returns true if this user defined type exists */
|
||||
export function Has(kind: string) {
|
||||
return customs.has(kind)
|
||||
}
|
||||
|
||||
/** Sets a validation function for a user defined type */
|
||||
export function Set<TSchema = unknown>(kind: string, func: CustomValidationFunction<TSchema>) {
|
||||
customs.set(kind, func)
|
||||
}
|
||||
|
||||
/** Gets a custom validation function for a user defined type */
|
||||
export function Get(kind: string) {
|
||||
return customs.get(kind)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/custom
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export * from './custom'
|
||||
@@ -25,23 +25,24 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as Types from '../typebox'
|
||||
import { TypeSystem } from '../system/index'
|
||||
import { TypeExtends } from '../guard/extends'
|
||||
import { Format } from '../format/index'
|
||||
import { Custom } from '../custom/index'
|
||||
import { ValueHash } from '../hash/index'
|
||||
import { ValueHash } from '../value/hash'
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// ValueErrorType
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export enum ValueErrorType {
|
||||
Array,
|
||||
ArrayMinItems,
|
||||
ArrayMaxItems,
|
||||
ArrayUniqueItems,
|
||||
BigInt,
|
||||
BigIntMultipleOf,
|
||||
BigIntExclusiveMinimum,
|
||||
BigIntExclusiveMaximum,
|
||||
BigIntMinimum,
|
||||
BigIntMaximum,
|
||||
Boolean,
|
||||
Date,
|
||||
DateExclusiveMinimumTimestamp,
|
||||
@@ -55,8 +56,11 @@ export enum ValueErrorType {
|
||||
IntegerExclusiveMaximum,
|
||||
IntegerMinimum,
|
||||
IntegerMaximum,
|
||||
Intersect,
|
||||
IntersectUnevaluatedProperties,
|
||||
Literal,
|
||||
Never,
|
||||
Not,
|
||||
Null,
|
||||
Number,
|
||||
NumberMultipleOf,
|
||||
@@ -78,6 +82,7 @@ export enum ValueErrorType {
|
||||
StringPattern,
|
||||
StringFormatUnknown,
|
||||
StringFormat,
|
||||
Symbol,
|
||||
TupleZeroLength,
|
||||
TupleLength,
|
||||
Undefined,
|
||||
@@ -88,11 +93,9 @@ export enum ValueErrorType {
|
||||
Void,
|
||||
Custom,
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// ValueError
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export interface ValueError {
|
||||
type: ValueErrorType
|
||||
schema: Types.TSchema
|
||||
@@ -100,33 +103,77 @@ export interface ValueError {
|
||||
value: unknown
|
||||
message: string
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// ValueErrorIterator
|
||||
// -------------------------------------------------------------------
|
||||
export class ValueErrorIterator {
|
||||
constructor(private readonly iterator: IterableIterator<ValueError>) {}
|
||||
public [Symbol.iterator]() {
|
||||
return this.iterator
|
||||
}
|
||||
/** Returns the first value error or undefined if no errors */
|
||||
public First(): ValueError | undefined {
|
||||
const next = this.iterator.next()
|
||||
return next.done ? undefined : next.value
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// ValueErrors
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export class ValueErrorsUnknownTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueErrors: Unknown type')
|
||||
}
|
||||
}
|
||||
|
||||
export class ValueErrorsDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TSelf) {
|
||||
super(`ValueErrors: Unable to dereference schema with $id '${schema.$ref}'`)
|
||||
}
|
||||
}
|
||||
/** Provides functionality to generate a sequence of errors against a TypeBox type. */
|
||||
export namespace ValueErrors {
|
||||
function IsNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && !isNaN(value)
|
||||
// ----------------------------------------------------------------------
|
||||
// Guards
|
||||
// ----------------------------------------------------------------------
|
||||
function IsObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
const result = typeof value === 'object' && value !== null
|
||||
return TypeSystem.AllowArrayObjects ? result : result && !globalThis.Array.isArray(value)
|
||||
}
|
||||
|
||||
function IsRecordObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
return IsObject(value) && !(value instanceof globalThis.Date) && !(value instanceof globalThis.Uint8Array)
|
||||
}
|
||||
function IsBigInt(value: unknown): value is bigint {
|
||||
return typeof value === 'bigint'
|
||||
}
|
||||
function IsNumber(value: unknown): value is number {
|
||||
const result = typeof value === 'number'
|
||||
return TypeSystem.AllowNaN ? result : result && globalThis.Number.isFinite(value)
|
||||
}
|
||||
function IsInteger(value: unknown): value is number {
|
||||
return globalThis.Number.isInteger(value)
|
||||
}
|
||||
function IsString(value: unknown): value is string {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
function IsVoid(value: unknown): value is void {
|
||||
const result = value === undefined
|
||||
return TypeSystem.AllowVoidNull ? result || value === null : result
|
||||
}
|
||||
function IsDefined<T>(value: unknown): value is T {
|
||||
return value !== undefined
|
||||
}
|
||||
// ----------------------------------------------------------------------
|
||||
// Types
|
||||
// ----------------------------------------------------------------------
|
||||
function* Any(schema: Types.TAny, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {}
|
||||
|
||||
function* Array(schema: Types.TArray, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!globalThis.Array.isArray(value)) {
|
||||
return yield { type: ValueErrorType.Array, schema, path, value, message: `Expected array` }
|
||||
}
|
||||
if (IsNumber(schema.minItems) && !(value.length >= schema.minItems)) {
|
||||
if (IsDefined<number>(schema.minItems) && !(value.length >= schema.minItems)) {
|
||||
yield { type: ValueErrorType.ArrayMinItems, schema, path, value, message: `Expected array length to be greater or equal to ${schema.minItems}` }
|
||||
}
|
||||
if (IsNumber(schema.maxItems) && !(value.length <= schema.maxItems)) {
|
||||
if (IsDefined<number>(schema.maxItems) && !(value.length <= schema.maxItems)) {
|
||||
yield { type: ValueErrorType.ArrayMinItems, schema, path, value, message: `Expected array length to be less or equal to ${schema.maxItems}` }
|
||||
}
|
||||
// prettier-ignore
|
||||
@@ -137,176 +184,204 @@ export namespace ValueErrors {
|
||||
yield* Visit(schema.items, references, `${path}/${i}`, value[i])
|
||||
}
|
||||
}
|
||||
|
||||
function* BigInt(schema: Types.TBigInt, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!IsBigInt(value)) {
|
||||
return yield { type: ValueErrorType.BigInt, schema, path, value, message: `Expected bigint` }
|
||||
}
|
||||
if (IsDefined<bigint>(schema.multipleOf) && !(value % schema.multipleOf === globalThis.BigInt(0))) {
|
||||
yield { type: ValueErrorType.BigIntMultipleOf, schema, path, value, message: `Expected bigint to be a multiple of ${schema.multipleOf}` }
|
||||
}
|
||||
if (IsDefined<bigint>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
yield { type: ValueErrorType.BigIntExclusiveMinimum, schema, path, value, message: `Expected bigint to be greater than ${schema.exclusiveMinimum}` }
|
||||
}
|
||||
if (IsDefined<bigint>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
yield { type: ValueErrorType.BigIntExclusiveMaximum, schema, path, value, message: `Expected bigint to be less than ${schema.exclusiveMaximum}` }
|
||||
}
|
||||
if (IsDefined<bigint>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
yield { type: ValueErrorType.BigIntMinimum, schema, path, value, message: `Expected bigint to be greater or equal to ${schema.minimum}` }
|
||||
}
|
||||
if (IsDefined<bigint>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
yield { type: ValueErrorType.BigIntMaximum, schema, path, value, message: `Expected bigint to be less or equal to ${schema.maximum}` }
|
||||
}
|
||||
}
|
||||
function* Boolean(schema: Types.TBoolean, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(typeof value === 'boolean')) {
|
||||
return yield { type: ValueErrorType.Boolean, schema, path, value, message: `Expected boolean` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Constructor(schema: Types.TConstructor, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
yield* Visit(schema.returns, references, path, value.prototype)
|
||||
}
|
||||
|
||||
function* Date(schema: Types.TNumeric, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(value instanceof globalThis.Date)) {
|
||||
return yield { type: ValueErrorType.Date, schema, path, value, message: `Expected Date object` }
|
||||
}
|
||||
if (isNaN(value.getTime())) {
|
||||
if (!globalThis.isFinite(value.getTime())) {
|
||||
return yield { type: ValueErrorType.Date, schema, path, value, message: `Invalid Date` }
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) {
|
||||
if (IsDefined<number>(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) {
|
||||
yield { type: ValueErrorType.DateExclusiveMinimumTimestamp, schema, path, value, message: `Expected Date timestamp to be greater than ${schema.exclusiveMinimum}` }
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
|
||||
if (IsDefined<number>(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
|
||||
yield { type: ValueErrorType.DateExclusiveMaximumTimestamp, schema, path, value, message: `Expected Date timestamp to be less than ${schema.exclusiveMaximum}` }
|
||||
}
|
||||
if (IsNumber(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) {
|
||||
if (IsDefined<number>(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) {
|
||||
yield { type: ValueErrorType.DateMinimumTimestamp, schema, path, value, message: `Expected Date timestamp to be greater or equal to ${schema.minimum}` }
|
||||
}
|
||||
if (IsNumber(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) {
|
||||
if (IsDefined<number>(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) {
|
||||
yield { type: ValueErrorType.DateMaximumTimestamp, schema, path, value, message: `Expected Date timestamp to be less or equal to ${schema.maximum}` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Function(schema: Types.TFunction, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(typeof value === 'function')) {
|
||||
return yield { type: ValueErrorType.Function, schema, path, value, message: `Expected function` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Integer(schema: Types.TNumeric, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(typeof value === 'number' && globalThis.Number.isInteger(value))) {
|
||||
if (!IsInteger(value)) {
|
||||
return yield { type: ValueErrorType.Integer, schema, path, value, message: `Expected integer` }
|
||||
}
|
||||
if (IsNumber(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
yield { type: ValueErrorType.IntegerMultipleOf, schema, path, value, message: `Expected integer to be a multiple of ${schema.multipleOf}` }
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
yield { type: ValueErrorType.IntegerExclusiveMinimum, schema, path, value, message: `Expected integer to be greater than ${schema.exclusiveMinimum}` }
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
yield { type: ValueErrorType.IntegerExclusiveMaximum, schema, path, value, message: `Expected integer to be less than ${schema.exclusiveMaximum}` }
|
||||
}
|
||||
if (IsNumber(schema.minimum) && !(value >= schema.minimum)) {
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
yield { type: ValueErrorType.IntegerMinimum, schema, path, value, message: `Expected integer to be greater or equal to ${schema.minimum}` }
|
||||
}
|
||||
if (IsNumber(schema.maximum) && !(value <= schema.maximum)) {
|
||||
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
yield { type: ValueErrorType.IntegerMaximum, schema, path, value, message: `Expected integer to be less or equal to ${schema.maximum}` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Intersect(schema: Types.TIntersect, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
for (const subschema of schema.allOf) {
|
||||
const next = Visit(subschema, references, path, value).next()
|
||||
if (!next.done) {
|
||||
yield next.value
|
||||
yield { type: ValueErrorType.Intersect, schema, path, value, message: `Expected all sub schemas to be valid` }
|
||||
return
|
||||
}
|
||||
}
|
||||
if (schema.unevaluatedProperties === false) {
|
||||
const schemaKeys = Types.KeyResolver.Resolve(schema)
|
||||
const valueKeys = globalThis.Object.getOwnPropertyNames(value)
|
||||
for (const valueKey of valueKeys) {
|
||||
if (!schemaKeys.includes(valueKey)) {
|
||||
yield { type: ValueErrorType.IntersectUnevaluatedProperties, schema, path: `${path}/${valueKey}`, value, message: `Unexpected property` }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof schema.unevaluatedProperties === 'object') {
|
||||
const schemaKeys = Types.KeyResolver.Resolve(schema)
|
||||
const valueKeys = globalThis.Object.getOwnPropertyNames(value)
|
||||
for (const valueKey of valueKeys) {
|
||||
if (!schemaKeys.includes(valueKey)) {
|
||||
const next = Visit(schema.unevaluatedProperties, references, `${path}/${valueKey}`, value[valueKey]).next()
|
||||
if (!next.done) {
|
||||
yield next.value
|
||||
yield { type: ValueErrorType.IntersectUnevaluatedProperties, schema, path: `${path}/${valueKey}`, value, message: `Invalid additional property` }
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function* Literal(schema: Types.TLiteral, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(value === schema.const)) {
|
||||
const error = typeof schema.const === 'string' ? `'${schema.const}'` : schema.const
|
||||
return yield { type: ValueErrorType.Literal, schema, path, value, message: `Expected ${error}` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Never(schema: Types.TNever, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
yield { type: ValueErrorType.Never, schema, path, value, message: `Value cannot be validated` }
|
||||
}
|
||||
|
||||
function* Not(schema: Types.TNot, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (Visit(schema.allOf[0].not, references, path, value).next().done === true) {
|
||||
yield { type: ValueErrorType.Not, schema, path, value, message: `Value should not validate` }
|
||||
}
|
||||
yield* Visit(schema.allOf[1], references, path, value)
|
||||
}
|
||||
function* Null(schema: Types.TNull, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(value === null)) {
|
||||
return yield { type: ValueErrorType.Null, schema, path, value, message: `Expected null` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Number(schema: Types.TNumeric, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (TypeSystem.AllowNaN) {
|
||||
if (!(typeof value === 'number')) {
|
||||
return yield { type: ValueErrorType.Number, schema, path, value, message: `Expected number` }
|
||||
}
|
||||
} else {
|
||||
if (!(typeof value === 'number' && !isNaN(value))) {
|
||||
return yield { type: ValueErrorType.Number, schema, path, value, message: `Expected number` }
|
||||
}
|
||||
if (!IsNumber(value)) {
|
||||
return yield { type: ValueErrorType.Number, schema, path, value, message: `Expected number` }
|
||||
}
|
||||
if (IsNumber(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
yield { type: ValueErrorType.NumberMultipleOf, schema, path, value, message: `Expected number to be a multiple of ${schema.multipleOf}` }
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
yield { type: ValueErrorType.NumberExclusiveMinimum, schema, path, value, message: `Expected number to be greater than ${schema.exclusiveMinimum}` }
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
yield { type: ValueErrorType.NumberExclusiveMaximum, schema, path, value, message: `Expected number to be less than ${schema.exclusiveMaximum}` }
|
||||
}
|
||||
if (IsNumber(schema.minimum) && !(value >= schema.minimum)) {
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
yield { type: ValueErrorType.NumberMaximum, schema, path, value, message: `Expected number to be greater or equal to ${schema.minimum}` }
|
||||
}
|
||||
if (IsNumber(schema.maximum) && !(value <= schema.maximum)) {
|
||||
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
yield { type: ValueErrorType.NumberMinumum, schema, path, value, message: `Expected number to be less or equal to ${schema.maximum}` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Object(schema: Types.TObject, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (TypeSystem.AllowArrayObjects) {
|
||||
if (!(typeof value === 'object' && value !== null)) {
|
||||
return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` }
|
||||
}
|
||||
} else {
|
||||
if (!(typeof value === 'object' && value !== null && !globalThis.Array.isArray(value))) {
|
||||
return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` }
|
||||
}
|
||||
if (!IsObject(value)) {
|
||||
return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` }
|
||||
}
|
||||
if (IsNumber(schema.minProperties) && !(globalThis.Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
|
||||
if (IsDefined<number>(schema.minProperties) && !(globalThis.Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
|
||||
yield { type: ValueErrorType.ObjectMinProperties, schema, path, value, message: `Expected object to have at least ${schema.minProperties} properties` }
|
||||
}
|
||||
if (IsNumber(schema.maxProperties) && !(globalThis.Object.getOwnPropertyNames(value).length <= schema.maxProperties)) {
|
||||
if (IsDefined<number>(schema.maxProperties) && !(globalThis.Object.getOwnPropertyNames(value).length <= schema.maxProperties)) {
|
||||
yield { type: ValueErrorType.ObjectMaxProperties, schema, path, value, message: `Expected object to have less than ${schema.minProperties} properties` }
|
||||
}
|
||||
const propertyNames = globalThis.Object.getOwnPropertyNames(schema.properties)
|
||||
if (schema.additionalProperties === false) {
|
||||
for (const propertyName of globalThis.Object.getOwnPropertyNames(value)) {
|
||||
if (!propertyNames.includes(propertyName)) {
|
||||
yield { type: ValueErrorType.ObjectAdditionalProperties, schema, path: `${path}/${propertyName}`, value: value[propertyName], message: `Unexpected property` }
|
||||
const requiredKeys = globalThis.Array.isArray(schema.required) ? schema.required : ([] as string[])
|
||||
const knownKeys = globalThis.Object.getOwnPropertyNames(schema.properties)
|
||||
const unknownKeys = globalThis.Object.getOwnPropertyNames(value)
|
||||
for (const knownKey of knownKeys) {
|
||||
const property = schema.properties[knownKey]
|
||||
if (schema.required && schema.required.includes(knownKey)) {
|
||||
yield* Visit(property, references, `${path}/${knownKey}`, value[knownKey])
|
||||
if (Types.ExtendsUndefined.Check(schema) && !(knownKey in value)) {
|
||||
yield { type: ValueErrorType.ObjectRequiredProperties, schema: property, path: `${path}/${knownKey}`, value: undefined, message: `Expected required property` }
|
||||
}
|
||||
} else {
|
||||
if (knownKey in value) {
|
||||
yield* Visit(property, references, `${path}/${knownKey}`, value[knownKey])
|
||||
}
|
||||
}
|
||||
}
|
||||
if (schema.required && schema.required.length > 0) {
|
||||
const propertyNames = globalThis.Object.getOwnPropertyNames(value)
|
||||
for (const requiredKey of schema.required) {
|
||||
if (propertyNames.includes(requiredKey)) continue
|
||||
yield { type: ValueErrorType.ObjectRequiredProperties, schema: schema.properties[requiredKey], path: `${path}/${requiredKey}`, value: undefined, message: `Expected required property` }
|
||||
for (const requiredKey of requiredKeys) {
|
||||
if (unknownKeys.includes(requiredKey)) continue
|
||||
yield { type: ValueErrorType.ObjectRequiredProperties, schema: schema.properties[requiredKey], path: `${path}/${requiredKey}`, value: undefined, message: `Expected required property` }
|
||||
}
|
||||
if (schema.additionalProperties === false) {
|
||||
for (const valueKey of unknownKeys) {
|
||||
if (!knownKeys.includes(valueKey)) {
|
||||
yield { type: ValueErrorType.ObjectAdditionalProperties, schema, path: `${path}/${valueKey}`, value: value[valueKey], message: `Unexpected property` }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof schema.additionalProperties === 'object') {
|
||||
for (const propertyName of globalThis.Object.getOwnPropertyNames(value)) {
|
||||
if (propertyNames.includes(propertyName)) continue
|
||||
yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${propertyName}`, value[propertyName])
|
||||
}
|
||||
}
|
||||
for (const propertyKey of propertyNames) {
|
||||
const propertySchema = schema.properties[propertyKey]
|
||||
if (schema.required && schema.required.includes(propertyKey)) {
|
||||
yield* Visit(propertySchema, references, `${path}/${propertyKey}`, value[propertyKey])
|
||||
if (TypeExtends.Undefined(schema) && !(propertyKey in value)) {
|
||||
yield { type: ValueErrorType.ObjectRequiredProperties, schema: propertySchema, path: `${path}/${propertyKey}`, value: undefined, message: `Expected required property` }
|
||||
}
|
||||
} else {
|
||||
if (value[propertyKey] !== undefined) {
|
||||
yield* Visit(propertySchema, references, `${path}/${propertyKey}`, value[propertyKey])
|
||||
}
|
||||
for (const valueKey of unknownKeys) {
|
||||
if (knownKeys.includes(valueKey)) continue
|
||||
yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${valueKey}`, value[valueKey])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function* Promise(schema: Types.TPromise, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(typeof value === 'object' && typeof value.then === 'function')) {
|
||||
yield { type: ValueErrorType.Promise, schema, path, value, message: `Expected Promise` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Record(schema: Types.TRecord, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (TypeSystem.AllowArrayObjects) {
|
||||
if (!(typeof value === 'object' && value !== null && !(value instanceof globalThis.Date))) {
|
||||
return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` }
|
||||
}
|
||||
} else {
|
||||
if (!(typeof value === 'object' && value !== null && !(value instanceof globalThis.Date) && !globalThis.Array.isArray(value))) {
|
||||
return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected object` }
|
||||
}
|
||||
if (!IsRecordObject(value)) {
|
||||
return yield { type: ValueErrorType.Object, schema, path, value, message: `Expected record object` }
|
||||
}
|
||||
const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0]
|
||||
const regex = new RegExp(keyPattern)
|
||||
@@ -320,27 +395,26 @@ export namespace ValueErrors {
|
||||
yield* Visit(valueSchema, references, `${path}/${propKey}`, propValue)
|
||||
}
|
||||
}
|
||||
|
||||
function* Ref(schema: Types.TRef<any>, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
const reference = references.find((reference) => reference.$id === schema.$ref)
|
||||
if (reference === undefined) throw new Error(`ValueErrors.Ref: Cannot find schema with $id '${schema.$ref}'.`)
|
||||
yield* Visit(reference, references, path, value)
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueErrorsDereferenceError(schema)
|
||||
const target = references[index]
|
||||
yield* Visit(target, references, path, value)
|
||||
}
|
||||
|
||||
function* Self(schema: Types.TSelf, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
const reference = references.find((reference) => reference.$id === schema.$ref)
|
||||
if (reference === undefined) throw new Error(`ValueErrors.Self: Cannot find schema with $id '${schema.$ref}'.`)
|
||||
yield* Visit(reference, references, path, value)
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueErrorsDereferenceError(schema)
|
||||
const target = references[index]
|
||||
yield* Visit(target, references, path, value)
|
||||
}
|
||||
|
||||
function* String(schema: Types.TString, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(typeof value === 'string')) {
|
||||
if (!IsString(value)) {
|
||||
return yield { type: ValueErrorType.String, schema, path, value, message: 'Expected string' }
|
||||
}
|
||||
if (IsNumber(schema.minLength) && !(value.length >= schema.minLength)) {
|
||||
if (IsDefined<number>(schema.minLength) && !(value.length >= schema.minLength)) {
|
||||
yield { type: ValueErrorType.StringMinLength, schema, path, value, message: `Expected string length greater or equal to ${schema.minLength}` }
|
||||
}
|
||||
if (IsNumber(schema.maxLength) && !(value.length <= schema.maxLength)) {
|
||||
if (IsDefined<number>(schema.maxLength) && !(value.length <= schema.maxLength)) {
|
||||
yield { type: ValueErrorType.StringMaxLength, schema, path, value, message: `Expected string length less or equal to ${schema.maxLength}` }
|
||||
}
|
||||
if (schema.pattern !== undefined) {
|
||||
@@ -350,17 +424,21 @@ export namespace ValueErrors {
|
||||
}
|
||||
}
|
||||
if (schema.format !== undefined) {
|
||||
if (!Format.Has(schema.format)) {
|
||||
if (!Types.FormatRegistry.Has(schema.format)) {
|
||||
yield { type: ValueErrorType.StringFormatUnknown, schema, path, value, message: `Unknown string format '${schema.format}'` }
|
||||
} else {
|
||||
const format = Format.Get(schema.format)!
|
||||
const format = Types.FormatRegistry.Get(schema.format)!
|
||||
if (!format(value)) {
|
||||
yield { type: ValueErrorType.StringFormat, schema, path, value, message: `Expected string to match format '${schema.format}'` }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function* Symbol(schema: Types.TSymbol, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(typeof value === 'symbol')) {
|
||||
return yield { type: ValueErrorType.Symbol, schema, path, value, message: 'Expected symbol' }
|
||||
}
|
||||
}
|
||||
function* Tuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!globalThis.Array.isArray(value)) {
|
||||
return yield { type: ValueErrorType.Array, schema, path, value, message: 'Expected Array' }
|
||||
@@ -378,13 +456,11 @@ export namespace ValueErrors {
|
||||
yield* Visit(schema.items[i], references, `${path}/${i}`, value[i])
|
||||
}
|
||||
}
|
||||
|
||||
function* Undefined(schema: Types.TUndefined, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(value === undefined)) {
|
||||
yield { type: ValueErrorType.Undefined, schema, path, value, message: `Expected undefined` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Union(schema: Types.TUnion, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
const errors: ValueError[] = []
|
||||
for (const inner of schema.anyOf) {
|
||||
@@ -399,91 +475,94 @@ export namespace ValueErrors {
|
||||
yield { type: ValueErrorType.Union, schema, path, value, message: 'Expected value of union' }
|
||||
}
|
||||
}
|
||||
|
||||
function* Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(value instanceof globalThis.Uint8Array)) {
|
||||
return yield { type: ValueErrorType.Uint8Array, schema, path, value, message: `Expected Uint8Array` }
|
||||
}
|
||||
if (IsNumber(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) {
|
||||
if (IsDefined<number>(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) {
|
||||
yield { type: ValueErrorType.Uint8ArrayMaxByteLength, schema, path, value, message: `Expected Uint8Array to have a byte length less or equal to ${schema.maxByteLength}` }
|
||||
}
|
||||
if (IsNumber(schema.minByteLength) && !(value.length >= schema.minByteLength)) {
|
||||
if (IsDefined<number>(schema.minByteLength) && !(value.length >= schema.minByteLength)) {
|
||||
yield { type: ValueErrorType.Uint8ArrayMinByteLength, schema, path, value, message: `Expected Uint8Array to have a byte length greater or equal to ${schema.maxByteLength}` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Unknown(schema: Types.TUnknown, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {}
|
||||
|
||||
function* Void(schema: Types.TVoid, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
if (!(value === null)) {
|
||||
return yield { type: ValueErrorType.Void, schema, path, value, message: `Expected null` }
|
||||
if (!IsVoid(value)) {
|
||||
return yield { type: ValueErrorType.Void, schema, path, value, message: `Expected void` }
|
||||
}
|
||||
}
|
||||
|
||||
function* UserDefined(schema: Types.TSchema, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
const func = Custom.Get(schema[Types.Kind])!
|
||||
if (!func(schema, value)) {
|
||||
const check = Types.TypeRegistry.Get(schema[Types.Kind])!
|
||||
if (!check(schema, value)) {
|
||||
return yield { type: ValueErrorType.Custom, schema, path, value, message: `Expected kind ${schema[Types.Kind]}` }
|
||||
}
|
||||
}
|
||||
|
||||
function* Visit<T extends Types.TSchema>(schema: T, references: Types.TSchema[], path: string, value: any): IterableIterator<ValueError> {
|
||||
const anyReferences = schema.$id === undefined ? references : [schema, ...references]
|
||||
const anySchema = schema as any
|
||||
switch (anySchema[Types.Kind]) {
|
||||
const references_ = IsDefined<string>(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema_[Types.Kind]) {
|
||||
case 'Any':
|
||||
return yield* Any(anySchema, anyReferences, path, value)
|
||||
return yield* Any(schema_, references_, path, value)
|
||||
case 'Array':
|
||||
return yield* Array(anySchema, anyReferences, path, value)
|
||||
return yield* Array(schema_, references_, path, value)
|
||||
case 'BigInt':
|
||||
return yield* BigInt(schema_, references_, path, value)
|
||||
case 'Boolean':
|
||||
return yield* Boolean(anySchema, anyReferences, path, value)
|
||||
return yield* Boolean(schema_, references_, path, value)
|
||||
case 'Constructor':
|
||||
return yield* Constructor(anySchema, anyReferences, path, value)
|
||||
return yield* Constructor(schema_, references_, path, value)
|
||||
case 'Date':
|
||||
return yield* Date(anySchema, anyReferences, path, value)
|
||||
return yield* Date(schema_, references_, path, value)
|
||||
case 'Function':
|
||||
return yield* Function(anySchema, anyReferences, path, value)
|
||||
return yield* Function(schema_, references_, path, value)
|
||||
case 'Integer':
|
||||
return yield* Integer(anySchema, anyReferences, path, value)
|
||||
return yield* Integer(schema_, references_, path, value)
|
||||
case 'Intersect':
|
||||
return yield* Intersect(schema_, references_, path, value)
|
||||
case 'Literal':
|
||||
return yield* Literal(anySchema, anyReferences, path, value)
|
||||
return yield* Literal(schema_, references_, path, value)
|
||||
case 'Never':
|
||||
return yield* Never(anySchema, anyReferences, path, value)
|
||||
return yield* Never(schema_, references_, path, value)
|
||||
case 'Not':
|
||||
return yield* Not(schema_, references_, path, value)
|
||||
case 'Null':
|
||||
return yield* Null(anySchema, anyReferences, path, value)
|
||||
return yield* Null(schema_, references_, path, value)
|
||||
case 'Number':
|
||||
return yield* Number(anySchema, anyReferences, path, value)
|
||||
return yield* Number(schema_, references_, path, value)
|
||||
case 'Object':
|
||||
return yield* Object(anySchema, anyReferences, path, value)
|
||||
return yield* Object(schema_, references_, path, value)
|
||||
case 'Promise':
|
||||
return yield* Promise(anySchema, anyReferences, path, value)
|
||||
return yield* Promise(schema_, references_, path, value)
|
||||
case 'Record':
|
||||
return yield* Record(anySchema, anyReferences, path, value)
|
||||
return yield* Record(schema_, references_, path, value)
|
||||
case 'Ref':
|
||||
return yield* Ref(anySchema, anyReferences, path, value)
|
||||
return yield* Ref(schema_, references_, path, value)
|
||||
case 'Self':
|
||||
return yield* Self(anySchema, anyReferences, path, value)
|
||||
return yield* Self(schema_, references_, path, value)
|
||||
case 'String':
|
||||
return yield* String(anySchema, anyReferences, path, value)
|
||||
return yield* String(schema_, references_, path, value)
|
||||
case 'Symbol':
|
||||
return yield* Symbol(schema_, references_, path, value)
|
||||
case 'Tuple':
|
||||
return yield* Tuple(anySchema, anyReferences, path, value)
|
||||
return yield* Tuple(schema_, references_, path, value)
|
||||
case 'Undefined':
|
||||
return yield* Undefined(anySchema, anyReferences, path, value)
|
||||
return yield* Undefined(schema_, references_, path, value)
|
||||
case 'Union':
|
||||
return yield* Union(anySchema, anyReferences, path, value)
|
||||
return yield* Union(schema_, references_, path, value)
|
||||
case 'Uint8Array':
|
||||
return yield* Uint8Array(anySchema, anyReferences, path, value)
|
||||
return yield* Uint8Array(schema_, references_, path, value)
|
||||
case 'Unknown':
|
||||
return yield* Unknown(anySchema, anyReferences, path, value)
|
||||
return yield* Unknown(schema_, references_, path, value)
|
||||
case 'Void':
|
||||
return yield* Void(anySchema, anyReferences, path, value)
|
||||
return yield* Void(schema_, references_, path, value)
|
||||
default:
|
||||
if (!Custom.Has(anySchema[Types.Kind])) throw new ValueErrorsUnknownTypeError(schema)
|
||||
return yield* UserDefined(anySchema, anyReferences, path, value)
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueErrorsUnknownTypeError(schema)
|
||||
return yield* UserDefined(schema_, references_, path, value)
|
||||
}
|
||||
}
|
||||
|
||||
export function* Errors<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: any): IterableIterator<ValueError> {
|
||||
yield* Visit(schema, references, '', value)
|
||||
export function Errors<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: any): ValueErrorIterator {
|
||||
const iterator = Visit(schema, references, '', value)
|
||||
return new ValueErrorIterator(iterator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export type FormatValidationFunction = (value: string) => boolean
|
||||
|
||||
/** Provides functions to create user defined string formats */
|
||||
export namespace Format {
|
||||
const formats = new Map<string, FormatValidationFunction>()
|
||||
|
||||
/** Clears all user defined string formats */
|
||||
export function Clear() {
|
||||
return formats.clear()
|
||||
}
|
||||
|
||||
/** Returns true if the user defined string format exists */
|
||||
export function Has(format: string) {
|
||||
return formats.has(format)
|
||||
}
|
||||
|
||||
/** Sets a validation function for a user defined string format */
|
||||
export function Set(format: string, func: FormatValidationFunction) {
|
||||
formats.set(format, func)
|
||||
}
|
||||
|
||||
/** Gets a validation function for a user defined string format */
|
||||
export function Get(format: string) {
|
||||
return formats.get(format)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/format
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export * from './format'
|
||||
@@ -1,46 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/guard
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, dTribute, 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 '../typebox'
|
||||
|
||||
export namespace TypeExtends {
|
||||
/**
|
||||
* This function returns true if the given schema is undefined, either directly or
|
||||
* through union composition. This check is required on object property types of
|
||||
* undefined, where an additional `'x' in value` check is required to determine
|
||||
* the keys existence.
|
||||
*/
|
||||
export function Undefined(schema: Types.TSchema): boolean {
|
||||
if (schema[Types.Kind] === 'Undefined') return true
|
||||
if (schema[Types.Kind] === 'Union') {
|
||||
const union = schema as Types.TUnion
|
||||
return union.anyOf.some((schema) => Undefined(schema))
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,487 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/guard
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, dTribute, 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 { Custom } from '../custom/index'
|
||||
import * as Types from '../typebox'
|
||||
|
||||
export class TypeGuardUnknownTypeError extends Error {
|
||||
constructor(public readonly schema: unknown) {
|
||||
super('TypeGuard: Unknown type')
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides functionality to test if values are TypeBox types */
|
||||
export namespace TypeGuard {
|
||||
function IsObject(value: unknown): value is Record<string | symbol, any> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function IsArray(value: unknown): value is any[] {
|
||||
return typeof value === 'object' && value !== null && Array.isArray(value)
|
||||
}
|
||||
|
||||
function IsPattern(value: unknown): value is string {
|
||||
try {
|
||||
new RegExp(value as string)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function IsControlCharacterFree(value: unknown): value is string {
|
||||
if (typeof value !== 'string') return false
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const code = value.charCodeAt(i)
|
||||
if ((code >= 7 && code <= 13) || code === 27 || code === 127) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function IsString(value: unknown): value is string {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
|
||||
function IsNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && !isNaN(value)
|
||||
}
|
||||
|
||||
function IsBoolean(value: unknown): value is boolean {
|
||||
return typeof value === 'boolean'
|
||||
}
|
||||
|
||||
function IsOptionalNumber(value: unknown): value is number | undefined {
|
||||
return value === undefined || (value !== undefined && IsNumber(value))
|
||||
}
|
||||
|
||||
function IsOptionalBoolean(value: unknown): value is boolean | undefined {
|
||||
return value === undefined || (value !== undefined && IsBoolean(value))
|
||||
}
|
||||
|
||||
function IsOptionalString(value: unknown): value is string | undefined {
|
||||
return value === undefined || (value !== undefined && IsString(value))
|
||||
}
|
||||
|
||||
function IsOptionalPattern(value: unknown): value is string | undefined {
|
||||
return value === undefined || (value !== undefined && IsString(value) && IsControlCharacterFree(value) && IsPattern(value))
|
||||
}
|
||||
|
||||
function IsOptionalFormat(value: unknown): value is string | undefined {
|
||||
return value === undefined || (value !== undefined && IsString(value) && IsControlCharacterFree(value))
|
||||
}
|
||||
|
||||
function IsOptionalSchema(value: unknown): value is boolean | undefined {
|
||||
return value === undefined || TSchema(value)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TAny */
|
||||
export function TAny(schema: unknown): schema is Types.TAny {
|
||||
return IsObject(schema) && schema[Types.Kind] === 'Any' && IsOptionalString(schema.$id)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TArray */
|
||||
export function TArray(schema: unknown): schema is Types.TArray {
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Array' &&
|
||||
schema.type === 'array' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
TSchema(schema.items) &&
|
||||
IsOptionalNumber(schema.minItems) &&
|
||||
IsOptionalNumber(schema.maxItems) &&
|
||||
IsOptionalBoolean(schema.uniqueItems)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TBoolean */
|
||||
export function TBoolean(schema: unknown): schema is Types.TBoolean {
|
||||
// prettier-ignore
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Boolean' &&
|
||||
schema.type === 'boolean' &&
|
||||
IsOptionalString(schema.$id)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TConstructor */
|
||||
export function TConstructor(schema: unknown): schema is Types.TConstructor {
|
||||
// prettier-ignore
|
||||
if (!(
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Constructor' &&
|
||||
schema.type === 'object' &&
|
||||
schema.instanceOf === 'Constructor' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsArray(schema.parameters) &&
|
||||
TSchema(schema.returns))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
for (const parameter of schema.parameters) {
|
||||
if (!TSchema(parameter)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TDate */
|
||||
export function TDate(schema: unknown): schema is Types.TDate {
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Date' &&
|
||||
schema.type === 'object' &&
|
||||
schema.instanceOf === 'Date' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsOptionalNumber(schema.minimumTimestamp) &&
|
||||
IsOptionalNumber(schema.maximumTimestamp) &&
|
||||
IsOptionalNumber(schema.exclusiveMinimumTimestamp) &&
|
||||
IsOptionalNumber(schema.exclusiveMaximumTimestamp)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TFunction */
|
||||
export function TFunction(schema: unknown): schema is Types.TFunction {
|
||||
// prettier-ignore
|
||||
if (!(
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Function' &&
|
||||
schema.type === 'object' &&
|
||||
schema.instanceOf === 'Function' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsArray(schema.parameters) &&
|
||||
TSchema(schema.returns))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
for (const parameter of schema.parameters) {
|
||||
if (!TSchema(parameter)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TInteger */
|
||||
export function TInteger(schema: unknown): schema is Types.TInteger {
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Integer' &&
|
||||
schema.type === 'integer' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsOptionalNumber(schema.multipleOf) &&
|
||||
IsOptionalNumber(schema.minimum) &&
|
||||
IsOptionalNumber(schema.maximum) &&
|
||||
IsOptionalNumber(schema.exclusiveMinimum) &&
|
||||
IsOptionalNumber(schema.exclusiveMaximum)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TLiteral */
|
||||
export function TLiteral(schema: unknown): schema is Types.TLiteral {
|
||||
// prettier-ignore
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Literal' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
(
|
||||
IsString(schema.const) ||
|
||||
IsNumber(schema.const) ||
|
||||
IsBoolean(schema.const)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TNever */
|
||||
export function TNever(schema: unknown): schema is Types.TNever {
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Never' &&
|
||||
IsArray(schema.allOf) &&
|
||||
schema.allOf.length === 2 &&
|
||||
IsObject(schema.allOf[0]) &&
|
||||
IsString(schema.allOf[0].type) &&
|
||||
schema.allOf[0].type === 'boolean' &&
|
||||
schema.allOf[0].const === false &&
|
||||
IsObject(schema.allOf[1]) &&
|
||||
IsString(schema.allOf[1].type) &&
|
||||
schema.allOf[1].type === 'boolean' &&
|
||||
schema.allOf[1].const === true
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TNull */
|
||||
export function TNull(schema: unknown): schema is Types.TNull {
|
||||
return IsObject(schema) && schema[Types.Kind] === 'Null' && schema.type === 'null' && IsOptionalString(schema.$id)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TNumber */
|
||||
export function TNumber(schema: unknown): schema is Types.TNumber {
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Number' &&
|
||||
schema.type === 'number' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsOptionalNumber(schema.multipleOf) &&
|
||||
IsOptionalNumber(schema.minimum) &&
|
||||
IsOptionalNumber(schema.maximum) &&
|
||||
IsOptionalNumber(schema.exclusiveMinimum) &&
|
||||
IsOptionalNumber(schema.exclusiveMaximum)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TObject */
|
||||
export function TObject(schema: unknown): schema is Types.TObject {
|
||||
if (
|
||||
!(
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Object' &&
|
||||
schema.type === 'object' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsObject(schema.properties) &&
|
||||
(IsOptionalBoolean(schema.additionalProperties) || IsOptionalSchema(schema.additionalProperties)) &&
|
||||
IsOptionalNumber(schema.minProperties) &&
|
||||
IsOptionalNumber(schema.maxProperties)
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
for (const [key, value] of Object.entries(schema.properties)) {
|
||||
if (!IsControlCharacterFree(key)) return false
|
||||
if (!TSchema(value)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TPromise */
|
||||
export function TPromise(schema: unknown): schema is Types.TPromise {
|
||||
// prettier-ignore
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Promise' &&
|
||||
schema.type === 'object' &&
|
||||
schema.instanceOf === 'Promise' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
TSchema(schema.item)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TRecord */
|
||||
export function TRecord(schema: unknown): schema is Types.TRecord {
|
||||
// prettier-ignore
|
||||
if (!(
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Record' &&
|
||||
schema.type === 'object' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
schema.additionalProperties === false &&
|
||||
IsObject(schema.patternProperties))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
const keys = Object.keys(schema.patternProperties)
|
||||
if (keys.length !== 1) {
|
||||
return false
|
||||
}
|
||||
if (!IsPattern(keys[0])) {
|
||||
return false
|
||||
}
|
||||
if (!TSchema(schema.patternProperties[keys[0]])) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TSelf */
|
||||
export function TSelf(schema: unknown): schema is Types.TSelf {
|
||||
// prettier-ignore
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Self' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsString(schema.$ref)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TRef */
|
||||
export function TRef(schema: unknown): schema is Types.TRef {
|
||||
// prettier-ignore
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Ref' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsString(schema.$ref)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TString */
|
||||
export function TString(schema: unknown): schema is Types.TString {
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'String' &&
|
||||
schema.type === 'string' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsOptionalNumber(schema.minLength) &&
|
||||
IsOptionalNumber(schema.maxLength) &&
|
||||
IsOptionalPattern(schema.pattern) &&
|
||||
IsOptionalFormat(schema.format)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TTuple */
|
||||
export function TTuple(schema: unknown): schema is Types.TTuple {
|
||||
// prettier-ignore
|
||||
if (!(
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Tuple' &&
|
||||
schema.type === 'array' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
IsNumber(schema.minItems) &&
|
||||
IsNumber(schema.maxItems) &&
|
||||
schema.minItems === schema.maxItems)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (schema.items === undefined && schema.additionalItems === undefined && schema.minItems === 0) {
|
||||
return true
|
||||
}
|
||||
if (!IsArray(schema.items)) {
|
||||
return false
|
||||
}
|
||||
for (const inner of schema.items) {
|
||||
if (!TSchema(inner)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TUndefined */
|
||||
export function TUndefined(schema: unknown): schema is Types.TUndefined {
|
||||
// prettier-ignore
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Undefined' &&
|
||||
schema.type === 'null' &&
|
||||
schema.typeOf === 'Undefined' &&
|
||||
IsOptionalString(schema.$id)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TUnion */
|
||||
export function TUnion(schema: unknown): schema is Types.TUnion {
|
||||
// prettier-ignore
|
||||
if (!(
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Union' &&
|
||||
IsArray(schema.anyOf) &&
|
||||
IsOptionalString(schema.$id))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
for (const inner of schema.anyOf) {
|
||||
if (!TSchema(inner)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TUint8Array */
|
||||
export function TUint8Array(schema: unknown): schema is Types.TUint8Array {
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Uint8Array' &&
|
||||
schema.type === 'object' &&
|
||||
IsOptionalString(schema.$id) &&
|
||||
schema.instanceOf === 'Uint8Array' &&
|
||||
IsOptionalNumber(schema.minByteLength) &&
|
||||
IsOptionalNumber(schema.maxByteLength)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TUnknown */
|
||||
export function TUnknown(schema: unknown): schema is Types.TUnknown {
|
||||
// prettier-ignore
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Unknown' &&
|
||||
IsOptionalString(schema.$id)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TVoid */
|
||||
export function TVoid(schema: unknown): schema is Types.TVoid {
|
||||
// prettier-ignore
|
||||
return (
|
||||
IsObject(schema) &&
|
||||
schema[Types.Kind] === 'Void' &&
|
||||
schema.type === 'null' &&
|
||||
schema.typeOf === 'Void' &&
|
||||
IsOptionalString(schema.$id)
|
||||
)
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is a registered user defined type */
|
||||
export function TUserDefined(schema: unknown): schema is Types.TSchema {
|
||||
return IsObject(schema) && IsString(schema[Types.Kind]) && Custom.Has(schema[Types.Kind])
|
||||
}
|
||||
|
||||
/** Returns true if the given schema is TSchema */
|
||||
export function TSchema(schema: unknown): schema is Types.TSchema {
|
||||
return (
|
||||
TAny(schema) ||
|
||||
TArray(schema) ||
|
||||
TBoolean(schema) ||
|
||||
TConstructor(schema) ||
|
||||
TDate(schema) ||
|
||||
TFunction(schema) ||
|
||||
TInteger(schema) ||
|
||||
TLiteral(schema) ||
|
||||
TNever(schema) ||
|
||||
TNull(schema) ||
|
||||
TNumber(schema) ||
|
||||
TObject(schema) ||
|
||||
TPromise(schema) ||
|
||||
TRecord(schema) ||
|
||||
TSelf(schema) ||
|
||||
TRef(schema) ||
|
||||
TString(schema) ||
|
||||
TTuple(schema) ||
|
||||
TUndefined(schema) ||
|
||||
TUnion(schema) ||
|
||||
TUint8Array(schema) ||
|
||||
TUnknown(schema) ||
|
||||
TVoid(schema) ||
|
||||
TUserDefined(schema)
|
||||
)
|
||||
}
|
||||
|
||||
/** Asserts if this schema and associated references are valid. */
|
||||
export function Assert<T extends Types.TSchema>(schema: T, references: Types.TSchema[] = []) {
|
||||
if (!TSchema(schema)) throw new TypeGuardUnknownTypeError(schema)
|
||||
for (const schema of references) {
|
||||
if (!TSchema(schema)) throw new TypeGuardUnknownTypeError(schema)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/guards
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export * from './guard'
|
||||
export * from './extends'
|
||||
@@ -26,40 +26,47 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { Kind, Type } from '../typebox'
|
||||
import { Custom } from '../custom/index'
|
||||
import { Format } from '../format/index'
|
||||
import * as Types from '../typebox'
|
||||
|
||||
export class TypeSystemDuplicateTypeKind extends Error {
|
||||
constructor(kind: string) {
|
||||
super(`Duplicate kind '${kind}' detected`)
|
||||
super(`Duplicate type kind '${kind}' detected`)
|
||||
}
|
||||
}
|
||||
export class TypeSystemDuplicateFormat extends Error {
|
||||
constructor(kind: string) {
|
||||
super(`Duplicate format '${kind}' detected`)
|
||||
super(`Duplicate string format '${kind}' detected`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates user defined types and formats and provides overrides for value checking behaviours */
|
||||
export namespace TypeSystem {
|
||||
/** Sets whether arrays should be treated as kinds of objects. The default is `false` */
|
||||
/** Sets whether arrays should be treated as a kind of objects. The default is `false` */
|
||||
export let AllowArrayObjects: boolean = false
|
||||
|
||||
/** Sets whether numeric checks should consider NaN a valid number type. The default is `false` */
|
||||
/** Sets whether `NaN` or `Infinity` should be treated as valid numeric values. The default is `false` */
|
||||
export let AllowNaN: boolean = false
|
||||
|
||||
/** Creates a custom type */
|
||||
export function CreateType<Type, Options = object>(kind: string, callback: (options: Options, value: unknown) => boolean) {
|
||||
if (Custom.Has(kind)) throw new TypeSystemDuplicateTypeKind(kind)
|
||||
Custom.Set(kind, callback)
|
||||
return (options: Partial<Options> = {}) => Type.Unsafe<Type>({ ...options, [Kind]: kind })
|
||||
/** Sets whether `null` should validate for void types. The default is `false` */
|
||||
export let AllowVoidNull: boolean = false
|
||||
/** Creates a new type */
|
||||
export function Type<Type, Options = object>(kind: string, check: (options: Options, value: unknown) => boolean) {
|
||||
if (Types.TypeRegistry.Has(kind)) throw new TypeSystemDuplicateTypeKind(kind)
|
||||
Types.TypeRegistry.Set(kind, check)
|
||||
return (options: Partial<Options> = {}) => Types.Type.Unsafe<Type>({ ...options, [Types.Kind]: kind })
|
||||
}
|
||||
|
||||
/** Creates a custom string format */
|
||||
export function CreateFormat(format: string, callback: (value: string) => boolean) {
|
||||
if (Format.Has(format)) throw new TypeSystemDuplicateFormat(format)
|
||||
Format.Set(format, callback)
|
||||
return callback
|
||||
/** Creates a new string format */
|
||||
export function Format<F extends string>(format: F, check: (value: string) => boolean): F {
|
||||
if (Types.FormatRegistry.Has(format)) throw new TypeSystemDuplicateFormat(format)
|
||||
Types.FormatRegistry.Set(format, check)
|
||||
return format
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
// Deprecated
|
||||
// ------------------------------------------------------------------------
|
||||
/** @deprecated Use `TypeSystem.Type()` instead. */
|
||||
export function CreateType<Type, Options = object>(kind: string, check: (options: Options, value: unknown) => boolean) {
|
||||
return Type<Type, Options>(kind, check)
|
||||
}
|
||||
/** @deprecated Use `TypeSystem.Format()` instead. */
|
||||
export function CreateFormat<F extends string>(format: F, check: (value: string) => boolean): F {
|
||||
return Format<F>(format, check)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"files": ["compiler/index.ts", "conditional/index.ts", "custom/index.ts", "format/index.ts", "guard/index.ts", "hash/index.ts", "value/index.ts", "typebox.ts"]
|
||||
"files": ["compiler/index.ts", "errors/index.ts", "system/index.ts", "value/index.ts", "typebox.ts"]
|
||||
}
|
||||
|
||||
2331
src/typebox.ts
2331
src/typebox.ts
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,6 @@ import * as Types from '../typebox'
|
||||
import { ValueCreate } from './create'
|
||||
import { ValueCheck } from './check'
|
||||
import { ValueClone } from './clone'
|
||||
import { Custom } from '../custom/index'
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Errors
|
||||
@@ -60,7 +59,11 @@ export class ValueCastUnknownTypeError extends Error {
|
||||
super('ValueCast: Unknown type')
|
||||
}
|
||||
}
|
||||
|
||||
export class ValueCastDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TSelf) {
|
||||
super(`ValueCast: Unable to dereference schema with $id '${schema.$ref}'`)
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// The following will score a schema against a value. For objects, the score is the tally of
|
||||
// points awarded for each property of the value. Property points are (1.0 / propertyCount)
|
||||
@@ -109,93 +112,18 @@ export namespace ValueCast {
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Guards
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
function IsObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
return typeof value === 'object' && value !== null && !globalThis.Array.isArray(value)
|
||||
}
|
||||
function IsArray(value: unknown): value is unknown[] {
|
||||
return typeof value === 'object' && globalThis.Array.isArray(value)
|
||||
}
|
||||
function IsDate(value: unknown): value is Date {
|
||||
return typeof value === 'object' && value instanceof globalThis.Date
|
||||
}
|
||||
function IsString(value: unknown): value is string {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
function IsBoolean(value: unknown): value is boolean {
|
||||
return typeof value === 'boolean'
|
||||
}
|
||||
function IsBigInt(value: unknown): value is bigint {
|
||||
return typeof value === 'bigint'
|
||||
}
|
||||
function IsNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && !isNaN(value)
|
||||
}
|
||||
function IsStringNumeric(value: unknown): value is string {
|
||||
return IsString(value) && !isNaN(value as any) && !isNaN(parseFloat(value))
|
||||
function IsString(value: unknown): value is string {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
function IsValueToString(value: unknown): value is { toString: () => string } {
|
||||
return IsBigInt(value) || IsBoolean(value) || IsNumber(value)
|
||||
}
|
||||
function IsValueTrue(value: unknown): value is true {
|
||||
return value === true || (IsNumber(value) && value === 1) || (IsBigInt(value) && value === globalThis.BigInt('1')) || (IsString(value) && (value.toLowerCase() === 'true' || value === '1'))
|
||||
}
|
||||
function IsValueFalse(value: unknown): value is true {
|
||||
return value === false || (IsNumber(value) && value === 0) || (IsBigInt(value) && value === globalThis.BigInt('0')) || (IsString(value) && (value.toLowerCase() === 'false' || value === '0'))
|
||||
}
|
||||
function IsTimeStringWithTimeZone(value: unknown): value is string {
|
||||
return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value)
|
||||
}
|
||||
function IsTimeStringWithoutTimeZone(value: unknown): value is string {
|
||||
return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value)
|
||||
}
|
||||
function IsDateTimeStringWithTimeZone(value: unknown): value is string {
|
||||
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value)
|
||||
}
|
||||
function IsDateTimeStringWithoutTimeZone(value: unknown): value is string {
|
||||
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value)
|
||||
}
|
||||
function IsDateString(value: unknown): value is string {
|
||||
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\d$/i.test(value)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Convert
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
function TryConvertString(value: unknown) {
|
||||
return IsValueToString(value) ? value.toString() : value
|
||||
}
|
||||
function TryConvertNumber(value: unknown) {
|
||||
return IsStringNumeric(value) ? parseFloat(value) : IsValueTrue(value) ? 1 : value
|
||||
}
|
||||
function TryConvertInteger(value: unknown) {
|
||||
return IsStringNumeric(value) ? parseInt(value) : IsValueTrue(value) ? 1 : value
|
||||
}
|
||||
function TryConvertBoolean(value: unknown) {
|
||||
return IsValueTrue(value) ? true : IsValueFalse(value) ? false : value
|
||||
}
|
||||
function TryConvertDate(value: unknown) {
|
||||
// note: this function may return an invalid dates for the regex tests
|
||||
// above. Invalid dates will however be checked during the casting
|
||||
// function and will return a epoch date if invalid. Consider better
|
||||
// string parsing for the iso dates in future revisions.
|
||||
return IsDate(value)
|
||||
? value
|
||||
: IsNumber(value)
|
||||
? new globalThis.Date(value)
|
||||
: IsValueTrue(value)
|
||||
? new globalThis.Date(1)
|
||||
: IsStringNumeric(value)
|
||||
? new globalThis.Date(parseInt(value))
|
||||
: IsTimeStringWithoutTimeZone(value)
|
||||
? new globalThis.Date(`1970-01-01T${value}.000Z`)
|
||||
: IsTimeStringWithTimeZone(value)
|
||||
? new globalThis.Date(`1970-01-01T${value}`)
|
||||
: IsDateTimeStringWithoutTimeZone(value)
|
||||
? new globalThis.Date(`${value}.000Z`)
|
||||
: IsDateTimeStringWithTimeZone(value)
|
||||
? new globalThis.Date(value)
|
||||
: IsDateString(value)
|
||||
? new globalThis.Date(`${value}T00:00:00.000Z`)
|
||||
: value
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Cast
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
@@ -213,9 +141,11 @@ export namespace ValueCast {
|
||||
if (!ValueCheck.Check(schema, references, unique)) throw new ValueCastArrayUniqueItemsTypeError(schema, unique)
|
||||
return unique
|
||||
}
|
||||
function BigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Boolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): any {
|
||||
const conversion = TryConvertBoolean(value)
|
||||
return ValueCheck.Check(schema, references, conversion) ? conversion : ValueCreate.Create(schema, references)
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Constructor(schema: Types.TConstructor, references: Types.TSchema[], value: any): any {
|
||||
if (ValueCheck.Check(schema, references, value)) return ValueCreate.Create(schema, references)
|
||||
@@ -228,31 +158,36 @@ export namespace ValueCast {
|
||||
return result
|
||||
}
|
||||
function Date(schema: Types.TDate, references: Types.TSchema[], value: any): any {
|
||||
const conversion = TryConvertDate(value)
|
||||
return ValueCheck.Check(schema, references, conversion) ? ValueClone.Clone(conversion) : ValueCreate.Create(schema, references)
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Function(schema: Types.TFunction, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Integer(schema: Types.TInteger, references: Types.TSchema[], value: any): any {
|
||||
const conversion = TryConvertInteger(value)
|
||||
return ValueCheck.Check(schema, references, conversion) ? conversion : ValueCreate.Create(schema, references)
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Intersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): any {
|
||||
const created = ValueCreate.Create(schema, references)
|
||||
const mapped = IsObject(created) && IsObject(value) ? { ...(created as any), ...value } : value
|
||||
return ValueCheck.Check(schema, references, mapped) ? mapped : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Literal(schema: Types.TLiteral, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Never(schema: Types.TNever, references: Types.TSchema[], value: any): any {
|
||||
throw new ValueCastNeverTypeError(schema)
|
||||
}
|
||||
function Not(schema: Types.TNot, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema.allOf[1], references)
|
||||
}
|
||||
function Null(schema: Types.TNull, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Number(schema: Types.TNumber, references: Types.TSchema[], value: any): any {
|
||||
const conversion = TryConvertNumber(value)
|
||||
return ValueCheck.Check(schema, references, conversion) ? conversion : ValueCreate.Create(schema, references)
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Object(schema: Types.TObject, references: Types.TSchema[], value: any): any {
|
||||
if (ValueCheck.Check(schema, references, value)) return ValueClone.Clone(value)
|
||||
if (ValueCheck.Check(schema, references, value)) return value
|
||||
if (value === null || typeof value !== 'object') return ValueCreate.Create(schema, references)
|
||||
const required = new Set(schema.required || [])
|
||||
const result = {} as Record<string, any>
|
||||
@@ -285,18 +220,22 @@ export namespace ValueCast {
|
||||
return result
|
||||
}
|
||||
function Ref(schema: Types.TRef<any>, references: Types.TSchema[], value: any): any {
|
||||
const reference = references.find((reference) => reference.$id === schema.$ref)
|
||||
if (reference === undefined) throw new ValueCastReferenceTypeError(schema)
|
||||
return Visit(reference, references, value)
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueCastDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references, value)
|
||||
}
|
||||
function Self(schema: Types.TSelf, references: Types.TSchema[], value: any): any {
|
||||
const reference = references.find((reference) => reference.$id === schema.$ref)
|
||||
if (reference === undefined) throw new ValueCastReferenceTypeError(schema)
|
||||
return Visit(reference, references, value)
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueCastDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references, value)
|
||||
}
|
||||
function String(schema: Types.TString, references: Types.TSchema[], value: any): any {
|
||||
const conversion = TryConvertString(value)
|
||||
return ValueCheck.Check(schema, references, conversion) ? conversion : ValueCreate.Create(schema, references)
|
||||
return ValueCheck.Check(schema, references, value) ? value : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Symbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): any {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
function Tuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: any): any {
|
||||
if (ValueCheck.Check(schema, references, value)) return ValueClone.Clone(value)
|
||||
@@ -323,61 +262,69 @@ export namespace ValueCast {
|
||||
return ValueCheck.Check(schema, references, value) ? ValueClone.Clone(value) : ValueCreate.Create(schema, references)
|
||||
}
|
||||
export function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any): any {
|
||||
const anyReferences = schema.$id === undefined ? references : [schema, ...references]
|
||||
const anySchema = schema as any
|
||||
const references_ = IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema[Types.Kind]) {
|
||||
case 'Any':
|
||||
return Any(anySchema, anyReferences, value)
|
||||
return Any(schema_, references_, value)
|
||||
case 'Array':
|
||||
return Array(anySchema, anyReferences, value)
|
||||
return Array(schema_, references_, value)
|
||||
case 'BigInt':
|
||||
return BigInt(schema_, references_, value)
|
||||
case 'Boolean':
|
||||
return Boolean(anySchema, anyReferences, value)
|
||||
return Boolean(schema_, references_, value)
|
||||
case 'Constructor':
|
||||
return Constructor(anySchema, anyReferences, value)
|
||||
return Constructor(schema_, references_, value)
|
||||
case 'Date':
|
||||
return Date(anySchema, anyReferences, value)
|
||||
return Date(schema_, references_, value)
|
||||
case 'Function':
|
||||
return Function(anySchema, anyReferences, value)
|
||||
return Function(schema_, references_, value)
|
||||
case 'Integer':
|
||||
return Integer(anySchema, anyReferences, value)
|
||||
return Integer(schema_, references_, value)
|
||||
case 'Intersect':
|
||||
return Intersect(schema_, references_, value)
|
||||
case 'Literal':
|
||||
return Literal(anySchema, anyReferences, value)
|
||||
return Literal(schema_, references_, value)
|
||||
case 'Never':
|
||||
return Never(anySchema, anyReferences, value)
|
||||
return Never(schema_, references_, value)
|
||||
case 'Not':
|
||||
return Not(schema_, references_, value)
|
||||
case 'Null':
|
||||
return Null(anySchema, anyReferences, value)
|
||||
return Null(schema_, references_, value)
|
||||
case 'Number':
|
||||
return Number(anySchema, anyReferences, value)
|
||||
return Number(schema_, references_, value)
|
||||
case 'Object':
|
||||
return Object(anySchema, anyReferences, value)
|
||||
return Object(schema_, references_, value)
|
||||
case 'Promise':
|
||||
return Promise(anySchema, anyReferences, value)
|
||||
return Promise(schema_, references_, value)
|
||||
case 'Record':
|
||||
return Record(anySchema, anyReferences, value)
|
||||
return Record(schema_, references_, value)
|
||||
case 'Ref':
|
||||
return Ref(anySchema, anyReferences, value)
|
||||
return Ref(schema_, references_, value)
|
||||
case 'Self':
|
||||
return Self(anySchema, anyReferences, value)
|
||||
return Self(schema_, references_, value)
|
||||
case 'String':
|
||||
return String(anySchema, anyReferences, value)
|
||||
return String(schema_, references_, value)
|
||||
case 'Symbol':
|
||||
return Symbol(schema_, references_, value)
|
||||
case 'Tuple':
|
||||
return Tuple(anySchema, anyReferences, value)
|
||||
return Tuple(schema_, references_, value)
|
||||
case 'Undefined':
|
||||
return Undefined(anySchema, anyReferences, value)
|
||||
return Undefined(schema_, references_, value)
|
||||
case 'Union':
|
||||
return Union(anySchema, anyReferences, value)
|
||||
return Union(schema_, references_, value)
|
||||
case 'Uint8Array':
|
||||
return Uint8Array(anySchema, anyReferences, value)
|
||||
return Uint8Array(schema_, references_, value)
|
||||
case 'Unknown':
|
||||
return Unknown(anySchema, anyReferences, value)
|
||||
return Unknown(schema_, references_, value)
|
||||
case 'Void':
|
||||
return Void(anySchema, anyReferences, value)
|
||||
return Void(schema_, references_, value)
|
||||
default:
|
||||
if (!Custom.Has(anySchema[Types.Kind])) throw new ValueCastUnknownTypeError(anySchema)
|
||||
return UserDefined(anySchema, anyReferences, value)
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueCastUnknownTypeError(schema_)
|
||||
return UserDefined(schema_, references_, value)
|
||||
}
|
||||
}
|
||||
export function Cast<T extends Types.TSchema, R extends Types.TSchema[]>(schema: T, references: [...R], value: any): Types.Static<T> {
|
||||
return schema.$id === undefined ? Visit(schema, references, value) : Visit(schema, [schema, ...references], value)
|
||||
export function Cast<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: any): Types.Static<T> {
|
||||
return Visit(schema, references, ValueClone.Clone(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,156 +28,197 @@ THE SOFTWARE.
|
||||
|
||||
import * as Types from '../typebox'
|
||||
import { TypeSystem } from '../system/index'
|
||||
import { TypeExtends } from '../guard/extends'
|
||||
import { Format } from '../format/index'
|
||||
import { Custom } from '../custom/index'
|
||||
import { ValueHash } from '../hash/index'
|
||||
import { ValueHash } from './hash'
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Errors
|
||||
// -------------------------------------------------------------------------
|
||||
export class ValueCheckUnknownTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super(`ValueCheck: ${schema[Types.Kind] ? `Unknown type '${schema[Types.Kind]}'` : 'Unknown type'}`)
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ValueCheck {
|
||||
// --------------------------------------------------------
|
||||
// Guards
|
||||
// --------------------------------------------------------
|
||||
|
||||
function IsNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && !isNaN(value)
|
||||
export class ValueCheckDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TSelf) {
|
||||
super(`ValueCheck: Unable to dereference schema with $id '${schema.$ref}'`)
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
}
|
||||
export namespace ValueCheck {
|
||||
// ----------------------------------------------------------------------
|
||||
// Guards
|
||||
// --------------------------------------------------------
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
function IsObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
const result = typeof value === 'object' && value !== null
|
||||
return TypeSystem.AllowArrayObjects ? result : result && !globalThis.Array.isArray(value)
|
||||
}
|
||||
function IsRecordObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
return IsObject(value) && !(value instanceof globalThis.Date) && !(value instanceof globalThis.Uint8Array)
|
||||
}
|
||||
function IsBigInt(value: unknown): value is bigint {
|
||||
return typeof value === 'bigint'
|
||||
}
|
||||
function IsNumber(value: unknown): value is number {
|
||||
const result = typeof value === 'number'
|
||||
return TypeSystem.AllowNaN ? result : result && globalThis.Number.isFinite(value)
|
||||
}
|
||||
function IsInteger(value: unknown): value is number {
|
||||
return globalThis.Number.isInteger(value)
|
||||
}
|
||||
function IsString(value: unknown): value is string {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
function IsVoid(value: unknown): value is void {
|
||||
const result = value === undefined
|
||||
return TypeSystem.AllowVoidNull ? result || value === null : result
|
||||
}
|
||||
function IsDefined<T>(value: unknown): value is T {
|
||||
return value !== undefined
|
||||
}
|
||||
// ----------------------------------------------------------------------
|
||||
// Types
|
||||
// ----------------------------------------------------------------------
|
||||
function Any(schema: Types.TAny, references: Types.TSchema[], value: any): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
function Array(schema: Types.TArray, references: Types.TSchema[], value: any): boolean {
|
||||
if (!globalThis.Array.isArray(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.minItems) && !(value.length >= schema.minItems)) {
|
||||
if (IsDefined<number>(schema.minItems) && !(value.length >= schema.minItems)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.maxItems) && !(value.length <= schema.maxItems)) {
|
||||
if (IsDefined<number>(schema.maxItems) && !(value.length <= schema.maxItems)) {
|
||||
return false
|
||||
}
|
||||
// prettier-ignore
|
||||
if (schema.uniqueItems === true && !((function() { const set = new Set(); for(const element of value) { const hashed = ValueHash.Create(element); if(set.has(hashed)) { return false } else { set.add(hashed) } } return true })())) {
|
||||
return false
|
||||
}
|
||||
return value.every((val) => Visit(schema.items, references, val))
|
||||
return value.every((value) => Visit(schema.items, references, value))
|
||||
}
|
||||
function BigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): boolean {
|
||||
if (!IsBigInt(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.multipleOf) && !(value % schema.multipleOf === globalThis.BigInt(0))) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<bigint>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function Boolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): boolean {
|
||||
return typeof value === 'boolean'
|
||||
}
|
||||
|
||||
function Constructor(schema: Types.TConstructor, references: Types.TSchema[], value: any): boolean {
|
||||
return Visit(schema.returns, references, value.prototype)
|
||||
}
|
||||
|
||||
function Date(schema: Types.TDate, references: Types.TSchema[], value: any): boolean {
|
||||
if (!(value instanceof globalThis.Date)) {
|
||||
return false
|
||||
}
|
||||
if (isNaN(value.getTime())) {
|
||||
if (!IsNumber(value.getTime())) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) {
|
||||
if (IsDefined<number>(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
|
||||
if (IsDefined<number>(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) {
|
||||
if (IsDefined<number>(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) {
|
||||
if (IsDefined<number>(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function Function(schema: Types.TFunction, references: Types.TSchema[], value: any): boolean {
|
||||
return typeof value === 'function'
|
||||
}
|
||||
|
||||
function Integer(schema: Types.TInteger, references: Types.TSchema[], value: any): boolean {
|
||||
if (!(typeof value === 'number' && globalThis.Number.isInteger(value))) {
|
||||
if (!IsInteger(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.minimum) && !(value >= schema.minimum)) {
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.maximum) && !(value <= schema.maximum)) {
|
||||
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function Intersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): boolean {
|
||||
if (!schema.allOf.every((schema) => Visit(schema, references, value))) {
|
||||
return false
|
||||
} else if (schema.unevaluatedProperties === false) {
|
||||
const schemaKeys = Types.KeyResolver.Resolve(schema)
|
||||
const valueKeys = globalThis.Object.getOwnPropertyNames(value)
|
||||
return valueKeys.every((key) => schemaKeys.includes(key))
|
||||
} else if (Types.TypeGuard.TSchema(schema.unevaluatedProperties)) {
|
||||
const schemaKeys = Types.KeyResolver.Resolve(schema)
|
||||
const valueKeys = globalThis.Object.getOwnPropertyNames(value)
|
||||
return valueKeys.every((key) => schemaKeys.includes(key) || Visit(schema.unevaluatedProperties as Types.TSchema, references, value[key]))
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
function Literal(schema: Types.TLiteral, references: Types.TSchema[], value: any): boolean {
|
||||
return value === schema.const
|
||||
}
|
||||
|
||||
function Never(schema: Types.TNever, references: Types.TSchema[], value: any): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
function Not(schema: Types.TNot, references: Types.TSchema[], value: any): boolean {
|
||||
return !Visit(schema.allOf[0].not, references, value) && Visit(schema.allOf[1], references, value)
|
||||
}
|
||||
function Null(schema: Types.TNull, references: Types.TSchema[], value: any): boolean {
|
||||
return value === null
|
||||
}
|
||||
|
||||
function Number(schema: Types.TNumber, references: Types.TSchema[], value: any): boolean {
|
||||
if (TypeSystem.AllowNaN) {
|
||||
if (!(typeof value === 'number')) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (!(typeof value === 'number' && !isNaN(value))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (IsNumber(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
if (!IsNumber(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.minimum) && !(value >= schema.minimum)) {
|
||||
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.maximum) && !(value <= schema.maximum)) {
|
||||
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
|
||||
return false
|
||||
}
|
||||
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function Object(schema: Types.TObject, references: Types.TSchema[], value: any): boolean {
|
||||
if (TypeSystem.AllowArrayObjects) {
|
||||
if (!(typeof value === 'object' && value !== null)) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (!(typeof value === 'object' && value !== null && !globalThis.Array.isArray(value))) {
|
||||
return false
|
||||
}
|
||||
if (!IsObject(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.minProperties) && !(globalThis.Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
|
||||
return false
|
||||
@@ -185,61 +226,43 @@ export namespace ValueCheck {
|
||||
if (IsNumber(schema.maxProperties) && !(globalThis.Object.getOwnPropertyNames(value).length <= schema.maxProperties)) {
|
||||
return false
|
||||
}
|
||||
const propertyKeys = globalThis.Object.getOwnPropertyNames(schema.properties)
|
||||
const knownKeys = globalThis.Object.getOwnPropertyNames(schema.properties)
|
||||
for (const knownKey of knownKeys) {
|
||||
const property = schema.properties[knownKey]
|
||||
if (schema.required && schema.required.includes(knownKey)) {
|
||||
if (!Visit(property, references, value[knownKey])) {
|
||||
return false
|
||||
}
|
||||
if (Types.ExtendsUndefined.Check(property)) {
|
||||
return knownKey in value
|
||||
}
|
||||
} else {
|
||||
if (knownKey in value && !Visit(property, references, value[knownKey])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (schema.additionalProperties === false) {
|
||||
// optimization: If the property key length matches the required keys length
|
||||
// then we only need check that the values property key length matches that
|
||||
// of the property key length. This is because exhaustive testing for values
|
||||
// will occur in subsequent property tests.
|
||||
if (schema.required && schema.required.length === propertyKeys.length && !(globalThis.Object.getOwnPropertyNames(value).length === propertyKeys.length)) {
|
||||
return false
|
||||
const valueKeys = globalThis.Object.getOwnPropertyNames(value)
|
||||
// optimization: value is valid if schemaKey length matches the valueKey length
|
||||
if (schema.required && schema.required.length === knownKeys.length && valueKeys.length === knownKeys.length) {
|
||||
return true
|
||||
} else {
|
||||
if (!globalThis.Object.getOwnPropertyNames(value).every((key) => propertyKeys.includes(key))) {
|
||||
return false
|
||||
}
|
||||
return valueKeys.every((valueKey) => knownKeys.includes(valueKey))
|
||||
}
|
||||
} else if (typeof schema.additionalProperties === 'object') {
|
||||
const valueKeys = globalThis.Object.getOwnPropertyNames(value)
|
||||
return valueKeys.every((key) => knownKeys.includes(key) || Visit(schema.additionalProperties as Types.TSchema, references, value[key]))
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
if (typeof schema.additionalProperties === 'object') {
|
||||
for (const objectKey of globalThis.Object.getOwnPropertyNames(value)) {
|
||||
if (propertyKeys.includes(objectKey)) continue
|
||||
if (!Visit(schema.additionalProperties as Types.TSchema, references, value[objectKey])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const propertyKey of propertyKeys) {
|
||||
const propertySchema = schema.properties[propertyKey]
|
||||
if (schema.required && schema.required.includes(propertyKey)) {
|
||||
if (!Visit(propertySchema, references, value[propertyKey])) {
|
||||
return false
|
||||
}
|
||||
if (TypeExtends.Undefined(propertySchema)) {
|
||||
return propertyKey in value
|
||||
}
|
||||
} else {
|
||||
if (value[propertyKey] !== undefined) {
|
||||
if (!Visit(propertySchema, references, value[propertyKey])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function Promise(schema: Types.TPromise<any>, references: Types.TSchema[], value: any): boolean {
|
||||
return typeof value === 'object' && typeof value.then === 'function'
|
||||
}
|
||||
|
||||
function Record(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: any): boolean {
|
||||
if (TypeSystem.AllowArrayObjects) {
|
||||
if (!(typeof value === 'object' && value !== null && !(value instanceof globalThis.Date))) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (!(typeof value === 'object' && value !== null && !(value instanceof globalThis.Date) && !globalThis.Array.isArray(value))) {
|
||||
return false
|
||||
}
|
||||
if (!IsRecordObject(value)) {
|
||||
return false
|
||||
}
|
||||
const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0]
|
||||
const regex = new RegExp(keyPattern)
|
||||
@@ -251,41 +274,45 @@ export namespace ValueCheck {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function Ref(schema: Types.TRef<any>, references: Types.TSchema[], value: any): boolean {
|
||||
const reference = references.find((reference) => reference.$id === schema.$ref)
|
||||
if (reference === undefined) throw new Error(`ValueCheck.Ref: Cannot find schema with $id '${schema.$ref}'.`)
|
||||
return Visit(reference, references, value)
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueCheckDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references, value)
|
||||
}
|
||||
|
||||
function Self(schema: Types.TSelf, references: Types.TSchema[], value: any): boolean {
|
||||
const reference = references.find((reference) => reference.$id === schema.$ref)
|
||||
if (reference === undefined) throw new Error(`ValueCheck.Self: Cannot find schema with $id '${schema.$ref}'.`)
|
||||
return Visit(reference, references, value)
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueCheckDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references, value)
|
||||
}
|
||||
|
||||
function String(schema: Types.TString, references: Types.TSchema[], value: any): boolean {
|
||||
if (!(typeof value === 'string')) {
|
||||
if (!IsString(value)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.minLength)) {
|
||||
if (IsDefined<number>(schema.minLength)) {
|
||||
if (!(value.length >= schema.minLength)) return false
|
||||
}
|
||||
if (IsNumber(schema.maxLength)) {
|
||||
if (IsDefined<number>(schema.maxLength)) {
|
||||
if (!(value.length <= schema.maxLength)) return false
|
||||
}
|
||||
if (schema.pattern !== undefined) {
|
||||
if (IsDefined<string>(schema.pattern)) {
|
||||
const regex = new RegExp(schema.pattern)
|
||||
if (!regex.test(value)) return false
|
||||
}
|
||||
if (schema.format !== undefined) {
|
||||
if (!Format.Has(schema.format)) return false
|
||||
const func = Format.Get(schema.format)!
|
||||
if (IsDefined<string>(schema.format)) {
|
||||
if (!Types.FormatRegistry.Has(schema.format)) return false
|
||||
const func = Types.FormatRegistry.Get(schema.format)!
|
||||
return func(value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function Symbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): boolean {
|
||||
if (!(typeof value === 'symbol')) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
function Tuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: any): boolean {
|
||||
if (!globalThis.Array.isArray(value)) {
|
||||
return false
|
||||
@@ -304,103 +331,102 @@ export namespace ValueCheck {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function Undefined(schema: Types.TUndefined, references: Types.TSchema[], value: any): boolean {
|
||||
return value === undefined
|
||||
}
|
||||
|
||||
function Union(schema: Types.TUnion<any[]>, references: Types.TSchema[], value: any): boolean {
|
||||
return schema.anyOf.some((inner) => Visit(inner, references, value))
|
||||
}
|
||||
|
||||
function Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: any): boolean {
|
||||
if (!(value instanceof globalThis.Uint8Array)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) {
|
||||
if (IsDefined<number>(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) {
|
||||
return false
|
||||
}
|
||||
if (IsNumber(schema.minByteLength) && !(value.length >= schema.minByteLength)) {
|
||||
if (IsDefined<number>(schema.minByteLength) && !(value.length >= schema.minByteLength)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function Unknown(schema: Types.TUnknown, references: Types.TSchema[], value: any): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
function Void(schema: Types.TVoid, references: Types.TSchema[], value: any): boolean {
|
||||
return value === null
|
||||
return IsVoid(value)
|
||||
}
|
||||
|
||||
function UserDefined(schema: Types.TSchema, references: Types.TSchema[], value: unknown): boolean {
|
||||
if (!Custom.Has(schema[Types.Kind])) return false
|
||||
const func = Custom.Get(schema[Types.Kind])!
|
||||
if (!Types.TypeRegistry.Has(schema[Types.Kind])) return false
|
||||
const func = Types.TypeRegistry.Get(schema[Types.Kind])!
|
||||
return func(schema, value)
|
||||
}
|
||||
|
||||
function Visit<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: any): boolean {
|
||||
const anyReferences = schema.$id === undefined ? references : [schema, ...references]
|
||||
const anySchema = schema as any
|
||||
switch (anySchema[Types.Kind]) {
|
||||
const references_ = IsDefined<string>(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema_[Types.Kind]) {
|
||||
case 'Any':
|
||||
return Any(anySchema, anyReferences, value)
|
||||
return Any(schema_, references_, value)
|
||||
case 'Array':
|
||||
return Array(anySchema, anyReferences, value)
|
||||
return Array(schema_, references_, value)
|
||||
case 'BigInt':
|
||||
return BigInt(schema_, references_, value)
|
||||
case 'Boolean':
|
||||
return Boolean(anySchema, anyReferences, value)
|
||||
return Boolean(schema_, references_, value)
|
||||
case 'Constructor':
|
||||
return Constructor(anySchema, anyReferences, value)
|
||||
return Constructor(schema_, references_, value)
|
||||
case 'Date':
|
||||
return Date(anySchema, anyReferences, value)
|
||||
return Date(schema_, references_, value)
|
||||
case 'Function':
|
||||
return Function(anySchema, anyReferences, value)
|
||||
return Function(schema_, references_, value)
|
||||
case 'Integer':
|
||||
return Integer(anySchema, anyReferences, value)
|
||||
return Integer(schema_, references_, value)
|
||||
case 'Intersect':
|
||||
return Intersect(schema_, references_, value)
|
||||
case 'Literal':
|
||||
return Literal(anySchema, anyReferences, value)
|
||||
return Literal(schema_, references_, value)
|
||||
case 'Never':
|
||||
return Never(anySchema, anyReferences, value)
|
||||
return Never(schema_, references_, value)
|
||||
case 'Not':
|
||||
return Not(schema_, references_, value)
|
||||
case 'Null':
|
||||
return Null(anySchema, anyReferences, value)
|
||||
return Null(schema_, references_, value)
|
||||
case 'Number':
|
||||
return Number(anySchema, anyReferences, value)
|
||||
return Number(schema_, references_, value)
|
||||
case 'Object':
|
||||
return Object(anySchema, anyReferences, value)
|
||||
return Object(schema_, references_, value)
|
||||
case 'Promise':
|
||||
return Promise(anySchema, anyReferences, value)
|
||||
return Promise(schema_, references_, value)
|
||||
case 'Record':
|
||||
return Record(anySchema, anyReferences, value)
|
||||
return Record(schema_, references_, value)
|
||||
case 'Ref':
|
||||
return Ref(anySchema, anyReferences, value)
|
||||
return Ref(schema_, references_, value)
|
||||
case 'Self':
|
||||
return Self(anySchema, anyReferences, value)
|
||||
return Self(schema_, references_, value)
|
||||
case 'String':
|
||||
return String(anySchema, anyReferences, value)
|
||||
return String(schema_, references_, value)
|
||||
case 'Symbol':
|
||||
return Symbol(schema_, references_, value)
|
||||
case 'Tuple':
|
||||
return Tuple(anySchema, anyReferences, value)
|
||||
return Tuple(schema_, references_, value)
|
||||
case 'Undefined':
|
||||
return Undefined(anySchema, anyReferences, value)
|
||||
return Undefined(schema_, references_, value)
|
||||
case 'Union':
|
||||
return Union(anySchema, anyReferences, value)
|
||||
return Union(schema_, references_, value)
|
||||
case 'Uint8Array':
|
||||
return Uint8Array(anySchema, anyReferences, value)
|
||||
return Uint8Array(schema_, references_, value)
|
||||
case 'Unknown':
|
||||
return Unknown(anySchema, anyReferences, value)
|
||||
return Unknown(schema_, references_, value)
|
||||
case 'Void':
|
||||
return Void(anySchema, anyReferences, value)
|
||||
return Void(schema_, references_, value)
|
||||
default:
|
||||
if (!Custom.Has(anySchema[Types.Kind])) throw new ValueCheckUnknownTypeError(anySchema)
|
||||
return UserDefined(anySchema, anyReferences, value)
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueCheckUnknownTypeError(schema_)
|
||||
return UserDefined(schema_, references_, value)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Check
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
export function Check<T extends Types.TSchema, R extends Types.TSchema[]>(schema: T, references: [...R], value: any): boolean {
|
||||
return schema.$id === undefined ? Visit(schema, references, value) : Visit(schema, [schema, ...references], value)
|
||||
export function Check<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: any): boolean {
|
||||
return Visit(schema, references, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,24 +32,19 @@ export namespace ValueClone {
|
||||
function Array(value: ArrayType): any {
|
||||
return value.map((element: any) => Clone(element))
|
||||
}
|
||||
|
||||
function Date(value: Date): any {
|
||||
return new globalThis.Date(value.toISOString())
|
||||
}
|
||||
|
||||
function Object(value: ObjectType): any {
|
||||
const keys = [...globalThis.Object.keys(value), ...globalThis.Object.getOwnPropertySymbols(value)]
|
||||
return keys.reduce((acc, key) => ({ ...acc, [key]: Clone(value[key]) }), {})
|
||||
}
|
||||
|
||||
function TypedArray(value: TypedArrayType): any {
|
||||
return value.slice()
|
||||
}
|
||||
|
||||
function Value(value: ValueType): any {
|
||||
return value
|
||||
}
|
||||
|
||||
export function Clone<T extends unknown>(value: T): T {
|
||||
if (Is.Date(value)) {
|
||||
return Date(value)
|
||||
|
||||
342
src/value/convert.ts
Normal file
342
src/value/convert.ts
Normal file
@@ -0,0 +1,342 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/value
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as Types from '../typebox'
|
||||
import { ValueClone } from './clone'
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Errors
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
export class ValueConvertUnknownTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueConvert: Unknown type')
|
||||
}
|
||||
}
|
||||
export class ValueConvertDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TSelf) {
|
||||
super(`ValueConvert: Unable to dereference schema with $id '${schema.$ref}'`)
|
||||
}
|
||||
}
|
||||
export namespace ValueConvert {
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Guards
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
function IsObject(value: unknown): value is Record<keyof any, unknown> {
|
||||
return typeof value === 'object' && value !== null && !globalThis.Array.isArray(value)
|
||||
}
|
||||
function IsArray(value: unknown): value is unknown[] {
|
||||
return typeof value === 'object' && globalThis.Array.isArray(value)
|
||||
}
|
||||
function IsDate(value: unknown): value is Date {
|
||||
return typeof value === 'object' && value instanceof globalThis.Date
|
||||
}
|
||||
function IsSymbol(value: unknown): value is symbol {
|
||||
return typeof value === 'symbol'
|
||||
}
|
||||
function IsString(value: unknown): value is string {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
function IsBoolean(value: unknown): value is boolean {
|
||||
return typeof value === 'boolean'
|
||||
}
|
||||
function IsBigInt(value: unknown): value is bigint {
|
||||
return typeof value === 'bigint'
|
||||
}
|
||||
function IsNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && !isNaN(value)
|
||||
}
|
||||
function IsStringNumeric(value: unknown): value is string {
|
||||
return IsString(value) && !isNaN(value as any) && !isNaN(parseFloat(value))
|
||||
}
|
||||
function IsValueToString(value: unknown): value is { toString: () => string } {
|
||||
return IsBigInt(value) || IsBoolean(value) || IsNumber(value)
|
||||
}
|
||||
function IsValueTrue(value: unknown): value is true {
|
||||
return value === true || (IsNumber(value) && value === 1) || (IsBigInt(value) && value === globalThis.BigInt('1')) || (IsString(value) && (value.toLowerCase() === 'true' || value === '1'))
|
||||
}
|
||||
function IsValueFalse(value: unknown): value is true {
|
||||
return value === false || (IsNumber(value) && value === 0) || (IsBigInt(value) && value === globalThis.BigInt('0')) || (IsString(value) && (value.toLowerCase() === 'false' || value === '0'))
|
||||
}
|
||||
function IsTimeStringWithTimeZone(value: unknown): value is string {
|
||||
return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value)
|
||||
}
|
||||
function IsTimeStringWithoutTimeZone(value: unknown): value is string {
|
||||
return IsString(value) && /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value)
|
||||
}
|
||||
function IsDateTimeStringWithTimeZone(value: unknown): value is string {
|
||||
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i.test(value)
|
||||
}
|
||||
function IsDateTimeStringWithoutTimeZone(value: unknown): value is string {
|
||||
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)?$/i.test(value)
|
||||
}
|
||||
function IsDateString(value: unknown): value is string {
|
||||
return IsString(value) && /^\d\d\d\d-[0-1]\d-[0-3]\d$/i.test(value)
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Convert
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
function TryConvertLiteralString(value: unknown, target: string) {
|
||||
const conversion = TryConvertString(value)
|
||||
return conversion === target ? conversion : value
|
||||
}
|
||||
function TryConvertLiteralNumber(value: unknown, target: number) {
|
||||
const conversion = TryConvertNumber(value)
|
||||
return conversion === target ? conversion : value
|
||||
}
|
||||
function TryConvertLiteralBoolean(value: unknown, target: boolean) {
|
||||
const conversion = TryConvertBoolean(value)
|
||||
return conversion === target ? conversion : value
|
||||
}
|
||||
function TryConvertLiteral(schema: Types.TLiteral, value: unknown) {
|
||||
if (typeof schema.const === 'string') {
|
||||
return TryConvertLiteralString(value, schema.const)
|
||||
} else if (typeof schema.const === 'number') {
|
||||
return TryConvertLiteralNumber(value, schema.const)
|
||||
} else if (typeof schema.const === 'boolean') {
|
||||
return TryConvertLiteralBoolean(value, schema.const)
|
||||
} else {
|
||||
return ValueClone.Clone(value)
|
||||
}
|
||||
}
|
||||
function TryConvertBoolean(value: unknown) {
|
||||
return IsValueTrue(value) ? true : IsValueFalse(value) ? false : value
|
||||
}
|
||||
function TryConvertBigInt(value: unknown) {
|
||||
return IsStringNumeric(value) ? globalThis.BigInt(parseInt(value)) : IsNumber(value) ? globalThis.BigInt(value | 0) : IsValueFalse(value) ? 0 : IsValueTrue(value) ? 1 : value
|
||||
}
|
||||
|
||||
function TryConvertString(value: unknown) {
|
||||
return IsValueToString(value) ? value.toString() : IsSymbol(value) && value.description !== undefined ? value.description.toString() : value
|
||||
}
|
||||
function TryConvertNumber(value: unknown) {
|
||||
return IsStringNumeric(value) ? parseFloat(value) : IsValueTrue(value) ? 1 : IsValueFalse(value) ? 0 : value
|
||||
}
|
||||
function TryConvertInteger(value: unknown) {
|
||||
return IsStringNumeric(value) ? parseInt(value) : IsNumber(value) ? value | 0 : IsValueTrue(value) ? 1 : IsValueFalse(value) ? 0 : value
|
||||
}
|
||||
function TryConvertNull(value: unknown) {
|
||||
return IsString(value) && value.toLowerCase() === 'null' ? null : value
|
||||
}
|
||||
function TryConvertUndefined(value: unknown) {
|
||||
return IsString(value) && value === 'undefined' ? undefined : value
|
||||
}
|
||||
function TryConvertDate(value: unknown) {
|
||||
// note: this function may return an invalid dates for the regex tests
|
||||
// above. Invalid dates will however be checked during the casting
|
||||
// function and will return a epoch date if invalid. Consider better
|
||||
// string parsing for the iso dates in future revisions.
|
||||
return IsDate(value)
|
||||
? value
|
||||
: IsNumber(value)
|
||||
? new globalThis.Date(value)
|
||||
: IsValueTrue(value)
|
||||
? new globalThis.Date(1)
|
||||
: IsValueFalse(value)
|
||||
? new globalThis.Date(0)
|
||||
: IsStringNumeric(value)
|
||||
? new globalThis.Date(parseInt(value))
|
||||
: IsTimeStringWithoutTimeZone(value)
|
||||
? new globalThis.Date(`1970-01-01T${value}.000Z`)
|
||||
: IsTimeStringWithTimeZone(value)
|
||||
? new globalThis.Date(`1970-01-01T${value}`)
|
||||
: IsDateTimeStringWithoutTimeZone(value)
|
||||
? new globalThis.Date(`${value}.000Z`)
|
||||
: IsDateTimeStringWithTimeZone(value)
|
||||
? new globalThis.Date(value)
|
||||
: IsDateString(value)
|
||||
? new globalThis.Date(`${value}T00:00:00.000Z`)
|
||||
: value
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Cast
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
function Any(schema: Types.TAny, references: Types.TSchema[], value: any): any {
|
||||
return value
|
||||
}
|
||||
function Array(schema: Types.TArray, references: Types.TSchema[], value: any): any {
|
||||
if (IsArray(value)) {
|
||||
return value.map((value) => Visit(schema.items, references, value))
|
||||
}
|
||||
return value
|
||||
}
|
||||
function BigInt(schema: Types.TBigInt, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertBigInt(value)
|
||||
}
|
||||
function Boolean(schema: Types.TBoolean, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertBoolean(value)
|
||||
}
|
||||
function Constructor(schema: Types.TConstructor, references: Types.TSchema[], value: any): unknown {
|
||||
return ValueClone.Clone(value)
|
||||
}
|
||||
function Date(schema: Types.TDate, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertDate(value)
|
||||
}
|
||||
function Function(schema: Types.TFunction, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Integer(schema: Types.TInteger, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertInteger(value)
|
||||
}
|
||||
function Intersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Literal(schema: Types.TLiteral, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertLiteral(schema, value)
|
||||
}
|
||||
function Never(schema: Types.TNever, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Null(schema: Types.TNull, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertNull(value)
|
||||
}
|
||||
function Number(schema: Types.TNumber, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertNumber(value)
|
||||
}
|
||||
function Object(schema: Types.TObject, references: Types.TSchema[], value: any): unknown {
|
||||
if (IsObject(value))
|
||||
return globalThis.Object.keys(schema.properties).reduce((acc, key) => {
|
||||
return value[key] !== undefined ? { ...acc, [key]: Visit(schema.properties[key], references, value[key]) } : { ...acc }
|
||||
}, value)
|
||||
return value
|
||||
}
|
||||
function Promise(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Record(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Ref(schema: Types.TRef<any>, references: Types.TSchema[], value: any): unknown {
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueConvertDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references, value)
|
||||
}
|
||||
function Self(schema: Types.TSelf, references: Types.TSchema[], value: any): unknown {
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$ref)
|
||||
if (index === -1) throw new ValueConvertDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references, value)
|
||||
}
|
||||
function String(schema: Types.TString, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertString(value)
|
||||
}
|
||||
function Symbol(schema: Types.TSymbol, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Tuple(schema: Types.TTuple<any[]>, references: Types.TSchema[], value: any): unknown {
|
||||
if (IsArray(value) && schema.items !== undefined) {
|
||||
return value.map((value, index) => {
|
||||
return index < schema.items!.length ? Visit(schema.items![index], references, value) : value
|
||||
})
|
||||
}
|
||||
return value
|
||||
}
|
||||
function Undefined(schema: Types.TUndefined, references: Types.TSchema[], value: any): unknown {
|
||||
return TryConvertUndefined(value)
|
||||
}
|
||||
function Union(schema: Types.TUnion, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Unknown(schema: Types.TUnknown, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function Void(schema: Types.TVoid, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
function UserDefined(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown {
|
||||
return value
|
||||
}
|
||||
export function Visit(schema: Types.TSchema, references: Types.TSchema[], value: any): unknown {
|
||||
const references_ = IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema[Types.Kind]) {
|
||||
case 'Any':
|
||||
return Any(schema_, references_, value)
|
||||
case 'Array':
|
||||
return Array(schema_, references_, value)
|
||||
case 'BigInt':
|
||||
return BigInt(schema_, references_, value)
|
||||
case 'Boolean':
|
||||
return Boolean(schema_, references_, value)
|
||||
case 'Constructor':
|
||||
return Constructor(schema_, references_, value)
|
||||
case 'Date':
|
||||
return Date(schema_, references_, value)
|
||||
case 'Function':
|
||||
return Function(schema_, references_, value)
|
||||
case 'Integer':
|
||||
return Integer(schema_, references_, value)
|
||||
case 'Intersect':
|
||||
return Intersect(schema_, references_, value)
|
||||
case 'Literal':
|
||||
return Literal(schema_, references_, value)
|
||||
case 'Never':
|
||||
return Never(schema_, references_, value)
|
||||
case 'Null':
|
||||
return Null(schema_, references_, value)
|
||||
case 'Number':
|
||||
return Number(schema_, references_, value)
|
||||
case 'Object':
|
||||
return Object(schema_, references_, value)
|
||||
case 'Promise':
|
||||
return Promise(schema_, references_, value)
|
||||
case 'Record':
|
||||
return Record(schema_, references_, value)
|
||||
case 'Ref':
|
||||
return Ref(schema_, references_, value)
|
||||
case 'Self':
|
||||
return Self(schema_, references_, value)
|
||||
case 'String':
|
||||
return String(schema_, references_, value)
|
||||
case 'Symbol':
|
||||
return Symbol(schema_, references_, value)
|
||||
case 'Tuple':
|
||||
return Tuple(schema_, references_, value)
|
||||
case 'Undefined':
|
||||
return Undefined(schema_, references_, value)
|
||||
case 'Union':
|
||||
return Union(schema_, references_, value)
|
||||
case 'Uint8Array':
|
||||
return Uint8Array(schema_, references_, value)
|
||||
case 'Unknown':
|
||||
return Unknown(schema_, references_, value)
|
||||
case 'Void':
|
||||
return Void(schema_, references_, value)
|
||||
default:
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueConvertUnknownTypeError(schema_)
|
||||
return UserDefined(schema_, references_, value)
|
||||
}
|
||||
}
|
||||
export function Convert<T extends Types.TSchema>(schema: T, references: Types.TSchema[], value: any): unknown {
|
||||
return Visit(schema, references, ValueClone.Clone(value))
|
||||
}
|
||||
}
|
||||
@@ -27,33 +27,55 @@ THE SOFTWARE.
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as Types from '../typebox'
|
||||
import { Custom } from '../custom/index'
|
||||
import { ValueCheck } from './check'
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Errors
|
||||
// --------------------------------------------------------------------------
|
||||
export class ValueCreateUnknownTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Unknown type')
|
||||
}
|
||||
}
|
||||
|
||||
export class ValueCreateNeverTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Never types cannot be created')
|
||||
}
|
||||
}
|
||||
|
||||
export class ValueCreateIntersectTypeError extends Error {
|
||||
constructor(public readonly schema: Types.TSchema) {
|
||||
super('ValueCreate: Can only create values for intersected objects and non-varying primitive types. Consider using a default value.')
|
||||
}
|
||||
}
|
||||
export class ValueCreateDereferenceError extends Error {
|
||||
constructor(public readonly schema: Types.TRef | Types.TSelf) {
|
||||
super(`ValueCreate: Unable to dereference schema with $id '${schema.$ref}'`)
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// ValueCreate
|
||||
// --------------------------------------------------------------------------
|
||||
export namespace ValueCreate {
|
||||
// --------------------------------------------------------
|
||||
// Guards
|
||||
// --------------------------------------------------------
|
||||
function IsString(value: unknown): value is string {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
// --------------------------------------------------------
|
||||
// Types
|
||||
// --------------------------------------------------------
|
||||
function Any(schema: Types.TAny, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function Array(schema: Types.TArray, references: Types.TSchema[]): any {
|
||||
if (schema.uniqueItems === true && schema.default === undefined) {
|
||||
throw new Error('ValueCreate.Array: Arrays with uniqueItems require a default value')
|
||||
} else if (schema.default !== undefined) {
|
||||
} else if ('default' in schema) {
|
||||
return schema.default
|
||||
} else if (schema.minItems !== undefined) {
|
||||
return globalThis.Array.from({ length: schema.minItems }).map((item) => {
|
||||
@@ -63,17 +85,22 @@ export namespace ValueCreate {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function BigInt(schema: Types.TBigInt, references: Types.TSchema[]): any {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return globalThis.BigInt(0)
|
||||
}
|
||||
}
|
||||
function Boolean(schema: Types.TBoolean, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function Constructor(schema: Types.TConstructor, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
const value = ValueCreate.Create(schema.returns, references) as any
|
||||
@@ -91,9 +118,8 @@ export namespace ValueCreate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Date(schema: Types.TDate, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else if (schema.minimumTimestamp !== undefined) {
|
||||
return new globalThis.Date(schema.minimumTimestamp)
|
||||
@@ -101,17 +127,15 @@ export namespace ValueCreate {
|
||||
return new globalThis.Date(0)
|
||||
}
|
||||
}
|
||||
|
||||
function Function(schema: Types.TFunction, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return () => ValueCreate.Create(schema.returns, references)
|
||||
}
|
||||
}
|
||||
|
||||
function Integer(schema: Types.TInteger, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else if (schema.minimum !== undefined) {
|
||||
return schema.minimum
|
||||
@@ -119,29 +143,41 @@ export namespace ValueCreate {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function Intersect(schema: Types.TIntersect, references: Types.TSchema[]): any {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
const value = schema.type === 'object' ? schema.allOf.reduce((acc, schema) => ({ ...acc, ...(Visit(schema, references) as any) }), {}) : schema.allOf.reduce((_, schema) => Visit(schema, references), undefined as any)
|
||||
if (!ValueCheck.Check(schema, references, value)) throw new ValueCreateIntersectTypeError(schema)
|
||||
return value
|
||||
}
|
||||
}
|
||||
function Literal(schema: Types.TLiteral, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return schema.const
|
||||
}
|
||||
}
|
||||
|
||||
function Never(schema: Types.TNever, references: Types.TSchema[]): any {
|
||||
throw new ValueCreateNeverTypeError(schema)
|
||||
}
|
||||
|
||||
function Not(schema: Types.TNot, references: Types.TSchema[]): any {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return Visit(schema.allOf[1], references)
|
||||
}
|
||||
}
|
||||
function Null(schema: Types.TNull, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function Number(schema: Types.TNumber, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else if (schema.minimum !== undefined) {
|
||||
return schema.minimum
|
||||
@@ -149,9 +185,8 @@ export namespace ValueCreate {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function Object(schema: Types.TObject, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
const required = new Set(schema.required)
|
||||
@@ -163,18 +198,16 @@ export namespace ValueCreate {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function Promise(schema: Types.TPromise<any>, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return globalThis.Promise.resolve(ValueCreate.Create(schema.item, references))
|
||||
}
|
||||
}
|
||||
|
||||
function Record(schema: Types.TRecord<any, any>, references: Types.TSchema[]): any {
|
||||
const [keyPattern, valueSchema] = globalThis.Object.entries(schema.patternProperties)[0]
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else if (!(keyPattern === '^.*$' || keyPattern === '^(0|[1-9][0-9]*)$')) {
|
||||
const propertyKeys = keyPattern.slice(1, keyPattern.length - 1).split('|')
|
||||
@@ -185,42 +218,41 @@ export namespace ValueCreate {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function Ref(schema: Types.TRef<any>, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
const reference = references.find((reference) => reference.$id === schema.$ref)
|
||||
if (reference === undefined) throw new Error(`ValueCreate.Ref: Cannot find schema with $id '${schema.$ref}'.`)
|
||||
return Visit(reference, references)
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$id)
|
||||
if (index === -1) throw new ValueCreateDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references)
|
||||
}
|
||||
}
|
||||
|
||||
function Self(schema: Types.TSelf, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
const reference = references.find((reference) => reference.$id === schema.$ref)
|
||||
if (reference === undefined) throw new Error(`ValueCreate.Self: Cannot locate schema with $id '${schema.$ref}'`)
|
||||
return Visit(reference, references)
|
||||
const index = references.findIndex((foreign) => foreign.$id === schema.$id)
|
||||
if (index === -1) throw new ValueCreateDereferenceError(schema)
|
||||
const target = references[index]
|
||||
return Visit(target, references)
|
||||
}
|
||||
}
|
||||
|
||||
function String(schema: Types.TString, references: Types.TSchema[]): any {
|
||||
if (schema.pattern !== undefined) {
|
||||
if (schema.default === undefined) {
|
||||
if (!('default' in schema)) {
|
||||
throw new Error('ValueCreate.String: String types with patterns must specify a default value')
|
||||
} else {
|
||||
return schema.default
|
||||
}
|
||||
} else if (schema.format !== undefined) {
|
||||
if (schema.default === undefined) {
|
||||
if (!('default' in schema)) {
|
||||
throw new Error('ValueCreate.String: String types with formats must specify a default value')
|
||||
} else {
|
||||
return schema.default
|
||||
}
|
||||
} else {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else if (schema.minLength !== undefined) {
|
||||
return globalThis.Array.from({ length: schema.minLength })
|
||||
@@ -231,9 +263,17 @@ export namespace ValueCreate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Symbol(schema: Types.TString, references: Types.TSchema[]): any {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else if ('value' in schema) {
|
||||
return globalThis.Symbol.for(schema.value)
|
||||
} else {
|
||||
return globalThis.Symbol()
|
||||
}
|
||||
}
|
||||
function Tuple(schema: Types.TTuple<any[]>, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
}
|
||||
if (schema.items === undefined) {
|
||||
@@ -242,17 +282,15 @@ export namespace ValueCreate {
|
||||
return globalThis.Array.from({ length: schema.minItems }).map((_, index) => ValueCreate.Create((schema.items as any[])[index], references))
|
||||
}
|
||||
}
|
||||
|
||||
function Undefined(schema: Types.TUndefined, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function Union(schema: Types.TUnion<any[]>, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else if (schema.anyOf.length === 0) {
|
||||
throw new Error('ValueCreate.Union: Cannot create Union with zero variants')
|
||||
@@ -261,7 +299,7 @@ export namespace ValueCreate {
|
||||
}
|
||||
}
|
||||
function Uint8Array(schema: Types.TUint8Array, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else if (schema.minByteLength !== undefined) {
|
||||
return new globalThis.Uint8Array(schema.minByteLength)
|
||||
@@ -269,90 +307,92 @@ export namespace ValueCreate {
|
||||
return new globalThis.Uint8Array(0)
|
||||
}
|
||||
}
|
||||
|
||||
function Unknown(schema: Types.TUnknown, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function Void(schema: Types.TVoid, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
return null
|
||||
return void 0
|
||||
}
|
||||
}
|
||||
|
||||
function UserDefined(schema: Types.TSchema, references: Types.TSchema[]): any {
|
||||
if (schema.default !== undefined) {
|
||||
if ('default' in schema) {
|
||||
return schema.default
|
||||
} else {
|
||||
throw new Error('ValueCreate.UserDefined: User defined types must specify a default value')
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a value from the given schema. If the schema specifies a default value, then that value is returned. */
|
||||
export function Visit<T extends Types.TSchema>(schema: T, references: Types.TSchema[]): Types.Static<T> {
|
||||
const anyReferences = schema.$id === undefined ? references : [schema, ...references]
|
||||
const anySchema = schema as any
|
||||
|
||||
switch (anySchema[Types.Kind]) {
|
||||
export function Visit(schema: Types.TSchema, references: Types.TSchema[]): unknown {
|
||||
const references_ = IsString(schema.$id) ? [...references, schema] : references
|
||||
const schema_ = schema as any
|
||||
switch (schema_[Types.Kind]) {
|
||||
case 'Any':
|
||||
return Any(anySchema, anyReferences)
|
||||
return Any(schema_, references_)
|
||||
case 'Array':
|
||||
return Array(anySchema, anyReferences)
|
||||
return Array(schema_, references_)
|
||||
case 'BigInt':
|
||||
return BigInt(schema_, references_)
|
||||
case 'Boolean':
|
||||
return Boolean(anySchema, anyReferences)
|
||||
return Boolean(schema_, references_)
|
||||
case 'Constructor':
|
||||
return Constructor(anySchema, anyReferences)
|
||||
return Constructor(schema_, references_)
|
||||
case 'Date':
|
||||
return Date(anySchema, anyReferences)
|
||||
return Date(schema_, references_)
|
||||
case 'Function':
|
||||
return Function(anySchema, anyReferences)
|
||||
return Function(schema_, references_)
|
||||
case 'Integer':
|
||||
return Integer(anySchema, anyReferences)
|
||||
return Integer(schema_, references_)
|
||||
case 'Intersect':
|
||||
return Intersect(schema_, references_)
|
||||
case 'Literal':
|
||||
return Literal(anySchema, anyReferences)
|
||||
return Literal(schema_, references_)
|
||||
case 'Never':
|
||||
return Never(anySchema, anyReferences)
|
||||
return Never(schema_, references_)
|
||||
case 'Not':
|
||||
return Not(schema_, references_)
|
||||
case 'Null':
|
||||
return Null(anySchema, anyReferences)
|
||||
return Null(schema_, references_)
|
||||
case 'Number':
|
||||
return Number(anySchema, anyReferences)
|
||||
return Number(schema_, references_)
|
||||
case 'Object':
|
||||
return Object(anySchema, anyReferences)
|
||||
return Object(schema_, references_)
|
||||
case 'Promise':
|
||||
return Promise(anySchema, anyReferences)
|
||||
return Promise(schema_, references_)
|
||||
case 'Record':
|
||||
return Record(anySchema, anyReferences)
|
||||
return Record(schema_, references_)
|
||||
case 'Ref':
|
||||
return Ref(anySchema, anyReferences)
|
||||
return Ref(schema_, references_)
|
||||
case 'Self':
|
||||
return Self(anySchema, anyReferences)
|
||||
return Self(schema_, references_)
|
||||
case 'String':
|
||||
return String(anySchema, anyReferences)
|
||||
return String(schema_, references_)
|
||||
case 'Symbol':
|
||||
return Symbol(schema_, references_)
|
||||
case 'Tuple':
|
||||
return Tuple(anySchema, anyReferences)
|
||||
return Tuple(schema_, references_)
|
||||
case 'Undefined':
|
||||
return Undefined(anySchema, anyReferences)
|
||||
return Undefined(schema_, references_)
|
||||
case 'Union':
|
||||
return Union(anySchema, anyReferences)
|
||||
return Union(schema_, references_)
|
||||
case 'Uint8Array':
|
||||
return Uint8Array(anySchema, anyReferences)
|
||||
return Uint8Array(schema_, references_)
|
||||
case 'Unknown':
|
||||
return Unknown(anySchema, anyReferences)
|
||||
return Unknown(schema_, references_)
|
||||
case 'Void':
|
||||
return Void(anySchema, anyReferences)
|
||||
return Void(schema_, references_)
|
||||
default:
|
||||
if (!Custom.Has(anySchema[Types.Kind])) throw new ValueCreateUnknownTypeError(anySchema)
|
||||
return UserDefined(anySchema, anyReferences)
|
||||
if (!Types.TypeRegistry.Has(schema_[Types.Kind])) throw new ValueCreateUnknownTypeError(schema_)
|
||||
return UserDefined(schema_, references_)
|
||||
}
|
||||
}
|
||||
|
||||
export function Create<T extends Types.TSchema, R extends Types.TSchema[]>(schema: T, references: [...R]): Types.Static<T> {
|
||||
export function Create<T extends Types.TSchema>(schema: T, references: Types.TSchema[]): Types.Static<T> {
|
||||
return Visit(schema, references)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,25 +36,20 @@ export namespace ValueEqual {
|
||||
if (leftKeys.length !== rightKeys.length) return false
|
||||
return leftKeys.every((key) => Equal(left[key], right[key]))
|
||||
}
|
||||
|
||||
function Date(left: Date, right: unknown): any {
|
||||
return Is.Date(right) && left.getTime() === right.getTime()
|
||||
}
|
||||
|
||||
function Array(left: ArrayType, right: unknown): any {
|
||||
if (!Is.Array(right) || left.length !== right.length) return false
|
||||
return left.every((value, index) => Equal(value, right[index]))
|
||||
}
|
||||
|
||||
function TypedArray(left: TypedArrayType, right: unknown): any {
|
||||
if (!Is.TypedArray(right) || left.length !== right.length || globalThis.Object.getPrototypeOf(left).constructor.name !== globalThis.Object.getPrototypeOf(right).constructor.name) return false
|
||||
return left.every((value, index) => Equal(value, right[index]))
|
||||
}
|
||||
|
||||
function Value(left: ValueType, right: unknown): any {
|
||||
return left === right
|
||||
}
|
||||
|
||||
export function Equal<T>(left: T, right: unknown): right is T {
|
||||
if (Is.Object(left)) {
|
||||
return Object(left, right)
|
||||
|
||||
@@ -31,7 +31,6 @@ export class ValueHashError extends Error {
|
||||
super(`Hash: Unable to hash value`)
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ValueHash {
|
||||
enum ByteMarker {
|
||||
Undefined,
|
||||
@@ -43,123 +42,122 @@ export namespace ValueHash {
|
||||
Array,
|
||||
Date,
|
||||
Uint8Array,
|
||||
Symbol,
|
||||
BigInt,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// State
|
||||
// ----------------------------------------------------
|
||||
|
||||
let Hash = globalThis.BigInt('14695981039346656037')
|
||||
const [Prime, Size] = [globalThis.BigInt('1099511628211'), globalThis.BigInt('2') ** globalThis.BigInt('64')]
|
||||
const Bytes = globalThis.Array.from({ length: 256 }).map((_, i) => globalThis.BigInt(i))
|
||||
const F64 = new globalThis.Float64Array(1)
|
||||
const F64In = new globalThis.DataView(F64.buffer)
|
||||
const F64Out = new globalThis.Uint8Array(F64.buffer)
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Guards
|
||||
// ----------------------------------------------------
|
||||
|
||||
function IsDate(value: unknown): value is Date {
|
||||
return value instanceof globalThis.Date
|
||||
}
|
||||
|
||||
function IsUint8Array(value: unknown): value is Uint8Array {
|
||||
return value instanceof globalThis.Uint8Array
|
||||
}
|
||||
|
||||
function IsArray(value: unknown): value is Array<unknown> {
|
||||
return globalThis.Array.isArray(value)
|
||||
}
|
||||
|
||||
function IsBoolean(value: unknown): value is boolean {
|
||||
return typeof value === 'boolean'
|
||||
}
|
||||
|
||||
function IsNull(value: unknown): value is null {
|
||||
return value === null
|
||||
}
|
||||
|
||||
function IsNumber(value: unknown): value is number {
|
||||
return typeof value === 'number'
|
||||
}
|
||||
|
||||
function IsSymbol(value: unknown): value is symbol {
|
||||
return typeof value === 'symbol'
|
||||
}
|
||||
function IsBigInt(value: unknown): value is bigint {
|
||||
return typeof value === 'bigint'
|
||||
}
|
||||
function IsObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !IsArray(value) && !IsDate(value) && !IsUint8Array(value)
|
||||
}
|
||||
|
||||
function IsString(value: unknown): value is string {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
|
||||
function IsUndefined(value: unknown): value is undefined {
|
||||
return value === undefined
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Encoding
|
||||
// ----------------------------------------------------
|
||||
|
||||
function Array(value: Array<unknown>) {
|
||||
Fnv1A64(ByteMarker.Array)
|
||||
FNV1A64(ByteMarker.Array)
|
||||
for (const item of value) {
|
||||
Visit(item)
|
||||
}
|
||||
}
|
||||
|
||||
function Boolean(value: boolean) {
|
||||
Fnv1A64(ByteMarker.Boolean)
|
||||
Fnv1A64(value ? 1 : 0)
|
||||
FNV1A64(ByteMarker.Boolean)
|
||||
FNV1A64(value ? 1 : 0)
|
||||
}
|
||||
|
||||
function Date(value: Date) {
|
||||
Fnv1A64(ByteMarker.Date)
|
||||
Visit(value.getTime())
|
||||
}
|
||||
|
||||
function Null(value: null) {
|
||||
Fnv1A64(ByteMarker.Null)
|
||||
}
|
||||
|
||||
function Number(value: number) {
|
||||
Fnv1A64(ByteMarker.Number)
|
||||
F64In.setFloat64(0, value)
|
||||
function BigInt(value: bigint) {
|
||||
FNV1A64(ByteMarker.BigInt)
|
||||
F64In.setBigInt64(0, value)
|
||||
for (const byte of F64Out) {
|
||||
Fnv1A64(byte)
|
||||
FNV1A64(byte)
|
||||
}
|
||||
}
|
||||
function Date(value: Date) {
|
||||
FNV1A64(ByteMarker.Date)
|
||||
Visit(value.getTime())
|
||||
}
|
||||
function Null(value: null) {
|
||||
FNV1A64(ByteMarker.Null)
|
||||
}
|
||||
function Number(value: number) {
|
||||
FNV1A64(ByteMarker.Number)
|
||||
F64In.setFloat64(0, value)
|
||||
for (const byte of F64Out) {
|
||||
FNV1A64(byte)
|
||||
}
|
||||
}
|
||||
|
||||
function Object(value: Record<string, unknown>) {
|
||||
Fnv1A64(ByteMarker.Object)
|
||||
FNV1A64(ByteMarker.Object)
|
||||
for (const key of globalThis.Object.keys(value).sort()) {
|
||||
Visit(key)
|
||||
Visit(value[key])
|
||||
}
|
||||
}
|
||||
|
||||
function String(value: string) {
|
||||
Fnv1A64(ByteMarker.String)
|
||||
FNV1A64(ByteMarker.String)
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
Fnv1A64(value.charCodeAt(i))
|
||||
FNV1A64(value.charCodeAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
function Symbol(value: symbol) {
|
||||
FNV1A64(ByteMarker.Symbol)
|
||||
Visit(value.description)
|
||||
}
|
||||
function Uint8Array(value: Uint8Array) {
|
||||
Fnv1A64(ByteMarker.Uint8Array)
|
||||
FNV1A64(ByteMarker.Uint8Array)
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
Fnv1A64(value[i])
|
||||
FNV1A64(value[i])
|
||||
}
|
||||
}
|
||||
|
||||
function Undefined(value: undefined) {
|
||||
return Fnv1A64(ByteMarker.Undefined)
|
||||
return FNV1A64(ByteMarker.Undefined)
|
||||
}
|
||||
|
||||
function Visit(value: any) {
|
||||
if (IsArray(value)) {
|
||||
Array(value)
|
||||
} else if (IsBoolean(value)) {
|
||||
Boolean(value)
|
||||
} else if (IsBigInt(value)) {
|
||||
BigInt(value)
|
||||
} else if (IsDate(value)) {
|
||||
Date(value)
|
||||
} else if (IsNull(value)) {
|
||||
@@ -170,6 +168,8 @@ export namespace ValueHash {
|
||||
Object(value)
|
||||
} else if (IsString(value)) {
|
||||
String(value)
|
||||
} else if (IsSymbol(value)) {
|
||||
Symbol(value)
|
||||
} else if (IsUint8Array(value)) {
|
||||
Uint8Array(value)
|
||||
} else if (IsUndefined(value)) {
|
||||
@@ -178,12 +178,10 @@ export namespace ValueHash {
|
||||
throw new ValueHashError(value)
|
||||
}
|
||||
}
|
||||
|
||||
function Fnv1A64(byte: number) {
|
||||
function FNV1A64(byte: number) {
|
||||
Hash = Hash ^ Bytes[byte]
|
||||
Hash = (Hash * Prime) % Size
|
||||
}
|
||||
|
||||
/** Creates a FNV1A-64 non cryptographic hash of the given value */
|
||||
export function Create(value: unknown) {
|
||||
Hash = globalThis.BigInt('14695981039346656037')
|
||||
@@ -26,7 +26,8 @@ THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export { ValueError, ValueErrorType } from '../errors/index'
|
||||
export { ValueError, ValueErrorIterator, ValueErrorType } from '../errors/index'
|
||||
export { ValueHash } from './hash'
|
||||
export { Edit, Insert, Update, Delete } from './delta'
|
||||
export * from './pointer'
|
||||
export * from './value'
|
||||
|
||||
@@ -27,11 +27,12 @@ THE SOFTWARE.
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import * as Types from '../typebox'
|
||||
import { ValueErrors, ValueError } from '../errors/index'
|
||||
import { ValueHash } from '../hash/index'
|
||||
import { ValueErrors, ValueErrorIterator, ValueError } from '../errors/index'
|
||||
import { ValueHash } from './hash'
|
||||
import { ValueEqual } from './equal'
|
||||
import { ValueCast } from './cast'
|
||||
import { ValueClone } from './clone'
|
||||
import { ValueConvert } from './convert'
|
||||
import { ValueCreate } from './create'
|
||||
import { ValueCheck } from './check'
|
||||
import { ValueDelta, Edit } from './delta'
|
||||
@@ -46,7 +47,6 @@ export namespace Value {
|
||||
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
|
||||
return ValueCast.Cast(schema, references, value)
|
||||
}
|
||||
|
||||
/** Creates a value from the given type */
|
||||
export function Create<T extends Types.TSchema, R extends Types.TSchema[]>(schema: T, references: [...R]): Types.Static<T>
|
||||
/** Creates a value from the given type */
|
||||
@@ -55,7 +55,6 @@ export namespace Value {
|
||||
const [schema, references] = args.length === 2 ? [args[0], args[1]] : [args[0], []]
|
||||
return ValueCreate.Create(schema, references)
|
||||
}
|
||||
|
||||
/** Returns true if the value matches the given type. */
|
||||
export function Check<T extends Types.TSchema, R extends Types.TSchema[]>(schema: T, references: [...R], value: unknown): value is Types.Static<T>
|
||||
/** Returns true if the value matches the given type. */
|
||||
@@ -64,36 +63,38 @@ export namespace Value {
|
||||
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
|
||||
return ValueCheck.Check(schema, references, value)
|
||||
}
|
||||
|
||||
/** Converts any type mismatched values to their target type if a conversion is possible. */
|
||||
export function Convert<T extends Types.TSchema, R extends Types.TSchema[]>(schema: T, references: [...R], value: unknown): unknown
|
||||
/** Converts any type mismatched values to their target type if a conversion is possible. */
|
||||
export function Convert<T extends Types.TSchema>(schema: T, value: unknown): unknown
|
||||
export function Convert(...args: any[]) {
|
||||
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
|
||||
return ValueConvert.Convert(schema, references, value)
|
||||
}
|
||||
/** Returns a structural clone of the given value */
|
||||
export function Clone<T>(value: T): T {
|
||||
return ValueClone.Clone(value)
|
||||
}
|
||||
|
||||
/** Returns an iterator for each error in this value. */
|
||||
export function Errors<T extends Types.TSchema, R extends Types.TSchema[]>(schema: T, references: [...R], value: unknown): IterableIterator<ValueError>
|
||||
export function Errors<T extends Types.TSchema, R extends Types.TSchema[]>(schema: T, references: [...R], value: unknown): ValueErrorIterator
|
||||
/** Returns an iterator for each error in this value. */
|
||||
export function Errors<T extends Types.TSchema>(schema: T, value: unknown): IterableIterator<ValueError>
|
||||
export function* Errors(...args: any[]) {
|
||||
export function Errors<T extends Types.TSchema>(schema: T, value: unknown): ValueErrorIterator
|
||||
export function Errors(...args: any[]) {
|
||||
const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]]
|
||||
yield* ValueErrors.Errors(schema, references, value)
|
||||
return ValueErrors.Errors(schema, references, value) as ValueErrorIterator
|
||||
}
|
||||
|
||||
/** Returns true if left and right values are structurally equal */
|
||||
export function Equal<T>(left: T, right: unknown): right is T {
|
||||
return ValueEqual.Equal(left, right)
|
||||
}
|
||||
|
||||
/** Returns edits to transform the current value into the next value */
|
||||
export function Diff(current: unknown, next: unknown): Edit[] {
|
||||
return ValueDelta.Diff(current, next)
|
||||
}
|
||||
|
||||
/** Returns a FNV1A-64 non cryptographic hash of the given value */
|
||||
export function Hash(value: unknown): bigint {
|
||||
return ValueHash.Create(value)
|
||||
}
|
||||
|
||||
/** Returns a new value with edits applied to the given value */
|
||||
export function Patch<T = any>(current: unknown, edits: Edit[]): T {
|
||||
return ValueDelta.Patch(current, edits) as T
|
||||
|
||||
@@ -30,4 +30,12 @@ describe('type/compiler/Any', () => {
|
||||
const T = Type.Any()
|
||||
Ok(T, undefined)
|
||||
})
|
||||
it('Should validate bigint', () => {
|
||||
const T = Type.Any()
|
||||
Ok(T, BigInt(1))
|
||||
})
|
||||
it('Should validate symbol', () => {
|
||||
const T = Type.Any()
|
||||
Ok(T, Symbol(1))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -39,10 +39,10 @@ describe('type/compiler/Array', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('Should not validate for an array of intersection types when passing additionalProperties false', () => {
|
||||
it('Should not validate for an array of composite types when passing additionalProperties false', () => {
|
||||
const A = Type.Object({ a: Type.String() })
|
||||
const B = Type.Object({ b: Type.String() })
|
||||
const C = Type.Intersect([A, B], { additionalProperties: false })
|
||||
const C = Type.Composite([A, B], { additionalProperties: false })
|
||||
const T = Type.Array(C)
|
||||
Fail(T, [
|
||||
{ a: 'hello', b: 'hello' },
|
||||
|
||||
84
test/runtime/compiler/bigint.ts
Normal file
84
test/runtime/compiler/bigint.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/BigInt', () => {
|
||||
it('Should not validate number', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, 3.14)
|
||||
})
|
||||
it('Should not validate NaN', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, NaN)
|
||||
})
|
||||
it('Should not validate +Infinity', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, Infinity)
|
||||
})
|
||||
it('Should not validate -Infinity', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, -Infinity)
|
||||
})
|
||||
it('Should not validate integer', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, 1)
|
||||
})
|
||||
it('Should not validate string', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, 'hello')
|
||||
})
|
||||
it('Should not validate boolean', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, true)
|
||||
})
|
||||
it('Should not validate array', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, [1, 2, 3])
|
||||
})
|
||||
it('Should not validate object', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, { a: 1, b: 2 })
|
||||
})
|
||||
it('Should not validate null', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, null)
|
||||
})
|
||||
it('Should not validate undefined', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, undefined)
|
||||
})
|
||||
it('Should not validate symbol', () => {
|
||||
const T = Type.BigInt()
|
||||
Fail(T, Symbol())
|
||||
})
|
||||
it('Should validate bigint', () => {
|
||||
const T = Type.BigInt()
|
||||
Ok(T, BigInt(1))
|
||||
})
|
||||
it('Should validate minimum', () => {
|
||||
const T = Type.BigInt({ minimum: BigInt(10) })
|
||||
Fail(T, BigInt(9))
|
||||
Ok(T, BigInt(10))
|
||||
})
|
||||
|
||||
it('Should validate maximum', () => {
|
||||
const T = Type.BigInt({ maximum: BigInt(10) })
|
||||
Ok(T, BigInt(10))
|
||||
Fail(T, BigInt(11))
|
||||
})
|
||||
|
||||
it('Should validate Date exclusiveMinimum', () => {
|
||||
const T = Type.BigInt({ exclusiveMinimum: BigInt(10) })
|
||||
Fail(T, BigInt(10))
|
||||
Ok(T, BigInt(11))
|
||||
})
|
||||
|
||||
it('Should validate Date exclusiveMaximum', () => {
|
||||
const T = Type.BigInt({ exclusiveMaximum: BigInt(10) })
|
||||
Ok(T, BigInt(9))
|
||||
Fail(T, BigInt(10))
|
||||
})
|
||||
|
||||
it('Should not validate NaN', () => {
|
||||
Fail(Type.Number(), NaN)
|
||||
})
|
||||
})
|
||||
@@ -37,4 +37,12 @@ describe('type/compiler/Boolean', () => {
|
||||
const T = Type.Boolean()
|
||||
Fail(T, undefined)
|
||||
})
|
||||
it('Should not validate bigint', () => {
|
||||
const T = Type.Boolean()
|
||||
Fail(T, BigInt(1))
|
||||
})
|
||||
it('Should not validate symbol', () => {
|
||||
const T = Type.Boolean()
|
||||
Fail(T, Symbol(1))
|
||||
})
|
||||
})
|
||||
|
||||
73
test/runtime/compiler/composite.ts
Normal file
73
test/runtime/compiler/composite.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Type, Static } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Composite', () => {
|
||||
it('Should compose two objects', () => {
|
||||
const A = Type.Object({ a: Type.String() })
|
||||
const B = Type.Object({ b: Type.Number() })
|
||||
const T = Type.Composite([A, B], { additionalProperties: false })
|
||||
Ok(T, { a: 'hello', b: 42 })
|
||||
})
|
||||
it('Should compose with partial', () => {
|
||||
const A = Type.Partial(Type.Object({ a: Type.Number() }))
|
||||
const B = Type.Partial(Type.Object({ b: Type.Number() }))
|
||||
const P = Type.Composite([A, B], { additionalProperties: false })
|
||||
Ok(P, { a: 1, b: 2 })
|
||||
})
|
||||
it('Should compose with overlapping same type', () => {
|
||||
const A = Type.Object({ a: Type.Number() })
|
||||
const B = Type.Object({ a: Type.Number() })
|
||||
const P = Type.Composite([A, B])
|
||||
Ok(P, { a: 1 })
|
||||
Fail(P, { a: 'hello' })
|
||||
Fail(P, {})
|
||||
})
|
||||
it('Should compose with overlapping varying type', () => {
|
||||
const A = Type.Object({ a: Type.Number() })
|
||||
const B = Type.Object({ a: Type.String() })
|
||||
const T = Type.Composite([A, B])
|
||||
Ok(T, { a: 1 })
|
||||
Ok(T, { a: 'hello' })
|
||||
Fail(T, {})
|
||||
})
|
||||
it('Should compose with deeply nest overlapping varying type', () => {
|
||||
const A = Type.Object({ a: Type.Number() })
|
||||
const B = Type.Object({ a: Type.String() })
|
||||
const C = Type.Object({ a: Type.Boolean() })
|
||||
const D = Type.Object({ a: Type.Null() })
|
||||
const T = Type.Composite([A, B, C, D])
|
||||
Ok(T, { a: 1 })
|
||||
Ok(T, { a: 'hello' })
|
||||
Ok(T, { a: false })
|
||||
Ok(T, { a: null })
|
||||
Fail(T, { a: [] })
|
||||
Fail(T, {})
|
||||
})
|
||||
it('Should pick from composited type', () => {
|
||||
const A = Type.Object({ x: Type.Number() })
|
||||
const B = Type.Object({ y: Type.Number() })
|
||||
const C = Type.Object({ z: Type.Number() })
|
||||
const T = Type.Composite([A, B, C])
|
||||
const P = Type.Pick(T, ['x', 'y'], { additionalProperties: false })
|
||||
Ok(P, { x: 1, y: 1 })
|
||||
Fail(P, { x: 1, y: 1, z: 1 })
|
||||
})
|
||||
it('Should omit from composited type', () => {
|
||||
const A = Type.Object({ x: Type.Number() })
|
||||
const B = Type.Object({ y: Type.Number() })
|
||||
const C = Type.Object({ z: Type.Number() })
|
||||
const T = Type.Composite([A, B, C])
|
||||
const P = Type.Omit(T, ['z'], { additionalProperties: false })
|
||||
Ok(P, { x: 1, y: 1 })
|
||||
Fail(P, { x: 1, y: 1, z: 1 })
|
||||
})
|
||||
|
||||
it('Should compose nested object properties', () => {
|
||||
const A = Type.Object({ x: Type.Object({ x: Type.Number() }) })
|
||||
const B = Type.Object({ x: Type.Object({ x: Type.String() }) })
|
||||
const T = Type.Composite([A, B])
|
||||
Ok(T, { x: { x: 1 } })
|
||||
Ok(T, { x: { x: 'hello' } })
|
||||
Fail(T, { x: { x: false } })
|
||||
})
|
||||
})
|
||||
@@ -1,10 +1,8 @@
|
||||
import { Custom } from '@sinclair/typebox/custom'
|
||||
import { Type, Kind } from '@sinclair/typebox'
|
||||
import { Type, Kind, TypeRegistry } from '@sinclair/typebox'
|
||||
import { Ok, Fail } from './validate'
|
||||
|
||||
describe('type/compiler/Custom', () => {
|
||||
Custom.Set('BigInt', (schema, value) => typeof value === 'bigint')
|
||||
|
||||
TypeRegistry.Set('BigInt', (schema, value) => typeof value === 'bigint')
|
||||
it('Should validate bigint', () => {
|
||||
const T = Type.Unsafe({ [Kind]: 'BigInt' })
|
||||
Ok(T, 1n)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user