From e67952f626251086281849e0108c17eae5bf8b51 Mon Sep 17 00:00:00 2001 From: sinclair Date: Sun, 26 Nov 2023 20:55:32 +0900 Subject: [PATCH] Reimplement Indexed Accessors --- examples/index.ts | 2 +- examples/prototypes/const.ts | 66 ++++++------- src/type/resolve/discard/discard.ts | 38 +++++++ src/type/resolve/discard/index.ts | 29 ++++++ src/type/resolve/index.ts | 2 + src/type/resolve/modifiers/modifiers.ts | 3 +- src/type/resolve/partial/partial.ts | 8 +- src/type/resolve/required/index.ts | 29 ++++++ src/type/resolve/required/required.ts | 126 ++++++++++++++++++++++++ src/typebox.ts | 86 ++-------------- 10 files changed, 275 insertions(+), 114 deletions(-) create mode 100644 src/type/resolve/discard/discard.ts create mode 100644 src/type/resolve/discard/index.ts create mode 100644 src/type/resolve/required/index.ts create mode 100644 src/type/resolve/required/required.ts diff --git a/examples/index.ts b/examples/index.ts index 00d65313..e49ae010 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -1,5 +1,5 @@ import { Value } from '@sinclair/typebox/value' -import { TSchema, Type, Static, TypeGuard, Discard, Evaluate, Ensure, TObject, Kind } from '@sinclair/typebox' +import { TSchema, Type, Static, TypeGuard, Evaluate, Ensure, TObject, Kind } from '@sinclair/typebox' import { PartialResolver, IndexedKeyResolver } from '@sinclair/typebox/type' const A = Type.Object({ x: Type.Number() }) diff --git a/examples/prototypes/const.ts b/examples/prototypes/const.ts index ef628987..fa6765a1 100644 --- a/examples/prototypes/const.ts +++ b/examples/prototypes/const.ts @@ -26,36 +26,36 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { Type, ObjectMap, TypeGuard, TSchema, TIntersect, TUnion, TObject, TProperties, TReadonly, AssertProperties, AssertType, AssertRest, Evaluate, ModifiersRemoveReadonly } from '@sinclair/typebox' - -// ------------------------------------------------------------------------------------- -// TConst -// ------------------------------------------------------------------------------------- -// prettier-ignore -export type TConstArray = T extends [infer L, ...infer R] - ? [TConst, E>, ...TConstArray, E>] - : [] -// prettier-ignore -export type TConstProperties = Evaluate, E> -}>> -// prettier-ignore -export type TConst = - T extends TIntersect ? TIntersect> : - T extends TUnion ? TUnion> : - T extends TObject ? TObject> : - E extends true ? T : TReadonly -// ------------------------------------------------------------------------------------- -// Const -// ------------------------------------------------------------------------------------- -/** `[Experimental]` Assigns readonly to all interior properties */ -export function Const(schema: T): TConst { - const mappable = (TypeGuard.TIntersect(schema) || TypeGuard.TUnion(schema) || TypeGuard.TObject(schema)) - // prettier-ignore - return mappable ? ObjectMap.Map(schema, (object) => { - const properties = Object.getOwnPropertyNames(object.properties).reduce((acc, key) => { - return { ...acc, [key]: Type.Readonly(object.properties[key] )} - }, {} as TProperties) - return Type.Object(properties, {...object}) - }, {}) : schema as any -} \ No newline at end of file +// import { Type, ObjectMap, TypeGuard, TSchema, TIntersect, TUnion, TObject, TProperties, TReadonly, AssertProperties, AssertType, AssertRest, Evaluate, ModifiersRemoveReadonly } from '@sinclair/typebox' + +// // ------------------------------------------------------------------------------------- +// // TConst +// // ------------------------------------------------------------------------------------- +// // prettier-ignore +// export type TConstArray = T extends [infer L, ...infer R] +// ? [TConst, E>, ...TConstArray, E>] +// : [] +// // prettier-ignore +// export type TConstProperties = Evaluate, E> +// }>> +// // prettier-ignore +// export type TConst = +// T extends TIntersect ? TIntersect> : +// T extends TUnion ? TUnion> : +// T extends TObject ? TObject> : +// E extends true ? T : TReadonly +// // ------------------------------------------------------------------------------------- +// // Const +// // ------------------------------------------------------------------------------------- +// /** `[Experimental]` Assigns readonly to all interior properties */ +// export function Const(schema: T): TConst { +// const mappable = (TypeGuard.TIntersect(schema) || TypeGuard.TUnion(schema) || TypeGuard.TObject(schema)) +// // prettier-ignore +// return mappable ? ObjectMap.Map(schema, (object) => { +// const properties = Object.getOwnPropertyNames(object.properties).reduce((acc, key) => { +// return { ...acc, [key]: Type.Readonly(object.properties[key] )} +// }, {} as TProperties) +// return Type.Object(properties, {...object}) +// }, {}) : schema as any +// } \ No newline at end of file diff --git a/src/type/resolve/discard/discard.ts b/src/type/resolve/discard/discard.ts new file mode 100644 index 00000000..13788b5f --- /dev/null +++ b/src/type/resolve/discard/discard.ts @@ -0,0 +1,38 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 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. + +---------------------------------------------------------------------------*/ + +/** Discards properties from record types */ +export namespace Discard { + function Key(value: Record, key: PropertyKey) { + const { [key]: _, ...rest } = value + return rest + } + export function Keys(value: Record, keys: PropertyKey[]) { + return keys.reduce((acc, key) => Key(acc, key), value) + } +} diff --git a/src/type/resolve/discard/index.ts b/src/type/resolve/discard/index.ts new file mode 100644 index 00000000..73ff2051 --- /dev/null +++ b/src/type/resolve/discard/index.ts @@ -0,0 +1,29 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 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 './discard' diff --git a/src/type/resolve/index.ts b/src/type/resolve/index.ts index 944b9908..ec7661d2 100644 --- a/src/type/resolve/index.ts +++ b/src/type/resolve/index.ts @@ -28,6 +28,7 @@ THE SOFTWARE. export * from './awaited/index' export * from './composite/index' +export * from './discard/index' export * from './distinct/index' export * from './extends/index' export * from './increment/index' @@ -41,5 +42,6 @@ export * from './omit/index' export * from './partial/index' export * from './pick/index' export * from './record/index' +export * from './required/index' export * from './template-literal/index' export * from './union/index' diff --git a/src/type/resolve/modifiers/modifiers.ts b/src/type/resolve/modifiers/modifiers.ts index 592a4f15..1bf074b6 100644 --- a/src/type/resolve/modifiers/modifiers.ts +++ b/src/type/resolve/modifiers/modifiers.ts @@ -26,7 +26,8 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { Type, Discard, TypeGuard, TSchema, TIntersect, TUnion, TOptional, TReadonly, Readonly, Optional } from '../../../typebox' +import { Type, TypeGuard, TSchema, TIntersect, TUnion, TOptional, TReadonly, Readonly, Optional } from '../../../typebox' +import { Discard } from '../discard/index' // ---------------------------------------------------------------- // Modifiers diff --git a/src/type/resolve/partial/partial.ts b/src/type/resolve/partial/partial.ts index 59e3a379..c2c2f139 100644 --- a/src/type/resolve/partial/partial.ts +++ b/src/type/resolve/partial/partial.ts @@ -26,7 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { Type, TSchema, TypeGuard, TObject, TProperties, TIntersect, TUnion, TOptional, TRecursive } from '../../../typebox' +import { Type, TSchema, TypeGuard, TObject, TProperties, TIntersect, TUnion, TOptional, TRecursive, TReadonlyOptional, TReadonly } from '../../../typebox' // prettier-ignore export namespace PartialResolver { @@ -67,7 +67,11 @@ export namespace PartialResolver { // Properties // ---------------------------------------------------------------- export type Properties = Evaluate<{ - [K in keyof T]: TOptional + [K in keyof T]: + T[K] extends (TReadonlyOptional) ? TReadonlyOptional : + T[K] extends (TReadonly) ? TReadonlyOptional : + T[K] extends (TOptional) ? TOptional : + TOptional }> export function Properties(T: T) { return globalThis.Object.getOwnPropertyNames(T).reduce((Acc, K) => { diff --git a/src/type/resolve/required/index.ts b/src/type/resolve/required/index.ts new file mode 100644 index 00000000..7958a5f8 --- /dev/null +++ b/src/type/resolve/required/index.ts @@ -0,0 +1,29 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 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 './required' diff --git a/src/type/resolve/required/required.ts b/src/type/resolve/required/required.ts new file mode 100644 index 00000000..16d108fe --- /dev/null +++ b/src/type/resolve/required/required.ts @@ -0,0 +1,126 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 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, TypeGuard, TObject, TProperties, TIntersect, TUnion, TOptional, TRecursive, TReadonlyOptional, TReadonly, Optional } from '../../../typebox' +import { Discard } from '../discard' +// return ObjectMap.Map(this.Discard(TypeClone.Type(schema), ['$id', Transform]), (object) => { +// const properties = Object.getOwnPropertyNames(object.properties).reduce((acc, key) => { +// return { ...acc, [key]: this.Discard(object.properties[key], [Optional]) as TSchema } +// }, {} as TProperties) +// return this.Object(properties, object /* object used as options to retain other constraints */) +// }, options) + +// // ------------------------------------------------------------------ +// // TRequired +// // ------------------------------------------------------------------ +// export type TRequiredRest = AssertRest<{ [K in keyof T]: TRequired> }> +// // prettier-ignore +// export type TRequiredProperties = Evaluate) ? TReadonly : +// T[K] extends (TReadonly) ? TReadonly : +// T[K] extends (TOptional) ? S : +// T[K] +// }>> +// // prettier-ignore +// export type TRequired = +// T extends TRecursive ? TRecursive> : +// T extends TIntersect ? TIntersect> : +// T extends TUnion ? TUnion> : +// T extends TObject ? TObject> : +// T + +// prettier-ignore +export namespace RequiredResolver { + export type Evaluate = T extends infer O ? { [K in keyof O]: O[K] } : never + // ---------------------------------------------------------------- + // Intersect + // ---------------------------------------------------------------- + export type Intersect = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? [Resolve, ...Intersect] + : [] + ) + export function Intersect(T: [...T]) : Intersect { + const [L, ...R] = T + return ( + T.length > 0 + ? [Resolve(L), ...Intersect(R)] + : [] + ) as Intersect + } + // ---------------------------------------------------------------- + // Union + // ---------------------------------------------------------------- + export type Union = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? [Resolve, ...Union] + : [] + ) + export function Union(T: [...T]): Union { + const [L, ...R] = T + return ( + T.length > 0 + ? [Resolve(L), ...Union(R)] + : [] + ) as Union + } + // ---------------------------------------------------------------- + // Properties + // ---------------------------------------------------------------- + export type Properties = Evaluate<{ + [K in keyof T]: + T[K] extends (TReadonlyOptional) ? TReadonly : + T[K] extends (TReadonly) ? TReadonly : + T[K] extends (TOptional) ? S : + T[K] + }> + export function Properties(T: T) { + return globalThis.Object.getOwnPropertyNames(T).reduce((Acc, K) => { + return { ...Acc, [K]: Discard.Keys(T[K], [Optional]) as TSchema } + }, {} as TProperties) + } + // ---------------------------------------------------------------- + // Resolve + // ---------------------------------------------------------------- + export type Resolve = ( + T extends TRecursive ? TRecursive> : + T extends TIntersect ? TIntersect> : + T extends TUnion ? TUnion> : + T extends TObject ? TObject> : + TObject<{}> + ) + export function Resolve(T: T): Resolve { + return ( + TypeGuard.TIntersect(T) ? Type.Intersect(Intersect(T.allOf)) : + TypeGuard.TUnion(T) ? Type.Union(Union(T.anyOf)) : + TypeGuard.TObject(T) ? Type.Object(Properties(T.properties)) : + Type.Object({}) + ) as Resolve + } +} diff --git a/src/typebox.ts b/src/typebox.ts index e0ca8a77..922da9e7 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -39,8 +39,10 @@ export { TypeClone } from './type/clone/index' import { TypeGuard, ValueGuard } from './type/guard/index' import { TypeClone } from './type/clone/index' import { + AwaitedResolver, ExtendsResolver, ExtendsResult, + Discard, IndexedTypeResolver, IndexedKeyResolver, KeyOfTypeResolver, @@ -48,15 +50,15 @@ import { TemplateLiteralPattern, TemplateLiteralToUnion, TemplateLiteralStatic, - UnionResolver, RecordResolver, - AwaitedResolver, + RequiredResolver, IntrinsicResolver, IntersectResolver, CompositeResolver, OmitResolver, PartialResolver, PickResolver, + UnionResolver, } from './type/resolve/index' // ------------------------------------------------------------------ // Symbols @@ -520,22 +522,7 @@ export type TReturnType = T['returns'] // ------------------------------------------------------------------ // TRequired // ------------------------------------------------------------------ -export type TRequiredRest = AssertRest<{ [K in keyof T]: TRequired> }> -// prettier-ignore -export type TRequiredProperties = Evaluate) ? TReadonly : - T[K] extends (TReadonly) ? TReadonly : - T[K] extends (TOptional) ? S : - T[K] -}>> -// prettier-ignore -export type TRequired = - T extends TRecursive ? TRecursive> : - T extends TIntersect ? TIntersect> : - T extends TUnion ? TUnion> : - T extends TObject ? TObject> : - T +export type TRequired = RequiredResolver.Resolve // ------------------------------------------------------------------ // TString // ------------------------------------------------------------------ @@ -736,51 +723,6 @@ export type StaticEncode = Static = (T & { params: P })['static'] -// ------------------------------------------------------------------ -// Discard -// ------------------------------------------------------------------ -/** Discards properties from record types */ -export namespace Discard { - function Key(value: Record, key: PropertyKey) { - const { [key]: _, ...rest } = value - return rest - } - export function Keys(value: Record, keys: PropertyKey[]) { - return keys.reduce((acc, key) => Key(acc, key), value) - } -} - -// ------------------------------------------------------------------ -// ObjectMap -// ------------------------------------------------------------------ -// prettier-ignore -export namespace ObjectMap { - function TIntersect(schema: TIntersect, callback: (object: TObject) => TObject) { - return Type.Intersect(schema.allOf.map((inner) => Visit(inner, callback)), { ...schema }) - } - function TUnion(schema: TUnion, callback: (object: TObject) => TObject) { - return Type.Union(schema.anyOf.map((inner) => Visit(inner, callback)), { ...schema }) - } - function TObject(schema: TObject, callback: (object: TObject) => TObject) { - return callback(schema) - } - function Visit(schema: TSchema, callback: (object: TObject) => TObject): TSchema { - // There are cases where users need to map objects with unregistered kinds. Using a TypeGuard here would - // prevent sub schema mapping as unregistered kinds will not pass TSchema checks. This is notable in the - // case of TObject where unregistered property kinds cause the TObject check to fail. As mapping is only - // used for composition, we use explicit checks instead. - return ( - schema[Kind] === 'Intersect' ? TIntersect(schema as TIntersect, callback) : - schema[Kind] === 'Union' ? TUnion(schema as TUnion, callback) : - schema[Kind] === 'Object' ? TObject(schema as TObject, callback) : - schema - ) as any - } - export function Map(schema: TSchema, callback: (object: TObject) => TObject, options: SchemaOptions): T { - return { ...Visit(TypeClone.Type(schema), callback), ...options } as unknown as T - } -} - // --------------------------------------------------------------------- // TransformBuilder // --------------------------------------------------------------------- @@ -821,13 +763,6 @@ export class TypeBuilder { protected Throw(message: string): never { throw new TypeBuilderError(message) } - /** `[Internal]` Discards property keys from the given record type */ - protected Discard(record: Record, keys: PropertyKey[]) { - return keys.reduce((acc, key) => { - const { [key as any]: _, ...rest } = acc - return rest - }, record) as any - } /** `[Json]` Omits compositing symbols from this schema */ public Strict(schema: T): T { return JSON.parse(JSON.stringify(schema)) @@ -1060,13 +995,10 @@ export class JsonTypeBuilder extends TypeBuilder { return { ...options, [Kind]: 'Ref', $ref: unresolved.$id! } } /** `[Json]` Constructs a type where all properties are required */ - public Required(schema: T, options: SchemaOptions = {}): TRequired { - return ObjectMap.Map(this.Discard(TypeClone.Type(schema), ['$id', Transform]), (object) => { - const properties = Object.getOwnPropertyNames(object.properties).reduce((acc, key) => { - return { ...acc, [key]: this.Discard(object.properties[key], [Optional]) as TSchema } - }, {} as TProperties) - return this.Object(properties, object /* object used as options to retain other constraints */) - }, options) + public Required(T: T, options: SchemaOptions = {}): TRequired { + const D = Discard.Keys(T, [ Transform, '$id', 'required' ]) as TSchema + const R = TypeClone.Type(RequiredResolver.Resolve(T), options) + return { ...D, ...R } } /** `[Json]` Extracts interior Rest elements from Tuple, Intersect and Union types */ public Rest(schema: T): TRest {