Revision 0.34.0 (#1069)

* Add Module Types

* Add Syntax Types
This commit is contained in:
Haydn Paterson
2024-11-13 23:56:23 +09:00
committed by GitHub
parent 4e2158deb2
commit 55ae006127
68 changed files with 2240 additions and 1038 deletions

196
changelog/0.34.0.md Normal file
View File

@@ -0,0 +1,196 @@
## [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

@@ -27,7 +27,6 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './from-schema'
export * from './module'
export * from './partial-deep'
export * from './union-enum'
export * from './union-oneof'

View File

@@ -1,166 +0,0 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-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 '@sinclair/typebox'
import { Check } from '@sinclair/typebox/value'
// ------------------------------------------------------------------
// Infer
// ------------------------------------------------------------------
// prettier-ignore
export type InferImport<Properties extends TModuleProperties, Key extends keyof Properties, Module extends TModuleProperties> = (
Infer<Properties[Key], Module>
)
// prettier-ignore
export type InferModuleRef<Ref extends string, Module extends TModuleProperties> = (
Ref extends keyof Module ? Infer<Module[Ref], Module> : never
)
// prettier-ignore
export type InferObject<Properties extends Types.TProperties, Module extends TModuleProperties> = {
[K in keyof Properties]: Infer<Properties[K], Module>
} & {}
// prettier-ignore
export type InferConstructor<Parameters extends Types.TSchema[], InstanceType extends Types.TSchema, Module extends TModuleProperties> = Types.Ensure<
new (...args: InferTuple<Parameters, Module>) => Infer<InstanceType, Module>
>
// prettier-ignore
export type InferFunction<Parameters extends Types.TSchema[], ReturnType extends Types.TSchema, Module extends TModuleProperties> = Types.Ensure<
(...args: InferTuple<Parameters, Module>) => Infer<ReturnType, Module>
>
// prettier-ignore
export type InferTuple<Types extends Types.TSchema[], Module extends TModuleProperties, Result extends unknown[] = []> = (
Types extends [infer L extends Types.TSchema, ...infer R extends Types.TSchema[]]
? InferTuple<R, Module, [...Result, Infer<L, Module>]>
: Result
)
// prettier-ignore
export type InferIntersect<Types extends Types.TSchema[], Module extends TModuleProperties, Result extends unknown = unknown> = (
Types extends [infer L extends Types.TSchema, ...infer R extends Types.TSchema[]]
? InferIntersect<R, Module, Result & Infer<L, Module>>
: Result
)
// prettier-ignore
export type InferUnion<Types extends Types.TSchema[], Module extends TModuleProperties, Result extends unknown = never> = (
Types extends [infer L extends Types.TSchema, ...infer R extends Types.TSchema[]]
? InferUnion<R, Module, Result | Infer<L, Module>>
: Result
)
// prettier-ignore
export type InferArray<Type extends Types.TSchema, Module extends TModuleProperties> = (
Types.Ensure<Array<Infer<Type, Module>>>
)
// prettier-ignore
export type InferAsyncIterator<Type extends Types.TSchema, Module extends TModuleProperties> = (
Types.Ensure<AsyncIterableIterator<Infer<Type, Module>>>
)
// prettier-ignore
export type InferIterator<Type extends Types.TSchema, Module extends TModuleProperties> = (
Types.Ensure<IterableIterator<Infer<Type, Module>>>
)
// prettier-ignore
type Infer<Type extends Types.TSchema, Module extends TModuleProperties = {}> = (
Type extends TImport<infer S extends TModuleProperties, infer K extends PropertyKey> ? InferImport<S, K, S> :
Type extends TModuleRef<infer S extends string> ? InferModuleRef<S, Module> :
Type extends Types.TObject<infer S extends Types.TProperties> ? InferObject<S, Module> :
Type extends Types.TConstructor<infer S extends Types.TSchema[], infer R extends Types.TSchema> ? InferConstructor<S, R, Module> :
Type extends Types.TFunction<infer S extends Types.TSchema[], infer R extends Types.TSchema> ? InferFunction<S, R, Module> :
Type extends Types.TTuple<infer S extends Types.TSchema[]> ? InferTuple<S, Module> :
Type extends Types.TIntersect<infer S extends Types.TSchema[]> ? InferIntersect<S, Module> :
Type extends Types.TUnion<infer S extends Types.TSchema[]> ? InferUnion<S, Module> :
Type extends Types.TArray<infer S extends Types.TSchema> ? InferArray<S, Module> :
Type extends Types.TAsyncIterator<infer S extends Types.TSchema> ? InferAsyncIterator<S, Module> :
Type extends Types.TIterator<infer S extends Types.TSchema> ? InferIterator<S, Module> :
Type extends Types.TTemplateLiteral<infer S extends Types.TTemplateLiteralKind[]> ? Types.Static<Types.TTemplateLiteral<S>> :
Type extends Types.TLiteral<infer S extends Types.TLiteralValue> ? S :
Type extends Types.TAny ? any :
Type extends Types.TBigInt ? bigint :
Type extends Types.TBoolean ? boolean :
Type extends Types.TDate ? Date :
Type extends Types.TInteger ? number :
Type extends Types.TNever ? never :
Type extends Types.TNumber ? number :
Type extends Types.TRegExp ? string :
Type extends Types.TString ? string :
Type extends Types.TSymbol ? symbol :
Type extends Types.TNull ? null :
Type extends Types.TUint8Array ? Uint8Array :
Type extends Types.TUndefined ? undefined :
Type extends Types.TUnknown ? unknown :
Type extends Types.TVoid ? void :
never
)
// ------------------------------------------------------------------
// ModuleRef
// ------------------------------------------------------------------
// prettier-ignore
export interface TModuleRef<Ref extends string> extends Types.TSchema {
[Types.Kind]: 'ModuleRef'
$ref: Ref
}
// prettier-ignore
export function ModuleRef<Ref extends string>($ref: Ref): TModuleRef<Ref> {
return Types.Type.Ref($ref) as never
}
// ------------------------------------------------------------------
// Import
// ------------------------------------------------------------------
// prettier-ignore
Types.TypeRegistry.Set('Import', (module: TImport, value: unknown) => {
const keys = Object.getOwnPropertyNames(module.$defs)
const references = keys.map(key => module.$defs[key as never]) as Types.TSchema[]
const schema = module.$defs[module.$ref]
return Check(schema, references, value)
})
// prettier-ignore
export type TModuleProperties = Record<PropertyKey, Types.TSchema>
// prettier-ignore
export interface TImport<Definitions extends TModuleProperties = {}, Key extends keyof Definitions = keyof Definitions> extends Types.TSchema {
[Types.Kind]: 'Import'
static: InferImport<Definitions, Key, Definitions>
$defs: Definitions
$ref: Key
}
// ------------------------------------------------------------------
// Module
// ------------------------------------------------------------------
// prettier-ignore
export class ModuleInstance<Properties extends TModuleProperties> {
constructor(private readonly properties: Properties) {}
public Import<Key extends keyof Properties>(key: Key): TImport<Properties, Key> {
const $defs = globalThis.Object.getOwnPropertyNames(this.properties).reduce((Result, Key) => (
{ ...Result, [Key]: { ...this.properties[Key], $id: Key }}
), {})
return { [Types.Kind]: 'Import', $defs, $ref: key } as never
}
}
export function Module<Properties extends TModuleProperties>(properties: Properties): ModuleInstance<Properties> {
return new ModuleInstance(properties)
}

View File

@@ -2,46 +2,6 @@
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.
## Module, ModuleRef and Import
The Module type as a candidate referencing system for TypeBox. Modules enable deferred cross type referencing and support mutual recursive inference. Module types must be instanced via `M.Import(...)` which constructs a `$def` schematic containing each definition required to validate, and a self referential `$ref` to one of the type being imported.
```typescript
import { Module, ModuleRef } from './prototypes'
// ------------------------------------------------------------------
// Module, ModuleRef
// ------------------------------------------------------------------
const Math = Module({
Vector2: Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
Vector3: Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number()
}),
Vertex: Type.Object({
position: ModuleRef('Vector3'),
normal: ModuleRef('Vector3'),
texcoord: ModuleRef('Vector2')
}),
Geometry: Type.Object({
vertices: Type.Array(ModuleRef('Vertex')),
indices: Type.Array(Type.Integer())
})
})
// ------------------------------------------------------------------
// Import
// -----------------------------------------------------------------
const Vector2 = Math.Import('Vector2')
const Vector3 = Math.Import('Vector2')
const Vertex = Math.Import('Vertex')
const Geometry = Math.Import('Geometry')
```
## PartialDeep

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@sinclair/typebox",
"version": "0.33.22",
"version": "0.34.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@sinclair/typebox",
"version": "0.33.22",
"version": "0.34.0",
"license": "MIT",
"devDependencies": {
"@arethetypeswrong/cli": "^0.13.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@sinclair/typebox",
"version": "0.33.22",
"version": "0.34.0",
"description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",

210
readme.md
View File

@@ -68,8 +68,7 @@ License MIT
- [Options](#types-options)
- [Properties](#types-properties)
- [Generics](#types-generics)
- [References](#types-references)
- [Recursive](#types-recursive)
- [Modules](#types-modules)
- [Template Literal](#types-template-literal)
- [Indexed](#types-indexed)
- [Mapped](#types-mapped)
@@ -79,7 +78,9 @@ License MIT
- [Unsafe](#types-unsafe)
- [Syntax](#syntax)
- [Parse](#syntax-parse)
- [Compose](#syntax-compose)
- [Context](#syntax-context)
- [Module](#syntax-module)
- [Static](#syntax-static)
- [Limits](#syntax-limits)
- [Values](#values)
@@ -769,103 +770,40 @@ const T = Nullable(Type.String()) // const T = {
type T = Static<typeof T> // type T = string | null
```
<a name='types-references'></a>
<a name='types-modules'></a>
### Reference Types
### Module Types
Reference types can be created with Ref. These types infer the same as the target type but only store a named `$ref` to the target type.
TypeBox Modules are containers for related types. They function as namespaces and enable internal types to reference each other via string references. Modules support both singular and mutually recursive types. They provide a mechanism to create circular types irrespective of the order in which types are defined.
```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'
// }
// The following creates a circular recursive type.
const VectorRef = Type.Ref(Vector) // const VectorRef = {
// $ref: 'Vector'
// }
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:
}),
})
type VectorRef = Static<typeof VectorRef> // type VectorRef = {
// x: number,
// y: number
// }
```
Use Deref to dereference a type. This function will replace any interior reference with the target type.
```typescript
const Vertex = Type.Object({ // const Vertex = {
position: VectorRef, // type: 'object',
texcoord: VectorRef, // required: ['position', 'texcoord'],
}) // properties: {
// position: { $ref: 'Vector' },
// texcoord: { $ref: 'Vector' }
// }
// }
// Module types must be imported before use.
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' }
// }
// }
// }
// }
```
Note that Ref types do not store structural information about the type they're referencing. Because of this, these types cannot be used with some mapping types (such as Partial or Pick). For applications that require mapping on Ref, use Deref to normalize the type first.
const A = Module.Import('A') // const A: TImport<{...}, 'A'>
<a name='types-recursive'></a>
### Recursive Types
TypeBox supports recursive data structures with Recursive. This type wraps an interior type and provides it a `this` context that allows the type to reference itself. The following creates a recursive type. Singular recursive inference is also supported.
```typescript
const Node = Type.Recursive(This => Type.Object({ // const Node = {
id: Type.String(), // $id: 'Node',
nodes: Type.Array(This) // type: 'object',
}), { $id: 'Node' }) // properties: {
// id: {
// type: 'string'
// },
// nodes: {
// type: 'array',
// items: {
// $ref: 'Node'
// }
// }
// },
// required: [
// 'id',
// 'nodes'
// ]
// }
type Node = Static<typeof Node> // type Node = {
// id: string
// nodes: Node[]
// }
function test(node: Node) {
const id = node.nodes[0].nodes[0].id // id is string
}
type A = Static<typeof A> // type A = {
// b: {
// c: {
// a: {
// b: ...
// }
// }
// }
// }
```
<a name='types-template-literal'></a>
@@ -1084,11 +1022,11 @@ if(TypeGuard.IsString(T)) {
<a name='syntax'></a>
## Syntax
## Syntax Types
TypeBox supports parsing TypeScript type annotation syntax directly into TypeBox types, providing an alternative to the Type.* API for type construction. This feature functions at runtime and within the TypeScript type system, enabling types encoded as strings to be inferred as equivalent TypeBox types.
TypeBox provides support for Syntax Types, enabling it to parse TypeScript syntax directly into TypeBox types. Syntax Types serve as a DSL frontend for TypeBox's type builder and are useful for converting existing TypeScript type definitions into Json Schema schematics.
TypeScript syntax parsing is provided as an optional import.
Syntax Types are provided via optional import.
```typescript
import { Parse } from '@sinclair/typebox/syntax'
@@ -1098,9 +1036,7 @@ import { Parse } from '@sinclair/typebox/syntax'
### Parse
The Parse function can be used to parse TypeScript code into TypeBox types. The left hand result will be a inferred TypeBox type of TSchema. Invalid syntax will result in an undefined return value.
[TypeScript Link Here](https://www.typescriptlang.org/play/?moduleResolution=99&module=199#code/JYWwDg9gTgLgBAbzgBQIZQM4FM4F84BmUEIcA5AAIbAB2AxgDarBQD0MAnmFgEYQAerDBxoxU-MgChJdCDQzwAgnAC8KdNgAUZBVFoBzMgEo4ps+YuXLrVnFnylALjgAVAMow9NfdPsK4AEKq6phY2gDaAIwANHAATLEAzAC6xlbpGTZ2cv4Bzi4uAK5gDFgAPOGSGdU1tdVZpi4AMsAwWFCoDGWRAHzRVXWDQ5m2jS1tHV1xfQPDc3MNruPtnWWJPbPzW7VZyRsyOfAAwsFooZoABkj8zjSFIDztsRy3949QeBcm6Vl+x-kAeR4ACssHQYGUEJttjCrIsbq4AHJvdrQ2Ho0yLF5IlFQNEY2FZXD7IA)
Use the Parse function to convert a TypeScript string into a TypeBox type. TypeBox will infer the appropriate TSchema type or return undefined if there is a syntax error.
```typescript
const A = Parse('string') // const A: TString
@@ -1117,64 +1053,96 @@ const C = Parse(`{ x: number, y: number }`) // const C: TObject<{
// }>
```
<a name='syntax-compose'></a>
### Compose
Syntax Types are designed to be interchangeable with standard Types.
```typescript
const T = Type.Object({ // const T: TObject<{
x: Parse('number'), // x: TNumber,
y: Parse('number'), // y: TNumber,
z: Parse('number') // z: TNumber
}) // }>
```
<a name='syntax-module'></a>
### Module
Syntax Types support Module parsing, which is useful for processing multiple TypeScript types. Module parsing supports type alias and interface definitions. Generics are currently unsupported as of 0.34.0.
```typescript
const Foo = Parse(`module Foo {
export type A = string
export type B = number
export type C = A | B
}`)
const C = Foo.Import('C') // const C: TImport<{
// ...
// }, 'C'>
```
<a name='syntax-context'></a>
### Context
The Parse function accepts an optional context parameter that enables external types to be referenced within the syntax.
[TypeScript Link Here](https://www.typescriptlang.org/play/?moduleResolution=99&module=199#code/JYWwDg9gTgLgBAbzgBQIZQM4FM4F84BmUEIcA5AAIbAB2AxgDarBQD0MAnmFgEYQAerDBxoxU-MgChQkWIjgAVLjnxES5KrUbM2nbnwmTJdCDQzwFcALyLlAOgDyPAFZY6MABRI4P33-8BgayscCYArgwAJnA8OADuUMAwMFg0cKgYAFwo6NgeAAYIkj782UrcdgByYSCxUB4AlAA0ga1tbcG+pXA0NXVNxXAcZfbVtVj1ze3TM50+wz19EwM+AF4jFWN1jTO7rXNr2b3jUJK4DXuXV-6duPkNRiZm8ACC1jmYWF6KeC35aLBgKgGAAeBQAPnuV06T3McBeZScrncIKK13R1wO3QUDjAMGApmBYK2E3BKwxFNmIXmiLxBJoRIUJKgZMGlPZQWpcHWilx+MJoKZSxZbI5Yp8t3Bj1McIAQu8AXkkJZcH8ANZYDgQAiKKHomEy+CysoAVRo9JBAG1ReKOQcFAAZJITIlkCSs222+1OlJQV0cMgez1i73Ov2gsirQM24MYzoAXSlxkNcAAwgrcl9lb84PlLAAyeRxI7CvB6zmhFOpsoASVEE2wKMtOJcbhgqJjscxXLg2OZAG5O13LgchmUB0Ph7tRzyhSdB1OKZKWi3ke20Yv9T3i4oJ5ut3hwYmjEA)
The Parse function accepts an initial Context argument, allowing external types to be passed into the parser.
```typescript
const T = Type.Object({ // could be written as: Parse(`{
x: Type.Number(), // x: number,
y: Type.Number(), // y: number,
z: Type.Number() // z: number
z: Type.Number() // z: number
}) // }`)
const A = Parse({ T }, `Partial<T>`) // const A: TObject<{
const A = Parse({ T }, 'Partial<T>') // const A: TObject<{
// x: TOptional<TNumber>,
// y: TOptional<TNumber>,
// z: TOptional<TNumber>
// }>
const B = Parse({ T }, `keyof T`) // const B: TUnion<[
const B = Parse({ T }, 'keyof T') // const B: TUnion<[
// TLiteral<'x'>,
// TLiteral<'y'>,
// TLiteral<'z'>
// ]>
const C = Parse({ T }, `T & { w: number }`) // const C: TIntersect<[TObject<{
const C = Parse({ T }, 'T & { w: number }') // const C: TIntersect<[TObject<{
// x: TNumber;
// y: TNumber;
// z: TNumber;
// }>, TObject<{
// w: TNumber;
// }>]>
```
<a name='syntax-static'></a>
### Static
TypeBox provides two Static types for inferring TypeScript syntax directly from strings.
[TypeScript Link Here](https://www.typescriptlang.org/play/?moduleResolution=99&module=199#code/JYWwDg9gTgLgBAbzgZRgQxsAxgBTVAZwFMBBA5LACyJDQBoV1Nd9iyAVATzCLgF84AMygQQcAOQABAsAB2WADZpgUAPQxuRAEYQAHqoKdZ6XeLgAoc6tVwA6sAUK4cwUShw0BD3HYVqtSw0eFDgAXkYMbDxCUnIqGjQAHgQ+BgADJF0ALjhZAFcQLTd+NIA+OArrOCDeZBz2AHktACsiLBhk8wrunt6+-oHBgaqK7J8AOQKiqC6hufmF3qq+Ussq+0dnWVd3T28awM0fMIjmaLYCLh5k1LgMuDH8wuK+MqWbGuPwhFnFv--3qMck9pr8AeDFiM4EA)
Syntax Types provide two Static types for inferring TypeScript syntax from strings.
```typescript
import { StaticParseAsSchema, StaticParseAsType } from '@sinclair/typebox/syntax'
// Will infer as a TSchema
type S = StaticParseAsSchema<{}, `{ x: number }`> // type S: TObject<{
type S = StaticParseAsSchema<{}, '{ x: number }'> // type S: TObject<{
// x: TNumber
// }>
// Will infer as a type
type T = StaticParseAsType<{}, `{ x: number }`> // type T = {
type T = StaticParseAsType<{}, '{ x: number }'> // type T = {
// x: number
//
```
@@ -1182,9 +1150,9 @@ type T = StaticParseAsType<{}, `{ x: number }`> // type T = {
<a name='syntax-limits'></a>
### Limits
### Limitations
The Parse function works by TypeBox parsing TypeScript syntax within the type system itself. This can place some additional demand on the TypeScript compiler, which may affect language service (LSP) responsiveness when working with especially large or complex types. In particular, very wide structures or deeply nested types may approach TypeScripts instantiation limits.
Syntax Types work by having TypeBox parse TypeScript syntax within the TypeScript type system. This approach can place some strain on the TypeScript compiler and language service, potentially affecting responsiveness. While TypeBox makes a best-effort attempt to optimize for Syntax Types, users should be mindful of the following structures:
```typescript
// Excessively wide structures will result in instantiation limits exceeding
@@ -1211,12 +1179,12 @@ const B = Parse(`{
}`)
```
For parsing especially complex types, TypeBox provides the ParseOnly function, which parses strings at runtime and returns a non-inferred TSchema type. ParseOnly allows for arbitrarily complex types without reaching instantiation limits, making it useful for cases where TypeScript types need to be directly converted to Json Schema.
In cases where Syntax Types busts through TypeScript instantiation limits, TypeBox offers a fallback ParseOnly function which will Parse the types at runtime, but not infer the type. This function can also be used for parsing non-constant strings.
```typescript
import { ParseOnly } from '@sinclair/typebox/syntax'
// ok: but where A is TSchema | undefined
// Where A is TSchema | undefined
const A = ParseOnly(`{
x: {
@@ -1229,7 +1197,7 @@ const A = ParseOnly(`{
}`)
```
Optimizing TypeScript string parsing performance is an ongoing area of research. For more information, see the [ParseBox](https://github.com/sinclairzx81/parsebox) project, which documents TypeBoxs parsing infrastructure and focuses efforts to improve parsing performance.
For more information on TypeBox's parsing infrastructure, refer to the [ParseBox](https://github.com/sinclairzx81/parsebox) project.
<a name='values'></a>
@@ -1914,12 +1882,12 @@ The following table lists esbuild compiled and minified sizes for each TypeBox m
┌──────────────────────┬────────────┬────────────┬─────────────┐
(index) Compiled Minified Compression
├──────────────────────┼────────────┼────────────┼─────────────┤
typebox/compiler '119.8 kb' ' 52.6 kb' '2.28 x'
typebox/errors ' 74.4 kb' ' 33.1 kb' '2.25 x'
typebox/syntax '115.3 kb' ' 48.3 kb' '2.39 x'
typebox/compiler '121.7 kb' ' 53.4 kb' '2.28 x'
typebox/errors ' 75.3 kb' ' 33.4 kb' '2.25 x'
typebox/syntax '120.1 kb' ' 50.5 kb' '2.38 x'
typebox/system ' 7.4 kb' ' 3.2 kb' '2.33 x'
typebox/value '157.2 kb' ' 66.1 kb' '2.38 x'
typebox ' 98.9 kb' ' 41.2 kb' '2.40 x'
typebox/value '160.3 kb' ' 67.4 kb' '2.38 x'
typebox ' 96.2 kb' ' 40.2 kb' '2.39 x'
└──────────────────────┴────────────┴────────────┴─────────────┘
```

View File

@@ -47,6 +47,7 @@ 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'
@@ -110,7 +111,7 @@ export class TypeCheck<T extends TSchema> {
return (this.hasTransform ? TransformDecode(this.schema, this.references, value) : value) as never
}
/** Encodes a value or throws if error */
public Encode<Static = StaticDecode<T>, Result extends Static = Static>(value: unknown): Result {
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
@@ -288,6 +289,11 @@ export namespace TypeCompiler {
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 definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
yield* Visit(target, [...references, ...definitions], value)
}
function* FromInteger(schema: TInteger, references: TSchema[], value: string): IterableIterator<string> {
yield `Number.isInteger(${value})`
if (IsNumber(schema.exclusiveMaximum)) yield `${value} < ${schema.exclusiveMaximum}`
@@ -385,8 +391,12 @@ export namespace TypeCompiler {
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})`
// If this isn't the case we defer to visit to generate and set the _recursion_end_for_ for subsequent
// passes. This operation is very awkward as we are using the functions state to store values to
// enable self referential types to terminate. This needs to be refactored.
const recursiveEnd = `_recursion_end_for_${schema.$ref}`
if (state.functions.has(recursiveEnd)) return yield `${CreateFunctionName(schema.$ref)}(${value})`
state.functions.set(recursiveEnd, '') // terminate recursion here by setting the name.
yield* Visit(target, references, value)
}
function* FromRegExp(schema: TRegExp, references: TSchema[], value: string): IterableIterator<string> {
@@ -485,6 +495,8 @@ export namespace TypeCompiler {
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':

View File

@@ -46,6 +46,7 @@ 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'
@@ -302,6 +303,11 @@ function* FromDate(schema: TDate, references: TSchema[], path: string, value: an
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)) {
@@ -566,6 +572,8 @@ function* Visit<T extends TSchema>(schema: T, references: TSchema[], path: strin
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':

View File

@@ -53,7 +53,6 @@ export * from './type/const/index'
export * from './type/constructor/index'
export * from './type/constructor-parameters/index'
export * from './type/date/index'
export * from './type/deref/index'
export * from './type/enum/index'
export * from './type/exclude/index'
export * from './type/extends/index'
@@ -67,6 +66,7 @@ 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'
@@ -90,7 +90,6 @@ export * from './type/rest/index'
export * from './type/return-type/index'
export * from './type/schema/index'
export * from './type/static/index'
export * from './type/strict/index'
export * from './type/string/index'
export * from './type/symbol/index'
export * from './type/template-literal/index'

View File

@@ -26,36 +26,35 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
import * as Types from '../type/index'
import { Static } from './parsebox/index'
import { CreateType } from '../type/create/type'
import { TSchema, SchemaOptions } from '../type/schema/index'
import { StaticDecode } from '../type/static/index'
import { Module } from './runtime'
import { Type } from './static'
import { Main } from './static'
/** `[Experimental]` Infers a TypeBox type from TypeScript syntax. */
export type StaticParseAsSchema<Context extends Record<PropertyKey, TSchema>, Code extends string> = Static.Parse<Type, Code, Context>[0]
/** `[Syntax]` Infers a TypeBox type from TypeScript syntax. */
export type StaticParseAsSchema<Context extends Record<PropertyKey, Types.TSchema>, Code extends string> = Static.Parse<Main, Code, Context>[0]
/** `[Experimental]` Infers a TypeScript type from TypeScript syntax. */
export type StaticParseAsType<Context extends Record<PropertyKey, TSchema>, Code extends string> = StaticParseAsSchema<Context, Code> extends infer Type extends TSchema ? StaticDecode<Type> : undefined
/** `[Syntax]` Infers a TypeScript type from TypeScript syntax. */
export type StaticParseAsType<Context extends Record<PropertyKey, Types.TSchema>, Code extends string> = StaticParseAsSchema<Context, Code> extends infer Type extends Types.TSchema ? Types.StaticDecode<Type> : undefined
/** `[Experimental]` Parses a TypeBox type from TypeScript syntax. */
export function Parse<Context extends Record<PropertyKey, TSchema>, Code extends string>(context: Context, code: Code, options?: SchemaOptions): StaticParseAsSchema<Context, Code>
/** `[Experimental]` Parses a TypeBox type from TypeScript syntax. */
export function Parse<Code extends string>(code: Code, options?: SchemaOptions): StaticParseAsSchema<{}, Code>
/** `[Experimental]` Parses a TypeBox type from TypeScript syntax. */
/** `[Syntax]` Parses a TypeBox type from TypeScript syntax. */
export function Parse<Context extends Record<PropertyKey, Types.TSchema>, Code extends string>(context: Context, code: Code, options?: Types.SchemaOptions): StaticParseAsSchema<Context, Code>
/** `[Syntax]` Parses a TypeBox type from TypeScript syntax. */
export function Parse<Code extends string>(code: Code, options?: Types.SchemaOptions): StaticParseAsSchema<{}, Code>
/** `[Syntax]` Parses a TypeBox type from TypeScript syntax. */
export function Parse(...args: any[]): never {
return ParseOnly.apply(null, args as never) as never
}
/** `[Experimental]` Parses a TypeBox TSchema from TypeScript syntax. This function does not infer the type. */
export function ParseOnly<Context extends Record<PropertyKey, TSchema>, Code extends string>(context: Context, code: Code, options?: SchemaOptions): TSchema | undefined
/** `[Experimental]` Parses a TypeBox TSchema from TypeScript syntax */
export function ParseOnly<Code extends string>(code: Code, options?: SchemaOptions): TSchema | undefined
/** `[Experimental]` Parses a TypeBox TSchema from TypeScript syntax. This function does not infer the type. */
export function ParseOnly(...args: any[]): TSchema | undefined {
/** `[Syntax]` Parses a TypeBox TSchema from TypeScript syntax. This function does not infer the type. */
export function ParseOnly<Context extends Record<PropertyKey, Types.TSchema>, Code extends string>(context: Context, code: Code, options?: Types.SchemaOptions): Types.TSchema | undefined
/** `[Syntax]` Parses a TypeBox TSchema from TypeScript syntax */
export function ParseOnly<Code extends string>(code: Code, options?: Types.SchemaOptions): Types.TSchema | undefined
/** `[Syntax]` Parses a TypeBox TSchema from TypeScript syntax. This function does not infer the type. */
export function ParseOnly(...args: any[]): Types.TSchema | undefined {
const withContext = typeof args[0] === 'string' ? false : true
const [context, code, options] = withContext ? [args[0], args[1], args[2] || {}] : [{}, args[0], args[1] || {}]
const type = Module.Parse('Type', code, context)[0] as TSchema | undefined
return (type !== undefined ? CreateType(type, options) : undefined) as never
const type = Module.Parse('Main', code, context)[0] as Types.TSchema | undefined
// Note: Parsing may return either a ModuleInstance or Type. We only apply options on the Type.
return Types.KindGuard.IsSchema(type) ? Types.CloneType(type, options) : type
}

View File

@@ -48,6 +48,7 @@ const SemiColon = ';'
const SingleQuote = "'"
const DoubleQuote = '"'
const Tilde = '`'
const Equals = '='
// ------------------------------------------------------------------
// DestructureRight
@@ -59,13 +60,140 @@ function DestructureRight<T>(values: T[]): [T[], T | undefined] {
: [values, undefined]
}
// ------------------------------------------------------------------
// Deref
// ------------------------------------------------------------------
const Deref = (context: Types.TProperties, key: string): Types.TSchema => {
return key in context ? context[key] : Types.Ref(key)
}
// ------------------------------------------------------------------
// ExportModifier
// ------------------------------------------------------------------
// prettier-ignore
const ExportModifierMapping = (values: unknown[]) => {
return values.length === 1
}
// prettier-ignore
const ExportModifier = Runtime.Union([
Runtime.Tuple([Runtime.Const('export')]), Runtime.Tuple([])
], ExportModifierMapping)
// ------------------------------------------------------------------
// TypeAliasDeclaration
// ------------------------------------------------------------------
// prettier-ignore
const TypeAliasDeclarationMapping = (_Export: boolean, _Keyword: 'type', Ident: string, _Equals: typeof Equals, Type: Types.TSchema) => {
return { [Ident]: Type }
}
// prettier-ignore
const TypeAliasDeclaration = Runtime.Tuple([
Runtime.Ref<boolean>('ExportModifier'),
Runtime.Const('type'),
Runtime.Ident(),
Runtime.Const(Equals),
Runtime.Ref<Types.TSchema>('Type')
], value => TypeAliasDeclarationMapping(...value))
// ------------------------------------------------------------------
// HeritageList
// ------------------------------------------------------------------
// prettier-ignore (note, heritage list should disallow trailing comma)
const HeritageListDelimiter = Runtime.Union([Runtime.Tuple([Runtime.Const(Comma), Runtime.Const(Newline)]), Runtime.Tuple([Runtime.Const(Comma)])])
// prettier-ignore
const HeritageListMapping = (values: string[], context: Types.TProperties): Types.TSchema[] => {
return (
values.length === 3 ? [Deref(context, values[0]), ...values[2] as never] :
values.length === 1 ? [Deref(context, values[0])] :
[]
) as never
}
// prettier-ignore
const HeritageList = Runtime.Union([
Runtime.Tuple([Runtime.Ident(), HeritageListDelimiter, Runtime.Ref('HeritageList')]),
Runtime.Tuple([Runtime.Ident()]),
Runtime.Tuple([])
], HeritageListMapping)
// ------------------------------------------------------------------
// Heritage
// ------------------------------------------------------------------
// prettier-ignore
const HeritageMapping = (values: unknown[]): unknown[] => {
return (values.length === 2 ? values[1] : []) as never
}
// prettier-ignore
const Heritage = Runtime.Union([
Runtime.Tuple([Runtime.Const('extends'), Runtime.Ref('HeritageList')]),
Runtime.Tuple([])
], HeritageMapping)
// ------------------------------------------------------------------
// InterfaceDeclaration
// ------------------------------------------------------------------
// prettier-ignore
const InterfaceDeclarationMapping = (_0: boolean, _1: 'interface', Ident: string, Heritage: Types.TRef[], _4: typeof LBrace, Properties: Types.TProperties, _6: typeof RBrace) => {
return { [Ident]: Types.Intersect([...Heritage, Types.Object(Properties)]) }
}
// prettier-ignore
const InterfaceDeclaration = Runtime.Tuple([
Runtime.Ref<boolean>('ExportModifier'),
Runtime.Const('interface'),
Runtime.Ident(),
Runtime.Ref<Types.TRef[]>('Heritage'),
Runtime.Const(LBrace),
Runtime.Ref<Types.TProperties>('Properties'),
Runtime.Const(RBrace),
], values => InterfaceDeclarationMapping(...values))
// ------------------------------------------------------------------
// ModuleType
// ------------------------------------------------------------------
// prettier-ignore
const ModuleType = Runtime.Union([
Runtime.Ref('InterfaceDeclaration'),
Runtime.Ref('TypeAliasDeclaration')
])
// ------------------------------------------------------------------
// ModuleProperties
// ------------------------------------------------------------------
// prettier-ignore
const ModulePropertiesDelimiter = Runtime.Union([
Runtime.Tuple([Runtime.Const(SemiColon), Runtime.Const(Newline)]),
Runtime.Tuple([Runtime.Const(SemiColon)]),
Runtime.Tuple([Runtime.Const(Newline)]),
])
// prettier-ignore
const ModulePropertiesMapping = (values: unknown[]): Types.TProperties => {
return (
values.length === 3 ? { ...values[0] as Types.TProperties, ...values[2] as Types.TProperties }:
values.length === 1 ? values[0] as Types.TProperties :
{} as Types.TProperties
)
}
// prettier-ignore
const ModuleProperties = Runtime.Union([
Runtime.Tuple([Runtime.Ref<Types.TProperties>('ModuleType'), ModulePropertiesDelimiter, Runtime.Ref<Types.TProperties>('ModuleProperties')]),
Runtime.Tuple([Runtime.Ref<Types.TProperties>('ModuleType')]),
Runtime.Tuple([]),
], ModulePropertiesMapping)
// ------------------------------------------------------------------
// ModuleDeclaration
// ------------------------------------------------------------------
// prettier-ignore
const ModuleIdentifier = Runtime.Union([
Runtime.Tuple([Runtime.Ident()]),
Runtime.Tuple([])
])
// prettier-ignore
const ModuleDeclarationMapping = (_1: boolean, _2: 'module', _Ident: string[], _3: typeof LBrace, Properties: Types.TProperties, _5: typeof RBrace) => {
return Types.Module(Properties)
}
// prettier-ignore
const ModuleDeclaration = Runtime.Tuple([
Runtime.Ref<boolean>('ExportModifier'), Runtime.Const('module'), ModuleIdentifier, Runtime.Const(LBrace), Runtime.Ref<Types.TProperties>('ModuleProperties'), Runtime.Const(RBrace)
], values => ModuleDeclarationMapping(...values))
// ------------------------------------------------------------------
// Reference
// ------------------------------------------------------------------
// prettier-ignore
const Reference = Runtime.Ident((value, context: Record<PropertyKey, Types.TSchema>) => {
return value in context ? context[value] : Types.Ref(value)
})
const Reference = Runtime.Ident((value, context: Types.TProperties) => Deref(context, value))
// ------------------------------------------------------------------
// Literal
// ------------------------------------------------------------------
@@ -75,7 +203,6 @@ const Literal = Runtime.Union([
Runtime.Number(value => Types.Literal(parseFloat(value))),
Runtime.String([SingleQuote, DoubleQuote, Tilde], value => Types.Literal(value))
])
// ------------------------------------------------------------------
// Keyword
// ------------------------------------------------------------------
@@ -94,7 +221,6 @@ const Keyword = Runtime.Union([
Runtime.Const('unknown', Runtime.As(Types.Unknown())),
Runtime.Const('void', Runtime.As(Types.Void())),
])
// ------------------------------------------------------------------
// KeyOf
// ------------------------------------------------------------------
@@ -106,7 +232,6 @@ const KeyOfMapping = (values: unknown[]) => (
const KeyOf = Runtime.Union([
Runtime.Tuple([Runtime.Const('keyof')]), Runtime.Tuple([])
], KeyOfMapping)
// ------------------------------------------------------------------
// IndexArray
// ------------------------------------------------------------------
@@ -137,7 +262,6 @@ const Extends = Runtime.Union([
Runtime.Tuple([Runtime.Const('extends'), Runtime.Ref('Type'), Runtime.Const(Question), Runtime.Ref('Type'), Runtime.Const(Colon), Runtime.Ref('Type')]),
Runtime.Tuple([])
], ExtendsMapping)
// ------------------------------------------------------------------
// Base
// ------------------------------------------------------------------
@@ -219,7 +343,6 @@ const Factor = Runtime.Tuple([
Runtime.Ref<unknown[]>('IndexArray'),
Runtime.Ref<Types.TSchema[]>('Extends')
], values => FactorMapping(...values))
// ------------------------------------------------------------------
// Expr
// ------------------------------------------------------------------
@@ -266,30 +389,26 @@ const Expr = Runtime.Tuple([
// Type
// ------------------------------------------------------------------
const Type = Runtime.Ref('Expr')
// ------------------------------------------------------------------
// Properties
// ------------------------------------------------------------------
// prettier-ignore
const PropertyKey = Runtime.Union([Runtime.Ident(), Runtime.String([SingleQuote, DoubleQuote])])
const Readonly = Runtime.Union([Runtime.Tuple([Runtime.Const('readonly')]), Runtime.Tuple([])], (value) => value.length > 0)
const Optional = Runtime.Union([Runtime.Tuple([Runtime.Const(Question)]), Runtime.Tuple([])], (value) => value.length > 0)
// prettier-ignore
const PropertyReadonly = Runtime.Union([Runtime.Tuple([Runtime.Const('readonly')]), Runtime.Tuple([])], value => value.length > 0)
// prettier-ignore
const PropertyOptional = Runtime.Union([Runtime.Tuple([Runtime.Const(Question)]), Runtime.Tuple([])], value => value.length > 0)
// prettier-ignore
const PropertyMapping = (Readonly: boolean, Key: string, Optional: boolean, _: typeof Colon, Type: Types.TSchema) => ({
const PropertyMapping = (IsReadonly: boolean, Key: string, IsOptional: boolean, _: typeof Colon, Type: Types.TSchema) => ({
[Key]: (
Readonly && Optional ? Types.ReadonlyOptional(Type) :
Readonly && !Optional ? Types.Readonly(Type) :
!Readonly && Optional ? Types.Optional(Type) :
IsReadonly && IsOptional ? Types.ReadonlyOptional(Type) :
IsReadonly && !IsOptional ? Types.Readonly(Type) :
!IsReadonly && IsOptional ? Types.Optional(Type) :
Type
)
})
// prettier-ignore
const Property = Runtime.Tuple([
Runtime.Ref<boolean>('PropertyReadonly'),
Runtime.Ref<boolean>('Readonly'),
Runtime.Ref<string>('PropertyKey'),
Runtime.Ref<boolean>('PropertyOptional'),
Runtime.Ref<boolean>('Optional'),
Runtime.Const(Colon),
Runtime.Ref<Types.TSchema>('Type'),
], value => PropertyMapping(...value))
@@ -303,30 +422,27 @@ const PropertyDelimiter = Runtime.Union([
])
// prettier-ignore
const Properties = Runtime.Union([
Runtime.Tuple([Runtime.Ref('Property'), Runtime.Ref('PropertyDelimiter'), Runtime.Ref('Properties')]),
Runtime.Tuple([Runtime.Ref('Property'), Runtime.Ref('PropertyDelimiter')]),
Runtime.Tuple([Runtime.Ref('Property')]),
Runtime.Tuple([Runtime.Ref<Types.TProperties>('Property'), Runtime.Ref('PropertyDelimiter'), Runtime.Ref<Types.TProperties>('Properties')]),
Runtime.Tuple([Runtime.Ref<Types.TProperties>('Property'), Runtime.Ref('PropertyDelimiter')]),
Runtime.Tuple([Runtime.Ref<Types.TProperties>('Property')]),
Runtime.Tuple([])
], values => (
values.length === 3 ? [values[0], ...values[2] as unknown[]] :
values.length === 2 ? [values[0]] :
values.length === 1 ? [values[0]] :
[]
values.length === 3 ? { ...values[0], ...values[2] } :
values.length === 2 ? values[0] :
values.length === 1 ? values[0] :
{}
))
// ------------------------------------------------------------------
// Object
// ------------------------------------------------------------------
// prettier-ignore
const ObjectMapping = (values: Record<string, Types.TSchema>[]) => Types.Object(values.reduce((properties, record) => {
return { ...properties, ...record }
}, {} as Types.TProperties))
const ObjectMapping = (_0: typeof LBrace, Properties: Types.TProperties, _2: typeof RBrace) => Types.Object(Properties)
// prettier-ignore
const _Object = Runtime.Tuple([
Runtime.Const(LBrace),
Runtime.Ref<Record<string, Types.TSchema>[]>('Properties'),
Runtime.Ref<Types.TProperties>('Properties'),
Runtime.Const(RBrace)
], values => ObjectMapping(values[1]))
], values => ObjectMapping(...values))
// ------------------------------------------------------------------
// Tuple
@@ -401,7 +517,15 @@ const MappedMapping = (values: unknown[]) => {
}
// prettier-ignore
const Mapped = Runtime.Tuple([
Runtime.Const(LBrace), Runtime.Const(LBracket), Runtime.Ident(), Runtime.Const('in'), Runtime.Ref<Types.TSchema>('Type'), Runtime.Const(RBracket), Runtime.Const(Colon), Runtime.Ref<Types.TSchema>('Type'), Runtime.Const(RBrace)
Runtime.Const(LBrace),
Runtime.Const(LBracket),
Runtime.Ident(),
Runtime.Const('in'),
Runtime.Ref<Types.TSchema>('Type'),
Runtime.Const(RBracket),
Runtime.Const(Colon),
Runtime.Ref<Types.TSchema>('Type'),
Runtime.Const(RBrace)
], MappedMapping)
// ------------------------------------------------------------------
// AsyncIterator
@@ -621,11 +745,37 @@ const Date = Runtime.Const('Date', Runtime.As(Types.Date()))
// Uint8Array
// ------------------------------------------------------------------
const Uint8Array = Runtime.Const('Uint8Array', Runtime.As(Types.Uint8Array()))
// ------------------------------------------------------------------
// Main
// ------------------------------------------------------------------
// prettier-ignore
const Main = Runtime.Union([
ModuleDeclaration,
TypeAliasDeclaration,
InterfaceDeclaration,
Type
])
// ------------------------------------------------------------------
// Module
// ------------------------------------------------------------------
// prettier-ignore
export const Module = new Runtime.Module({
// ----------------------------------------------------------------
// Modules, Interfaces and Type Aliases
// ----------------------------------------------------------------
ExportModifier,
HeritageList,
Heritage,
InterfaceDeclaration,
TypeAliasDeclaration,
ModuleType,
ModuleProperties,
ModuleDeclaration,
// ----------------------------------------------------------------
// Type Expressions
// ----------------------------------------------------------------
Literal,
Keyword,
KeyOf,
@@ -637,10 +787,10 @@ export const Module = new Runtime.Module({
ExprTerm,
ExprTail,
Expr,
Type,
Type, // Alias for Expr
PropertyKey,
PropertyReadonly,
PropertyOptional,
Readonly,
Optional,
Property,
PropertyDelimiter,
Properties,
@@ -674,5 +824,10 @@ export const Module = new Runtime.Module({
Uncapitalize,
Date,
Uint8Array,
Reference
Reference,
// ----------------------------------------------------------------
// Main
// ----------------------------------------------------------------
Main
})

View File

@@ -48,6 +48,7 @@ type SemiColon = ';'
type SingleQuote = "'"
type DoubleQuote = '"'
type Tilde = '`'
type Equals = '='
// ------------------------------------------------------------------
// Delimit
@@ -135,18 +136,156 @@ type Delimit<Parser extends Static.IParser, Delimiter extends Static.IParser> =
], DelimitMapping>
)
// ------------------------------------------------------------------
// Deref
// ------------------------------------------------------------------
// prettier-ignore
type Deref<Context extends Types.TProperties, Ref extends string> = (
Ref extends keyof Context ? Context[Ref] : Types.TRef<Ref>
)
// ------------------------------------------------------------------
// ExportModifier
// ------------------------------------------------------------------
// prettier-ignore
interface ExportModifierMapping extends Static.IMapping {
output: this['input'] extends [string] ? true : false
}
// prettier-ignore
type ExportModifier = Static.Union<[
Static.Tuple<[Static.Const<'export'>]>,
Static.Tuple<[]>,
], ExportModifierMapping>
// ------------------------------------------------------------------
// TypeAliasDeclaration
// ------------------------------------------------------------------
// prettier-ignore
interface TypeAliasDeclarationMapping extends Static.IMapping {
output: this['input'] extends [infer _Export extends boolean, 'type', infer Ident extends string, Equals, infer Type extends Types.TSchema]
? { [_ in Ident]: Type }
: never
}
// prettier-ignore
type TypeAliasDeclaration = Static.Tuple<[
ExportModifier, Static.Const<'type'>, Static.Ident, Static.Const<Equals>, Type
], TypeAliasDeclarationMapping>
// ------------------------------------------------------------------
// HeritageList
// ------------------------------------------------------------------
// prettier-ignore (note, heritage list should disallow trailing comma)
type HeritageListDelimiter = Static.Union<[Static.Tuple<[Static.Const<Comma>, Static.Const<Newline>]>, Static.Tuple<[Static.Const<Comma>]>]>
// prettier-ignore
type HeritageListReduce<Values extends string[], Context extends Types.TProperties, Result extends Types.TSchema[] = []> = (
Values extends [infer Ref extends string, ...infer Rest extends string[]]
? HeritageListReduce<Rest, Context, [...Result, Deref<Context, Ref>]>
: Result
)
// prettier-ignore
interface HeritageListMapping extends Static.IMapping {
output: (
this['context'] extends Types.TProperties ?
this['input'] extends string[]
? HeritageListReduce<this['input'], this['context']>
: []
: []
)
}
// prettier-ignore
type HeritageList = Static.Union<[Delimit<Static.Ident, HeritageListDelimiter>], HeritageListMapping>
// ------------------------------------------------------------------
// Heritage
// ------------------------------------------------------------------
// prettier-ignore
interface HeritageMapping extends Static.IMapping {
output: this['input'] extends ['extends', infer List extends Types.TSchema[]] ? List : []
}
// prettier-ignore
type Heritage = Static.Union<[
Static.Tuple<[Static.Const<'extends'>, HeritageList]>,
Static.Tuple<[]>
], HeritageMapping>
// ------------------------------------------------------------------
// InterfaceDeclaration
// ------------------------------------------------------------------
// prettier-ignore
interface InterfaceDeclarationMapping extends Static.IMapping {
output: this['input'] extends [boolean, 'interface', infer Ident extends string, infer Heritage extends Types.TSchema[], LBrace, infer Properties extends Types.TProperties, RBrace]
? { [_ in Ident]: Types.TIntersectEvaluated<[...Heritage, Types.TObject<Properties>]> }
: never
}
// prettier-ignore
type InterfaceDeclaration = Static.Tuple<[
ExportModifier,
Static.Const<'interface'>,
Static.Ident,
Heritage,
Static.Const<LBrace>,
Properties,
Static.Const<RBrace>,
], InterfaceDeclarationMapping>
// ------------------------------------------------------------------
// ModuleType
// ------------------------------------------------------------------
// prettier-ignore
type ModuleType = Static.Union<[
InterfaceDeclaration,
TypeAliasDeclaration
]>
// ------------------------------------------------------------------
// ModuleProperties
// ------------------------------------------------------------------
// prettier-ignore
type ModulePropertiesDelimiter = Static.Union<[
Static.Tuple<[Static.Const<SemiColon>, Static.Const<Newline>]>,
Static.Tuple<[Static.Const<SemiColon>]>,
Static.Tuple<[Static.Const<Newline>]>,
]>
// prettier-ignore
type ModulePropertiesReduce<Value extends unknown[], Result extends Types.TProperties = {}> = (
Value extends [infer ModuleType extends Types.TProperties, unknown[], ...infer Rest extends unknown[]] ? ModulePropertiesReduce<Rest, Result & ModuleType> :
Value extends [infer ModuleType extends Types.TProperties] ? ModulePropertiesReduce<[], Result & ModuleType> :
Types.Evaluate<Result>
)
// prettier-ignore
interface ModulePropertiesMapping extends Static.IMapping {
output: this['input'] extends unknown[] ? ModulePropertiesReduce<this['input']> : never
}
// prettier-ignore
type ModuleProperties = Static.Union<[
Static.Tuple<[ModuleType, ModulePropertiesDelimiter, ModuleProperties]>,
Static.Tuple<[ModuleType]>,
Static.Tuple<[]>,
], ModulePropertiesMapping>
// ------------------------------------------------------------------
// ModuleDeclaration
// ------------------------------------------------------------------
// prettier-ignore
type ModuleIdentifier = Static.Union<[
Static.Tuple<[Static.Ident]>,
Static.Tuple<[]>
]>
// prettier-ignore
interface ModuleDeclarationMapping extends Static.IMapping {
output: this['input'] extends [boolean, 'module', infer _Ident extends string[], LBrace, infer Properties extends Types.TProperties, RBrace]
? Types.TModule<Properties>
: never
}
// prettier-ignore
type ModuleDeclaration = Static.Tuple<[
ExportModifier, Static.Const<'module'>, ModuleIdentifier, Static.Const<LBrace>, ModuleProperties, Static.Const<RBrace>
], ModuleDeclarationMapping>
// ------------------------------------------------------------------
// Reference
// ------------------------------------------------------------------
// prettier-ignore
interface ReferenceMapping extends Static.IMapping {
output: this['input'] extends [infer Key extends string]
? Key extends keyof this['context']
? this['context'][Key]
: Types.TRef<Types.TUnknown>
: never
output: this['context'] extends Types.TProperties
? this['input'] extends string
? Deref<this['context'], this['input']>
: never
: never
}
type Reference = Static.Tuple<[Static.Ident], ReferenceMapping>
type Reference = Static.Ident<ReferenceMapping>
// ------------------------------------------------------------------
// Literal
// ------------------------------------------------------------------
@@ -198,7 +337,6 @@ type KeyOf = Static.Union<[
Static.Tuple<[Static.Const<'keyof'>]>,
Static.Tuple<[]>
], KeyOfMapping>
// ------------------------------------------------------------------
// IndexArray
// ------------------------------------------------------------------
@@ -231,7 +369,6 @@ type Extends = Static.Union<[
Static.Tuple<[Static.Const<'extends'>, Type, Static.Const<Question>, Type, Static.Const<Colon>, Type]>,
Static.Tuple<[]>
], ExtendsMapping>
// ------------------------------------------------------------------
// Base
// ------------------------------------------------------------------
@@ -361,7 +498,7 @@ type Expr = Static.Tuple<[
// ------------------------------------------------------------------
// Type
// ------------------------------------------------------------------
export type Type = Expr
type Type = Expr
// ------------------------------------------------------------------
// Properties
// ------------------------------------------------------------------
@@ -370,34 +507,30 @@ interface PropertyKeyStringMapping extends Static.IMapping {
output: this['input']
}
type PropertyKeyString = Static.String<[SingleQuote, DoubleQuote], PropertyKeyStringMapping>
type PropertyKey = Static.Union<[Static.Ident, PropertyKeyString]>
// prettier-ignore
interface PropertyReadonlyMapping extends Static.IMapping {
interface ReadonlyMapping extends Static.IMapping {
output: this['input'] extends ['readonly'] ? true : false
}
type PropertyReadonly = Static.Union<[Static.Tuple<[Static.Const<'readonly'>]>, Static.Tuple<[]>], PropertyReadonlyMapping>
type Readonly = Static.Union<[Static.Tuple<[Static.Const<'readonly'>]>, Static.Tuple<[]>], ReadonlyMapping>
// prettier-ignore
interface PropertyOptionalMapping extends Static.IMapping {
interface OptionalMapping extends Static.IMapping {
output: this['input'] extends [Question] ? true : false
}
type PropertyOptional = Static.Union<[Static.Tuple<[Static.Const<Question>]>, Static.Tuple<[]>], PropertyOptionalMapping>
type Optional = Static.Union<[Static.Tuple<[Static.Const<Question>]>, Static.Tuple<[]>], OptionalMapping>
// prettier-ignore
interface PropertyMapping extends Static.IMapping {
output: this['input'] extends [infer Readonly extends boolean, infer Key extends string, infer Optional extends boolean, string, infer Type extends Types.TSchema]
output: this['input'] extends [infer IsReadonly extends boolean, infer Key extends string, infer IsOptional extends boolean, string, infer Type extends Types.TSchema]
? {
[_ in Key]: (
[Readonly, Optional] extends [true, true] ? Types.TReadonlyOptional<Type> :
[Readonly, Optional] extends [true, false] ? Types.TReadonly<Type> :
[Readonly, Optional] extends [false, true] ? Types.TOptional<Type> :
[IsReadonly, IsOptional] extends [true, true] ? Types.TReadonlyOptional<Type> :
[IsReadonly, IsOptional] extends [true, false] ? Types.TReadonly<Type> :
[IsReadonly, IsOptional] extends [false, true] ? Types.TOptional<Type> :
Type
)
} : never
}
type Property = Static.Tuple<[PropertyReadonly, PropertyKey, PropertyOptional, Static.Const<Colon>, Type], PropertyMapping>
type PropertiesEvaluate<T> = { [K in keyof T]: T[K] } & {}
type Property = Static.Tuple<[Readonly, PropertyKey, Optional, Static.Const<Colon>, Type], PropertyMapping>
// prettier-ignore
type PropertyDelimiter = Static.Union<[
Static.Tuple<[Static.Const<Comma>, Static.Const<Newline>]>,
@@ -409,7 +542,7 @@ type PropertyDelimiter = Static.Union<[
// prettier-ignore
type PropertiesReduce<PropertiesArray extends Types.TProperties[], Result extends Types.TProperties = {}> = (
PropertiesArray extends [infer Left extends Types.TProperties, ...infer Right extends Types.TProperties[]]
? PropertiesReduce<Right, PropertiesEvaluate<Result & Left>>
? PropertiesReduce<Right, Types.Evaluate<Result & Left>>
: Result
)
// prettier-ignore
@@ -485,7 +618,7 @@ type Constructor = Static.Tuple<[
// ------------------------------------------------------------------
// prettier-ignore
interface MappedMapping extends Static.IMapping {
output: this['input'] extends [LBrace, LBracket, infer Key extends string, 'in', infer Right extends Types.TSchema, RBracket, Colon, infer Type extends Types.TSchema, RBrace]
output: this['input'] extends [LBrace, LBracket, infer _Key extends string, 'in', infer _Right extends Types.TSchema, RBracket, Colon, infer Type extends Types.TSchema, RBrace]
? Types.TLiteral<'Mapped types not supported'>
: this['input']
}
@@ -762,3 +895,14 @@ type Date = Static.Const<'Date', Static.As<Types.TDate>>
// Uint8Array
// ------------------------------------------------------------------
type Uint8Array = Static.Const<'Uint8Array', Static.As<Types.TUint8Array>>
// ------------------------------------------------------------------
// Main
// ------------------------------------------------------------------
// prettier-ignore
export type Main = Static.Union<[
ModuleDeclaration,
TypeAliasDeclaration,
InterfaceDeclaration,
Type
]>

View File

@@ -1,174 +0,0 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/type
The MIT License (MIT)
Copyright (c) 2017-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 type { TSchema } from '../schema/index'
import type { Evaluate } from '../helpers/index'
import type { TTuple } from '../tuple/index'
import type { TIntersect } from '../intersect/index'
import type { TUnion } from '../union/index'
import type { TPromise } from '../promise/index'
import type { TAsyncIterator } from '../async-iterator/index'
import type { TIterator } from '../iterator/index'
import type { TArray } from '../array/index'
import type { TConstructor } from '../constructor/index'
import type { TFunction } from '../function/index'
import type { TRef } from '../ref/index'
import type { TObject, TProperties } from '../object/index'
import { CloneType, CloneRest } from '../clone/type'
import { Discard } from '../discard/index'
import { IsUndefined } from '../guard/value'
// ------------------------------------------------------------------
// TypeGuard
// ------------------------------------------------------------------
import { IsConstructor, IsFunction, IsIntersect, IsUnion, IsTuple, IsArray, IsObject, IsPromise, IsAsyncIterator, IsIterator, IsRef } from '../guard/kind'
// ------------------------------------------------------------------
// FromRest
// ------------------------------------------------------------------
// prettier-ignore
export type TFromRest<T extends TSchema[], Acc extends TSchema[] = []> = (
T extends [infer L extends TSchema, ...infer R extends TSchema[]]
? TFromRest<R, [...Acc, TDeref<L>]>
: Acc
)
function FromRest<T extends TSchema[]>(schema: [...T], references: TSchema[]): TFromRest<T> {
return schema.map((schema) => Deref(schema, references)) as never
}
// ------------------------------------------------------------------
// FromProperties
// ------------------------------------------------------------------
// prettier-ignore
type FromProperties<T extends TProperties> = Evaluate<{
[K in keyof T]: TDeref<T[K]>
}>
// prettier-ignore
function FromProperties(properties: TProperties, references: TSchema[]) {
const Acc = {} as TProperties
for(const K of globalThis.Object.getOwnPropertyNames(properties)) {
Acc[K] = Deref(properties[K], references)
}
return Acc as never
}
// prettier-ignore
function FromConstructor(schema: TConstructor, references: TSchema[]) {
schema.parameters = FromRest(schema.parameters, references)
schema.returns = Deref(schema.returns, references)
return schema
}
// prettier-ignore
function FromFunction(schema: TFunction, references: TSchema[]) {
schema.parameters = FromRest(schema.parameters, references)
schema.returns = Deref(schema.returns, references)
return schema
}
// prettier-ignore
function FromIntersect(schema: TIntersect, references: TSchema[]) {
schema.allOf = FromRest(schema.allOf, references)
return schema
}
// prettier-ignore
function FromUnion(schema: TUnion, references: TSchema[]) {
schema.anyOf = FromRest(schema.anyOf, references)
return schema
}
// prettier-ignore
function FromTuple(schema: TTuple, references: TSchema[]) {
if(IsUndefined(schema.items)) return schema
schema.items = FromRest(schema.items, references)
return schema
}
// prettier-ignore
function FromArray(schema: TArray, references: TSchema[]) {
schema.items = Deref(schema.items, references)
return schema
}
// prettier-ignore
function FromObject(schema: TObject, references: TSchema[]) {
schema.properties = FromProperties(schema.properties, references)
return schema
}
// prettier-ignore
function FromPromise(schema: TPromise, references: TSchema[]) {
schema.item = Deref(schema.item, references)
return schema
}
// prettier-ignore
function FromAsyncIterator(schema: TAsyncIterator, references: TSchema[]) {
schema.items = Deref(schema.items, references)
return schema
}
// prettier-ignore
function FromIterator(schema: TIterator, references: TSchema[]) {
schema.items = Deref(schema.items, references)
return schema
}
// prettier-ignore
function FromRef(schema: TRef, references: TSchema[]) {
const target = references.find(remote => remote.$id === schema.$ref)
if(target === undefined) throw Error(`Unable to dereference schema with $id ${schema.$ref}`)
const discard = Discard(target, ['$id']) as TSchema
return Deref(discard, references)
}
// prettier-ignore
function DerefResolve<T extends TSchema>(schema: T, references: TSchema[]): TDeref<T> {
return (
IsConstructor(schema) ? FromConstructor(schema, references) :
IsFunction(schema) ? FromFunction(schema, references) :
IsIntersect(schema) ? FromIntersect(schema, references) :
IsUnion(schema) ? FromUnion(schema, references) :
IsTuple(schema) ? FromTuple(schema, references) :
IsArray(schema) ? FromArray(schema, references) :
IsObject(schema) ? FromObject(schema, references) :
IsPromise(schema) ? FromPromise(schema, references) :
IsAsyncIterator(schema) ? FromAsyncIterator(schema, references) :
IsIterator(schema) ? FromIterator(schema, references) :
IsRef(schema) ? FromRef(schema, references) :
schema
) as never
}
// prettier-ignore
export type TDeref<T extends TSchema> =
T extends TConstructor<infer S extends TSchema[], infer R extends TSchema> ? TConstructor<TFromRest<S>, TDeref<R>> :
T extends TFunction<infer S extends TSchema[], infer R extends TSchema> ? TFunction<TFromRest<S>, TDeref<R>> :
T extends TIntersect<infer S extends TSchema[]> ? TIntersect<TFromRest<S>> :
T extends TUnion<infer S extends TSchema[]> ? TUnion<TFromRest<S>> :
T extends TTuple<infer S extends TSchema[]> ? TTuple<TFromRest<S>> :
T extends TObject<infer S extends TProperties> ? TObject<FromProperties<S>> :
T extends TArray<infer S extends TSchema> ? TArray<TDeref<S>> :
T extends TPromise<infer S extends TSchema> ? TPromise<TDeref<S>> :
T extends TAsyncIterator<infer S extends TSchema> ? TAsyncIterator<TDeref<S>> :
T extends TIterator<infer S extends TSchema> ? TIterator<TDeref<S>> :
T extends TRef<infer S extends TSchema> ? TDeref<S> :
T
// ------------------------------------------------------------------
// TDeref
// ------------------------------------------------------------------
/** `[Json]` Creates a dereferenced type */
export function Deref<T extends TSchema>(schema: T, references: TSchema[]): TDeref<T> {
return DerefResolve(CloneType(schema), CloneRest(references))
}

View File

@@ -40,6 +40,7 @@ import type { TAsyncIterator } from '../async-iterator/index'
import type { TBigInt } from '../bigint/index'
import type { TConstructor } from '../constructor/index'
import type { TFunction } from '../function/index'
import type { TImport } from '../module/index'
import type { TInteger } from '../integer/index'
import type { TIntersect } from '../intersect/index'
import type { TIterator } from '../iterator/index'
@@ -107,6 +108,10 @@ export function IsFunction(value: unknown): value is TFunction {
return IsKindOf(value, 'Function')
}
/** `[Kind-Only]` Returns true if the given value is TInteger */
export function IsImport(value: unknown): value is TImport {
return IsKindOf(value, 'Import')
}
/** `[Kind-Only]` Returns true if the given value is TInteger */
export function IsInteger(value: unknown): value is TInteger {
return IsKindOf(value, 'Integer')
}

View File

@@ -41,6 +41,7 @@ import type { TAsyncIterator } from '../async-iterator/index'
import type { TBigInt } from '../bigint/index'
import type { TConstructor } from '../constructor/index'
import type { TFunction } from '../function/index'
import type { TImport } from '../module/index'
import type { TInteger } from '../integer/index'
import type { TIntersect } from '../intersect/index'
import type { TIterator } from '../iterator/index'
@@ -253,6 +254,19 @@ export function IsFunction(value: unknown): value is TFunction {
IsSchema(value.returns)
)
}
/** Returns true if the given value is TImport */
export function IsImport(value: unknown): value is TImport {
// prettier-ignore
return (
IsKindOf(value, 'Import') &&
ValueGuard.HasPropertyKey(value, '$defs') &&
ValueGuard.IsObject(value.$defs) &&
IsProperties(value.$defs) &&
ValueGuard.HasPropertyKey(value, '$ref') &&
ValueGuard.IsString(value.$ref) &&
value.$ref in value.$defs // required
)
}
/** Returns true if the given value is TInteger */
export function IsInteger(value: unknown): value is TInteger {
return (

View File

@@ -38,7 +38,6 @@ export * from './const/index'
export * from './constructor/index'
export * from './constructor-parameters/index'
export * from './date/index'
export * from './deref/index'
export * from './discard/index'
export * from './enum/index'
export * from './error/index'
@@ -57,6 +56,7 @@ export * from './iterator/index'
export * from './keyof/index'
export * from './literal/index'
export * from './mapped/index'
export * from './module/index'
export * from './never/index'
export * from './not/index'
export * from './null/index'
@@ -82,7 +82,6 @@ export * from './return-type/index'
export * from './schema/index'
export * from './sets/index'
export * from './static/index'
export * from './strict/index'
export * from './string/index'
export * from './symbol/index'
export * from './symbols/index'

View File

@@ -26,4 +26,4 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
export * from './deref'
export * from './module'

189
src/type/module/module.ts Normal file
View File

@@ -0,0 +1,189 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/type
The MIT License (MIT)
Copyright (c) 2017-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 { Ensure } from '../helpers/index'
import { CreateType } from '../create/index'
import { Kind } from '../symbols/index'
import { SchemaOptions, TSchema } from '../schema/index'
import { TObject, TProperties } from '../object/index'
import { TConstructor } from '../constructor/index'
import { TFunction } from '../function/index'
import { TTuple } from '../tuple/index'
import { TIntersect } from '../intersect/index'
import { TUnion } from '../union/index'
import { TArray } from '../array/index'
import { TAsyncIterator } from '../async-iterator/index'
import { TIterator } from '../iterator/index'
import { TLiteral, TLiteralValue } from '../literal/index'
import { TAny } from '../any/index'
import { TBigInt } from '../bigint/index'
import { TBoolean } from '../boolean/index'
import { TDate } from '../date/index'
import { TInteger } from '../integer/index'
import { TNever } from '../never/index'
import { TNumber } from '../number/index'
import { TNull } from '../null/index'
import { TRef } from '../ref/index'
import { TRegExp } from '../regexp/index'
import { TString } from '../string/index'
import { TSymbol } from '../symbol/index'
import { TTemplateLiteral, TTemplateLiteralKind } from '../template-literal/index'
import { TUint8Array } from '../uint8array/index'
import { TUndefined } from '../undefined/index'
import { TUnknown } from '../unknown/index'
import { TVoid } from '../void/index'
import { Static } from '../static/index'
// ------------------------------------------------------------------
// Infer
// ------------------------------------------------------------------
// prettier-ignore
type InferImport<Properties extends TProperties, Key extends keyof Properties, ModuleProperties extends TProperties> = (
Infer<Properties[Key], ModuleProperties>
)
// prettier-ignore
type InferRef<Ref extends string, Properties extends TProperties> = (
Ref extends keyof Properties ? Infer<Properties[Ref], Properties> : never
)
// prettier-ignore
type InferObject<Properties extends TProperties, ModuleProperties extends TProperties> = {
[K in keyof Properties]: Infer<Properties[K], ModuleProperties>
} & {}
// prettier-ignore
type InferConstructor<Parameters extends TSchema[], InstanceType extends TSchema, Properties extends TProperties> = Ensure<
new (...args: InferTuple<Parameters, Properties>) => Infer<InstanceType, Properties>
>
// prettier-ignore
type InferFunction<Parameters extends TSchema[], ReturnType extends TSchema, Properties extends TProperties> = Ensure<
(...args: InferTuple<Parameters, Properties>) => Infer<ReturnType, Properties>
>
// prettier-ignore
type InferTuple<Types extends TSchema[], Properties extends TProperties, Result extends unknown[] = []> = (
Types extends [infer L extends TSchema, ...infer R extends TSchema[]]
? InferTuple<R, Properties, [...Result, Infer<L, Properties>]>
: Result
)
// prettier-ignore
type InferIntersect<Types extends TSchema[], Properties extends TProperties, Result extends unknown = unknown> = (
Types extends [infer L extends TSchema, ...infer R extends TSchema[]]
? InferIntersect<R, Properties, Result & Infer<L, Properties>>
: Result
)
// prettier-ignore
type InferUnion<Types extends TSchema[], Properties extends TProperties, Result extends unknown = never> = (
Types extends [infer L extends TSchema, ...infer R extends TSchema[]]
? InferUnion<R, Properties, Result | Infer<L, Properties>>
: Result
)
// prettier-ignore
type InferArray<Type extends TSchema, Module extends TProperties> = (
Ensure<Array<Infer<Type, Module>>>
)
// prettier-ignore
type InferAsyncIterator<Type extends TSchema, Properties extends TProperties> = (
Ensure<AsyncIterableIterator<Infer<Type, Properties>>>
)
// prettier-ignore
type InferIterator<Type extends TSchema, Properties extends TProperties> = (
Ensure<IterableIterator<Infer<Type, Properties>>>
)
// prettier-ignore
type Infer<Type extends TSchema, Properties extends TProperties = {}> = (
Type extends TImport<infer S extends TProperties, infer K extends string> ? InferImport<S, K, S> :
Type extends TRef<infer S extends string> ? InferRef<S, Properties> :
Type extends TObject<infer S extends TProperties> ? InferObject<S, Properties> :
Type extends TConstructor<infer S extends TSchema[], infer R extends TSchema> ? InferConstructor<S, R, Properties> :
Type extends TFunction<infer S extends TSchema[], infer R extends TSchema> ? InferFunction<S, R, Properties> :
Type extends TTuple<infer S extends TSchema[]> ? InferTuple<S, Properties> :
Type extends TIntersect<infer S extends TSchema[]> ? InferIntersect<S, Properties> :
Type extends TUnion<infer S extends TSchema[]> ? InferUnion<S, Properties> :
Type extends TArray<infer S extends TSchema> ? InferArray<S, Properties> :
Type extends TAsyncIterator<infer S extends TSchema> ? InferAsyncIterator<S, Properties> :
Type extends TIterator<infer S extends TSchema> ? InferIterator<S, Properties> :
Type extends TTemplateLiteral<infer S extends TTemplateLiteralKind[]> ? Static<TTemplateLiteral<S>> :
Type extends TLiteral<infer S extends TLiteralValue> ? S :
Type extends TAny ? any :
Type extends TBigInt ? bigint :
Type extends TBoolean ? boolean :
Type extends TDate ? Date :
Type extends TInteger ? number :
Type extends TNever ? never :
Type extends TNumber ? number :
Type extends TRegExp ? string :
Type extends TString ? string :
Type extends TSymbol ? symbol :
Type extends TNull ? null :
Type extends TUint8Array ? Uint8Array :
Type extends TUndefined ? undefined :
Type extends TUnknown ? unknown :
Type extends TVoid ? void :
never
)
// ------------------------------------------------------------------
// Definitions
// ------------------------------------------------------------------
export interface TDefinitions<ModuleProperties extends TProperties> extends TSchema {
static: { [K in keyof ModuleProperties]: Static<ModuleProperties[K]> }
$defs: ModuleProperties
}
// ------------------------------------------------------------------
// Import
// ------------------------------------------------------------------
// prettier-ignore
export interface TImport<ModuleProperties extends TProperties = {}, Key extends keyof ModuleProperties = keyof ModuleProperties> extends TSchema {
[Kind]: 'Import'
static: InferImport<ModuleProperties, Key, ModuleProperties>
$defs: ModuleProperties
$ref: Key
}
// ------------------------------------------------------------------
// Module
// ------------------------------------------------------------------
// prettier-ignore
export class TModule<Properties extends TProperties> {
constructor(private readonly $defs: Properties, private readonly options: SchemaOptions = {}) {}
/** `[Json]` Returns the Type definitions for this module */
public Defs(): TDefinitions<Properties> {
return CreateType({ $defs: this.ResolveDefinitionsWithIdentifiers() }, this.options) as never
}
/** `[Json]` Imports a Type by Key. */
public Import<Key extends keyof Properties>(key: Key, options?: SchemaOptions): TImport<Properties, Key> {
return CreateType({ [Kind]: 'Import', $defs: this.ResolveDefinitionsWithIdentifiers(), $ref: key }, options) as never
}
/** `[Internal]` For each definition, assign an `$id` property. */
private ResolveDefinitionsWithIdentifiers() {
return globalThis.Object.getOwnPropertyNames(this.$defs).reduce((Result, Key) => (
{ ...Result, [Key]: { ...this.$defs[Key], $id: Key }}
), {})
}
}
/** `[Json]` Creates a Type Definition Module. */
export function Module<Properties extends TProperties>(properties: Properties): TModule<Properties> {
return new TModule(properties)
}

View File

@@ -26,29 +26,19 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
import { CreateType } from '../create/type'
import type { TSchema, SchemaOptions } from '../schema/index'
import type { Static } from '../static/index'
import { CreateType } from '../create/type'
import { Kind } from '../symbols/index'
// ------------------------------------------------------------------
// ValueGuard
// ------------------------------------------------------------------
import { IsString, IsUndefined } from '../guard/value'
// ------------------------------------------------------------------
// TRef
// ------------------------------------------------------------------
export interface TRef<T extends TSchema = TSchema> extends TSchema {
export interface TRef<Ref extends string = string> extends TSchema {
[Kind]: 'Ref'
static: Static<T, this['params']>
$ref: string
static: unknown
$ref: Ref
}
/** `[Json]` Creates a Ref type. The referenced type must contain a $id */
export function Ref<T extends TSchema>(schema: T, options?: SchemaOptions): TRef<T>
/** `[Json]` Creates a Ref type. */
export function Ref<T extends TSchema>($ref: string, options?: SchemaOptions): TRef<T>
/** `[Json]` Creates a Ref type. */
export function Ref(unresolved: TSchema | string, options?: SchemaOptions) {
if (IsString(unresolved)) return CreateType({ [Kind]: 'Ref', $ref: unresolved }, options)
if (IsUndefined(unresolved.$id)) throw new Error('Reference target type must specify an $id')
return CreateType({ [Kind]: 'Ref', $ref: unresolved.$id! }, options)
export function Ref<Ref extends string>($ref: Ref, options?: SchemaOptions): TRef<Ref> {
return CreateType({ [Kind]: 'Ref', $ref }, options) as never
}

View File

@@ -77,7 +77,7 @@ export type TDecodeType<T extends TSchema> = (
T extends TPromise<infer S extends TSchema> ? TPromise<TDecodeType<S>> :
T extends TRecord<infer K, infer S> ? TRecord<K, TDecodeType<S>> :
T extends TRecursive<infer S extends TSchema> ? TRecursive<TDecodeType<S>> :
T extends TRef<infer S extends TSchema> ? TRef<TDecodeType<S>> :
T extends TRef<infer S extends string> ? TRef<S> :
T extends TTuple<infer S extends TSchema[]> ? TTuple<TDecodeRest<S>> :
T extends TUnion<infer S extends TSchema[]> ? TUnion<TDecodeRest<S>> :
T

View File

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

View File

@@ -1,44 +0,0 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/type
The MIT License (MIT)
Copyright (c) 2017-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 type { TSchema } from '../schema/index'
export type TStrict<T extends TSchema> = T
/**
* @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

@@ -31,7 +31,6 @@ import { Array, type TArray, type ArrayOptions } from '../array/index'
import { Boolean, type TBoolean } from '../boolean/index'
import { Composite, type TComposite } from '../composite/index'
import { Const, type TConst } from '../const/index'
import { Deref, type TDeref } from '../deref/index'
import { Enum, type TEnum, type TEnumKey, type TEnumValue } from '../enum/index'
import { Exclude, type TExclude, type TExcludeFromMappedResult, type TExcludeFromTemplateLiteral } from '../exclude/index'
import { Extends, type TExtends, type TExtendsFromMappedKey, type TExtendsFromMappedResult } from '../extends/index'
@@ -47,6 +46,7 @@ import { Never, type TNever } from '../never/index'
import { Not, type TNot } from '../not/index'
import { Null, type TNull } from '../null/index'
import { type TMappedKey } from '../mapped/index'
import { Module, TModule } from '../module/index'
import { Number, type TNumber, type NumberOptions } from '../number/index'
import { Object, type TObject, type TProperties, type ObjectOptions } from '../object/index'
import { Omit, type TOmit, type TOmitFromMappedKey, type TOmitFromMappedResult } from '../omit/index'
@@ -61,7 +61,6 @@ import { Ref, type TRef } from '../ref/index'
import { Required, type TRequired, type TRequiredFromMappedResult } from '../required/index'
import { Rest, type TRest } from '../rest/index'
import { type TSchema, type SchemaOptions } from '../schema/index'
import { Strict, type TStrict } from '../strict/index'
import { String, type TString, type StringOptions } from '../string/index'
import { TemplateLiteral, type TTemplateLiteral, type TTemplateLiteralKind, type TTemplateLiteralSyntax } from '../template-literal/index'
import { Transform, TransformDecodeBuilder } from '../transform/index'
@@ -72,21 +71,6 @@ import { Unsafe, type TUnsafe, type UnsafeOptions } from '../unsafe/index'
/** Json Type Builder with Static Resolution for TypeScript */
export class JsonTypeBuilder {
// ------------------------------------------------------------------------
// Strict
// ------------------------------------------------------------------------
/**
* @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()))
* ```
*/
public Strict<T extends TSchema>(schema: T): TStrict<T> {
return Strict(schema)
}
// ------------------------------------------------------------------------
// Modifiers
// ------------------------------------------------------------------------
@@ -145,10 +129,6 @@ export class JsonTypeBuilder {
public Const</* const (not supported in 4.0) */ T>(value: T, options?: SchemaOptions): TConst<T> {
return Const(value, options)
}
/** `[Json]` Creates a dereferenced type */
public Deref<T extends TSchema>(schema: T, references: TSchema[]): TDeref<T> {
return Deref(schema, references)
}
/** `[Json]` Creates a Enum type */
public Enum<V extends TEnumValue, T extends Record<TEnumKey, V>>(item: T, options?: SchemaOptions): TEnum<T> {
return Enum(item, options)
@@ -227,6 +207,10 @@ export class JsonTypeBuilder {
public Mapped(key: any, map: TMappedFunction<any>, options?: ObjectOptions): any {
return Mapped(key, map, options)
}
/** `[Json]` Creates a Type Definition Module. */
public Module<Properties extends TProperties>(properties: Properties): TModule<Properties> {
return Module(properties)
}
/** `[Json]` Creates a Never type */
public Never(options?: SchemaOptions): TNever {
return Never(options)
@@ -287,13 +271,9 @@ export class JsonTypeBuilder {
public Recursive<T extends TSchema>(callback: (thisType: TThis) => T, options?: SchemaOptions): TRecursive<T> {
return Recursive(callback, options)
}
/** `[Json]` Creates a Ref type. The referenced type must contain a $id */
public Ref<T extends TSchema>(schema: T, options?: SchemaOptions): TRef<T>
/** `[Json]` Creates a Ref type. */
public Ref<T extends TSchema>($ref: string, options?: SchemaOptions): TRef<T>
/** `[Json]` Creates a Ref type. */
public Ref(unresolved: TSchema | string, options?: SchemaOptions) {
return Ref(unresolved as any, options)
public Ref<Ref extends string>($ref: Ref, options?: SchemaOptions): TRef<Ref> {
return Ref($ref, options)
}
/** `[Json]` Constructs a type where all properties are required */
public Required<T extends TMappedResult>(T: T, options?: SchemaOptions): TRequiredFromMappedResult<T>

View File

@@ -40,7 +40,6 @@ export { Const } from '../const/index'
export { Constructor } from '../constructor/index'
export { ConstructorParameters } from '../constructor-parameters/index'
export { Date } from '../date/index'
export { Deref } from '../deref/index'
export { Enum } from '../enum/index'
export { Exclude } from '../exclude/index'
export { Extends } from '../extends/index'
@@ -55,6 +54,7 @@ export { Iterator } from '../iterator/index'
export { KeyOf } from '../keyof/index'
export { Literal } from '../literal/index'
export { Mapped } from '../mapped/index'
export { Module } from '../module/index'
export { Never } from '../never/index'
export { Not } from '../not/index'
export { Null } from '../null/index'
@@ -76,7 +76,6 @@ export { Required } from '../required/index'
export { Rest } from '../rest/index'
export { ReturnType } from '../return-type/index'
export { String } from '../string/index'
export { Strict } from '../strict/index'
export { Symbol } from '../symbol/index'
export { TemplateLiteral } from '../template-literal/index'
export { Transform } from '../transform/index'

View File

@@ -32,12 +32,13 @@ import { Kind } from '../../type/symbols/index'
import { Create } from '../create/index'
import { Check } from '../check/index'
import { Clone } from '../clone/index'
import { Deref } from '../deref/index'
import { Deref, Pushref } from '../deref/index'
import type { TSchema } from '../../type/schema/index'
import type { Static } from '../../type/static/index'
import type { TArray } from '../../type/array/index'
import type { TConstructor } from '../../type/constructor/index'
import type { TImport } from '../../type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
@@ -133,6 +134,11 @@ function FromConstructor(schema: TConstructor, references: TSchema[], value: any
}
return result
}
function FromImport(schema: TImport, references: TSchema[], value: unknown): boolean {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
function FromIntersect(schema: TIntersect, references: TSchema[], value: any): any {
const created = Create(schema, references)
const mapped = IsObject(created) && IsObject(value) ? { ...(created as any), ...value } : value
@@ -187,7 +193,7 @@ function FromUnion(schema: TUnion, references: TSchema[], value: any): any {
return Check(schema, references, value) ? Clone(value) : CastUnion(schema, references, value)
}
function Visit(schema: TSchema, references: TSchema[], value: any): any {
const references_ = IsString(schema.$id) ? [...references, schema] : references
const references_ = IsString(schema.$id) ? Pushref(schema, references) : references
const schema_ = schema as any
switch (schema[Kind]) {
// --------------------------------------------------------------
@@ -197,6 +203,8 @@ function Visit(schema: TSchema, references: TSchema[], value: any): any {
return FromArray(schema_, references_, value)
case 'Constructor':
return FromConstructor(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Intersect':
return FromIntersect(schema_, references_, value)
case 'Never':

View File

@@ -27,7 +27,7 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
import { TypeSystemPolicy } from '../../system/index'
import { Deref } from '../deref/index'
import { Deref, Pushref } from '../deref/index'
import { Hash } from '../hash/index'
import { Kind } from '../../type/symbols/index'
import { KeyOfPattern } from '../../type/keyof/index'
@@ -48,6 +48,7 @@ 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 type { TImport } from '../../type/module/index'
import { Never, type TNever } from '../../type/never/index'
import type { TNot } from '../../type/not/index'
import type { TNull } from '../../type/null/index'
@@ -185,6 +186,11 @@ function FromDate(schema: TDate, references: TSchema[], value: any): boolean {
function FromFunction(schema: TFunction, references: TSchema[], value: any): boolean {
return IsFunction(value)
}
function FromImport(schema: TImport, references: TSchema[], value: any): boolean {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
function FromInteger(schema: TInteger, references: TSchema[], value: any): boolean {
if (!IsInteger(value)) {
return false
@@ -415,7 +421,7 @@ function FromKind(schema: TSchema, references: TSchema[], value: unknown): boole
return func(schema, value)
}
function Visit<T extends TSchema>(schema: T, references: TSchema[], value: any): boolean {
const references_ = IsDefined<string>(schema.$id) ? [...references, schema] : references
const references_ = IsDefined<string>(schema.$id) ? Pushref(schema, references) : references
const schema_ = schema as any
switch (schema_[Kind]) {
case 'Any':
@@ -434,6 +440,8 @@ function Visit<T extends TSchema>(schema: T, references: TSchema[], value: any):
return FromDate(schema_, references_, value)
case 'Function':
return FromFunction(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Integer':
return FromInteger(schema_, references_, value)
case 'Intersect':

View File

@@ -29,11 +29,12 @@ THE SOFTWARE.
import { KeyOfPropertyKeys } from '../../type/keyof/index'
import { Check } from '../check/index'
import { Clone } from '../clone/index'
import { Deref } from '../deref/index'
import { Deref, Pushref } from '../deref/index'
import { Kind } from '../../type/symbols/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TImport } from 'src/type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
@@ -60,6 +61,7 @@ import {
import {
IsKind
} from '../../type/guard/kind'
// ------------------------------------------------------------------
// IsCheckable
// ------------------------------------------------------------------
@@ -73,6 +75,11 @@ function FromArray(schema: TArray, references: TSchema[], value: unknown): any {
if (!IsArray(value)) return value
return value.map((value) => Visit(schema.items, references, value))
}
function FromImport(schema: TImport, references: TSchema[], value: unknown): any {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
function FromIntersect(schema: TIntersect, references: TSchema[], value: unknown): any {
const unevaluatedProperties = schema.unevaluatedProperties as TSchema
const intersections = schema.allOf.map((schema) => Visit(schema, references, Clone(value)))
@@ -149,11 +156,13 @@ function FromUnion(schema: TUnion, references: TSchema[], value: unknown): any {
return value
}
function Visit(schema: TSchema, references: TSchema[], value: unknown): unknown {
const references_ = IsString(schema.$id) ? [...references, schema] : references
const references_ = IsString(schema.$id) ? Pushref(schema, references) : references
const schema_ = schema as any
switch (schema_[Kind]) {
case 'Array':
return FromArray(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Intersect':
return FromIntersect(schema_, references_, value)
case 'Object':

View File

@@ -38,6 +38,7 @@ import type { TBoolean } from '../../type/boolean/index'
import type { TDate } from '../../type/date/index'
import type { TInteger } from '../../type/integer/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TImport } from '../../type/module/index'
import type { TLiteral } from '../../type/literal/index'
import type { TNull } from '../../type/null/index'
import type { TNumber } from '../../type/number/index'
@@ -182,6 +183,11 @@ function FromBoolean(schema: TBoolean, references: TSchema[], value: any): unkno
function FromDate(schema: TDate, references: TSchema[], value: any): unknown {
return TryConvertDate(value)
}
function FromImport(schema: TImport, references: TSchema[], value: unknown): unknown {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
function FromInteger(schema: TInteger, references: TSchema[], value: any): unknown {
return TryConvertInteger(value)
}
@@ -261,6 +267,8 @@ function Visit(schema: TSchema, references: TSchema[], value: any): unknown {
return FromBoolean(schema_, references_, value)
case 'Date':
return FromDate(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Integer':
return FromInteger(schema_, references_, value)
case 'Intersect':

View File

@@ -45,6 +45,7 @@ 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'
@@ -167,6 +168,11 @@ function FromFunction(schema: TFunction, references: TSchema[]): any {
return () => Visit(schema.returns, references)
}
}
function FromImport(schema: TImport, references: TSchema[]): any {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions])
}
function FromInteger(schema: TInteger, references: TSchema[]): any {
if (HasPropertyKey(schema, 'default')) {
return FromDefault(schema.default)
@@ -411,6 +417,8 @@ function Visit(schema: TSchema, references: TSchema[]): unknown {
return FromDate(schema_, references_)
case 'Function':
return FromFunction(schema_, references_)
case 'Import':
return FromImport(schema_, references_)
case 'Integer':
return FromInteger(schema_, references_)
case 'Intersect':

View File

@@ -33,6 +33,7 @@ import { Kind } from '../../type/symbols/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TImport } from '../../type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TObject } from '../../type/object/index'
import type { TRecord } from '../../type/record/index'
@@ -78,6 +79,11 @@ function FromDate(schema: TArray, references: TSchema[], value: unknown): any {
// special case intercept for dates
return IsDate(value) ? value : ValueOrDefault(schema, value)
}
function FromImport(schema: TImport, references: TSchema[], value: unknown): any {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
return Visit(target, [...references, ...definitions], value)
}
function FromIntersect(schema: TIntersect, references: TSchema[], value: unknown): any {
const defaulted = ValueOrDefault(schema, value)
return schema.allOf.reduce((acc, schema) => {
@@ -161,6 +167,8 @@ function Visit(schema: TSchema, references: TSchema[], value: unknown): any {
return FromArray(schema_, references_, value)
case 'Date':
return FromDate(schema_, references_, value)
case 'Import':
return FromImport(schema_, references_, value)
case 'Intersect':
return FromIntersect(schema_, references_, value)
case 'Object':

View File

@@ -36,6 +36,7 @@ import { Check } from '../check/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TImport } from '../../type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TNot } from '../../type/not/index'
import type { TObject } from '../../type/object/index'
@@ -82,7 +83,7 @@ export class TransformDecodeError extends TypeBoxError {
// Decode
// ------------------------------------------------------------------
// prettier-ignore
function Default(schema: TSchema, path: string, value: any) {
function Default(schema: TSchema, path: string, value: any): unknown {
try {
return IsTransform(schema) ? schema[TransformKind].Decode(value) : value
} catch (error) {
@@ -90,13 +91,13 @@ function Default(schema: TSchema, path: string, value: any) {
}
}
// prettier-ignore
function FromArray(schema: TArray, references: TSchema[], path: string, value: any): any {
function FromArray(schema: TArray, references: TSchema[], path: string, value: any): unknown {
return (IsArray(value))
? Default(schema, path, value.map((value: any, index) => Visit(schema.items, references, `${path}/${index}`, value)))
: Default(schema, path, value)
}
// prettier-ignore
function FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any) {
function FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any): unknown {
if (!IsObject(value) || IsValueType(value)) return Default(schema, path, value)
const knownEntries = KeyOfPropertyEntries(schema)
const knownKeys = knownEntries.map(entry => entry[0])
@@ -115,11 +116,20 @@ function FromIntersect(schema: TIntersect, references: TSchema[], path: string,
}
return Default(schema, path, unknownProperties)
}
function FromNot(schema: TNot, references: TSchema[], path: string, value: any) {
// prettier-ignore
function FromImport(schema: TImport, references: TSchema[], path: string, value: unknown): unknown {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
const transform = schema[TransformKind as never]
// Note: we need to re-spec the target as TSchema + [TransformKind]
const transformTarget = { [TransformKind]: transform, ...target } as TSchema
return Visit(transformTarget as never, [...references, ...definitions], path, value)
}
function FromNot(schema: TNot, references: TSchema[], path: string, value: any): unknown {
return Default(schema, path, Visit(schema.not, references, path, value))
}
// prettier-ignore
function FromObject(schema: TObject, references: TSchema[], path: string, value: any) {
function FromObject(schema: TObject, references: TSchema[], path: string, value: any): unknown {
if (!IsObject(value)) return Default(schema, path, value)
const knownKeys = KeyOfPropertyKeys(schema) as string[]
const knownProperties = { ...value } as Record<PropertyKey, unknown>
@@ -147,7 +157,7 @@ function FromObject(schema: TObject, references: TSchema[], path: string, value:
return Default(schema, path, unknownProperties)
}
// prettier-ignore
function FromRecord(schema: TRecord, references: TSchema[], path: string, value: any) {
function FromRecord(schema: TRecord, references: TSchema[], path: string, value: any): unknown {
if (!IsObject(value)) return Default(schema, path, value)
const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0]
const knownKeys = new RegExp(pattern)
@@ -167,23 +177,23 @@ function FromRecord(schema: TRecord, references: TSchema[], path: string, value:
return Default(schema, path, unknownProperties)
}
// prettier-ignore
function FromRef(schema: TRef, references: TSchema[], path: string, value: any) {
function FromRef(schema: TRef, references: TSchema[], path: string, value: any): unknown {
const target = Deref(schema, references)
return Default(schema, path, Visit(target, references, path, value))
}
// prettier-ignore
function FromThis(schema: TThis, references: TSchema[], path: string, value: any) {
function FromThis(schema: TThis, references: TSchema[], path: string, value: any): unknown {
const target = Deref(schema, references)
return Default(schema, path, Visit(target, references, path, value))
}
// prettier-ignore
function FromTuple(schema: TTuple, references: TSchema[], path: string, value: any) {
function FromTuple(schema: TTuple, references: TSchema[], path: string, value: any): unknown {
return (IsArray(value) && IsArray(schema.items))
? Default(schema, path, schema.items.map((schema, index) => Visit(schema, references, `${path}/${index}`, value[index])))
: Default(schema, path, value)
}
// prettier-ignore
function FromUnion(schema: TUnion, references: TSchema[], path: string, value: any) {
function FromUnion(schema: TUnion, references: TSchema[], path: string, value: any): unknown {
for (const subschema of schema.anyOf) {
if (!Check(subschema, references, value)) continue
// note: ensure interior is decoded first
@@ -199,6 +209,8 @@ function Visit(schema: TSchema, references: TSchema[], path: string, value: any)
switch (schema[Kind]) {
case 'Array':
return FromArray(schema_, references_, path, value)
case 'Import':
return FromImport(schema_, references_, path, value)
case 'Intersect':
return FromIntersect(schema_, references_, path, value)
case 'Not':

View File

@@ -36,6 +36,7 @@ import { Check } from '../check/index'
import type { TSchema } from '../../type/schema/index'
import type { TArray } from '../../type/array/index'
import type { TImport } from '../../type/module/index'
import type { TIntersect } from '../../type/intersect/index'
import type { TNot } from '../../type/not/index'
import type { TObject } from '../../type/object/index'
@@ -96,6 +97,15 @@ function FromArray(schema: TArray, references: TSchema[], path: string, value: a
: defaulted
}
// prettier-ignore
function FromImport(schema: TImport, references: TSchema[], path: string, value: unknown): unknown {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
const transform = schema[TransformKind as never]
// Note: we need to re-spec the target as TSchema + [TransformKind]
const transformTarget = { [TransformKind]: transform, ...target } as TSchema
return Visit(transformTarget as never, [...references, ...definitions], path, value)
}
// prettier-ignore
function FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any) {
const defaulted = Default(schema, path, value)
if (!IsObject(value) || IsValueType(value)) return defaulted
@@ -210,6 +220,8 @@ function Visit(schema: TSchema, references: TSchema[], path: string, value: any)
switch (schema[Kind]) {
case 'Array':
return FromArray(schema_, references_, path, value)
case 'Import':
return FromImport(schema_, references_, path, value)
case 'Intersect':
return FromIntersect(schema_, references_, path, value)
case 'Not':

View File

@@ -9,6 +9,7 @@ import './integer'
import './intersect'
import './keyof'
import './literal'
import './module'
import './never'
import './not'
import './null'

View File

@@ -0,0 +1,78 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler-ajv/Module', () => {
it('Should validate string', () => {
const Module = Type.Module({
A: Type.String(),
})
const T = Module.Import('A')
Ok(T, 'hello')
Fail(T, true)
})
it('Should validate referenced string', () => {
const Module = Type.Module({
A: Type.String(),
B: Type.Ref('A'),
})
const T = Module.Import('B')
Ok(T, 'hello')
Fail(T, true)
})
it('Should validate self referential', () => {
const Module = Type.Module({
A: Type.Object({
nodes: Type.Array(Type.Ref('A')),
}),
})
const T = Module.Import('A')
Ok(T, { nodes: [{ nodes: [{ nodes: [] }, { nodes: [] }] }] })
Fail(T, { nodes: [{ nodes: [{ nodes: [] }, { nodes: false }] }] })
Fail(T, true)
})
it('Should validate mutual recursive', () => {
const Module = Type.Module({
A: Type.Object({
b: Type.Ref('B'),
}),
B: Type.Object({
a: Type.Union([Type.Ref('A'), Type.Null()]),
}),
})
const T = Module.Import('A')
Ok(T, { b: { a: null } })
Ok(T, { b: { a: { b: { a: null } } } })
Fail(T, { b: { a: 1 } })
Fail(T, { b: { a: { b: { a: 1 } } } })
Fail(T, true)
})
it('Should validate mutual recursive (Array)', () => {
const Module = Type.Module({
A: Type.Object({
b: Type.Ref('B'),
}),
B: Type.Object({
a: Type.Array(Type.Ref('A')),
}),
})
const T = Module.Import('A')
Ok(T, { b: { a: [{ b: { a: [] } }] } })
Fail(T, { b: { a: [{ b: { a: [null] } }] } })
Fail(T, true)
})
it('Should validate deep referential', () => {
const Module = Type.Module({
A: Type.Ref('B'),
B: Type.Ref('C'),
C: Type.Ref('D'),
D: Type.Ref('E'),
E: Type.Ref('F'),
F: Type.Ref('G'),
G: Type.Ref('H'),
H: Type.Literal('hello'),
})
const T = Module.Import('A')
Ok(T, 'hello')
Fail(T, 'world')
})
})

View File

@@ -11,7 +11,7 @@ describe('compiler-ajv/Ref', () => {
},
{ $id: 'T' },
)
const R = Type.Ref(T)
const R = Type.Ref(T.$id!)
Ok(
R,
{
@@ -31,7 +31,7 @@ describe('compiler-ajv/Ref', () => {
},
{ $id: 'T' },
)
const R = Type.Ref(T)
const R = Type.Ref(T.$id!)
Fail(
R,
{
@@ -54,7 +54,7 @@ describe('compiler-ajv/Ref', () => {
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
r: Type.Optional(Type.Ref(R)),
r: Type.Optional(Type.Ref(R.$id!)),
},
{ $id: 'T' },
)

View File

@@ -16,6 +16,7 @@ import './iterator'
import './keyof'
import './kind'
import './literal'
import './module'
import './never'
import './not'
import './null'

View File

@@ -0,0 +1,78 @@
import { Type } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
describe('compiler/Module', () => {
it('Should validate string', () => {
const Module = Type.Module({
A: Type.String(),
})
const T = Module.Import('A')
Ok(T, 'hello')
Fail(T, true)
})
it('Should validate referenced string', () => {
const Module = Type.Module({
A: Type.String(),
B: Type.Ref('A'),
})
const T = Module.Import('B')
Ok(T, 'hello')
Fail(T, true)
})
it('Should validate self referential', () => {
const Module = Type.Module({
A: Type.Object({
nodes: Type.Array(Type.Ref('A')),
}),
})
const T = Module.Import('A')
Ok(T, { nodes: [{ nodes: [{ nodes: [] }, { nodes: [] }] }] })
Fail(T, { nodes: [{ nodes: [{ nodes: [] }, { nodes: false }] }] })
Fail(T, true)
})
it('Should validate mutual recursive', () => {
const Module = Type.Module({
A: Type.Object({
b: Type.Ref('B'),
}),
B: Type.Object({
a: Type.Union([Type.Ref('A'), Type.Null()]),
}),
})
const T = Module.Import('A')
Ok(T, { b: { a: null } })
Ok(T, { b: { a: { b: { a: null } } } })
Fail(T, { b: { a: 1 } })
Fail(T, { b: { a: { b: { a: 1 } } } })
Fail(T, true)
})
it('Should validate mutual recursive (Array)', () => {
const Module = Type.Module({
A: Type.Object({
b: Type.Ref('B'),
}),
B: Type.Object({
a: Type.Array(Type.Ref('A')),
}),
})
const T = Module.Import('A')
Ok(T, { b: { a: [{ b: { a: [] } }] } })
Fail(T, { b: { a: [{ b: { a: [null] } }] } })
Fail(T, true)
})
it('Should validate deep referential', () => {
const Module = Type.Module({
A: Type.Ref('B'),
B: Type.Ref('C'),
C: Type.Ref('D'),
D: Type.Ref('E'),
E: Type.Ref('F'),
F: Type.Ref('G'),
G: Type.Ref('H'),
H: Type.Literal('hello'),
})
const T = Module.Import('A')
Ok(T, 'hello')
Fail(T, 'world')
})
})

View File

@@ -12,7 +12,7 @@ describe('compiler/Ref', () => {
},
{ $id: Assert.NextId() },
)
const R = Type.Ref(T)
const R = Type.Ref(T.$id!)
Ok(
R,
{
@@ -32,7 +32,7 @@ describe('compiler/Ref', () => {
},
{ $id: Assert.NextId() },
)
const R = Type.Ref(T)
const R = Type.Ref(T.$id!)
Fail(
R,
{
@@ -54,7 +54,7 @@ describe('compiler/Ref', () => {
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
r: Type.Optional(Type.Ref(T)),
r: Type.Optional(Type.Ref(T.$id!)),
},
{ $id: 'T' },
)
@@ -70,7 +70,7 @@ describe('compiler/Ref', () => {
nodes: Type.Array(Node),
}),
)
const R = Type.Ref(T)
const R = Type.Ref(T.$id!)
Ok(R, { id: '', nodes: [{ id: '', nodes: [] }] }, [T])
Fail(R, { id: '', nodes: [{ id: 1, nodes: [] }] }, [T])
})

View File

@@ -31,7 +31,7 @@ describe('compiler/Unicode', () => {
},
)
const T = Type.Object({
vector: Type.Ref(R),
vector: Type.Ref(R.$id!),
})
Ok(
T,

View File

@@ -1,10 +1,100 @@
import { TypeGuard } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
import { Type, TModule } from '@sinclair/typebox'
import { Parse } from '@sinclair/typebox/syntax'
import { Assert } from '../assert/index'
// prettier-ignore
describe('parse/Parse', () => {
describe('syntax/Parse', () => {
// ----------------------------------------------------------------
// Type Alias
// ----------------------------------------------------------------
it('Should parse Type Alias 1', () => {
const T = Parse('type A = 1')
Assert.IsTrue(TypeGuard.IsLiteral(T.A))
Assert.IsTrue(T.A.const === 1)
})
it('Should parse Type Alias 2', () => {
const T = Parse('export type A = 1')
Assert.IsTrue(TypeGuard.IsLiteral(T.A))
Assert.IsTrue(T.A.const === 1)
})
// ----------------------------------------------------------------
// Interface
// ----------------------------------------------------------------
it('Should parse Interface 1', () => {
const T = Parse('interface A { x: 1 }')
Assert.IsTrue(TypeGuard.IsObject(T.A))
Assert.IsTrue(TypeGuard.IsLiteral(T.A.properties.x))
Assert.IsTrue(T.A.properties.x.const === 1)
})
it('Should parse Interface 2', () => {
const T = Parse('export interface A { x: 1 }')
Assert.IsTrue(TypeGuard.IsObject(T.A))
Assert.IsTrue(TypeGuard.IsLiteral(T.A.properties.x))
Assert.IsTrue(T.A.properties.x.const === 1)
})
// ----------------------------------------------------------------
// Module
// ----------------------------------------------------------------
it('Should parse Module 1', () => {
const T = Parse('module {}')
Assert.IsTrue(T instanceof TModule)
})
it('Should parse Module 2', () => {
const T = Parse('export module {}')
Assert.IsTrue(T instanceof TModule)
})
it('Should parse Module 3', () => {
const T = Parse('module A {}')
Assert.IsTrue(T instanceof TModule)
})
it('Should parse Module 4', () => {
const T = Parse('export module A {}')
Assert.IsTrue(T instanceof TModule)
})
it('Should parse Module 5', () => {
const T = Parse(`export module A {
export type A = number
}`)
const A = T.Import('A')
Assert.IsTrue(T instanceof TModule)
Assert.IsTrue(TypeGuard.IsImport(A))
const N = A.$defs[A.$ref]
Assert.IsTrue(TypeGuard.IsNumber(N))
})
it('Should parse Module 6', () => {
const T = Parse(`export module A {
export interface A { x: number }
}`)
const A = T.Import('A')
Assert.IsTrue(T instanceof TModule)
Assert.IsTrue(TypeGuard.IsImport(A))
const N = A.$defs[A.$ref]
Assert.IsTrue(TypeGuard.IsObject(N))
Assert.IsTrue(TypeGuard.IsNumber(N.properties.x))
})
it('Should parse Module 7', () => {
const T = Parse(`export module A {
export interface A { x: number }
export type B = number
}`)
// A
const A = T.Import('A')
Assert.IsTrue(T instanceof TModule)
Assert.IsTrue(TypeGuard.IsImport(A))
const N1 = A.$defs[A.$ref]
Assert.IsTrue(TypeGuard.IsObject(N1))
Assert.IsTrue(TypeGuard.IsNumber(N1.properties.x))
// B
const B = T.Import('B')
Assert.IsTrue(T instanceof TModule)
Assert.IsTrue(TypeGuard.IsImport(B))
const N2 = B.$defs[B.$ref]
Assert.IsTrue(TypeGuard.IsNumber(N2))
})
// ----------------------------------------------------------------
// Type Expressions
// ----------------------------------------------------------------
it('Should parse Any', () => {
const T = Parse(`any`)
Assert.IsTrue(TypeGuard.IsAny(T))

View File

@@ -1,110 +0,0 @@
import { KindGuard } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../../assert/index'
describe('guard/kind/TDeref', () => {
it('Should should deref 1', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const D = Type.Deref(R, [T])
Assert.IsTrue(KindGuard.IsString(D))
Assert.IsFalse('$id' in D)
})
it('Should should deref 2', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Object({
x: R,
y: R,
})
const D = Type.Deref(O, [T])
Assert.IsTrue(KindGuard.IsObject(D))
Assert.IsTrue(KindGuard.IsString(D.properties.x))
Assert.IsTrue(KindGuard.IsString(D.properties.y))
Assert.IsFalse('$id' in D.properties.x)
Assert.IsFalse('$id' in D.properties.y)
})
it('Should should deref 3', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Array(R)
const D = Type.Deref(O, [T])
Assert.IsTrue(KindGuard.IsArray(D))
Assert.IsTrue(KindGuard.IsString(D.items))
Assert.IsFalse('$id' in D.items)
})
it('Should should deref 4', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.AsyncIterator(R)
const D = Type.Deref(O, [T])
Assert.IsTrue(KindGuard.IsAsyncIterator(D))
Assert.IsTrue(KindGuard.IsString(D.items))
Assert.IsFalse('$id' in D.items)
})
it('Should should deref 5', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Iterator(R)
const D = Type.Deref(O, [T])
Assert.IsTrue(KindGuard.IsIterator(D))
Assert.IsTrue(KindGuard.IsString(D.items))
Assert.IsFalse('$id' in D.items)
})
it('Should should deref 6', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Function([R], R)
const D = Type.Deref(O, [T])
Assert.IsTrue(KindGuard.IsFunction(D))
Assert.IsTrue(KindGuard.IsString(D.parameters[0]))
Assert.IsTrue(KindGuard.IsString(D.returns))
Assert.IsFalse('$id' in D.parameters[0])
Assert.IsFalse('$id' in D.returns)
})
it('Should should deref 7', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Constructor([R], R)
const D = Type.Deref(O, [T])
Assert.IsTrue(KindGuard.IsConstructor(D))
Assert.IsTrue(KindGuard.IsString(D.parameters[0]))
Assert.IsTrue(KindGuard.IsString(D.returns))
Assert.IsFalse('$id' in D.parameters[0])
Assert.IsFalse('$id' in D.returns)
})
it('Should should deref 8', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Promise(R)
const D = Type.Deref(O, [T])
Assert.IsTrue(KindGuard.IsPromise(D))
Assert.IsTrue(KindGuard.IsString(D.item))
Assert.IsFalse('$id' in D.item)
})
it('Should should deref 9', () => {
const T = Type.String({ $id: 'T' })
const R1 = Type.Ref(T, { $id: 'R1' })
const R2 = Type.Ref(R1, { $id: 'R2' })
const R3 = Type.Ref(R2, { $id: 'R3' })
const R4 = Type.Ref(R3, { $id: 'R4' })
const R5 = Type.Ref(R4, { $id: 'R5' })
const R6 = Type.Ref(R5, { $id: 'R6' })
const O = Type.Array(R6)
const D = Type.Deref(O, [R6, R5, R4, R3, R2, R1, T])
Assert.IsTrue(KindGuard.IsArray(D))
Assert.IsTrue(KindGuard.IsString(D.items))
Assert.IsFalse('$id' in D.items)
})
it('Should should deref 10', () => {
const T = Type.String({ $id: 'T' })
const R1 = Type.Ref(T, { $id: 'R1' })
const R2 = Type.Ref(R1, { $id: 'R2' })
const R3 = Type.Ref(R2, { $id: 'R3' })
const R4 = Type.Ref(R3, { $id: 'R4' })
const R5 = Type.Ref(R4, { $id: 'R5' })
const R6 = Type.Ref(R5, { $id: 'R6' })
const O = Type.Array(R6)
Assert.Throws(() => Type.Deref(O, [R6, R5, R4, R3, R2, R1])) // Omit T
})
})

View File

@@ -0,0 +1,15 @@
import { KindGuard } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../../assert/index'
describe('guard/type/TImport', () => {
it('Should guard for TImport', () => {
const M = Type.Module({
A: Type.String(),
})
const I = M.Import('A')
const N = I.$defs[I.$ref]
Assert.IsTrue(KindGuard.IsImport(I))
Assert.IsTrue(KindGuard.IsString(N))
})
})

View File

@@ -9,11 +9,11 @@ import './composite'
import './const'
import './constructor'
import './date'
import './deref'
import './enum'
import './exclude'
import './extract'
import './function'
import './import'
import './indexed'
import './integer'
import './intersect'

View File

@@ -1,110 +0,0 @@
import { TypeGuard } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../../assert/index'
describe('guard/type/TDeref', () => {
it('Should should deref 1', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const D = Type.Deref(R, [T])
Assert.IsTrue(TypeGuard.IsString(D))
Assert.IsFalse('$id' in D)
})
it('Should should deref 2', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Object({
x: R,
y: R,
})
const D = Type.Deref(O, [T])
Assert.IsTrue(TypeGuard.IsObject(D))
Assert.IsTrue(TypeGuard.IsString(D.properties.x))
Assert.IsTrue(TypeGuard.IsString(D.properties.y))
Assert.IsFalse('$id' in D.properties.x)
Assert.IsFalse('$id' in D.properties.y)
})
it('Should should deref 3', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Array(R)
const D = Type.Deref(O, [T])
Assert.IsTrue(TypeGuard.IsArray(D))
Assert.IsTrue(TypeGuard.IsString(D.items))
Assert.IsFalse('$id' in D.items)
})
it('Should should deref 4', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.AsyncIterator(R)
const D = Type.Deref(O, [T])
Assert.IsTrue(TypeGuard.IsAsyncIterator(D))
Assert.IsTrue(TypeGuard.IsString(D.items))
Assert.IsFalse('$id' in D.items)
})
it('Should should deref 5', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Iterator(R)
const D = Type.Deref(O, [T])
Assert.IsTrue(TypeGuard.IsIterator(D))
Assert.IsTrue(TypeGuard.IsString(D.items))
Assert.IsFalse('$id' in D.items)
})
it('Should should deref 6', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Function([R], R)
const D = Type.Deref(O, [T])
Assert.IsTrue(TypeGuard.IsFunction(D))
Assert.IsTrue(TypeGuard.IsString(D.parameters[0]))
Assert.IsTrue(TypeGuard.IsString(D.returns))
Assert.IsFalse('$id' in D.parameters[0])
Assert.IsFalse('$id' in D.returns)
})
it('Should should deref 7', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Constructor([R], R)
const D = Type.Deref(O, [T])
Assert.IsTrue(TypeGuard.IsConstructor(D))
Assert.IsTrue(TypeGuard.IsString(D.parameters[0]))
Assert.IsTrue(TypeGuard.IsString(D.returns))
Assert.IsFalse('$id' in D.parameters[0])
Assert.IsFalse('$id' in D.returns)
})
it('Should should deref 8', () => {
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const O = Type.Promise(R)
const D = Type.Deref(O, [T])
Assert.IsTrue(TypeGuard.IsPromise(D))
Assert.IsTrue(TypeGuard.IsString(D.item))
Assert.IsFalse('$id' in D.item)
})
it('Should should deref 9', () => {
const T = Type.String({ $id: 'T' })
const R1 = Type.Ref(T, { $id: 'R1' })
const R2 = Type.Ref(R1, { $id: 'R2' })
const R3 = Type.Ref(R2, { $id: 'R3' })
const R4 = Type.Ref(R3, { $id: 'R4' })
const R5 = Type.Ref(R4, { $id: 'R5' })
const R6 = Type.Ref(R5, { $id: 'R6' })
const O = Type.Array(R6)
const D = Type.Deref(O, [R6, R5, R4, R3, R2, R1, T])
Assert.IsTrue(TypeGuard.IsArray(D))
Assert.IsTrue(TypeGuard.IsString(D.items))
Assert.IsFalse('$id' in D.items)
})
it('Should should deref 10', () => {
const T = Type.String({ $id: 'T' })
const R1 = Type.Ref(T, { $id: 'R1' })
const R2 = Type.Ref(R1, { $id: 'R2' })
const R3 = Type.Ref(R2, { $id: 'R3' })
const R4 = Type.Ref(R3, { $id: 'R4' })
const R5 = Type.Ref(R4, { $id: 'R5' })
const R6 = Type.Ref(R5, { $id: 'R6' })
const O = Type.Array(R6)
Assert.Throws(() => Type.Deref(O, [R6, R5, R4, R3, R2, R1])) // Omit T
})
})

View File

@@ -0,0 +1,15 @@
import { TypeGuard } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../../assert/index'
describe('guard/type/TImport', () => {
it('Should guard for TImport', () => {
const M = Type.Module({
A: Type.String(),
})
const I = M.Import('A')
const N = I.$defs[I.$ref]
Assert.IsTrue(TypeGuard.IsImport(I))
Assert.IsTrue(TypeGuard.IsString(N))
})
})

View File

@@ -9,11 +9,11 @@ import './composite'
import './const'
import './constructor'
import './date'
import './deref'
import './enum'
import './exclude'
import './extract'
import './function'
import './import'
import './indexed'
import './integer'
import './intersect'

View File

@@ -5,7 +5,7 @@ import { Assert } from '../../../assert/index'
describe('guard/type/TRef', () => {
it('Should guard for TRef', () => {
const T = Type.Number({ $id: 'T' })
const R = TypeGuard.IsRef(Type.Ref(T))
const R = TypeGuard.IsRef(Type.Ref('T'))
Assert.IsTrue(R)
})
it('Should not guard for TRef', () => {
@@ -14,7 +14,7 @@ describe('guard/type/TRef', () => {
})
it('Should not guard for TRef with invalid $ref', () => {
const T = Type.Number({ $id: 'T' })
const S = CloneType(Type.Ref(T))
const S = CloneType(Type.Ref('T'))
// @ts-ignore
S.$ref = 1
const R = TypeGuard.IsRef(S)

View File

@@ -0,0 +1,51 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'
describe('value/cast/Import', () => {
const T = Type.Module({
A: Type.Number(),
}).Import('A')
const E = 0
it('Should upcast from string', () => {
const value = 'world'
const result = Value.Cast(T, value)
Assert.IsEqual(result, E)
})
it('Should upcast from number', () => {
const value = 1
const result = Value.Cast(T, value)
Assert.IsEqual(result, 1)
})
it('Should upcast from object', () => {
const value = {}
const result = Value.Cast(T, value)
Assert.IsEqual(result, E)
})
it('Should upcast from array', () => {
const value = [1]
const result = Value.Cast(T, value)
Assert.IsEqual(result, E)
})
it('Should upcast from undefined', () => {
const value = undefined
const result = Value.Cast(T, value)
Assert.IsEqual(result, E)
})
it('Should upcast from null', () => {
const value = null
const result = Value.Cast(T, value)
Assert.IsEqual(result, E)
})
it('Should upcast from date', () => {
const value = new Date(100)
const result = Value.Cast(T, value)
Assert.IsEqual(result, E)
})
it('Should preseve', () => {
const value = 123
const result = Value.Cast(T, value)
Assert.IsEqual(result, 123)
})
})

View File

@@ -6,6 +6,7 @@ import './boolean'
import './composite'
import './date'
import './enum'
import './import'
import './integer'
import './intersect'
import './iterator'

View File

@@ -153,7 +153,7 @@ describe('value/cast/Union', () => {
const A = Type.Object({ type: Type.Literal('A') }, { $id: 'A' })
const B = Type.Object({ type: Type.Literal('B'), value: Type.Number() }, { $id: 'B' })
const RA = Type.Union([A, B])
const RB = Type.Union([Type.Ref(A), Type.Ref(B)])
const RB = Type.Union([Type.Ref('A'), Type.Ref('B')])
// variant 0
Assert.IsEqual(Value.Cast(RA, [A, B], { type: 'B' }), { type: 'B', value: 0 })
Assert.IsEqual(Value.Cast(RB, [A, B], { type: 'B' }), { type: 'B', value: 0 })

View File

@@ -15,6 +15,7 @@ import './iterator'
import './keyof'
import './kind'
import './literal'
import './module'
import './never'
import './not'
import './null'

View File

@@ -0,0 +1,80 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert'
describe('value/check/Module', () => {
it('Should validate string', () => {
const Module = Type.Module({
A: Type.String(),
})
const T = Module.Import('A')
Assert.IsTrue(Value.Check(T, 'hello'))
Assert.IsFalse(Value.Check(T, true))
})
it('Should validate referenced string', () => {
const Module = Type.Module({
A: Type.String(),
B: Type.Ref('A'),
})
const T = Module.Import('B')
Assert.IsTrue(Value.Check(T, 'hello'))
Assert.IsFalse(Value.Check(T, true))
})
it('Should validate self referential', () => {
const Module = Type.Module({
A: Type.Object({
nodes: Type.Array(Type.Ref('A')),
}),
})
const T = Module.Import('A')
Assert.IsTrue(Value.Check(T, { nodes: [{ nodes: [{ nodes: [] }, { nodes: [] }] }] }))
Assert.IsFalse(Value.Check(T, { nodes: [{ nodes: [{ nodes: [] }, { nodes: false }] }] }))
Assert.IsFalse(Value.Check(T, true))
})
it('Should validate mutual recursive', () => {
const Module = Type.Module({
A: Type.Object({
b: Type.Ref('B'),
}),
B: Type.Object({
a: Type.Union([Type.Ref('A'), Type.Null()]),
}),
})
const T = Module.Import('A')
Assert.IsTrue(Value.Check(T, { b: { a: null } }))
Assert.IsTrue(Value.Check(T, { b: { a: { b: { a: null } } } }))
Assert.IsFalse(Value.Check(T, { b: { a: 1 } }))
Assert.IsFalse(Value.Check(T, { b: { a: { b: { a: 1 } } } }))
Assert.IsFalse(Value.Check(T, true))
})
it('Should validate mutual recursive (Array)', () => {
const Module = Type.Module({
A: Type.Object({
b: Type.Ref('B'),
}),
B: Type.Object({
a: Type.Array(Type.Ref('A')),
}),
})
const T = Module.Import('A')
Assert.IsTrue(Value.Check(T, { b: { a: [{ b: { a: [] } }] } }))
Assert.IsFalse(Value.Check(T, { b: { a: [{ b: { a: [null] } }] } }))
Assert.IsFalse(Value.Check(T, true))
})
it('Should validate deep referential', () => {
const Module = Type.Module({
A: Type.Ref('B'),
B: Type.Ref('C'),
C: Type.Ref('D'),
D: Type.Ref('E'),
E: Type.Ref('F'),
F: Type.Ref('G'),
G: Type.Ref('H'),
H: Type.Literal('hello'),
})
const T = Module.Import('A')
Assert.IsTrue(Value.Check(T, 'hello'))
Assert.IsFalse(Value.Check(T, 'world'))
})
})

View File

@@ -12,7 +12,7 @@ describe('value/check/Ref', () => {
},
{ $id: Assert.NextId() },
)
const R = Type.Ref(T)
const R = Type.Ref(T.$id!)
Assert.IsEqual(
Value.Check(R, [T], {
x: 1,
@@ -32,7 +32,7 @@ describe('value/check/Ref', () => {
},
{ $id: Assert.NextId() },
)
const R = Type.Ref(T)
const R = Type.Ref(T.$id!)
Assert.IsEqual(
Value.Check(R, [T], {
x: 1,
@@ -55,7 +55,7 @@ describe('value/check/Ref', () => {
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
r: Type.Optional(Type.Ref(T)),
r: Type.Optional(Type.Ref(T.$id!)),
},
{ $id: 'T' },
)
@@ -76,7 +76,7 @@ describe('value/check/Ref', () => {
nodes: Type.Array(Node),
}),
)
const R = Type.Ref(T)
const R = Type.Ref(T.$id!)
Assert.IsEqual(Value.Check(R, [T], { id: '', nodes: [{ id: '', nodes: [] }] }), true)
Assert.IsEqual(Value.Check(R, [T], { id: '', nodes: [{ id: 1, nodes: [] }] }), false)
})

View File

@@ -0,0 +1,203 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'
describe('value/clean/Import', () => {
// ----------------------------------------------------------------
// Clean
// ----------------------------------------------------------------
it('Should clean 1', () => {
const T = Type.Module({ A: Type.Object({ x: Type.Number() }) }).Import('A')
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
it('Should clean 2', () => {
const T = Type.Module({ A: Type.Object({ x: Type.Number() }) }).Import('A')
const R = Value.Clean(T, {})
Assert.IsEqual(R, {})
})
it('Should clean 3', () => {
const T = Type.Module({ A: Type.Object({ x: Type.Number() }) }).Import('A')
const R = Value.Clean(T, { x: 1 })
Assert.IsEqual(R, { x: 1 })
})
it('Should clean 4', () => {
const T = Type.Module({ A: Type.Object({ x: Type.Number() }) }).Import('A')
const R = Value.Clean(T, { x: null })
Assert.IsEqual(R, { x: null })
})
// ----------------------------------------------------------------
// Nested
// ----------------------------------------------------------------
it('Should clean nested 1', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Object({
y: Type.Number(),
}),
}),
}).Import('A')
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
it('Should clean nested 2', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Object({
y: Type.Number(),
}),
}),
}).Import('A')
const R = Value.Clean(T, {})
Assert.IsEqual(R, {})
})
it('Should clean nested 3', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Object({
y: Type.Number(),
}),
}),
}).Import('A')
const R = Value.Clean(T, { x: null })
Assert.IsEqual(R, { x: null })
})
it('Should clean nested 4', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Object({
y: Type.Number(),
}),
}),
}).Import('A')
const R = Value.Clean(T, { x: { y: null } })
Assert.IsEqual(R, { x: { y: null } })
})
// ----------------------------------------------------------------
// Additional Properties
// ----------------------------------------------------------------
it('Should clean additional properties 1', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
),
}).Import('A')
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
it('Should clean additional properties 2', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
),
}).Import('A')
const R = Value.Clean(T, {})
Assert.IsEqual(R, {})
})
it('Should clean additional properties 3', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
),
}).Import('A')
const R = Value.Clean(T, { x: 1 })
Assert.IsEqual(R, { x: 1 })
})
it('Should clean additional properties 4', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
),
}).Import('A')
const R = Value.Clean(T, { x: 1, y: 2 })
Assert.IsEqual(R, { x: 1, y: 2 })
})
// ----------------------------------------------------------------
// Additional Properties Discard
// ----------------------------------------------------------------
it('Should clean additional properties discard 1', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
),
}).Import('A')
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
it('Should clean additional properties discard 2', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
),
}).Import('A')
const R = Value.Clean(T, { k: '', d: null })
Assert.IsEqual(R, { k: '' })
})
it('Should clean additional properties discard 3', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
),
}).Import('A')
const R = Value.Clean(T, { k: '', d: null, x: 1 })
Assert.IsEqual(R, { k: '', x: 1 })
})
it('Should clean additional properties discard 4', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{
additionalProperties: Type.String(),
},
),
}).Import('A')
const R = Value.Clean(T, { k: '', d: null, x: 1, y: 2 })
Assert.IsEqual(R, { k: '', x: 1, y: 2 })
})
})

View File

@@ -8,6 +8,7 @@ import './constructor'
import './date'
import './enum'
import './function'
import './import'
import './integer'
import './intersect'
import './iterator'

View File

@@ -0,0 +1,29 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'
// prettier-ignore
describe('value/convert/Import', () => {
it('Should convert properties', () => {
const T = Type.Module({ A: Type.Object({
x: Type.Number(),
y: Type.Boolean(),
z: Type.Boolean()
})}).Import('A')
const R = Value.Convert(T, { x: '42', y: 'true', z: 'hello' })
Assert.IsEqual(R, { x: 42, y: true, z: 'hello' })
})
it('Should convert known properties', () => {
const T = Type.Module({ A: Type.Object({
x: Type.Number(),
y: Type.Boolean()
})}).Import('A')
const R = Value.Convert(T, { x: '42', y: 'true', z: 'hello' })
Assert.IsEqual(R, { x: 42, y: true, z: 'hello' })
})
it('Should not convert missing properties', () => {
const T = Type.Module({ A: Type.Object({ x: Type.Number() }) }).Import('A')
const R = Value.Convert(T, { })
Assert.IsEqual(R, { })
})
})

View File

@@ -9,6 +9,7 @@ import './kind'
import './date'
import './enum'
import './function'
import './import'
import './integer'
import './intersect'
import './iterator'

View File

@@ -0,0 +1,98 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'
describe('value/create/Import', () => {
it('Should create value', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
}),
}).Import('A')
Assert.IsEqual(Value.Create(T), {
x: 0,
y: 0,
z: 0,
})
})
it('Should create value with optional properties', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Optional(Type.Number()),
y: Type.Optional(Type.Number()),
z: Type.Optional(Type.Number()),
}),
}).Import('A')
Assert.IsEqual(Value.Create(T), {})
})
it('Should create default with default properties', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
z: Type.Number({ default: 3 }),
}),
}).Import('A')
Assert.IsEqual(Value.Create(T), {
x: 1,
y: 2,
z: 3,
})
})
it('Should create nested object', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
w: Type.Object({
x: Type.Number({ default: 7 }),
y: Type.Number(),
z: Type.Number(),
}),
}),
}).Import('A')
Assert.IsEqual(Value.Create(T), {
x: 0,
y: 0,
z: 0,
w: { x: 7, y: 0, z: 0 },
})
})
it('Should create with default', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
z: Type.Number(),
},
{ default: { x: 1, y: 2, z: 3 } },
),
}).Import('A')
Assert.IsEqual(Value.Create(T), {
x: 1,
y: 2,
z: 3,
})
})
// ----------------------------------------------------------------
// Mutation
// ----------------------------------------------------------------
// https://github.com/sinclairzx81/typebox/issues/726
it('Should clone defaults on assignment - no mutation', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
},
{ default: { x: 1 } },
),
}).Import('A')
const V = Value.Create(T)
V.x = 123
Assert.IsEqual(T.$defs.A.default, { x: 1 })
})
})

View File

@@ -9,6 +9,7 @@ import './constructor'
import './date'
import './enum'
import './function'
import './import'
import './integer'
import './intersect'
import './iterator'

View File

@@ -12,7 +12,7 @@ describe('value/create/Ref', () => {
},
{ $id: 'T', default: 'target' },
)
const R = Type.Ref(T)
const R = Type.Ref('T')
Assert.Throws(() => Value.Create(R))
})
it('Should create ref default if ref default is defined', () => {
@@ -24,12 +24,12 @@ describe('value/create/Ref', () => {
},
{ $id: 'T', default: 'target' },
)
const R = Type.Ref(T, { default: 'override' })
const R = Type.Ref('T', { default: 'override' })
Assert.IsEqual(Value.Create(R), 'override') // terminated at R default value
})
it('Should dereference remote schema via $ref', () => {
const R = Type.Number({ $id: 'S' })
const T = Type.Object({ x: Type.Ref(R) })
const R = Type.Number({ $id: 'R' })
const T = Type.Object({ x: Type.Ref('R') })
Assert.IsEqual(Value.Create(T, [R]), { x: 0 })
})
})

View File

@@ -0,0 +1,299 @@
import { Value } from '@sinclair/typebox/value'
import { Type, CloneType } from '@sinclair/typebox'
import { Assert } from '../../assert/index'
describe('value/default/Import', () => {
it('Should use default', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{ default: 1 },
),
}).Import('A')
const R = Value.Default(T, undefined)
Assert.IsEqual(R, 1)
})
it('Should use value', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number(),
y: Type.Number(),
},
{ default: 1 },
),
}).Import('A')
const R = Value.Default(T, null)
Assert.IsEqual(R, null)
})
// ----------------------------------------------------------------
// Construction
// ----------------------------------------------------------------
it('Should should fully construct object 1', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Object(
{
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
},
{ default: {} },
),
y: Type.Object(
{
x: Type.Number({ default: 3 }),
y: Type.Number({ default: 4 }),
},
{ default: {} },
),
},
{ default: {} },
),
}).Import('A')
const R = Value.Default(T, undefined)
Assert.IsEqual(R, { x: { x: 1, y: 2 }, y: { x: 3, y: 4 } })
})
it('Should should fully construct object 2', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Object(
{
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
},
{ default: {} },
),
y: Type.Object(
{
x: Type.Number({ default: 3 }),
y: Type.Number({ default: 4 }),
},
{ default: {} },
),
},
{ default: {} },
),
}).Import('A')
const R = Value.Default(T, { x: null })
Assert.IsEqual(R, { x: null, y: { x: 3, y: 4 } })
})
it('Should should fully construct object 3', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Object(
{
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
},
{ default: {} },
),
y: Type.Object(
{
x: Type.Number({ default: 3 }),
y: Type.Number({ default: 4 }),
},
{ default: {} },
),
},
{ default: {} },
),
}).Import('A')
const R = Value.Default(T, { x: { x: null, y: null } })
Assert.IsEqual(R, { x: { x: null, y: null }, y: { x: 3, y: 4 } })
})
// ----------------------------------------------------------------
// Properties
// ----------------------------------------------------------------
it('Should use property defaults 1', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
},
{ default: 1 },
),
}).Import('A')
const R = Value.Default(T, {})
Assert.IsEqual(R, { x: 1, y: 2 })
})
it('Should use property defaults 2', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
}).Import('A')
const R = Value.Default(T, {})
Assert.IsEqual(R, {})
})
it('Should use property defaults 3', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Number({ default: 1 }),
y: Type.Number(),
}),
}).Import('A')
const R = Value.Default(T, {})
Assert.IsEqual(R, { x: 1 })
})
it('Should use property defaults 4', () => {
const T = Type.Module({
A: Type.Object({
x: Type.Number({ default: 1 }),
y: Type.Number(),
}),
}).Import('A')
const R = Value.Default(T, { x: 3 })
Assert.IsEqual(R, { x: 3 })
})
// ----------------------------------------------------------------
// AdditionalProperties
// ----------------------------------------------------------------
it('Should use additional property defaults 1', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
},
{
additionalProperties: Type.Number({ default: 3 }),
},
),
}).Import('A')
const R = Value.Default(T, {})
Assert.IsEqual(R, { x: 1, y: 2 })
})
it('Should use additional property defaults 2', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
},
{
additionalProperties: Type.Number({ default: 3 }),
},
),
}).Import('A')
const R = Value.Default(T, { x: null, y: null, z: undefined })
Assert.IsEqual(R, { x: null, y: null, z: 3 })
})
it('Should use additional property defaults 3', () => {
const T = Type.Module({
A: Type.Object(
{
x: Type.Number({ default: 1 }),
y: Type.Number({ default: 2 }),
},
{
additionalProperties: Type.Number(),
},
),
}).Import('A')
const R = Value.Default(T, { x: null, y: null, z: undefined })
Assert.IsEqual(R, { x: null, y: null, z: undefined })
})
// ----------------------------------------------------------------
// Mutation
// ----------------------------------------------------------------
// https://github.com/sinclairzx81/typebox/issues/726
it('Should retain defaults on operation', () => {
const A = Type.Module({
A: Type.Object({
a: Type.Object(
{
b: Type.Array(Type.String(), { default: [] }),
},
{ default: {} },
),
}),
}).Import('A')
const value = Value.Default(A, {})
Assert.IsEqual(value, { a: { b: [] } })
Assert.IsEqual(A.$defs.A.properties.a.default, {})
Assert.IsEqual(A.$defs.A.properties.a.properties.b.default, [])
})
// https://github.com/sinclairzx81/typebox/issues/726
it('Should retain schematics on operation', () => {
const A = Type.Module({
A: Type.Object({
a: Type.Object(
{
b: Type.Array(Type.String(), { default: [] }),
},
{ default: {} },
),
}),
}).Import('A')
const B = CloneType(A)
Value.Default(A, {})
Assert.IsEqual(A, B)
})
// ----------------------------------------------------------------
// Traveral: https://github.com/sinclairzx81/typebox/issues/962
// ----------------------------------------------------------------
it('Should traverse into an object 1 (initialize)', () => {
const M = Type.Module({
Y: Type.Object({ y: Type.String({ default: 'y' }) }),
X: Type.Object({ x: Type.Ref('Y') }),
})
const Y = M.Import('Y')
const X = M.Import('X')
Assert.IsEqual(Value.Default(Y, {}), { y: 'y' })
Assert.IsEqual(Value.Default(X, { x: {} }), { x: { y: 'y' } })
})
it('Should traverse into an object 2 (retain)', () => {
const M = Type.Module({
Y: Type.Object({ y: Type.String({ default: 'y' }) }),
X: Type.Object({ x: Type.Ref('Y') }),
})
const Y = M.Import('Y')
const X = M.Import('X')
Assert.IsEqual(Value.Default(X, { x: { y: 1 } }), { x: { y: 1 } })
})
it('Should traverse into an object 3 (ignore on undefined)', () => {
const M = Type.Module({
Y: Type.Object({ y: Type.String({ default: 'y' }) }),
X: Type.Object({ x: Type.Ref('Y') }),
})
const Y = M.Import('Y')
const X = M.Import('X')
Assert.IsEqual(Value.Default(X, { x: undefined }), { x: undefined })
})
// ----------------------------------------------------------------
// Exterior Object Defaults
// ----------------------------------------------------------------
it('Should default exterior into an object 1', () => {
const X = Type.Module({ A: Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) }).Import('A')
const R = Value.Default(X, undefined)
Assert.IsEqual(R, { x: 1 })
})
it('Should default exterior into an object 2', () => {
const X = Type.Module({ A: Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) }).Import('A')
const R = Value.Default(X, {})
Assert.IsEqual(R, { x: 1 })
})
it('Should default exterior into an object 3', () => {
const X = Type.Module({ A: Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) }).Import('A')
const R = Value.Default(X, { y: 3 })
Assert.IsEqual(R, { y: 3, x: 1 })
})
it('Should default exterior into an object 4', () => {
const X = Type.Module({ A: Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) }).Import('A')
const R = Value.Default(X, { y: 3, x: 7 })
Assert.IsEqual(R, { y: 3, x: 7 })
})
it('Should default exterior into an object 5', () => {
const X = Type.Module({ A: Type.Object({ x: Type.String({ default: 1 }) }, { default: {} }) }).Import('A')
const R = Value.Default(X, { x: 2 })
Assert.IsEqual(R, { x: 2 })
})
})

