From 1f5b63245dc0f20e03c12e94437d2f92df5a49a6 Mon Sep 17 00:00:00 2001 From: sinclair Date: Sun, 26 Nov 2023 17:35:51 +0900 Subject: [PATCH] Reimplement Indexed Accessors --- examples/index.ts | 64 +++++++-------- src/type/guard/type.ts | 134 ++++++++++++++++++++----------- src/type/resolve/index.ts | 1 + src/type/resolve/omit/omit.ts | 130 ++++++++++++++++++++++++------ src/typebox.ts | 47 ++++------- test/runtime/type/guard/union.ts | 4 +- 6 files changed, 235 insertions(+), 145 deletions(-) diff --git a/examples/index.ts b/examples/index.ts index c3f0dbb26..106d17d1b 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -1,41 +1,31 @@ import { Value } from '@sinclair/typebox/value' -import { TSchema, Type, Evaluate } from '@sinclair/typebox' -import { KeyOfStringResolver, IndexedTypeResolver, Logic as PropertySet } from '@sinclair/typebox/type' +import { TSchema, Type, TypeGuard, Discard, Evaluate, Ensure, TObject, Kind } from '@sinclair/typebox' +import { KeyOfStringResolver, IndexedTypeResolver, OmitResolver } from '@sinclair/typebox/type' -export namespace OmitResolver { - export type Evaluate = T extends infer O ? { [K in keyof O]: O[K] } : never - // ---------------------------------------------------------------- - // Reduce - // ---------------------------------------------------------------- - export type Reduce = K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] ? { [_ in L]: IndexedTypeResolver.Resolve } & Reduce : {} - export function Reduce(T: T, K: [...K]): Reduce { - return K.reduce((acc, key) => { - return { ...acc, [key]: IndexedTypeResolver.Resolve(T, [key]) } - }, {}) as Reduce - } - // ---------------------------------------------------------------- - // Resolve - // ---------------------------------------------------------------- - export type Resolve, B extends PropertyKey[] = PropertySet.Complement> = Evaluate> +// const R = TypeGuard.TUnion( +// Type.Union([ +// Type.Object({ +// x: Type.Number(), +// }), +// {} as any, +// ]), +// ) +console.log( + TypeGuard.TSchema( + Type.Object({ + y: {} as any, + }), + ), + '?', +) - export function Resolve(T: T, K: [...K]): Resolve { - const A = KeyOfStringResolver.Resolve(T) as PropertyKey[] - const B = PropertySet.Complement(A, K) as PropertyKey[] - return Reduce(T, B) as Resolve - } -} +// // @ts-ignore +// Assert.IsEqual(T.properties.x, undefined) +// // @ts-ignore +// Assert.IsEqual(T.properties.y, undefined) +// // @ts-ignore +// Assert.IsEqual(T.required, undefined) -const A = Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number(), -}) -const B = Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number(), -}) -const T = Type.Intersect([Type.Intersect([A, B]), Type.Intersect([A, B]), Type.Intersect([A, B])]) - -const R = OmitResolver.Resolve(T, ['x']) -console.log(JSON.stringify(R, null, 2)) +// const K = Type.TemplateLiteral('A${0|1}${0|1}${0|1}${0|1}${0|1}${0|1}${0|1}${0|1}') +// const T = Type.Record(K, Type.Null()) +// const O = Type.Omit(T, ['asd']) diff --git a/src/type/guard/type.ts b/src/type/guard/type.ts index 96f5f47dc..2e02941b7 100644 --- a/src/type/guard/type.ts +++ b/src/type/guard/type.ts @@ -26,7 +26,6 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { TypeRegistry } from '../registry/index' import { ValueGuard } from './value' import { TransformOptions, @@ -77,7 +76,40 @@ import { // ------------------------------------------------------------------ export class TypeGuardUnknownTypeError extends Error {} /** Provides functions to test if JavaScript values are TypeBox types */ +// prettier-ignore export namespace TypeGuard { + const KnownKinds = [ + 'Any', + 'Array', + 'AsyncIterator', + 'BigInt', + 'Boolean', + 'Constructor', + 'Date', + 'Function', + 'Integer', + 'Intersect', + 'Iterator', + 'Literal', + 'Never', + 'Not', + 'Null', + 'Number', + 'Object', + 'Promise', + 'Record', + 'Ref', + 'String', + 'Symbol', + 'TemplateLiteral', + 'This', + 'Tuple', + 'Undefined', + 'Union', + 'Uint8Array', + 'Unknown', + 'Void' + ] function IsPattern(value: unknown): value is string { try { new RegExp(value as string) @@ -121,6 +153,17 @@ export namespace TypeGuard { return ValueGuard.IsUndefined(value) || TSchema(value) } // ---------------------------------------------------------------- + // Modifiers + // ---------------------------------------------------------------- + /** Returns true if this value has a Readonly symbol */ + export function TReadonly(schema: T): schema is TReadonly { + return ValueGuard.IsObject(schema) && schema[Readonly] === 'Readonly' + } + /** Returns true if this value has a Optional symbol */ + export function TOptional(schema: T): schema is TOptional { + return ValueGuard.IsObject(schema) && schema[Optional] === 'Optional' + } + // ---------------------------------------------------------------- // Types // ---------------------------------------------------------------- /** Returns true if the given value is TAny */ @@ -254,11 +297,7 @@ export namespace TypeGuard { } /** Returns true if the given value is a TKind with the given name. */ export function TKindOf(schema: unknown, kind: T): schema is Record & { [Kind]: T } { - return TKind(schema) && schema[Kind] === kind - } - /** Returns true if the given value is TKind */ - export function TKind(schema: unknown): schema is Record & { [Kind]: string } { - return ValueGuard.IsObject(schema) && Kind in schema && ValueGuard.IsString(schema[Kind]) + return ValueGuard.IsObject(schema) && Kind in schema && schema[Kind] === kind } /** Returns true if the given value is TLiteral */ export function TLiteralString(schema: unknown): schema is TLiteral { @@ -331,10 +370,10 @@ export namespace TypeGuard { schema.type === 'object' && IsOptionalString(schema.$id) && ValueGuard.IsObject(schema.properties) && + Object.entries(schema.properties).every(([key, schema]) => IsControlCharacterFree(key) && TSchema(schema)) && IsAdditionalProperties(schema.additionalProperties) && IsOptionalNumber(schema.minProperties) && - IsOptionalNumber(schema.maxProperties) && - Object.entries(schema.properties).every(([key, schema]) => IsControlCharacterFree(key) && TSchema(schema)) + IsOptionalNumber(schema.maxProperties) ) } /** Returns true if the given value is TPromise */ @@ -502,13 +541,14 @@ export namespace TypeGuard { IsOptionalString(schema.$id) ) } - /** Returns true if this value has a Readonly symbol */ - export function TReadonly(schema: T): schema is TReadonly { - return ValueGuard.IsObject(schema) && schema[Readonly] === 'Readonly' - } - /** Returns true if this value has a Optional symbol */ - export function TOptional(schema: T): schema is TOptional { - return ValueGuard.IsObject(schema) && schema[Optional] === 'Optional' + /** Returns true if the given value is TKind */ + export function TKind(schema: unknown): schema is Record & { [Kind]: string } { + return ( + ValueGuard.IsObject(schema) && + Kind in schema + && ValueGuard.IsString(schema[Kind]) && + !KnownKinds.includes(schema[Kind]) + ) } /** Returns true if the given value is TSchema */ export function TSchema(schema: unknown): schema is TSchema { @@ -516,38 +556,38 @@ export namespace TypeGuard { return ( ValueGuard.IsObject(schema) ) && ( - TAny(schema) || - TArray(schema) || - TBoolean(schema) || - TBigInt(schema) || - TAsyncIterator(schema) || - TConstructor(schema) || - TDate(schema) || - TFunction(schema) || - TInteger(schema) || - TIntersect(schema) || - TIterator(schema) || - TLiteral(schema) || - TNever(schema) || - TNot(schema) || - TNull(schema) || - TNumber(schema) || - TObject(schema) || - TPromise(schema) || - TRecord(schema) || - TRef(schema) || - TString(schema) || - TSymbol(schema) || - TTemplateLiteral(schema) || - TThis(schema) || - TTuple(schema) || - TUndefined(schema) || - TUnion(schema) || - TUint8Array(schema) || - TUnknown(schema) || - TUnsafe(schema) || - TVoid(schema) || - (TKind(schema) && TypeRegistry.Has(schema[Kind] as any)) + TAny(schema) || + TArray(schema) || + TBoolean(schema) || + TBigInt(schema) || + TAsyncIterator(schema) || + TConstructor(schema) || + TDate(schema) || + TFunction(schema) || + TInteger(schema) || + TIntersect(schema) || + TIterator(schema) || + TLiteral(schema) || + TNever(schema) || + TNot(schema) || + TNull(schema) || + TNumber(schema) || + TObject(schema) || + TPromise(schema) || + TRecord(schema) || + TRef(schema) || + TString(schema) || + TSymbol(schema) || + TTemplateLiteral(schema) || + TThis(schema) || + TTuple(schema) || + TUndefined(schema) || + TUnion(schema) || + TUint8Array(schema) || + TUnknown(schema) || + TUnsafe(schema) || + TVoid(schema) || + TKind(schema) ) } } diff --git a/src/type/resolve/index.ts b/src/type/resolve/index.ts index e1d5c89ef..6862222e6 100644 --- a/src/type/resolve/index.ts +++ b/src/type/resolve/index.ts @@ -37,6 +37,7 @@ export * from './intrinsic/index' export * from './keyof/index' export * from './logic/index' export * from './modifiers/index' +export * from './omit/index' export * from './record/index' export * from './template-literal/index' export * from './union/index' diff --git a/src/type/resolve/omit/omit.ts b/src/type/resolve/omit/omit.ts index 9072ea762..e28c39d6d 100644 --- a/src/type/resolve/omit/omit.ts +++ b/src/type/resolve/omit/omit.ts @@ -26,50 +26,128 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { TSchema, Type, TObject, TProperties } from '../../../typebox' +import { TSchema, Type, TObject, TProperties, TIntersect, TUnion, TRecursive, TypeGuard } from '../../../typebox' import { KeyOfStringResolver } from '../keyof/index' import { IndexedTypeResolver } from '../indexed/index' import { Logic } from '../logic/index' -// prettier-ignore +// const keys = TypeGuard.TSchema(unresolved) ? IndexedKeyResolver.Resolve(unresolved) as string[] : unresolved as string[] +// return ObjectMap.Map(this.Discard(TypeClone.Type(schema), ['$id', Transform]), (object) => { +// if (ValueGuard.IsArray(object.required)) { +// object.required = object.required.filter((key: string) => !keys.includes(key as any)) +// if (object.required.length === 0) delete object.required +// } +// for (const key of Object.getOwnPropertyNames(object.properties)) { +// if (keys.includes(key as any)) delete object.properties[key] +// } +// return object +// }, options) + export namespace OmitResolver { + // ---------------------------------------------------------------- + // Utility + // ---------------------------------------------------------------- export type Evaluate = T extends infer O ? { [K in keyof O]: O[K] } : never + export type Ensure = T extends infer U ? U : never + export type TupleToUnion = + T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] + ? L | TupleToUnion + : never + // ---------------------------------------------------------------- + // Intersect + // ---------------------------------------------------------------- + export type Intersect = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? [Resolve, ...Intersect] + : [] + ) + export function Intersect(T: T, K: K) { + return T.map(T => Resolve(T, K)) as Intersect + } // ---------------------------------------------------------------- - // ReduceProperties + // Union // ---------------------------------------------------------------- - export type ReduceProperties = - K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? { [_ in L]: IndexedTypeResolver.Resolve } & ReduceProperties - : {} - export function ReduceProperties(T: T, K: [...K]): ReduceProperties { - return K.reduce((acc, key) => { - return { ...acc, [key]: IndexedTypeResolver.Resolve(T, [key]) } - }, {}) as ReduceProperties + export type Union = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? [Resolve, ...Union] + : [] + ) + export function Union(T: T, K: K) { + return T.map(T => Resolve(T, K)) as Union } // ---------------------------------------------------------------- // Properties // ---------------------------------------------------------------- - export type Properties< - T extends TSchema, - K extends PropertyKey[], - A extends PropertyKey[] = KeyOfStringResolver.Resolve, - B extends PropertyKey[] = Logic.Complement - > = ( - Evaluate> + export function Property, K extends PropertyKey>(T: T, K: K) { + const { [K]: _, ...R } = T + return R as TProperties + } + export type Properties> = ( + Evaluate> ) - export function Properties(T: T, K: [...K]): Properties { - const A = KeyOfStringResolver.Resolve(T) as PropertyKey[] - const B = Logic.Complement(A, K) as PropertyKey[] - return ReduceProperties(T, B) as Properties + export function Properties(T: T, K: K) { + return K.reduce((T, K) => Property(T, K), T as TProperties) } // ---------------------------------------------------------------- // Resolve // ---------------------------------------------------------------- - export type Resolve> = ( - TObject

+ 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, K: [...K]): Resolve { - const P = Properties(T, K) as TProperties - return Type.Object(P) as Resolve + return ( + TypeGuard.TIntersect(T) ? Type.Intersect(Intersect(T.allOf, K)) : + TypeGuard.TUnion(T) ? Type.Union(Union(T.anyOf, K)) : + TypeGuard.TObject(T) ? Type.Object(Properties(T.properties, K)) : + Type.Object({}) + ) as Resolve } } + +// prettier-ignore +// export namespace OmitResolver { +// export type Evaluate = T extends infer O ? { [K in keyof O]: O[K] } : never +// export type Ensure = T extends infer U ? U : never +// // ---------------------------------------------------------------- +// // ReduceProperties +// // ---------------------------------------------------------------- +// export type ReduceProperties = +// K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] +// ? { [_ in L]: IndexedTypeResolver.Resolve } & ReduceProperties +// : {} +// export function ReduceProperties(T: T, K: [...K]): ReduceProperties { +// return K.reduce((acc, key) => { +// return { ...acc, [key]: IndexedTypeResolver.Resolve(T, [key]) } +// }, {}) as ReduceProperties +// } +// // ---------------------------------------------------------------- +// // Properties +// // ---------------------------------------------------------------- +// export type Properties< +// T extends TSchema, +// K extends PropertyKey[], +// A extends PropertyKey[] = KeyOfStringResolver.Resolve, +// B extends PropertyKey[] = Logic.Complement +// > = ( +// Evaluate> +// ) +// export function Properties(T: T, K: [...K]): Properties { +// const A = KeyOfStringResolver.Resolve(T) as PropertyKey[] +// const B = Logic.Complement(A, K) as PropertyKey[] +// return ReduceProperties(T, B) as Properties +// } +// // ---------------------------------------------------------------- +// // Resolve +// // ---------------------------------------------------------------- +// export type Resolve> = ( +// Ensure> +// ) +// export function Resolve(T: T, K: [...K]): Resolve { +// const P = Properties(T, K) as TProperties +// return Type.Object(P) as Resolve +// } +// } diff --git a/src/typebox.ts b/src/typebox.ts index d0207cd10..f1b845fa7 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -32,6 +32,8 @@ THE SOFTWARE. export { TypeRegistry, FormatRegistry } from './type/registry/index' export { TypeGuard, ValueGuard } from './type/guard/index' export { TypeClone } from './type/clone/index' +export * from './type/resolve/index' + // ------------------------------------------------------------------ // Import: Infrastructure // ------------------------------------------------------------------ @@ -53,6 +55,7 @@ import { IntrinsicResolver, IntersectResolver, CompositeResolver, + OmitResolver, } from './type/resolve/index' // ------------------------------------------------------------------ // Symbols @@ -452,15 +455,7 @@ export interface TObject extends TSchema, O // ------------------------------------------------------------------ // TOmit // ------------------------------------------------------------------ -export type TOmitProperties = Evaluate>> -export type TOmitRest = AssertRest<{ [K2 in keyof T]: TOmit, K> }> -// prettier-ignore -export type TOmit = - T extends TRecursive ? TRecursive> : - T extends TIntersect ? TIntersect> : - T extends TUnion ? TUnion> : - T extends TObject ? TObject> : - T +export type TOmit = OmitResolver.Resolve // ------------------------------------------------------------------ // TParameters // ------------------------------------------------------------------ @@ -964,11 +959,11 @@ export class JsonTypeBuilder extends TypeBuilder { ) as TExtract } /** `[Json]` Returns an Indexed property type for the given keys */ - public Index>(T: T, keys: K, options?: SchemaOptions): TIndex + public Index>(T: T, K: K, options?: SchemaOptions): TIndex /** `[Json]` Returns an Indexed property type for the given keys */ - public Index(T: T, K: [...K], options?: SchemaOptions): TIndex + public Index(T: T, K: readonly [...K], options?: SchemaOptions): TIndex /** `[Json]` Returns an Indexed property type for the given keys */ - public Index(T: TSchema, K: unknown, options: SchemaOptions = {}): any { + public Index(T: TSchema, K: TSchema | readonly PropertyKey[], options: SchemaOptions = {}): any { const keys = TypeGuard.TSchema(K) ? IndexedKeyResolver.Resolve(K) : K as string[] const type = IndexedTypeResolver.Resolve(T, keys) return TypeClone.Type(type, options) @@ -1047,28 +1042,14 @@ export class JsonTypeBuilder extends TypeBuilder { ) as unknown as TObject } /** `[Json]` Constructs a type whose keys are omitted from the given type */ - public Omit)[]>(schema: T, keys: readonly [...K], options?: SchemaOptions): TOmit - /** `[Json]` Constructs a type whose keys are omitted from the given type */ - public Omit[]>>(schema: T, keys: K, options?: SchemaOptions): TOmit[number]> + public Omit>(T: T, K: K, options?: SchemaOptions): TOmit /** `[Json]` Constructs a type whose keys are omitted from the given type */ - public Omit>(schema: T, key: K, options?: SchemaOptions): TOmit - /** `[Json]` Constructs a type whose keys are omitted from the given type */ - //public Omit(schema: T, key: K, options?: SchemaOptions): TOmit[number]> - /** `[Json]` Constructs a type whose keys are omitted from the given type */ - public Omit(schema: T, key: K, options?: SchemaOptions): TOmit - /** `[Json]` Constructs a type whose keys are omitted from the given type */ - public Omit(schema: TSchema, unresolved: any, options: SchemaOptions = {}): any { - const keys = TypeGuard.TSchema(unresolved) ? IndexedKeyResolver.Resolve(unresolved) as string[] : unresolved as string[] - return ObjectMap.Map(this.Discard(TypeClone.Type(schema), ['$id', Transform]), (object) => { - if (ValueGuard.IsArray(object.required)) { - object.required = object.required.filter((key: string) => !keys.includes(key as any)) - if (object.required.length === 0) delete object.required - } - for (const key of Object.getOwnPropertyNames(object.properties)) { - if (keys.includes(key as any)) delete object.properties[key] - } - return object - }, options) + public Omit(T: T, K: readonly [...K], options?: SchemaOptions): TOmit + public Omit(T: TSchema, K: TSchema | readonly PropertyKey[], options: SchemaOptions = {}): any { + const I = TypeGuard.TSchema(K) ? IndexedKeyResolver.Resolve(K) : K as string[] + const D = Discard.Keys(T, [ Transform, '$id', 'required' ]) as TSchema + const R = TypeClone.Type(OmitResolver.Resolve(T, I), options) + return { ...D, ...R } } /** `[Json]` Constructs a type where all properties are optional */ public Partial(schema: T, options: ObjectOptions = {}): TPartial { diff --git a/test/runtime/type/guard/union.ts b/test/runtime/type/guard/union.ts index 62b485e40..0a52c57c6 100644 --- a/test/runtime/type/guard/union.ts +++ b/test/runtime/type/guard/union.ts @@ -1,4 +1,4 @@ -import { TypeGuard } from '@sinclair/typebox' +import { TSchema, TypeGuard } from '@sinclair/typebox' import { Type } from '@sinclair/typebox' import { Assert } from '../../assert/index' @@ -46,7 +46,7 @@ describe('type/guard/TUnion', () => { Type.Object({ x: Type.Number(), }), - {} as any, + {} as TSchema, ]), ) Assert.IsFalse(R)