From 0306823c823930143245e1c62b32a83c0b2b6529 Mon Sep 17 00:00:00 2001 From: Haydn Paterson Date: Thu, 3 Oct 2024 11:47:38 +0900 Subject: [PATCH] Module Prototype (#1016) * Add Module Prototype * Discard Evaluate and Mutual Prototypes * Update Prototype Readme --- example/prototypes/evaluate.ts | 134 -------------------------- example/prototypes/index.ts | 2 +- example/prototypes/module.ts | 166 +++++++++++++++++++++++++++++++++ example/prototypes/mutual.ts | 48 ---------- example/prototypes/readme.md | 70 +++++++------- 5 files changed, 201 insertions(+), 219 deletions(-) delete mode 100644 example/prototypes/evaluate.ts create mode 100644 example/prototypes/module.ts delete mode 100644 example/prototypes/mutual.ts diff --git a/example/prototypes/evaluate.ts b/example/prototypes/evaluate.ts deleted file mode 100644 index 460d867..0000000 --- a/example/prototypes/evaluate.ts +++ /dev/null @@ -1,134 +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 { - AssertRest, - AssertType, - Static, - Type, - TypeGuard, - TSchema, - TObject, - Evaluate, - TArray, - TFunction, - TConstructor, - TPromise, - TIterator, - TAsyncIterator, - TTuple, - TProperties, - TIntersect, - TUnion, - TNever -} from '@sinclair/typebox' - -// -------------------------------------------------------------------------- -// TEvaluate -// -------------------------------------------------------------------------- -// prettier-ignore -export type TEvaluateIntersectType = ( - Static extends Static ? - Static extends Static ? R : - Static extends Static ? L : - TNever : - Static extends Static ? R : - TNever -) -// prettier-ignore -export type TEvaluateIntersectRest = - T extends [infer L, infer R, ...infer Rest] ? TEvaluateIntersectRest<[TEvaluateIntersectType, AssertType>, ...AssertRest]> : - T -// prettier-ignore -export type TEvaluateProperties = Evaluate<{ - [K in keyof T]: TEvaluate -}> -// prettier-ignore -export type TEvaluateArray = T extends [infer L, ...infer R] ? - [TEvaluate>, ...TEvaluateArray>] : - [] -// prettier-ignore -export type TEvaluate = - T extends TIntersect ? TIntersect> : - T extends TUnion ? TUnion> : - T extends TConstructor ? TConstructor, TEvaluate> : - T extends TFunction ? TFunction, TEvaluate> : - T extends TObject ? TObject> : - T extends TTuple ? TTuple> : - T extends TArray ? TArray> : - T extends TPromise ? TPromise> : - T extends TIterator ? TIterator> : - T extends TAsyncIterator ? TAsyncIterator> : - T -// -------------------------------------------------------------------------- -// Evaluate -// -------------------------------------------------------------------------- -// prettier-ignore -export function EvaluateIntersectType(L: X, R: Y) { - return Type.Extends(L, R, - Type.Extends(R, L, R, - Type.Extends(L, R, L, - Type.Never())), - Type.Extends(R, L, R, - Type.Never())) -} -// prettier-ignore -export function EvaluateIntersectRest(T: [...T]): TEvaluateIntersectRest { - if(T.length >= 2) { - const [L, R, ...Rest] = T - return EvaluateIntersectRest([EvaluateIntersectType(L, R), ...Rest]) as any - } else { - return T as any - } -} -export function EvaluateProperties(properties: T) { - return Object.getOwnPropertyNames(properties).reduce((acc, key) => { - return { ...acc, [key]: Evaluate(properties[key]) } - }, {} as TProperties) -} -export function EvaluateArray(rest: T) { - if (rest === undefined) return [] // for tuple items - return rest.map((schema) => Evaluate(schema)) -} -// prettier-ignore -export function Evaluate(schema: T): TEvaluate { - return ( - TypeGuard.IsIntersect(schema) ? Type.Intersect(EvaluateIntersectRest(schema.allOf)) : - TypeGuard.IsUnion(schema) ? Type.Union(EvaluateArray(schema.anyOf)) : - TypeGuard.IsAsyncIterator(schema) ? Type.AsyncIterator(Evaluate(schema.items)) : - TypeGuard.IsIterator(schema) ? Type.Iterator(Evaluate(schema.items)) : - TypeGuard.IsObject(schema) ? Type.Object(EvaluateProperties(schema.properties)) : - TypeGuard.IsConstructor(schema) ? Type.Constructor(EvaluateArray(schema.parameters), Evaluate(schema.returns)) : - TypeGuard.IsFunction(schema) ? Type.Function(EvaluateArray(schema.parameters), Evaluate(schema.returns)) : - TypeGuard.IsTuple(schema) ? Type.Tuple(EvaluateArray(schema.items)) : - TypeGuard.IsArray(schema) ? Type.Promise(Evaluate(schema.items)) : - TypeGuard.IsPromise(schema) ? Type.Promise(Evaluate(schema.item)) : - schema - ) as TEvaluate -} - diff --git a/example/prototypes/index.ts b/example/prototypes/index.ts index c0039fc..cb81ad0 100644 --- a/example/prototypes/index.ts +++ b/example/prototypes/index.ts @@ -26,8 +26,8 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -export * from './evaluate' 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 new file mode 100644 index 0000000..e56dcb2 --- /dev/null +++ b/example/prototypes/module.ts @@ -0,0 +1,166 @@ +/*-------------------------------------------------------------------------- + +@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/mutual.ts b/example/prototypes/mutual.ts deleted file mode 100644 index c47b7c7..0000000 --- a/example/prototypes/mutual.ts +++ /dev/null @@ -1,48 +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 { Type, TSchema, Static } from '@sinclair/typebox' - -// Mutual Recursive Template -const __A = (reference: T) => Type.Object({ - b: Type.Union([reference, Type.Null()]) -}, { additionalProperties: false }) - -const __B = (reference: T) => Type.Object({ - a: Type.Union([reference, Type.Null()]) -}, { additionalProperties: false }) - -// .... - -// Mutual Recursive Types -const A = Type.Recursive((This) => __A(__B(This))) -const B = Type.Recursive((This) => __B(__A(This))) - -type A = Static; -type B = Static; - diff --git a/example/prototypes/readme.md b/example/prototypes/readme.md index 77a962c..7c7c429 100644 --- a/example/prototypes/readme.md +++ b/example/prototypes/readme.md @@ -1,48 +1,46 @@ # TypeBox Prototypes -TypeBox prototypes are a set of types that are either under consideration for inclusion into the library, or have been requested by users but cannot be added to the library either due to complexity, using schematics that fall outside the supported TypeBox or should be expressed by users via advanced type composition. +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. -Each prototype is written as a standalone module that can be copied into projects and used directly, or integrated into extended type builders. +## Module, ModuleRef and Import -## Const - -This type will wrap all interior properties as `readonly` leaving the outer type unwrapped. This type is analogous to the `Readonly` TypeScript utility type, but as TypeBox uses this name as a property modifier, the name `Const` is used. +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 { Const } from './prototypes' +import { Module, ModuleRef } from './prototypes' -const T = Const(Type.Object({ // const T: TObject<{ - x: Type.Number() // x: Type.Readonly(Type.Number()) -})) // }> +// ------------------------------------------------------------------ +// 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()) + }) +}) -type T = Static // type T = { - // readonly x: number - // } -``` -## Evaluate +// ------------------------------------------------------------------ +// Import +// ----------------------------------------------------------------- -This type is an advanced mapping type will evaluate for schema redundancy by reducing evaluating intersection rest arguments. This type detects if intersection would produce illogical `never`, removes duplicates and handles intersection type narrowing. This type is a strong candidate for inclusion into the TypeBox library but is pending an equivalent redundancy check for `union` rest arguments. - -```typescript -import { Evaluate } from './prototypes' - -// Evaluate for Duplicates -// -const T = Type.Intersect([ Type.Number(), Type.Number(), Type.Number() ]) - -const E = Evaluate(T) // const E: TNumber - -// Evaluate for TNever -// -const T = Type.Intersect([ Type.Number(), Type.String() ]) - -const E = Evaluate(T) // const E: TIntersect<[TNumber, TString]> - -// Evaluate for most narrowed type -// -const T = Type.Intersect([ Type.Number(), Type.Literal(1) ]) - -const E = Evaluate(T) // const E: TLiteral<1> +const Vector2 = Math.Import('Vector2') +const Vector3 = Math.Import('Vector2') +const Vertex = Math.Import('Vertex') +const Geometry = Math.Import('Geometry') ``` ## PartialDeep