View File

@@ -9,6 +9,7 @@ import './constructor'
import './date'
import './enum'
import './function'
import './import'
import './integer'
import './intersect'
import './iterator'

View File

@@ -0,0 +1,157 @@
import * as Encoder from './_encoder'
import { Assert } from '../../assert'
import { TypeSystemPolicy } from '@sinclair/typebox/system'
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
// prettier-ignore
describe('value/transform/Import', () => {
// --------------------------------------------------------
// Identity
// --------------------------------------------------------
const T0 = Type.Transform(
Type.Module({ A: Type.Object({
x: Type.Number(),
y: Type.Number(),
})}).Import('A'),
)
.Decode((value) => value)
.Encode((value) => value)
it('Should decode identity', () => {
const R = Encoder.Decode(T0, { x: 1, y: 2 })
Assert.IsEqual(R, { x: 1, y: 2 })
})
it('Should encode identity', () => {
const R = Encoder.Encode(T0, { x: 1, y: 2 })
Assert.IsEqual(R, { x: 1, y: 2 })
})
it('Should throw on identity decode', () => {
Assert.Throws(() => Encoder.Decode(T0, undefined))
})
// ----------------------------------------------------------
// Object
// ----------------------------------------------------------
const T1 = Type.Transform(
Type.Module({ A: Type.Object({
x: Type.Number(),
y: Type.Number(),
})}).Import('A'),
)
.Decode((value) => 42)
.Encode((value) => ({ x: 1, y: 2 }))
it('Should decode mapped', () => {
const R = Encoder.Decode(T1, { x: 1, y: 2 })
Assert.IsEqual(R, 42)
})
it('Should encode mapped', () => {
const R = Encoder.Encode(T1, null)
Assert.IsEqual(R, { x: 1, y: 2 })
})
it('Should throw on mapped decode', () => {
Assert.Throws(() => Encoder.Decode(T1, undefined))
})
// ----------------------------------------------------------
// Object: Transform Property
// ----------------------------------------------------------
const N2 = Type.Transform(Type.Module({ A: Type.Integer() }).Import('A'))
.Decode((value) => value.toString())
.Encode((value) => parseInt(value))
const T2 = Type.Object({
x: N2,
y: N2,
})
it('Should decode transform property', () => {
const R = Encoder.Decode(T2, { x: 1, y: 2 })
Assert.IsEqual(R, { x: '1', y: '2' })
})
it('Should encode transform property', () => {
const R = Encoder.Encode(T2, { x: '1', y: '2' })
Assert.IsEqual(R, { x: 1, y: 2 })
})
it('Should throw on decode transform property', () => {
Assert.Throws(() => Encoder.Decode(T2, undefined))
})
// ----------------------------------------------------------
// Object: Transform Property Nested (Twizzle)
// ----------------------------------------------------------
const N3 = Type.Transform(Type.Module({ A: Type.Integer() }).Import('A'))
.Decode((value) => value.toString())
.Encode((value) => parseInt(value))
const T3 = Type.Transform(
Type.Object({
x: N3,
y: N3,
}),
)
.Decode((value) => ({ x: value.y, y: value.x }))
.Encode((value) => ({ x: value.y, y: value.x }))
it('Should decode transform property nested', () => {
const R = Encoder.Decode(T3, { x: 1, y: 2 })
Assert.IsEqual(R, { x: '2', y: '1' })
})
it('Should encode transform property nested', () => {
const R = Encoder.Encode(T3, { x: '1', y: '2' })
Assert.IsEqual(R, { x: 2, y: 1 })
})
it('Should throw on decode transform property nested', () => {
Assert.Throws(() => Encoder.Decode(T3, undefined))
})
// ----------------------------------------------------------
// Object Additional Properties
// ----------------------------------------------------------
const N4 = Type.Transform(Type.Module({ A: Type.Integer() }).Import('A'))
.Decode((value) => value.toString())
.Encode((value) => parseInt(value))
const T4 = Type.Transform(
Type.Object(
{
x: Type.Number(),
},
{
additionalProperties: N4,
},
),
)
.Decode((value) => value)
.Encode((value) => value)
it('Should decode additional property', () => {
const R = Encoder.Decode(T4, { x: 1, y: 2 })
Assert.IsEqual(R, { x: 1, y: '2' })
})
it('Should encode additional property', () => {
const R = Encoder.Encode(T4, { x: 1, y: '5' })
Assert.IsEqual(R, { x: 1, y: 5 })
})
it('Should throw on additional property 1', () => {
Assert.Throws(() => Encoder.Decode(T4, undefined))
})
it('Should throw on additional property 2', () => {
Assert.Throws(() => Encoder.Decode(T4, { x: 1, y: true }))
})
// ------------------------------------------------------------
// Map
// ------------------------------------------------------------
const T5 = Type.Transform(Type.Module({ A: Type.Object({ x: Type.String(), y: Type.String() })}).Import('A'))
.Decode((value) => new Map(Object.entries(value)))
.Encode((value) => Object.fromEntries(value.entries()) as any)
it('should decode map', () => {
const R = Encoder.Decode(T5, { x: 'hello', y: 'world' })
Assert.IsInstanceOf(R, Map)
Assert.IsEqual(R.get('x'), 'hello')
Assert.IsEqual(R.get('y'), 'world')
})
it('should encode map', () => {
const R = Encoder.Encode(
T5,
new Map([
['x', 'hello'],
['y', 'world'],
]),
)
Assert.IsEqual(R, { x: 'hello', y: 'world' })
})
it('Should throw on map decode', () => {
Assert.Throws(() => Encoder.Decode(T5, {}))
})
})

