Skip to content

Commit

Permalink
Reimplement Index Types
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Nov 24, 2023
1 parent d5f5d53 commit fb6127f
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 257 deletions.
257 changes: 27 additions & 230 deletions examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 TSchema[]> = (
T extends [infer L extends TSchema, ...infer R extends TSchema[]]
? [Resolve<L>, ...Collect<R>]
export type Literals<T extends TLiteralValue[]> = (
T extends [infer L extends TLiteralValue, ...infer R extends TLiteralValue[]]
? [TLiteral<L>, ...Literals<R>]
: []
)
export function Collect<T extends TSchema[]>(T: [...T]): Collect<T> {
export function Literals<T extends TLiteralValue[]>(T: [...T]): Literals<T> {
const [L, ...R] = T
return (
T.length > 0
? [Resolve(L), ...Collect(R)]
? [Type.Literal(L as TLiteralValue), ...Literals(R)]
: []
) as Collect<T>
}
// ----------------------------------------------------------------
// Includes
// ----------------------------------------------------------------
export type Includes<T extends PropertyKey[], S extends PropertyKey> = (
T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]]
? S extends L
? true
: Includes<R, S>
: false
)
export function Includes<T extends PropertyKey[], S extends PropertyKey>(T: [...T], S: S) {
return (
T.includes(S)
) as Includes<T, S>
}
// ----------------------------------------------------------------
// IntersectDistinct
// ----------------------------------------------------------------
export type IntersectDistinct<T extends PropertyKey[], S extends PropertyKey[]> = (
T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]]
? Includes<S, L> extends true
? [...IntersectDistinct<R, S>]
: [L, ...IntersectDistinct<R, S>]
: S
)
export function IntersectDistinct<T extends PropertyKey[], S extends PropertyKey[]>(T: [...T], S: [...S]): IntersectDistinct<T, S> {
const [L, ...R] = T
return (
T.length > 0
? Includes(S, L) === true
? [...IntersectDistinct(R, S)]
: [L, ...IntersectDistinct(R, S)]
: S
) as IntersectDistinct<T, S>
}
// ----------------------------------------------------------------
// IntersectIntersection
// ----------------------------------------------------------------
export type IntersectIntersection<T extends PropertyKey[][]> = (
T extends [infer L extends PropertyKey[]]
? L
: T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]]
? IntersectDistinct<L, IntersectIntersection<R>>
: []
)
export function IntersectIntersection<T extends PropertyKey[][]>(T: [...T]): IntersectIntersection<T> {
return (
T.length === 1
? T[0]
: Into(() => {
const [L, ...R] = T
return L.length > 0
? IntersectDistinct(L, IntersectIntersection(R))
: []
})
) as IntersectIntersection<T>
}
// ----------------------------------------------------------------
// Intersect
// ----------------------------------------------------------------
export type Intersect<T extends TSchema[], C extends PropertyKey[][] = Collect<T>> = (
IntersectIntersection<C>
)
export function Intersect<T extends TSchema[]>(T: [...T], C = Collect(T)): Intersect<T> {
return (
IntersectIntersection(C as PropertyKey[][]) as Intersect<T>
)
}
// ----------------------------------------------------------------
// UnionDistinct
// ----------------------------------------------------------------
export type UnionDistinct<T extends PropertyKey[], S extends PropertyKey[]> = (
T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]]
? Includes<S, L> extends true
? [L, ...UnionDistinct<R, S>]
: [...UnionDistinct<R, S>]
: []
)
export function UnionDistinct<T extends PropertyKey[], S extends PropertyKey[]>(T: [...T], S: [...S]): UnionDistinct<T, S> {
const [L, ...R] = T
return (
T.length > 0
? Includes(S, L) === true
? [L, ...UnionDistinct(R, S)]
: [...UnionDistinct(R, S)]
: []
) as UnionDistinct<T, S>
}
// ----------------------------------------------------------------
// UnionIntersection
// ----------------------------------------------------------------
export type UnionIntersection<T extends PropertyKey[][]> = (
T extends [infer L extends PropertyKey[]]
? L
: T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]]
? UnionDistinct<L, UnionIntersection<R>>
: []
)
export function UnionIntersection<T extends PropertyKey[][]>(T: [...T]): UnionIntersection<T> {
return (
T.length === 1
? T[0]
: Into(() => {
const [L, ...R] = T
return L.length > 0
? UnionDistinct(L, UnionIntersection(R))
: []
})
) as UnionIntersection<T>
}
// ----------------------------------------------------------------
// Union
// ----------------------------------------------------------------
export type Union<T extends TSchema[], C extends PropertyKey[][] = Collect<T>> = (
UnionIntersection<C>
)
export function Union<T extends TSchema[]>(T: [...T], C = Collect(T)): Union<T> {
return (
UnionIntersection(C as PropertyKey[][]) as Union<T>
)
}
// ----------------------------------------------------------------
// Tuple
// ----------------------------------------------------------------
export type TupleNext<I extends string[] = []> = (
I extends [infer L extends string, ...infer _]
? Increment.Next<L>
: '0'
)
export type Tuple<T extends TSchema[], I extends string[] = []> = (
T extends [infer _, ...infer R extends TSchema[]]
? Tuple<R, [TupleNext<I>, ...I]>
: I
)
export function Tuple<T extends TSchema[]>(T: [...T]): Tuple<T> {
return (
T.map((_, index) => index.toString())
) as Tuple<T>
}
// ----------------------------------------------------------------
// Array
// ----------------------------------------------------------------
export type Array<_ extends TSchema> = (
['number']
)
export function Array<_ extends TSchema>(_: _): Array<_> {
return (
['number']
)
}
// ----------------------------------------------------------------
// Properties
// ----------------------------------------------------------------
export type Properties<T extends TProperties> = (
UnionToTuple<keyof T>
)
export function Properties<T extends TProperties>(T: T): Properties<T> {
return (
globalThis.Object.getOwnPropertyNames(T)
) as Properties<T>
}
// ----------------------------------------------------------------
// Pattern
// ----------------------------------------------------------------
function PatternProperties(patternProperties: Record<PropertyKey, TSchema>): 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<T>
}
// ----------------------------------------------------------------
// Resolve
// ----------------------------------------------------------------
export type Resolve<T extends TSchema> = (
T extends TRecursive<infer S> ? Resolve<S> :
T extends TIntersect<infer S> ? Intersect<S> :
T extends TUnion<infer S> ? Union<S> :
T extends TTuple<infer S> ? Tuple<S> :
T extends TArray<infer S> ? Array<S> :
T extends TObject<infer S> ? Properties<S> :
[]
Ensure<UnionType.Resolve<Literals<KeyResolver.Resolve<T>>>>
)
/** Resolves finite keys from this type */
export function Resolve<T extends TSchema>(T: T): Resolve<T> {
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<T>
}
// ----------------------------------------------------------------
// 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<T>
}
}

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)
62 changes: 37 additions & 25 deletions src/typebox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2766,7 +2766,7 @@ export namespace KeyResolver {
)
export function Tuple<T extends TSchema[]>(T: [...T]): Tuple<T> {
return (
T.map((_, index) => index.toString())
T.map((_, index) => index).reverse()
) as Tuple<T>
}
// ----------------------------------------------------------------
Expand Down Expand Up @@ -3088,6 +3088,39 @@ export namespace AccessResolver {
) as Resolve<T, K>
}
}
// ------------------------------------------------------------------
// KeyOfResolver
// ------------------------------------------------------------------
// prettier-ignore
export namespace KeyOfResolver {
// ----------------------------------------------------------------
// Literals
// ----------------------------------------------------------------
export type Literals<T extends TLiteralValue[]> = (
T extends [infer L extends TLiteralValue, ...infer R extends TLiteralValue[]]
? [TLiteral<L>, ...Literals<R>]
: []
)
export function Literals<T extends TLiteralValue[]>(T: [...T]): Literals<T> {
const [L, ...R] = T
return (
T.length > 0
? [Type.Literal(L as TLiteralValue), ...Literals(R)]
: []
) as Literals<T>
}
// ----------------------------------------------------------------
// Resolve
// ----------------------------------------------------------------
export type Resolve<T extends TSchema> = (
Ensure<UnionType.Resolve<Literals<KeyResolver.Resolve<T>>>>
)
export function Resolve<T extends TSchema>(T: T): Resolve<T> {
return (
UnionType.Resolve(Literals(KeyResolver.Resolve(T) as TLiteralValue[]))
) as unknown as Resolve<T>
}
}
// --------------------------------------------------------------------------
// KeyArrayResolver
// --------------------------------------------------------------------------
Expand Down Expand Up @@ -3594,30 +3627,9 @@ export class JsonTypeBuilder extends TypeBuilder {
: this.Create({ ...options, ...clonedUnevaluatedProperties, [Kind]: 'Intersect', allOf: cloned })
}
/** `[Json]` Creates a KeyOf type */
public KeyOf<T extends TSchema>(schema: T, options: SchemaOptions = {}): TKeyOf<T> {
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<T>
const literals = keys.map((key) => this.Literal(key))
return this.Union(literals, options)
})()
) as unknown as TKeyOf<T>
public KeyOf<T extends TSchema>(schema: T, options: SchemaOptions = {}): KeyOfResolver.Resolve<T> {
const type = KeyOfResolver.Resolve(schema)
return TypeClone.Type(type, options)
}
/** `[Json]` Creates a Literal type */
public Literal<T extends TLiteralValue>(value: T, options: SchemaOptions = {}): TLiteral<T> {
Expand Down
4 changes: 2 additions & 2 deletions test/runtime/type/guard/keyof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})

0 comments on commit fb6127f

Please sign in to comment.