diff --git a/examples/index.ts b/examples/index.ts index 6dd4cfc4b..29c7739c3 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -46,257 +46,54 @@ import { IndexResolver, Assert, UnionToIntersect, + KeyResolver, + Ensure } from '@sinclair/typebox' +// ------------------------------------------------------------------ +// KeyOfResolver +// ------------------------------------------------------------------ // prettier-ignore -export namespace KeyResolver { +export namespace KeyOfResolver { // ---------------------------------------------------------------- - // Collect + // Literals // ---------------------------------------------------------------- - export type Collect = ( - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? [Resolve, ...Collect] + export type Literals = ( + T extends [infer L extends TLiteralValue, ...infer R extends TLiteralValue[]] + ? [TLiteral, ...Literals] : [] ) - export function Collect(T: [...T]): Collect { + export function Literals(T: [...T]): Literals { const [L, ...R] = T return ( T.length > 0 - ? [Resolve(L), ...Collect(R)] + ? [Type.Literal(L as TLiteralValue), ...Literals(R)] : [] - ) as Collect - } - // ---------------------------------------------------------------- - // Includes - // ---------------------------------------------------------------- - export type Includes = ( - T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? S extends L - ? true - : Includes - : false - ) - export function Includes(T: [...T], S: S) { - return ( - T.includes(S) - ) as Includes - } - // ---------------------------------------------------------------- - // IntersectDistinct - // ---------------------------------------------------------------- - export type IntersectDistinct = ( - T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? Includes extends true - ? [...IntersectDistinct] - : [L, ...IntersectDistinct] - : S - ) - export function IntersectDistinct(T: [...T], S: [...S]): IntersectDistinct { - const [L, ...R] = T - return ( - T.length > 0 - ? Includes(S, L) === true - ? [...IntersectDistinct(R, S)] - : [L, ...IntersectDistinct(R, S)] - : S - ) as IntersectDistinct - } - // ---------------------------------------------------------------- - // IntersectIntersection - // ---------------------------------------------------------------- - export type IntersectIntersection = ( - T extends [infer L extends PropertyKey[]] - ? L - : T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]] - ? IntersectDistinct> - : [] - ) - export function IntersectIntersection(T: [...T]): IntersectIntersection { - return ( - T.length === 1 - ? T[0] - : Into(() => { - const [L, ...R] = T - return L.length > 0 - ? IntersectDistinct(L, IntersectIntersection(R)) - : [] - }) - ) as IntersectIntersection - } - // ---------------------------------------------------------------- - // Intersect - // ---------------------------------------------------------------- - export type Intersect> = ( - IntersectIntersection - ) - export function Intersect(T: [...T], C = Collect(T)): Intersect { - return ( - IntersectIntersection(C as PropertyKey[][]) as Intersect - ) - } - // ---------------------------------------------------------------- - // UnionDistinct - // ---------------------------------------------------------------- - export type UnionDistinct = ( - T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? Includes extends true - ? [L, ...UnionDistinct] - : [...UnionDistinct] - : [] - ) - export function UnionDistinct(T: [...T], S: [...S]): UnionDistinct { - const [L, ...R] = T - return ( - T.length > 0 - ? Includes(S, L) === true - ? [L, ...UnionDistinct(R, S)] - : [...UnionDistinct(R, S)] - : [] - ) as UnionDistinct - } - // ---------------------------------------------------------------- - // UnionIntersection - // ---------------------------------------------------------------- - export type UnionIntersection = ( - T extends [infer L extends PropertyKey[]] - ? L - : T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]] - ? UnionDistinct> - : [] - ) - export function UnionIntersection(T: [...T]): UnionIntersection { - return ( - T.length === 1 - ? T[0] - : Into(() => { - const [L, ...R] = T - return L.length > 0 - ? UnionDistinct(L, UnionIntersection(R)) - : [] - }) - ) as UnionIntersection - } - // ---------------------------------------------------------------- - // Union - // ---------------------------------------------------------------- - export type Union> = ( - UnionIntersection - ) - export function Union(T: [...T], C = Collect(T)): Union { - return ( - UnionIntersection(C as PropertyKey[][]) as Union - ) - } - // ---------------------------------------------------------------- - // Tuple - // ---------------------------------------------------------------- - export type TupleNext = ( - I extends [infer L extends string, ...infer _] - ? Increment.Next - : '0' - ) - export type Tuple = ( - T extends [infer _, ...infer R extends TSchema[]] - ? Tuple, ...I]> - : I - ) - export function Tuple(T: [...T]): Tuple { - return ( - T.map((_, index) => index.toString()) - ) as Tuple - } - // ---------------------------------------------------------------- - // Array - // ---------------------------------------------------------------- - export type Array<_ extends TSchema> = ( - ['number'] - ) - export function Array<_ extends TSchema>(_: _): Array<_> { - return ( - ['number'] - ) - } - // ---------------------------------------------------------------- - // Properties - // ---------------------------------------------------------------- - export type Properties = ( - UnionToTuple - ) - export function Properties(T: T): Properties { - return ( - globalThis.Object.getOwnPropertyNames(T) - ) as Properties - } - // ---------------------------------------------------------------- - // Pattern - // ---------------------------------------------------------------- - function PatternProperties(patternProperties: Record): string[] { - if(!includePatternProperties) return [] - const patternPropertyKeys = globalThis.Object.getOwnPropertyNames(patternProperties) - return patternPropertyKeys.map(key => { - return (key[0] === '^' && key[key.length - 1] === '$') - ? key.slice(1, key.length - 1) - : key - }) + ) as Literals } // ---------------------------------------------------------------- // Resolve // ---------------------------------------------------------------- export type Resolve = ( - T extends TRecursive ? Resolve : - T extends TIntersect ? Intersect : - T extends TUnion ? Union : - T extends TTuple ? Tuple : - T extends TArray ? Array : - T extends TObject ? Properties : - [] + Ensure>>> ) - /** Resolves finite keys from this type */ export function Resolve(T: T): Resolve { return ( - TypeGuard.TIntersect(T) ? Intersect(T.allOf) : - TypeGuard.TUnion(T) ? Union(T.anyOf) : - TypeGuard.TTuple(T) ? Tuple(T.items ?? []) : - TypeGuard.TArray(T) ? Array(T.items) : - TypeGuard.TObject(T) ? Properties(T.properties) : - TypeGuard.TRecord(T) ? PatternProperties(T.patternProperties) : - [] - ) as Resolve - } - // ---------------------------------------------------------------- - // ResolvePattern - // ---------------------------------------------------------------- - let includePatternProperties = false - /** Resolves keys as a regular expression, including pattern property keys */ - export function ResolvePattern(schema: TSchema): string { - includePatternProperties = true - const keys = Resolve(schema) - includePatternProperties = false - const pattern = keys.map((key) => `(${key})`) - return `^(${pattern.join('|')})$` + UnionType.Resolve(Literals(KeyResolver.Resolve(T) as TLiteralValue[])) + ) as unknown as Resolve } } -const A = KeyResolver.Intersect([ - Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number(), - }), - Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number(), - }), - - Type.Record(Type.Number(), Type.Number()), -]) - -console.log(A) -// const B = KeyResolver.Resolve(Type.Union([ -// Type.Object({ x: Type.Number(), y: Type.Number() }), -// Type.Object({ x: Type.Number(), z: Type.Number() }), -// ])) +const K = KeyOfResolver.Resolve(Type.Tuple([ + Type.Number(), + Type.Number(), + Type.Number(), + Type.Number(), + Type.Number(), + Type.Number(), + Type.Number(), + Type.Number(), +])) -// console.log(B) +console.log(K) diff --git a/src/typebox.ts b/src/typebox.ts index e8e37db0d..1ea5e0e7b 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -2766,7 +2766,7 @@ export namespace KeyResolver { ) export function Tuple(T: [...T]): Tuple { return ( - T.map((_, index) => index.toString()) + T.map((_, index) => index).reverse() ) as Tuple } // ---------------------------------------------------------------- @@ -3088,6 +3088,39 @@ export namespace AccessResolver { ) as Resolve } } +// ------------------------------------------------------------------ +// KeyOfResolver +// ------------------------------------------------------------------ +// prettier-ignore +export namespace KeyOfResolver { + // ---------------------------------------------------------------- + // Literals + // ---------------------------------------------------------------- + export type Literals = ( + T extends [infer L extends TLiteralValue, ...infer R extends TLiteralValue[]] + ? [TLiteral, ...Literals] + : [] + ) + export function Literals(T: [...T]): Literals { + const [L, ...R] = T + return ( + T.length > 0 + ? [Type.Literal(L as TLiteralValue), ...Literals(R)] + : [] + ) as Literals + } + // ---------------------------------------------------------------- + // Resolve + // ---------------------------------------------------------------- + export type Resolve = ( + Ensure>>> + ) + export function Resolve(T: T): Resolve { + return ( + UnionType.Resolve(Literals(KeyResolver.Resolve(T) as TLiteralValue[])) + ) as unknown as Resolve + } +} // -------------------------------------------------------------------------- // KeyArrayResolver // -------------------------------------------------------------------------- @@ -3594,30 +3627,9 @@ export class JsonTypeBuilder extends TypeBuilder { : this.Create({ ...options, ...clonedUnevaluatedProperties, [Kind]: 'Intersect', allOf: cloned }) } /** `[Json]` Creates a KeyOf type */ - public KeyOf(schema: T, options: SchemaOptions = {}): TKeyOf { - return ( - TypeGuard.TRecord(schema) ? (() => { - const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0] - return ( - pattern === PatternNumberExact ? this.Number(options) : - pattern === PatternStringExact ? this.String(options) : - this.Throw('Unable to resolve key type from Record key pattern') - ) - })() : - TypeGuard.TTuple(schema) ? (() => { - const items = ValueGuard.IsUndefined(schema.items) ? [] : schema.items - const literals = items.map((_, index) => Type.Literal(index.toString())) - return this.Union(literals, options) - })() : - TypeGuard.TArray(schema) ? (() => { - return this.Number(options) - })() : (() => { - const keys = KeyResolver.Resolve(schema) - if (keys.length === 0) return this.Never(options) as TKeyOf - const literals = keys.map((key) => this.Literal(key)) - return this.Union(literals, options) - })() - ) as unknown as TKeyOf + public KeyOf(schema: T, options: SchemaOptions = {}): KeyOfResolver.Resolve { + const type = KeyOfResolver.Resolve(schema) + return TypeClone.Type(type, options) } /** `[Json]` Creates a Literal type */ public Literal(value: T, options: SchemaOptions = {}): TLiteral { diff --git a/test/runtime/type/guard/keyof.ts b/test/runtime/type/guard/keyof.ts index d8b24ad74..43a7426d9 100644 --- a/test/runtime/type/guard/keyof.ts +++ b/test/runtime/type/guard/keyof.ts @@ -70,7 +70,7 @@ describe('type/guard/TKeyOf', () => { const T = Type.Tuple([Type.Number(), Type.Null()]) const K = Type.KeyOf(T) Assert.IsTrue(TypeGuard.TUnion(K)) - Assert.IsEqual(K.anyOf[0].const, '0') - Assert.IsEqual(K.anyOf[1].const, '1') + Assert.IsEqual(K.anyOf[0].const, 0) + Assert.IsEqual(K.anyOf[1].const, 1) }) })