View File

@@ -8,6 +8,7 @@ import './constructor'
import './date'
import './enum'
import './function'
import './import'
import './integer'
import './intersect'
import './iterator'

View File

@@ -8,7 +8,7 @@ describe('value/transform/Ref', () => {
// Identity
// --------------------------------------------------------
const N0 = Type.Number({ $id: 'N0' })
const T0 = Type.Transform(Type.Ref(N0))
const T0 = Type.Transform(Type.Ref('N0'))
.Decode((value) => value)
.Encode((value) => value)
it('Should decode mapped', () => {
@@ -26,7 +26,7 @@ describe('value/transform/Ref', () => {
// Mapped
// --------------------------------------------------------
const N1 = Type.Number({ $id: 'N1' })
const T1 = Type.Transform(Type.Ref(N1))
const T1 = Type.Transform(Type.Unsafe<number>(Type.Ref('N1')))
.Decode((value) => value + 1)
.Encode((value) => value - 1)
it('Should decode mapped', () => {
@@ -46,7 +46,7 @@ describe('value/transform/Ref', () => {
const N2 = Type.Transform(Type.Number({ $id: 'N2' }))
.Decode((value) => value + 1)
.Encode((value) => value - 1)
const T2 = Type.Transform(Type.Ref(N2))
const T2 = Type.Transform(Type.Unsafe<number>(Type.Ref('N2')))
.Decode((value) => value + 1)
.Encode((value) => value - 1)
it('Should decode mapped remote', () => {

View File

@@ -1,56 +0,0 @@
import { Expect } from './assert'
import { Type, TRef, TObject, TNumber } from '@sinclair/typebox'
// prettier-ignore
const Vector: TObject<{
x: TNumber;
y: TNumber;
}> = Type.Object({
x: Type.Number(),
y: Type.Number(),
}, { $id: 'Vector' })
// prettier-ignore
const VectorRef: TRef<TObject<{
x: TNumber;
y: TNumber;
}>> = Type.Ref(Vector)
// prettier-ignore
const Vertex: TObject<{
position: TRef<TObject<{
x: TNumber;
y: TNumber;
}>>;
texcoord: TRef<TObject<{
x: TNumber;
y: TNumber;
}>>;
}> = Type.Object({
position: VectorRef,
texcoord: VectorRef,
})
// prettier-ignore
const VertexDeref: TObject<{
position: TObject<{
x: TNumber;
y: TNumber;
}>;
texcoord: TObject<{
x: TNumber;
y: TNumber;
}>;
}> = Type.Deref(Vertex, [Vector])
// prettier-ignore
Expect(VertexDeref).ToStatic<{
position: {
x: number;
y: number;
};
texcoord: {
x: number;
y: number;
};
}>

View File

@@ -3,11 +3,11 @@ import { Type, Static } from '@sinclair/typebox'
{
const T = Type.String({ $id: 'T' })
const R = Type.Ref(T)
const R = Type.Ref('T')
type T = Static<typeof T>
type R = Static<typeof R>
Expect(T).ToStatic<string>()
Expect(R).ToStatic<string>()
Expect(R).ToStatic<unknown>()
}