From f8935c4e0c286b56f151d6f04705836dbdae9eda Mon Sep 17 00:00:00 2001 From: sinclair Date: Thu, 23 Nov 2023 21:06:25 +0900 Subject: [PATCH] Reimplement Index Types --- examples/index.ts | 204 +++++++++------------ examples/next/accessor.ts | 101 +++++++++++ examples/next/distinct.ts | 33 ++++ examples/next/indexer.ts | 55 ++++++ examples/prototypes/const.ts | 4 +- examples/prototypes/evaluate.ts | 4 +- src/typebox.ts | 313 +++++++++++++++++++++----------- 7 files changed, 480 insertions(+), 234 deletions(-) create mode 100644 examples/next/accessor.ts create mode 100644 examples/next/distinct.ts create mode 100644 examples/next/indexer.ts diff --git a/examples/index.ts b/examples/index.ts index 791529c88..576a0b569 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -1,141 +1,107 @@ -import { TypeSystem } from '@sinclair/typebox/system' -import { TypeCompiler } from '@sinclair/typebox/compiler' -import { Value, ValuePointer } from '@sinclair/typebox/value' -import { Type, Accessor, TemplateLiteralParser, TemplateLiteralFinite, TemplateLiteralResolver, TypeGuard, TIntersect, TUnion, TTemplateLiteral, IsTemplateLiteralFiniteCheck, UnionToTuple, Static, TSchema, TLiteralValue, TLiteral, TNumber, TInteger, TBigInt, TString, IndexerOptions, PatternNumber } from '@sinclair/typebox' -import { TObject } from '@sinclair/typebox' - -// function TTemplateLiteral(schema: TTemplateLiteral, options: IndexerOptions): string[] { -// const expression = TemplateLiteralParser.ParseExact(schema.pattern) -// const finite = TemplateLiteralFinite.Check(expression) -// if (!finite && options.includePatterns) return [schema.pattern] -// if (!finite && !options.includePatterns) return [] -// const resolved = TemplateLiteralResolver.Resolve(schema) -// return TypeGuard.TUnionLiteral(resolved) ? resolved.anyOf.map((schema) => schema.const.toString()) : [] -// } - // -------------------------------------------------------------------------- -// TIndexer +// Accessor // -------------------------------------------------------------------------- -export type TIndexerTemplateLiteral> = F extends true ? UnionToTuple> : [] -export type TIndexerUnion = T extends [infer L extends TSchema, ...infer R extends TSchema[]] ? [...TIndexer, ...TIndexerUnion] : [] -export type TIndexerLiteral = T extends PropertyKey ? [T] : [] -// prettier-ignore -export type TIndexer = - T extends TTemplateLiteral ? TIndexerTemplateLiteral : - T extends TUnion ? TIndexerUnion : - T extends TLiteral ? TIndexerLiteral : - T extends TNumber ? ['number'] : // tuple + array indexing - T extends TInteger ? ['number'] : // tuple + array indexing - [] -export namespace Indexer { - function TTemplateLiteral(schema: TTemplateLiteral): string[] { - const expression = TemplateLiteralParser.ParseExact(schema.pattern) - const finite = TemplateLiteralFinite.Check(expression) - if (!finite) return [] - const resolved = TemplateLiteralResolver.Resolve(schema) - return TypeGuard.TUnionLiteral(resolved) ? resolved.anyOf.map((schema) => schema.const.toString()) : [] - } - function Union(schema: TSchema[]): string[] { - const [L, ...R] = schema - return (schema.length > 0) ? [...Keys(L), ...Union(R)] : [] - } - function TLiteral(schema: TLiteral): string[] { - return [schema.const.toString()] - } - function Visit(schema: TSchema) { - return ( - TypeGuard.TTemplateLiteral(schema) ? TTemplateLiteral(schema) : - TypeGuard.TUnion(schema) ? Union(schema.anyOf) : - TypeGuard.TLiteral(schema) ? TLiteral(schema) : - TypeGuard.TNumber(schema) ? ['number'] : // tuple + array indexing - TypeGuard.TInteger(schema) ? ['number'] : // tuple + array indexing - [] - ) - } - export function Pattern(schema: TSchema) { - const keys = Keys(schema) - const pattern = keys.map((key) => key === 'number' ? PatternNumber : key) - return `^(${pattern.join('|')})$` - } - export function Keys(schema: TSchema) { - return [...new Set(Visit(schema))] - } -} +import { Type, Optional, TOptional, TReadonly, TObject, TTuple, TIntersect, TUnion, Kind, TSchema, TypeClone, TypeGuard, ValueGuard, TPropertyKey, SchemaOptions, TProperties, TNever, TRecursive, TArray, Discard, TNumber, TString, TBoolean } from "@sinclair/typebox" -const K = Indexer.Pattern(Type.Union([ - Type.String(), - Type.Number(), - Type.String() -])) +export function Into(func: () => T): T { + return func() +} +const T = Type.Object({ + x: Type.String(), + y: Type.Union([ + Type.Number(), + Type.BigInt() + ]) +}) +const I = Type.Index(T, Type.KeyOf(T)) -console.log(K) -// export interface IndexerOptions { -// includePatterns: boolean -// } +// ------------------------------------------------------------------ +// Accessor +// ------------------------------------------------------------------ +// export type TAccessorProperty = K extends keyof T ? T[K] : TNever +// export type TAccessorTuple = K extends keyof T ? T[K] : K extends 'number' ? UnionTypeResolve : TNever +// export type TAccessorArray = K extends number ? T : TNever // // prettier-ignore -// export namespace Indexer { -// function UnwrapPattern(key: string) { -// return key[0] === '^' && key[key.length - 1] === '$' ? key.slice(1, key.length - 1) : key -// } +// export type TAccessorRest = +// T extends [infer L extends TSchema, ...infer R extends TSchema[]] +// ? TAccessorKey extends infer N extends TSchema +// ? N extends TNever +// ? [...TAccessorRest] +// : [N, ...TAccessorRest] +// : [] +// : [] +// // prettier-ignore +// export type TAccessorKey = +// T extends TRecursive ? TAccessorKey : +// T extends TIntersect ? IntersectTypeResolve> : +// T extends TUnion ? UnionTypeResolve> : +// T extends TTuple ? TAccessorTuple : +// T extends TArray ? TAccessorArray : +// T extends TObject ? TAccessorProperty : +// TNever +// // prettier-ignore +// export type TAccessorKeys = +// K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] +// ? [TAccessorKey, ...TAccessorKeys] +// : [] -// function TIntersect(schema: TIntersect, options: IndexerOptions): string[] { -// return schema.allOf.reduce((acc, schema) => [...acc, ...Visit(schema, options)], [] as string[]) -// } -// function TUnion(schema: TUnion, options: IndexerOptions): string[] { -// const sets = schema.anyOf.reduce((acc, schema) => [...acc, ...Visit(schema, options)], [] as string[]) -// console.log('Accessor Union', sets) -// return [...sets.reduce((set, outer) => outer.map((key) => (sets.every((inner) => inner.includes(key)) ? set.add(key) : set))[0], new Set())] -// } -// function TObject(schema: TObject, options: IndexerOptions): string[] { -// return Object.getOwnPropertyNames(schema.properties) -// } -// function TRecord(schema: TRecord, options: IndexerOptions): string[] { -// return options.includePatterns ? Object.getOwnPropertyNames(schema.patternProperties) : [] +// export type TAccessor = UnionTypeResolve> + +// // prettier-ignore +// export namespace Accessor { +// function Property(T: TProperties, K: PropertyKey): TSchema { +// return K in T ? T[K as string] : Type.Never() // } -// function TLiteral(schema: TLiteral, options: IndexerOptions): string[] { -// return [schema.const.toString()] +// function Tuple(T: TSchema[], K: PropertyKey): TSchema { +// return K in T ? T[K as number] : K === 'number' ? Union.Resolve(T) : Type.Never() // } -// function TNumber(schema: TNumber, options: IndexerOptions): string[] { -// return ['number'] +// function Array(T: TSchema, K: PropertyKey): TSchema { +// return K === 'number' ? T : Type.Never() // } -// function TInteger(schema: TInteger, options: IndexerOptions): string[] { -// return ['number'] +// function Rest(T: TSchema[], K: PropertyKey): TSchema[] { +// const [L, ...R] = T +// return T.length > 0 +// ? Into(() => { +// const N = Key(L, K) +// return TypeGuard.TNever(N) +// ? [...Rest(R, K)] +// : [N, ...Rest(R, K)] +// }) +// : [] // } -// function Visit(schema: TSchema, options: IndexerOptions): string[] { +// function Key(T: TSchema, K: PropertyKey) { // return ( -// TypeGuard.TTemplateLiteral(schema) ? TTemplateLiteral(schema, options) : -// TypeGuard.TIntersect(schema) ? TIntersect(schema, options) : -// TypeGuard.TUnion(schema) ? TUnion(schema, options) : -// TypeGuard.TObject(schema) ? TObject(schema, options) : -// TypeGuard.TRecord(schema) ? TRecord(schema, options) : -// TypeGuard.TLiteral(schema) ? TLiteral(schema, options) : -// TypeGuard.TInteger(schema) ? TInteger(schema, options) : -// TypeGuard.TNumber(schema) ? TNumber(schema, options) : -// [] +// TypeGuard.TIntersect(T) ? Intersect.Resolve(Rest(T.allOf, K)) : +// TypeGuard.TUnion(T) ? Union.Resolve(Rest(T.anyOf, K)) : +// TypeGuard.TTuple(T) ? Tuple(T.items ?? [], K) : +// TypeGuard.TArray(T) ? Array(T.items, K) : +// TypeGuard.TObject(T) ? Property(T.properties, K) : +// Type.Never() // ) // } -// /** Resolves a regular expression pattern matching all keys in this schema */ -// export function Pattern(schema: TSchema): string { -// const keys = Keys(schema, { includePatterns: true }) -// const pattern = keys.map((key) => `(${UnwrapPattern(key)})`) -// return `^(${pattern.join('|')})$` +// function Keys(T: TSchema, K: PropertyKey[]): TSchema[] { +// const [L, ...R] = K +// return (K.length > 0) +// ? [Key(T, L), ...Keys(T, R)] +// : [] // } -// /** Resolves an array of keys derived from this schema */ -// export function Keys(schema: TSchema, options: IndexerOptions): string[] { -// console.log('Indexer', Visit(schema, options)) -// return [...new Set(Visit(schema, options))] +// export function Resolve(schema: TSchema, keys: PropertyKey[]) { +// return Union.Resolve(Keys(schema, keys)) // } // } -// const T = Type.Object({ -// 0: Type.Number(), -// 1: Type.String(), -// 2: Type.Boolean(), -// }) -// const I = Type.Index(T, Type.Union([Type.Literal(0), Type.Literal(2)])) +// const T = Type.Composite([ +// Type.Object({ x: Type.Optional(Type.Number()) }), +// Type.Object({ x: Type.Optional(Type.Number()) }) +// ]) + +// const A = Accessor.Resolve(T, ['x']) +// const I = Type.Index(T, ['x']) + + -// console.log(I) +// console.log(A) \ No newline at end of file diff --git a/examples/next/accessor.ts b/examples/next/accessor.ts new file mode 100644 index 000000000..b624ce3ec --- /dev/null +++ b/examples/next/accessor.ts @@ -0,0 +1,101 @@ +// // -------------------------------------------------------------------------- +// // Accessor +// // -------------------------------------------------------------------------- + +// import { Type, Optional, TObject, TTuple, TIntersect, TUnion, Kind, TSchema, TypeClone, TypeGuard, ValueGuard, TPropertyKey, SchemaOptions, TProperties, TNever, IntersectType, TRecursive, TArray, UnionTypeResolve } from "@sinclair/typebox" + +// // ------------------------------------------------------------------ +// // TAccessor +// // ------------------------------------------------------------------ +// export type TAccessorProperty = K extends keyof T ? T[K] : TNever +// export type TAccessorTuple = K extends keyof T ? T[K] : K extends 'number' ? UnionTypeResolve : TNever +// export type TAccessorArray = K extends number ? T : TNever +// // prettier-ignore +// export type TAccessorRest = +// T extends [infer L extends TSchema, ...infer R extends TSchema[]] +// ? TIndexKey extends infer N extends TSchema +// ? N extends TNever +// ? [...TAccessorRest] +// : [N, ...TAccessorRest] +// : [] +// : [] +// // prettier-ignore +// export type TIndexKey = +// T extends TRecursive ? TIndexKey : +// T extends TIntersect ? IntersectType.Resolve> : +// T extends TUnion ? UnionTypeResolve> : +// T extends TTuple ? TAccessorTuple : +// T extends TArray ? TAccessorArray : +// T extends TObject ? TAccessorProperty : +// TNever +// // prettier-ignore +// export type TAccessorKeys = +// K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] +// ? [TIndexKey, ...TAccessorKeys] +// : [] + +// export type TAccessor = UnionTypeResolve> + +// // prettier-ignore +// export namespace Accessor { +// function OptionalUnwrap(schema: TSchema[]): TSchema[] { +// return schema.map((schema) => { +// const { [Optional]: _, ...clone } = TypeClone.Type(schema) +// return clone +// }) +// } +// function IsIntersectOptional(schema: TSchema[]): boolean { +// return schema.every((schema) => TypeGuard.TOptional(schema)) +// } +// function IsUnionOptional(schema: TSchema[]): boolean { +// return schema.some((schema) => TypeGuard.TOptional(schema)) +// } +// function ResolveIntersect(schema: TIntersect): TSchema { +// return IsIntersectOptional(schema.allOf) ? Type.Optional(Type.Intersect(OptionalUnwrap(schema.allOf))) : schema +// } +// function ResolveUnion(schema: TUnion): TSchema { +// return IsUnionOptional(schema.anyOf) ? Type.Optional(Type.Union(OptionalUnwrap(schema.anyOf))) : schema +// } +// function ResolveOptional(schema: TSchema) { +// return ( +// schema[Kind] === 'Intersect' ? ResolveIntersect(schema as TIntersect) : +// schema[Kind] === 'Union' ? ResolveUnion(schema as TUnion) : +// schema +// ) +// } +// function TIntersect(schema: TIntersect, key: string): TSchema { +// const resolved = schema.allOf.reduce((acc, schema) => { +// const indexed = Visit(schema, key) +// return indexed[Kind] === 'Never' ? acc : [...acc, indexed] +// }, [] as TSchema[]) +// return ResolveOptional(Type.Intersect(resolved)) +// } +// function TUnion(schema: TUnion, key: string): TSchema { +// const resolved = schema.anyOf.map((schema) => Visit(schema, key)) +// return ResolveOptional(Type.Union(resolved)) +// } +// function TObject(schema: TObject, key: string): TSchema { +// const property = schema.properties[key] +// return ValueGuard.IsUndefined(property) ? Type.Never() : Type.Union([property]) +// } +// function TTuple(schema: TTuple, key: string): TSchema { +// const items = schema.items +// if (ValueGuard.IsUndefined(items)) return Type.Never() +// const element = items[key as any as number] // +// if (ValueGuard.IsUndefined(element)) return Type.Never() +// return element +// } +// function Visit(schema: TSchema, key: string): TSchema { +// return ( +// schema[Kind] === 'Intersect' ? TIntersect(schema as TIntersect, key) : +// schema[Kind] === 'Union' ? TUnion(schema as TUnion, key) : +// schema[Kind] === 'Object' ? TObject(schema as TObject, key) : +// schema[Kind] === 'Tuple' ? TTuple(schema as TTuple, key) : +// Type.Never() +// ) +// } +// export function Resolve(schema: TSchema, keys: TPropertyKey[], options: SchemaOptions = {}): TSchema { +// const resolved = keys.map((key) => Visit(schema, key.toString())) +// return ResolveOptional(Type.Union(resolved, options)) +// } +// } \ No newline at end of file diff --git a/examples/next/distinct.ts b/examples/next/distinct.ts new file mode 100644 index 000000000..6bea27e08 --- /dev/null +++ b/examples/next/distinct.ts @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------ +// Distinct +// ------------------------------------------------------------------ +import { TSchema, TypeExtends, TypeExtendsResult } from "@sinclair/typebox" + +// prettier-ignore +export type TDistinctIncludes = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? C extends L + ? true + : TDistinctIncludes + : false +// prettier-ignore +export type TDistinct = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? TDistinctIncludes extends false + ? TDistinct + : TDistinct + : Acc + +export namespace Distinct { + function DistinctIncludes(schemas: TSchema[], candidate: TSchema) { + return schemas.find((schema) => TypeExtends.Extends(candidate, schema) === TypeExtendsResult.True) !== undefined + } + // prettier-ignore + export function Distinct(schemas: [...T]): TDistinct { + return schemas.reduce((acc, candidate) => { + return !DistinctIncludes(acc, candidate) + ? [...acc, candidate] + : [...acc] + }, [] as TSchema[]) as TDistinct + } +} \ No newline at end of file diff --git a/examples/next/indexer.ts b/examples/next/indexer.ts new file mode 100644 index 000000000..7c9156938 --- /dev/null +++ b/examples/next/indexer.ts @@ -0,0 +1,55 @@ +import { TypeSystem } from '@sinclair/typebox/system' +import { TypeCompiler } from '@sinclair/typebox/compiler' +import { Value, ValuePointer } from '@sinclair/typebox/value' +import { Type, Accessor, TemplateLiteralParser, TemplateLiteralFinite, TemplateLiteralResolver, TypeGuard, TIntersect, TUnion, TTemplateLiteral, IsTemplateLiteralFiniteCheck, UnionToTuple, Static, TSchema, TLiteralValue, TLiteral, TNumber, TInteger, TBigInt, TString, IndexerOptions, PatternNumber } from '@sinclair/typebox' +import { TObject } from '@sinclair/typebox' + +// -------------------------------------------------------------------------- +// TIndexer +// -------------------------------------------------------------------------- +export type TIndexerTemplateLiteral> = F extends true ? UnionToTuple> : [] +export type TIndexerUnion = T extends [infer L extends TSchema, ...infer R extends TSchema[]] ? [...TIndexer, ...TIndexerUnion] : [] +export type TIndexerLiteral = T extends PropertyKey ? [T] : [] +// prettier-ignore +export type TIndexer = + T extends TTemplateLiteral ? TIndexerTemplateLiteral : + T extends TUnion ? TIndexerUnion : + T extends TLiteral ? TIndexerLiteral : + T extends TNumber ? ['number'] : // tuple + array indexing + T extends TInteger ? ['number'] : // tuple + array indexing + [] + +export namespace Indexer { + function TTemplateLiteral(schema: TTemplateLiteral): string[] { + const expression = TemplateLiteralParser.ParseExact(schema.pattern) + const finite = TemplateLiteralFinite.Check(expression) + if (!finite) return [] + const resolved = TemplateLiteralResolver.Resolve(schema) + return TypeGuard.TUnionLiteral(resolved) ? resolved.anyOf.map((schema) => schema.const.toString()) : [] + } + function Union(schema: TSchema[]): string[] { + const [L, ...R] = schema + return (schema.length > 0) ? [...Keys(L), ...Union(R)] : [] + } + function TLiteral(schema: TLiteral): string[] { + return [schema.const.toString()] + } + function Visit(schema: TSchema) { + return ( + TypeGuard.TTemplateLiteral(schema) ? TTemplateLiteral(schema) : + TypeGuard.TUnion(schema) ? Union(schema.anyOf) : + TypeGuard.TLiteral(schema) ? TLiteral(schema) : + TypeGuard.TNumber(schema) ? ['number'] : // tuple + array indexing + TypeGuard.TInteger(schema) ? ['number'] : // tuple + array indexing + [] + ) + } + export function Pattern(schema: TSchema) { + const keys = Keys(schema) + const pattern = keys.map((key) => key === 'number' ? PatternNumber : key) + return `^(${pattern.join('|')})$` + } + export function Keys(schema: TSchema) { + return [...new Set(Visit(schema))] + } +} diff --git a/examples/prototypes/const.ts b/examples/prototypes/const.ts index 75399a0aa..ef6289879 100644 --- a/examples/prototypes/const.ts +++ b/examples/prototypes/const.ts @@ -26,7 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { Type, ObjectMap, TypeGuard, TSchema, TIntersect, TUnion, TObject, TProperties, TReadonly, AssertProperties, AssertType, AssertRest, Evaluate, ReadonlyUnwrapType } from '@sinclair/typebox' +import { Type, ObjectMap, TypeGuard, TSchema, TIntersect, TUnion, TObject, TProperties, TReadonly, AssertProperties, AssertType, AssertRest, Evaluate, ModifiersRemoveReadonly } from '@sinclair/typebox' // ------------------------------------------------------------------------------------- // TConst @@ -37,7 +37,7 @@ export type TConstArray = T extends [inf : [] // prettier-ignore export type TConstProperties = Evaluate, E> + [K in keyof T]: TConst, E> }>> // prettier-ignore export type TConst = diff --git a/examples/prototypes/evaluate.ts b/examples/prototypes/evaluate.ts index 5fa7b2128..576cf06bb 100644 --- a/examples/prototypes/evaluate.ts +++ b/examples/prototypes/evaluate.ts @@ -44,7 +44,7 @@ import { TTuple, TProperties, TIntersect, - IntersectType, + IntersectTypeResolve, TUnion, TNever } from '@sinclair/typebox' @@ -75,7 +75,7 @@ export type TEvaluateArray = T extends [infer L, ...infer [] // prettier-ignore export type TEvaluate = - T extends TIntersect ? IntersectType> : + T extends TIntersect ? IntersectTypeResolve> : T extends TUnion ? TUnion> : T extends TConstructor ? TConstructor, TEvaluate> : T extends TFunction ? TFunction, TEvaluate> : diff --git a/src/typebox.ts b/src/typebox.ts index 9fadd4219..7c972027f 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -44,6 +44,10 @@ export const PatternBooleanExact = `^${PatternBoolean}$` export const PatternNumberExact = `^${PatternNumber}$` export const PatternStringExact = `^${PatternString}$` // -------------------------------------------------------------------------- +// Into +// -------------------------------------------------------------------------- +export const Into = (func: () => T): T => func() +// -------------------------------------------------------------------------- // Helpers // -------------------------------------------------------------------------- export type TupleToIntersect = T extends [infer I] ? I : T extends [infer I, ...infer R] ? I & TupleToIntersect : never @@ -70,93 +74,6 @@ export type TReadonlyOptional = TOptional & TReadonly export type TReadonly = T & { [Readonly]: 'Readonly' } export type TOptional = T & { [Optional]: 'Optional' } // -------------------------------------------------------------------------- -// Readonly Unwrap -// -------------------------------------------------------------------------- - -export type ReadonlyUnwrapType = - T extends TReadonly ? ReadonlyUnwrapType : - T extends TOptional ? TOptional> : - T -// prettier-ignore -export type ReadonlyUnwrapRest = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? L extends TReadonly - ? [ReadonlyUnwrapType, ...ReadonlyUnwrapRest] - : [L, ...ReadonlyUnwrapRest] - : [] -// -------------------------------------------------------------------------- -// Optional Unwrap -// -------------------------------------------------------------------------- -// prettier-ignore -export type OptionalUnwrapType = - T extends TReadonly ? TReadonly> : - T extends TOptional ? OptionalUnwrapType : - T -// prettier-ignore -export type OptionalUnwrapRest = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? L extends TOptional - ? [OptionalUnwrapType, ...OptionalUnwrapRest] - : [L, ...OptionalUnwrapRest] - : [] -// ------------------------------------------------------------------ -// DistinctRest -// ------------------------------------------------------------------ -// prettier-ignore -export type DistinctRestIncludes = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? C extends L - ? true - : DistinctRestIncludes - : false -// prettier-ignore -export type DistinctRest = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? DistinctRestIncludes extends false - ? DistinctRest - : DistinctRest - : Acc -// -------------------------------------------------------------------------- -// IntersectType -// -------------------------------------------------------------------------- -// prettier-ignore -export type IntersectTypeIsOptional = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? L extends TOptional - ? IntersectTypeIsOptional - : false - : true -// prettier-ignore -export type IntersectTypeOptional> = - IntersectTypeIsOptional extends true - ? TOptional> - : TIntersect -// prettier-ignore -export type IntersectType = //extends TSchema[] = DistinctRest> = - D extends [] ? TNever : - D extends [TSchema] ? T[0] : - IntersectTypeOptional -// -------------------------------------------------------------------------- -// UnionType -// -------------------------------------------------------------------------- -// prettier-ignore -export type UnionTypeIsOptional = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? L extends TOptional - ? true - : UnionTypeIsOptional - : false -// prettier-ignore -export type UnionTypeOptional> = - UnionTypeIsOptional extends true - ? TOptional> - : TUnion -// prettier-ignore -export type UnionType = // = DistinctRest> = - D extends [] ? TNever : - D extends [TSchema] ? T[0] : - UnionTypeOptional -// -------------------------------------------------------------------------- // TSchema // -------------------------------------------------------------------------- export interface SchemaOptions { @@ -388,12 +305,12 @@ export type TExtends = UnionType }[T]>>> +export type TExcludeTemplateLiteralResult = UnionType.Resolve }[T]>>> export type TExcludeTemplateLiteral = Exclude, Static> extends infer S ? TExcludeTemplateLiteralResult> : never // prettier-ignore export type TExcludeArray = AssertRest> extends Static ? never : T[K] -}[number]>> extends infer R extends TSchema[] ? UnionType : never +}[number]>> extends infer R extends TSchema[] ? UnionType.Resolve : never // prettier-ignore export type TExclude = T extends TTemplateLiteral ? TExcludeTemplateLiteral : @@ -402,12 +319,12 @@ export type TExclude = // -------------------------------------------------------------------------- // TExtract // -------------------------------------------------------------------------- -export type TExtractTemplateLiteralResult = UnionType }[T]>>> +export type TExtractTemplateLiteralResult = UnionType.Resolve }[T]>>> export type TExtractTemplateLiteral = Extract, Static> extends infer S ? TExtractTemplateLiteralResult> : never // prettier-ignore export type TExtractArray = AssertRest> extends Static ? T[K] : never - }[number]>> extends infer R extends TSchema[] ? UnionType : never + }[number]>> extends infer R extends TSchema[] ? UnionType.Resolve : never // prettier-ignore export type TExtract = T extends TTemplateLiteral ? TExtractTemplateLiteral : @@ -446,7 +363,7 @@ export type TIndexer = // TIndex // ------------------------------------------------------------------ export type TIndexProperty = K extends keyof T ? T[K] : TNever -export type TIndexTuple = K extends keyof T ? T[K] : K extends 'number' ? UnionType : TNever +export type TIndexTuple = K extends keyof T ? T[K] : K extends 'number' ? UnionType.Resolve : TNever export type TIndexArray = K extends number ? T : TNever // prettier-ignore export type TIndexRest = @@ -460,8 +377,8 @@ export type TIndexRest = // prettier-ignore export type TIndexKey = T extends TRecursive ? TIndexKey : - T extends TIntersect ? IntersectType> : - T extends TUnion ? UnionType> : + T extends TIntersect ? IntersectType.Resolve> : + T extends TUnion ? UnionType.Resolve> : T extends TTuple ? TIndexTuple : T extends TArray ? TIndexArray : T extends TObject ? TIndexProperty : @@ -472,7 +389,7 @@ export type TIndexKeys = ? [TIndexKey, ...TIndexKeys] : [] -export type TIndex = UnionType> +export type TIndex = UnionType.Resolve> // -------------------------------------------------------------------------- // TIntrinsic // -------------------------------------------------------------------------- @@ -561,7 +478,7 @@ export type TKeyOf = ( T extends TArray ? [TNumber] : T extends TRecord ? [K] : [] -) extends infer R ? UnionType> : never +) extends infer R ? UnionType.Resolve> : never // -------------------------------------------------------------------------- // TLiteral // -------------------------------------------------------------------------- @@ -866,7 +783,7 @@ export type TTemplateLiteralDslParserUnionLiteral = T extends `${infer L}|${infer R}` ? [TLiteral>, ...TTemplateLiteralDslParserUnionLiteral] : T extends `${infer L}` ? [TLiteral>] : [] -export type TTemplateLiteralDslParserUnion = UnionType> +export type TTemplateLiteralDslParserUnion = UnionType.Resolve> // prettier-ignore export type TTemplateLiteralDslParserTerminal = T extends 'boolean' ? TBoolean : @@ -1002,7 +919,7 @@ export type TUnionLiteralKeyRest[]>> // TUnion // -------------------------------------------------------------------------- // prettier-ignore -export type TUnionTemplateLiteral> = Ensure }[S]>, TLiteral[]>>> +export type TUnionTemplateLiteral> = Ensure }[S]>, TLiteral[]>>> export interface TUnion extends TSchema { [Kind]: 'Union' static: { [K in keyof T]: T[K] extends TSchema ? Static : never }[number] @@ -1126,6 +1043,19 @@ export namespace FormatRegistry { return map.get(format) } } +// ------------------------------------------------------------------ +// 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) + } +} // -------------------------------------------------------------------------- // ValueGuard // -------------------------------------------------------------------------- @@ -1651,6 +1581,139 @@ export namespace TypeGuard { ) } } +// ---------------------------------------------------------------- +// Modifiers +// ---------------------------------------------------------------- +// prettier-ignore +export namespace Modifiers { + // ---------------------------------------------------------------- + // isOptionalIntersect + // ---------------------------------------------------------------- + export type IsOptionalIntersect = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? L extends TOptional + ? IsOptionalIntersect + : false + : true + // IsOptional + function IsOptionalIntersect(T: TSchema[]): boolean { + const [L, ...R] = T + return T.length > 0 ? TypeGuard.TOptional(L) ? IsOptionalIntersect(R) : false : true + } + // ---------------------------------------------------------------- + // IsOptionalUnion + // ---------------------------------------------------------------- + export type IsOptionalUnion = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] ? + L extends TOptional + ? true + : IsOptionalUnion + : false + function IsOptionalUnion(T: TSchema[]): boolean { + const [L, ...R] = T + return T.length > 0 ? TypeGuard.TOptional(L) ? true : IsOptionalUnion(R) : false + } + // ---------------------------------------------------------------- + // RemoveReadonly + // ---------------------------------------------------------------- + export type RemoveReadonlyRest = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? L extends TReadonly + ? [RemoveReadonly, ...RemoveReadonlyRest] + : [L, ...RemoveReadonlyRest] + : [] + export type RemoveReadonly = + T extends TReadonly ? RemoveReadonly : + T extends TOptional ? TOptional> : + T + export function RemoveReadonlyRest(T: TSchema[]) { + return T.map(T => RemoveReadonly(T)) + } + export function RemoveReadonly(T: TSchema): TSchema { + return Discard.Keys(T, [Optional]) as TSchema + } + // ---------------------------------------------------------------- + // RemoveOptional + // ---------------------------------------------------------------- + export type RemoveOptionalRest = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? L extends TOptional + ? [RemoveOptional, ...RemoveOptionalRest] + : [L, ...RemoveOptionalRest] + : [] + export type RemoveOptional = + T extends TReadonly ? TReadonly> : + T extends TOptional ? RemoveOptional : + T + export function RemoveOptionalRest(T: TSchema[]) { + return T.map(T => RemoveOptional(T)) + } + export function RemoveOptional(T: TSchema): TSchema { + return Discard.Keys(T, [Optional]) as TSchema + } + // ---------------------------------------------------------------- + // ResolveOptionalIntersect + // ---------------------------------------------------------------- + export type ResolveOptionalIntersect = + IsOptionalIntersect extends true + ? TOptional>> + : TIntersect + export function ResolveOptionalIntersect(T: TSchema[]): TIntersect { + const D = RemoveOptionalRest(T) + return IsOptionalIntersect(T) + ? Type.Optional(Type.Intersect(D)) + : Type.Intersect(D) + } + // ---------------------------------------------------------------- + // ResolveOptionalUnion + // ---------------------------------------------------------------- + export type ResolveOptionalUnion = + IsOptionalUnion extends true + ? TOptional>> + : TUnion + export function ResolveOptionalUnion(T: TSchema[]): TUnion{ + const D = RemoveOptionalRest(T) + return IsOptionalUnion(T) + ? Type.Optional(Type.Union(D)) + : Type.Union(D) + } +} +// ------------------------------------------------------------------ +// Intersect +// ------------------------------------------------------------------ +// prettier-ignore +export namespace IntersectType { + export type Resolve = ( + T extends [] ? TNever : + T extends [TSchema] ? T[0] : + Modifiers.ResolveOptionalIntersect + ) + export function Resolve(T: TSchema[]): TIntersect | TSchema { + return ( + T.length === 0 ? Type.Never() : + T.length === 1 ? T[0] : + Modifiers.ResolveOptionalIntersect(T) + ) + } +} +// ------------------------------------------------------------------ +// Union +// ------------------------------------------------------------------ +// prettier-ignore +export namespace UnionType { + export type Resolve = ( + T extends [] ? TNever : + T extends [TSchema] ? T[0] : + Modifiers.ResolveOptionalUnion + ) + export function Resolve(T: TSchema[]): TUnion | TSchema { + return ( + T.length === 0 ? Type.Never() : + T.length === 1 ? T[0] : + Modifiers.ResolveOptionalUnion(T) + ) + } +} // -------------------------------------------------------------------------- // ExtendsUndefined // -------------------------------------------------------------------------- @@ -2321,10 +2384,9 @@ export namespace TypeExtends { } } // -------------------------------------------------------------------------- -// TypeClone +// Clone // -------------------------------------------------------------------------- -/** Specialized Clone for Types */ -export namespace TypeClone { +export namespace Clone { function ArrayType(value: unknown[]) { return (value as any).map((value: unknown) => Visit(value as any)) } @@ -2339,36 +2401,66 @@ export namespace TypeClone { const clonedSymbols = Object.getOwnPropertySymbols(value).reduce((acc, key) => ({ ...acc, [key]: Visit(value[key as any]) }), {}) return { ...clonedProperties, ...clonedSymbols } } + // prettier-ignore function Visit(value: unknown): any { - return ValueGuard.IsArray(value) ? ArrayType(value) : ValueGuard.IsDate(value) ? DateType(value) : ValueGuard.IsUint8Array(value) ? Uint8ArrayType(value) : ValueGuard.IsObject(value) ? ObjectType(value) : value + return ( + ValueGuard.IsArray(value) ? ArrayType(value) : + ValueGuard.IsDate(value) ? DateType(value) : + ValueGuard.IsUint8Array(value) ? Uint8ArrayType(value) : + ValueGuard.IsObject(value) ? ObjectType(value) : + value + ) + } + /** Clones a value */ + export function Value(value: T): T { + return Visit(value) } +} +// -------------------------------------------------------------------------- +// TypeClone +// -------------------------------------------------------------------------- +/** Specialized Clone for Types */ +export namespace TypeClone { /** Clones a Rest */ export function Rest(schemas: [...T]): T { return schemas.map((schema) => Type(schema)) as T } /** Clones a Type */ export function Type(schema: T, options: SchemaOptions = {}): T { - return { ...Visit(schema), ...options } + return { ...Clone.Value(schema), ...options } } } +// ------------------------------------------------------------------ +// Distinct +// ------------------------------------------------------------------ +// prettier-ignore +export type TDistinctIncludes = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? C extends L + ? true + : TDistinctIncludes + : false +// prettier-ignore +export type TDistinct = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? TDistinctIncludes extends false + ? TDistinct + : TDistinct + : Acc -// -------------------------------------------------------------------------- -// DistinctRest -// -------------------------------------------------------------------------- -export namespace DistinctRest { +export namespace Distinct { function DistinctIncludes(schemas: TSchema[], candidate: TSchema) { return schemas.find((schema) => TypeExtends.Extends(candidate, schema) === TypeExtendsResult.True) !== undefined } // prettier-ignore - export function Distinct(schemas: [...T]): DistinctRest { + export function Distinct(schemas: [...T]): TDistinct { return schemas.reduce((acc, candidate) => { return !DistinctIncludes(acc, candidate) ? [...acc, candidate] : [...acc] - }, [] as TSchema[]) as DistinctRest + }, [] as TSchema[]) as TDistinct } } - // -------------------------------------------------------------------------- // Intrinsic // -------------------------------------------------------------------------- @@ -2580,7 +2672,6 @@ export namespace Accessor { schema[Kind] === 'Tuple' ? TTuple(schema as TTuple, key) : Type.Never() ) - } export function Resolve(schema: TSchema, keys: TPropertyKey[], options: SchemaOptions = {}): TSchema { const resolved = keys.map((key) => Visit(schema, key.toString()))