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

0
.github/.keep vendored Normal file
View File

23
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Build
on: [push, pull_request]
jobs:
TypeBox:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node: [16.x, 18.x, 20.x]
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- name: Install Packages
run: npm install
- name: Build Library
run: npm run build
- name: Test Library
run: npm run test

30
.github/workflows/nightly.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Build Nightly
on:
schedule:
- cron: '0 18 * * *' # 6pm Daily
jobs:
TypeBox:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node: [20.x]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- name: Install Packages
run: npm install
- name: Install TypeScript Latest
run: npm install typescript@latest
- name: Build TypeBox
run: npm run build
- name: Install TypeScript Next
run: npm install typescript@next
- name: Build TypeBox
run: npm run build

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
target
dist

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"files.exclude": {
"node_modules": true,
"package-lock.json": true
},
"editor.suggest.showStatusBar": false,
"typescript.tsdk": "node_modules\\typescript\\lib"
}

15
changelog/0.17.1.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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.IsString(T)) {
const { type } = T // safe: type is 'string'
}
```

6
changelog/0.24.8.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
```

38
changelog/0.26.2.md Normal file
View File

@@ -0,0 +1,38 @@
## [0.26.2](https://www.npmjs.com/package/@sinclair/typebox/v/0.26.2)
Updates:
- [331](https://github.com/sinclairzx81/typebox/pull/349) Revert 0.25.0 Intersect logic for Composite
Notes:
This PR reverts the logic on Type.Composite back to 0.25.0 Type.Intersect due to excessive type instantiation issues. On 0.26.0, Type.Composite attempted to take a union for overlapping properties, however due to the sophistication required to type map the unions for overlapping properties, this has resulted in type instantiation problems for some users upgrading to 0.26.0.
As such, 0.26.2 reverts back to the 0.25.0 interpretation, but applies type mappings more inline with TS's interpretation of an overlapping varying property types. In the examples below, the type `C` is the evaluated type for Type.Composite. Note that TS will not union for overlapping properties and instead evaluate `never`. The 0.26.2 implementation falls inline with this evaluation.
```typescript
{ // evaluation case 1: non-varying
type T = { a: number } & { a: number }
type C = {[K in keyof T]: T[K] } // type C = { a: number }
}
{ // evaluation case 2: varying
type T = { a: number } & { a: string }
type C = {[K in keyof T]: T[K] } // type C = { a: never }
}
{ // evaluation case 3: single optional
type T = { a?: number } & { a: number }
type C = {[K in keyof T]: T[K] } // type C = { a: number }
}
{ // evaluation case 4: all optional
type T = { a?: number } & { a?: number }
type C = {[K in keyof T]: T[K] } // type C = { a?: number | undefined }
}
```
Note: the Type.Composite is intended to be a temporary type which can be replaced with a more general `Type.Mapped` in future revisions of TypeBox. As the infrastructure to support mapped types does not exist, users can use Type.Composite to partially replicate mapped type evaluation for composited object types only.

152
changelog/0.27.0.md Normal file
View File

@@ -0,0 +1,152 @@
## [0.27.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.27.0)
## Overview
Revision 0.27.0 adds support for runtime [Template Literal Types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html). This revision does not include any functional breaking changes but does rename some public type aliases. As such this revision requires a minor semver increment.
## Contents
- Enhancements
- [Template Literal Types](#Template-Literal-Types)
- [TemplateLiteralParser](#TemplateLiteralParser)
- [WorkBench](#WorkBench)
- Breaking Changes
- [TSelf renamed to TThis](#TSelf-renamed-to-TThis)
<a href="Template-Literal-Types"></a>
## Template Literal Types
Revision 0.27.0 adds support for Template Literal types. These types operate as a form of computed `TUnion<TLiteral<string>>`. TypeBox encodes template literals using a subset of [ECMA 262](https://json-schema.org/understanding-json-schema/reference/regular_expressions.html) regular erxpressions which are applied to `pattern` properties of type `string`. This encoding enables JSON Schema validators to assert using existing regular expression checks.
### TypeScript
TypeScript defines Template Literals using back tick quoted strings which may include embedded union groups.
```typescript
type T = `option${'A'|'B'}` // type T = 'optionA' | 'optionB'
type R = Record<T, string> // type R = {
// optionA: string
// optionB: string
// }
```
### TypeBox
TypeBox defines Template Literals using the `TemplateLiteral` function. This function accepts a sequence of TLiteral, TString, TNumber, TInteger and TBigInt which describe a sequence concatenations. The embedded TUnion type defines an option group which can later be expanded into a set of `TLiteral<string>`. This expansion enables Template Literal types to also be used as Record keys.
```typescript
const T = Type.TemplateLiteral([ // const T = {
Type.Literal('option'), // pattern: '^option(A|B)$',
Type.Union([ // type: 'string'
Type.Literal('A'), // }
Type.Literal('B')
])
])
const R = Type.Record(T, Type.String()) // const R = {
// type: 'object',
// required: ['optionA', 'optionB'],
// properties: {
// optionA: {
// type: 'string'
// },
// optionB: {
// type: 'string'
// }
// }
// }
type T = Static<typeof T> // type T = 'optionA' | 'optionB'
type R = Static<typeof R> // type R = {
// optionA: string
// optionB: string
// }
```
## TemplateLiteralParser
Template Literal types are encoded as `string` patterns. Because these types also need to act as composable union types, Revision 0.27.0 includes an expression parser / generator system specifically for regular expressions. This system is used during composition to allow templates to compose with other types, but can also be used in isolation to generate string sequences for the supported expression grammar. This functionality may be provided as standard on the `Value.*` sub module in subsequent revisions.
The following generates a 8-bit binary sequence for the given expression.
```typescript
import { TemplateLiteralParser, TemplateLiteralGenerator, TemplateLiteralFinite } from '@sinclair/typebox'
const Bit = `(0|1)` // bit union
const Byte = `${Bit}${Bit}${Bit}${Bit}${Bit}${Bit}${Bit}${Bit}` // byte sequence
const E = TemplateLiteralParser.Parse(Byte) // parsed expression tree
const F = TemplateLiteralFinite.Check(E) // is the expression finite?
const S = [...TemplateLiteralGenerator.Generate(E)] // generate sequence
// const S = [ // computed sequence
// '00000000', '00000001', '00000010', '00000011', '00000100',
// '00000101', '00000110', '00000111', '00001000', '00001001',
// '00001010', '00001011', '00001100', '00001101', '00001110',
// '00001111', '00010000', '00010001', '00010010', '00010011',
// '00010100', '00010101', '00010110', '00010111', '00011000',
// '00011001', '00011010', '00011011', '00011100', '00011101',
// '00011110', '00011111', '00100000', '00100001', '00100010',
// '00100011', '00100100', '00100101', '00100110', '00100111',
// '00101000', '00101001', '00101010', '00101011', '00101100',
// '00101101', '00101110', '00101111', '00110000', '00110001',
// '00110010', '00110011', '00110100', '00110101', '00110110',
// '00110111', '00111000', '00111001', '00111010', '00111011',
// '00111100', '00111101', '00111110', '00111111', '01000000',
// '01000001', '01000010', '01000011', '01000100', '01000101',
// '01000110', '01000111', '01001000', '01001001', '01001010',
// '01001011', '01001100', '01001101', '01001110', '01001111',
// '01010000', '01010001', '01010010', '01010011', '01010100',
// '01010101', '01010110', '01010111', '01011000', '01011001',
// '01011010', '01011011', '01011100', '01011101', '01011110',
// '01011111', '01100000', '01100001', '01100010', '01100011',
// ... 156 more items
// ]
```
<a href="Workbench"></a>
## Workbench
To assist with TypeScript alignment and to prototype new features. A new web based compiler tool has been written that allows interactive cross compiling between TypeScript and TypeBox. This tool will be enhanced seperately from the TypeBox project, but can be used to quickly generate TypeBox type definitions from existing TypeScript types.
[TypeBox Workbench Application](https://sinclairzx81.github.io/typebox-workbench)
[TypeBox Workbench Project](https://github.com/sinclairzx81/typebox-workbench)
<a href="https://sinclairzx81.github.io/typebox-workbench/"><img src="https://github.com/sinclairzx81/typebox-workbench/raw/main/typebox.png" /></a>
## Breaking Changes
The following are breaking changes in Revision 0.27.0
<a href="TSelf-renamed-to-TThis"></a>
## TSelf renamed to TThis
This rename is to align with TypeScript interfaces. Unlike `type` aliases, TypeScript `interface` types include a implicit `this` type. This change relates specifically to TypeBox's current Recursive type which passes the `TThis` parameter via callback. The `TThis` parameter can be seen as analogous to the implicit TypeScript interface `this`.
Consider the following.
```typescript
// type T = { id: string, nodes: this[] } // error: no implicit this
interface Node { // ok: this is implicit for interfaces
id: string,
nodes: this[]
}
const T = Type.Recursive(This => // `This` === implicit 'this' for interface
Type.Object({ //
id: Type.String(), // Should `Recursive` be renamed to `Interface`?
nodes: Type.Array(This)
})
)
```
Future revisions may rename `Recurisve` to `Interface`, but for now, just the `TSelf` has been renamed.

5
changelog/0.27.1.md Normal file
View File

@@ -0,0 +1,5 @@
## [0.27.1](https://www.npmjs.com/package/@sinclair/typebox/v/0.27.1)
## Updates
- Adds a `Value.Mutate(left, right)` function. This function performs a deep mutable assignment on a value by internally remapping the `right` values on the `left`. Values omitted on the right will also be deleted on the left. This function can be useful scenarios where mutation of data is required without replacing existing reference values. An example of which might be React which tracks reference values to indicate redraw. This function is implemented by way of `ValuePointer`.

199
changelog/0.28.0.md Normal file
View File

@@ -0,0 +1,199 @@
## [0.28.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.28.0)
## Overview
Revision 0.28.0 adds support for Indexed Access Types. This update also includes moderate breaking changes to Record and Composite types and does require a minor semver revision tick.
## Contents
- Enhancements
- [Indexed Access Types](#Indexed-Access-Types)
- [KeyOf Tuple and Array](#KeyOf-Tuple-and-Array)
- Breaking Changes
- [Record Types Allow Additional Properties By Default](#Record-Types-Allow-Additional-Properties-By-Default)
- [Composite Returns Intersect for Overlapping Properties](#Composite-Returns-Intersect-for-Overlapping-Properties)
<a href="Indexed-Access-Types"></a>
## Indexed Access Types
Revision 0.28.0 adds [Indexed Access Type](https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html) support with a new `Type.Index()` mapping type. These types allow for deep property lookups without needing to prop dive through JSON Schema properties. This type is based on the TypeScript implementation of Indexed Access Types and allows for generalized selection of properties for complex types irrespective of if that type is a Object, Union, Intersection, Array or Tuple.
```typescript
// ----------------------------------------------------------
// The following types A and B are structurally equivalent,
// but have varying JSON Schema representations.
// ----------------------------------------------------------
const A = Type.Object({
x: Type.Number(),
y: Type.String(),
z: Type.Boolean(),
})
const B = Type.Intersect([
Type.Object({ x: Type.Number() }),
Type.Object({ y: Type.String() }),
Type.Object({ z: Type.Boolean() })
])
// ----------------------------------------------------------
// TypeBox 0.27.0 - Non Uniform
// ----------------------------------------------------------
const A_X = A.properties.x // TNumber
const A_Y = A.properties.y // TString
const A_Z = A.properties.z // TBoolean
const B_X = B.allOf[0].properties.x // TNumber
const B_Y = B.allOf[1].properties.y // TString
const B_Z = B.allOf[2].properties.z // TBoolean
// ----------------------------------------------------------
// TypeBox 0.28.0 - Uniform via Type.Index
// ----------------------------------------------------------
const A_X = Type.Index(A, ['x']) // TNumber
const A_Y = Type.Index(A, ['y']) // TString
const A_Z = Type.Index(A, ['z']) // TBoolean
const B_X = Type.Index(B, ['x']) // TNumber
const B_Y = Type.Index(B, ['y']) // TString
const B_Z = Type.Index(B, ['z']) // TBoolean
```
Indexed Access Types support has also been extended to Tuple and Array types.
```typescript
// -----------------------------------------------------------
// Array
// -----------------------------------------------------------
type T = string[]
type I = T[number] // type T = string
const T = Type.Array(Type.String())
const I = Type.Index(T, Type.Number()) // const I = TString
// -----------------------------------------------------------
// Tuple
// -----------------------------------------------------------
type T = ['A', 'B', 'C']
type I = T[0 | 1] // type I = 'A' | 'B'
const T = Type.Array(Type.String())
const I = Type.Index(T, Type.Union([ // const I = TUnion<[
Type.Literal(0), // TLiteral<'A'>,
Type.Literal(1), // TLiteral<'B'>
])) // ]>
```
<a href="KeyOf-Tuple-and-Array"></a>
## KeyOf Tuple and Array
Revision 0.28.0 includes additional `Type.KeyOf` support for Array and Tuple types. Keys of Array will always return `TNumber`, whereas keys of Tuple will return a LiteralUnion for each index of that tuple.
```typescript
// -----------------------------------------------------------
// KeyOf: Tuple
// -----------------------------------------------------------
const T = Type.Tuple([Type.Number(), Type.Number(), Type.Number()])
const K = Type.KeyOf(T) // const K = TUnion<[
// TLiteral<'0'>,
// TLiteral<'1'>,
// TLiteral<'2'>,
// ]>
// -----------------------------------------------------------
// KeyOf: Array
// -----------------------------------------------------------
const T = Type.Array(Type.String())
const K = Type.KeyOf(T) // const K = TNumber
```
It is possible to combine KeyOf with Index types to extract properties from array and object constructs.
```typescript
// -----------------------------------------------------------
// KeyOf + Index: Object
// -----------------------------------------------------------
const T = Type.Object({ x: Type.Number(), y: Type.String(), z: Type.Boolean() })
const K = Type.Index(T, Type.KeyOf(T)) // const K = TUnion<[
// TNumber,
// TString,
// TBoolean,
// ]>
// -----------------------------------------------------------
// KeyOf + Index: Tuple
// -----------------------------------------------------------
const T = Type.Tuple([Type.Number(), Type.String(), Type.Boolean()])
const K = Type.Index(T, Type.KeyOf(T)) // const K = TUnion<[
// TNumber,
// TString,
// TBoolean,
// ]>
```
## Breaking Changes
The following are breaking changes in Revision 0.28.0
<a href="Record-Types-Allow-Additional-Properties-By-Default"></a>
## Record Types Allow Additional Properties By Default
Revision 0.28.0 no longer applies an automatic `additionalProperties: false` constraint to types of `TRecord`. Previously this constraint was set to prevent records with numeric keys from allowing unevaluated additional properties with non-numeric keys. This constraint worked in revisions up to 0.26.0, but since the move to use `allOf` intersect schema representations, this meant that types of Record could no longer be composed with intersections. This is due to the JSON Schema rules around extending closed schemas. Information on these rules can be found at the link below.
https://json-schema.org/understanding-json-schema/reference/object.html#extending-closed-schemas
For the most part, the omission of this constraint shouldn't impact existing record types with string keys, however numeric keys may cause problems. Consider the following where the validation unexpectedly succeeds for the following numeric keyed record.
```typescript
const T = Type.Record(Type.Number(), Type.String())
const R = Value.Check(T, { a: null }) // true - Because `a` is non-numeric and thus is treated as an
// additional unevaluated property.
```
Moving forward, Records with numeric keys "should" be constrained explicitly with `additionalProperties: false` via options if that record does not require composition through intersection. This is largely inline with the existing constraints one might apply to types of Object.
```typescript
const T = Type.Record(Type.Number(), Type.String(), {
additionalProperties: false
})
const R = Value.Check(T, { a: null }) // false - Because `a` is non-numeric additional property
```
<a href="Composite-Returns-Intersect-for-Overlapping-Properties"></a>
## Composite Returns Intersect for Overlapping Properties
This is a minor breaking change with respect to the schema returned for Composite objects with overlapping varying property types. Previously TypeBox would evaluate `TNever` by performing an internal `extends` check against each overlapping property type. However problems emerged using this implementation for users who needed to use Composite with types of `TUnsafe`. This is due to unsafe types being incompatible with TypeBox's internal extends logic.
The solution implemented in 0.28.0 is to return the full intersection of all overlapping properties. The reasoning here is that if the overlapping properties of varying types result in an illogical intersection, this is semantically the same as resolving `never` for that property. This approach avoids the need to internally check if all overlapping properties extend or narrow one another.
```typescript
const T = Type.Composite([
Type.Object({ x: Type.Number() }), // overlapping property 'x' of varying type
Type.Object({ x: Type.String() })
])
// -----------------------------------------------------------
// Revision 0.27.0
// -----------------------------------------------------------
const R = Type.Object({
x: Type.Never() // Never evaluated through extends checks.
})
// -----------------------------------------------------------
// Revision 0.28.0
// -----------------------------------------------------------
const R = Type.Object({
x: Type.Intersect([ // Illogical intersections are semantically the same as never
Type.Number(),
Type.String()
])
})
```
This implementation should make it more clear what the internal mechanics are for object compositing. Future revisions of TypeBox may however provide a utility function to test illogical intersections for Never for known types.

57
changelog/0.28.3.md Normal file
View File

@@ -0,0 +1,57 @@
## [0.28.3](https://www.npmjs.com/package/@sinclair/typebox/v/0.28.3)
## Overview
Revision 0.28.3 adds a new Rest type to support variadic type composition.
## Contents
- Enhancements
- [Variadic Types](#Variadic-Types)
<a href="Variadic-Types"></a>
## Variadic Types
Revision 0.28.3 adds a new type named `Type.Rest`. This type is used to extract a tuple array of type of `[...TSchema]`. The return value of this type is not strictly JSON Schema, however the tuple array can be used as a parameter to other types that accept tuples as their arguments.
### Tuple Concatenation
```typescript
// TypeScript
type A = [1, 2]
type B = [3, 4]
type C = [...A, ...B]
// TypeBox
const A = Type.Tuple([Type.Literal(1), Type.Literal(2)])
const B = Type.Tuple([Type.Literal(3), Type.Literal(4)])
const C = Type.Tuple([...Type.Rest(A), ...Type.Rest(B)])
```
### Tuple To Parameter
```typescript
// TypeScript
type P = [number, number]
type F1 = (param: [...P]) => void
type F2 = (param: [...P, number]) => void
// TypeBox
const P = Type.Tuple([Type.Number(), Type.Number()])
const F1 = Type.Function(Type.Rest(P), Type.Void())
const F2 = Type.Function([...Type.Rest(P), Type.Number()], Type.Void())
```

144
changelog/0.29.0.md Normal file
View File

@@ -0,0 +1,144 @@
## [0.29.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.29.0)
## Overview
Revision 0.29.0 makes a minor interface and schema representation change to the `Type.Not` type. This revision also includes a fix for indexed access types on TypeScript 5.1.6.
As this revision constitutes a breaking representation change for `Type.Not`, a minor semver revision is required.
## Contents
- Enhancements
- [Type.Not Representation Change](#Representation-Change)
- [Not Inversion](#Not-Inversion)
- [Inference Limitations](#Inference-Limitations)
<a name="Representation-Change"></a>
## Type.Not Representation Change
The `Type.Not` was first introduced in Revision 0.26.0. This type accepted two arguments, the first is the `not` type, the second is the `allowed` type. In 0.26.0, TypeBox would treat the `allowed` type as the inferred type with the schema represented in the following form.
### 0.26.0
```typescript
// allow all numbers except the number 42
//
const T = Type.Not(Type.Literal(42), Type.Number())
// ^ ^
// not type allowed type
// represented as
//
const T = {
allOf: [
{ not: { const: 42 } },
{ type: 'number' }
]
}
// inferred as
//
type T = Static<typeof T> // type T = number
```
In 0.26.0. the rationale for the second `allowed` argument was provide a correct static type to infer, where one could describe what the type wasn't on the first and what it was on the second (with inference of operating on the second argument). This approach was to echo possible suggestions for negated type syntax in TypeScript.
```typescript
type T = number & not 42 // not actual typescript syntax!
```
### 0.29.0
Revision 0.29.0 changes the `Type.Not` type to take a single `not` argument only. This type statically infers as `unknown`
```typescript
// allow all types except the literal number 42
//
const T = Type.Not(Type.Literal(42))
// ^
// not type
// represented as
//
const T = { not: { const: 42 } }
// inferred as
//
type T = Static<typeof T> // type T = unknown
```
### Upgrading to 0.29.0
In revision 0.29.0, you can express the 0.26.0 Not type via `Type.Intersect` which explicitly creates the `allOf` representation. The type inference works in this case as intersected `number & unknown` yields the most narrowed type (which is `number`)
```typescript
// allow all numbers except the number 42
//
const T = Type.Intersect([ Type.Not(Type.Literal(42)), Type.Number() ])
// ^ ^
// not type allowed type
// represented as
//
const T = {
allOf: [
{ not: { const: 42 } },
{ type: 'number' }
]
}
// inferred as
//
type T = Static<typeof T> // type T = number
```
The 0.29.0 `Not` type properly represents the JSON Schema `not` keyword in its simplest form, as well as making better use of intersection type narrowing capabilities of TypeScript.
<a name="Not-Inversion"></a>
## Not Inversion
The not type can be inverted through nesting.
```typescript
// not not string
//
const T = Type.Not(Type.Not(Type.String()))
// represented as
//
const T = {
not: {
not: {
type: "string"
}
}
}
// inferred as
//
type T = Static<typeof T> // type T = string
```
<a name="Inference-Limitations"></a>
## Inference Limitations
Not types are synonymous with the concept of [negated types](https://github.com/microsoft/TypeScript/issues/4196) which are not supported in the TypeScript language. Because of this, it is not currently possible to infer negated types in a way one would naturally expect for some cases. Consider the following.
```typescript
const T = Type.Intersect([Type.String(), Type.Not(Type.String())])
type T = Static<typeof T> // type T = string & not string
// actual: string
// expect: never
```
As such, the use of Not types should be used with some consideration to current limitations, and reserved primarily for narrowing cases such as the following.
```typescript
const T = Type.Intersect([Type.String(), Type.Not(Type.Literal('disallowed string'))])
type T = Static<typeof T> // type T = string & not 'disallowed string'
// actual: string
// expect: string
```

141
changelog/0.29.2.md Normal file
View File

@@ -0,0 +1,141 @@
## [0.29.2](https://www.npmjs.com/package/@sinclair/typebox/v/0.29.2)
## Overview
Revision 0.29.2 includes enhancements to `Type.Index`
This revision contains no breaking changes
## Contents
- Enhancements
- [Modifier Index Resolver](#Modifier-Index-Resolver)
- [Indexed Intersect](#Indexed-Intersect)
- [Indexed Union](#Indexed-Union)
- [Composite](#Composite)
<a name="Modifier-Index-Resolver"></a>
## Modifier Index Resolver
Revision 0.29.2 re-introduces optional property narrowing for Indexed Access Types. This functionality is specific when indexing overlapping properties with one or more optional modifiers. Revision 0.28.0 attempted to implement this narrowing, however was pulled due to instantiation issues raised on issue [419](https://github.com/sinclairzx81/typebox/issues/419) (specific to composite narrowing)
Revision 0.29.2 attempts to re-introduce this functionality using a different resolution strategy. It uses Indexed Access Types as the construct in which to apply such narrowing and expands upon Union and Intersection type normalization for optional modifier unwrap and remapping. This approach unifies Composite inference with Index Access Types and makes provisions for a possible `Type.Mapped` feature in later releases.
<a name="Indexed-Intersect"></a>
## Indexed Intersect
The following are the index resolver cases for intersect types.
### Case 1
```typescript
const T = Type.Intersect([
Type.Object({ x: Type.Optional(Type.Number()) }),
Type.Object({ x: Type.Optional(Type.Number()) })
])
const I = Type.Index(T, ['x'])
// type I = TOptional<TUnion<[TNumber, TNumber]>>
```
### Case 2
```typescript
const T = Type.Intersect([
Type.Object({ x: Type.Optional(Type.Number()) }),
Type.Object({ x: Type.Number() })
])
const I = Type.Index(T, ['x'])
// type I = TUnion<[TNumber, TNumber]>
```
### Case 3
```typescript
const T = Type.Intersect([
Type.Object({ x: Type.Number() }),
Type.Object({ x: Type.Number() })
])
const I = Type.Index(T, ['x'])
// type I = TUnion<[TNumber, TNumber]>
```
<a name="Indexed-Union"></a>
## Indexed Union
The following are the index resolver cases for union types.
### Case 1
```typescript
const T = Type.Union([
Type.Object({ x: Type.Optional(Type.Number()) }),
Type.Object({ x: Type.Optional(Type.Number()) })
])
const I = Type.Index(T, ['x'])
// type I = TOptional<TUnion<[TNumber, TNumber]>>
```
### Case 2
```typescript
const T = Type.Union([
Type.Object({ x: Type.Optional(Type.Number()) }),
Type.Object({ x: Type.Number() })
])
const I = Type.Index(T, ['x'])
// type I = TOptional<TUnion<[TNumber, TNumber]>>
```
### Case 3
```typescript
const T = Type.Union([
Type.Object({ x: Type.Number() }),
Type.Object({ x: Type.Number() })
])
const I = Type.Index(T, ['x'])
// type I = TUnion<[TNumber, TNumber]>
```
<a name="Composite"></a>
## Composite
The following are the resolver cases for indexed types when applied to composite intersection.
### Case 1
```typescript
const T = Type.Composite([
Type.Object({ x: Type.Optional(Type.Number()) }),
Type.Object({ x: Type.Optional(Type.Number()) })
])
// type T = TObject<{
// x: TOptional<TUnion<[TNumber, TNumber]>>
// }>
```
### Case 2
```typescript
const T = Type.Composite([
Type.Object({ x: Type.Optional(Type.Number()) }),
Type.Object({ x: Type.Number() })
])
// type T = TObject<{
// x: TUnion<[TNumber, TNumber]>
// }>
```
### Case 3
```typescript
const T = Type.Composite([
Type.Object({ x: Type.Number() }),
Type.Object({ x: Type.Number() })
])
// type T = TObject<{
// x: TUnion<[TNumber, TNumber]>
// }>
```

419
changelog/0.30.0.md Normal file
View File

@@ -0,0 +1,419 @@
## [0.30.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.30.0)
## Overview
Revision 0.30.0 is a milestone revision for the TypeBox project. It is primarily focused on internal optimizations, refactoring work to reduce package and bundle sizes, enable increased modularity of internal sub modules (with some considerations given to future ESM publishing), renaming internal functions to address react native bundling issues and consolidating shared internal modules to reduce overall code overhead.
This revision also implements several new features, including new validation constraints for Array, new types for iterators, new utility types, a TypeScript code generation option for the compiler, enhancements made to modifiers and better options for TypeScript to TypeBox code translation. This revision also includes new examples including a transform type for handling IO encode an decode as well as a reference implementation for JSON Type Definition specification.
This revision includes breaking changes and some deprecations. It requires a minor semver revision.
## Contents
- Enhancements
- [TypeScript Code Generation](#TypeScript-Code-Generation)
- [Optional and Readonly](#Optional-and-Readonly)
- [Iterator and AsyncIterator](#Iterator-and-AsyncIterator)
- [Order Independent References](#Order-Independent-References)
- [Value Submodules](#Value-Submodules)
- [Array Contains Constraint](#Array-Contains-Constraint)
- [Additional Utility Types](#Additional-Utility-Types)
- [Reduced Package Size](#Reduced-Package-Size)
- [TypeBox Codegen](#TypeBox-Codegen)
- Examples
- [JSON Type Definition](#JSON-Type-Definition)
- [Prototype Types](#Prototype-Types)
- [Transform Types](#Transform-Types)
- Breaking
- [Extended Type Representation-Change](#Extended-Type-Representation-Change)
- [Modifier Symbol Deprecated](#Modifier-Deprecated)
- [RegEx Renamed To RegExp](#RegEx-Renamed-To-RegExp)
- [ValueErrorType Custom Renamed To Kind](#ValueErrorType-Custom-Renamed-To-Kind)
<a name="TypeScript Code Generation"></a>
## TypeScript Code Generation
Revision 0.30.0 adds TypeScript code generation support to the TypeCompiler. By specifying the language option on the `.Code()` function, TypeBox will add type annotations to the compiled output. This functionality can be used to produce typed TS functions for projects that preference AOT compilation.
```typescript
const Code = TypeCompiler.Code(Type.String(), { // return function check(value: any): boolean {
language: 'typescript' // return (
}) // (typeof value === 'string')
// )
// }
```
<a name="Optional-and-Readonly"></a>
## Optional and Readonly
Revision 0.30.0 deprecates the `[Modifier]` symbol and introduces two new symbols, `[Readonly]` and `[Optional]`. This change is carried out to simplify type inference as well as to simplify runtime mapping logic. This change should not implicate users leveraging the `Type.*` purely for type composition, however implementors using TypeBox for reflection and code generation should update to the new symbols.
```typescript
// Revision 0.29.0
//
const A = Type.ReadonlyOptional(Type.Number()) // const A: TReadonlyOptional<TNumber> = {
// type: 'number',
// [TypeBox.Modifier]: 'ReadonlyOptional'
// }
const B = Type.Readonly(Type.Number()) // const B: TReadonly<TNumber> = {
// type: 'number',
// [TypeBox.Modifier]: 'Readonly'
// }
const C = Type.Optional(Type.Number()) // const C: TOptional<TNumber> = {
// type: 'number',
// [TypeBox.Modifier]: 'Optional'
// }
// Revision 0.30.0
//
const A = Type.ReadonlyOptional(Type.Number()) // const A: TReadonly<TOptional<TNumber>> = {
// type: 'number',
// [TypeBox.Readonly]: 'Readonly',
// [TypeBox.Optional]: 'Optional'
// }
const B = Type.Readonly(Type.Number()) // const B: TReadonly<TNumber> = {
// type: 'number',
// [TypeBox.Readonly]: 'Readonly'
// }
const C = Type.Optional(Type.Number()) // const C: TOptional<TNumber> = {
// type: 'number',
// [TypeBox.Optional]: 'Optional'
// }
```
<a name="Iterator-and-AsyncIterator"></a>
## Iterator and AsyncIterator
Revision 0.30.0 adds the types `Iterator` and `AsyncIterator`. These types add to the existing non-validatable extended type set and can be used build callable generator functions. These types are written primarily to describe RPC network interfaces that return multiple values. Examples of which may include web socket streams or reading database result cursors over a network.
```typescript
// Revision 0.30.0
//
const Enumerable = <T extends TSchema>(T: T) => Type.Function([
Type.Number({ description: 'Start index' }),
Type.Number({ description: 'End index' })
], Type.Iterator(T))
const EnumerableNumber = Enumerable(Type.Number())
const Range: Static<typeof EnumerableNumber> = function * (start: number, end: number) {
for(let i = start; i < end; i++) yield i
}
const R = [...Range(10, 20)] // const R = [10, 11, 12, ..., 19]
```
<a name="Order-Independent-References"></a>
## Order Independent References
Revision 0.30.0 adds an overload for `Ref` to enable non order dependent type referencing. Prior to this revision, reference targets needed to be defined first before being referenced. Revision 0.30.0 lifts this restriction and allows referencing of "yet to be defined" targets through the use of `typeof` operator. This overload borrows on TypeScript's ability to derive type information irrespective of topological ordering.
This overload is implemented for "TypeScript to TypeBox" code generation utilities where TypeScript types are not guaranteed ordered in a runtime sorted fashion.
```typescript
// Revision 0.29.0
//
const R = Type.Ref(T) // Error: T isn't defined yet
const T = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number()
}, { $id: 'T' })
// Revision 0.30.0
//
const R = Type.Ref<typeof T>('T') // Ok: infer from typeof T
const T = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number()
}, { $id: 'T' })
```
<a name="Value-Submodules"></a>
## Value Submodules
Revision 0.30.0 carries out a number of refactorings for the `Value.*` modules to enable each submodule to be imported individually. These refactorings are support better "pay to play" library characteristics, allowing users to import only the submodules they need. This update also makes provisions for ESM publishing by removing internal namespaces.
The top level `Value.*` namespace will remain on all subsequent versions of TypeBox.
```typescript
// Revision 0.29.0
//
import { Value } from '@sinclair/typebox/value' // Value.* namespace
const A = Value.Create(Type.String())
// Revision 0.30.0
//
import { Create } from '@sinclair/typebox/value/create' // Only Create()
const A = Create(Type.String())
```
<a name="Array-Contains-Constraint"></a>
## Array Contains Constraint
Revision 0.30.0 implements validation support for the `contains` keyword as well as the draft 2019-09 `minContains` and `maxContains` constraints on `Array`. Documentation on these constraints can be found https://json-schema.org/understanding-json-schema/reference/array.html#contains
```typescript
// Revision 0.30.0
//
const T = Type.Array(Type.Number(), {
contains: Type.Literal(1),
minContains: 3,
maxContains: 5
})
Value.Check(T, [1, 1, 1]) // true - between 3 and 5 instances of 1
Value.Check(T, [1, 1, 1, 1, 1]) // true - between 3 and 5 instances of 1
Value.Check(T, [0, 1, 1, 1, 1, 1]) // true - between 3 and 5 instances of 1
Value.Check(T, [1, 1]) // false - less than 3 instances of 1
Value.Check(T, [1, 1, 1, 1, 1, 1]) // false - more than 5 instances of 1
Value.Check(T, [0]) // false - no instances of 1
```
<a name="Additional-Utility-Types"></a>
## Additional Utility Types
Revision 0.30.0 adds the utility types `Awaited`, `Uppercase`, `Lowercase`, `Capitalize`, and `Uncapitalize` to the supported type set.
```typescript
// Revision 0.30.0
const T1 = Type.Awaited(Type.Promise(Type.String())) // const T1: TString
const T2 = Type.Uppercase(Type.Literal('hello')) // const T2: TLiteral<'HELLO'>
const T3 = Type.Lowercase(Type.Literal('HELLO')) // const T3: TLiteral<'hello'>
const T4 = Type.Capitalize(Type.Literal('hello')) // const T4: TLiteral<'Hello'>
const T5 = Type.Uncapitalize(Type.Literal('HELLO')) // const T5: TLiteral<'hELLO'>
```
A full list of TypeScript utility types can be found at this [link](https://www.typescriptlang.org/docs/handbook/utility-types.html).
<a name="Reduced-Package-Size"></a>
## Reduced Package Size
Revision 0.30.0 carries out several internal refactorings to reduce package and bundle sizes. This work is largely an ongoing process with provisional work carried out across `type`, `value` and `compiler` modules. Revision 0.30.0 manages to weigh in slightly less than Revision 0.29.0 with the additional functionality provided on the revision.
```typescript
// Revision 0.29.0
//
(index) Compiled Minified Compression
typebox/compiler '130.3 kb' ' 58.2 kb' '2.24 x'
typebox/errors '113.3 kb' ' 49.8 kb' '2.27 x'
typebox/system ' 78.8 kb' ' 32.2 kb' '2.45 x'
typebox/value '180.0 kb' ' 77.7 kb' '2.32 x'
typebox ' 77.7 kb' ' 31.7 kb' '2.45 x'
// Revision 0.30.0
//
(index) Compiled Minified Compression
typebox/compiler '129.4 kb' ' 58.6 kb' '2.21 x'
typebox/errors '111.6 kb' ' 50.1 kb' '2.23 x'
typebox/system ' 76.5 kb' ' 31.7 kb' '2.41 x'
typebox/value '180.7 kb' ' 79.3 kb' '2.28 x'
typebox ' 75.4 kb' ' 31.3 kb' '2.41 x'
```
<a name="TypeBox-Codegen"></a>
## TypeBox Codegen
Revision 0.30.0 offers an external code generation API tool which can be used to programmatically convert TypeScript types into TypeBox types.
[TypeBox-Code Project](https://github.com/sinclairzx81/typebox-codegen)
```typescript
import * as Codegen from '@sinclair/typebox-codegen'
const Code = Codegen.TypeScriptToTypeBox.Generate(`
type T = { x: number, y: number, z: number }
`)
console.log(Code)
// Output:
//
// import { Type, Static } from '@sinclair/typebox'
//
// type T = Static<typeof T>
// const T = Type.Object({
// x: Type.Number(),
// y: Type.Number(),
// z: Type.Number()
// })
```
<a name="JSON-Type-Definition"></a>
## JSON Type Definition
Revision 0.30.0 includes a reference implementation for JSON Type Definition (RFC 8927). This specification is currently under consideration for inclusion in the TypeBox library as an alternative schema representation for nominal type systems. The implementation currently contains all types expressed in the JSON Type Definition spec, but omits constraints such and `minimum` and `maximum` values (which are not formally represented in the specification).
The implementation is offered as a single file which can be copied in to projects with TypeBox installed. This implementation may be enhanced over the next few revisions (with some potential to implement mapping types such as partial, required, omit, pick, keyof). This specification will be considered for inclusion under `@sinclair/typebox/typedef` if there is enough interest.
```typescript
import { Type } from './typedef' // from: examples/typedef/typedef.ts
const T3 = Type.Struct({ // const T3 = {
x: Type.Float32(), // properties: {
y: Type.Float32(), // x: { type: 'float32' },
z: Type.Float32() // y: { type: 'float32' },
}) // z: { type: 'float32' }
// }
// }
const T2 = Type.Struct({ // const T3 = {
x: Type.Float32(), // properties: {
y: Type.Float32() // x: { type: 'float32' },
}) // y: { type: 'float32' }
// }
// }
const U = Type.Union([ // const U = {
T3, // discriminator: 'type',
T2 // mapping: {
]) // 0: {
// properties: {
// x: { type: 'float32' },
// y: { type: 'float32' },
// z: { type: 'float32' }
// }
// },
// 1: {
// properties: {
// x: { type: 'float32' },
// y: { type: 'float32' }
// }
// }
// }
// }
```
<a name="Prototype-Types"></a>
## Prototype Types
Revision 0.30.0 renames `Experimental` types to `Prototype` types within the examples directory. Updates here include additional documentation and rationales for the existing types `UnionOneOf`, `UnionEnum`, `Const`, and includes two new types `Evaluate` and `PartialDeep`. These types are written as standalone modules and can be copied into a project for direct use. The TypeBox project is open to community discussions around the inclusion of these types in future revisions.
<a name="Transform-Types"></a>
## Transform Types
Revision 0.30.0 provides a reference implementation for Transform types. There has been some interest from users to offer combinators similar to Zod's `.transform()` function that permits remapping values during `.parse()` like operations. As TypeBox types do not have fluent combinators or a parse function (and are just JSON Schema objects), introducing similar functionality without augmenting types or implementing a `.parse()` on all types has proven to be particularily challenging.
The reference Transform implementation implements a workable design by augmenting TypeBox types with codec functions outside the type system. These functions allow values to be structurally encoded and decoded through the `.parseLike()` functions `Encode()` and `Decode()`. TypeBox adopts the `io-ts` perspective for value transformation, viewing the act of transforming values primarily the role of dedicated codec system. As much of this functionality is considered high level and above and beyond the type system, Transform types will not likely be added to TypeBox type system; but rather added as an optional import in later revisions.
```typescript
import { Transform, Encode, Decode } from './transform'
const Timestamp = Transform(Type.Number(), { // The Transform function wraps a TypeBox type with two codec
decode: (value) => new Date(value), // functions which implement logic to decode a received value
encode: (value) => value.getTime(), // (i.e. number) into a application type (Date). The encode
}) // function handles the reverse mapping.
type N = Static<typeof N> // type N = { timestamp: number }
//
const N = Type.Object({ // Transform types are to be used like any other type and will
timestamp: Timestamp // infer as the original TypeBox type. For example, the type `N`
}) // above will infer as { timestamp: number } (as derived from
// the TB type)
const D = Decode(N, { timestamp: 123 }) // const D = { timestamp: Date(123) }
//
// The Decode function accepts any type plus a value. The Decode
// function return type will be that of the transforms decode()
// return type (which is Date), with the second argument statically
// typed as N. This function acts as a kind of parse() that returns
// the decoded type or throws on validation error.
const E = Encode(N, { timestamp: new Date(123) }) // const E = { timestamp: 123 }
//
// The encode function performs the inverse, accepting the
// decoded type { timestamp: Date } and re-encoding to the
// target type { timestamp: number }. This function will
// also throw on validation error.
```
<a name="Extended-Type-Representation-Change"></a>
## Extended Type Representation Change
Revision 0.30.0 updates representations for all extended types. This change is made due to TypeBox's observed role as a general purpose JavaScript validation library as well as to deprecate support for extended type validation in Ajv which was only ever partially functional at best.
Attempts were made on Revision 0.25.0 to restructure extended types to provide Ajv hooks for custom type configuration. These hooks used the `type` property where `{ type: 'object', instanceOf: 'Type' }` was used to configure schematics for JavaScript objects, and `{ type: 'null', typeOf: 'Type' }` was used for JavaScript primitives. Despite these hooks, Ajv would still struggle with validation of primitive types (such as `undefined`), and for the types `Function`, `Constructor` and `Promise`; these were meaningless to Ajv and it did not make sense to try provide hooks for a validator that could not make use of them.
This change represents a move towards a formal specification to express pure JavaScript constructs which is partially under discussion within the runtime type community. This change will implicate the use of `Uint8Array` and `Date` objects when configuring for Ajv. A supplimentary fallback will be provided in the `/examples` directory using `Type.Unsafe`
```typescript
// Revision 0.29.0
//
const T = Type.Date() // const T: TDate = { type: 'object', instanceOf: 'Date' }
const U = Type.Undefined() // const U: TUndefined = { type: 'null', typeOf: 'Undefined' }
// Revision 0.30.0
//
const T = Type.Date() // const T: TDate = { type: 'Date' }
const U = Type.Undefined() // const U: TUndefined = { type: 'undefined' }
```
<a name="RegEx-Renamed-To-RegExp"></a>
## RegEx Renamed To RegExp
Revision 0.30.0 marks `Type.RegEx` as deprecated but provides `Type.RegExp` as an alternative (matching the JavaScript `RegExp` type name). Additionally this type has also been moved from the `Standard` to `Extended` type set. The `RegExp` type will no longer considered part of the Standard type set due to JavaScript Regular Expressions supporting a wider range of symbols and control characeters than is supported by the ECMA262 subset used by the JSON Schema specification. Information on the ECMA262 subset supported by JSON Schema can be found at the following Url https://json-schema.org/understanding-json-schema/reference/regular_expressions.html
As `Type.RegEx()` is widely used, this function will be retained under the `@deprecated` annotation for the 0.30.0 revision.
```typescript
// Revision 0.29.0
const T = Type.RegEx(/abc/)
// Revision 0.30.0
const A = Type.RegEx(/abc/) // deprecation warning!
const B = Type.RegExp(/abc/) // Extended Type
const T = Type.String({ pattern: /abc/.source }) // Standard Type
```
For Unicode (UTF-16) support on 0.30.0, the recommendation is to continue using user defined formats.
```typescript
import { Type, FormatRegistry } from '@sinclair/typebox'
FormatRegistry.Set('emoji', value => /<a?:.+?:\d{18}>|\p{Extended_Pictographic}/gu.test(value))
const T = Type.String({ format: 'emoji' })
Value.Check(T, '♥️♦️♠️♣️') // Ok
```
For information on configuring custom formats on Ajv, refer to https://ajv.js.org/guide/formats.html#user-defined-formats

331
changelog/0.31.0.md Normal file
View File

@@ -0,0 +1,331 @@
## [0.31.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.31.0)
## Overview
Revision 0.31.0 is a subsequent milestone revision for the TypeBox library and a direct continuation of the work carried out in 0.30.0 to optimize and prepare TypeBox for a 1.0 release candidate. This revision implements a new codec system with Transform types, provides configurable error message generation for i18n support, adds a library wide exception type named TypeBoxError and generalizes the Rest type to enable richer composition. This revision also finalizes optimization work to reduce the TypeBox package size.
This revision contains relatively minor breaking changes due to internal type renaming. A minor semver revision is required.
## Contents
- Enhancements
- [Transform Types](#Transform-Types)
- [Encode and Decode](#Encode-and-Decode)
- [StaticEncode and StaticDecode](#StaticEncode-and-StaticDecode)
- [Rest Types](#Rest-Types)
- [Record Key](#Record-Key)
- [TypeBoxError](#TypeBoxError)
- [TypeSystemErrorFunction](#TypeSystemErrorFunction)
- [Reduce Package Size](#Reduce-Package-Size)
- Breaking
- [JsonTypeBuilder and JavaScriptTypeBuilder](#JsonTypeBuilder-and-JavaScriptTypeBuilder)
- [TypeSystemPolicy](#TypeSystemPolicy)
<a name="Transform-Types"></a>
## Transform Types
Revision 0.31.0 includes a new codec system referred to as Transform types. A Transform type is used to augment a regular TypeBox type with Encode and Decode functions. These functions are invoked via the new Encode and Decode functions available on both Value and TypeCompiler modules.
The following shows a Transform type which increments and decrements a number.
```typescript
import { Value } from '@sinclair/typebox/value'
const T = Type.Transform(Type.Number()) // const T = {
.Decode(value => value + 1) // type: 'number',
.Encode(value => value - 1) // [Symbol(TypeBox.Kind)]: 'Number',
// [Symbol(TypeBox.Transform)]: {
// Decode: [Function: Decode],
// Encode: [Function: Encode]
// }
// }
const A = Value.Decode(T, 0) // const A: number = 1
const B = Value.Encode(T, 1) // const B: number = 0
```
<a name="Encode-and-Decode"></a>
## Encode and Decode
Revision 0.31.0 includes new functions to Decode and Encode values. These functions are written in service to Transform types, but can be used equally well without them. These functions return a typed value that matches the type being transformed. TypeBox will infer decode and encode differently, yielding the correct type as derived from the codec implementation.
The following shows decoding and encoding between number to Date. Note these functions will throw if the value is invalid.
```typescript
const T = Type.Transform(Type.Number())
.Decode(value => new Date(value)) // number to Date
.Encode(value => value.getTime()) // Date to number
// Ok
//
const A = Value.Decode(T, 42) // const A = new Date(42)
const B = Value.Encode(T, new Date(42)) // const B = 42
// Error
//
const C = Value.Decode(T, true) // Error: Expected number
const D = Value.Encode(T, 'not a date') // Error: getTime is not a function
```
The Decode function is extremely fast when decoding regular TypeBox types; and TypeBox will by pass codec execution if the type being decoded contains no interior Transforms (and will only use Check). When using Transforms however, these functions may incur a performance penelty due to codecs operating structurally on values using dynamic techniques (as would be the case for applications manually decoding values). As such the Decode design is built to be general and opt in, but not necessarily high performance.
<a name="StaticEncode-and-StaticDecode"></a>
## StaticEncode and StaticDecode
Revision 0.31.0 includes new inference types `StaticEncode` and `StaticDecode`. These types can be used to infer the encoded and decoded states of a Transform as well as regular TypeBox types. These types can be used to replace `Static` for `Request` and `Response` inference pipelines.
The following shows an example `Route` function that uses Transform inference via `StaticDecode`.
```typescript
// Route
//
export type RouteCallback<TRequest extends TSchema, TResponse extends TSchema> =
(request: StaticDecode<TRequest>) => StaticDecode<TResponse> // replace Static with StaticDecode
export function Route<TPath extends string, TRequest extends TSchema, TResponse extends TSchema>(
path: TPath,
requestType: TRequest,
responseType: TResponse,
callback: RouteCallback<TRequest, TResponse>
) {
// route handling here ...
const input = null // receive input
const request = Value.Decode(requestType, input)
const response = callback(request)
const output = Value.Encode(responseType, response)
// send output
}
// Route: Without Transform
//
const Timestamp = Type.Number()
Route('/exampleA', Timestamp, Timestamp, (value) => {
return value // value observed as number
})
// Route: With Transform
//
const Timestamp = Type.Transform(Type.Number())
.Decode(value => new Date(value))
.Encode(value => value.getTime())
Route('/exampleB', Timestamp, Timestamp, (value) => {
return value // value observed as Date
})
```
<a name="Rest-Types"></a>
## Rest Types
Revision 0.31.0 updates the Rest type to support variadic tuple extraction from Union, Intersection and Tuple types. Previously the Rest type was limited to Tuple types only, but has been extended to other types to allow uniform remapping without having to extract types from specific schema representations.
The following remaps a Tuple into a Union.
```typescript
const T = Type.Tuple([ // const T = {
Type.String(), // type: 'array',
Type.Number() // items: [
]) // { type: 'string' },
// { type: 'number' }
// ],
// additionalItems: false,
// minItems: 2,
// maxItems: 2,
// }
const R = Type.Rest(T) // const R = [
// { type: 'string' },
// { type: 'number' }
// ]
const U = Type.Union(R) // const U = {
// anyOf: [
// { type: 'string' },
// { type: 'number' }
// ]
// }
```
This type can be used to remap Intersect a Composite
```typescript
const I = Type.Intersect([ // const I = {
Type.Object({ x: Type.Number() }), // allOf: [{
Type.Object({ y: Type.Number() }) // type: 'object',
]) // required: ['x'],
// properties: {
// x: { type: 'number' }
// }
// }, {
// type: 'object',
// required: ['y'],
// properties: {
// y: { type: 'number' }
// }
// }]
// }
const C = Type.Composite(Type.Rest(I)) // const C = {
// type: 'object',
// required: ['x', 'y'],
// properties: {
// 'x': { type: 'number' },
// 'y': { type: 'number' }
// }
// }
```
<a name="Record-Key"></a>
## Record Key
Revision 0.31.0 updates the inference strategy for Record types and generalizes RecordKey to TSchema. This update aims to help Record types compose better when used with generic functions. The update also removes the overloaded Record factory methods, opting for a full conditional inference path. It also removes the `RecordKey` type which would type error when used with Record overloads. The return type of Record will be TNever if passing an invalid key. Valid Record key types include TNumber, TString, TInteger, TTemplateLiteral, TLiteralString, TLiteralNumber and TUnion.
```typescript
// 0.30.0
//
import { RecordKey, TSchema } from '@sinclair/typebox'
function StrictRecord<K extends RecordKey, T extends TSchema>(K: K, T: T) {
return Type.Record(K, T, { additionalProperties: false }) // Error: RecordKey unresolvable to overload
}
// 0.31.0
//
import { TSchema } from '@sinclair/typebox'
function StrictRecord<K extends TSchema, T extends TSchema>(K: K, T: T) {
return Type.Record(K, T, { additionalProperties: false }) // Ok: dynamically mapped
}
const A = StrictRecord(Type.String(), Type.Null()) // const A: TRecord<TString, TNull>
const B = StrictRecord(Type.Literal('A'), Type.Null()) // const B: TObject<{ A: TNull }>
const C = StrictRecord(Type.BigInt(), Type.Null()) // const C: TNever
```
<a name="TypeBoxError"></a>
## TypeBoxError
Revision 0.31.0 updates all errors thrown by TypeBox to extend the sub type `TypeBoxError`. This can be used to help narrow down the source of errors in `try/catch` blocks.
```typescript
import { Type, TypeBoxError } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
try {
const A = Value.Decode(Type.Number(), 'hello')
} catch(error) {
if(error instanceof TypeBoxError) {
// typebox threw this error
}
}
```
<a name="TypeSystemErrorFunction"></a>
## TypeSystemErrorFunction
Revision 0.31.0 adds functionality to remap error messages with the TypeSystemErrorFunction. This function is invoked whenever a validation error is generated in TypeBox. The following is an example of a custom TypeSystemErrorFunction using some of the messages TypeBox generates by default. TypeBox also provides the DefaultErrorFunction which can be used for fallthrough cases.
```typescript
import { TypeSystemErrorFunction, DefaultErrorFunction } from '@sinclair/typebox/system'
// Example CustomErrorFunction
export function CustomErrorFunction(schema: Types.TSchema, errorType: ValueErrorType) {
switch (errorType) {
case ValueErrorType.ArrayContains:
return 'Expected array to contain at least one matching value'
case ValueErrorType.ArrayMaxContains:
return `Expected array to contain no more than ${schema.maxContains} matching values`
case ValueErrorType.ArrayMinContains:
return `Expected array to contain at least ${schema.minContains} matching values`
...
default: return DefaultErrorFunction(schema, errorType)
}
}
// Sets the CustomErrorFunction
TypeSystemErrorFunction.Set(CustomErrorFunction)
```
It is possible to call `.Set()` on the TypeSystemErrorFunction module prior to each call to `.Errors()`. This can be useful for applications that require i18n support in their validation pipelines.
<a name="Reduce Package Size"></a>
## Reduce Package Size
Revision 0.31.0 completes a full sweep of code optimizations and modularization to reduce package bundle size. The following table shows the bundle sizes inclusive of the new 0.31.0 functionality against 0.30.0.
```typescript
// Revision 0.30.0
//
(index) Compiled Minified Compression
typebox/compiler '131.4 kb' ' 59.4 kb' '2.21 x'
typebox/errors '113.6 kb' ' 50.9 kb' '2.23 x'
typebox/system ' 78.5 kb' ' 32.5 kb' '2.42 x'
typebox/value '182.8 kb' ' 80.0 kb' '2.28 x'
typebox ' 77.4 kb' ' 32.0 kb' '2.42 x'
// Revision 0.31.0
//
(index) Compiled Minified Compression
typebox/compiler '149.5 kb' ' 66.1 kb' '2.26 x'
typebox/errors '112.1 kb' ' 49.4 kb' '2.27 x'
typebox/system ' 83.2 kb' ' 37.1 kb' '2.24 x'
typebox/value '191.1 kb' ' 82.7 kb' '2.31 x'
typebox ' 73.0 kb' ' 31.9 kb' '2.29 x'
```
Additional code reductions may not be possible without implicating code maintainability. The `typebox` module may however be broken down into sub modules in later revisions to further bolster modularity, but is retained as a single file on this revision for historical reasons (not necessarily technical ones).
<a name="JsonTypeBuilder-and-JavaScriptTypeBuilder"></a>
## JsonTypeBuilder and JavaScriptTypeBuilder
Revision 0.31.0 renames the `StandardTypeBuilder` and `ExtendedTypeBuilder` to `JsonTypeBuilder` and `JavaScriptTypeBuilder` respectively. Applications that extend TypeBox's TypeBuilders will need to update to these names.
```typescript
// 0.30.0
//
export class ApplicationTypeBuilder extends ExtendedTypeBuilder {}
// 0.31.0
//
export class ApplicationTypeBuilder extends JavaScriptTypeBuilder {}
```
These builders also update the jsdoc comment to `[Json]` and `[JavaScript]` inline with this new naming convention.
<a name="TypeSystemPolicy"></a>
## TypeSystemPolicy
Revision 0.31.0 moves the `TypeSystem.Policy` configurations into a new type named `TypeSystemPolicy`. This change was done to unify internal policy checks used by the Value and Error modules during bundle size optimization; as well as to keep policy configurations contextually separate from the Type and Format API on the TypeSystem module.
```typescript
// Revision 0.30.0
//
import { TypeSystem } from '@sinclair/typebox/system'
TypeSystem.AllowNaN = true
// Revision 0.31.0
//
import { TypeSystemPolicy } from '@sinclair/typebox/system'
TypeSystemPolicy.AllowNaN = true
TypeSystemPolicy.IsNumberLike(NaN) // true
```

514
changelog/0.32.0.md Normal file
View File

@@ -0,0 +1,514 @@
### 0.32.0
---
### Revision Updates
- [Revision 0.32.35](https://github.com/sinclairzx81/typebox/pull/914) Support Any for Record keys, Revert error message on required property, Fix order dependency for Union Convert.
- [Revision 0.32.34](https://github.com/sinclairzx81/typebox/pull/914) Fix template literal generation for template literals embedded within template literals.
- [Revision 0.32.33](https://github.com/sinclairzx81/typebox/pull/905) Pin ESM compiler target to ES2020.
- [Revision 0.32.32](https://github.com/sinclairzx81/typebox/pull/898) Fix for Enum properties when used with Mapped types.
- [Revision 0.32.31](https://github.com/sinclairzx81/typebox/pull/881) Fix for Cast. Dereference Union variants before scoring.
- [Revision 0.32.30](https://github.com/sinclairzx81/typebox/pull/868) Support null object prototypes for Encode/Decode.
- [Revision 0.32.29](https://github.com/sinclairzx81/typebox/pull/862) Key derive optimization to improve Intersect Encode/Decode performance.
- [Revision 0.32.28](https://github.com/sinclairzx81/typebox/pull/861) Fix for TransformEncode introduced with 0.32.24, 0.32.25 optimizations.
- [Revision 0.32.27](https://github.com/sinclairzx81/typebox/pull/854) Support for esm.sh and general build tooling updates.
- [Revision 0.32.26](https://github.com/sinclairzx81/typebox/pull/851) Optimization for number checks, use Number.isFinite(x) over typeof `number`.
- [Revision 0.32.25](https://github.com/sinclairzx81/typebox/pull/849) Optimizations for type builder to improve schema creation performance for computed types.
- [Revision 0.32.24](https://github.com/sinclairzx81/typebox/pull/848) Optimizations for Convert to avoid unnecessary object initialization and cloning.
- [Revision 0.32.22](https://github.com/sinclairzx81/typebox/pull/840) Add Support for Optional and Readonly Function and Constructor Arguments.
- [Revision 0.32.21](https://github.com/sinclairzx81/typebox/pull/836) Refactor Array Conversion logic. Discard TNever on TComposite.
- [Revision 0.32.20](https://github.com/sinclairzx81/typebox/pull/810) Fix compiler regression (TS 5.3.3 -> 5.4.2) generating Diff declaration structures.
- [Revision 0.32.19](https://github.com/sinclairzx81/typebox/pull/805) Revert Union Convert logic added on 0.32.16.
- [Revision 0.32.18](https://github.com/sinclairzx81/typebox/pull/801) Add explicit return type on TypeSystem.Type.
- [Revision 0.32.17](https://github.com/sinclairzx81/typebox/pull/799) Detect ambiguous inference for StaticDecode when inferring as any.
- [Revision 0.32.16](https://github.com/sinclairzx81/typebox/pull/791) Enhance Composite, Mapped, Indexed and Transform types. Intersect and Union Convert updates, Include Path in Validation Error.
- [Revision 0.32.15](https://github.com/sinclairzx81/typebox/pull/774) Additional internal guards for Type Arrays, Map and Set structures.
- [Revision 0.32.14](https://github.com/sinclairzx81/typebox/pull/753) Use barrel exports for submodules.
- [Revision 0.32.13](https://github.com/sinclairzx81/typebox/pull/744) Add minLength and maxLength constraint for RegExp
- [Revision 0.32.12](https://github.com/sinclairzx81/typebox/pull/740) Fix option assignment on Record types.
- [Revision 0.32.11](https://github.com/sinclairzx81/typebox/pull/738) Optimize Extract, Exclude. Overloads for Template Literal
- [Revision 0.32.10](https://github.com/sinclairzx81/typebox/pull/734) Export additional type infrastructure for Partial and Required
- [Revision 0.32.9](https://github.com/sinclairzx81/typebox/pull/731) Generalize Composite to accept schematics of type TSchema[]
- [Revision 0.32.8](https://github.com/sinclairzx81/typebox/pull/728) Ensure schema `default` annotation is cloned on Create.
- [Revision 0.32.7](https://github.com/sinclairzx81/typebox/pull/727) Ensure schema `default` annotation is cloned on Default.
- [Revision 0.32.6](https://github.com/sinclairzx81/typebox/pull/724) Export additional type infrastructure for mapping types
- [Revision 0.32.5](https://github.com/sinclairzx81/typebox/pull/718) Update licence year span for 2024
- [Revision 0.32.4](https://github.com/sinclairzx81/typebox/pull/708) Ensure ErrorFunctionParameter type is exported
- [Revision 0.32.3](https://github.com/sinclairzx81/typebox/pull/703) Simplify Record Static Type
- [Revision 0.32.1](https://github.com/sinclairzx81/typebox/pull/701) Specify default exports for Web Pack
## [0.32.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.32.0)
## Overview
Revision 0.32.0 adds support for ESM and carries out the work necessary to fully modularize the TypeBox type system to enable selective type imports. This revision also adds three new types (Mapped, Const, and Deref), along with two new Value functions (Clean and Default) as well as many enhancements to existing types (Index, KeyOf, RegExp, Optional and Readonly). This revision also carries out many internal optimizations to enhance type inference across all types.
This revision is a milestone revision for the TypeBox project. It has several breaking changes and requires a minor revision.
## Contents
- [Type Imports](#Type-Imports)
- [Value Function Import](#Value-Function-Imports)
- [CommonJS and ESM](#CommonJS-and-ESM)
- [Types](#Types)
- [Mapped Type](#Types-Mapped-Type)
- [Const Type](#Types-Const-Type)
- [Deref Type](#Types-Deref-Type)
- [RegExp Type](#Types-RegExp-Type)
- [Subtract Modifiers](#Types-Subtract-Modifiers)
- [Values](#Values)
- [Clean Function](#Values-Clean-Function)
- [Default Function](#Values-Default-Function)
- [Errors](#Errors)
- [Error Parameter](#Errors-Error-Parameter)
- [Optimizations](#Optimizations)
- [Bundle Size](#Optimizations-Bundle-Size)
- [Breaking](#Breaking)
- [Renamed Symbols](#Breaking-Renamed-Symbols)
- [TypeGuard Interface Change](#Breaking-TypeGuard-Interface-Change)
- [Value Submodule Imports](#Breaking-Value-Submodule-Imports)
- [Error Function](#Breaking-Error-Function)
- [RegEx](#Breaking-RegEx)
<a name="Type-Imports"></a>
### Type Imports
Revision 0.32.0 adds the ability to import types individually.
```typescript
import { Type, type Static } from '@sinclair/typebox' // classic - 37.0 kb minified
import { Object, String, Number, type Static } from '@sinclair/typebox' // selective - 6.5 kb minified
```
<a name="Value-Function-Imports"></a>
### Value Function Imports
Revision 0.32.0 adds the ability to import value functions from the `/value` module path.
```typescript
import { Value } from '@sinclair/typebox/value' // classic - 61.5 kb minified
import { Check } from '@sinclair/typebox/value' // selective - 18.2 kb minified
```
### CommonJS and ESM
<a name="CommonJS-and-ESM"></a>
Revision 0.32.0 now publishes both CommonJS and ESM builds of TypeBox. Existing CommonJS users should not be impacted by the addition of ESM. For ESM users however, particularily those using bundlers, it's now possible to benefit from deep tree shake optimizations provided by modern bundler tooling.
<a name="Types"></a>
## Types
Revision 0.32.0 adds three new types to the type system and makes enhancements to Readonly and Optional modifiers.
<a name="Types-Mapped-Type"></a>
### Mapped Type
Revision 0.32.0 adds the Mapped type which replicates TS [Mapped Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html) at runtime. The following shows the syntax comparison between TypeScript and TypeBox.
#### TypeScript
```typescript
type T = {
x: number,
y: number,
z: number
}
type M = { [K in keyof T]: T[K] } // a mapped type
```
#### TypeBox
```typescript
const T = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number()
})
const M = Type.Mapped(Type.KeyOf(T), K => Type.Index(T, K)) // a mapped type
```
Mapped types use a functional design to replicate the TypeScript feature. For users interested in this type, it may be helpful to use the [TypeBox Workbench](https://sinclairzx81.github.io/typebox-workbench/) which can generate runtime Mapped types from TypeScript syntax.
<a name="Types-Const-Type"></a>
### Const Type
Revision 0.32.0 adds a new Const type that creates `readonly` types from object, array and primitive literal values. This type analogs the TypeScript `as const` syntax. The following shows general usage.
```typescript
const A = Type.Const(1 as const) // const A: TLiteral<1>
const B = Type.Const([1, 2, 3] as const) // const B: TReadonly<TTuple<[
// TLiteral<1>,
// TLiteral<2>,
// TLiteral<3>
// ]>>
const C = Type.Const({ // const C: TObject<{
x: 1, // x: TReadonly<TLiteral<1>>,
y: 2, // y: TReadonly<TLiteral<2>>,
z: 3 // z: TReadonly<TLiteral<3>>,
} as const) // }>
```
Revision 0.32.0 continues support for TypeScript 4.0, and because of this, the `as const` syntax must be appended to each literal value passed to the Const type. When TypeBox ends support for 4.0, updates will be made to this type to make use of [Const Type Parameters](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#const-type-parameters). This update will enable TypeBox to correctly infer the readonly literal type without the need for `as const`.
<a name="Types-Deref-Type"></a>
### Deref Type
Revision 0.32.0 adds a new Type.Deref type which can be used to dereference type schematics.
```typescript
const Vector = Type.Object({ // const Vector = {
x: Type.Number(), // type: 'object',
y: Type.Number(), // required: ['x', 'y', 'z'],
}, { $id: 'Vector' }) // properties: {
// x: { type: 'number' },
// y: { type: 'number' }
// },
// $id: 'Vector'
// }
const VectorRef = Type.Ref(Vector) // const VectorRef = {
// $ref: 'Vector'
// }
// ... Embedded Reference Type
const Vertex = Type.Object({ // const Vertex = {
position: VectorRef, // type: 'object',
texcoord: VectorRef, // required: ['position', 'texcoord'],
}) // properties: {
// position: { $ref: 'Vector' },
// texcoord: { $ref: 'Vector' }
// }
// }
// ... Dereferenced Embedded Reference Type
const VertexDeref = Type.Deref(Vertex, [Vector]) // const VertexDeref = {
// type: 'object',
// required: ['position', 'texcoord'],
// properties: {
// position: {
// type: 'object',
// required: ['x', 'y', 'z'],
// properties: {
// x: { type: 'number' },
// y: { type: 'number' }
// }
// },
// texcoord: {
// type: 'object',
// required: ['x', 'y', 'z'],
// properties: {
// x: { type: 'number' },
// y: { type: 'number' }
// }
// }
// }
// }
```
The addition of Deref was prompted by issues composing reference types with mapping types (such as Partial, Required, Pick and Omit) which is generally not supported. Prior to Revision 0.32.0, there was some expectation for users to maintain and dereference types manually. In 0.32.0, users will still need to maintain references, but Deref will offer a more convenient mechanism to normalize reference types prior to composition.
<a name="Types-RegExp-Type"></a>
### RegExp Type
Revision 0.32.0 updates RegExp to support the full ECMA 262 regular expression syntax. In previous revisions, this type had been expressed as an alias for `TString` with a `pattern` to try ensure compliance with the [regular expression](https://json-schema.org/understanding-json-schema/reference/regular_expressions) subset supported by Json Schema. In Revision 0.32.0, RegExp is given a new type representation unto itself (named `TRegExp`) which houses both `source` and `flags` properties used to reconstruct a JavaScript regular expression object, making it properly distinct from `TString` and fully supportive of UTF-16.
```typescript
// Case Insensitive
const T = Type.RegExp(/abc/i) // const T = {
// type: 'RegExp',
// source: 'abc',
// flags: 'i'
// }
type T = Static<typeof T> // type T = string
Value.Check(T, 'abc') // ok
Value.Check(T, 'ABC') // ok
// Extended Syntax
const E = Type.RegExp(/<a?:.+?:\d{18}>|\p{Extended_Pictographic}/gu)
Value.Check(E, '♥️♦️♠️♣️') // ok - emoji supported
```
The RegExp type can be thought of as a more capable TemplateLiteral that can only reasonably infer as `string`. Additionally, the RegExp inference type of `string` is unique to the other `[JavaScript]` types in that it does not infer as it's named type. The updates to RegExp were prompted by the limitations with Json Schema expressions, and to provide better options for users requiring general Unicode validation support. For Json Schema compliance, the recommendation moving forward will be to use either String with pattern or TemplateLiteral.
```typescript
const T = Type.String({ pattern: '^(a|b|c)$' }) // Json Schema compliant
const T = Type.TemplateLiteral('${a|b|c}') // Json Schema compliant
const T = Type.RegExp(/$(a|b|c)$/) // Non Json Schema compliant
```
<a name="Types-Subtractive-Modifiers"></a>
### Subtract Modifier
Revision 0.32.0 adds new overloads for Readonly and Optional modifiers that enable them to subtract (or remove) that modifier from a type. Both Readonly and Optional now accept an optional secondary boolean argument that if `false`, will remove the modifier.
#### TypeScript
```typescript
type T = {
x?: number,
y?: number
}
type M = { [K in keyof T]-?: T[K] } // -? - subtract optional modifier
```
#### TypeBox
```typescript
const T = Type.Object({
x: Type.Optional(Type.Number()),
y: Type.Optional(Type.Number())
})
const M = Type.Mapped(Type.KeyOf(T), K => {
return Type.Optional(Type.Index(T, K), false) // false - subtract optional modifier
})
```
Subtractive modifiers are provided in support of the new Mapped type feature.
<a name="Values"></a>
## Values
Revision 0.32.0 adds two new functions to the Value module.
<a name="Values-Clean-Function"></a>
### Clean Function
Revision 0.32.0 adds a new Clean function that can be used to omit any values unknown to the type. This function will work irrespective of if `additionalProperties` is specified on the type. The Clean function is intended to replicate the functionality of Ajv's `removeAdditional` configuration.
```typescript
const T = Type.Object({
x: Type.Number(),
y: Type.Number()
})
const X = Value.Clean(T, null) // const 'X = null
const Y = Value.Clean(T, { x: 1 }) // const 'Y = { x: 1 }
const Z = Value.Clean(T, { x: 1, y: 2, z: 3 }) // const 'Z = { x: 1, y: 2 }
```
Note: the Clean function does not check the validity of the value being cleaned, and does not provide assurances that the result will be valid. Its return value is `unknown` and should be checked before use.
<a name="Values-Default-Function"></a>
### Default Function
Revision 0.32.0 adds a new Default function that can be used to add missing values if the type specifies a `default` annotation. This function is intended to replicate Ajv's `useDefaults` functionality.
```typescript
const T = Type.Object({
x: Type.Number({ default: 0 }),
y: Type.Number({ default: 0 })
})
const X = Value.Default(T, null) // const 'X = null - non-enumerable
const Y = Value.Default(T, { }) // const 'Y = { x: 0, y: 0 }
const Z = Value.Default(T, { x: 1 }) // const 'Z = { x: 1, y: 0 }
```
The Default function does not check the validity of the value being defaulted, and does not provide assurances that the result will be valid. Its return value is `unknown` and should be checked before use.
<a name="Optimizations"></a>
## Optimizations
Following the work to modularize TypeBox's type system, additional optimizations were carried out across each submodule to only import dependent type infrastructure. This has led to some fairly significant reductions in output sizes across each submodule. The main TypeBox import has increased in size due in part to the new Mapped types feature and other associative types, however selective imports supported on this revision should offer options for users concerned about output bundle size. There will be contined work to optimize the new type system throughout 0.32.0 and subsequent revisions.
The following shows the comparisons between 0.31.0 and 0.32.0.
<a name="Optimizations-Bundle-Size"></a>
```typescript
// Revision 0.31.0
(index) Compiled Minified Compression
typebox/compiler '163.6 kb' ' 71.6 kb' '2.28 x'
typebox/errors '113.3 kb' ' 50.1 kb' '2.26 x'
typebox/system ' 83.9 kb' ' 37.5 kb' '2.24 x'
typebox/value '191.1 kb' ' 82.3 kb' '2.32 x'
typebox ' 73.8 kb' ' 32.3 kb' '2.29 x'
// Revision 0.32.0
(index) Compiled Minified Compression
typebox/compiler '120.6 kb' ' 52.9 kb' '2.28 x'
typebox/errors ' 55.7 kb' ' 25.5 kb' '2.19 x'
typebox/system ' 4.7 kb' ' 2.0 kb' '2.33 x'
typebox/value '146.2 kb' ' 62.0 kb' '2.36 x'
typebox ' 91.4 kb' ' 37.8 kb' '2.42 x'
```
<a name="Errors"></a>
## Errors
Revision 0.32.0 makes some enhancements to errors.
<a name="Errors-Error-Parameter"></a>
### Error Parameter
Revision 0.32.0 updates TypeBox's ErrorFunction to accept an ErrorParameter that contains additional information regarding the cause of a validation error. In Revision 0.31.0, only `errorType` and `schema` were passed through to this function. In 0.32.0 the additional properties `value` and `path` are also passed through. This update was prompted by some users needing to be able to generate specific error messages derived from specific values or other associated information.
The following shows the changes from 0.31.0 to 0.32.0.
```typescript
// Revision 0.31.0
import { TypeSystemErrorFunction } from '@sinclair/typebox/system'
TypeSystemErrorFunction.Set((schema, errorType) => {
return 'oh no, an error!'
})
// Revision 0.32.0
import { SetErrorFunction } from '@sinclair/typebox/errors'
SetErrorFunction(({ schema, errorType, path, value }) => { // as destructured object
return 'oh no, an error!'
})
```
Note that Revision 0.32.0 does make a breaking interface change by moving the ErrorFunction from `/system` to `/errors`. See breaking changes for more information.
<a name="Breaking"></a>
## Breaking
The following list the breaking changes in Revision 0.32.0.
<a name="Breaking-Renamed-Symbols"></a>
### Renamed Symbols
Revision 0.32.0 renames the `Optional`, `Required` and `Transform` symbols to `OptionalKind`, `RequiredKind` and `TransformKind`. This change was necessary to avoid conflicts with exported type functions.
```typescript
// Revision 0.31.0
import { Kind, Hint, Optional, Required, Transform } from '@sinclair/typebox' // these are symbols
// Revision 0.32.0
import {
Kind, Hint, OptionalKind, RequiredKind, TransformKind, // these are symbols
Optional, Required, Transform // these are type imports
} from '@sinclair/typebox'
```
<a name="Breaking-TypeGuard-Interface-Change"></a>
### TypeGuard Interface Change
Revision 0.32.0 has a breaking interface change on the TypeGuard utility where the `T` prefixed guard functions have been updated to use the `Is` prefix. This naming change is perhaps somewhat more sensible than the previous naming, however the update was largely prompted by TypeScript compiler issues where interface types (i.e. `TString`) where conflicting with the `TString` functions leading to breakage in CommonJS.
```typescript
// Revision 0.31.0
import { TypeGuard, Kind } from '@sinclair/typebox'
const R = TypeGuard.TString({ ... })
// Revision 0.32.0
import { TypeGuard } from '@sinclair/typebox'
const R = TypeGuard.IsString({ ... })
```
<a name="Breaking-Value-Submodule-Imports"></a>
### Value Submodule Imports
The value submodule function import paths are unfortunately no longer supported. Instead, these can be imported directly on the `/value` path. The need to break the submodule paths was mostly due to complexities configuring dual ESM and CommonJS publishing for the package, as well as retaining support for pre and post node16 module resolution (of which many complexities reside, both for Node as well as for TypeScript type module resolution)
```typescript
// Revision 0.31.0
import { Check } from '@sinclair/typebox/value/check'
// Revision 0.32.0
import { Check } from '@sinclair/typebox/value'
```
<a name="Breaking-Error-Function"></a>
### Error Function
The TypeSystemErrorFunction has been replaced with SetErrorFunction which can be imported on the `/errors` submodule. This change is generally a tidy up, and to reserve the `/system` submodule for type system policy configuration, as well as future Json Schema generation options (draft 2020-12)
```typescript
// Revision 0.31.0
import { TypeSystemErrorFunction, ValueErrorType, DefaultErrorFunction } from '@sinclair/typebox/system'
TypeSystemErrorFunction.Set((schema, errorType) => { // i18n override
switch(errorType) {
/* en-US */ case ValueErrorType.String: return 'Expected string'
/* fr-FR */ case ValueErrorType.Number: return 'Nombre attendu'
/* ko-KR */ case ValueErrorType.Boolean: return '예상 부울'
/* en-US */ default: return DefaultErrorFunction(schema, errorType)
}
})
// Revision 0.32.0
import { SetErrorFunction, ValueErrorType, DefaultErrorFunction } from '@sinclair/typebox/errors'
SetErrorFunction((error) => { // i18n override
switch(error.errorType) {
/* en-US */ case ValueErrorType.String: return 'Expected string'
/* fr-FR */ case ValueErrorType.Number: return 'Nombre attendu'
/* ko-KR */ case ValueErrorType.Boolean: return '예상 부울'
/* en-US */ default: return DefaultErrorFunction(error)
}
})
```
<a name="Breaking-RegEx"></a>
### RegEx
This RegEx function was flagged for deprecation on 0.30.0. It has been removed on Revision 0.32.0. Use the Type.RegExp type, or Type.String with a pattern to remain compatible with the Json Schema specification.
```typescript
// Revision 0.31.0
const T = Type.RegEx(/abc/) // deprecation warning
// Revision 0.32.0
const A = Type.RegExp(/abc/) // JavaScript Type
const B = Type.String({ pattern: /abc/.source }) // Json Type
```

59
changelog/0.33.0.md Normal file
View File

@@ -0,0 +1,59 @@
### 0.33.0
---
### Revision Updates
- [Revision 0.33.22](https://github.com/sinclairzx81/typebox/pull/1065)
- Rename TypeScript parsing infrastructure from `/parse` to `/syntax`. Remove Parse API from top level import.
- [Revision 0.33.21](https://github.com/sinclairzx81/typebox/pull/1064)
- [1063](https://github.com/sinclairzx81/typebox/issues/1063) Hotfix to resolve variable shadowing on Object (Parser Runtime)
- [Revision 0.33.20](https://github.com/sinclairzx81/typebox/pull/1062)
- Add TypeScript Parsing Infrastructure. Add Parse API to top level import.
- [Revision 0.33.19](https://github.com/sinclairzx81/typebox/pull/1061)
- Preemptive fix for TypeScript 5.8.0-dev (Type Fix for Immutable Function)
- [Revision 0.33.18](https://github.com/sinclairzx81/typebox/pull/1060)
- [1052](https://github.com/sinclairzx81/typebox/pull/1052) Export the Encode | Decode functions directly. Refactoring on Value submodule.
- [1057](https://github.com/sinclairzx81/typebox/pull/1057) Export Object with var declaration to prevent global shadowing. Related Babel [Issue](https://github.com/babel/babel/issues/16943).
- [Revision 0.33.17](https://github.com/sinclairzx81/typebox/pull/1042)
- [1041](https://github.com/sinclairzx81/typebox/issues/1041) Avoid Exponentiation operator on Value.Hash
- [Revision 0.33.16](https://github.com/sinclairzx81/typebox/pull/1015)
- [1015](https://github.com/sinclairzx81/typebox/issues/1015) Add sub error iterators to ValueError
- [Revision 0.33.15](https://github.com/sinclairzx81/typebox/pull/1025)
- [1024](https://github.com/sinclairzx81/typebox/issues/1024) Fix to correctly resolve default Dates
- [Revision 0.33.14](https://github.com/sinclairzx81/typebox/pull/1019)
- [1019](https://github.com/sinclairzx81/typebox/pull/1019) Converting Large Numbers to BigInt
- [Revision 0.33.13](https://github.com/sinclairzx81/typebox/pull/1011)
- [1010](https://github.com/sinclairzx81/typebox/pull/1011) Fixes Value.Parse fails with recursive types
- [Revision 0.33.12](https://github.com/sinclairzx81/typebox/pull/999)
- [998](https://github.com/sinclairzx81/typebox/issues/998) Avoid losing precision when converting to bigints
- [Revision 0.33.11](https://github.com/sinclairzx81/typebox/pull/994)
- [993](https://github.com/sinclairzx81/typebox/issues/993) Prevent mutation on union values during Convert
- [Revision 0.33.10](https://github.com/sinclairzx81/typebox/pull/991)
- [907](https://github.com/sinclairzx81/typebox/issues/907) Add package.json metadata to specify possible side effect modules
- [Revision 0.33.9](https://github.com/sinclairzx81/typebox/pull/984)
- [887](https://github.com/sinclairzx81/typebox/issues/887) Generate Nested Intersect Errors
- [Revision 0.33.8](https://github.com/sinclairzx81/typebox/pull/983)
- [982](https://github.com/sinclairzx81/typebox/issues/982) Prevent Intersect Transform Encode callback from being called twice
- [974](https://github.com/sinclairzx81/typebox/issues/974) Make strict the Encode and Decode return type
- [975](https://github.com/sinclairzx81/typebox/issues/975) Support default annotation being assigned Functions for lazy value initialization on Create
- [980](https://github.com/sinclairzx81/typebox/issues/980) Enable Mapping types to override user defined options from source type
- [976](https://github.com/sinclairzx81/typebox/issues/976) Support Constraint Copy for Pick, Omit (inline with Partial / Required) (Trialing Implementation)
- Flag Strict For Deprecation
- [Revision 0.33.7](https://github.com/sinclairzx81/typebox/pull/964)
- Additional updates to improve Default for enumerable objects.
- [Revision 0.33.6](https://github.com/sinclairzx81/typebox/pull/963)
- Add object traversal path for Default. Ensure enumerable objects are traversed.
- [Revision 0.33.5](https://github.com/sinclairzx81/typebox/pull/959)
- Provide better support for transforming properties with optional modifiers.
- [Revision 0.33.4](https://github.com/sinclairzx81/typebox/pull/953)
- Add Assert and Parse value functions. Add defacto AssertError type.
- [Revision 0.33.3](https://github.com/sinclairzx81/typebox/pull/950)
- Optimize Value Diff algorithm. Update edit sequence to INSERT, UPDATE then DELETE.
- [Revision 0.33.2](https://github.com/sinclairzx81/typebox/pull/947)
- Ensure user defined schema options are retained on mapping types, Pick, Omit and Mapped.
- [Revision 0.33.1](https://github.com/sinclairzx81/typebox/pull/945)
- Apply mutability fix for Intrinsic and Not schematics (inline with Default)
- [Revision 0.33.0](https://github.com/sinclairzx81/typebox/pull/941)
- Add InstanceMode to enable Clone, Freeze and Default schema initialization options. Optimize for Default.

289
changelog/0.34.0.md Normal file
View File

@@ -0,0 +1,289 @@
### 0.34.0
---
### Revision Updates
- [Revision 0.34.41](https://github.com/sinclairzx81/typebox/pull/1310)
- Disable Node10 Module Resolution | TS7 Deprecation Warning.
- [Revision 0.34.40](https://github.com/sinclairzx81/typebox/pull/1293)
- Use Uniform over Reciprocal weighting on Cast Union Select
- [Revision 0.34.39](https://github.com/sinclairzx81/typebox/pull/1296)
- Guard for Array in Object and Record conversion
- [Revision 0.34.38](https://github.com/sinclairzx81/typebox/pull/1282)
- Preserve exact type matches in Union conversion
- [Revision 0.34.37](https://github.com/sinclairzx81/typebox/pull/1278)
- Fix Support nested Union selection when scoring for Cast
- [Revision 0.34.36](https://github.com/sinclairzx81/typebox/pull/1276)
- Fix Record Intersect on Cast
- [Revision 0.34.35](https://github.com/sinclairzx81/typebox/pull/1265)
- Deep Assign on Intersect Cast
- [Revision 0.34.34](https://github.com/sinclairzx81/typebox/pull/1263)
- Support Inference of Ref inside Recursive inside Module
- [Revision 0.34.33](https://github.com/sinclairzx81/typebox/pull/1220)
- Hotfix: Correct Invalid Import Specifier
- [Revision 0.34.32](https://github.com/sinclairzx81/typebox/pull/1218)
- Accelerated | High Performance Syntax Parsing
- [Revision 0.34.31](https://github.com/sinclairzx81/typebox/pull/1209)
- Use Tail Call Optimized Inference for Records with Large Union Keys
- [Revision 0.34.30](https://github.com/sinclairzx81/typebox/pull/1198)
- Additional Syntax Parsing Optimizations
- [Revision 0.34.29](https://github.com/sinclairzx81/typebox/pull/1197)
- Syntax Parsing Optimizations
- [Revision 0.34.28](https://github.com/sinclairzx81/typebox/pull/1187)
- Add Cast to Configurable Parse Pipeline
- [Revision 0.34.27](https://github.com/sinclairzx81/typebox/pull/1182)
- [1178](https://github.com/sinclairzx81/typebox/issues/1178) Support Deep Referential Transform Inference Inside Modules
- [Revision 0.34.26](https://github.com/sinclairzx81/typebox/pull/1181)
- Internal: Use Parser Context Threading for Generic Arguments.
- [Revision 0.34.25](https://github.com/sinclairzx81/typebox/pull/1176)
- Evaluate Conditional Expression for Instatiated Object Types
- [Revision 0.34.24](https://github.com/sinclairzx81/typebox/pull/1175)
- Add Support For Generic Parameter Syntax
- [Revision 0.34.23](https://github.com/sinclairzx81/typebox/pull/1174)
- Inline Instantiate Type Logic Local to Instantiate Module
- [Revision 0.34.22](https://github.com/sinclairzx81/typebox/pull/1171)
- Intrinsic Types Number, String, Boolean, etc Passthrough on Required and Partial Mapping
- [Revision 0.34.21](https://github.com/sinclairzx81/typebox/pull/1168)
- Reimplement Computed Record Types
- [Revision 0.34.20](https://github.com/sinclairzx81/typebox/pull/1167)
- Hotfix: Disable Computed Record Types
- [Revision 0.34.19](https://github.com/sinclairzx81/typebox/pull/1166)
- Hotfix: Republished due to NPM error
- [Revision 0.34.18](https://github.com/sinclairzx81/typebox/pull/1164)
- Hotfix: Internal Remap Imports
- [Revision 0.34.17](https://github.com/sinclairzx81/typebox/pull/1162)
- Add Argument() and Instantiate() Types and Instancing via Syntax support.
- [Revision 0.34.16](https://github.com/sinclairzx81/typebox/pull/1156)
- Export TypeBox String Parsing Infrastructure
- [Revision 0.34.15](https://github.com/sinclairzx81/typebox/pull/1148)
- [1147](https://github.com/sinclairzx81/typebox/issues/1147) Fix incorrect truncation for integers that exceed 32-bit values in Value.Convert
- [Revision 0.34.14](https://github.com/sinclairzx81/typebox/pull/1140)
- [1139](https://github.com/sinclairzx81/typebox/issues/1139) Update TypeCompiler Check for Promise (use instanceof Promise over Thenable check)
- [Revision 0.34.13](https://github.com/sinclairzx81/typebox/pull/1124)
- Pre emptive fix for TypeScript 5.8.0-nightly to resolve symbol narrowing on Convert.
- [Revision 0.34.12](https://github.com/sinclairzx81/typebox/pull/1120)
- [1119](https://github.com/sinclairzx81/typebox/issues/1119) Fix for Mutate Object Comparison
- [1117](https://github.com/sinclairzx81/typebox/issues/1117) Re-Add Type.Recursive Documentation
- [Revision 0.34.11](https://github.com/sinclairzx81/typebox/pull/1110)
- Fix Compiler Emit for Deeply Referential Module Types
- [Revision 0.34.10](https://github.com/sinclairzx81/typebox/pull/1107)
- Fix Declaration Emit for Index and Mapped Types
- Fix Record Inference Presentation when Embedded in Modules
- Fix Record Mapping when using TImport as Key
- Add Encode to Parse Operation List
- [Revision 0.34.9](https://github.com/sinclairzx81/typebox/pull/1101)
- User Defined Parse Pipelines
- Access to Schema and References on TypeCheck
- [Revision 0.34.8](https://github.com/sinclairzx81/typebox/pull/1098)
- Fix for Computed Readonly and Optional Properties
- [Revision 0.34.7](https://github.com/sinclairzx81/typebox/pull/1093)
- Revert Ref(Schema) Signature with Deprecation Notice
- [Revision 0.34.6](https://github.com/sinclairzx81/typebox/pull/1090)
- Add Computed To Type and Kind Guards (IsSchema)
- [Revision 0.34.5](https://github.com/sinclairzx81/typebox/pull/1088)
- Record Types no longer TCompute for TRef Value Type (Modules)
- [Revision 0.34.4](https://github.com/sinclairzx81/typebox/pull/1085)
- Inference Path for Enum within Modules
- [Revision 0.34.3](https://github.com/sinclairzx81/typebox/pull/1083)
- Retain Array Elements on Value Default
- [Revision 0.34.2](https://github.com/sinclairzx81/typebox/pull/1082)
- Resolve import pathing issue introduced on 0.34.1
- [Revision 0.34.1](https://github.com/sinclairzx81/typebox/pull/1080)
- Implement Computed Type Deref in Modules
## [0.34.0](https://www.npmjs.com/package/@sinclair/typebox/v/0.34.0)
## Overview
Revision 0.34.0 represents a significant milestone for the TypeBox project. This update changes how TypeBox manages type references (Ref) and introduces a new Module type to support mutual recursion and self-referencing types. Additionally, it includes a new submodule for parsing TypeScript syntax directly into TypeBox types.
Please note that this release includes breaking changes to Ref and some deprecations. These updates require a minor semver revision.
## Contents
- [Enhancements](#Enhancements)
- [Module Types](#Module-Types)
- [Syntax Types](#Syntax-Types)
- [Breaking Changes](#Breaking-Changes)
- [Ref](#Ref)
- [Deref](#Deref)
- [Strict](#Strict)
<a name="Enhancements"></a>
## Enhancements
Below are the enhancements introduced in Version 0.34.0.
<a name="Module-Type"></a>
### Module Types
Revision 0.34.0 introduces a new type, called Module. Modules are represented as JSON Schema $def schematics, specifically designed to support both mutual and self-recursive types. This addition resolves a longstanding issue in TypeBox, where complex recursive structures often encountered "definition order" problems, making certain recursive structures difficult to represent cleanly. With Modules, you can now define schematics within a Module context, allowing them to be referenced within the type system through a separate inference operation.
```typescript
// The following creates a circular recursive type.
const Module = Type.Module({
A: Type.Object({
b: Type.Ref('B') // Ref B:
}),
B: Type.Object({
c: Type.Ref('C') // Ref C:
}),
C: Type.Object({
a: Type.Ref('A') // Ref A:
}),
})
// Module types must be imported before use.
const A = Module.Import('A') // const A: TImport<{...}, 'A'>
type A = Static<typeof A> // type A = {
// b: {
// c: {
// a: {
// b: ...
// }
// }
// }
// }
```
<a name="Syntax-Types"></a>
### Syntax Types
Revision 0.34.0 introduces a new submodule for parsing TypeScript syntax directly into TypeBox types, implemented both at runtime and within the type system. This feature was made possible through the development of a separate project, [ParseBox](https://github.com/sinclairzx81/parsebox) (MIT-licensed), which provides a symmetric runtime and type-level parsing infrastructure.
As of 0.34.0, Syntax Types are available as an opt-in feature, with the parsing infrastructure adding approximately 10kb (minified) to the existing type builder. With further optimizations, this feature may be elevated to a top-level import in future updates to minimize bundling size.
To use Syntax Types, import them from the `@sinclair/typebox/syntax` path.
```typescript
import { Parse } from '@sinclair/typebox/syntax'
// All primitive types are supported
const A = Parse('string') // const A: TString
const B = Parse('number') // const B: TNumber
const C = Parse('boolean') // const C: TBoolean
// ... Multiline parsing is supported (but comments are not)
const T = Parse(`{
x: number
y: number
z: number
}`)
// ... Parametertized parsing is supported
const O = Parse({ T }, `T & { w: number }`) // const O: TIntersect<[
// TObject<{
// x: TNumber,
// y: TNumber,
// z: TNumber,
// }>,
// TObject<{
// w: TNumber
// }>
// ]>
// ... Module parsing is also supported.
const Math = Parse(`module Math {
export interface X {
x: number
}
export interface Y {
y: number
}
export interface Z {
z: number
}
export interface Vector extends X, Y, Z {
type: 'Vector'
}
}`)
const Vector = Math.Import('Vector')
```
Runtime parsing performance should be quite good; however, static parsing performance could be improved. TypeScript will invoke the parser for each property accessed at design time. Ongoing efforts within the ParseBox project aim to optimize string parsing in TypeScript, with additional research underway into type-level caching strategies within the TypeScript compiler. Additional optimizations will be explored over the course of 0.34.x.
<a name="Breaking-Changes"></a>
## Breaking Changes
The following are the breaking changes in Revision 0.34.0.
<a name="Ref"></a>
### Ref
Revision 0.34.0 introduces a breaking change to Ref, modifying its signature to accept only constant string values. Previously, Ref could accept an existing TypeBox type, provided it had an $id assigned.
```typescript
// Revision 0.33.0
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
type R = Static<typeof R> // type R = string
// Revision 0.34.0
const T = Type.String({ $id: 'T' })
const R = Type.Ref('T')
type R = Static<typeof R> // type R = unknown
```
In Revision 0.34.0, the inferred type for Ref is now unknown. Implementations using the previous version of Ref can switch to Unsafe to type the reference to the target value.
```typescript
// Revision 0.34.0
const T = Type.String({ $id: 'T' })
const R = Type.Unsafe<Static<typeof T>>(Type.Ref('T'))
type R = Static<typeof R> // type R = string
```
<a name="Deref"></a>
### Deref
Revision 0.34.0 removes the Deref type, which was previously used to dereference schematics. Since the Ref signature has changed from TSchema to string, there is no longer a way to resolve reference types accurately. TypeBox may provide a prototype example of this type upon request.
<a name="Strict"></a>
### Strict
Revision 0.34.0 removes the Strict type from the Type Builder, which was deprecated in version 0.33.8. This type was introduced several years ago in response to a change in Ajv that prohibited unknown keywords. At that time, TypeBox used string property keys for `kind` and `modifier`, which required either Ajv configuration or the use of Strict. These properties have since been updated to Symbol properties, resolving the issues with Ajv. However, the Strict type remained due to some use in ecosystem projects, which has since reduced.
For those who still need Strict, the recommended approach is to use the JSON stringify/parse method outlined in the deprecation notice.
```typescript
/**
* @deprecated `[Json]` Omits compositing symbols from this schema. It is recommended
* to use the JSON parse/stringify to remove compositing symbols if needed. This
* is how Strict works internally.
*
* ```typescript
* JSON.parse(JSON.stringify(Type.String()))
* ```
*/
export function Strict<T extends TSchema>(schema: T): TStrict<T> {
return JSON.parse(JSON.stringify(schema))
}
```

View File

@@ -0,0 +1,148 @@
/*--------------------------------------------------------------------------
@sinclair/typebox
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import * as Types from '@sinclair/typebox'
// -------------------------------------------------------------------
// Annotation
//
// Generates TypeScript Type Annotations from TypeBox types
// -------------------------------------------------------------------
/** Generates TypeScript Type Annotations from TypeBox types */
export namespace Annotation {
// -----------------------------------------------------------------
// Escape
// -----------------------------------------------------------------
function Escape(content: string) {
return content.replace(/'/g, "\\'")
}
// -----------------------------------------------------------------
// Types
// -----------------------------------------------------------------
function Intersect(schema: Types.TSchema[], references: Types.TSchema[]): string {
const [L, ...R] = schema
// prettier-ignore
return R.length === 0
? `${Visit(L, references)}`
: `${Visit(L, references)} & ${Intersect(R, references)}`
}
function Union(schema: Types.TSchema[], references: Types.TSchema[]): string {
const [L, ...R] = schema
// prettier-ignore
return R.length === 0
? `${Visit(L, references)}`
: `${Visit(L, references)} | ${Union(R, references)}`
}
function Tuple(schema: Types.TSchema[], references: Types.TSchema[]): string {
const [L, ...R] = schema
// prettier-ignore
return R.length > 0
? `${Visit(L, references)}, ${Tuple(R, references)}`
: ``
}
function Property(schema: Types.TProperties, K: string, references: Types.TSchema[]): string {
const TK = schema[K]
// prettier-ignore
return (
Types.TypeGuard.IsOptional(TK) && Types.TypeGuard.IsReadonly(TK) ? `readonly ${K}?: ${Visit(TK, references)}` :
Types.TypeGuard.IsReadonly(TK) ? `readonly ${K}: ${Visit(TK, references)}` :
Types.TypeGuard.IsOptional(TK) ? `${K}?: ${Visit(TK, references)}` :
`${K}: ${Visit(TK, references)}`
)
}
function Properties(schema: Types.TProperties, K: string[], references: Types.TSchema[]): string {
const [L, ...R] = K
// prettier-ignore
return R.length === 0
? `${Property(schema, L, references)}`
: `${Property(schema, L, references)}; ${Properties(schema, R, references)}`
}
function Parameters(schema: Types.TSchema[], I: number, references: Types.TSchema[]): string {
const [L, ...R] = schema
// prettier-ignore
return R.length === 0
? `param_${I}: ${Visit(L, references)}`
: `param_${I}: ${Visit(L, references)}, ${Parameters(R, I + 1, references)}`
}
function Literal(schema: Types.TLiteral, references: Types.TSchema[]): string {
return typeof schema.const === 'string' ? `'${Escape(schema.const)}'` : schema.const.toString()
}
function Record(schema: Types.TRecord, references: Types.TSchema[]): string {
// prettier-ignore
return (
Types.PatternBooleanExact in schema.patternProperties ? `Record<boolean, ${Visit(schema.patternProperties[Types.PatternBooleanExact], references)}>` :
Types.PatternNumberExact in schema.patternProperties ? `Record<number, ${Visit(schema.patternProperties[Types.PatternNumberExact], references)}>` :
Types.PatternStringExact in schema.patternProperties ? `Record<string, ${Visit(schema.patternProperties[Types.PatternStringExact], references)}>` :
`{}`
)
}
function TemplateLiteral(schema: Types.TTemplateLiteral, references: Types.TSchema[]) {
const E = Types.TemplateLiteralParseExact(schema.pattern)
if (!Types.IsTemplateLiteralExpressionFinite(E)) return 'string'
return [...Types.TemplateLiteralExpressionGenerate(E)].map((literal) => `'${Escape(literal)}'`).join(' | ')
}
function Visit(schema: Types.TSchema, references: Types.TSchema[]): string {
// prettier-ignore
return (
Types.TypeGuard.IsAny(schema) ? 'any' :
Types.TypeGuard.IsArray(schema) ? `${Visit(schema.items, references)}[]` :
Types.TypeGuard.IsAsyncIterator(schema) ? `AsyncIterableIterator<${Visit(schema.items, references)}>` :
Types.TypeGuard.IsBigInt(schema) ? `bigint` :
Types.TypeGuard.IsBoolean(schema) ? `boolean` :
Types.TypeGuard.IsConstructor(schema) ? `new (${Parameters(schema.parameter, 0, references)}) => ${Visit(schema.returns, references)}` :
Types.TypeGuard.IsDate(schema) ? 'Date' :
Types.TypeGuard.IsFunction(schema) ? `(${Parameters(schema.parameters, 0, references)}) => ${Visit(schema.returns, references)}` :
Types.TypeGuard.IsInteger(schema) ? 'number' :
Types.TypeGuard.IsIntersect(schema) ? `(${Intersect(schema.allOf, references)})` :
Types.TypeGuard.IsIterator(schema) ? `IterableIterator<${Visit(schema.items, references)}>` :
Types.TypeGuard.IsLiteral(schema) ? `${Literal(schema, references)}` :
Types.TypeGuard.IsNever(schema) ? `never` :
Types.TypeGuard.IsNull(schema) ? `null` :
Types.TypeGuard.IsNot(schema) ? 'unknown' :
Types.TypeGuard.IsNumber(schema) ? 'number' :
Types.TypeGuard.IsObject(schema) ? `{ ${Properties(schema.properties, Object.getOwnPropertyNames(schema.properties), references)} }` :
Types.TypeGuard.IsPromise(schema) ? `Promise<${Visit(schema.item, references)}>` :
Types.TypeGuard.IsRecord(schema) ? `${Record(schema, references)}` :
Types.TypeGuard.IsRef(schema) ? `${Visit(Types.Type.Deref(schema, references), references)}` :
Types.TypeGuard.IsString(schema) ? 'string' :
Types.TypeGuard.IsSymbol(schema) ? 'symbol' :
Types.TypeGuard.IsTemplateLiteral(schema) ? `${TemplateLiteral(schema, references)}` :
Types.TypeGuard.IsThis(schema) ? 'unknown' : // requires named interface
Types.TypeGuard.IsTuple(schema) ? `[${Tuple(schema.items || [], references)}]` :
Types.TypeGuard.IsUint8Array(schema) ? `Uint8Array` :
Types.TypeGuard.IsUndefined(schema) ? 'undefined' :
Types.TypeGuard.IsUnion(schema) ? `${Union(schema.anyOf, references)}` :
Types.TypeGuard.IsVoid(schema) ? `void` :
'unknown'
)
}
/** Generates a TypeScript annotation for the given schema */
export function Code(schema: Types.TSchema, references: Types.TSchema[] = []): string {
return Visit(schema, references)
}
}

View File

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

View File

@@ -0,0 +1,350 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/collections
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeCheck, TypeCompiler, ValueError } from '@sinclair/typebox/compiler'
import { TSchema, Static, TypeBoxError } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
// ----------------------------------------------------------------
// TypeArrayError
// ----------------------------------------------------------------
export class TypeArrayError extends TypeBoxError {
constructor(message: string) {
super(`${message}`)
}
}
export class TypeArrayLengthError extends TypeBoxError {
constructor() {
super('arrayLength not a number')
}
}
// ----------------------------------------------------------------
// TypeArray<T>
// ----------------------------------------------------------------
export class TypeArray<T extends TSchema> implements Iterable<Static<T>> {
readonly #typeCheck: TypeCheck<T>
readonly #values: Static<T>[]
constructor(schema: T, arrayLength: number = 0) {
if (typeof arrayLength !== 'number') throw new TypeArrayLengthError()
this.#typeCheck = TypeCompiler.Compile(schema)
this.#values = new Array(arrayLength)
for (let i = 0; i < arrayLength; i++) {
this.#values[i] = Value.Create(schema)
}
}
// ---------------------------------------------------
// Indexer
// ---------------------------------------------------
/** Sets the value at the given index */
public set(index: number, item: Static<T>): void {
this.#assertIndexInBounds(index)
this.#assertItem(index, item)
this.#values[index] = item
}
// ---------------------------------------------------
// Array<T>
// ---------------------------------------------------
/** Iterator for values in this array */
public [Symbol.iterator](): IterableIterator<Static<T>> {
return this.#values[Symbol.iterator]() as IterableIterator<T>
}
/** Gets the value at the given index */
public at(index: number): Static<T> {
this.#assertIndexInBounds(index)
return this.#values[index] as T
}
/**
* Gets the length of the array. This is a number one higher than the highest index in the array.
*/
public get length(): number {
return this.#values.length
}
/**
* Returns a string representation of an array.
*/
public toString(): string {
return this.#values.toString()
}
/**
* Returns a string representation of an array. The elements are converted to string using their toLocaleString methods.
*/
public toLocaleString(): string {
return this.#values.toLocaleString()
}
/**
* Removes the last element from an array and returns it.
* If the array is empty, undefined is returned and the array is not modified.
*/
public pop(): Static<T> | undefined {
return this.#values.pop()
}
/**
* Appends new elements to the end of an array, and returns the new length of the array.
* @param items New elements to add to the array.
*/
public push(...items: Static<T>[]): number {
this.#assertItems(items)
return this.#values.push(...items)
}
/**
* Combines two or more arrays.
* This method returns a new array without modifying any existing arrays.
* @param items Additional arrays and/or items to add to the end of the array.
*/
public concat(...items: ConcatArray<Static<T>>[]): Static<T>[]
/**
* Combines two or more arrays.
* This method returns a new array without modifying any existing arrays.
* @param items Additional arrays and/or items to add to the end of the array.
*/
public concat(...items: (T | ConcatArray<Static<T>>)[]): Static<T>[] {
this.#assertItems(items)
return this.#values.concat(...items) as Static<T>[]
}
/**
* Adds all the elements of an array into a string, separated by the specified separator string.
* @param separator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
*/
public join(separator?: string): string {
return this.#values.join(separator)
}
/**
* Reverses the elements in an array in place.
* This method mutates the array and returns a reference to the same array.
*/
public reverse(): Static<T>[] {
return this.#values.reverse() as Static<T>[]
}
/**
* Removes the first element from an array and returns it.
* If the array is empty, undefined is returned and the array is not modified.
*/
public shift(): Static<T> | undefined {
return this.#values.shift() as Static<T> | undefined
}
/**
* Returns a copy of a section of an array.
* For both start and end, a negative index can be used to indicate an offset from the end of the array.
* For example, -2 refers to the second to last element of the array.
* @param start The beginning index of the specified portion of the array.
* If start is undefined, then the slice begins at index 0.
* @param end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'.
* If end is undefined, then the slice extends to the end of the array.
*/
public slice(start?: number, end?: number): Static<T>[] {
return this.#values.slice(start, end) as Static<T>[]
}
/**
* Sorts an array in place.
* This method mutates the array and returns a reference to the same array.
* @param compareFn Function used to determine the order of the elements. It is expected to return
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
* ```ts
* [11,2,22,1].sort((a, b) => a - b)
* ```
*/
public sort(compareFn?: (a: Static<T>, b: Static<T>) => number): this {
this.#values.sort(compareFn as any)
return this
}
/**
* Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements.
* @param start The zero-based location in the array from which to start removing elements.
* @param deleteCount The number of elements to remove.
* @returns An array containing the elements that were deleted.
*/
public splice(start: number, deleteCount?: number): Static<T>[]
/**
* Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements.
* @param start The zero-based location in the array from which to start removing elements.
* @param deleteCount The number of elements to remove.
* @param items Elements to insert into the array in place of the deleted elements.
* @returns An array containing the elements that were deleted.
*/
public splice(start: number, deleteCount: number, ...items: Static<T>[]): Static<T>[] {
this.#assertItems(items)
return this.#values.splice(start, deleteCount, items) as Static<T>[]
}
/**
* Inserts new elements at the start of an array, and returns the new length of the array.
* @param items Elements to insert at the start of the array.
*/
public unshift(...items: Static<T>[]): number {
this.#assertItems(items)
return this.#values.unshift(items)
}
/**
* Returns the index of the first occurrence of a value in an array, or -1 if it is not present.
* @param searchElement The value to locate in the array.
* @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0.
*/
public indexOf(searchElement: Static<T>, fromIndex?: number): number {
return this.#values.indexOf(searchElement, fromIndex)
}
/**
* Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present.
* @param searchElement The value to locate in the array.
* @param fromIndex The array index at which to begin searching backward. If fromIndex is omitted, the search starts at the last index in the array.
*/
public lastIndexOf(searchElement: Static<T>, fromIndex?: number): number {
return this.#values.lastIndexOf(searchElement, fromIndex)
}
/**
* Determines whether all the members of an array satisfy the specified test.
* @param predicate A function that accepts up to three arguments. The every method calls
* the predicate function for each element in the array until the predicate returns a value
* which is coercible to the Boolean value false, or until the end of the array.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
public every<S extends T>(predicate: (value: Static<T>, index: number, array: Static<T>[]) => value is Static<S>, thisArg?: any): this is Static<S>[]
/**
* Determines whether all the members of an array satisfy the specified test.
* @param predicate A function that accepts up to three arguments. The every method calls
* the predicate function for each element in the array until the predicate returns a value
* which is coercible to the Boolean value false, or until the end of the array.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
public every(predicate: (value: Static<T>, index: number, array: Static<T>[]) => unknown, thisArg?: any): boolean {
return this.#values.every(predicate as any, thisArg)
}
/**
* Determines whether the specified callback function returns true for any element of an array.
* @param predicate A function that accepts up to three arguments. The some method calls
* the predicate function for each element in the array until the predicate returns a value
* which is coercible to the Boolean value true, or until the end of the array.
* @param thisArg An object to which the this keyword can refer in the predicate function.
* If thisArg is omitted, undefined is used as the this value.
*/
public some(predicate: (value: Static<T>, index: number, array: T[]) => unknown, thisArg?: any): boolean {
return this.#values.some(predicate as any, thisArg)
}
/**
* Performs the specified action for each element in an array.
* @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
public forEach(callbackfn: (value: Static<T>, index: number, array: Static<T>[]) => void, thisArg?: any): void {
return this.#values.forEach(callbackfn as any, thisArg)
}
/**
* Calls a defined callback function on each element of an array, and returns an array that contains the results.
* @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
public map<U>(callbackfn: (value: Static<T>, index: number, array: Static<T>[]) => U, thisArg?: any): U[] {
return this.#values.map(callbackfn as any, thisArg)
}
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
public filter<S extends T>(predicate: (value: Static<T>, index: number, array: Static<T>[]) => value is Static<S>, thisArg?: any): Static<S>[]
/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
*/
public filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[]
public filter(predicate: any, thisArg: any): any {
return this.#values.filter(predicate as any, thisArg)
}
/**
* Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
public reduce(callbackfn: (previousValue: Static<T>, currentValue: Static<T>, currentIndex: number, array: T[]) => Static<T>): Static<T>
public reduce(callbackfn: (previousValue: Static<T>, currentValue: Static<T>, currentIndex: number, array: T[]) => Static<T>, initialValue: Static<T>): Static<T>
/**
* Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
public reduce<U>(callbackfn: (previousValue: U, currentValue: Static<T>, currentIndex: number, array: Static<T>[]) => U, initialValue: U): U
public reduce(callbackfn: any, initialValue?: any): any {
return this.#values.reduce(callbackfn, initialValue)
}
/**
* Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
public reduceRight(callbackfn: (previousValue: Static<T>, currentValue: Static<T>, currentIndex: number, array: Static<T>[]) => Static<T>): Static<T>
public reduceRight(callbackfn: (previousValue: Static<T>, currentValue: Static<T>, currentIndex: number, array: Static<T>[]) => Static<T>, initialValue: T): Static<T>
/**
* Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
*/
public reduceRight<U>(callbackfn: (previousValue: U, currentValue: Static<T>, currentIndex: number, array: Static<T>[]) => U, initialValue: U): U
public reduceRight(callbackfn: any, initialValue?: any): any {
return this.#values.reduceRight(callbackfn, initialValue)
}
// ---------------------------------------------------
// Assertions
// ---------------------------------------------------
#formatError(errors: ValueError[]) {
return errors.map((error) => `${error.message} ${error.path}`).join('. ')
}
/** Asserts the given values */
#assertIndexInBounds(index: number) {
if (index >= 0 && index < this.#values.length) return
throw new TypeArrayError(`Index ${index} is outside the bounds of this Array.`)
}
/** Asserts the given values */
#assertItem(index: number, item: unknown): asserts item is Static<T> {
if (this.#typeCheck.Check(item)) return
const message = this.#formatError([...this.#typeCheck.Errors(item)])
throw new TypeArrayError(`Item at Index ${index} is invalid. ${message}`)
}
/** Asserts the given values */
#assertItems(items: unknown[]): asserts items is Static<T>[] {
for (let i = 0; i < items.length; i++) {
this.#assertItem(i, items[i])
}
}
/** Creates a typed array from an existing array */
public static from<T extends TSchema>(schema: T, iterable: IterableIterator<Static<T>> | Array<Static<T>>): TypeArray<T> {
if (globalThis.Array.isArray(iterable)) {
const array = new TypeArray(schema, iterable.length)
for (let i = 0; i < iterable.length; i++) {
array.set(i, iterable[i])
}
return array
}
const array = new TypeArray(schema)
for (const value of iterable) {
array.push(value)
}
return array
}
}

View File

@@ -0,0 +1,31 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/collections
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './array'
export * from './map'
export * from './set'

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

@@ -0,0 +1,156 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/collections
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeCheck, TypeCompiler, ValueError } from '@sinclair/typebox/compiler'
import { TSchema, Static, TypeBoxError } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
// ----------------------------------------------------------------
// TypeMapKeyError
// ----------------------------------------------------------------
export class TypeMapKeyError extends TypeBoxError {
constructor(message: string) {
super(`${message} for key`)
}
}
export class TypeMapValueError extends TypeBoxError {
constructor(key: unknown, message: string) {
super(`${message} for key ${JSON.stringify(key)}`)
}
}
// ----------------------------------------------------------------
// TypeMap<K, V>
// ----------------------------------------------------------------
// prettier-ignore
type TypeMapEntries<K extends TSchema, V extends TSchema> =
| Iterable<[Static<K>, Static<V>]>
| Array<[Static<K>, Static<V>]>
/** Runtime type checked Map collection */
export class TypeMap<K extends TSchema, V extends TSchema> {
readonly #keycheck: TypeCheck<K>
readonly #valuecheck: TypeCheck<V>
readonly #keys: Map<bigint, Static<K>>
readonly #values: Map<bigint, Static<V>>
/** Constructs a new HashMap of the given key and value types. */
constructor(key: K, value: V, entries: TypeMapEntries<K, V> = []) {
this.#keycheck = TypeCompiler.Compile(key)
this.#valuecheck = TypeCompiler.Compile(value)
this.#keys = new Map<bigint, Static<K>>()
this.#values = new Map<bigint, Static<V>>()
for (const [key, value] of entries) {
this.set(key, value)
}
}
/** Iterator for this TypeMap */
public *[Symbol.iterator](): IterableIterator<[Static<K>, Static<V>]> {
for (const [key, value] of this.#values) {
yield [this.#keys.get(key)!, value]
}
}
/** Iterator for the keys in this TypeMap */
public *keys(): IterableIterator<Static<K>> {
yield* this.#keys.values()
}
/** Iterator for the values in this TypeMap */
public *values(): IterableIterator<Static<V>> {
yield* this.#values.values()
}
/** Clears all entries in this map */
public clear(): void {
this.#values.clear()
this.#keys.clear()
}
/** Executes a provided function once per each key/value pair in the Map, in insertion order. */
public forEach(callbackfn: (value: Static<V>, key: Static<K>, map: TypeMap<K, V>) => void, thisArg?: any): void {
this.#values.forEach((value, key) => callbackfn(value, this.#keys.get(key)!, this))
}
/** @returns the number of elements in the TypeMap. */
public get size(): number {
return this.#values.size
}
/**
* @returns boolean indicating whether an element with the specified key exists or not.
*/
public has(key: Static<K>): boolean {
this.#assertKey(key)
return this.#values.has(this.#encodeKey(key))
}
/**
* Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.
* @returns Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
*/
public get(key: Static<K>): Static<V> | undefined {
this.#assertKey(key)
return this.#values.get(this.#encodeKey(key))!
}
/**
* Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
*/
public set(key: Static<K>, value: Static<V>) {
this.#assertKey(key)
this.#assertValue(key, value)
const encodedKey = this.#encodeKey(key)
this.#keys.set(encodedKey, key)
this.#values.set(encodedKey, value)
}
/**
* @returns true if an element in the Map existed and has been removed, or false if the element does not exist.
*/
public delete(key: Static<K>): boolean {
this.#assertKey(key)
const encodedKey = this.#encodeKey(key)
this.#keys.delete(encodedKey)
return this.#values.delete(encodedKey)
}
// ---------------------------------------------------
// Encoder
// ---------------------------------------------------
/** Encodes the key as a 64bit numeric */
#encodeKey(key: Static<K>) {
return Value.Hash(key)
}
// ---------------------------------------------------
// Assertions
// ---------------------------------------------------
#formatError(errors: ValueError[]) {
return errors
.map((error) => `${error.message} ${error.path}`)
.join('. ')
.trim()
}
/** Asserts the key matches the key schema */
#assertKey(key: unknown): asserts key is Static<K> {
if (this.#keycheck.Check(key)) return
throw new TypeMapKeyError(this.#formatError([...this.#keycheck.Errors(key)]))
}
/** Asserts the key matches the value schema */
#assertValue(key: Static<K>, value: unknown): asserts value is Static<V> {
if (this.#valuecheck.Check(value)) return
throw new TypeMapValueError(key, this.#formatError([...this.#valuecheck.Errors(value)]))
}
}

View File

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

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

@@ -0,0 +1,109 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/collections
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeCheck, TypeCompiler, ValueError } from '@sinclair/typebox/compiler'
import { TSchema, Static, TypeBoxError } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
// ----------------------------------------------------------------
// Errors
// ----------------------------------------------------------------
export class TypeSetError extends TypeBoxError {
constructor(message: string) {
super(`${message}`)
}
}
// ----------------------------------------------------------------
// TypeSet
// ----------------------------------------------------------------
/** Runtime type checked Set collection */
export class TypeSet<T extends TSchema> {
readonly #valuecheck: TypeCheck<T>
readonly values: Map<bigint, Static<T>>
constructor(schema: T, iterable: Array<T> | Iterable<T> = []) {
this.#valuecheck = TypeCompiler.Compile(schema)
this.values = new Map<bigint, Static<T>>()
for (const value of iterable) {
this.add(value)
}
}
/** Adds a value to this set */
public add(value: Static<T>): this {
this.#assertValue(value)
this.values.set(this.#encodeKey(value), value)
return this
}
/** Clears the values in this set */
public clear(): void {
this.values.clear()
}
/**
* Removes a specified value from the Set.
* @returns Returns true if an element in the Set existed and has been removed, or false if the element does not exist.
*/
public delete(value: Static<T>): boolean {
return this.values.delete(this.#encodeKey(value))
}
/** Executes a provided function once per each value in the Set object, in insertion order. */
public forEach(callbackfn: (value: Static<T>, value2: Static<T>, set: TypeSet<T>) => void, thisArg?: any): void {
this.values.forEach((value, value2) => callbackfn(value, value2, this), thisArg)
}
/**
* @returns a boolean indicating whether an element with the specified value exists in the Set or not.
*/
public has(value: Static<T>): boolean {
return this.values.has(this.#encodeKey(value))
}
/**
* @returns the number of (unique) elements in Set.
*/
public get size(): number {
return this.values.size
}
// ---------------------------------------------------
// Encoder
// ---------------------------------------------------
#encodeKey(value: Static<T>) {
return Value.Hash(value)
}
// ---------------------------------------------------
// Assertions
// ---------------------------------------------------
/** Formats errors */
#formatError(errors: ValueError[]) {
return errors
.map((error) => `${error.message} ${error.path}`)
.join('. ')
.trim()
}
/** Asserts the key matches the value schema */
#assertValue(value: unknown): asserts value is Static<T> {
if (this.#valuecheck.Check(value)) return
throw new TypeSetError(this.#formatError([...this.#valuecheck.Errors(value)]))
}
}

View File

@@ -0,0 +1,39 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IsDate } from './date'
import { IsTime } from './time'
const DATE_TIME_SEPARATOR = /t|\s/i
/**
* `[ajv-formats]` ISO8601 DateTime
* @example `2020-12-12T20:20:40+00:00`
*/
export function IsDateTime(value: string, strictTimeZone?: boolean): boolean {
const dateTime: string[] = value.split(DATE_TIME_SEPARATOR)
return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1], strictTimeZone)
}

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

@@ -0,0 +1,44 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
function IsLeapYear(year: number): boolean {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
}
/**
* `[ajv-formats]` ISO8601 Date component
* @example `2020-12-12`
*/
export function IsDate(value: string): boolean {
const matches: string[] | null = DATE.exec(value)
if (!matches) return false
const year: number = +matches[1]
const month: number = +matches[2]
const day: number = +matches[3]
return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && IsLeapYear(year) ? 29 : DAYS[month])
}

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

@@ -0,0 +1,35 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
const Email = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i
/**
* `[ajv-formats]` Internet Email Address [RFC 5321, section 4.1.2.](http://tools.ietf.org/html/rfc5321#section-4.1.2)
* @example `user@domain.com`
*/
export function IsEmail(value: string): boolean {
return Email.test(value)
}

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

@@ -0,0 +1,36 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './date-time'
export * from './date'
export * from './email'
export * from './ipv4'
export * from './ipv6'
export * from './time'
export * from './url'
export * from './uuid'

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

@@ -0,0 +1,35 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
const IPv4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/
/**
* `[ajv-formats]` IPv4 address according to dotted-quad ABNF syntax as defined in [RFC 2673, section 3.2](http://tools.ietf.org/html/rfc2673#section-3.2)
* @example `192.168.0.1`
*/
export function IsIPv4(value: string): boolean {
return IPv4.test(value)
}

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

@@ -0,0 +1,36 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
const IPv6 =
/^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i
/**
* `[ajv-formats]` IPv6 address as defined in [RFC 2373, section 2.2](http://tools.ietf.org/html/rfc2373#section-2.2).
* @example `2001:0db8:85a3:0000:0000:8a2e:0370:7334`
*/
export function IsIPv6(value: string): boolean {
return IPv6.test(value)
}

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

@@ -0,0 +1,48 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i
/**
* `[ajv-formats]` ISO8601 Time component
* @example `20:20:39+00:00`
*/
export function IsTime(value: string, strictTimeZone?: boolean): boolean {
const matches: string[] | null = TIME.exec(value)
if (!matches) return false
const hr: number = +matches[1]
const min: number = +matches[2]
const sec: number = +matches[3]
const tz: string | undefined = matches[4]
const tzSign: number = matches[5] === '-' ? -1 : 1
const tzH: number = +(matches[6] || 0)
const tzM: number = +(matches[7] || 0)
if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false
if (hr <= 23 && min <= 59 && sec < 60) return true
const utcMin = min - tzM * tzSign
const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0)
return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61
}

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

@@ -0,0 +1,36 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
const Url =
/^(?:https?|wss?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu
/**
* `[ajv-formats:deprecated]` A uniform resource locator as defined in [RFC 1738](https://www.rfc-editor.org/rfc/rfc1738)
* @example `http://domain.com`
*/
export function IsUrl(value: string) {
return Url.test(value)
}

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

@@ -0,0 +1,35 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/format
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
const Uuid = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i
/**
* `[ajv-formats]` A Universally Unique Identifier as defined by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122).
* @example `9aa8a673-8590-4db2-9830-01755844f7c1`
*/
export function IsUuid(value: string): boolean {
return Uuid.test(value)
}

49
example/index.ts Normal file
View File

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

View File

@@ -0,0 +1,96 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { Static, Kind, TSchema, TObject, SchemaOptions, CreateType, TLiteral, TypeRegistry, ValueGuard, KindGuard, TUnion } from '@sinclair/typebox'
import { GetErrorFunction, SetErrorFunction } from 'src/errors/function'
import { Value } from '@sinclair/typebox/value'
// ------------------------------------------------------------------
// DiscriminatedUnionError
// ------------------------------------------------------------------
const errorFunction = GetErrorFunction()
// prettier-ignore
SetErrorFunction((parameter) => {
if (parameter.schema[Kind] !== 'DiscriminatedUnion') {
return errorFunction(parameter)
}
const union = parameter.schema as TDiscriminatedUnion
// Try generate error when value matches known discriminator literal
if (ValueGuard.IsObject(parameter.value) && union.discriminator in parameter.value) {
const variant = parameter.schema.anyOf.find((variant: TSchema) => union.discriminator in variant.properties
&& (variant.properties[union.discriminator] as TLiteral).const ===
(parameter.value as Record<PropertyKey, unknown>)[union.discriminator])
if (KindGuard.IsSchema(variant)) {
const literal = variant.properties[union.discriminator]
return `Invalid value for DiscriminatedUnion variant '${literal.const}'`
}
}
// Return generic error containing possible discriminator types.
const options = union.anyOf.map(object => object.properties[union.discriminator].const) as string[]
return `Expected value of ${options.map(option => `'${option}'`).join(', ')} for DiscriminatedUnion`
})
// ------------------------------------------------------------------
// TDiscriminatedUnionObject
//
// Constructs a base TObject type requiring 1 discriminator property
// ------------------------------------------------------------------
// prettier-ignore
type TDiscriminatedUnionProperties<Discriminator extends string> = {
[_ in Discriminator]: TLiteral
}
// prettier-ignore
type TDiscriminatedUnionObject<Discriminator extends string> = TObject<TDiscriminatedUnionProperties<Discriminator>>
// ------------------------------------------------------------------
// DiscriminatedUnion
// ------------------------------------------------------------------
// prettier-ignore
TypeRegistry.Set('DiscriminatedUnion', (schema: TDiscriminatedUnion, value) => {
return schema.anyOf.some(variant => Value.Check(variant, [], value))
})
// prettier-ignore
export interface TDiscriminatedUnion<Discriminator extends string = string, Types extends TObject[] = TObject[]> extends TSchema {
[Kind]: 'DiscriminatedUnion'
static: Static<TUnion<Types>>
discriminator: Discriminator
anyOf: Types
}
/** Creates a DiscriminatedUnion. */
// prettier-ignore
export function DiscriminatedUnion<Discriminator extends string, Types extends TDiscriminatedUnionObject<Discriminator>[]>(
discriminator: Discriminator, types: [...Types], options?: SchemaOptions
): TDiscriminatedUnion<Discriminator, Types> {
return CreateType({ [Kind]: 'DiscriminatedUnion', anyOf: types, discriminator }, options) as never
}

View File

@@ -0,0 +1,251 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import * as Type from '@sinclair/typebox'
// ------------------------------------------------------------------
// Schematics
// ------------------------------------------------------------------
const IsExact = (value: unknown, expect: unknown) => value === expect
const IsSValue = (value: unknown): value is SValue => Type.ValueGuard.IsString(value) || Type.ValueGuard.IsNumber(value) || Type.ValueGuard.IsBoolean(value)
const IsSEnum = (value: unknown): value is SEnum => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.enum) && value.enum.every((value) => IsSValue(value))
const IsSAllOf = (value: unknown): value is SAllOf => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.allOf)
const IsSAnyOf = (value: unknown): value is SAnyOf => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.anyOf)
const IsSOneOf = (value: unknown): value is SOneOf => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsArray(value.oneOf)
const IsSTuple = (value: unknown): value is STuple => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'array') && Type.ValueGuard.IsArray(value.items)
const IsSArray = (value: unknown): value is SArray => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'array') && !Type.ValueGuard.IsArray(value.items) && Type.ValueGuard.IsObject(value.items)
const IsSConst = (value: unknown): value is SConst => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsObject(value['const'])
const IsSString = (value: unknown): value is SString => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'string')
const IsSRef = (value: unknown): value is SRef => Type.ValueGuard.IsObject(value) && Type.ValueGuard.IsString(value.$ref)
const IsSNumber = (value: unknown): value is SNumber => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'number')
const IsSInteger = (value: unknown): value is SInteger => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'integer')
const IsSBoolean = (value: unknown): value is SBoolean => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'boolean')
const IsSNull = (value: unknown): value is SBoolean => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'null')
const IsSProperties = (value: unknown): value is SProperties => Type.ValueGuard.IsObject(value)
// prettier-ignore
const IsSObject = (value: unknown): value is SObject => Type.ValueGuard.IsObject(value) && IsExact(value.type, 'object') && IsSProperties(value.properties) && (value.required === undefined || Type.ValueGuard.IsArray(value.required) && value.required.every((value: unknown) => Type.ValueGuard.IsString(value)))
type SValue = string | number | boolean
type SEnum = Readonly<{ enum: readonly SValue[] }>
type SAllOf = Readonly<{ allOf: readonly unknown[] }>
type SAnyOf = Readonly<{ anyOf: readonly unknown[] }>
type SOneOf = Readonly<{ oneOf: readonly unknown[] }>
type SProperties = Record<PropertyKey, unknown>
type SObject = Readonly<{ type: 'object'; properties: SProperties; required?: readonly string[] }>
type STuple = Readonly<{ type: 'array'; items: readonly unknown[] }>
type SArray = Readonly<{ type: 'array'; items: unknown }>
type SConst = Readonly<{ const: SValue }>
type SRef = Readonly<{ $ref: string }>
type SString = Readonly<{ type: 'string' }>
type SNumber = Readonly<{ type: 'number' }>
type SInteger = Readonly<{ type: 'integer' }>
type SBoolean = Readonly<{ type: 'boolean' }>
type SNull = Readonly<{ type: 'null' }>
// ------------------------------------------------------------------
// FromRest
// ------------------------------------------------------------------
// prettier-ignore
type TFromRest<T extends readonly unknown[], Acc extends Type.TSchema[] = []> = (
T extends readonly [infer L extends unknown, ...infer R extends unknown[]]
? TFromSchema<L> extends infer S extends Type.TSchema
? TFromRest<R, [...Acc, S]>
: TFromRest<R, [...Acc]>
: Acc
)
function FromRest<T extends readonly unknown[]>(T: T): TFromRest<T> {
return T.map((L) => FromSchema(L)) as never
}
// ------------------------------------------------------------------
// FromEnumRest
// ------------------------------------------------------------------
// prettier-ignore
type TFromEnumRest<T extends readonly SValue[], Acc extends Type.TSchema[] = []> = (
T extends readonly [infer L extends SValue, ...infer R extends SValue[]]
? TFromEnumRest<R, [...Acc, Type.TLiteral<L>]>
: Acc
)
function FromEnumRest<T extends readonly SValue[]>(T: T): TFromEnumRest<T> {
return T.map((L) => Type.Literal(L)) as never
}
// ------------------------------------------------------------------
// AllOf
// ------------------------------------------------------------------
// prettier-ignore
type TFromAllOf<T extends SAllOf> = (
TFromRest<T['allOf']> extends infer Rest extends Type.TSchema[]
? Type.TIntersectEvaluated<Rest>
: Type.TNever
)
function FromAllOf<T extends SAllOf>(T: T): TFromAllOf<T> {
return Type.IntersectEvaluated(FromRest(T.allOf), T)
}
// ------------------------------------------------------------------
// AnyOf
// ------------------------------------------------------------------
// prettier-ignore
type TFromAnyOf<T extends SAnyOf> = (
TFromRest<T['anyOf']> extends infer Rest extends Type.TSchema[]
? Type.TUnionEvaluated<Rest>
: Type.TNever
)
function FromAnyOf<T extends SAnyOf>(T: T): TFromAnyOf<T> {
return Type.UnionEvaluated(FromRest(T.anyOf), T)
}
// ------------------------------------------------------------------
// OneOf
// ------------------------------------------------------------------
// prettier-ignore
type TFromOneOf<T extends SOneOf> = (
TFromRest<T['oneOf']> extends infer Rest extends Type.TSchema[]
? Type.TUnionEvaluated<Rest>
: Type.TNever
)
function FromOneOf<T extends SOneOf>(T: T): TFromOneOf<T> {
return Type.UnionEvaluated(FromRest(T.oneOf), T)
}
// ------------------------------------------------------------------
// Enum
// ------------------------------------------------------------------
// prettier-ignore
type TFromEnum<T extends SEnum> = (
TFromEnumRest<T['enum']> extends infer Elements extends Type.TSchema[]
? Type.TUnionEvaluated<Elements>
: Type.TNever
)
function FromEnum<T extends SEnum>(T: T): TFromEnum<T> {
return Type.UnionEvaluated(FromEnumRest(T.enum))
}
// ------------------------------------------------------------------
// Tuple
// ------------------------------------------------------------------
// prettier-ignore
type TFromTuple<T extends STuple> = (
TFromRest<T['items']> extends infer Elements extends Type.TSchema[]
? Type.TTuple<Elements>
: Type.TTuple<[]>
)
// prettier-ignore
function FromTuple<T extends STuple>(T: T): TFromTuple<T> {
return Type.Tuple(FromRest(T.items), T) as never
}
// ------------------------------------------------------------------
// Array
// ------------------------------------------------------------------
// prettier-ignore
type TFromArray<T extends SArray> = (
TFromSchema<T['items']> extends infer Items extends Type.TSchema
? Type.TArray<Items>
: Type.TArray<Type.TUnknown>
)
// prettier-ignore
function FromArray<T extends SArray>(T: T): TFromArray<T> {
return Type.Array(FromSchema(T.items), T) as never
}
// ------------------------------------------------------------------
// Const
// ------------------------------------------------------------------
// prettier-ignore
type TFromConst<T extends SConst> = (
Type.Ensure<Type.TLiteral<T['const']>>
)
function FromConst<T extends SConst>(T: T) {
return Type.Literal(T.const, T)
}
// ------------------------------------------------------------------
// Ref
// ------------------------------------------------------------------
// prettier-ignore
type TFromRef<T extends SRef> = (
Type.Ensure<Type.TRef<T['$ref']>>
)
function FromRef<T extends SRef>(T: T) {
return Type.Ref(T['$ref'])
}
// ------------------------------------------------------------------
// Object
// ------------------------------------------------------------------
type TFromPropertiesIsOptional<K extends PropertyKey, R extends string | unknown> = unknown extends R ? true : K extends R ? false : true
// prettier-ignore
type TFromProperties<T extends SProperties, R extends string | unknown> = Type.Evaluate<{
-readonly [K in keyof T]: TFromPropertiesIsOptional<K, R> extends true
? Type.TOptional<TFromSchema<T[K]>>
: TFromSchema<T[K]>
}>
// prettier-ignore
type TFromObject<T extends SObject> = (
TFromProperties<T['properties'], Exclude<T['required'], undefined>[number]> extends infer Properties extends Type.TProperties
? Type.TObject<Properties>
: Type.TObject<{}>
)
function FromObject<T extends SObject>(T: T): TFromObject<T> {
const properties = globalThis.Object.getOwnPropertyNames(T.properties).reduce((Acc, K) => {
return { ...Acc, [K]: T.required && T.required.includes(K) ? FromSchema(T.properties[K]) : Type.Optional(FromSchema(T.properties[K])) }
}, {} as Type.TProperties)
return Type.Object(properties, T) as never
}
// ------------------------------------------------------------------
// FromSchema
// ------------------------------------------------------------------
// prettier-ignore
export type TFromSchema<T> = (
T extends SAllOf ? TFromAllOf<T> :
T extends SAnyOf ? TFromAnyOf<T> :
T extends SOneOf ? TFromOneOf<T> :
T extends SEnum ? TFromEnum<T> :
T extends SObject ? TFromObject<T> :
T extends STuple ? TFromTuple<T> :
T extends SArray ? TFromArray<T> :
T extends SConst ? TFromConst<T> :
T extends SRef ? TFromRef<T> :
T extends SString ? Type.TString :
T extends SNumber ? Type.TNumber :
T extends SInteger ? Type.TInteger :
T extends SBoolean ? Type.TBoolean :
T extends SNull ? Type.TNull :
Type.TUnknown
)
/** Parses a TypeBox type from raw JsonSchema */
export function FromSchema<T>(T: T): TFromSchema<T> {
// prettier-ignore
return (
IsSAllOf(T) ? FromAllOf(T) :
IsSAnyOf(T) ? FromAnyOf(T) :
IsSOneOf(T) ? FromOneOf(T) :
IsSEnum(T) ? FromEnum(T) :
IsSObject(T) ? FromObject(T) :
IsSTuple(T) ? FromTuple(T) :
IsSArray(T) ? FromArray(T) :
IsSConst(T) ? FromConst(T) :
IsSRef(T) ? FromRef(T) :
IsSString(T) ? Type.String(T) :
IsSNumber(T) ? Type.Number(T) :
IsSInteger(T) ? Type.Integer(T) :
IsSBoolean(T) ? Type.Boolean(T) :
IsSNull(T) ? Type.Null(T) :
Type.Unknown(T || {})
) as never
}

View File

@@ -0,0 +1,35 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './discriminated-union'
export * from './from-schema'
export * from './options'
export * from './partial-deep'
export * from './recursive-map'
export * from './union-enum'
export * from './union-oneof'

View File

@@ -0,0 +1,40 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TSchema, CloneType } from '@sinclair/typebox'
// prettier-ignore
export type TOptions<Type extends TSchema, Options extends Record<PropertyKey, unknown>> = (
Type & Options
)
/** `[Prototype]` Augments a schema with additional generics aware properties */
// prettier-ignore
export function Options<Type extends TSchema, Options extends Record<PropertyKey, unknown>>(type: Type, options: Options): TOptions<Type, Options> {
return CloneType(type, options) as never
}

View File

@@ -0,0 +1,68 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeGuard, Type, TSchema, TIntersect, TUnion, TObject, TPartial, TProperties, Evaluate } from '@sinclair/typebox'
// -------------------------------------------------------------------------------------
// TPartialDeepProperties
// -------------------------------------------------------------------------------------
export type TPartialDeepProperties<T extends TProperties> = {
[K in keyof T]: TPartialDeep<T[K]>
}
function PartialDeepProperties<T extends TProperties>(properties: T): TPartialDeepProperties<T> {
return Object.getOwnPropertyNames(properties).reduce((acc, key) => {
return {...acc, [key]: PartialDeep(properties[key])}
}, {}) as never
}
// -------------------------------------------------------------------------------------
// TPartialDeepRest
// -------------------------------------------------------------------------------------
export type TPartialDeepRest<T extends TSchema[], Acc extends TSchema[] = []> = (
T extends [infer L extends TSchema, ...infer R extends TSchema[]]
? TPartialDeepRest<R, [...Acc, TPartialDeep<L>]>
: Acc
)
function PartialDeepRest<T extends TSchema[]>(rest: [...T]): TPartialDeepRest<T> {
return rest.map(schema => PartialDeep(schema)) as never
}
// -------------------------------------------------------------------------------------
// TPartialDeep
// -------------------------------------------------------------------------------------
export type TPartialDeep<T extends TSchema> =
T extends TIntersect<infer S> ? TIntersect<TPartialDeepRest<S>> :
T extends TUnion<infer S> ? TUnion<TPartialDeepRest<S>> :
T extends TObject<infer S> ? TPartial<TObject<Evaluate<TPartialDeepProperties<S>>>> :
T
export function PartialDeep<T extends TSchema>(schema: T): TPartialDeep<T> {
return (
TypeGuard.IsIntersect(schema) ? Type.Intersect(PartialDeepRest(schema.allOf)) :
TypeGuard.IsUnion(schema) ? Type.Union(PartialDeepRest(schema.anyOf)) :
TypeGuard.IsObject(schema) ? Type.Partial(Type.Object(PartialDeepProperties(schema.properties))) :
schema
) as never
}

View File

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

View File

@@ -0,0 +1,153 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import * as Types from '@sinclair/typebox'
// ------------------------------------------------------------------
// Mapping: Functions and Type
// ------------------------------------------------------------------
export type TMappingFunction = (schema: Types.TSchema) => Types.TSchema
export interface TMappingType {
input: unknown
output: unknown
}
// ------------------------------------------------------------------
// Record Parameters
// ------------------------------------------------------------------
function GetRecordPattern(record: Types.TRecord): string {
return globalThis.Object.getOwnPropertyNames(record.patternProperties)[0]
}
function GetRecordKey(record: Types.TRecord): Types.TSchema {
const pattern = GetRecordPattern(record)
return (
pattern === Types.PatternStringExact ? Types.String() :
pattern === Types.PatternNumberExact ? Types.Number() :
pattern === Types.PatternBooleanExact ? Types.Boolean() :
Types.String({ pattern })
)
}
function GetRecordValue(record: Types.TRecord): Types.TSchema {
return record.patternProperties[GetRecordPattern(record)]
}
// ------------------------------------------------------------------
// Traversal
// ------------------------------------------------------------------
// prettier-ignore
type TApply<Type extends Types.TSchema, Func extends TMappingType,
Mapped = (Func & { input: Type })['output'],
Result = Mapped extends Types.TSchema ? Mapped : never
> = Result
// prettier-ignore
type TFromProperties<Properties extends Types.TProperties, Func extends TMappingType, Result extends Types.TProperties = {
[Key in keyof Properties]: TRecursiveMap<Properties[Key], Func>
}> = Result
function FromProperties(properties: Types.TProperties, func: TMappingFunction): Types.TProperties {
return globalThis.Object.getOwnPropertyNames(properties).reduce((result, key) => {
return {...result, [key]: RecursiveMap(properties[key], func) }
}, {})
}
// prettier-ignore
type TFromRest<Types extends Types.TSchema[], Func extends TMappingType, Result extends Types.TSchema[] = []> = (
Types extends [infer Left extends Types.TSchema, ...infer Right extends Types.TSchema[]]
? TFromRest<Right, Func, [...Result, TRecursiveMap<Left, Func>]>
: Result
)
function FromRest(types: Types.TSchema[], func: TMappingFunction): Types.TSchema[] {
return types.map(type => RecursiveMap(type, func))
}
// prettier-ignore
type TFromType<Type extends Types.TSchema, Func extends TMappingType, Result extends Types.TSchema = (
TApply<Type, Func>
)> = Result
function FromType(type: Types.TSchema, func: TMappingFunction): Types.TSchema {
return func(type)
}
// ------------------------------------------------------------------
// TRecursiveMap<Type, Mapping>
// ------------------------------------------------------------------
/** `[Prototype]` Applies a deep recursive map across the given type and sub types. */
// prettier-ignore
export type TRecursiveMap<Type extends Types.TSchema, Func extends TMappingType,
// Maps the Exterior Type
Exterior extends Types.TSchema = TFromType<Type, Func>,
// Maps the Interior Parameterized Types
Interior extends Types.TSchema = (
Exterior extends Types.TConstructor<infer Parameters extends Types.TSchema[], infer ReturnType extends Types.TSchema> ? Types.TConstructor<TFromRest<Parameters, Func>, TFromType<ReturnType, Func>> :
Exterior extends Types.TFunction<infer Parameters extends Types.TSchema[], infer ReturnType extends Types.TSchema> ? Types.TFunction<TFromRest<Parameters, Func>, TFromType<ReturnType, Func>> :
Exterior extends Types.TIntersect<infer Types extends Types.TSchema[]> ? Types.TIntersect<TFromRest<Types, Func>> :
Exterior extends Types.TUnion<infer Types extends Types.TSchema[]> ? Types.TUnion<TFromRest<Types, Func>> :
Exterior extends Types.TTuple<infer Types extends Types.TSchema[]> ? Types.TTuple<TFromRest<Types, Func>> :
Exterior extends Types.TArray<infer Type extends Types.TSchema> ? Types.TArray<TFromType<Type, Func>>:
Exterior extends Types.TAsyncIterator<infer Type extends Types.TSchema> ? Types.TAsyncIterator<TFromType<Type, Func>> :
Exterior extends Types.TIterator<infer Type extends Types.TSchema> ? Types.TIterator<TFromType<Type, Func>> :
Exterior extends Types.TPromise<infer Type extends Types.TSchema> ? Types.TPromise<TFromType<Type, Func>> :
Exterior extends Types.TObject<infer Properties extends Types.TProperties> ? Types.TObject<TFromProperties<Properties, Func>> :
Exterior extends Types.TRecord<infer Key extends Types.TSchema, infer Value extends Types.TSchema> ? Types.TRecordOrObject<TFromType<Key, Func>, TFromType<Value, Func>> :
Exterior
),
// Modifiers Derived from Exterior Type Mapping
IsOptional extends number = Exterior extends Types.TOptional<Types.TSchema> ? 1 : 0,
IsReadonly extends number = Exterior extends Types.TReadonly<Types.TSchema> ? 1 : 0,
Result extends Types.TSchema = (
[IsReadonly, IsOptional] extends [1, 1] ? Types.TReadonlyOptional<Interior> :
[IsReadonly, IsOptional] extends [0, 1] ? Types.TOptional<Interior> :
[IsReadonly, IsOptional] extends [1, 0] ? Types.TReadonly<Interior> :
Interior
)
> = Result
/** `[Prototype]` Applies a deep recursive map across the given type and sub types. */
// prettier-ignore
export function RecursiveMap(type: Types.TSchema, func: TMappingFunction): Types.TSchema {
// Maps the Exterior Type
const exterior = Types.CloneType(FromType(type, func), type)
// Maps the Interior Parameterized Types
const interior = (
Types.KindGuard.IsConstructor(type) ? Types.Constructor(FromRest(type.parameters, func), FromType(type.returns, func), exterior) :
Types.KindGuard.IsFunction(type) ? Types.Function(FromRest(type.parameters, func), FromType(type.returns, func), exterior) :
Types.KindGuard.IsIntersect(type) ? Types.Intersect(FromRest(type.allOf, func), exterior) :
Types.KindGuard.IsUnion(type) ? Types.Union(FromRest(type.anyOf, func), exterior) :
Types.KindGuard.IsTuple(type) ? Types.Tuple(FromRest(type.items || [], func), exterior) :
Types.KindGuard.IsArray(type) ? Types.Array(FromType(type.items, func), exterior) :
Types.KindGuard.IsAsyncIterator(type) ? Types.AsyncIterator(FromType(type.items, func), exterior) :
Types.KindGuard.IsIterator(type) ? Types.Iterator(FromType(type.items, func), exterior) :
Types.KindGuard.IsPromise(type) ? Types.Promise(FromType(type.items, func), exterior) :
Types.KindGuard.IsObject(type) ? Types.Object(FromProperties(type.properties, func), exterior) :
Types.KindGuard.IsRecord(type) ? Types.Record(FromType(GetRecordKey(type), func), FromType(GetRecordValue(type), func), exterior) :
Types.CloneType(exterior, exterior)
)
// Modifiers Derived from Exterior Type Mapping
const isOptional = Types.KindGuard.IsOptional(exterior)
const isReadonly = Types.KindGuard.IsOptional(exterior)
return (
isOptional && isReadonly ? Types.ReadonlyOptional(interior) :
isOptional ? Types.Optional(interior) :
isReadonly ? Types.Readonly(interior) :
interior
)
}

View File

@@ -0,0 +1,49 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeRegistry, Kind, TSchema, SchemaOptions } from '@sinclair/typebox'
// -------------------------------------------------------------------------------------
// TUnionEnum
// -------------------------------------------------------------------------------------
export interface TUnionEnum<T extends (string | number)[]> extends TSchema {
[Kind]: 'UnionEnum'
static: T[number]
enum: T
}
// -------------------------------------------------------------------------------------
// UnionEnum
// -------------------------------------------------------------------------------------
/** `[Experimental]` Creates a Union type with a `enum` schema representation */
export function UnionEnum<T extends (string | number)[]>(values: [...T], options: SchemaOptions = {}) {
function UnionEnumCheck(schema: TUnionEnum<(string | number)[]>, value: unknown) {
return (typeof value === 'string' || typeof value === 'number') && schema.enum.includes(value)
}
if (!TypeRegistry.Has('UnionEnum')) TypeRegistry.Set('UnionEnum', UnionEnumCheck)
return { ...options, [Kind]: 'UnionEnum', enum: values } as TUnionEnum<T>
}

View File

@@ -0,0 +1,50 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeRegistry, Kind, Static, TSchema, SchemaOptions } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
// -------------------------------------------------------------------------------------
// TUnionOneOf
// -------------------------------------------------------------------------------------
export interface TUnionOneOf<T extends TSchema[]> extends TSchema {
[Kind]: 'UnionOneOf'
static: { [K in keyof T]: Static<T[K]> }[number]
oneOf: T
}
// -------------------------------------------------------------------------------------
// UnionOneOf
// -------------------------------------------------------------------------------------
/** `[Experimental]` Creates a Union type with a `oneOf` schema representation */
export function UnionOneOf<T extends TSchema[]>(oneOf: [...T], options: SchemaOptions = {}) {
function UnionOneOfCheck(schema: TUnionOneOf<TSchema[]>, value: unknown) {
return 1 === schema.oneOf.reduce((acc: number, schema: any) => (Value.Check(schema, value) ? acc + 1 : acc), 0)
}
if (!TypeRegistry.Has('UnionOneOf')) TypeRegistry.Set('UnionOneOf', UnionOneOfCheck)
return { ...options, [Kind]: 'UnionOneOf', oneOf } as TUnionOneOf<T>
}

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

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/standard-schema
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './standard'

View File

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

View File

@@ -0,0 +1,85 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/standard-schema
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { AssertError, Value, ValueError, ValueErrorType } from '@sinclair/typebox/value'
import { TSchema, StaticDecode, CloneType } from '@sinclair/typebox'
// ------------------------------------------------------------------
// StandardSchema
// ------------------------------------------------------------------
interface StandardResult<Output> {
value: Output
issues: ValueError[]
}
interface StandardSchema<Input = unknown, Output = Input> {
readonly "~standard": StandardSchemaProperties<Input, Output>
}
interface StandardSchemaProperties<Input = unknown, Output = Input> {
readonly version: 1
readonly vendor: 'TypeBox'
readonly validate: (value: unknown) => StandardResult<Output>
readonly types?: undefined
}
// ------------------------------------------------------------------
// Issues
// ------------------------------------------------------------------
// prettier-ignore
function CreateIssues(schema: TSchema, value: unknown, error: unknown): ValueError[] {
const isAssertError = error instanceof AssertError ? error : undefined
return !isAssertError
? [{errors: [], message: 'Unknown error', path: '/', type: ValueErrorType.Kind, schema, value }]
: [...isAssertError.Errors()]
}
// ------------------------------------------------------------------
// Validate
// ------------------------------------------------------------------
// prettier-ignore
function CreateValidator<Type extends TSchema>(schema: Type, references: TSchema[]): (value: unknown) => StandardResult<StaticDecode<Type>> {
return (value: unknown): StandardResult<StaticDecode<Type>> => {
try {
return { value: Value.Parse(schema, references, value), issues: [] }
} catch (error) {
return { value: undefined, issues: CreateIssues(schema, value, error) }
}
}
}
// ------------------------------------------------------------------
// StandardSchema
// ------------------------------------------------------------------
/** Augments a TypeBox type with the `~standard` validation interface. */
export type TStandardSchema<Input = unknown, Output = Input> = (
Input & StandardSchema<Input, Output>
)
/** Augments a TypeBox type with the `~standard` validation interface. */
export function StandardSchema<Type extends TSchema>(schema: Type, references: TSchema[] = []): TStandardSchema<Type, StaticDecode<Type>> {
const standard = { version: 1, vendor: 'TypeBox', validate: CreateValidator(schema, references) }
return Object.defineProperty(CloneType(schema), "~standard", {
enumerable: false,
value: standard
}) as never
}

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

@@ -0,0 +1,29 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/typedef
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './typedef'

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

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

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

@@ -0,0 +1,619 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/typedef
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { SetErrorFunction, DefaultErrorFunction } from '@sinclair/typebox/errors'
import * as Types from '@sinclair/typebox/type'
// --------------------------------------------------------------------------
// Metadata
// --------------------------------------------------------------------------
export interface Metadata {
[name: string]: any
}
// --------------------------------------------------------------------------
// TArray
// --------------------------------------------------------------------------
export interface TArray<T extends Types.TSchema = Types.TSchema> extends Types.TSchema {
[Types.Kind]: 'TypeDef:Array'
static: Types.Static<T, this['params']>[]
elements: T
}
// --------------------------------------------------------------------------
// TBoolean
// --------------------------------------------------------------------------
export interface TBoolean extends Types.TSchema {
[Types.Kind]: 'TypeDef:Boolean'
static: 'boolean'
type: 'boolean'
}
// --------------------------------------------------------------------------
// TUnion
// --------------------------------------------------------------------------
export type InferUnion<T extends TStruct[], D extends string, Index = string> =
T extends [infer L extends TStruct, ...infer R extends TStruct[]]
? Types.Evaluate<{ [_ in D]: Index } & Types.Static<L>> | InferUnion<R, D, Types.TIncrement<Types.Assert<Index, string>>>
: never
export interface TUnion<T extends TStruct[] = TStruct[], D extends string = string> extends Types.TSchema {
[Types.Kind]: 'TypeDef:Union'
static: InferUnion<T, D, '0'>
discriminator: D,
mapping: T
}
// --------------------------------------------------------------------------
// TEnum
// --------------------------------------------------------------------------
export interface TEnum<T extends string[] = string[]> extends Types.TSchema {
[Types.Kind]: 'TypeDef:Enum'
static: T[number]
enum: [...T]
}
// --------------------------------------------------------------------------
// TFloat32
// --------------------------------------------------------------------------
export interface TFloat32 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Float32'
type: 'float32'
static: number
}
// --------------------------------------------------------------------------
// TFloat64
// --------------------------------------------------------------------------
export interface TFloat64 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Float64'
type: 'float64'
static: number
}
// --------------------------------------------------------------------------
// TInt8
// --------------------------------------------------------------------------
export interface TInt8 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Int8'
type: 'int8'
static: number
}
// --------------------------------------------------------------------------
// TInt16
// --------------------------------------------------------------------------
export interface TInt16 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Int16'
type: 'int16'
static: number
}
// --------------------------------------------------------------------------
// TInt32
// --------------------------------------------------------------------------
export interface TInt32 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Int32'
type: 'int32'
static: number
}
// --------------------------------------------------------------------------
// TUint8
// --------------------------------------------------------------------------
export interface TUint8 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Uint8'
type: 'uint8'
static: number
}
// --------------------------------------------------------------------------
// TUint16
// --------------------------------------------------------------------------
export interface TUint16 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Uint16'
type: 'uint16'
static: number
}
// --------------------------------------------------------------------------
// TUint32
// --------------------------------------------------------------------------
export interface TUint32 extends Types.TSchema {
[Types.Kind]: 'TypeDef:Uint32'
type: 'uint32'
static: number
}
// --------------------------------------------------------------------------
// TProperties
// --------------------------------------------------------------------------
export type TFields = Record<string, Types.TSchema>
// --------------------------------------------------------------------------
// TRecord
// --------------------------------------------------------------------------
export interface TRecord<T extends Types.TSchema = Types.TSchema> extends Types.TSchema {
[Types.Kind]: 'TypeDef:Record'
static: Record<string, Types.Static<T, this['params']>>
values: T
}
// --------------------------------------------------------------------------
// TString
// --------------------------------------------------------------------------
export interface TString extends Types.TSchema {
[Types.Kind]: 'TypeDef:String'
type: 'string'
static: string
}
// --------------------------------------------------------------------------
// TStruct
// --------------------------------------------------------------------------
// used for structural type inference
type OptionalKeys<T extends TFields> = { [K in keyof T]: T[K] extends (Types.TOptional<T[K]>) ? T[K] : never }
type RequiredKeys<T extends TFields> = { [K in keyof T]: T[K] extends (Types.TOptional<T[K]>) ? never : T[K] }
// static inference
type ReadonlyOptionalPropertyKeys<T extends TFields> = { [K in keyof T]: T[K] extends Types.TReadonly<Types.TSchema> ? (T[K] extends Types.TOptional<T[K]> ? K : never) : never }[keyof T]
type ReadonlyPropertyKeys<T extends TFields> = { [K in keyof T]: T[K] extends Types.TReadonly<Types.TSchema> ? (T[K] extends Types.TOptional<T[K]> ? never : K) : never }[keyof T]
type OptionalPropertyKeys<T extends TFields> = { [K in keyof T]: T[K] extends Types.TOptional<Types.TSchema> ? (T[K] extends Types.TReadonly<T[K]> ? never : K) : never }[keyof T]
type RequiredPropertyKeys<T extends TFields> = keyof Omit<T, ReadonlyOptionalPropertyKeys<T> | ReadonlyPropertyKeys<T> | OptionalPropertyKeys<T>>
// prettier-ignore
type StructStaticProperties<T extends TFields, R extends Record<keyof any, unknown>> = Types.Evaluate<(
Readonly<Partial<Pick<R, ReadonlyOptionalPropertyKeys<T>>>> &
Readonly<Pick<R, ReadonlyPropertyKeys<T>>> &
Partial<Pick<R, OptionalPropertyKeys<T>>> &
Required<Pick<R, RequiredPropertyKeys<T>>>
)>
// prettier-ignore
export type StructStatic<T extends TFields, P extends unknown[]> = StructStaticProperties<T, {
[K in keyof T]: Static<T[K], P>
}>
export interface StructMetadata extends Metadata {
additionalProperties?: boolean
}
export interface TStruct<T extends TFields = TFields> extends Types.TSchema, StructMetadata {
[Types.Kind]: 'TypeDef:Struct'
static: StructStatic<T, this['params']>
optionalProperties: { [K in Types.Assert<OptionalKeys<T>, keyof T>]: T[K] }
properties: { [K in Types.Assert<RequiredKeys<T>, keyof T>]: T[K] }
}
// --------------------------------------------------------------------------
// TTimestamp
// --------------------------------------------------------------------------
export interface TTimestamp extends Types.TSchema {
[Types.Kind]: 'TypeDef:Timestamp'
type: 'timestamp'
static: string
}
// --------------------------------------------------------------------------
// Static
// --------------------------------------------------------------------------
export type Static<T extends Types.TSchema, P extends unknown[] = []> = Types.Static<T, P>
// --------------------------------------------------------------------------
// TimestampFormat
// --------------------------------------------------------------------------
export namespace TimestampFormat {
const DATE_TIME_SEPARATOR = /t|\s/i
const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i
const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
function IsLeapYear(year: number): boolean {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
}
function IsDate(str: string): boolean {
const matches: string[] | null = DATE.exec(str)
if (!matches) return false
const year: number = +matches[1]
const month: number = +matches[2]
const day: number = +matches[3]
return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && IsLeapYear(year) ? 29 : DAYS[month])
}
function IsTime(str: string, strictTimeZone?: boolean): boolean {
const matches: string[] | null = TIME.exec(str)
if (!matches) return false
const hr: number = +matches[1]
const min: number = +matches[2]
const sec: number = +matches[3]
const tz: string | undefined = matches[4]
const tzSign: number = matches[5] === '-' ? -1 : 1
const tzH: number = +(matches[6] || 0)
const tzM: number = +(matches[7] || 0)
if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false
if (hr <= 23 && min <= 59 && sec < 60) return true
const utcMin = min - tzM * tzSign
const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0)
return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61
}
function IsDateTime(value: string, strictTimeZone?: boolean): boolean {
const dateTime: string[] = value.split(DATE_TIME_SEPARATOR)
return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1], strictTimeZone)
}
export function Check(value: string): boolean {
return IsDateTime(value)
}
}
// --------------------------------------------------------------------------
// ValueCheck
// --------------------------------------------------------------------------
export class ValueCheckError extends Types.TypeBoxError {
constructor(public readonly schema: Types.TSchema) {
super('Unknown type')
}
}
export namespace ValueCheck {
// ------------------------------------------------------------------------
// Guards
// ------------------------------------------------------------------------
function IsObject(value: unknown): value is Record<keyof any, any> {
return typeof value === 'object' && value !== null && !globalThis.Array.isArray(value)
}
function IsArray(value: unknown): value is unknown[] {
return globalThis.Array.isArray(value)
}
function IsString(value: unknown): value is string {
return typeof value === 'string'
}
function IsInt(value: unknown, min: number, max: number): value is number {
return typeof value === 'number' && globalThis.Number.isInteger(value) && value >= min && value < max
}
// ------------------------------------------------------------------------
// Types
// ------------------------------------------------------------------------
function Array(schema: TArray, value: unknown): boolean {
return IsArray(value) && value.every(value => Visit(schema.elements, value))
}
function Boolean(schema: TBoolean, value: unknown): boolean {
return typeof value === 'boolean'
}
function Enum(schema: TEnum, value: unknown): boolean {
return typeof value === 'string' && schema.enum.includes(value)
}
function Float32(schema: TFloat32, value: unknown): boolean {
return typeof value === 'number'
}
function Float64(schema: TFloat64, value: unknown): boolean {
return typeof value === 'number'
}
function Int8(schema: TInt8, value: unknown): boolean {
return IsInt(value, -128, 127)
}
function Int16(schema: TInt16, value: unknown): boolean {
return IsInt(value, -32_768, 32_767)
}
function Int32(schema: TInt32, value: unknown): boolean {
return IsInt(value, -2_147_483_648, 2_147_483_647)
}
function Uint8(schema: TUint8, value: unknown): boolean {
return IsInt(value, 0, 255)
}
function Uint16(schema: TUint16, value: unknown): boolean {
return IsInt(value, 0, 65535)
}
function Uint32(schema: TUint32, value: unknown): boolean {
return IsInt(value, 0, 4_294_967_295)
}
function Record(schema: TRecord, value: unknown): boolean {
return IsObject(value) && globalThis.Object.getOwnPropertyNames(value).every(key => Visit(schema.values, value[key]))
}
function String(schema: TString, value: unknown): boolean {
return typeof value === 'string'
}
function Struct(schema: TStruct, value: unknown, descriminator?: string): boolean {
if (!IsObject(value)) return false
const optionalKeys = schema.optionalProperties === undefined ? [] : globalThis.Object.getOwnPropertyNames(schema.optionalProperties)
const requiredKeys = schema.properties === undefined ? [] : globalThis.Object.getOwnPropertyNames(schema.properties)
const unknownKeys = globalThis.Object.getOwnPropertyNames(value)
for (const requiredKey of requiredKeys) {
if (!(requiredKey in value)) return false
const requiredProperty = value[requiredKey]
const requiredSchema = (schema as any).properties[requiredKey]
if (!Visit(requiredSchema, requiredProperty)) return false
}
for (const optionalKey of optionalKeys) {
if (!(optionalKey in value)) continue
const optionalProperty = value[optionalKey]
const optionalSchema = (schema as any).properties[optionalKey]
if (!Visit(optionalSchema, optionalProperty)) return false
}
if (schema.additionalProperties === true) return true
const knownKeys = [...optionalKeys, ...requiredKeys]
for (const unknownKey of unknownKeys) if (!knownKeys.includes(unknownKey) && (descriminator !== undefined && unknownKey !== descriminator)) return false
for (const knownKey of knownKeys) if (!unknownKeys.includes(knownKey)) return false
return true
}
function Timestamp(schema: TString, value: unknown): boolean {
return IsString(value) && TimestampFormat.Check(value)
}
function Union(schema: TUnion, value: unknown): boolean {
if (!IsObject(value)) return false
if (!(schema.discriminator in value)) return false
if (!IsString(value[schema.discriminator])) return false
if (!(value[schema.discriminator] in schema.mapping)) return false
const struct = schema.mapping[value[schema.discriminator]] as TStruct
return Struct(struct, value, schema.discriminator)
}
function Visit(schema: Types.TSchema, value: unknown): boolean {
const anySchema = schema as any
switch (anySchema[Types.Kind]) {
case 'TypeDef:Array': return Array(anySchema, value)
case 'TypeDef:Boolean': return Boolean(anySchema, value)
case 'TypeDef:Union': return Union(anySchema, value)
case 'TypeDef:Enum': return Enum(anySchema, value)
case 'TypeDef:Float32': return Float32(anySchema, value)
case 'TypeDef:Float64': return Float64(anySchema, value)
case 'TypeDef:Int8': return Int8(anySchema, value)
case 'TypeDef:Int16': return Int16(anySchema, value)
case 'TypeDef:Int32': return Int32(anySchema, value)
case 'TypeDef:Uint8': return Uint8(anySchema, value)
case 'TypeDef:Uint16': return Uint16(anySchema, value)
case 'TypeDef:Uint32': return Uint32(anySchema, value)
case 'TypeDef:Record': return Record(anySchema, value)
case 'TypeDef:String': return String(anySchema, value)
case 'TypeDef:Struct': return Struct(anySchema, value)
case 'TypeDef:Timestamp': return Timestamp(anySchema, value)
default: throw new ValueCheckError(anySchema)
}
}
export function Check<T extends Types.TSchema>(schema: T, value: unknown): value is Types.Static<T> {
return Visit(schema, value)
}
}
// --------------------------------------------------------------------------
// TypeGuard
// --------------------------------------------------------------------------
export namespace TypeGuard {
// ------------------------------------------------------------------------
// Guards
// ------------------------------------------------------------------------
function IsObject(value: unknown): value is Record<keyof any, unknown> {
return typeof value === 'object'
}
function IsArray(value: unknown): value is unknown[] {
return globalThis.Array.isArray(value)
}
function IsOptionalBoolean(value: unknown): value is boolean | undefined {
return IsBoolean(value) || value === undefined
}
function IsBoolean(value: unknown): value is boolean {
return typeof value === 'boolean'
}
function IsString(value: unknown): value is string {
return typeof value === 'string'
}
// ------------------------------------------------------------------------
// Types
// ------------------------------------------------------------------------
export function TArray(schema: unknown): schema is TArray {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Array' && TSchema(schema['elements'])
}
export function TBoolean(schema: unknown): schema is TBoolean {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Boolean' && schema['type'] === 'boolean'
}
export function TUnion(schema: unknown): schema is TUnion {
if(!(IsObject(schema) && schema[Types.Kind] === 'TypeDef:Union' && IsString(schema['discriminator']) && IsObject(schema['mapping']))) return false
return globalThis.Object.getOwnPropertyNames(schema['mapping']).every(key => TSchema((schema['mapping'] as any)[key]))
}
export function TEnum(schema: unknown): schema is TEnum {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Enum' && IsArray(schema['enum']) && schema['enum'].every(item => IsString(item))
}
export function TFloat32(schema: unknown): schema is TFloat32 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Float32' && schema['type'] === 'float32'
}
export function TFloat64(schema: unknown): schema is TFloat64 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Float64' && schema['type'] === 'float64'
}
export function TInt8(schema: unknown): schema is TInt8 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Int8' && schema['type'] === 'int8'
}
export function TInt16(schema: unknown): schema is TInt16 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Int16' && schema['type'] === 'int16'
}
export function TInt32(schema: unknown): schema is TInt32 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Int32' && schema['type'] === 'int32'
}
export function TUint8(schema: unknown): schema is TUint8 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Uint8' && schema['type'] === 'uint8'
}
export function TUint16(schema: unknown): schema is TUint16 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Uint16' && schema['type'] === 'uint16'
}
export function TUint32(schema: unknown): schema is TUint32 {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Uint32' && schema['type'] === 'uint32'
}
export function TRecord(schema: unknown): schema is TRecord {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Record' && TSchema(schema['values'])
}
export function TString(schema: unknown): schema is TString {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:String' && schema['type'] === 'string'
}
export function TStruct(schema: unknown): schema is TStruct {
if(!(IsObject(schema) && schema[Types.Kind] === 'TypeDef:Struct' && IsOptionalBoolean(schema['additionalProperties']))) return false
const optionalProperties = schema['optionalProperties']
const requiredProperties = schema['properties']
const optionalCheck = optionalProperties === undefined || IsObject(optionalProperties) && globalThis.Object.getOwnPropertyNames(optionalProperties).every(key => TSchema(optionalProperties[key]))
const requiredCheck = requiredProperties === undefined || IsObject(requiredProperties) && globalThis.Object.getOwnPropertyNames(requiredProperties).every(key => TSchema(requiredProperties[key]))
return optionalCheck && requiredCheck
}
export function TTimestamp(schema: unknown): schema is TTimestamp {
return IsObject(schema) && schema[Types.Kind] === 'TypeDef:Timestamp' && schema['type'] === 'timestamp'
}
export function TKind(schema: unknown): schema is Types.TKind {
return IsObject(schema) && Types.Kind in schema && typeof (schema as any)[Types.Kind] === 'string' // TS 4.1.5: any required for symbol indexer
}
export function TSchema(schema: unknown): schema is Types.TSchema {
// prettier-ignore
return (
TArray(schema) ||
TBoolean(schema) ||
TUnion(schema) ||
TEnum(schema) ||
TFloat32(schema) ||
TFloat64(schema) ||
TInt8(schema) ||
TInt16(schema) ||
TInt32(schema) ||
TUint8(schema) ||
TUint16(schema) ||
TUint32(schema) ||
TRecord(schema) ||
TString(schema) ||
TStruct(schema) ||
TTimestamp(schema) ||
(TKind(schema) && Types.TypeRegistry.Has(schema[Types.Kind]))
)
}
}
// --------------------------------------------------------------------------
// TypeRegistry
// --------------------------------------------------------------------------
Types.TypeRegistry.Set<TArray>('TypeDef:Array', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TBoolean>('TypeDef:Boolean', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TUnion>('TypeDef:Union', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TInt8>('TypeDef:Int8', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TInt16>('TypeDef:Int16', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TInt32>('TypeDef:Int32', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TUint8>('TypeDef:Uint8', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TUint16>('TypeDef:Uint16', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TUint32>('TypeDef:Uint32', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TRecord>('TypeDef:Record', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TString>('TypeDef:String', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TStruct>('TypeDef:Struct', (schema, value) => ValueCheck.Check(schema, value))
Types.TypeRegistry.Set<TTimestamp>('TypeDef:Timestamp', (schema, value) => ValueCheck.Check(schema, value))
// --------------------------------------------------------------------------
// TypeSystemErrorFunction
// --------------------------------------------------------------------------
SetErrorFunction((error) => {
switch(error.schema[Types.Kind]) {
case 'TypeDef:Array': return 'Expected Array'
case 'TypeDef:Boolean': return 'Expected Boolean'
case 'TypeDef:Union': return 'Expected Union'
case 'TypeDef:Int8': return 'Expected Int8'
case 'TypeDef:Int16': return 'Expected Int16'
case 'TypeDef:Int32': return 'Expected Int32'
case 'TypeDef:Uint8': return 'Expected Uint8'
case 'TypeDef:Uint16': return 'Expected Uint16'
case 'TypeDef:Uint32': return 'Expected Uint32'
case 'TypeDef:Record': return 'Expected Record'
case 'TypeDef:String': return 'Expected String'
case 'TypeDef:Struct': return 'Expected Struct'
case 'TypeDef:Timestamp': return 'Expected Timestamp'
}
return DefaultErrorFunction(error)
})
// --------------------------------------------------------------------------
// TypeDefBuilder
// --------------------------------------------------------------------------
export class TypeDefBuilder {
// ------------------------------------------------------------------------
// Core
// ------------------------------------------------------------------------
protected Create(schema: Record<PropertyKey, any>, metadata: Record<keyof any, any>): any {
const keys = globalThis.Object.getOwnPropertyNames(metadata)
return keys.length > 0 ? { ...schema, metadata: { ...metadata } } : { ...schema }
}
/** [Standard] Removes compositing symbols from this schema */
public Strict<T extends Types.TSchema>(schema: T): T {
return JSON.parse(JSON.stringify(schema)) as T
}
// ------------------------------------------------------------------------
// Modifiers
// ------------------------------------------------------------------------
/** `[Standard]` Creates an Optional property */
public Optional<T extends Types.TSchema>(schema: T): Types.TOptional<T> {
return this.Optional(schema)
}
/** `[Standard]` Creates a Readonly property */
public Readonly<T extends Types.TSchema>(schema: T): Types.TReadonly<T> {
return this.Readonly(schema)
}
// ------------------------------------------------------------------------
// Types
// ------------------------------------------------------------------------
/** [Standard] Creates a Array type */
public Array<T extends Types.TSchema>(elements: T, metadata: Metadata = {}): TArray<T> {
return this.Create({ [Types.Kind]: 'TypeDef:Array', elements }, metadata)
}
/** [Standard] Creates a Boolean type */
public Boolean(metadata: Metadata = {}): TBoolean {
return this.Create({ [Types.Kind]: 'TypeDef:Boolean', type: 'boolean' }, metadata)
}
/** [Standard] Creates a Enum type */
public Enum<T extends string[]>(values: [...T], metadata: Metadata = {}): TEnum<T> {
return this.Create({[Types.Kind]: 'TypeDef:Enum', enum: values }, metadata )
}
/** [Standard] Creates a Float32 type */
public Float32(metadata: Metadata = {}): TFloat32 {
return this.Create({ [Types.Kind]: 'TypeDef:Float32', type: 'float32' }, metadata)
}
/** [Standard] Creates a Float64 type */
public Float64(metadata: Metadata = {}): TFloat64 {
return this.Create({ [Types.Kind]: 'TypeDef:Float64', type: 'float64' }, metadata)
}
/** [Standard] Creates a Int8 type */
public Int8(metadata: Metadata = {}): TInt8 {
return this.Create({ [Types.Kind]: 'TypeDef:Int8', type: 'int8' }, metadata)
}
/** [Standard] Creates a Int16 type */
public Int16(metadata: Metadata = {}): TInt16 {
return this.Create({ [Types.Kind]: 'TypeDef:Int16', type: 'int16' }, metadata)
}
/** [Standard] Creates a Int32 type */
public Int32(metadata: Metadata = {}): TInt32 {
return this.Create({ [Types.Kind]: 'TypeDef:Int32', type: 'int32' }, metadata)
}
/** [Standard] Creates a Uint8 type */
public Uint8(metadata: Metadata = {}): TUint8 {
return this.Create({ [Types.Kind]: 'TypeDef:Uint8', type: 'uint8' }, metadata)
}
/** [Standard] Creates a Uint16 type */
public Uint16(metadata: Metadata = {}): TUint16 {
return this.Create({ [Types.Kind]: 'TypeDef:Uint16', type: 'uint16' }, metadata)
}
/** [Standard] Creates a Uint32 type */
public Uint32(metadata: Metadata = {}): TUint32 {
return this.Create({ [Types.Kind]: 'TypeDef:Uint32', type: 'uint32' }, metadata)
}
/** [Standard] Creates a Record type */
public Record<T extends Types.TSchema>(values: T, metadata: Metadata = {}): TRecord<T> {
return this.Create({ [Types.Kind]: 'TypeDef:Record', values },metadata)
}
/** [Standard] Creates a String type */
public String(metadata: Metadata = {}): TString {
return this.Create({ [Types.Kind]: 'TypeDef:String', type: 'string' }, metadata)
}
/** [Standard] Creates a Struct type */
public Struct<T extends TFields>(fields: T, metadata: StructMetadata = {}): TStruct<T> {
const optionalProperties = globalThis.Object.getOwnPropertyNames(fields).reduce((acc, key) => (Types.TypeGuard.IsOptional(fields[key]) ? { ...acc, [key]: fields[key] } : { ...acc }), {} as TFields)
const properties = globalThis.Object.getOwnPropertyNames(fields).reduce((acc, key) => (Types.TypeGuard.IsOptional(fields[key]) ? { ...acc } : { ...acc, [key]: fields[key] }), {} as TFields)
const optionalObject = globalThis.Object.getOwnPropertyNames(optionalProperties).length > 0 ? { optionalProperties: optionalProperties } : {}
const requiredObject = globalThis.Object.getOwnPropertyNames(properties).length === 0 ? {} : { properties: properties }
return this.Create({ [Types.Kind]: 'TypeDef:Struct', ...requiredObject, ...optionalObject }, metadata)
}
/** [Standard] Creates a Union type */
public Union<T extends TStruct<TFields>[], D extends string = 'type'>(structs: [...T], discriminator?: D): TUnion<T, D> {
discriminator = (discriminator || 'type') as D
if (structs.length === 0) throw new Error('TypeDefBuilder: Union types must contain at least one struct')
const mapping = structs.reduce((acc, current, index) => ({ ...acc, [index.toString()]: current }), {})
return this.Create({ [Types.Kind]: 'TypeDef:Union', discriminator, mapping }, {})
}
/** [Standard] Creates a Timestamp type */
public Timestamp(metadata: Metadata = {}): TTimestamp {
return this.Create({ [Types.Kind]: 'TypeDef:Timestamp', type: 'timestamp' }, metadata)
}
}
/** JSON Type Definition Type Builder */
export const Type = new TypeDefBuilder()

128
hammer.mjs Normal file
View File

@@ -0,0 +1,128 @@
import * as Benchmark from './task/benchmark'
import * as Build from './task/build'
import * as Fs from 'fs'
// -------------------------------------------------------------------------------
// Clean
// -------------------------------------------------------------------------------
export async function clean() {
await folder('node_modules/typebox').delete()
await folder('target').delete()
}
// -------------------------------------------------------------------------------
// Format
// -------------------------------------------------------------------------------
export async function format() {
await shell('prettier --no-semi --single-quote --print-width 240 --trailing-comma all --write src test task example/index.ts')
}
// -------------------------------------------------------------------------------
// Start
// -------------------------------------------------------------------------------
export async function start() {
await shell(`hammer run example/index.ts --dist target/example`)
}
// -------------------------------------------------------------------------------
// Benchmark
// -------------------------------------------------------------------------------
export async function benchmark() {
await Benchmark.compression()
await Benchmark.measurement()
}
// -------------------------------------------------------------------------------
// Test
// -------------------------------------------------------------------------------
export async function test_typescript() {
for (const version of [
'4.9.5', '5.0.4', '5.1.3', '5.1.6',
'5.2.2', '5.3.2', '5.3.3', '5.4.3',
'5.4.5', '5.5.2', '5.5.3', '5.5.4',
'5.6.2', '5.6.3', '5.7.2', '5.7.3',
'5.8.2', '5.8.3',
]) {
await shell(`npm install typescript@${version} --no-save`)
await test_static()
}
}
export async function test_static() {
await shell(`tsc -v`)
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_check(target = 'target/build') {
const { version } = JSON.parse(Fs.readFileSync('package.json', 'utf8'))
await shell(`cd ${target} && attw sinclair-typebox-${version}.tgz`)
}
export async function build(target = 'target/build') {
await test()
await clean()
await Promise.all([
Build.Package.build(target),
Build.Esm.build(target),
Build.Cjs.build(target),
])
await folder(target).add('readme.md')
await folder(target).add('license')
await shell(`cd ${target} && npm pack`)
await build_check(target)
}
// -------------------------------------------------------------------------------
// Build To
// -------------------------------------------------------------------------------
export async function build_to(remote = 'target/remote', target = 'target/build') {
await clean()
await Promise.all([
Build.Package.build(target),
Build.Esm.build(target),
Build.Cjs.build(target),
])
await folder(target).add('readme.md')
await folder(target).add('license')
await shell(`cd ${target} && npm pack`)
const { version } = JSON.parse(Fs.readFileSync('package.json', 'utf8'))
const filename = `${target}/sinclair-typebox-${version}.tgz`
await folder(remote).add(filename)
}
// -------------------------------------------------------------------------------
// Install
// -------------------------------------------------------------------------------
export async function install_local() {
await clean()
await build('target/typebox')
await folder('node_modules').add('target/typebox')
}
// -------------------------------------------------------------
// Publish
// -------------------------------------------------------------
export async function publish(otp, target = 'target/build') {
const { version } = JSON.parse(Fs.readFileSync('package.json', 'utf8'))
if(version.includes('-dev')) throw Error(`package version should not include -dev specifier`)
await shell(`cd ${target} && npm publish sinclair-typebox-${version}.tgz --access=public --otp ${otp}`)
await shell(`git tag ${version}`)
await shell(`git push origin ${version}`)
}
// -------------------------------------------------------------
// Publish-Dev
// -------------------------------------------------------------
export async function publish_dev(otp, target = 'target/build') {
const { version } = JSON.parse(Fs.readFileSync(`${target}/package.json`, 'utf8'))
if(!version.includes('-dev')) throw Error(`development package version should include -dev specifier`)
await shell(`cd ${target} && npm publish sinclair-typebox-${version}.tgz --access=public --otp ${otp} --tag dev`)
}

25
license Normal file
View File

@@ -0,0 +1,25 @@
TypeBox
Json Schema Type Builder with Static Type Resolution for TypeScript
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

3393
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "@sinclair/typebox",
"version": "0.34.41",
"description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",
"json-schema",
"validate",
"typecheck"
],
"author": "sinclairzx81",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/sinclairzx81/typebox"
},
"scripts": {
"test:typescript": "hammer task test_typescript",
"test:static": "hammer task test_static",
"test:runtime": "hammer task test_runtime",
"install:local": "hammer task install_local",
"benchmark": "hammer task benchmark",
"build:to": "hammer task build_to",
"build": "hammer task build",
"test": "hammer task test",
"clean": "hammer task clean",
"format": "hammer task format",
"start": "hammer task start",
"publish": "hammer task publish",
"publish:dev": "hammer task publish_dev"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.13.2",
"@sinclair/hammer": "^0.18.0",
"@types/mocha": "^9.1.1",
"@types/node": "^22.13.5",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"mocha": "^11.1.0",
"prettier": "^2.7.1",
"typescript": "^5.9.2"
}
}

1863
readme.md Normal file

File diff suppressed because it is too large Load Diff

665
src/compiler/compiler.ts Normal file
View File

@@ -0,0 +1,665 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/compiler
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TransformEncode, TransformDecode, HasTransform, TransformDecodeCheckError, TransformEncodeCheckError } from '../value/transform/index'
import { Errors, ValueErrorIterator } from '../errors/index'
import { TypeSystemPolicy } from '../system/index'
import { TypeBoxError } from '../type/error/index'
import { Deref } from '../value/deref/index'
import { Hash } from '../value/hash/index'
import { Kind } from '../type/symbols/index'
import { TypeRegistry, FormatRegistry } from '../type/registry/index'
import { KeyOfPattern } from '../type/keyof/index'
import { ExtendsUndefinedCheck } from '../type/extends/extends-undefined'
import type { TSchema } from '../type/schema/index'
import type { TAsyncIterator } from '../type/async-iterator/index'
import type { TAny } from '../type/any/index'
import type { TArgument } from '../type/argument/index'
import type { TArray } from '../type/array/index'
import type { TBigInt } from '../type/bigint/index'
import type { TBoolean } from '../type/boolean/index'
import type { TDate } from '../type/date/index'
import type { TConstructor } from '../type/constructor/index'
import type { TFunction } from '../type/function/index'
import type { TImport } from '../type/module/index'
import type { TInteger } from '../type/integer/index'
import type { TIntersect } from '../type/intersect/index'
import type { TIterator } from '../type/iterator/index'
import type { TLiteral } from '../type/literal/index'
import { Never, type TNever } from '../type/never/index'
import type { TNot } from '../type/not/index'
import type { TNull } from '../type/null/index'
import type { TNumber } from '../type/number/index'
import type { TObject } from '../type/object/index'
import type { TPromise } from '../type/promise/index'
import type { TRecord } from '../type/record/index'
import { Ref, type TRef } from '../type/ref/index'
import type { TRegExp } from '../type/regexp/index'
import type { TTemplateLiteral } from '../type/template-literal/index'
import type { TThis } from '../type/recursive/index'
import type { TTuple } from '../type/tuple/index'
import type { TUnion } from '../type/union/index'
import type { TUnknown } from '../type/unknown/index'
import type { Static, StaticDecode, StaticEncode } from '../type/static/index'
import type { TString } from '../type/string/index'
import type { TSymbol } from '../type/symbol/index'
import type { TUndefined } from '../type/undefined/index'
import type { TUint8Array } from '../type/uint8array/index'
import type { TVoid } from '../type/void/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsArray, IsString, IsNumber, IsBigInt } from '../value/guard/index'
// ------------------------------------------------------------------
// TypeGuard
// ------------------------------------------------------------------
import { IsSchema } from '../type/guard/type'
// ------------------------------------------------------------------
// CheckFunction
// ------------------------------------------------------------------
export type CheckFunction = (value: unknown) => boolean
// ------------------------------------------------------------------
// TypeCheck
// ------------------------------------------------------------------
export class TypeCheck<T extends TSchema> {
private readonly hasTransform: boolean
constructor(private readonly schema: T, private readonly references: TSchema[], private readonly checkFunc: CheckFunction, private readonly code: string) {
this.hasTransform = HasTransform(schema, references)
}
/** Returns the generated assertion code used to validate this type. */
public Code(): string {
return this.code
}
/** Returns the schema type used to validate */
public Schema(): T {
return this.schema
}
/** Returns reference types used to validate */
public References(): TSchema[] {
return this.references
}
/** Returns an iterator for each error in this value. */
public Errors(value: unknown): ValueErrorIterator {
return Errors(this.schema, this.references, value)
}
/** Returns true if the value matches the compiled type. */
public Check(value: unknown): value is Static<T> {
return this.checkFunc(value)
}
/** Decodes a value or throws if error */
public Decode<Static = StaticDecode<T>, Result extends Static = Static>(value: unknown): Result {
if (!this.checkFunc(value)) throw new TransformDecodeCheckError(this.schema, value, this.Errors(value).First()!)
return (this.hasTransform ? TransformDecode(this.schema, this.references, value) : value) as never
}
/** Encodes a value or throws if error */
public Encode<Static = StaticEncode<T>, Result extends Static = Static>(value: unknown): Result {
const encoded = this.hasTransform ? TransformEncode(this.schema, this.references, value) : value
if (!this.checkFunc(encoded)) throw new TransformEncodeCheckError(this.schema, value, this.Errors(value).First()!)
return encoded as never
}
}
// ------------------------------------------------------------------
// Character
// ------------------------------------------------------------------
namespace Character {
export function DollarSign(code: number) {
return code === 36
}
export function IsUnderscore(code: number) {
return code === 95
}
export function IsAlpha(code: number) {
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122)
}
export function IsNumeric(code: number) {
return code >= 48 && code <= 57
}
}
// ------------------------------------------------------------------
// MemberExpression
// ------------------------------------------------------------------
namespace MemberExpression {
function IsFirstCharacterNumeric(value: string) {
if (value.length === 0) return false
return Character.IsNumeric(value.charCodeAt(0))
}
function IsAccessor(value: string) {
if (IsFirstCharacterNumeric(value)) return false
for (let i = 0; i < value.length; i++) {
const code = value.charCodeAt(i)
const check = Character.IsAlpha(code) || Character.IsNumeric(code) || Character.DollarSign(code) || Character.IsUnderscore(code)
if (!check) return false
}
return true
}
function EscapeHyphen(key: string) {
return key.replace(/'/g, "\\'")
}
export function Encode(object: string, key: string) {
return IsAccessor(key) ? `${object}.${key}` : `${object}['${EscapeHyphen(key)}']`
}
}
// ------------------------------------------------------------------
// Identifier
// ------------------------------------------------------------------
namespace Identifier {
export function Encode($id: string) {
const buffer: string[] = []
for (let i = 0; i < $id.length; i++) {
const code = $id.charCodeAt(i)
if (Character.IsNumeric(code) || Character.IsAlpha(code)) {
buffer.push($id.charAt(i))
} else {
buffer.push(`_${code}_`)
}
}
return buffer.join('').replace(/__/g, '_')
}
}
// ------------------------------------------------------------------
// LiteralString
// ------------------------------------------------------------------
namespace LiteralString {
export function Escape(content: string) {
return content.replace(/'/g, "\\'")
}
}
// ------------------------------------------------------------------
// Errors
// ------------------------------------------------------------------
export class TypeCompilerUnknownTypeError extends TypeBoxError {
constructor(public readonly schema: TSchema) {
super('Unknown type')
}
}
export class TypeCompilerTypeGuardError extends TypeBoxError {
constructor(public readonly schema: TSchema) {
super('Preflight validation check failed to guard for the given schema')
}
}
// ------------------------------------------------------------------
// Policy
// ------------------------------------------------------------------
export namespace Policy {
export function IsExactOptionalProperty(value: string, key: string, expression: string) {
return TypeSystemPolicy.ExactOptionalPropertyTypes ? `('${key}' in ${value} ? ${expression} : true)` : `(${MemberExpression.Encode(value, key)} !== undefined ? ${expression} : true)`
}
export function IsObjectLike(value: string): string {
return !TypeSystemPolicy.AllowArrayObject ? `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}))` : `(typeof ${value} === 'object' && ${value} !== null)`
}
export function IsRecordLike(value: string): string {
return !TypeSystemPolicy.AllowArrayObject
? `(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))`
}
export function IsNumberLike(value: string): string {
return TypeSystemPolicy.AllowNaN ? `typeof ${value} === 'number'` : `Number.isFinite(${value})`
}
export function IsVoidLike(value: string): string {
return TypeSystemPolicy.AllowNullVoid ? `(${value} === undefined || ${value} === null)` : `${value} === undefined`
}
}
// ------------------------------------------------------------------
// TypeCompiler
// ------------------------------------------------------------------
export type TypeCompilerLanguageOption = 'typescript' | 'javascript'
export interface TypeCompilerCodegenOptions {
language?: TypeCompilerLanguageOption
}
/** Compiles Types for Runtime Type Checking */
export namespace TypeCompiler {
// ----------------------------------------------------------------
// Guards
// ----------------------------------------------------------------
function IsAnyOrUnknown(schema: TSchema) {
return schema[Kind] === 'Any' || schema[Kind] === 'Unknown'
}
// ----------------------------------------------------------------
// Types
// ----------------------------------------------------------------
function* FromAny(schema: TAny, references: TSchema[], value: string): IterableIterator<string> {
yield 'true'
}
function* FromArgument(schema: TArgument, references: TSchema[], value: string): IterableIterator<string> {
yield 'true'
}
function* FromArray(schema: TArray, references: TSchema[], value: string): IterableIterator<string> {
yield `Array.isArray(${value})`
const [parameter, accumulator] = [CreateParameter('value', 'any'), CreateParameter('acc', 'number')]
if (IsNumber(schema.maxItems)) yield `${value}.length <= ${schema.maxItems}`
if (IsNumber(schema.minItems)) yield `${value}.length >= ${schema.minItems}`
const elementExpression = CreateExpression(schema.items, references, 'value')
yield `${value}.every((${parameter}) => ${elementExpression})`
if (IsSchema(schema.contains) || IsNumber(schema.minContains) || IsNumber(schema.maxContains)) {
const containsSchema = IsSchema(schema.contains) ? schema.contains : Never()
const checkExpression = CreateExpression(containsSchema, references, 'value')
const checkMinContains = IsNumber(schema.minContains) ? [`(count >= ${schema.minContains})`] : []
const checkMaxContains = IsNumber(schema.maxContains) ? [`(count <= ${schema.maxContains})`] : []
const checkCount = `const count = value.reduce((${accumulator}, ${parameter}) => ${checkExpression} ? acc + 1 : acc, 0)`
const check = [`(count > 0)`, ...checkMinContains, ...checkMaxContains].join(' && ')
yield `((${parameter}) => { ${checkCount}; return ${check}})(${value})`
}
if (schema.uniqueItems === true) {
const check = `const hashed = hash(element); if(set.has(hashed)) { return false } else { set.add(hashed) } } return true`
const block = `const set = new Set(); for(const element of value) { ${check} }`
yield `((${parameter}) => { ${block} )(${value})`
}
}
function* FromAsyncIterator(schema: TAsyncIterator, references: TSchema[], value: string): IterableIterator<string> {
yield `(typeof value === 'object' && Symbol.asyncIterator in ${value})`
}
function* FromBigInt(schema: TBigInt, references: TSchema[], value: string): IterableIterator<string> {
yield `(typeof ${value} === 'bigint')`
if (IsBigInt(schema.exclusiveMaximum)) yield `${value} < BigInt(${schema.exclusiveMaximum})`
if (IsBigInt(schema.exclusiveMinimum)) yield `${value} > BigInt(${schema.exclusiveMinimum})`
if (IsBigInt(schema.maximum)) yield `${value} <= BigInt(${schema.maximum})`
if (IsBigInt(schema.minimum)) yield `${value} >= BigInt(${schema.minimum})`
if (IsBigInt(schema.multipleOf)) yield `(${value} % BigInt(${schema.multipleOf})) === 0`
}
function* FromBoolean(schema: TBoolean, references: TSchema[], value: string): IterableIterator<string> {
yield `(typeof ${value} === 'boolean')`
}
function* FromConstructor(schema: TConstructor, references: TSchema[], value: string): IterableIterator<string> {
yield* Visit(schema.returns, references, `${value}.prototype`)
}
function* FromDate(schema: TDate, references: TSchema[], value: string): IterableIterator<string> {
yield `(${value} instanceof Date) && Number.isFinite(${value}.getTime())`
if (IsNumber(schema.exclusiveMaximumTimestamp)) yield `${value}.getTime() < ${schema.exclusiveMaximumTimestamp}`
if (IsNumber(schema.exclusiveMinimumTimestamp)) yield `${value}.getTime() > ${schema.exclusiveMinimumTimestamp}`
if (IsNumber(schema.maximumTimestamp)) yield `${value}.getTime() <= ${schema.maximumTimestamp}`
if (IsNumber(schema.minimumTimestamp)) yield `${value}.getTime() >= ${schema.minimumTimestamp}`
if (IsNumber(schema.multipleOfTimestamp)) yield `(${value}.getTime() % ${schema.multipleOfTimestamp}) === 0`
}
function* FromFunction(schema: TFunction, references: TSchema[], value: string): IterableIterator<string> {
yield `(typeof ${value} === 'function')`
}
function* FromImport(schema: TImport, references: TSchema[], value: string): IterableIterator<string> {
const members = globalThis.Object.getOwnPropertyNames(schema.$defs).reduce((result, key) => {
return [...result, schema.$defs[key as never] as TSchema]
}, [] as TSchema[])
yield* Visit(Ref(schema.$ref), [...references, ...members], value)
}
function* FromInteger(schema: TInteger, references: TSchema[], value: string): IterableIterator<string> {
yield `Number.isInteger(${value})`
if (IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}`
if (IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}`
if (IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}`
if (IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}`
if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0`
}
function* FromIntersect(schema: TIntersect, references: TSchema[], value: string): IterableIterator<string> {
const check1 = schema.allOf.map((schema: TSchema) => CreateExpression(schema, references, value)).join(' && ')
if (schema.unevaluatedProperties === false) {
const keyCheck = CreateVariable(`${new RegExp(KeyOfPattern(schema))};`)
const check2 = `Object.getOwnPropertyNames(${value}).every(key => ${keyCheck}.test(key))`
yield `(${check1} && ${check2})`
} else if (IsSchema(schema.unevaluatedProperties)) {
const keyCheck = CreateVariable(`${new RegExp(KeyOfPattern(schema))};`)
const check2 = `Object.getOwnPropertyNames(${value}).every(key => ${keyCheck}.test(key) || ${CreateExpression(schema.unevaluatedProperties, references, `${value}[key]`)})`
yield `(${check1} && ${check2})`
} else {
yield `(${check1})`
}
}
function* FromIterator(schema: TIterator, references: TSchema[], value: string): IterableIterator<string> {
yield `(typeof value === 'object' && Symbol.iterator in ${value})`
}
function* FromLiteral(schema: TLiteral, references: TSchema[], value: string): IterableIterator<string> {
if (typeof schema.const === 'number' || typeof schema.const === 'boolean') {
yield `(${value} === ${schema.const})`
} else {
yield `(${value} === '${LiteralString.Escape(schema.const)}')`
}
}
function* FromNever(schema: TNever, references: TSchema[], value: string): IterableIterator<string> {
yield `false`
}
function* FromNot(schema: TNot, references: TSchema[], value: string): IterableIterator<string> {
const expression = CreateExpression(schema.not, references, value)
yield `(!${expression})`
}
function* FromNull(schema: TNull, references: TSchema[], value: string): IterableIterator<string> {
yield `(${value} === null)`
}
function* FromNumber(schema: TNumber, references: TSchema[], value: string): IterableIterator<string> {
yield Policy.IsNumberLike(value)
if (IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}`
if (IsNumber(schema.exclusiveMinimum)) yield `${value} > ${schema.exclusiveMinimum}`
if (IsNumber(schema.maximum)) yield `${value} <= ${schema.maximum}`
if (IsNumber(schema.minimum)) yield `${value} >= ${schema.minimum}`
if (IsNumber(schema.multipleOf)) yield `(${value} % ${schema.multipleOf}) === 0`
}
function* FromObject(schema: TObject, references: TSchema[], value: string): IterableIterator<string> {
yield Policy.IsObjectLike(value)
if (IsNumber(schema.minProperties)) yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}`
if (IsNumber(schema.maxProperties)) yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}`
const knownKeys = Object.getOwnPropertyNames(schema.properties)
for (const knownKey of knownKeys) {
const memberExpression = MemberExpression.Encode(value, knownKey)
const property = schema.properties[knownKey]
if (schema.required && schema.required.includes(knownKey)) {
yield* Visit(property, references, memberExpression)
if (ExtendsUndefinedCheck(property) || IsAnyOrUnknown(property)) yield `('${knownKey}' in ${value})`
} else {
const expression = CreateExpression(property, references, memberExpression)
yield Policy.IsExactOptionalProperty(value, knownKey, expression)
}
}
if (schema.additionalProperties === false) {
if (schema.required && schema.required.length === knownKeys.length) {
yield `Object.getOwnPropertyNames(${value}).length === ${knownKeys.length}`
} else {
const keys = `[${knownKeys.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 = `[${knownKeys.map((key) => `'${key}'`).join(', ')}]`
yield `(Object.getOwnPropertyNames(${value}).every(key => ${keys}.includes(key) || ${expression}))`
}
}
function* FromPromise(schema: TPromise, references: TSchema[], value: string): IterableIterator<string> {
yield `${value} instanceof Promise`
}
function* FromRecord(schema: TRecord, references: TSchema[], value: string): IterableIterator<string> {
yield Policy.IsRecordLike(value)
if (IsNumber(schema.minProperties)) yield `Object.getOwnPropertyNames(${value}).length >= ${schema.minProperties}`
if (IsNumber(schema.maxProperties)) yield `Object.getOwnPropertyNames(${value}).length <= ${schema.maxProperties}`
const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0]
const variable = CreateVariable(`${new RegExp(patternKey)}`)
const check1 = CreateExpression(patternSchema, references, 'value')
const check2 = IsSchema(schema.additionalProperties) ? CreateExpression(schema.additionalProperties, references, value) : schema.additionalProperties === false ? 'false' : 'true'
const expression = `(${variable}.test(key) ? ${check1} : ${check2})`
yield `(Object.entries(${value}).every(([key, value]) => ${expression}))`
}
function* FromRef(schema: TRef, references: TSchema[], value: string): IterableIterator<string> {
const target = Deref(schema, references)
// 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.
if (state.functions.has(schema.$ref)) return yield `${CreateFunctionName(schema.$ref)}(${value})`
yield* Visit(target, references, value)
}
function* FromRegExp(schema: TRegExp, references: TSchema[], value: string): IterableIterator<string> {
const variable = CreateVariable(`${new RegExp(schema.source, schema.flags)};`)
yield `(typeof ${value} === 'string')`
if (IsNumber(schema.maxLength)) yield `${value}.length <= ${schema.maxLength}`
if (IsNumber(schema.minLength)) yield `${value}.length >= ${schema.minLength}`
yield `${variable}.test(${value})`
}
function* FromString(schema: TString, references: TSchema[], value: string): IterableIterator<string> {
yield `(typeof ${value} === 'string')`
if (IsNumber(schema.maxLength)) yield `${value}.length <= ${schema.maxLength}`
if (IsNumber(schema.minLength)) yield `${value}.length >= ${schema.minLength}`
if (schema.pattern !== undefined) {
const variable = CreateVariable(`${new RegExp(schema.pattern)};`)
yield `${variable}.test(${value})`
}
if (schema.format !== undefined) {
yield `format('${schema.format}', ${value})`
}
}
function* FromSymbol(schema: TSymbol, references: TSchema[], value: string): IterableIterator<string> {
yield `(typeof ${value} === 'symbol')`
}
function* FromTemplateLiteral(schema: TTemplateLiteral, references: TSchema[], value: string): IterableIterator<string> {
yield `(typeof ${value} === 'string')`
const variable = CreateVariable(`${new RegExp(schema.pattern)};`)
yield `${variable}.test(${value})`
}
function* FromThis(schema: TThis, references: TSchema[], value: string): IterableIterator<string> {
// Note: This types are assured to be hoisted prior to this call. Just yield the function.
yield `${CreateFunctionName(schema.$ref)}(${value})`
}
function* FromTuple(schema: TTuple, references: TSchema[], value: string): IterableIterator<string> {
yield `Array.isArray(${value})`
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], references, `${value}[${i}]`)
yield `${expression}`
}
}
function* FromUndefined(schema: TUndefined, references: TSchema[], value: string): IterableIterator<string> {
yield `${value} === undefined`
}
function* FromUnion(schema: TUnion, references: TSchema[], value: string): IterableIterator<string> {
const expressions = schema.anyOf.map((schema: TSchema) => CreateExpression(schema, references, value))
yield `(${expressions.join(' || ')})`
}
function* FromUint8Array(schema: TUint8Array, references: 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* FromUnknown(schema: TUnknown, references: TSchema[], value: string): IterableIterator<string> {
yield 'true'
}
function* FromVoid(schema: TVoid, references: TSchema[], value: string): IterableIterator<string> {
yield Policy.IsVoidLike(value)
}
function* FromKind(schema: TSchema, references: TSchema[], value: string): IterableIterator<string> {
const instance = state.instances.size
state.instances.set(instance, schema)
yield `kind('${schema[Kind]}', ${instance}, ${value})`
}
function* Visit(schema: TSchema, references: TSchema[], value: string, useHoisting: boolean = true): IterableIterator<string> {
const references_ = IsString(schema.$id) ? [...references, schema] : references
const schema_ = schema as any
// --------------------------------------------------------------
// Hoisting
// --------------------------------------------------------------
if (useHoisting && IsString(schema.$id)) {
const functionName = CreateFunctionName(schema.$id)
if (state.functions.has(functionName)) {
return yield `${functionName}(${value})`
} else {
// Note: In the case of cyclic types, we need to create a 'functions' record
// to prevent infinitely re-visiting the CreateFunction. Subsequent attempts
// to visit will be caught by the above condition.
state.functions.set(functionName, '<deferred>')
const functionCode = CreateFunction(functionName, schema, references, 'value', false)
state.functions.set(functionName, functionCode)
return yield `${functionName}(${value})`
}
}
switch (schema_[Kind]) {
case 'Any':
return yield* FromAny(schema_, references_, value)
case 'Argument':
return yield* FromArgument(schema_, references_, value)
case 'Array':
return yield* FromArray(schema_, references_, value)
case 'AsyncIterator':
return yield* FromAsyncIterator(schema_, references_, value)
case 'BigInt':
return yield* FromBigInt(schema_, references_, value)
case 'Boolean':
return yield* FromBoolean(schema_, references_, value)
case 'Constructor':
return yield* FromConstructor(schema_, references_, value)
case 'Date':
return yield* FromDate(schema_, references_, value)
case 'Function':
return yield* FromFunction(schema_, references_, value)
case 'Import':
return yield* FromImport(schema_, references_, value)
case 'Integer':
return yield* FromInteger(schema_, references_, value)
case 'Intersect':
return yield* FromIntersect(schema_, references_, value)
case 'Iterator':
return yield* FromIterator(schema_, references_, value)
case 'Literal':
return yield* FromLiteral(schema_, references_, value)
case 'Never':
return yield* FromNever(schema_, references_, value)
case 'Not':
return yield* FromNot(schema_, references_, value)
case 'Null':
return yield* FromNull(schema_, references_, value)
case 'Number':
return yield* FromNumber(schema_, references_, value)
case 'Object':
return yield* FromObject(schema_, references_, value)
case 'Promise':
return yield* FromPromise(schema_, references_, value)
case 'Record':
return yield* FromRecord(schema_, references_, value)
case 'Ref':
return yield* FromRef(schema_, references_, value)
case 'RegExp':
return yield* FromRegExp(schema_, references_, value)
case 'String':
return yield* FromString(schema_, references_, value)
case 'Symbol':
return yield* FromSymbol(schema_, references_, value)
case 'TemplateLiteral':
return yield* FromTemplateLiteral(schema_, references_, value)
case 'This':
return yield* FromThis(schema_, references_, value)
case 'Tuple':
return yield* FromTuple(schema_, references_, value)
case 'Undefined':
return yield* FromUndefined(schema_, references_, value)
case 'Union':
return yield* FromUnion(schema_, references_, value)
case 'Uint8Array':
return yield* FromUint8Array(schema_, references_, value)
case 'Unknown':
return yield* FromUnknown(schema_, references_, value)
case 'Void':
return yield* FromVoid(schema_, references_, value)
default:
if (!TypeRegistry.Has(schema_[Kind])) throw new TypeCompilerUnknownTypeError(schema)
return yield* FromKind(schema_, references_, value)
}
}
// ----------------------------------------------------------------
// Compiler State
// ----------------------------------------------------------------
// prettier-ignore
const state = {
language: 'javascript', // target language
functions: new Map<string, string>(), // local functions
variables: new Map<string, string>(), // local variables
instances: new Map<number, TSchema>() // exterior kind instances
}
// ----------------------------------------------------------------
// Compiler Factory
// ----------------------------------------------------------------
function CreateExpression(schema: TSchema, references: TSchema[], value: string, useHoisting: boolean = true): string {
return `(${[...Visit(schema, references, value, useHoisting)].join(' && ')})`
}
function CreateFunctionName($id: string) {
return `check_${Identifier.Encode($id)}`
}
function CreateVariable(expression: string) {
const variableName = `local_${state.variables.size}`
state.variables.set(variableName, `const ${variableName} = ${expression}`)
return variableName
}
function CreateFunction(name: string, schema: TSchema, references: TSchema[], value: string, useHoisting: boolean = true): string {
const [newline, pad] = ['\n', (length: number) => ''.padStart(length, ' ')]
const parameter = CreateParameter('value', 'any')
const returns = CreateReturns('boolean')
const expression = [...Visit(schema, references, value, useHoisting)].map((expression) => `${pad(4)}${expression}`).join(` &&${newline}`)
return `function ${name}(${parameter})${returns} {${newline}${pad(2)}return (${newline}${expression}${newline}${pad(2)})\n}`
}
function CreateParameter(name: string, type: string) {
const annotation = state.language === 'typescript' ? `: ${type}` : ''
return `${name}${annotation}`
}
function CreateReturns(type: string) {
return state.language === 'typescript' ? `: ${type}` : ''
}
// ----------------------------------------------------------------
// Compile
// ----------------------------------------------------------------
function Build<T extends TSchema>(schema: T, references: TSchema[], options: TypeCompilerCodegenOptions): string {
const functionCode = CreateFunction('check', schema, references, 'value') // will populate functions and variables
const parameter = CreateParameter('value', 'any')
const returns = CreateReturns('boolean')
const functions = [...state.functions.values()]
const variables = [...state.variables.values()]
// prettier-ignore
const checkFunction = IsString(schema.$id) // ensure top level schemas with $id's are hoisted
? `return function check(${parameter})${returns} {\n return ${CreateFunctionName(schema.$id)}(value)\n}`
: `return ${functionCode}`
return [...variables, ...functions, checkFunction].join('\n')
}
/** Generates the code used to assert this type and returns it as a string */
export function Code<T extends TSchema>(schema: T, references: TSchema[], options?: TypeCompilerCodegenOptions): string
/** Generates the code used to assert this type and returns it as a string */
export function Code<T extends TSchema>(schema: T, options?: TypeCompilerCodegenOptions): string
/** Generates the code used to assert this type and returns it as a string */
export function Code(...args: any[]) {
const defaults = { language: 'javascript' }
// prettier-ignore
const [schema, references, options] = (
args.length === 2 && IsArray(args[1]) ? [args[0], args[1], defaults] :
args.length === 2 && !IsArray(args[1]) ? [args[0], [], args[1]] :
args.length === 3 ? [args[0], args[1], args[2]] :
args.length === 1 ? [args[0], [], defaults] :
[null, [], defaults]
)
// compiler-reset
state.language = options.language
state.variables.clear()
state.functions.clear()
state.instances.clear()
if (!IsSchema(schema)) throw new TypeCompilerTypeGuardError(schema)
for (const schema of references) if (!IsSchema(schema)) throw new TypeCompilerTypeGuardError(schema)
return Build(schema, references, options)
}
/** Compiles a TypeBox type for optimal runtime type checking. Types must be valid TypeBox types of TSchema */
export function Compile<T extends TSchema>(schema: T, references: TSchema[] = []): TypeCheck<T> {
const generatedCode = Code(schema, references, { language: 'javascript' })
const compiledFunction = globalThis.Function('kind', 'format', 'hash', generatedCode)
const instances = new Map(state.instances)
function typeRegistryFunction(kind: string, instance: number, value: unknown) {
if (!TypeRegistry.Has(kind) || !instances.has(instance)) return false
const checkFunc = TypeRegistry.Get(kind)!
const schema = instances.get(instance)!
return checkFunc(schema, value)
}
function formatRegistryFunction(format: string, value: string) {
if (!FormatRegistry.Has(format)) return false
const checkFunc = FormatRegistry.Get(format)!
return checkFunc(value)
}
function hashFunction(value: unknown) {
return Hash(value)
}
const checkFunction = compiledFunction(typeRegistryFunction, formatRegistryFunction, hashFunction)
return new TypeCheck(schema, references, checkFunction, generatedCode)
}
}

30
src/compiler/index.ts Normal file
View File

@@ -0,0 +1,30 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/compiler
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export { ValueError, ValueErrorType, ValueErrorIterator } from '../errors/index'
export * from './compiler'

639
src/errors/errors.ts Normal file
View File

@@ -0,0 +1,639 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/errors
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeSystemPolicy } from '../system/index'
import { KeyOfPattern } from '../type/keyof/index'
import { TypeRegistry, FormatRegistry } from '../type/registry/index'
import { ExtendsUndefinedCheck } from '../type/extends/extends-undefined'
import { GetErrorFunction } from './function'
import { TypeBoxError } from '../type/error/index'
import { Deref } from '../value/deref/index'
import { Hash } from '../value/hash/index'
import { Check } from '../value/check/index'
import { Kind } from '../type/symbols/index'
import type { TSchema } from '../type/schema/index'
import type { TAsyncIterator } from '../type/async-iterator/index'
import type { TAny } from '../type/any/index'
import type { TArray } from '../type/array/index'
import type { TBigInt } from '../type/bigint/index'
import type { TBoolean } from '../type/boolean/index'
import type { TDate } from '../type/date/index'
import type { TConstructor } from '../type/constructor/index'
import type { TFunction } from '../type/function/index'
import type { TImport } from '../type/module/index'
import type { TInteger } from '../type/integer/index'
import type { TIntersect } from '../type/intersect/index'
import type { TIterator } from '../type/iterator/index'
import type { TLiteral } from '../type/literal/index'
import { Never, type TNever } from '../type/never/index'
import type { TNot } from '../type/not/index'
import type { TNull } from '../type/null/index'
import type { TNumber } from '../type/number/index'
import type { TObject } from '../type/object/index'
import type { TPromise } from '../type/promise/index'
import type { TRecord } from '../type/record/index'
import type { TRef } from '../type/ref/index'
import type { TRegExp } from '../type/regexp/index'
import type { TTemplateLiteral } from '../type/template-literal/index'
import type { TThis } from '../type/recursive/index'
import type { TTuple } from '../type/tuple/index'
import type { TUnion } from '../type/union/index'
import type { TUnknown } from '../type/unknown/index'
import type { TString } from '../type/string/index'
import type { TSymbol } from '../type/symbol/index'
import type { TUndefined } from '../type/undefined/index'
import type { TUint8Array } from '../type/uint8array/index'
import type { TVoid } from '../type/void/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
// prettier-ignore
import {
IsArray,
IsUint8Array,
IsDate,
IsPromise,
IsFunction,
IsAsyncIterator,
IsIterator,
IsBoolean,
IsNumber,
IsBigInt,
IsString,
IsSymbol,
IsInteger,
IsNull,
IsUndefined
} from '../value/guard/index'
// ------------------------------------------------------------------
// ValueErrorType
// ------------------------------------------------------------------
export enum ValueErrorType {
ArrayContains,
ArrayMaxContains,
ArrayMaxItems,
ArrayMinContains,
ArrayMinItems,
ArrayUniqueItems,
Array,
AsyncIterator,
BigIntExclusiveMaximum,
BigIntExclusiveMinimum,
BigIntMaximum,
BigIntMinimum,
BigIntMultipleOf,
BigInt,
Boolean,
DateExclusiveMaximumTimestamp,
DateExclusiveMinimumTimestamp,
DateMaximumTimestamp,
DateMinimumTimestamp,
DateMultipleOfTimestamp,
Date,
Function,
IntegerExclusiveMaximum,
IntegerExclusiveMinimum,
IntegerMaximum,
IntegerMinimum,
IntegerMultipleOf,
Integer,
IntersectUnevaluatedProperties,
Intersect,
Iterator,
Kind,
Literal,
Never,
Not,
Null,
NumberExclusiveMaximum,
NumberExclusiveMinimum,
NumberMaximum,
NumberMinimum,
NumberMultipleOf,
Number,
ObjectAdditionalProperties,
ObjectMaxProperties,
ObjectMinProperties,
ObjectRequiredProperty,
Object,
Promise,
RegExp,
StringFormatUnknown,
StringFormat,
StringMaxLength,
StringMinLength,
StringPattern,
String,
Symbol,
TupleLength,
Tuple,
Uint8ArrayMaxByteLength,
Uint8ArrayMinByteLength,
Uint8Array,
Undefined,
Union,
Void,
}
// ------------------------------------------------------------------
// ValueError
// ------------------------------------------------------------------
export interface ValueError {
type: ValueErrorType
schema: TSchema
path: string
value: unknown
message: string
errors: ValueErrorIterator[]
}
// ------------------------------------------------------------------
// ValueErrors
// ------------------------------------------------------------------
export class ValueErrorsUnknownTypeError extends TypeBoxError {
constructor(public readonly schema: TSchema) {
super('Unknown type')
}
}
// ------------------------------------------------------------------
// EscapeKey
// ------------------------------------------------------------------
function EscapeKey(key: string): string {
return key.replace(/~/g, '~0').replace(/\//g, '~1') // RFC6901 Path
}
// ------------------------------------------------------------------
// Guards
// ------------------------------------------------------------------
function IsDefined<T>(value: unknown): value is T {
return value !== undefined
}
// ------------------------------------------------------------------
// 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
}
}
// --------------------------------------------------------------------------
// Create
// --------------------------------------------------------------------------
function Create(errorType: ValueErrorType, schema: TSchema, path: string, value: unknown, errors: ValueErrorIterator[] = []): ValueError {
return {
type: errorType,
schema,
path,
value,
message: GetErrorFunction()({ errorType, path, schema, value, errors }),
errors,
}
}
// --------------------------------------------------------------------------
// Types
// --------------------------------------------------------------------------
function* FromAny(schema: TAny, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {}
function* FromArgument(schema: TAny, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {}
function* FromArray(schema: TArray, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsArray(value)) {
return yield Create(ValueErrorType.Array, schema, path, value)
}
if (IsDefined<number>(schema.minItems) && !(value.length >= schema.minItems)) {
yield Create(ValueErrorType.ArrayMinItems, schema, path, value)
}
if (IsDefined<number>(schema.maxItems) && !(value.length <= schema.maxItems)) {
yield Create(ValueErrorType.ArrayMaxItems, schema, path, value)
}
for (let i = 0; i < value.length; i++) {
yield* Visit(schema.items, references, `${path}/${i}`, value[i])
}
// prettier-ignore
if (schema.uniqueItems === true && !((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 })())) {
yield Create(ValueErrorType.ArrayUniqueItems, schema, path, value)
}
// contains
if (!(IsDefined(schema.contains) || IsDefined(schema.minContains) || IsDefined(schema.maxContains))) {
return
}
const containsSchema = IsDefined<TSchema>(schema.contains) ? schema.contains : Never()
const containsCount = value.reduce((acc: number, value, index) => (Visit(containsSchema, references, `${path}${index}`, value).next().done === true ? acc + 1 : acc), 0)
if (containsCount === 0) {
yield Create(ValueErrorType.ArrayContains, schema, path, value)
}
if (IsNumber(schema.minContains) && containsCount < schema.minContains) {
yield Create(ValueErrorType.ArrayMinContains, schema, path, value)
}
if (IsNumber(schema.maxContains) && containsCount > schema.maxContains) {
yield Create(ValueErrorType.ArrayMaxContains, schema, path, value)
}
}
function* FromAsyncIterator(schema: TAsyncIterator, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsAsyncIterator(value)) yield Create(ValueErrorType.AsyncIterator, schema, path, value)
}
function* FromBigInt(schema: TBigInt, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsBigInt(value)) return yield Create(ValueErrorType.BigInt, schema, path, value)
if (IsDefined<bigint>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
yield Create(ValueErrorType.BigIntExclusiveMaximum, schema, path, value)
}
if (IsDefined<bigint>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
yield Create(ValueErrorType.BigIntExclusiveMinimum, schema, path, value)
}
if (IsDefined<bigint>(schema.maximum) && !(value <= schema.maximum)) {
yield Create(ValueErrorType.BigIntMaximum, schema, path, value)
}
if (IsDefined<bigint>(schema.minimum) && !(value >= schema.minimum)) {
yield Create(ValueErrorType.BigIntMinimum, schema, path, value)
}
if (IsDefined<bigint>(schema.multipleOf) && !(value % schema.multipleOf === BigInt(0))) {
yield Create(ValueErrorType.BigIntMultipleOf, schema, path, value)
}
}
function* FromBoolean(schema: TBoolean, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsBoolean(value)) yield Create(ValueErrorType.Boolean, schema, path, value)
}
function* FromConstructor(schema: TConstructor, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
yield* Visit(schema.returns, references, path, value.prototype)
}
function* FromDate(schema: TDate, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsDate(value)) return yield Create(ValueErrorType.Date, schema, path, value)
if (IsDefined<number>(schema.exclusiveMaximumTimestamp) && !(value.getTime() < schema.exclusiveMaximumTimestamp)) {
yield Create(ValueErrorType.DateExclusiveMaximumTimestamp, schema, path, value)
}
if (IsDefined<number>(schema.exclusiveMinimumTimestamp) && !(value.getTime() > schema.exclusiveMinimumTimestamp)) {
yield Create(ValueErrorType.DateExclusiveMinimumTimestamp, schema, path, value)
}
if (IsDefined<number>(schema.maximumTimestamp) && !(value.getTime() <= schema.maximumTimestamp)) {
yield Create(ValueErrorType.DateMaximumTimestamp, schema, path, value)
}
if (IsDefined<number>(schema.minimumTimestamp) && !(value.getTime() >= schema.minimumTimestamp)) {
yield Create(ValueErrorType.DateMinimumTimestamp, schema, path, value)
}
if (IsDefined<number>(schema.multipleOfTimestamp) && !(value.getTime() % schema.multipleOfTimestamp === 0)) {
yield Create(ValueErrorType.DateMultipleOfTimestamp, schema, path, value)
}
}
function* FromFunction(schema: TFunction, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsFunction(value)) yield Create(ValueErrorType.Function, schema, path, value)
}
function* FromImport(schema: TImport, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
yield* Visit(target, [...references, ...definitions], path, value)
}
function* FromInteger(schema: TInteger, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsInteger(value)) return yield Create(ValueErrorType.Integer, schema, path, value)
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
yield Create(ValueErrorType.IntegerExclusiveMaximum, schema, path, value)
}
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
yield Create(ValueErrorType.IntegerExclusiveMinimum, schema, path, value)
}
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
yield Create(ValueErrorType.IntegerMaximum, schema, path, value)
}
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
yield Create(ValueErrorType.IntegerMinimum, schema, path, value)
}
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
yield Create(ValueErrorType.IntegerMultipleOf, schema, path, value)
}
}
function* FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
let hasError = false
for (const inner of schema.allOf) {
for (const error of Visit(inner, references, path, value)) {
hasError = true
yield error
}
}
if (hasError) {
return yield Create(ValueErrorType.Intersect, schema, path, value)
}
if (schema.unevaluatedProperties === false) {
const keyCheck = new RegExp(KeyOfPattern(schema))
for (const valueKey of Object.getOwnPropertyNames(value)) {
if (!keyCheck.test(valueKey)) {
yield Create(ValueErrorType.IntersectUnevaluatedProperties, schema, `${path}/${valueKey}`, value)
}
}
}
if (typeof schema.unevaluatedProperties === 'object') {
const keyCheck = new RegExp(KeyOfPattern(schema))
for (const valueKey of Object.getOwnPropertyNames(value)) {
if (!keyCheck.test(valueKey)) {
const next = Visit(schema.unevaluatedProperties, references, `${path}/${valueKey}`, value[valueKey]).next()
if (!next.done) yield next.value // yield interior
}
}
}
}
function* FromIterator(schema: TIterator, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsIterator(value)) yield Create(ValueErrorType.Iterator, schema, path, value)
}
function* FromLiteral(schema: TLiteral, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!(value === schema.const)) yield Create(ValueErrorType.Literal, schema, path, value)
}
function* FromNever(schema: TNever, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
yield Create(ValueErrorType.Never, schema, path, value)
}
function* FromNot(schema: TNot, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (Visit(schema.not, references, path, value).next().done === true) yield Create(ValueErrorType.Not, schema, path, value)
}
function* FromNull(schema: TNull, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsNull(value)) yield Create(ValueErrorType.Null, schema, path, value)
}
function* FromNumber(schema: TNumber, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!TypeSystemPolicy.IsNumberLike(value)) return yield Create(ValueErrorType.Number, schema, path, value)
if (IsDefined<number>(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) {
yield Create(ValueErrorType.NumberExclusiveMaximum, schema, path, value)
}
if (IsDefined<number>(schema.exclusiveMinimum) && !(value > schema.exclusiveMinimum)) {
yield Create(ValueErrorType.NumberExclusiveMinimum, schema, path, value)
}
if (IsDefined<number>(schema.maximum) && !(value <= schema.maximum)) {
yield Create(ValueErrorType.NumberMaximum, schema, path, value)
}
if (IsDefined<number>(schema.minimum) && !(value >= schema.minimum)) {
yield Create(ValueErrorType.NumberMinimum, schema, path, value)
}
if (IsDefined<number>(schema.multipleOf) && !(value % schema.multipleOf === 0)) {
yield Create(ValueErrorType.NumberMultipleOf, schema, path, value)
}
}
function* FromObject(schema: TObject, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!TypeSystemPolicy.IsObjectLike(value)) return yield Create(ValueErrorType.Object, schema, path, value)
if (IsDefined<number>(schema.minProperties) && !(Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
yield Create(ValueErrorType.ObjectMinProperties, schema, path, value)
}
if (IsDefined<number>(schema.maxProperties) && !(Object.getOwnPropertyNames(value).length <= schema.maxProperties)) {
yield Create(ValueErrorType.ObjectMaxProperties, schema, path, value)
}
const requiredKeys = Array.isArray(schema.required) ? schema.required : ([] as string[])
const knownKeys = Object.getOwnPropertyNames(schema.properties)
const unknownKeys = Object.getOwnPropertyNames(value)
for (const requiredKey of requiredKeys) {
if (unknownKeys.includes(requiredKey)) continue
yield Create(ValueErrorType.ObjectRequiredProperty, schema.properties[requiredKey], `${path}/${EscapeKey(requiredKey)}`, undefined)
}
if (schema.additionalProperties === false) {
for (const valueKey of unknownKeys) {
if (!knownKeys.includes(valueKey)) {
yield Create(ValueErrorType.ObjectAdditionalProperties, schema, `${path}/${EscapeKey(valueKey)}`, value[valueKey])
}
}
}
if (typeof schema.additionalProperties === 'object') {
for (const valueKey of unknownKeys) {
if (knownKeys.includes(valueKey)) continue
yield* Visit(schema.additionalProperties as TSchema, references, `${path}/${EscapeKey(valueKey)}`, value[valueKey])
}
}
for (const knownKey of knownKeys) {
const property = schema.properties[knownKey]
if (schema.required && schema.required.includes(knownKey)) {
yield* Visit(property, references, `${path}/${EscapeKey(knownKey)}`, value[knownKey])
if (ExtendsUndefinedCheck(schema) && !(knownKey in value)) {
yield Create(ValueErrorType.ObjectRequiredProperty, property, `${path}/${EscapeKey(knownKey)}`, undefined)
}
} else {
if (TypeSystemPolicy.IsExactOptionalProperty(value, knownKey)) {
yield* Visit(property, references, `${path}/${EscapeKey(knownKey)}`, value[knownKey])
}
}
}
}
function* FromPromise(schema: TPromise, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsPromise(value)) yield Create(ValueErrorType.Promise, schema, path, value)
}
function* FromRecord(schema: TRecord, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!TypeSystemPolicy.IsRecordLike(value)) return yield Create(ValueErrorType.Object, schema, path, value)
if (IsDefined<number>(schema.minProperties) && !(Object.getOwnPropertyNames(value).length >= schema.minProperties)) {
yield Create(ValueErrorType.ObjectMinProperties, schema, path, value)
}
if (IsDefined<number>(schema.maxProperties) && !(Object.getOwnPropertyNames(value).length <= schema.maxProperties)) {
yield Create(ValueErrorType.ObjectMaxProperties, schema, path, value)
}
const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0]
const regex = new RegExp(patternKey)
for (const [propertyKey, propertyValue] of Object.entries(value)) {
if (regex.test(propertyKey)) yield* Visit(patternSchema, references, `${path}/${EscapeKey(propertyKey)}`, propertyValue)
}
if (typeof schema.additionalProperties === 'object') {
for (const [propertyKey, propertyValue] of Object.entries(value)) {
if (!regex.test(propertyKey)) yield* Visit(schema.additionalProperties as TSchema, references, `${path}/${EscapeKey(propertyKey)}`, propertyValue)
}
}
if (schema.additionalProperties === false) {
for (const [propertyKey, propertyValue] of Object.entries(value)) {
if (regex.test(propertyKey)) continue
return yield Create(ValueErrorType.ObjectAdditionalProperties, schema, `${path}/${EscapeKey(propertyKey)}`, propertyValue)
}
}
}
function* FromRef(schema: TRef, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
yield* Visit(Deref(schema, references), references, path, value)
}
function* FromRegExp(schema: TRegExp, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsString(value)) return yield Create(ValueErrorType.String, schema, path, value)
if (IsDefined<number>(schema.minLength) && !(value.length >= schema.minLength)) {
yield Create(ValueErrorType.StringMinLength, schema, path, value)
}
if (IsDefined<number>(schema.maxLength) && !(value.length <= schema.maxLength)) {
yield Create(ValueErrorType.StringMaxLength, schema, path, value)
}
const regex = new RegExp(schema.source, schema.flags)
if (!regex.test(value)) {
return yield Create(ValueErrorType.RegExp, schema, path, value)
}
}
function* FromString(schema: TString, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsString(value)) return yield Create(ValueErrorType.String, schema, path, value)
if (IsDefined<number>(schema.minLength) && !(value.length >= schema.minLength)) {
yield Create(ValueErrorType.StringMinLength, schema, path, value)
}
if (IsDefined<number>(schema.maxLength) && !(value.length <= schema.maxLength)) {
yield Create(ValueErrorType.StringMaxLength, schema, path, value)
}
if (IsString(schema.pattern)) {
const regex = new RegExp(schema.pattern)
if (!regex.test(value)) {
yield Create(ValueErrorType.StringPattern, schema, path, value)
}
}
if (IsString(schema.format)) {
if (!FormatRegistry.Has(schema.format)) {
yield Create(ValueErrorType.StringFormatUnknown, schema, path, value)
} else {
const format = FormatRegistry.Get(schema.format)!
if (!format(value)) {
yield Create(ValueErrorType.StringFormat, schema, path, value)
}
}
}
}
function* FromSymbol(schema: TSymbol, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsSymbol(value)) yield Create(ValueErrorType.Symbol, schema, path, value)
}
function* FromTemplateLiteral(schema: TTemplateLiteral, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsString(value)) return yield Create(ValueErrorType.String, schema, path, value)
const regex = new RegExp(schema.pattern)
if (!regex.test(value)) {
yield Create(ValueErrorType.StringPattern, schema, path, value)
}
}
function* FromThis(schema: TThis, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
yield* Visit(Deref(schema, references), references, path, value)
}
function* FromTuple(schema: TTuple<any[]>, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsArray(value)) return yield Create(ValueErrorType.Tuple, schema, path, value)
if (schema.items === undefined && !(value.length === 0)) {
return yield Create(ValueErrorType.TupleLength, schema, path, value)
}
if (!(value.length === schema.maxItems)) {
return yield Create(ValueErrorType.TupleLength, schema, path, value)
}
if (!schema.items) {
return
}
for (let i = 0; i < schema.items.length; i++) {
yield* Visit(schema.items[i], references, `${path}/${i}`, value[i])
}
}
function* FromUndefined(schema: TUndefined, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsUndefined(value)) yield Create(ValueErrorType.Undefined, schema, path, value)
}
function* FromUnion(schema: TUnion, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (Check(schema, references, value)) return
const errors = schema.anyOf.map((variant) => new ValueErrorIterator(Visit(variant, references, path, value)))
yield Create(ValueErrorType.Union, schema, path, value, errors)
}
function* FromUint8Array(schema: TUint8Array, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsUint8Array(value)) return yield Create(ValueErrorType.Uint8Array, schema, path, value)
if (IsDefined<number>(schema.maxByteLength) && !(value.length <= schema.maxByteLength)) {
yield Create(ValueErrorType.Uint8ArrayMaxByteLength, schema, path, value)
}
if (IsDefined<number>(schema.minByteLength) && !(value.length >= schema.minByteLength)) {
yield Create(ValueErrorType.Uint8ArrayMinByteLength, schema, path, value)
}
}
function* FromUnknown(schema: TUnknown, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {}
function* FromVoid(schema: TVoid, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!TypeSystemPolicy.IsVoidLike(value)) yield Create(ValueErrorType.Void, schema, path, value)
}
function* FromKind(schema: TSchema, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
const check = TypeRegistry.Get(schema[Kind])!
if (!check(schema, value)) yield Create(ValueErrorType.Kind, schema, path, value)
}
function* Visit<T extends TSchema>(schema: T, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
const references_ = IsDefined<string>(schema.$id) ? [...references, schema] : references
const schema_ = schema as any
switch (schema_[Kind]) {
case 'Any':
return yield* FromAny(schema_, references_, path, value)
case 'Argument':
return yield* FromArgument(schema_, references_, path, value)
case 'Array':
return yield* FromArray(schema_, references_, path, value)
case 'AsyncIterator':
return yield* FromAsyncIterator(schema_, references_, path, value)
case 'BigInt':
return yield* FromBigInt(schema_, references_, path, value)
case 'Boolean':
return yield* FromBoolean(schema_, references_, path, value)
case 'Constructor':
return yield* FromConstructor(schema_, references_, path, value)
case 'Date':
return yield* FromDate(schema_, references_, path, value)
case 'Function':
return yield* FromFunction(schema_, references_, path, value)
case 'Import':
return yield* FromImport(schema_, references_, path, value)
case 'Integer':
return yield* FromInteger(schema_, references_, path, value)
case 'Intersect':
return yield* FromIntersect(schema_, references_, path, value)
case 'Iterator':
return yield* FromIterator(schema_, references_, path, value)
case 'Literal':
return yield* FromLiteral(schema_, references_, path, value)
case 'Never':
return yield* FromNever(schema_, references_, path, value)
case 'Not':
return yield* FromNot(schema_, references_, path, value)
case 'Null':
return yield* FromNull(schema_, references_, path, value)
case 'Number':
return yield* FromNumber(schema_, references_, path, value)
case 'Object':
return yield* FromObject(schema_, references_, path, value)
case 'Promise':
return yield* FromPromise(schema_, references_, path, value)
case 'Record':
return yield* FromRecord(schema_, references_, path, value)
case 'Ref':
return yield* FromRef(schema_, references_, path, value)
case 'RegExp':
return yield* FromRegExp(schema_, references_, path, value)
case 'String':
return yield* FromString(schema_, references_, path, value)
case 'Symbol':
return yield* FromSymbol(schema_, references_, path, value)
case 'TemplateLiteral':
return yield* FromTemplateLiteral(schema_, references_, path, value)
case 'This':
return yield* FromThis(schema_, references_, path, value)
case 'Tuple':
return yield* FromTuple(schema_, references_, path, value)
case 'Undefined':
return yield* FromUndefined(schema_, references_, path, value)
case 'Union':
return yield* FromUnion(schema_, references_, path, value)
case 'Uint8Array':
return yield* FromUint8Array(schema_, references_, path, value)
case 'Unknown':
return yield* FromUnknown(schema_, references_, path, value)
case 'Void':
return yield* FromVoid(schema_, references_, path, value)
default:
if (!TypeRegistry.Has(schema_[Kind])) throw new ValueErrorsUnknownTypeError(schema)
return yield* FromKind(schema_, references_, path, value)
}
}
/** Returns an iterator for each error in this value. */
export function Errors<T extends TSchema>(schema: T, references: TSchema[], value: unknown): ValueErrorIterator
/** Returns an iterator for each error in this value. */
export function Errors<T extends TSchema>(schema: T, value: unknown): ValueErrorIterator
/** Returns an iterator for each error in this value. */
export function Errors(...args: any[]) {
const iterator = args.length === 3 ? Visit(args[0], args[1], '', args[2]) : Visit(args[0], [], '', args[1])
return new ValueErrorIterator(iterator)
}

195
src/errors/function.ts Normal file
View File

@@ -0,0 +1,195 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/system
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TSchema } from '../type/schema/index'
import { Kind } from '../type/symbols/index'
import { ValueErrorIterator, ValueErrorType } from './errors'
/** Creates an error message using en-US as the default locale */
export function DefaultErrorFunction(error: ErrorFunctionParameter) {
switch (error.errorType) {
case ValueErrorType.ArrayContains:
return 'Expected array to contain at least one matching value'
case ValueErrorType.ArrayMaxContains:
return `Expected array to contain no more than ${error.schema.maxContains} matching values`
case ValueErrorType.ArrayMinContains:
return `Expected array to contain at least ${error.schema.minContains} matching values`
case ValueErrorType.ArrayMaxItems:
return `Expected array length to be less or equal to ${error.schema.maxItems}`
case ValueErrorType.ArrayMinItems:
return `Expected array length to be greater or equal to ${error.schema.minItems}`
case ValueErrorType.ArrayUniqueItems:
return 'Expected array elements to be unique'
case ValueErrorType.Array:
return 'Expected array'
case ValueErrorType.AsyncIterator:
return 'Expected AsyncIterator'
case ValueErrorType.BigIntExclusiveMaximum:
return `Expected bigint to be less than ${error.schema.exclusiveMaximum}`
case ValueErrorType.BigIntExclusiveMinimum:
return `Expected bigint to be greater than ${error.schema.exclusiveMinimum}`
case ValueErrorType.BigIntMaximum:
return `Expected bigint to be less or equal to ${error.schema.maximum}`
case ValueErrorType.BigIntMinimum:
return `Expected bigint to be greater or equal to ${error.schema.minimum}`
case ValueErrorType.BigIntMultipleOf:
return `Expected bigint to be a multiple of ${error.schema.multipleOf}`
case ValueErrorType.BigInt:
return 'Expected bigint'
case ValueErrorType.Boolean:
return 'Expected boolean'
case ValueErrorType.DateExclusiveMinimumTimestamp:
return `Expected Date timestamp to be greater than ${error.schema.exclusiveMinimumTimestamp}`
case ValueErrorType.DateExclusiveMaximumTimestamp:
return `Expected Date timestamp to be less than ${error.schema.exclusiveMaximumTimestamp}`
case ValueErrorType.DateMinimumTimestamp:
return `Expected Date timestamp to be greater or equal to ${error.schema.minimumTimestamp}`
case ValueErrorType.DateMaximumTimestamp:
return `Expected Date timestamp to be less or equal to ${error.schema.maximumTimestamp}`
case ValueErrorType.DateMultipleOfTimestamp:
return `Expected Date timestamp to be a multiple of ${error.schema.multipleOfTimestamp}`
case ValueErrorType.Date:
return 'Expected Date'
case ValueErrorType.Function:
return 'Expected function'
case ValueErrorType.IntegerExclusiveMaximum:
return `Expected integer to be less than ${error.schema.exclusiveMaximum}`
case ValueErrorType.IntegerExclusiveMinimum:
return `Expected integer to be greater than ${error.schema.exclusiveMinimum}`
case ValueErrorType.IntegerMaximum:
return `Expected integer to be less or equal to ${error.schema.maximum}`
case ValueErrorType.IntegerMinimum:
return `Expected integer to be greater or equal to ${error.schema.minimum}`
case ValueErrorType.IntegerMultipleOf:
return `Expected integer to be a multiple of ${error.schema.multipleOf}`
case ValueErrorType.Integer:
return 'Expected integer'
case ValueErrorType.IntersectUnevaluatedProperties:
return 'Unexpected property'
case ValueErrorType.Intersect:
return 'Expected all values to match'
case ValueErrorType.Iterator:
return 'Expected Iterator'
case ValueErrorType.Literal:
return `Expected ${typeof error.schema.const === 'string' ? `'${error.schema.const}'` : error.schema.const}`
case ValueErrorType.Never:
return 'Never'
case ValueErrorType.Not:
return 'Value should not match'
case ValueErrorType.Null:
return 'Expected null'
case ValueErrorType.NumberExclusiveMaximum:
return `Expected number to be less than ${error.schema.exclusiveMaximum}`
case ValueErrorType.NumberExclusiveMinimum:
return `Expected number to be greater than ${error.schema.exclusiveMinimum}`
case ValueErrorType.NumberMaximum:
return `Expected number to be less or equal to ${error.schema.maximum}`
case ValueErrorType.NumberMinimum:
return `Expected number to be greater or equal to ${error.schema.minimum}`
case ValueErrorType.NumberMultipleOf:
return `Expected number to be a multiple of ${error.schema.multipleOf}`
case ValueErrorType.Number:
return 'Expected number'
case ValueErrorType.Object:
return 'Expected object'
case ValueErrorType.ObjectAdditionalProperties:
return 'Unexpected property'
case ValueErrorType.ObjectMaxProperties:
return `Expected object to have no more than ${error.schema.maxProperties} properties`
case ValueErrorType.ObjectMinProperties:
return `Expected object to have at least ${error.schema.minProperties} properties`
case ValueErrorType.ObjectRequiredProperty:
return 'Expected required property'
case ValueErrorType.Promise:
return 'Expected Promise'
case ValueErrorType.RegExp:
return 'Expected string to match regular expression'
case ValueErrorType.StringFormatUnknown:
return `Unknown format '${error.schema.format}'`
case ValueErrorType.StringFormat:
return `Expected string to match '${error.schema.format}' format`
case ValueErrorType.StringMaxLength:
return `Expected string length less or equal to ${error.schema.maxLength}`
case ValueErrorType.StringMinLength:
return `Expected string length greater or equal to ${error.schema.minLength}`
case ValueErrorType.StringPattern:
return `Expected string to match '${error.schema.pattern}'`
case ValueErrorType.String:
return 'Expected string'
case ValueErrorType.Symbol:
return 'Expected symbol'
case ValueErrorType.TupleLength:
return `Expected tuple to have ${error.schema.maxItems || 0} elements`
case ValueErrorType.Tuple:
return 'Expected tuple'
case ValueErrorType.Uint8ArrayMaxByteLength:
return `Expected byte length less or equal to ${error.schema.maxByteLength}`
case ValueErrorType.Uint8ArrayMinByteLength:
return `Expected byte length greater or equal to ${error.schema.minByteLength}`
case ValueErrorType.Uint8Array:
return 'Expected Uint8Array'
case ValueErrorType.Undefined:
return 'Expected undefined'
case ValueErrorType.Union:
return 'Expected union value'
case ValueErrorType.Void:
return 'Expected void'
case ValueErrorType.Kind:
return `Expected kind '${error.schema[Kind]}'`
default:
return 'Unknown error type'
}
}
// ------------------------------------------------------------------
// ErrorFunction
// ------------------------------------------------------------------
export type ErrorFunctionParameter = {
/** The type of validation error */
errorType: ValueErrorType
/** The path of the error */
path: string
/** The schema associated with the error */
schema: TSchema
/** The value associated with the error */
value: unknown
/** Interior errors for this error */
errors: ValueErrorIterator[]
}
export type ErrorFunction = (parameter: ErrorFunctionParameter) => string
/** Manages error message providers */
let errorFunction: ErrorFunction = DefaultErrorFunction
/** Sets the error function used to generate error messages. */
export function SetErrorFunction(callback: ErrorFunction) {
errorFunction = callback
}
/** Gets the error function used to generate error messages */
export function GetErrorFunction(): ErrorFunction {
return errorFunction
}

30
src/errors/index.ts Normal file
View File

@@ -0,0 +1,30 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/errors
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './errors'
export * from './function'

109
src/index.ts Normal file
View File

@@ -0,0 +1,109 @@
/*--------------------------------------------------------------------------
@sinclair/typebox
The MIT License (MIT)
Copyright (c) 2017-2025 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
// ------------------------------------------------------------------
// Infrastructure
// ------------------------------------------------------------------
export * from './type/clone/index'
export * from './type/create/index'
export * from './type/error/index'
export * from './type/guard/index'
export * from './type/helpers/index'
export * from './type/patterns/index'
export * from './type/registry/index'
export * from './type/sets/index'
export * from './type/symbols/index'
// ------------------------------------------------------------------
// Types
// ------------------------------------------------------------------
export * from './type/any/index'
export * from './type/array/index'
export * from './type/argument/index'
export * from './type/async-iterator/index'
export * from './type/awaited/index'
export * from './type/bigint/index'
export * from './type/boolean/index'
export * from './type/composite/index'
export * from './type/const/index'
export * from './type/constructor/index'
export * from './type/constructor-parameters/index'
export * from './type/date/index'
export * from './type/enum/index'
export * from './type/exclude/index'
export * from './type/extends/index'
export * from './type/extract/index'
export * from './type/function/index'
export * from './type/indexed/index'
export * from './type/instance-type/index'
export * from './type/instantiate/index'
export * from './type/integer/index'
export * from './type/intersect/index'
export * from './type/iterator/index'
export * from './type/intrinsic/index'
export * from './type/keyof/index'
export * from './type/literal/index'
export * from './type/module/index'
export * from './type/mapped/index'
export * from './type/never/index'
export * from './type/not/index'
export * from './type/null/index'
export * from './type/number/index'
export * from './type/object/index'
export * from './type/omit/index'
export * from './type/optional/index'
export * from './type/parameters/index'
export * from './type/partial/index'
export * from './type/pick/index'
export * from './type/promise/index'
export * from './type/readonly/index'
export * from './type/readonly-optional/index'
export * from './type/record/index'
export * from './type/recursive/index'
export * from './type/ref/index'
export * from './type/regexp/index'
export * from './type/required/index'
export * from './type/rest/index'
export * from './type/return-type/index'
export * from './type/schema/index'
export * from './type/static/index'
export * from './type/string/index'
export * from './type/symbol/index'
export * from './type/template-literal/index'
export * from './type/transform/index'
export * from './type/tuple/index'
export * from './type/uint8array/index'
export * from './type/undefined/index'
export * from './type/union/index'
export * from './type/unknown/index'
export * from './type/unsafe/index'
export * from './type/void/index'
// ------------------------------------------------------------------
// Type.*
// ------------------------------------------------------------------
export * from './type/type/index'

30
src/parser/index.ts Normal file
View File

@@ -0,0 +1,30 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * as Runtime from './runtime/index'
export * as Static from './static/index'

104
src/parser/runtime/guard.ts Normal file
View File

@@ -0,0 +1,104 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { IArray, IConst, IContext, IIdent, INumber, IOptional, IRef, IString, ITuple, IUnion } from './types'
// ------------------------------------------------------------------
// Value Guard
// ------------------------------------------------------------------
// prettier-ignore
function HasPropertyKey<Key extends PropertyKey>(value: Record<PropertyKey, unknown>, key: Key): value is Record<PropertyKey, unknown> & { [_ in Key]: unknown } {
return key in value
}
// prettier-ignore
function IsObjectValue(value: unknown): value is Record<PropertyKey, unknown> {
return typeof value === 'object' && value !== null
}
// prettier-ignore
function IsArrayValue(value: unknown): value is unknown[] {
return globalThis.Array.isArray(value)
}
// ------------------------------------------------------------------
// Parser Guard
// ------------------------------------------------------------------
/** Returns true if the value is a Array Parser */
export function IsArray(value: unknown): value is IArray {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Array' && HasPropertyKey(value, 'parser') && IsObjectValue(value.parser)
}
/** Returns true if the value is a Const Parser */
export function IsConst(value: unknown): value is IConst {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Const' && HasPropertyKey(value, 'value') && typeof value.value === 'string'
}
/** Returns true if the value is a Context Parser */
export function IsContext(value: unknown): value is IContext {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Context' && HasPropertyKey(value, 'left') && IsParser(value.left) && HasPropertyKey(value, 'right') && IsParser(value.right)
}
/** Returns true if the value is a Ident Parser */
export function IsIdent(value: unknown): value is IIdent {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Ident'
}
/** Returns true if the value is a Number Parser */
export function IsNumber(value: unknown): value is INumber {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Number'
}
/** Returns true if the value is a Optional Parser */
export function IsOptional(value: unknown): value is IOptional {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Optional' && HasPropertyKey(value, 'parser') && IsObjectValue(value.parser)
}
/** Returns true if the value is a Ref Parser */
export function IsRef(value: unknown): value is IRef {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Ref' && HasPropertyKey(value, 'ref') && typeof value.ref === 'string'
}
/** Returns true if the value is a String Parser */
export function IsString(value: unknown): value is IString {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'String' && HasPropertyKey(value, 'options') && IsArrayValue(value.options)
}
/** Returns true if the value is a Tuple Parser */
export function IsTuple(value: unknown): value is ITuple {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Tuple' && HasPropertyKey(value, 'parsers') && IsArrayValue(value.parsers)
}
/** Returns true if the value is a Union Parser */
export function IsUnion(value: unknown): value is IUnion {
return IsObjectValue(value) && HasPropertyKey(value, 'type') && value.type === 'Union' && HasPropertyKey(value, 'parsers') && IsArrayValue(value.parsers)
}
/** Returns true if the value is a Parser */
export function IsParser(value: unknown) {
// prettier-ignore
return (
IsArray(value) ||
IsConst(value) ||
IsContext(value) ||
IsIdent(value) ||
IsNumber(value) ||
IsOptional(value) ||
IsRef(value) ||
IsString(value) ||
IsTuple(value) ||
IsUnion(value)
)
}

View File

@@ -0,0 +1,33 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * as Guard from './guard'
export * as Token from './token'
export * from './types'
export * from './module'
export * from './parse'

View File

@@ -0,0 +1,52 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import * as Types from './types'
import { Parse } from './parse'
// ------------------------------------------------------------------
// Module
// ------------------------------------------------------------------
export class Module<Properties extends Types.IModuleProperties = Types.IModuleProperties> {
constructor(private readonly properties: Properties) {}
/** Parses using one of the parsers defined on this instance */
public Parse<Key extends keyof Properties>(key: Key, content: string, context: unknown): [] | [Types.StaticParser<Properties[Key]>, string]
/** Parses using one of the parsers defined on this instance */
public Parse<Key extends keyof Properties>(key: Key, content: string): [] | [Types.StaticParser<Properties[Key]>, string]
/** Parses using one of the parsers defined on this instance */
public Parse(...args: any[]): never {
// prettier-ignore
const [key, content, context] = (
args.length === 3 ? [args[0], args[1], args[2]] :
args.length === 2 ? [args[0], args[1], undefined] :
(() => { throw Error('Invalid parse arguments') })()
)
return Parse(this.properties, this.properties[key], content, context) as never
}
}

179
src/parser/runtime/parse.ts Normal file
View File

@@ -0,0 +1,179 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import * as Guard from './guard'
import * as Token from './token'
import * as Types from './types'
// ------------------------------------------------------------------
// Context
// ------------------------------------------------------------------
function ParseContext<ModuleProperties extends Types.IModuleProperties, Parser extends Types.IParser>(moduleProperties: ModuleProperties, left: Parser, right: Parser, code: string, context: unknown): unknown[] {
const result = ParseParser(moduleProperties, left, code, context)
return result.length === 2 ? ParseParser(moduleProperties, right, result[1], result[0]) : []
}
// ------------------------------------------------------------------
// Array
// ------------------------------------------------------------------
function ParseArray<ModuleProperties extends Types.IModuleProperties, Parser extends Types.IParser>(moduleProperties: ModuleProperties, parser: Parser, code: string, context: unknown): unknown[] {
const buffer = [] as unknown[]
let rest = code
while (rest.length > 0) {
const result = ParseParser(moduleProperties, parser, rest, context)
if (result.length === 0) return [buffer, rest]
buffer.push(result[0])
rest = result[1]
}
return [buffer, rest]
}
// ------------------------------------------------------------------
// Const
// ------------------------------------------------------------------
function ParseConst<Value extends string>(value: Value, code: string, context: unknown): [] | [Value, string] {
return Token.Const(value, code) as never
}
// ------------------------------------------------------------------
// Ident
// ------------------------------------------------------------------
function ParseIdent(code: string, _context: unknown): [] | [string, string] {
return Token.Ident(code)
}
// ------------------------------------------------------------------
// Number
// ------------------------------------------------------------------
// prettier-ignore
function ParseNumber(code: string, _context: unknown): [] | [string, string] {
return Token.Number(code)
}
// ------------------------------------------------------------------
// Optional
// ------------------------------------------------------------------
function ParseOptional<ModuleProperties extends Types.IModuleProperties, Parser extends Types.IParser>(moduleProperties: ModuleProperties, parser: Parser, code: string, context: unknown): [] | [[unknown] | [], unknown] {
const result = ParseParser(moduleProperties, parser, code, context)
return (result.length === 2 ? [[result[0]], result[1]] : [[], code]) as never
}
// ------------------------------------------------------------------
// Ref
// ------------------------------------------------------------------
function ParseRef<ModuleProperties extends Types.IModuleProperties, Ref extends string>(moduleProperties: ModuleProperties, ref: Ref, code: string, context: unknown): [] | [string, string] {
const parser = moduleProperties[ref]
if (!Guard.IsParser(parser)) throw Error(`Cannot dereference Parser '${ref}'`)
return ParseParser(moduleProperties, parser, code, context) as never
}
// ------------------------------------------------------------------
// String
// ------------------------------------------------------------------
// prettier-ignore
function ParseString(options: string[], code: string, _context: unknown): [] | [string, string] {
return Token.String(options, code)
}
// ------------------------------------------------------------------
// Tuple
// ------------------------------------------------------------------
function ParseTuple<ModuleProperties extends Types.IModuleProperties, Parsers extends Types.IParser[]>(moduleProperties: ModuleProperties, parsers: [...Parsers], code: string, context: unknown): [] | [unknown[], string] {
const buffer = [] as unknown[]
let rest = code
for (const parser of parsers) {
const result = ParseParser(moduleProperties, parser, rest, context)
if (result.length === 0) return []
buffer.push(result[0])
rest = result[1]
}
return [buffer, rest]
}
// ------------------------------------------------------------------
// Union
// ------------------------------------------------------------------
// prettier-ignore
function ParseUnion<ModuleProperties extends Types.IModuleProperties, Parsers extends Types.IParser[]>(moduleProperties: ModuleProperties, parsers: [...Parsers], code: string, context: unknown): [] | [unknown, string] {
for(const parser of parsers) {
const result = ParseParser(moduleProperties, parser, code, context)
if(result.length === 0) continue
return result
}
return []
}
// ------------------------------------------------------------------
// Parser
// ------------------------------------------------------------------
// prettier-ignore
function ParseParser<Parser extends Types.IParser>(moduleProperties: Types.IModuleProperties, parser: Parser, code: string, context: unknown): [] | [Types.StaticParser<Parser>, string] {
const result = (
Guard.IsContext(parser) ? ParseContext(moduleProperties, parser.left, parser.right, code, context) :
Guard.IsArray(parser) ? ParseArray(moduleProperties, parser.parser, code, context) :
Guard.IsConst(parser) ? ParseConst(parser.value, code, context) :
Guard.IsIdent(parser) ? ParseIdent(code, context) :
Guard.IsNumber(parser) ? ParseNumber(code, context) :
Guard.IsOptional(parser) ? ParseOptional(moduleProperties, parser.parser, code, context) :
Guard.IsRef(parser) ? ParseRef(moduleProperties, parser.ref, code, context) :
Guard.IsString(parser) ? ParseString(parser.options, code, context) :
Guard.IsTuple(parser) ? ParseTuple(moduleProperties, parser.parsers, code, context) :
Guard.IsUnion(parser) ? ParseUnion(moduleProperties, parser.parsers, code, context) :
[]
)
return (
result.length === 2
? [parser.mapping(result[0], context), result[1]]
: result
) as never
}
// ------------------------------------------------------------------
// Parse
// ------------------------------------------------------------------
/** Parses content using the given Parser */
// prettier-ignore
export function Parse<Parser extends Types.IParser>(moduleProperties: Types.IModuleProperties, parser: Parser, code: string, context: unknown): [] | [Types.StaticParser<Parser>, string]
/** Parses content using the given Parser */
// prettier-ignore
export function Parse<Parser extends Types.IParser>(moduleProperties: Types.IModuleProperties, parser: Parser, code: string): [] | [Types.StaticParser<Parser>, string]
/** Parses content using the given Parser */
// prettier-ignore
export function Parse<Parser extends Types.IParser>(parser: Parser, content: string, context: unknown): [] | [Types.StaticParser<Parser>, string]
/** Parses content using the given Parser */
// prettier-ignore
export function Parse<Parser extends Types.IParser>(parser: Parser, content: string): [] | [Types.StaticParser<Parser>, string]
/** Parses content using the given parser */
// prettier-ignore
export function Parse(...args: any[]): never {
const withModuleProperties = typeof args[1] === 'string' ? false : true
const [moduleProperties, parser, content, context] = withModuleProperties
? [args[0], args[1], args[2], args[3]]
: [{}, args[0], args[1], args[2]]
return ParseParser(moduleProperties, parser, content, context) as never
}

247
src/parser/runtime/token.ts Normal file
View File

@@ -0,0 +1,247 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
// ------------------------------------------------------------------
// Chars
// ------------------------------------------------------------------
// prettier-ignore
namespace Chars {
/** Returns true if the char code is a whitespace */
export function IsWhitespace(value: number): boolean {
return value === 32
}
/** Returns true if the char code is a newline */
export function IsNewline(value: number): boolean {
return value === 10
}
/** Returns true if the char code is a alpha */
export function IsAlpha(value: number): boolean {
return (
(value >= 65 && value <= 90) || // A-Z
(value >= 97 && value <= 122) // a-z
)
}
/** Returns true if the char code is zero */
export function IsZero(value: number): boolean {
return value === 48
}
/** Returns true if the char code is non-zero */
export function IsNonZero(value: number): boolean {
return value >= 49 && value <= 57
}
/** Returns true if the char code is a digit */
export function IsDigit(value: number): boolean {
return (
IsNonZero(value) ||
IsZero(value)
)
}
/** Returns true if the char code is a dot */
export function IsDot(value: number): boolean {
return value === 46
}
/** Returns true if this char code is a underscore */
export function IsUnderscore(value: unknown): boolean {
return value === 95
}
/** Returns true if this char code is a dollar sign */
export function IsDollarSign(value: unknown): boolean {
return value === 36
}
}
// ------------------------------------------------------------------
// Trim
// ------------------------------------------------------------------
// prettier-ignore
namespace Trim {
/** Trims Whitespace and retains Newline, Tabspaces, etc. */
export function TrimWhitespaceOnly(code: string): string {
for (let i = 0; i < code.length; i++) {
if (Chars.IsWhitespace(code.charCodeAt(i))) continue
return code.slice(i)
}
return code
}
/** Trims Whitespace including Newline, Tabspaces, etc. */
export function TrimAll(code: string): string {
return code.trimStart()
}
}
// ------------------------------------------------------------------
// Const
// ------------------------------------------------------------------
/** Checks the value matches the next string */
// prettier-ignore
function NextTokenCheck(value: string, code: string): boolean {
if (value.length > code.length) return false
for (let i = 0; i < value.length; i++) {
if (value.charCodeAt(i) !== code.charCodeAt(i)) return false
}
return true
}
/** Gets the next constant string value or empty if no match */
// prettier-ignore
function NextConst(value: string, code: string, ): [] | [string, string] {
return NextTokenCheck(value, code)
? [code.slice(0, value.length), code.slice(value.length)]
: []
}
/** Takes the next constant string value skipping any whitespace */
// prettier-ignore
export function Const(value: string, code: string): [] | [string, string] {
if(value.length === 0) return ['', code]
const char_0 = value.charCodeAt(0)
return (
Chars.IsNewline(char_0) ? NextConst(value, Trim.TrimWhitespaceOnly(code)) :
Chars.IsWhitespace(char_0) ? NextConst(value, code) :
NextConst(value, Trim.TrimAll(code))
)
}
// ------------------------------------------------------------------
// Ident
// ------------------------------------------------------------------
// prettier-ignore
function IdentIsFirst(char: number) {
return (
Chars.IsAlpha(char) ||
Chars.IsDollarSign(char) ||
Chars.IsUnderscore(char)
)
}
// prettier-ignore
function IdentIsRest(char: number) {
return (
Chars.IsAlpha(char) ||
Chars.IsDigit(char) ||
Chars.IsDollarSign(char) ||
Chars.IsUnderscore(char)
)
}
// prettier-ignore
function NextIdent(code: string): [] | [string, string] {
if (!IdentIsFirst(code.charCodeAt(0))) return []
for (let i = 1; i < code.length; i++) {
const char = code.charCodeAt(i)
if (IdentIsRest(char)) continue
const slice = code.slice(0, i)
const rest = code.slice(i)
return [slice, rest]
}
return [code, '']
}
/** Scans for the next Ident token */
// prettier-ignore
export function Ident(code: string): [] | [string, string] {
return NextIdent(Trim.TrimAll(code))
}
// ------------------------------------------------------------------
// Number
// ------------------------------------------------------------------
/** Checks that the next number is not a leading zero */
// prettier-ignore
function NumberLeadingZeroCheck(code: string, index: number) {
const char_0 = code.charCodeAt(index + 0)
const char_1 = code.charCodeAt(index + 1)
return (
(
// 1-9
Chars.IsNonZero(char_0)
) || (
// 0
Chars.IsZero(char_0) &&
!Chars.IsDigit(char_1)
) || (
// 0.
Chars.IsZero(char_0) &&
Chars.IsDot(char_1)
) || (
// .0
Chars.IsDot(char_0) &&
Chars.IsDigit(char_1)
)
)
}
/** Gets the next number token */
// prettier-ignore
function NextNumber(code: string): [] | [string, string] {
const negated = code.charAt(0) === '-'
const index = negated ? 1 : 0
if (!NumberLeadingZeroCheck(code, index)) {
return []
}
const dash = negated ? '-' : ''
let hasDot = false
for (let i = index; i < code.length; i++) {
const char_i = code.charCodeAt(i)
if (Chars.IsDigit(char_i)) {
continue
}
if (Chars.IsDot(char_i)) {
if (hasDot) {
const slice = code.slice(index, i)
const rest = code.slice(i)
return [`${dash}${slice}`, rest]
}
hasDot = true
continue
}
const slice = code.slice(index, i)
const rest = code.slice(i)
return [`${dash}${slice}`, rest]
}
return [code, '']
}
/** Scans for the next number token */
// prettier-ignore
export function Number(code: string) {
return NextNumber(Trim.TrimAll(code))
}
// ------------------------------------------------------------------
// String
// ------------------------------------------------------------------
// prettier-ignore
function NextString(options: string[], code: string): [] | [string, string] {
const first = code.charAt(0)
if(!options.includes(first)) return []
const quote = first
for(let i = 1; i < code.length; i++) {
const char = code.charAt(i)
if(char === quote) {
const slice = code.slice(1, i)
const rest = code.slice(i + 1)
return [slice, rest]
}
}
return []
}
/** Scans the next Literal String value */
// prettier-ignore
export function String(options: string[], code: string) {
return NextString(options, Trim.TrimAll(code))
}

250
src/parser/runtime/types.ts Normal file
View File

@@ -0,0 +1,250 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export type IModuleProperties = Record<PropertyKey, IParser>
// ------------------------------------------------------------------
// Static
// ------------------------------------------------------------------
/** Force output static type evaluation for Arrays */
export type StaticEnsure<T> = T extends infer R ? R : never
/** Infers the Output Parameter for a Parser */
export type StaticParser<Parser extends IParser> = Parser extends IParser<infer Output extends unknown> ? Output : unknown
// ------------------------------------------------------------------
// Mapping
// ------------------------------------------------------------------
export type IMapping<Input extends unknown = any, Output extends unknown = unknown> = (input: Input, context: any) => Output
/** Maps input to output. This is the default Mapping */
export const Identity = (value: unknown) => value
/** Maps the output as the given parameter T */
// prettier-ignore
export const As = <T>(mapping: T): ((value: unknown) => T) => (_: unknown) => mapping
// ------------------------------------------------------------------
// Parser
// ------------------------------------------------------------------
export interface IParser<Output extends unknown = unknown> {
type: string
mapping: IMapping<any, Output>
}
// ------------------------------------------------------------------
// Context
// ------------------------------------------------------------------
// prettier-ignore
export type ContextParameter<_Left extends IParser, Right extends IParser> = (
StaticParser<Right>
)
export interface IContext<Output extends unknown = unknown> extends IParser<Output> {
type: 'Context'
left: IParser
right: IParser
}
/** `[Context]` Creates a Context Parser */
export function Context<Left extends IParser, Right extends IParser, Mapping extends IMapping = IMapping<ContextParameter<Left, Right>>>(left: Left, right: Right, mapping: Mapping): IContext<ReturnType<Mapping>>
/** `[Context]` Creates a Context Parser */
export function Context<Left extends IParser, Right extends IParser>(left: Left, right: Right): IContext<ContextParameter<Left, Right>>
/** `[Context]` Creates a Context Parser */
export function Context(...args: unknown[]): never {
const [left, right, mapping] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], args[1], Identity]
return { type: 'Context', left, right, mapping } as never
}
// ------------------------------------------------------------------
// Array
// ------------------------------------------------------------------
// prettier-ignore
export type ArrayParameter<Parser extends IParser> = StaticEnsure<
StaticParser<Parser>[]
>
export interface IArray<Output extends unknown = unknown> extends IParser<Output> {
type: 'Array'
parser: IParser
}
/** `[EBNF]` Creates an Array Parser */
export function Array<Parser extends IParser, Mapping extends IMapping = IMapping<ArrayParameter<Parser>>>(parser: Parser, mapping: Mapping): IArray<ReturnType<Mapping>>
/** `[EBNF]` Creates an Array Parser */
export function Array<Parser extends IParser>(parser: Parser): IArray<ArrayParameter<Parser>>
/** `[EBNF]` Creates an Array Parser */
export function Array(...args: unknown[]): never {
const [parser, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity]
return { type: 'Array', parser, mapping } as never
}
// ------------------------------------------------------------------
// Const
// ------------------------------------------------------------------
export interface IConst<Output extends unknown = unknown> extends IParser<Output> {
type: 'Const'
value: string
}
/** `[TERM]` Creates a Const Parser */
export function Const<Value extends string, Mapping extends IMapping<Value>>(value: Value, mapping: Mapping): IConst<ReturnType<Mapping>>
/** `[TERM]` Creates a Const Parser */
export function Const<Value extends string>(value: Value): IConst<Value>
/** `[TERM]` Creates a Const Parser */
export function Const(...args: unknown[]): never {
const [value, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity]
return { type: 'Const', value, mapping } as never
}
// ------------------------------------------------------------------
// Ref
// ------------------------------------------------------------------
export interface IRef<Output extends unknown = unknown> extends IParser<Output> {
type: 'Ref'
ref: string
}
/** `[BNF]` Creates a Ref Parser. This Parser can only be used in the context of a Module */
export function Ref<Type extends unknown, Mapping extends IMapping<Type>>(ref: string, mapping: Mapping): IRef<ReturnType<Mapping>>
/** `[BNF]` Creates a Ref Parser. This Parser can only be used in the context of a Module */
export function Ref<Type extends unknown>(ref: string): IRef<Type>
/** `[BNF]` Creates a Ref Parser. This Parser can only be used in the context of a Module */
export function Ref(...args: unknown[]): never {
const [ref, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity]
return { type: 'Ref', ref, mapping } as never
}
// ------------------------------------------------------------------
// String
// ------------------------------------------------------------------
export interface IString<Output extends unknown = unknown> extends IParser<Output> {
type: 'String'
options: string[]
}
/** `[TERM]` Creates a String Parser. Options are an array of permissable quote characters */
export function String<Mapping extends IMapping<string>>(options: string[], mapping: Mapping): IString<ReturnType<Mapping>>
/** `[TERM]` Creates a String Parser. Options are an array of permissable quote characters */
export function String(options: string[]): IString<string>
/** `[TERM]` Creates a String Parser. Options are an array of permissable quote characters */
export function String(...params: unknown[]): never {
const [options, mapping] = params.length === 2 ? [params[0], params[1]] : [params[0], Identity]
return { type: 'String', options, mapping } as never
}
// ------------------------------------------------------------------
// Ident
// ------------------------------------------------------------------
export interface IIdent<Output extends unknown = unknown> extends IParser<Output> {
type: 'Ident'
}
/** `[TERM]` Creates an Ident Parser where Ident matches any valid JavaScript identifier */
export function Ident<Mapping extends IMapping<string>>(mapping: Mapping): IIdent<ReturnType<Mapping>>
/** `[TERM]` Creates an Ident Parser where Ident matches any valid JavaScript identifier */
export function Ident(): IIdent<string>
/** `[TERM]` Creates an Ident Parser where Ident matches any valid JavaScript identifier */
export function Ident(...params: unknown[]): never {
const mapping = params.length === 1 ? params[0] : Identity
return { type: 'Ident', mapping } as never
}
// ------------------------------------------------------------------
// Number
// ------------------------------------------------------------------
export interface INumber<Output extends unknown = unknown> extends IParser<Output> {
type: 'Number'
}
/** `[TERM]` Creates an Number Parser */
export function Number<Mapping extends IMapping<string>>(mapping: Mapping): INumber<ReturnType<Mapping>>
/** `[TERM]` Creates an Number Parser */
export function Number(): INumber<string>
/** `[TERM]` Creates an Number Parser */
export function Number(...params: unknown[]): never {
const mapping = params.length === 1 ? params[0] : Identity
return { type: 'Number', mapping } as never
}
// ------------------------------------------------------------------
// Optional
// ------------------------------------------------------------------
// prettier-ignore
export type OptionalParameter<Parser extends IParser, Result extends unknown = [StaticParser<Parser>] | []> = (
Result
)
export interface IOptional<Output extends unknown = unknown> extends IParser<Output> {
type: 'Optional'
parser: IParser
}
/** `[EBNF]` Creates an Optional Parser */
export function Optional<Parser extends IParser, Mapping extends IMapping = IMapping<OptionalParameter<Parser>>>(parser: Parser, mapping: Mapping): IOptional<ReturnType<Mapping>>
/** `[EBNF]` Creates an Optional Parser */
export function Optional<Parser extends IParser>(parser: Parser): IOptional<OptionalParameter<Parser>>
/** `[EBNF]` Creates an Optional Parser */
export function Optional(...args: unknown[]): never {
const [parser, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity]
return { type: 'Optional', parser, mapping } as never
}
// ------------------------------------------------------------------
// Tuple
// ------------------------------------------------------------------
// prettier-ignore
export type TupleParameter<Parsers extends IParser[], Result extends unknown[] = []> = StaticEnsure<
Parsers extends [infer Left extends IParser, ...infer Right extends IParser[]]
? TupleParameter<Right, [...Result, StaticEnsure<StaticParser<Left>>]>
: Result
>
export interface ITuple<Output extends unknown = unknown> extends IParser<Output> {
type: 'Tuple'
parsers: IParser[]
}
/** `[BNF]` Creates a Tuple Parser */
export function Tuple<Parsers extends IParser[], Mapping extends IMapping = IMapping<TupleParameter<Parsers>>>(parsers: [...Parsers], mapping: Mapping): ITuple<ReturnType<Mapping>>
/** `[BNF]` Creates a Tuple Parser */
export function Tuple<Parsers extends IParser[]>(parsers: [...Parsers]): ITuple<TupleParameter<Parsers>>
/** `[BNF]` Creates a Tuple Parser */
export function Tuple(...args: unknown[]): never {
const [parsers, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity]
return { type: 'Tuple', parsers, mapping } as never
}
// ------------------------------------------------------------------
// Union
// ------------------------------------------------------------------
// prettier-ignore
export type UnionParameter<Parsers extends IParser[], Result extends unknown = never> = StaticEnsure<
Parsers extends [infer Left extends IParser, ...infer Right extends IParser[]]
? UnionParameter<Right, Result | StaticParser<Left>>
: Result
>
export interface IUnion<Output extends unknown = unknown> extends IParser<Output> {
type: 'Union'
parsers: IParser[]
}
/** `[BNF]` Creates a Union parser */
export function Union<Parsers extends IParser[], Mapping extends IMapping = IMapping<UnionParameter<Parsers>>>(parsers: [...Parsers], mapping: Mapping): IUnion<ReturnType<Mapping>>
/** `[BNF]` Creates a Union parser */
export function Union<Parsers extends IParser[]>(parsers: [...Parsers]): IUnion<UnionParameter<Parsers>>
/** `[BNF]` Creates a Union parser */
export function Union(...args: unknown[]): never {
const [parsers, mapping] = args.length === 2 ? [args[0], args[1]] : [args[0], Identity]
return { type: 'Union', parsers, mapping } as never
}

View File

@@ -0,0 +1,31 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
export * as Token from './token'
export * from './parse'
export * from './types'

153
src/parser/static/parse.ts Normal file
View File

@@ -0,0 +1,153 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import * as Tokens from './token'
import * as Types from './types'
// ------------------------------------------------------------------
// Context
// ------------------------------------------------------------------
// prettier-ignore
type ContextParser<Left extends Types.IParser, Right extends Types.IParser, Code extends string, Context extends unknown> = (
Parse<Left, Code, Context> extends [infer Context extends unknown, infer Rest extends string]
? Parse<Right, Rest, Context>
: []
)
// ------------------------------------------------------------------
// Array
// ------------------------------------------------------------------
// prettier-ignore
type ArrayParser<Parser extends Types.IParser, Code extends string, Context extends unknown, Result extends unknown[] = []> = (
Parse<Parser, Code, Context> extends [infer Value1 extends unknown, infer Rest extends string]
? ArrayParser<Parser, Rest, Context, [...Result, Value1]>
: [Result, Code]
)
// ------------------------------------------------------------------
// Const
// ------------------------------------------------------------------
// prettier-ignore
type ConstParser<Value extends string, Code extends string, _Context extends unknown> = (
Tokens.Const<Value, Code> extends [infer Match extends Value, infer Rest extends string]
? [Match, Rest]
: []
)
// ------------------------------------------------------------------
// Ident
// ------------------------------------------------------------------
// prettier-ignore
type IdentParser<Code extends string, _Context extends unknown> = (
Tokens.Ident<Code> extends [infer Match extends string, infer Rest extends string]
? [Match, Rest]
: []
)
// ------------------------------------------------------------------
// Number
// ------------------------------------------------------------------
// prettier-ignore
type NumberParser<Code extends string, _Context extends unknown> = (
Tokens.Number<Code> extends [infer Match extends string, infer Rest extends string]
? [Match, Rest]
: []
)
// ------------------------------------------------------------------
// Optional
// ------------------------------------------------------------------
// prettier-ignore
type OptionalParser<Parser extends Types.IParser, Code extends string, Context extends unknown> = (
Parse<Parser, Code, Context> extends [infer Value extends unknown, infer Rest extends string]
? [[Value], Rest]
: [[], Code]
)
// ------------------------------------------------------------------
// String
// ------------------------------------------------------------------
// prettier-ignore
type StringParser<Options extends string[], Code extends string, _Context extends unknown> = (
Tokens.String<Options, Code> extends [infer Match extends string, infer Rest extends string]
? [Match, Rest]
: []
)
// ------------------------------------------------------------------
// Tuple
// ------------------------------------------------------------------
// prettier-ignore
type TupleParser<Parsers extends Types.IParser[], Code extends string, Context extends unknown, Result extends unknown[] = []> = (
Parsers extends [infer Left extends Types.IParser, ...infer Right extends Types.IParser[]]
? Parse<Left, Code, Context> extends [infer Value extends unknown, infer Rest extends string]
? TupleParser<Right, Rest, Context, [...Result, Value]>
: []
: [Result, Code]
)
// ------------------------------------------------------------------
// Union
// ------------------------------------------------------------------
// prettier-ignore
type UnionParser<Parsers extends Types.IParser[], Code extends string, Context extends unknown> = (
Parsers extends [infer Left extends Types.IParser, ...infer Right extends Types.IParser[]]
? Parse<Left, Code, Context> extends [infer Value extends unknown, infer Rest extends string]
? [Value, Rest]
: UnionParser<Right, Code, Context>
: []
)
// ------------------------------------------------------------------
// Parse
// ------------------------------------------------------------------
// prettier-ignore
type ParseCode<Type extends Types.IParser, Code extends string, Context extends unknown = unknown> = (
Type extends Types.Context<infer Left extends Types.IParser, infer Right extends Types.IParser> ? ContextParser<Left, Right, Code, Context> :
Type extends Types.Array<infer Parser extends Types.IParser> ? ArrayParser<Parser, Code, Context> :
Type extends Types.Const<infer Value extends string> ? ConstParser<Value, Code, Context> :
Type extends Types.Ident ? IdentParser<Code, Context> :
Type extends Types.Number ? NumberParser<Code, Context> :
Type extends Types.Optional<infer Parser extends Types.IParser> ? OptionalParser<Parser, Code, Context> :
Type extends Types.String<infer Options extends string[]> ? StringParser<Options, Code, Context> :
Type extends Types.Tuple<infer Parsers extends Types.IParser[]> ? TupleParser<Parsers, Code, Context> :
Type extends Types.Union<infer Parsers extends Types.IParser[]> ? UnionParser<Parsers, Code, Context> :
[]
)
// prettier-ignore
type ParseMapping<Parser extends Types.IParser, Result extends unknown, Context extends unknown = unknown> = (
(Parser['mapping'] & { input: Result, context: Context })['output']
)
/** Parses code with the given parser */
// prettier-ignore
export type Parse<Type extends Types.IParser, Code extends string, Context extends unknown = unknown> = (
ParseCode<Type, Code, Context> extends [infer L extends unknown, infer R extends string]
? [ParseMapping<Type, L, Context>, R]
: []
)

213
src/parser/static/token.ts Normal file
View File

@@ -0,0 +1,213 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
// ------------------------------------------------------------------
// Chars
// ------------------------------------------------------------------
// prettier-ignore
namespace Chars {
export type Empty = ''
export type Space = ' '
export type Newline = '\n'
export type Dot = '.'
export type Hyphen = '-'
export type Digit = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
]
export type Alpha = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z'
]
}
// ------------------------------------------------------------------
// Trim
// ------------------------------------------------------------------
// prettier-ignore
namespace Trim {
// ------------------------------------------------------------------
// Whitespace Filters
// ------------------------------------------------------------------
type W9 = `${W8}${W8}` // 512
type W8 = `${W7}${W7}` // 256
type W7 = `${W6}${W6}` // 128
type W6 = `${W5}${W5}` // 64
type W5 = `${W4}${W4}` // 32
type W4 = `${W3}${W3}` // 16
type W3 = `${W2}${W2}` // 8
type W2 = `${W1}${W1}` // 4
type W1 = `${W0}${W0}` // 2
type W0 = ` ` // 1
// ------------------------------------------------------------------
// TrimWhitespace
// ------------------------------------------------------------------
/** Trims whitespace only */
export type TrimWhitespace<Code extends string> = (
Code extends `${W4}${infer Rest extends string}` ? TrimWhitespace<Rest> :
Code extends `${W3}${infer Rest extends string}` ? TrimWhitespace<Rest> :
Code extends `${W1}${infer Rest extends string}` ? TrimWhitespace<Rest> :
Code extends `${W0}${infer Rest extends string}` ? TrimWhitespace<Rest> :
Code
)
// ------------------------------------------------------------------
// Trim
// ------------------------------------------------------------------
/** Trims Whitespace and Newline */
export type TrimAll<Code extends string> = (
Code extends `${W4}${infer Rest extends string}` ? TrimAll<Rest> :
Code extends `${W3}${infer Rest extends string}` ? TrimAll<Rest> :
Code extends `${W1}${infer Rest extends string}` ? TrimAll<Rest> :
Code extends `${W0}${infer Rest extends string}` ? TrimAll<Rest> :
Code extends `${Chars.Newline}${infer Rest extends string}` ? TrimAll<Rest> :
Code
)
}
// ------------------------------------------------------------------
// Union
// ------------------------------------------------------------------
/** Scans for the next match union */
// prettier-ignore
type NextUnion<Variants extends string[], Code extends string> = (
Variants extends [infer Variant extends string, ...infer Rest1 extends string[]]
? NextConst<Variant, Code> extends [infer Match extends string, infer Rest2 extends string]
? [Match, Rest2]
: NextUnion<Rest1, Code>
: []
)
// ------------------------------------------------------------------
// Const
// ------------------------------------------------------------------
// prettier-ignore
type NextConst<Value extends string, Code extends string> = (
Code extends `${Value}${infer Rest extends string}`
? [Value, Rest]
: []
)
/** Scans for the next constant value */
// prettier-ignore
export type Const<Value extends string, Code extends string> = (
Value extends '' ? ['', Code] :
Value extends `${infer First extends string}${string}`
? (
First extends Chars.Newline ? NextConst<Value, Trim.TrimWhitespace<Code>> :
First extends Chars.Space ? NextConst<Value, Code> :
NextConst<Value, Trim.TrimAll<Code>>
) : never
)
// ------------------------------------------------------------------
// Number
// ------------------------------------------------------------------
// prettier-ignore
type NextNumberNegate<Code extends string> = (
Code extends `${Chars.Hyphen}${infer Rest extends string}`
? [Chars.Hyphen, Rest]
: [Chars.Empty, Code]
)
// prettier-ignore
type NextNumberZeroCheck<Code extends string> = (
Code extends `0${infer Rest}`
? NextUnion<Chars.Digit, Rest> extends [string, string] ? false : true
: true
)
// prettier-ignore
type NextNumberScan<Code extends string, HasDecimal extends boolean = false, Result extends string = Chars.Empty> = (
NextUnion<[...Chars.Digit, Chars.Dot], Code> extends [infer Char extends string, infer Rest extends string]
? Char extends Chars.Dot
? HasDecimal extends false
? NextNumberScan<Rest, true, `${Result}${Char}`>
: [Result, `.${Rest}`]
: NextNumberScan<Rest, HasDecimal, `${Result}${Char}`>
: [Result, Code]
)
// prettier-ignore
export type NextNumber<Code extends string> = (
NextNumberNegate<Code> extends [infer Negate extends string, infer Rest extends string]
? NextNumberZeroCheck<Rest> extends true
? NextNumberScan<Rest> extends [infer Number extends string, infer Rest2 extends string]
? Number extends Chars.Empty
? []
: [`${Negate}${Number}`, Rest2]
: []
: []
: []
)
/** Scans for the next literal number */
export type Number<Code extends string> = NextNumber<Trim.TrimAll<Code>>
// ------------------------------------------------------------------
// String
// ------------------------------------------------------------------
type NextStringQuote<Options extends string[], Code extends string> = NextUnion<Options, Code>
// prettier-ignore
type NextStringBody<Code extends string, Quote extends string, Result extends string = Chars.Empty> = (
Code extends `${infer Char extends string}${infer Rest extends string}`
? Char extends Quote
? [Result, Rest]
: NextStringBody<Rest, Quote, `${Result}${Char}`>
: []
)
// prettier-ignore
type NextString<Options extends string[], Code extends string> = (
NextStringQuote<Options, Code> extends [infer Quote extends string, infer Rest extends string]
? NextStringBody<Rest, Quote> extends [infer String extends string, infer Rest extends string]
? [String, Rest]
: []
: []
)
/** Scans for the next literal string */
export type String<Options extends string[], Code extends string> = NextString<Options, Trim.TrimAll<Code>>
// ------------------------------------------------------------------
// Ident
// ------------------------------------------------------------------
type IdentLeft = [...Chars.Alpha, '_', '$'] // permissable first characters
type IdentRight = [...Chars.Digit, ...IdentLeft] // permissible subsequent characters
// prettier-ignore
type NextIdentScan<Code extends string, Result extends string = Chars.Empty> = (
NextUnion<IdentRight, Code> extends [infer Char extends string, infer Rest extends string]
? NextIdentScan<Rest, `${Result}${Char}`>
: [Result, Code]
)
// prettier-ignore
type NextIdent<Code extends string> = (
NextUnion<IdentLeft, Code> extends [infer Left extends string, infer Rest1 extends string]
? NextIdentScan<Rest1> extends [infer Right extends string, infer Rest2 extends string]
? [`${Left}${Right}`, Rest2]
: []
: []
)
/** Scans for the next Ident */
export type Ident<Code extends string> = NextIdent<Trim.TrimAll<Code>>

143
src/parser/static/types.ts Normal file
View File

@@ -0,0 +1,143 @@
/*--------------------------------------------------------------------------
@sinclair/parsebox
The MIT License (MIT)
Copyright (c) 2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
// ------------------------------------------------------------------
// Mapping
// ------------------------------------------------------------------
/**
* `[ACTION]` Inference mapping base type. Used to specify semantic actions for
* Parser productions. This type is implemented as a higher-kinded type where
* productions are received on the `input` property with mapping assigned
* the `output` property. The parsing context is available on the `context`
* property.
*/
export interface IMapping {
context: unknown
input: unknown
output: unknown
}
/** `[ACTION]` Default inference mapping. */
export interface Identity extends IMapping {
output: this['input']
}
/** `[ACTION]` Maps the given argument `T` as the mapping output */
export interface As<T> extends IMapping {
output: T
}
// ------------------------------------------------------------------
// Parser
// ------------------------------------------------------------------
/** Base type Parser implemented by all other parsers */
export interface IParser<Mapping extends IMapping = Identity> {
type: string
mapping: Mapping
}
// ------------------------------------------------------------------
// Context
// ------------------------------------------------------------------
/** `[Context]` Creates a Context Parser */
export interface Context<Left extends IParser = IParser, Right extends IParser = IParser, Mapping extends IMapping = Identity> extends IParser<Mapping> {
type: 'Context'
left: Left
right: Right
}
// ------------------------------------------------------------------
// Array
// ------------------------------------------------------------------
/** `[EBNF]` Creates an Array Parser */
export interface Array<Parser extends IParser = IParser, Mapping extends IMapping = Identity> extends IParser<Mapping> {
type: 'Array'
parser: Parser
}
// ------------------------------------------------------------------
// Const
// ------------------------------------------------------------------
/** `[TERM]` Creates a Const Parser */
export interface Const<Value extends string = string, Mapping extends IMapping = Identity> extends IParser<Mapping> {
type: 'Const'
value: Value
}
// ------------------------------------------------------------------
// Ident
// ------------------------------------------------------------------
/** `[TERM]` Creates an Ident Parser. */
// prettier-ignore
export interface Ident<Mapping extends IMapping = Identity> extends IParser<Mapping> {
type: 'Ident'
}
// ------------------------------------------------------------------
// Number
// ------------------------------------------------------------------
/** `[TERM]` Creates a Number Parser. */
// prettier-ignore
export interface Number<Mapping extends IMapping = Identity> extends IParser<Mapping> {
type: 'Number'
}
// ------------------------------------------------------------------
// Optional
// ------------------------------------------------------------------
/** `[EBNF]` Creates a Optional Parser */
export interface Optional<Parser extends IParser = IParser, Mapping extends IMapping = Identity> extends IParser<Mapping> {
type: 'Optional'
parser: Parser
}
// ------------------------------------------------------------------
// String
// ------------------------------------------------------------------
/** `[TERM]` Creates a String Parser. Options are an array of permissable quote characters */
export interface String<Options extends string[], Mapping extends IMapping = Identity> extends IParser<Mapping> {
type: 'String'
quote: Options
}
// ------------------------------------------------------------------
// Tuple
// ------------------------------------------------------------------
/** `[BNF]` Creates a Tuple Parser */
export interface Tuple<Parsers extends IParser[] = [], Mapping extends IMapping = Identity> extends IParser<Mapping> {
type: 'Tuple'
parsers: [...Parsers]
}
// ------------------------------------------------------------------
// Union
// ------------------------------------------------------------------
/** `[BNF]` Creates a Union Parser */
export interface Union<Parsers extends IParser[] = [], Mapping extends IMapping = Identity> extends IParser<Mapping> {
type: 'Union'
parsers: [...Parsers]
}

Some files were not shown because too many files have changed in this diff Show More