diff --git a/changelog/0.34.0.md b/changelog/0.34.0.md new file mode 100644 index 0000000..fdc9a75 --- /dev/null +++ b/changelog/0.34.0.md @@ -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) + + + + +## Enhancements + +Below are the enhancements introduced in Version 0.34.0. + + + +### 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 // type A = { + // b: { + // c: { + // a: { + // b: ... + // } + // } + // } + // } +``` + + + +### 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. + + + +## Breaking Changes + +The following are the breaking changes in Revision 0.34.0. + + + +### 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 // type R = string + +// Revision 0.34.0 + +const T = Type.String({ $id: 'T' }) + +const R = Type.Ref('T') + +type R = Static // 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>(Type.Ref('T')) + +type R = Static // type R = string +``` + + + +### 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. + + + +### 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(schema: T): TStrict { + return JSON.parse(JSON.stringify(schema)) +} +``` \ No newline at end of file diff --git a/example/prototypes/index.ts b/example/prototypes/index.ts index cb81ad0..179da98 100644 --- a/example/prototypes/index.ts +++ b/example/prototypes/index.ts @@ -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' diff --git a/example/prototypes/module.ts b/example/prototypes/module.ts deleted file mode 100644 index e56dcb2..0000000 --- a/example/prototypes/module.ts +++ /dev/null @@ -1,166 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/prototypes - -The MIT License (MIT) - -Copyright (c) 2017-2024 Haydn Paterson (sinclair) - -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 = ( - Infer -) -// prettier-ignore -export type InferModuleRef = ( - Ref extends keyof Module ? Infer : never -) -// prettier-ignore -export type InferObject = { - [K in keyof Properties]: Infer -} & {} -// prettier-ignore -export type InferConstructor = Types.Ensure< - new (...args: InferTuple) => Infer -> -// prettier-ignore -export type InferFunction = Types.Ensure< - (...args: InferTuple) => Infer -> -// prettier-ignore -export type InferTuple = ( - Types extends [infer L extends Types.TSchema, ...infer R extends Types.TSchema[]] - ? InferTuple]> - : Result -) -// prettier-ignore -export type InferIntersect = ( - Types extends [infer L extends Types.TSchema, ...infer R extends Types.TSchema[]] - ? InferIntersect> - : Result -) -// prettier-ignore -export type InferUnion = ( - Types extends [infer L extends Types.TSchema, ...infer R extends Types.TSchema[]] - ? InferUnion> - : Result -) -// prettier-ignore -export type InferArray = ( - Types.Ensure>> -) -// prettier-ignore -export type InferAsyncIterator = ( - Types.Ensure>> -) -// prettier-ignore -export type InferIterator = ( - Types.Ensure>> -) -// prettier-ignore -type Infer = ( - Type extends TImport ? InferImport : - Type extends TModuleRef ? InferModuleRef : - Type extends Types.TObject ? InferObject : - Type extends Types.TConstructor ? InferConstructor : - Type extends Types.TFunction ? InferFunction : - Type extends Types.TTuple ? InferTuple : - Type extends Types.TIntersect ? InferIntersect : - Type extends Types.TUnion ? InferUnion : - Type extends Types.TArray ? InferArray : - Type extends Types.TAsyncIterator ? InferAsyncIterator : - Type extends Types.TIterator ? InferIterator : - Type extends Types.TTemplateLiteral ? Types.Static> : - Type extends Types.TLiteral ? 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 extends Types.TSchema { - [Types.Kind]: 'ModuleRef' - $ref: Ref -} -// prettier-ignore -export function ModuleRef($ref: Ref): TModuleRef { - 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 - -// prettier-ignore -export interface TImport extends Types.TSchema { - [Types.Kind]: 'Import' - static: InferImport - $defs: Definitions - $ref: Key -} -// ------------------------------------------------------------------ -// Module -// ------------------------------------------------------------------ -// prettier-ignore -export class ModuleInstance { - constructor(private readonly properties: Properties) {} - public Import(key: Key): TImport { - 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: Properties): ModuleInstance { - return new ModuleInstance(properties) -} - - - diff --git a/example/prototypes/readme.md b/example/prototypes/readme.md index 7c7c429..cd95b34 100644 --- a/example/prototypes/readme.md +++ b/example/prototypes/readme.md @@ -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 diff --git a/package-lock.json b/package-lock.json index 262945a..fbacace 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 1a8dff9..44b16b8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/readme.md b/readme.md index 5836c10..39fd650 100644 --- a/readme.md +++ b/readme.md @@ -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 // type T = string | null ``` - + -### 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 // 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'> - - -### 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 // type Node = { - // id: string - // nodes: Node[] - // } - -function test(node: Node) { - const id = node.nodes[0].nodes[0].id // id is string -} +type A = Static // type A = { + // b: { + // c: { + // a: { + // b: ... + // } + // } + // } + // } ``` @@ -1084,11 +1022,11 @@ if(TypeGuard.IsString(T)) { -## 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<{ // }> ``` + + + + +### 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 +}) // }> +``` + + + +### 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'> +``` + ### 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`) // const A: TObject<{ +const A = Parse({ T }, 'Partial') // const A: TObject<{ // x: TOptional, // y: TOptional, // z: TOptional // }> -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; // }>]> - - ``` ### 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 = { -### 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 TypeScript’s 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 TypeBox’s 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. @@ -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' │ └──────────────────────┴────────────┴────────────┴─────────────┘ ``` diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index bc1fd8b..6e43870 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -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 { return (this.hasTransform ? TransformDecode(this.schema, this.references, value) : value) as never } /** Encodes a value or throws if error */ - public Encode, Result extends Static = Static>(value: unknown): Result { + public Encode, 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 { yield `(typeof ${value} === 'function')` } + function* FromImport(schema: TImport, references: TSchema[], value: string): IterableIterator { + 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 { 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 { 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 { @@ -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': diff --git a/src/errors/errors.ts b/src/errors/errors.ts index 44a8b02..9bb1f6c 100644 --- a/src/errors/errors.ts +++ b/src/errors/errors.ts @@ -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 { if (!IsFunction(value)) yield Create(ValueErrorType.Function, schema, path, value) } +function* FromImport(schema: TImport, references: TSchema[], path: string, value: any): IterableIterator { + 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 { if (!IsInteger(value)) return yield Create(ValueErrorType.Integer, schema, path, value) if (IsDefined(schema.exclusiveMaximum) && !(value < schema.exclusiveMaximum)) { @@ -566,6 +572,8 @@ function* Visit(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': diff --git a/src/index.ts b/src/index.ts index 6a99799..90525bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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' diff --git a/src/syntax/parse.ts b/src/syntax/parse.ts index 4605b9c..1ec0d41 100644 --- a/src/syntax/parse.ts +++ b/src/syntax/parse.ts @@ -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, Code extends string> = Static.Parse[0] +/** `[Syntax]` Infers a TypeBox type from TypeScript syntax. */ +export type StaticParseAsSchema, Code extends string> = Static.Parse[0] -/** `[Experimental]` Infers a TypeScript type from TypeScript syntax. */ -export type StaticParseAsType, Code extends string> = StaticParseAsSchema extends infer Type extends TSchema ? StaticDecode : undefined +/** `[Syntax]` Infers a TypeScript type from TypeScript syntax. */ +export type StaticParseAsType, Code extends string> = StaticParseAsSchema extends infer Type extends Types.TSchema ? Types.StaticDecode : undefined -/** `[Experimental]` Parses a TypeBox type from TypeScript syntax. */ -export function Parse, Code extends string>(context: Context, code: Code, options?: SchemaOptions): StaticParseAsSchema -/** `[Experimental]` Parses a TypeBox type from TypeScript syntax. */ -export function Parse(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, Code extends string>(context: Context, code: Code, options?: Types.SchemaOptions): StaticParseAsSchema +/** `[Syntax]` Parses a TypeBox type from TypeScript syntax. */ +export function Parse(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, Code extends string>(context: Context, code: Code, options?: SchemaOptions): TSchema | undefined -/** `[Experimental]` Parses a TypeBox TSchema from TypeScript syntax */ -export function ParseOnly(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, 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: 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 } diff --git a/src/syntax/runtime.ts b/src/syntax/runtime.ts index 0d28dbb..15001ee 100644 --- a/src/syntax/runtime.ts +++ b/src/syntax/runtime.ts @@ -48,6 +48,7 @@ const SemiColon = ';' const SingleQuote = "'" const DoubleQuote = '"' const Tilde = '`' +const Equals = '=' // ------------------------------------------------------------------ // DestructureRight @@ -59,13 +60,140 @@ function DestructureRight(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('ExportModifier'), + Runtime.Const('type'), + Runtime.Ident(), + Runtime.Const(Equals), + Runtime.Ref('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('ExportModifier'), + Runtime.Const('interface'), + Runtime.Ident(), + Runtime.Ref('Heritage'), + Runtime.Const(LBrace), + Runtime.Ref('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('ModuleType'), ModulePropertiesDelimiter, Runtime.Ref('ModuleProperties')]), + Runtime.Tuple([Runtime.Ref('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('ExportModifier'), Runtime.Const('module'), ModuleIdentifier, Runtime.Const(LBrace), Runtime.Ref('ModuleProperties'), Runtime.Const(RBrace) +], values => ModuleDeclarationMapping(...values)) +// ------------------------------------------------------------------ // Reference // ------------------------------------------------------------------ // prettier-ignore -const Reference = Runtime.Ident((value, context: Record) => { - 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('IndexArray'), Runtime.Ref('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('PropertyReadonly'), + Runtime.Ref('Readonly'), Runtime.Ref('PropertyKey'), - Runtime.Ref('PropertyOptional'), + Runtime.Ref('Optional'), Runtime.Const(Colon), Runtime.Ref('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('Property'), Runtime.Ref('PropertyDelimiter'), Runtime.Ref('Properties')]), + Runtime.Tuple([Runtime.Ref('Property'), Runtime.Ref('PropertyDelimiter')]), + Runtime.Tuple([Runtime.Ref('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[]) => 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[]>('Properties'), + Runtime.Ref('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('Type'), Runtime.Const(RBracket), Runtime.Const(Colon), Runtime.Ref('Type'), Runtime.Const(RBrace) + Runtime.Const(LBrace), + Runtime.Const(LBracket), + Runtime.Ident(), + Runtime.Const('in'), + Runtime.Ref('Type'), + Runtime.Const(RBracket), + Runtime.Const(Colon), + Runtime.Ref('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 }) diff --git a/src/syntax/static.ts b/src/syntax/static.ts index ff75dca..0b30b8e 100644 --- a/src/syntax/static.ts +++ b/src/syntax/static.ts @@ -48,6 +48,7 @@ type SemiColon = ';' type SingleQuote = "'" type DoubleQuote = '"' type Tilde = '`' +type Equals = '=' // ------------------------------------------------------------------ // Delimit @@ -135,18 +136,156 @@ type Delimit = ], DelimitMapping> ) // ------------------------------------------------------------------ +// Deref +// ------------------------------------------------------------------ +// prettier-ignore +type Deref = ( + Ref extends keyof Context ? Context[Ref] : Types.TRef +) +// ------------------------------------------------------------------ +// 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, Type +], TypeAliasDeclarationMapping> + +// ------------------------------------------------------------------ +// HeritageList +// ------------------------------------------------------------------ +// prettier-ignore (note, heritage list should disallow trailing comma) +type HeritageListDelimiter = Static.Union<[Static.Tuple<[Static.Const, Static.Const]>, Static.Tuple<[Static.Const]>]> +// prettier-ignore +type HeritageListReduce = ( + Values extends [infer Ref extends string, ...infer Rest extends string[]] + ? HeritageListReduce]> + : Result +) +// prettier-ignore +interface HeritageListMapping extends Static.IMapping { + output: ( + this['context'] extends Types.TProperties ? + this['input'] extends string[] + ? HeritageListReduce + : [] + : [] + ) +} +// prettier-ignore +type HeritageList = Static.Union<[Delimit], 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]> } + : never +} +// prettier-ignore +type InterfaceDeclaration = Static.Tuple<[ + ExportModifier, + Static.Const<'interface'>, + Static.Ident, + Heritage, + Static.Const, + Properties, + Static.Const, +], InterfaceDeclarationMapping> +// ------------------------------------------------------------------ +// ModuleType +// ------------------------------------------------------------------ +// prettier-ignore +type ModuleType = Static.Union<[ + InterfaceDeclaration, + TypeAliasDeclaration +]> +// ------------------------------------------------------------------ +// ModuleProperties +// ------------------------------------------------------------------ +// prettier-ignore +type ModulePropertiesDelimiter = Static.Union<[ + Static.Tuple<[Static.Const, Static.Const]>, + Static.Tuple<[Static.Const]>, + Static.Tuple<[Static.Const]>, +]> +// prettier-ignore +type ModulePropertiesReduce = ( + Value extends [infer ModuleType extends Types.TProperties, unknown[], ...infer Rest extends unknown[]] ? ModulePropertiesReduce : + Value extends [infer ModuleType extends Types.TProperties] ? ModulePropertiesReduce<[], Result & ModuleType> : + Types.Evaluate +) +// prettier-ignore +interface ModulePropertiesMapping extends Static.IMapping { + output: this['input'] extends unknown[] ? ModulePropertiesReduce : 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 + : never +} +// prettier-ignore +type ModuleDeclaration = Static.Tuple<[ + ExportModifier, Static.Const<'module'>, ModuleIdentifier, Static.Const, ModuleProperties, Static.Const +], 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 - : never + output: this['context'] extends Types.TProperties + ? this['input'] extends string + ? Deref + : never + : never } -type Reference = Static.Tuple<[Static.Ident], ReferenceMapping> - +type Reference = Static.Ident // ------------------------------------------------------------------ // 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, Type, Static.Const, 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]>, Static.Tuple<[]>], PropertyOptionalMapping> +type Optional = Static.Union<[Static.Tuple<[Static.Const]>, 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 : - [Readonly, Optional] extends [true, false] ? Types.TReadonly : - [Readonly, Optional] extends [false, true] ? Types.TOptional : + [IsReadonly, IsOptional] extends [true, true] ? Types.TReadonlyOptional : + [IsReadonly, IsOptional] extends [true, false] ? Types.TReadonly : + [IsReadonly, IsOptional] extends [false, true] ? Types.TOptional : Type ) } : never } - -type Property = Static.Tuple<[PropertyReadonly, PropertyKey, PropertyOptional, Static.Const, Type], PropertyMapping> - -type PropertiesEvaluate = { [K in keyof T]: T[K] } & {} +type Property = Static.Tuple<[Readonly, PropertyKey, Optional, Static.Const, Type], PropertyMapping> // prettier-ignore type PropertyDelimiter = Static.Union<[ Static.Tuple<[Static.Const, Static.Const]>, @@ -409,7 +542,7 @@ type PropertyDelimiter = Static.Union<[ // prettier-ignore type PropertiesReduce = ( PropertiesArray extends [infer Left extends Types.TProperties, ...infer Right extends Types.TProperties[]] - ? PropertiesReduce> + ? PropertiesReduce> : 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> // Uint8Array // ------------------------------------------------------------------ type Uint8Array = Static.Const<'Uint8Array', Static.As> + +// ------------------------------------------------------------------ +// Main +// ------------------------------------------------------------------ +// prettier-ignore +export type Main = Static.Union<[ + ModuleDeclaration, + TypeAliasDeclaration, + InterfaceDeclaration, + Type +]> diff --git a/src/type/deref/deref.ts b/src/type/deref/deref.ts deleted file mode 100644 index 76d626a..0000000 --- a/src/type/deref/deref.ts +++ /dev/null @@ -1,174 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/type - -The MIT License (MIT) - -Copyright (c) 2017-2024 Haydn Paterson (sinclair) - -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 [infer L extends TSchema, ...infer R extends TSchema[]] - ? TFromRest]> - : Acc -) -function FromRest(schema: [...T], references: TSchema[]): TFromRest { - return schema.map((schema) => Deref(schema, references)) as never -} -// ------------------------------------------------------------------ -// FromProperties -// ------------------------------------------------------------------ -// prettier-ignore -type FromProperties = Evaluate<{ - [K in keyof T]: TDeref -}> -// 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(schema: T, references: TSchema[]): TDeref { - 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 TConstructor ? TConstructor, TDeref> : - T extends TFunction ? TFunction, TDeref> : - T extends TIntersect ? TIntersect> : - T extends TUnion ? TUnion> : - T extends TTuple ? TTuple> : - T extends TObject ? TObject> : - T extends TArray ? TArray> : - T extends TPromise ? TPromise> : - T extends TAsyncIterator ? TAsyncIterator> : - T extends TIterator ? TIterator> : - T extends TRef ? TDeref : - T -// ------------------------------------------------------------------ -// TDeref -// ------------------------------------------------------------------ -/** `[Json]` Creates a dereferenced type */ -export function Deref(schema: T, references: TSchema[]): TDeref { - return DerefResolve(CloneType(schema), CloneRest(references)) -} diff --git a/src/type/guard/kind.ts b/src/type/guard/kind.ts index b68f01b..2e61a1d 100644 --- a/src/type/guard/kind.ts +++ b/src/type/guard/kind.ts @@ -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') } diff --git a/src/type/guard/type.ts b/src/type/guard/type.ts index b2e9daa..ed8eb54 100644 --- a/src/type/guard/type.ts +++ b/src/type/guard/type.ts @@ -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 ( diff --git a/src/type/index.ts b/src/type/index.ts index b69a1fa..d9d5d50 100644 --- a/src/type/index.ts +++ b/src/type/index.ts @@ -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' diff --git a/src/type/deref/index.ts b/src/type/module/index.ts similarity index 98% rename from src/type/deref/index.ts rename to src/type/module/index.ts index 28c03fc..ce46d5a 100644 --- a/src/type/deref/index.ts +++ b/src/type/module/index.ts @@ -26,4 +26,4 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -export * from './deref' +export * from './module' diff --git a/src/type/module/module.ts b/src/type/module/module.ts new file mode 100644 index 0000000..3fef83c --- /dev/null +++ b/src/type/module/module.ts @@ -0,0 +1,189 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox/type + +The MIT License (MIT) + +Copyright (c) 2017-2024 Haydn Paterson (sinclair) + +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 = ( + Infer +) +// prettier-ignore +type InferRef = ( + Ref extends keyof Properties ? Infer : never +) +// prettier-ignore +type InferObject = { + [K in keyof Properties]: Infer +} & {} +// prettier-ignore +type InferConstructor = Ensure< + new (...args: InferTuple) => Infer +> +// prettier-ignore +type InferFunction = Ensure< + (...args: InferTuple) => Infer +> +// prettier-ignore +type InferTuple = ( + Types extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? InferTuple]> + : Result +) +// prettier-ignore +type InferIntersect = ( + Types extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? InferIntersect> + : Result +) +// prettier-ignore +type InferUnion = ( + Types extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? InferUnion> + : Result +) +// prettier-ignore +type InferArray = ( + Ensure>> +) +// prettier-ignore +type InferAsyncIterator = ( + Ensure>> +) +// prettier-ignore +type InferIterator = ( + Ensure>> +) +// prettier-ignore +type Infer = ( + Type extends TImport ? InferImport : + Type extends TRef ? InferRef : + Type extends TObject ? InferObject : + Type extends TConstructor ? InferConstructor : + Type extends TFunction ? InferFunction : + Type extends TTuple ? InferTuple : + Type extends TIntersect ? InferIntersect : + Type extends TUnion ? InferUnion : + Type extends TArray ? InferArray : + Type extends TAsyncIterator ? InferAsyncIterator : + Type extends TIterator ? InferIterator : + Type extends TTemplateLiteral ? Static> : + Type extends TLiteral ? 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 extends TSchema { + static: { [K in keyof ModuleProperties]: Static } + $defs: ModuleProperties +} +// ------------------------------------------------------------------ +// Import +// ------------------------------------------------------------------ +// prettier-ignore +export interface TImport extends TSchema { + [Kind]: 'Import' + static: InferImport + $defs: ModuleProperties + $ref: Key +} +// ------------------------------------------------------------------ +// Module +// ------------------------------------------------------------------ +// prettier-ignore +export class TModule { + constructor(private readonly $defs: Properties, private readonly options: SchemaOptions = {}) {} + + /** `[Json]` Returns the Type definitions for this module */ + public Defs(): TDefinitions { + return CreateType({ $defs: this.ResolveDefinitionsWithIdentifiers() }, this.options) as never + } + /** `[Json]` Imports a Type by Key. */ + public Import(key: Key, options?: SchemaOptions): TImport { + 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: Properties): TModule { + return new TModule(properties) +} diff --git a/src/type/ref/ref.ts b/src/type/ref/ref.ts index 3edd35b..f61f04f 100644 --- a/src/type/ref/ref.ts +++ b/src/type/ref/ref.ts @@ -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 extends TSchema { +export interface TRef extends TSchema { [Kind]: 'Ref' - static: Static - $ref: string + static: unknown + $ref: Ref } /** `[Json]` Creates a Ref type. The referenced type must contain a $id */ -export function Ref(schema: T, options?: SchemaOptions): TRef -/** `[Json]` Creates a Ref type. */ -export function Ref($ref: string, options?: SchemaOptions): TRef -/** `[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: Ref, options?: SchemaOptions): TRef { + return CreateType({ [Kind]: 'Ref', $ref }, options) as never } diff --git a/src/type/static/static.ts b/src/type/static/static.ts index 7e346b7..2ba1f1b 100644 --- a/src/type/static/static.ts +++ b/src/type/static/static.ts @@ -77,7 +77,7 @@ export type TDecodeType = ( T extends TPromise ? TPromise> : T extends TRecord ? TRecord> : T extends TRecursive ? TRecursive> : - T extends TRef ? TRef> : + T extends TRef ? TRef : T extends TTuple ? TTuple> : T extends TUnion ? TUnion> : T diff --git a/src/type/strict/index.ts b/src/type/strict/index.ts deleted file mode 100644 index 24555f7..0000000 --- a/src/type/strict/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/type - -The MIT License (MIT) - -Copyright (c) 2017-2024 Haydn Paterson (sinclair) - -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' diff --git a/src/type/strict/strict.ts b/src/type/strict/strict.ts deleted file mode 100644 index 96da5a1..0000000 --- a/src/type/strict/strict.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*-------------------------------------------------------------------------- - -@sinclair/typebox/type - -The MIT License (MIT) - -Copyright (c) 2017-2024 Haydn Paterson (sinclair) - -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 - -/** - * @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(schema: T): TStrict { - return JSON.parse(JSON.stringify(schema)) -} diff --git a/src/type/type/json.ts b/src/type/type/json.ts index 0650ebc..7613dc3 100644 --- a/src/type/type/json.ts +++ b/src/type/type/json.ts @@ -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(schema: T): TStrict { - return Strict(schema) - } // ------------------------------------------------------------------------ // Modifiers // ------------------------------------------------------------------------ @@ -145,10 +129,6 @@ export class JsonTypeBuilder { public Const(value: T, options?: SchemaOptions): TConst { return Const(value, options) } - /** `[Json]` Creates a dereferenced type */ - public Deref(schema: T, references: TSchema[]): TDeref { - return Deref(schema, references) - } /** `[Json]` Creates a Enum type */ public Enum>(item: T, options?: SchemaOptions): TEnum { return Enum(item, options) @@ -227,6 +207,10 @@ export class JsonTypeBuilder { public Mapped(key: any, map: TMappedFunction, options?: ObjectOptions): any { return Mapped(key, map, options) } + /** `[Json]` Creates a Type Definition Module. */ + public Module(properties: Properties): TModule { + 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(callback: (thisType: TThis) => T, options?: SchemaOptions): TRecursive { return Recursive(callback, options) } - /** `[Json]` Creates a Ref type. The referenced type must contain a $id */ - public Ref(schema: T, options?: SchemaOptions): TRef /** `[Json]` Creates a Ref type. */ - public Ref($ref: string, options?: SchemaOptions): TRef - /** `[Json]` Creates a Ref type. */ - public Ref(unresolved: TSchema | string, options?: SchemaOptions) { - return Ref(unresolved as any, options) + public Ref($ref: Ref, options?: SchemaOptions): TRef { + return Ref($ref, options) } /** `[Json]` Constructs a type where all properties are required */ public Required(T: T, options?: SchemaOptions): TRequiredFromMappedResult diff --git a/src/type/type/type.ts b/src/type/type/type.ts index 8dd7cc8..d00686c 100644 --- a/src/type/type/type.ts +++ b/src/type/type/type.ts @@ -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' diff --git a/src/value/cast/cast.ts b/src/value/cast/cast.ts index 78ac754..a6f9d98 100644 --- a/src/value/cast/cast.ts +++ b/src/value/cast/cast.ts @@ -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': diff --git a/src/value/check/check.ts b/src/value/check/check.ts index 436c0f0..b1af0dd 100644 --- a/src/value/check/check.ts +++ b/src/value/check/check.ts @@ -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(schema: T, references: TSchema[], value: any): boolean { - const references_ = IsDefined(schema.$id) ? [...references, schema] : references + const references_ = IsDefined(schema.$id) ? Pushref(schema, references) : references const schema_ = schema as any switch (schema_[Kind]) { case 'Any': @@ -434,6 +440,8 @@ function Visit(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': diff --git a/src/value/clean/clean.ts b/src/value/clean/clean.ts index d3be4a7..341cf02 100644 --- a/src/value/clean/clean.ts +++ b/src/value/clean/clean.ts @@ -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': diff --git a/src/value/convert/convert.ts b/src/value/convert/convert.ts index a5ddf7b..1cfaa10 100644 --- a/src/value/convert/convert.ts +++ b/src/value/convert/convert.ts @@ -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': diff --git a/src/value/create/create.ts b/src/value/create/create.ts index a1c24c5..478aab1 100644 --- a/src/value/create/create.ts +++ b/src/value/create/create.ts @@ -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': diff --git a/src/value/default/default.ts b/src/value/default/default.ts index 19b4b64..30ddbc9 100644 --- a/src/value/default/default.ts +++ b/src/value/default/default.ts @@ -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': diff --git a/src/value/transform/decode.ts b/src/value/transform/decode.ts index be56d12..2498058 100644 --- a/src/value/transform/decode.ts +++ b/src/value/transform/decode.ts @@ -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 @@ -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': diff --git a/src/value/transform/encode.ts b/src/value/transform/encode.ts index cb626f9..1f033dc 100644 --- a/src/value/transform/encode.ts +++ b/src/value/transform/encode.ts @@ -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': diff --git a/test/runtime/compiler-ajv/index.ts b/test/runtime/compiler-ajv/index.ts index ae974b9..3963d5c 100644 --- a/test/runtime/compiler-ajv/index.ts +++ b/test/runtime/compiler-ajv/index.ts @@ -9,6 +9,7 @@ import './integer' import './intersect' import './keyof' import './literal' +import './module' import './never' import './not' import './null' diff --git a/test/runtime/compiler-ajv/module.ts b/test/runtime/compiler-ajv/module.ts new file mode 100644 index 0000000..9de6813 --- /dev/null +++ b/test/runtime/compiler-ajv/module.ts @@ -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') + }) +}) diff --git a/test/runtime/compiler-ajv/ref.ts b/test/runtime/compiler-ajv/ref.ts index d9bf5c9..564dbc1 100644 --- a/test/runtime/compiler-ajv/ref.ts +++ b/test/runtime/compiler-ajv/ref.ts @@ -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' }, ) diff --git a/test/runtime/compiler/index.ts b/test/runtime/compiler/index.ts index 487c94a..21decd7 100644 --- a/test/runtime/compiler/index.ts +++ b/test/runtime/compiler/index.ts @@ -16,6 +16,7 @@ import './iterator' import './keyof' import './kind' import './literal' +import './module' import './never' import './not' import './null' diff --git a/test/runtime/compiler/module.ts b/test/runtime/compiler/module.ts new file mode 100644 index 0000000..02fcb4d --- /dev/null +++ b/test/runtime/compiler/module.ts @@ -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') + }) +}) diff --git a/test/runtime/compiler/ref.ts b/test/runtime/compiler/ref.ts index 771a7b4..1cd002f 100644 --- a/test/runtime/compiler/ref.ts +++ b/test/runtime/compiler/ref.ts @@ -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]) }) diff --git a/test/runtime/compiler/unicode.ts b/test/runtime/compiler/unicode.ts index 0e3e569..f5ef171 100644 --- a/test/runtime/compiler/unicode.ts +++ b/test/runtime/compiler/unicode.ts @@ -31,7 +31,7 @@ describe('compiler/Unicode', () => { }, ) const T = Type.Object({ - vector: Type.Ref(R), + vector: Type.Ref(R.$id!), }) Ok( T, diff --git a/test/runtime/syntax/syntax.ts b/test/runtime/syntax/syntax.ts index 11e2829..ae70567 100644 --- a/test/runtime/syntax/syntax.ts +++ b/test/runtime/syntax/syntax.ts @@ -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)) diff --git a/test/runtime/type/guard/kind/deref.ts b/test/runtime/type/guard/kind/deref.ts deleted file mode 100644 index 98e6811..0000000 --- a/test/runtime/type/guard/kind/deref.ts +++ /dev/null @@ -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 - }) -}) diff --git a/test/runtime/type/guard/kind/import.ts b/test/runtime/type/guard/kind/import.ts new file mode 100644 index 0000000..ee430ad --- /dev/null +++ b/test/runtime/type/guard/kind/import.ts @@ -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)) + }) +}) diff --git a/test/runtime/type/guard/kind/index.ts b/test/runtime/type/guard/kind/index.ts index 271b53f..ec0073d 100644 --- a/test/runtime/type/guard/kind/index.ts +++ b/test/runtime/type/guard/kind/index.ts @@ -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' diff --git a/test/runtime/type/guard/type/deref.ts b/test/runtime/type/guard/type/deref.ts deleted file mode 100644 index 64f6d7d..0000000 --- a/test/runtime/type/guard/type/deref.ts +++ /dev/null @@ -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 - }) -}) diff --git a/test/runtime/type/guard/type/import.ts b/test/runtime/type/guard/type/import.ts new file mode 100644 index 0000000..fdce650 --- /dev/null +++ b/test/runtime/type/guard/type/import.ts @@ -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)) + }) +}) diff --git a/test/runtime/type/guard/type/index.ts b/test/runtime/type/guard/type/index.ts index 271b53f..ec0073d 100644 --- a/test/runtime/type/guard/type/index.ts +++ b/test/runtime/type/guard/type/index.ts @@ -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' diff --git a/test/runtime/type/guard/type/ref.ts b/test/runtime/type/guard/type/ref.ts index c864cbd..c11b1a3 100644 --- a/test/runtime/type/guard/type/ref.ts +++ b/test/runtime/type/guard/type/ref.ts @@ -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) diff --git a/test/runtime/value/cast/import.ts b/test/runtime/value/cast/import.ts new file mode 100644 index 0000000..56a5a92 --- /dev/null +++ b/test/runtime/value/cast/import.ts @@ -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) + }) +}) diff --git a/test/runtime/value/cast/index.ts b/test/runtime/value/cast/index.ts index 911bd9f..0016139 100644 --- a/test/runtime/value/cast/index.ts +++ b/test/runtime/value/cast/index.ts @@ -6,6 +6,7 @@ import './boolean' import './composite' import './date' import './enum' +import './import' import './integer' import './intersect' import './iterator' diff --git a/test/runtime/value/cast/union.ts b/test/runtime/value/cast/union.ts index d9ae730..96b159c 100644 --- a/test/runtime/value/cast/union.ts +++ b/test/runtime/value/cast/union.ts @@ -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 }) diff --git a/test/runtime/value/check/index.ts b/test/runtime/value/check/index.ts index 9012725..27bda1e 100644 --- a/test/runtime/value/check/index.ts +++ b/test/runtime/value/check/index.ts @@ -15,6 +15,7 @@ import './iterator' import './keyof' import './kind' import './literal' +import './module' import './never' import './not' import './null' diff --git a/test/runtime/value/check/module.ts b/test/runtime/value/check/module.ts new file mode 100644 index 0000000..3a64267 --- /dev/null +++ b/test/runtime/value/check/module.ts @@ -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')) + }) +}) diff --git a/test/runtime/value/check/ref.ts b/test/runtime/value/check/ref.ts index cc54114..20156eb 100644 --- a/test/runtime/value/check/ref.ts +++ b/test/runtime/value/check/ref.ts @@ -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) }) diff --git a/test/runtime/value/clean/import.ts b/test/runtime/value/clean/import.ts new file mode 100644 index 0000000..1ccc7c8 --- /dev/null +++ b/test/runtime/value/clean/import.ts @@ -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 }) + }) +}) diff --git a/test/runtime/value/clean/index.ts b/test/runtime/value/clean/index.ts index b290dd2..99af15b 100644 --- a/test/runtime/value/clean/index.ts +++ b/test/runtime/value/clean/index.ts @@ -8,6 +8,7 @@ import './constructor' import './date' import './enum' import './function' +import './import' import './integer' import './intersect' import './iterator' diff --git a/test/runtime/value/convert/import.ts b/test/runtime/value/convert/import.ts new file mode 100644 index 0000000..f22c506 --- /dev/null +++ b/test/runtime/value/convert/import.ts @@ -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, { }) + }) +}) diff --git a/test/runtime/value/convert/index.ts b/test/runtime/value/convert/index.ts index ec3e1b6..fe5f47f 100644 --- a/test/runtime/value/convert/index.ts +++ b/test/runtime/value/convert/index.ts @@ -9,6 +9,7 @@ import './kind' import './date' import './enum' import './function' +import './import' import './integer' import './intersect' import './iterator' diff --git a/test/runtime/value/create/import.ts b/test/runtime/value/create/import.ts new file mode 100644 index 0000000..b07f96d --- /dev/null +++ b/test/runtime/value/create/import.ts @@ -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 }) + }) +}) diff --git a/test/runtime/value/create/index.ts b/test/runtime/value/create/index.ts index 222f793..9962a44 100644 --- a/test/runtime/value/create/index.ts +++ b/test/runtime/value/create/index.ts @@ -9,6 +9,7 @@ import './constructor' import './date' import './enum' import './function' +import './import' import './integer' import './intersect' import './iterator' diff --git a/test/runtime/value/create/ref.ts b/test/runtime/value/create/ref.ts index ce3d9d1..07142c1 100644 --- a/test/runtime/value/create/ref.ts +++ b/test/runtime/value/create/ref.ts @@ -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 }) }) }) diff --git a/test/runtime/value/default/import.ts b/test/runtime/value/default/import.ts new file mode 100644 index 0000000..42ae356 --- /dev/null +++ b/test/runtime/value/default/import.ts @@ -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 }) + }) +}) diff --git a/test/runtime/value/default/index.ts b/test/runtime/value/default/index.ts index e553311..fd70399 100644 --- a/test/runtime/value/default/index.ts +++ b/test/runtime/value/default/index.ts @@ -9,6 +9,7 @@ import './constructor' import './date' import './enum' import './function' +import './import' import './integer' import './intersect' import './iterator' diff --git a/test/runtime/value/transform/import.ts b/test/runtime/value/transform/import.ts new file mode 100644 index 0000000..a8646dc --- /dev/null +++ b/test/runtime/value/transform/import.ts @@ -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, {})) + }) +}) diff --git a/test/runtime/value/transform/index.ts b/test/runtime/value/transform/index.ts index bc4fb93..ff005d5 100644 --- a/test/runtime/value/transform/index.ts +++ b/test/runtime/value/transform/index.ts @@ -8,6 +8,7 @@ import './constructor' import './date' import './enum' import './function' +import './import' import './integer' import './intersect' import './iterator' diff --git a/test/runtime/value/transform/ref.ts b/test/runtime/value/transform/ref.ts index 92d252c..39113f4 100644 --- a/test/runtime/value/transform/ref.ts +++ b/test/runtime/value/transform/ref.ts @@ -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(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(Type.Ref('N2'))) .Decode((value) => value + 1) .Encode((value) => value - 1) it('Should decode mapped remote', () => { diff --git a/test/static/deref.ts b/test/static/deref.ts deleted file mode 100644 index 4df0737..0000000 --- a/test/static/deref.ts +++ /dev/null @@ -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> = Type.Ref(Vector) - -// prettier-ignore -const Vertex: TObject<{ - position: TRef>; - texcoord: TRef>; -}> = 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; - }; -}> diff --git a/test/static/ref.ts b/test/static/ref.ts index 47ccd9b..76aca67 100644 --- a/test/static/ref.ts +++ b/test/static/ref.ts @@ -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 type R = Static Expect(T).ToStatic() - Expect(R).ToStatic() + Expect(R).ToStatic() }