Module Prototype (#1016)

* Add Module Prototype

* Discard Evaluate and Mutual Prototypes

* Update Prototype Readme
This commit is contained in:
Haydn Paterson
2024-10-03 11:47:38 +09:00
committed by GitHub
parent e5d5ec0515
commit 0306823c82
5 changed files with 201 additions and 219 deletions
-134
View File
@@ -1,134 +0,0 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import {
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<L extends TSchema, R extends TSchema> = (
Static<L> extends Static<R> ?
Static<R> extends Static<L> ? R :
Static<L> extends Static<R> ? L :
TNever :
Static<R> extends Static<L> ? R :
TNever
)
// prettier-ignore
export type TEvaluateIntersectRest<T extends TSchema[]> =
T extends [infer L, infer R, ...infer Rest] ? TEvaluateIntersectRest<[TEvaluateIntersectType<AssertType<L>, AssertType<R>>, ...AssertRest<Rest>]> :
T
// prettier-ignore
export type TEvaluateProperties<T extends TProperties> = Evaluate<{
[K in keyof T]: TEvaluate<T[K]>
}>
// prettier-ignore
export type TEvaluateArray<T extends TSchema[]> = T extends [infer L, ...infer R] ?
[TEvaluate<AssertType<L>>, ...TEvaluateArray<AssertRest<R>>] :
[]
// prettier-ignore
export type TEvaluate<T extends TSchema> =
T extends TIntersect<infer S> ? TIntersect<TEvaluateIntersectRest<S>> :
T extends TUnion<infer S> ? TUnion<TEvaluateArray<S>> :
T extends TConstructor<infer P, infer R> ? TConstructor<TEvaluateArray<P>, TEvaluate<R>> :
T extends TFunction<infer P, infer R> ? TFunction<TEvaluateArray<P>, TEvaluate<R>> :
T extends TObject<infer S> ? TObject<TEvaluateProperties<S>> :
T extends TTuple<infer S> ? TTuple<TEvaluateArray<S>> :
T extends TArray<infer S> ? TArray<TEvaluate<S>> :
T extends TPromise<infer S> ? TPromise<TEvaluate<S>> :
T extends TIterator<infer S> ? TIterator<TEvaluate<S>> :
T extends TAsyncIterator<infer S> ? TAsyncIterator<TEvaluate<S>> :
T
// --------------------------------------------------------------------------
// Evaluate
// --------------------------------------------------------------------------
// prettier-ignore
export function EvaluateIntersectType<X extends TSchema, Y extends TSchema>(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 extends TSchema[]>(T: [...T]): TEvaluateIntersectRest<T> {
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<T extends TProperties>(properties: T) {
return Object.getOwnPropertyNames(properties).reduce((acc, key) => {
return { ...acc, [key]: Evaluate(properties[key]) }
}, {} as TProperties)
}
export function EvaluateArray<T extends TSchema[] | undefined>(rest: T) {
if (rest === undefined) return [] // for tuple items
return rest.map((schema) => Evaluate(schema))
}
// prettier-ignore
export function Evaluate<T extends TSchema>(schema: T): TEvaluate<T> {
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<T>
}
+1 -1
View File
@@ -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'
+166
View File
@@ -0,0 +1,166 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import * as Types from '@sinclair/typebox'
import { Check } from '@sinclair/typebox/value'
// ------------------------------------------------------------------
// Infer
// ------------------------------------------------------------------
// prettier-ignore
export type InferImport<Properties extends TModuleProperties, Key extends keyof Properties, Module extends TModuleProperties> = (
Infer<Properties[Key], Module>
)
// prettier-ignore
export type InferModuleRef<Ref extends string, Module extends TModuleProperties> = (
Ref extends keyof Module ? Infer<Module[Ref], Module> : never
)
// prettier-ignore
export type InferObject<Properties extends Types.TProperties, Module extends TModuleProperties> = {
[K in keyof Properties]: Infer<Properties[K], Module>
} & {}
// prettier-ignore
export type InferConstructor<Parameters extends Types.TSchema[], InstanceType extends Types.TSchema, Module extends TModuleProperties> = Types.Ensure<
new (...args: InferTuple<Parameters, Module>) => Infer<InstanceType, Module>
>
// prettier-ignore
export type InferFunction<Parameters extends Types.TSchema[], ReturnType extends Types.TSchema, Module extends TModuleProperties> = Types.Ensure<
(...args: InferTuple<Parameters, Module>) => Infer<ReturnType, Module>
>
// prettier-ignore
export type InferTuple<Types extends Types.TSchema[], Module extends TModuleProperties, Result extends unknown[] = []> = (
Types extends [infer L extends Types.TSchema, ...infer R extends Types.TSchema[]]
? InferTuple<R, Module, [...Result, Infer<L, Module>]>
: Result
)
// prettier-ignore
export type InferIntersect<Types extends Types.TSchema[], Module extends TModuleProperties, Result extends unknown = unknown> = (
Types extends [infer L extends Types.TSchema, ...infer R extends Types.TSchema[]]
? InferIntersect<R, Module, Result & Infer<L, Module>>
: Result
)
// prettier-ignore
export type InferUnion<Types extends Types.TSchema[], Module extends TModuleProperties, Result extends unknown = never> = (
Types extends [infer L extends Types.TSchema, ...infer R extends Types.TSchema[]]
? InferUnion<R, Module, Result | Infer<L, Module>>
: Result
)
// prettier-ignore
export type InferArray<Type extends Types.TSchema, Module extends TModuleProperties> = (
Types.Ensure<Array<Infer<Type, Module>>>
)
// prettier-ignore
export type InferAsyncIterator<Type extends Types.TSchema, Module extends TModuleProperties> = (
Types.Ensure<AsyncIterableIterator<Infer<Type, Module>>>
)
// prettier-ignore
export type InferIterator<Type extends Types.TSchema, Module extends TModuleProperties> = (
Types.Ensure<IterableIterator<Infer<Type, Module>>>
)
// prettier-ignore
type Infer<Type extends Types.TSchema, Module extends TModuleProperties = {}> = (
Type extends TImport<infer S extends TModuleProperties, infer K extends PropertyKey> ? InferImport<S, K, S> :
Type extends TModuleRef<infer S extends string> ? InferModuleRef<S, Module> :
Type extends Types.TObject<infer S extends Types.TProperties> ? InferObject<S, Module> :
Type extends Types.TConstructor<infer S extends Types.TSchema[], infer R extends Types.TSchema> ? InferConstructor<S, R, Module> :
Type extends Types.TFunction<infer S extends Types.TSchema[], infer R extends Types.TSchema> ? InferFunction<S, R, Module> :
Type extends Types.TTuple<infer S extends Types.TSchema[]> ? InferTuple<S, Module> :
Type extends Types.TIntersect<infer S extends Types.TSchema[]> ? InferIntersect<S, Module> :
Type extends Types.TUnion<infer S extends Types.TSchema[]> ? InferUnion<S, Module> :
Type extends Types.TArray<infer S extends Types.TSchema> ? InferArray<S, Module> :
Type extends Types.TAsyncIterator<infer S extends Types.TSchema> ? InferAsyncIterator<S, Module> :
Type extends Types.TIterator<infer S extends Types.TSchema> ? InferIterator<S, Module> :
Type extends Types.TTemplateLiteral<infer S extends Types.TTemplateLiteralKind[]> ? Types.Static<Types.TTemplateLiteral<S>> :
Type extends Types.TLiteral<infer S extends Types.TLiteralValue> ? S :
Type extends Types.TAny ? any :
Type extends Types.TBigInt ? bigint :
Type extends Types.TBoolean ? boolean :
Type extends Types.TDate ? Date :
Type extends Types.TInteger ? number :
Type extends Types.TNever ? never :
Type extends Types.TNumber ? number :
Type extends Types.TRegExp ? string :
Type extends Types.TString ? string :
Type extends Types.TSymbol ? symbol :
Type extends Types.TNull ? null :
Type extends Types.TUint8Array ? Uint8Array :
Type extends Types.TUndefined ? undefined :
Type extends Types.TUnknown ? unknown :
Type extends Types.TVoid ? void :
never
)
// ------------------------------------------------------------------
// ModuleRef
// ------------------------------------------------------------------
// prettier-ignore
export interface TModuleRef<Ref extends string> extends Types.TSchema {
[Types.Kind]: 'ModuleRef'
$ref: Ref
}
// prettier-ignore
export function ModuleRef<Ref extends string>($ref: Ref): TModuleRef<Ref> {
return Types.Type.Ref($ref) as never
}
// ------------------------------------------------------------------
// Import
// ------------------------------------------------------------------
// prettier-ignore
Types.TypeRegistry.Set('Import', (module: TImport, value: unknown) => {
const keys = Object.getOwnPropertyNames(module.$defs)
const references = keys.map(key => module.$defs[key as never]) as Types.TSchema[]
const schema = module.$defs[module.$ref]
return Check(schema, references, value)
})
// prettier-ignore
export type TModuleProperties = Record<PropertyKey, Types.TSchema>
// prettier-ignore
export interface TImport<Definitions extends TModuleProperties = {}, Key extends keyof Definitions = keyof Definitions> extends Types.TSchema {
[Types.Kind]: 'Import'
static: InferImport<Definitions, Key, Definitions>
$defs: Definitions
$ref: Key
}
// ------------------------------------------------------------------
// Module
// ------------------------------------------------------------------
// prettier-ignore
export class ModuleInstance<Properties extends TModuleProperties> {
constructor(private readonly properties: Properties) {}
public Import<Key extends keyof Properties>(key: Key): TImport<Properties, Key> {
const $defs = globalThis.Object.getOwnPropertyNames(this.properties).reduce((Result, Key) => (
{ ...Result, [Key]: { ...this.properties[Key], $id: Key }}
), {})
return { [Types.Kind]: 'Import', $defs, $ref: key } as never
}
}
export function Module<Properties extends TModuleProperties>(properties: Properties): ModuleInstance<Properties> {
return new ModuleInstance(properties)
}
-48
View File
@@ -1,48 +0,0 @@
/*--------------------------------------------------------------------------
@sinclair/typebox/prototypes
The MIT License (MIT)
Copyright (c) 2017-2024 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
import { Type, TSchema, Static } from '@sinclair/typebox'
// Mutual Recursive Template
const __A = <T extends TSchema>(reference: T) => Type.Object({
b: Type.Union([reference, Type.Null()])
}, { additionalProperties: false })
const __B = <T extends TSchema>(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<typeof A>;
type B = Static<typeof B>;
+34 -36
View File
@@ -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<T>` 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<typeof T> // 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