diff --git a/examples/index.ts b/examples/index.ts index de220bb85..d73c23a95 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -2,102 +2,54 @@ // Accessor // -------------------------------------------------------------------------- -import { Type, Static, Distinct, 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" +import { + Type, + Into, + Static, + Distinct, + Optional, + TOptional, + TReadonly, + TObject, + TTuple, + TIntersect, + TUnion, + Kind, + TSchema, + TypeClone, + TypeGuard, + ValueGuard, + TPropertyKey, + SchemaOptions, + TProperties, + TNever, + TRecursive, + TArray, + Discard, + TNumber, + TString, + TBoolean, + IntersectType, + UnionType, + TemplateLiteralParser, + TemplateLiteralFinite, + TemplateLiteralResolver, + TTemplateLiteral, + TLiteral, + TInteger, + TLiteralValue, + IsTemplateLiteralFiniteCheck, + UnionToTuple, + PatternNumber +} from '@sinclair/typebox' + +const T = Type.Union([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Number() }) +]) +const I = Type.Index(T, ['x']) + +console.log(I) + +type T = { x: 1 } | { y: 1 } -const T = Type.Object({ - x: Type.Number(), - y: Type.Number(), - z: Type.Number() -}) -type T = typeof T - -type A = Distinct.Resolve<[TString, T]> -const A = Distinct.Resolve([Type.String(), T]) -console.log(A) - -// ------------------------------------------------------------------ -// 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 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] -// : [] - -// 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 Tuple(T: TSchema[], K: PropertyKey): TSchema { -// return K in T ? T[K as number] : K === 'number' ? Union.Resolve(T) : Type.Never() -// } -// function Array(T: TSchema, K: PropertyKey): TSchema { -// return K === 'number' ? T : Type.Never() -// } -// 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 Key(T: TSchema, K: PropertyKey) { -// return ( -// 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() -// ) -// } -// function Keys(T: TSchema, K: PropertyKey[]): TSchema[] { -// const [L, ...R] = K -// return (K.length > 0) -// ? [Key(T, L), ...Keys(T, R)] -// : [] -// } -// export function Resolve(schema: TSchema, keys: PropertyKey[]) { -// return Union.Resolve(Keys(schema, keys)) -// } -// } - -// 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(A) \ No newline at end of file diff --git a/examples/next/accessor.ts b/examples/next/accessor.ts index b624ce3ec..73918379b 100644 --- a/examples/next/accessor.ts +++ b/examples/next/accessor.ts @@ -1,101 +1,113 @@ -// // -------------------------------------------------------------------------- -// // Accessor -// // -------------------------------------------------------------------------- +// ------------------------------------------------------------------ +// Accessor +// ------------------------------------------------------------------ -// import { Type, Optional, TObject, TTuple, TIntersect, TUnion, Kind, TSchema, TypeClone, TypeGuard, ValueGuard, TPropertyKey, SchemaOptions, TProperties, TNever, IntersectType, TRecursive, TArray, UnionTypeResolve } from "@sinclair/typebox" +import { IntersectType, Into, TArray, TIntersect, TNever, TObject, TProperties, TRecursive, TSchema, TTuple, TUnion, Type, TypeGuard, UnionType } 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 +// prettier-ignore +export namespace Accessor { + // ---------------------------------------------------------------- + // Property + // ---------------------------------------------------------------- + export type Property = + K extends keyof T + ? T[K] + : TNever + export function Property(T: TProperties, K: PropertyKey): TSchema { + return K in T + ? T[K as string] + : Type.Never() + } + // ---------------------------------------------------------------- + // Tuple + // ---------------------------------------------------------------- + export type Tuple = ( + K extends keyof T ? T[K] : + K extends 'number' ? UnionType.Resolve : + TNever + ) + export function Tuple(T: TSchema[], K: PropertyKey): TSchema { + return ( + K in T ? T[K as number] : + K === 'number' ? UnionType.Resolve(T) : + Type.Never() + ) + } + // ---------------------------------------------------------------- + // Array + // ---------------------------------------------------------------- + export type Array = + K extends number + ? T + : TNever + export function Array(T: TSchema, K: PropertyKey): TSchema { + return K === 'number' + ? T + : Type.Never() + } + // ---------------------------------------------------------------- + // Rest + // ---------------------------------------------------------------- + export type Rest = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? Key extends infer N extends TSchema + ? N extends TNever + ? [...Rest] + : [N, ...Rest] + : [] + : [] + 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)] + }) + : [] + } + // ---------------------------------------------------------------- + // Key + // ---------------------------------------------------------------- + export type Key = ( + T extends TRecursive ? Key : + T extends TIntersect ? IntersectType.Resolve> : + T extends TUnion ? UnionType.Resolve> : + T extends TTuple ? Tuple : + T extends TArray ? Array : + T extends TObject ? Property : + TNever + ) + export function Key(T: TSchema, K: PropertyKey) { + return ( + TypeGuard.TIntersect(T) ? IntersectType.Resolve(Rest(T.allOf, K)) : + TypeGuard.TUnion(T) ? UnionType.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() + ) + } + // ---------------------------------------------------------------- + // Keys + // ---------------------------------------------------------------- + export type Keys = + K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] + ? [Key, ...Keys] + : [] + export function Keys(T: TSchema, K: PropertyKey[]): TSchema[] { + const [L, ...R] = K + return (K.length > 0) + ? [Key(T, L), ...Keys(T, R)] + : [] + } + // ---------------------------------------------------------------- + // Resolve + // ---------------------------------------------------------------- + export type Resolve = + UnionType.Resolve> + export function Resolve(T: TSchema, K: PropertyKey[]) { + return UnionType.Resolve(Keys(T, K)) + } +} \ No newline at end of file diff --git a/examples/next/indexer.ts b/examples/next/indexer.ts index 7c9156938..d1d18e5e6 100644 --- a/examples/next/indexer.ts +++ b/examples/next/indexer.ts @@ -1,55 +1,59 @@ 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 { Type, Accessor, TemplateLiteralParser, TemplateLiteralFinite, TemplateLiteralResolver, TypeGuard, TIntersect, TUnion, TTemplateLiteral, IsTemplateLiteralFiniteCheck, UnionToTuple, Static, TSchema, TLiteralValue, TLiteral, TNumber, TInteger, TBigInt, TString, IndexerOldOptions, 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 - [] +const A = Type.Index(Type.Object({ + x: Type.Number() +}), ['x']) -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))] - } -} +// // -------------------------------------------------------------------------- +// // 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/src/compiler/compiler.ts b/src/compiler/compiler.ts index f0f109deb..17fc732cd 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -253,11 +253,11 @@ export namespace TypeCompiler { function* TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: string): IterableIterator { const check1 = schema.allOf.map((schema: Types.TSchema) => CreateExpression(schema, references, value)).join(' && ') if (schema.unevaluatedProperties === false) { - const keyCheck = CreateVariable(`${new RegExp(Types.Indexer.Pattern(schema))};`) + const keyCheck = CreateVariable(`${new RegExp(Types.IndexerOld.Pattern(schema))};`) const check2 = `Object.getOwnPropertyNames(${value}).every(key => ${keyCheck}.test(key))` yield `(${check1} && ${check2})` } else if (Types.TypeGuard.TSchema(schema.unevaluatedProperties)) { - const keyCheck = CreateVariable(`${new RegExp(Types.Indexer.Pattern(schema))};`) + const keyCheck = CreateVariable(`${new RegExp(Types.IndexerOld.Pattern(schema))};`) const check2 = `Object.getOwnPropertyNames(${value}).every(key => ${keyCheck}.test(key) || ${CreateExpression(schema.unevaluatedProperties, references, `${value}[key]`)})` yield `(${check1} && ${check2})` } else { diff --git a/src/errors/errors.ts b/src/errors/errors.ts index 5e413d582..22344d7e4 100644 --- a/src/errors/errors.ts +++ b/src/errors/errors.ts @@ -262,7 +262,7 @@ function* TIntersect(schema: Types.TIntersect, references: Types.TSchema[], path } } if (schema.unevaluatedProperties === false) { - const keyCheck = new RegExp(Types.Indexer.Pattern(schema)) + const keyCheck = new RegExp(Types.IndexerOld.Pattern(schema)) for (const valueKey of Object.getOwnPropertyNames(value)) { if (!keyCheck.test(valueKey)) { yield Create(ValueErrorType.IntersectUnevaluatedProperties, schema, `${path}/${valueKey}`, value) @@ -270,7 +270,7 @@ function* TIntersect(schema: Types.TIntersect, references: Types.TSchema[], path } } if (typeof schema.unevaluatedProperties === 'object') { - const keyCheck = new RegExp(Types.Indexer.Pattern(schema)) + const keyCheck = new RegExp(Types.IndexerOld.Pattern(schema)) for (const valueKey of Object.getOwnPropertyNames(value)) { if (!keyCheck.test(valueKey)) { const next = Visit(schema.unevaluatedProperties, references, `${path}/${valueKey}`, value[valueKey]).next() diff --git a/src/typebox.ts b/src/typebox.ts index c39b8ece4..a6372c670 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -46,7 +46,9 @@ export const PatternStringExact = `^${PatternString}$` // -------------------------------------------------------------------------- // Into // -------------------------------------------------------------------------- -export function Into(func: () => T): T { return func() } +export function Into(func: () => T): T { + return func() +} // -------------------------------------------------------------------------- // Helpers // -------------------------------------------------------------------------- @@ -1581,6 +1583,46 @@ export namespace TypeGuard { ) } } +// ------------------------------------------------------------------ +// Distinct +// ------------------------------------------------------------------ +// prettier-ignore +export namespace Distinct { + // ---------------------------------------------------------------- + // Includes + // ---------------------------------------------------------------- + export type Includes = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? C extends L + ? true + : Includes + : false + export function Includes(T: TSchema[], C: TSchema): boolean { + const [L, ...R] = T + return T.length > 0 + ? TypeExtends.Extends(C, L) === TypeExtendsResult.True + ? true + : Includes(R, C) + : false + } + // ---------------------------------------------------------------- + // Resolve + // ---------------------------------------------------------------- + export type Resolve = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? Includes extends false + ? Resolve + : Resolve + : Acc + export function Resolve(T: TSchema[], Acc: TSchema[] = []): TSchema[] { + const [L, ...R] = T + return T.length > 0 + ? Includes(Acc, L) === false + ? Resolve(R, [...Acc, L]) + : Resolve(R, [...Acc]) + : Acc + } +} // ---------------------------------------------------------------- // Modifiers // ---------------------------------------------------------------- @@ -1654,28 +1696,28 @@ export namespace Modifiers { // ---------------------------------------------------------------- // ResolveOptionalIntersect // ---------------------------------------------------------------- - export type ResolveOptionalIntersect = + export type ResolveOptionalIntersect> = IsOptionalIntersect extends true - ? TOptional>> + ? TOptional> : TIntersect export function ResolveOptionalIntersect(T: TSchema[]): TIntersect { const D = RemoveOptionalRest(T) return IsOptionalIntersect(T) ? Type.Optional(Type.Intersect(D)) - : Type.Intersect(D) + : Type.Intersect(T) } // ---------------------------------------------------------------- // ResolveOptionalUnion // ---------------------------------------------------------------- - export type ResolveOptionalUnion = + export type ResolveOptionalUnion> = IsOptionalUnion extends true - ? TOptional>> + ? TOptional> : TUnion export function ResolveOptionalUnion(T: TSchema[]): TUnion{ const D = RemoveOptionalRest(T) return IsOptionalUnion(T) ? Type.Optional(Type.Union(D)) - : Type.Union(D) + : Type.Union(T) } } // ------------------------------------------------------------------ @@ -1728,16 +1770,13 @@ export namespace ExtendsUndefined { export function TNot(schema: TNot) { return !Check(schema.not) } - export function TUndefined(schema: TUndefined) { - return true - } // prettier-ignore export function Check(schema: TSchema): boolean { return ( schema[Kind] === 'Intersect' ? TIntersect(schema as TIntersect) : schema[Kind] === 'Union' ? TUnion(schema as TUnion) : schema[Kind] === 'Not' ? TNot(schema as TNot) : - schema[Kind] === 'Undefined' ? TUndefined(schema as TUndefined) : + schema[Kind] === 'Undefined' ? true : false ) } @@ -2430,46 +2469,6 @@ export namespace TypeClone { return { ...Clone.Value(schema), ...options } } } -// ------------------------------------------------------------------ -// Distinct -// ------------------------------------------------------------------ -// prettier-ignore -export namespace Distinct { - // ---------------------------------------------------------------- - // Includes - // ---------------------------------------------------------------- - export type Includes = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? C extends L - ? true - : Includes - : false - export function Includes(T: TSchema[], C: TSchema): boolean { - const [L, ...R] = T - return T.length > 0 - ? TypeExtends.Extends(C, L) === TypeExtendsResult.True - ? true - : Includes(R, C) - : false - } - // ---------------------------------------------------------------- - // Resolve - // ---------------------------------------------------------------- - export type Resolve = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? Includes extends false - ? Resolve - : Resolve - : Acc - export function Resolve(T: TSchema[], Acc: TSchema[] = []): TSchema[] { - const [L, ...R] = T - return T.length > 0 - ? Includes(Acc, L) === false - ? Resolve(R, [...Acc, L]) - : Resolve(R, [...Acc]) - : Acc - } -} // -------------------------------------------------------------------------- // Intrinsic // -------------------------------------------------------------------------- @@ -2477,11 +2476,11 @@ export namespace Distinct { export namespace Intrinsic { function Uncapitalize(value: string): string { const [first, rest] = [value.slice(0, 1), value.slice(1)] - return `${first.toLowerCase()}${rest}` + return [first.toLowerCase(), rest].join('') } function Capitalize(value: string): string { const [first, rest] = [value.slice(0, 1), value.slice(1)] - return `${first.toUpperCase()}${rest}` + return [first.toUpperCase(), rest].join('') } function Uppercase(value: string): string { return value.toUpperCase() @@ -2563,15 +2562,15 @@ export namespace ObjectMap { // -------------------------------------------------------------------------- // Indexer // -------------------------------------------------------------------------- -export interface IndexerOptions { +export interface IndexerOldOptions { includePatterns: boolean } // prettier-ignore -export namespace Indexer { +export namespace IndexerOld { function UnwrapPattern(key: string) { return key[0] === '^' && key[key.length - 1] === '$' ? key.slice(1, key.length - 1) : key } - function TTemplateLiteral(schema: TTemplateLiteral, options: IndexerOptions): string[] { + function TTemplateLiteral(schema: TTemplateLiteral, options: IndexerOldOptions): string[] { const expression = TemplateLiteralParser.ParseExact(schema.pattern) const finite = TemplateLiteralFinite.Check(expression) if (!finite && options.includePatterns) return [schema.pattern] @@ -2579,26 +2578,26 @@ export namespace Indexer { const resolved = TemplateLiteralResolver.Resolve(schema) return TypeGuard.TUnionLiteral(resolved) ? resolved.anyOf.map((schema) => schema.const.toString()) : [] } - function TIntersect(schema: TIntersect, options: IndexerOptions): string[] { + function TIntersect(schema: TIntersect, options: IndexerOldOptions): string[] { return schema.allOf.reduce((acc, schema) => [...acc, ...Visit(schema, options)], [] as string[]) } - function TUnion(schema: TUnion, options: IndexerOptions): string[] { + function TUnion(schema: TUnion, options: IndexerOldOptions): string[] { const sets = schema.anyOf.map((inner) => Visit(inner, options)) 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[] { + function TObject(schema: TObject, options: IndexerOldOptions): string[] { return Object.getOwnPropertyNames(schema.properties) } - function TRecord(schema: TRecord, options: IndexerOptions): string[] { + function TRecord(schema: TRecord, options: IndexerOldOptions): string[] { return options.includePatterns ? Object.getOwnPropertyNames(schema.patternProperties) : [] } - function TNumber(schema: TNumber, options: IndexerOptions): string[] { + function TNumber(schema: TNumber, options: IndexerOldOptions): string[] { return ['number'] } - function TInteger(schema: TInteger, options: IndexerOptions): string[] { + function TInteger(schema: TInteger, options: IndexerOldOptions): string[] { return ['number'] } - function Visit(schema: TSchema, options: IndexerOptions): string[] { + function Visit(schema: TSchema, options: IndexerOldOptions): string[] { return ( TypeGuard.TTemplateLiteral(schema) ? TTemplateLiteral(schema, options) : TypeGuard.TIntersect(schema) ? TIntersect(schema, options) : @@ -2617,74 +2616,183 @@ export namespace Indexer { return `^(${pattern.join('|')})$` } /** Resolves an array of keys derived from this schema */ - export function Keys(schema: TSchema, options: IndexerOptions): string[] { + export function Keys(schema: TSchema, options: IndexerOldOptions): string[] { return [...new Set(Visit(schema, options))] } } -// -------------------------------------------------------------------------- -// Accessor -// -------------------------------------------------------------------------- +// ------------------------------------------------------------------ +// Indexer +// ------------------------------------------------------------------ // prettier-ignore -export namespace Accessor { - function OptionalUnwrap(schema: TSchema[]): TSchema[] { - return schema.map((schema) => { - const { [Optional]: _, ...clone } = TypeClone.Type(schema) - return clone - }) +export namespace Indexer { + // TemplateLiteral + export type TemplateLiteral> = ( + F extends true + ? UnionToTuple> + : [] + ) + export function TTemplateLiteral(T: TTemplateLiteral): string[] { + const E = TemplateLiteralParser.ParseExact(T.pattern) + const F = TemplateLiteralFinite.Check(E) + const S = TemplateLiteralResolver.Resolve(T) + return F === true + ? TypeGuard.TUnionLiteral(S) + ? S.anyOf.map(S => S.const.toString()) + : [] + : [] } - function IsIntersectOptional(schema: TSchema[]): boolean { - return schema.every((schema) => TypeGuard.TOptional(schema)) + // Union + export type Union = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? [...Resolve, ...Union] + : [] + export function Union(T: TSchema[]): string[] { + const [L, ...R] = T + return (T.length > 0) + ? [...Resolve(L), ...Union(R)] + : [] } - function IsUnionOptional(schema: TSchema[]): boolean { - return schema.some((schema) => TypeGuard.TOptional(schema)) + // Literal + export type Literal = + T extends PropertyKey + ? [`${T}`] + : [] + export function TLiteral(schema: TLiteral): string[] { + return [schema.const.toString()] } - function ResolveIntersect(schema: TIntersect): TSchema { - return IsIntersectOptional(schema.allOf) ? Type.Optional(Type.Intersect(OptionalUnwrap(schema.allOf))) : schema + // Resolve + export type Resolve = + T extends TTemplateLiteral ? TemplateLiteral : + T extends TUnion ? Union : + T extends TLiteral ? Literal : + T extends TNumber ? ['number'] : // tuple + array indexing + T extends TInteger ? ['number'] : // tuple + array indexing + [] + export function Resolve(schema: TSchema): string[] { + return [...new Set(( + 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 + [] + ))] } - function ResolveUnion(schema: TUnion): TSchema { - return IsUnionOptional(schema.anyOf) ? Type.Optional(Type.Union(OptionalUnwrap(schema.anyOf))) : schema + // ResolvePattern + export function ResolvePattern(schema: TSchema): string { + const keys = Resolve(schema) + const pattern = keys.map((key) => key === 'number' ? PatternNumber : key) + return `^(${pattern.join('|')})$` } - function ResolveOptional(schema: TSchema) { +} +// ------------------------------------------------------------------ +// Accessor +// ------------------------------------------------------------------ +// prettier-ignore +export namespace Accessor { + // ---------------------------------------------------------------- + // Property + // ---------------------------------------------------------------- + export type Property = + K extends keyof T + ? T[K] + : TNever + export function Property(T: TProperties, K: PropertyKey): TSchema { + return K in T + ? T[K as string] + : Type.Never() + } + // ---------------------------------------------------------------- + // Tuple + // ---------------------------------------------------------------- + export type Tuple = ( + K extends keyof T ? T[K] : + K extends 'number' ? UnionType.Resolve : + TNever + ) + export function Tuple(T: TSchema[], K: PropertyKey): TSchema { return ( - schema[Kind] === 'Intersect' ? ResolveIntersect(schema as TIntersect) : - schema[Kind] === 'Union' ? ResolveUnion(schema as TUnion) : - schema + K in T ? T[K as number] : + K === 'number' ? UnionType.Resolve(T) : + Type.Never() ) } - 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]) + // ---------------------------------------------------------------- + // Array + // ---------------------------------------------------------------- + export type Array = + K extends number + ? T + : TNever + export function Array(T: TSchema, K: PropertyKey): TSchema { + return K === 'number' + ? T + : Type.Never() } - 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 + // ---------------------------------------------------------------- + // Rest + // ---------------------------------------------------------------- + export type Rest = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? Key extends infer N extends TSchema + ? N extends TNever + ? [...Rest] + : [N, ...Rest] + : [] + : [] + 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, key: string): TSchema { + // ---------------------------------------------------------------- + // Key + // ---------------------------------------------------------------- + export type Key = ( + T extends TRecursive ? Key : + T extends TIntersect ? IntersectType.Resolve> : + T extends TUnion ? UnionType.Resolve> : + T extends TTuple ? Tuple : + T extends TArray ? Array : + T extends TObject ? Property : + TNever + ) + export function Key(T: TSchema, K: PropertyKey) { 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) : + TypeGuard.TIntersect(T) ? IntersectType.Resolve(Rest(T.allOf, K)) : + TypeGuard.TUnion(T) ? UnionType.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() ) } - 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)) + // ---------------------------------------------------------------- + // Keys + // ---------------------------------------------------------------- + export type Keys = + K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] + ? [Key, ...Keys] + : [] + export function Keys(T: TSchema, K: PropertyKey[]): TSchema[] { + const [L, ...R] = K + return (K.length > 0) + ? [Key(T, L), ...Keys(T, R)] + : [] + } + // ---------------------------------------------------------------- + // Resolve + // ---------------------------------------------------------------- + export type Resolve = + UnionType.Resolve> + export function Resolve(T: TSchema, K: PropertyKey[]) { + return UnionType.Resolve(Keys(T, K)) } } // -------------------------------------------------------------------------- @@ -3109,7 +3217,7 @@ export class JsonTypeBuilder extends TypeBuilder { /** `[Json]` Creates a Composite object type */ public Composite(objects: [...T], options?: ObjectOptions): TComposite { const intersect: any = Type.Intersect(objects, {}) - const keys = Indexer.Keys(intersect, { includePatterns: false }) + const keys = IndexerOld.Keys(intersect, { includePatterns: false }) const properties = keys.reduce((acc, key) => ({ ...acc, [key]: Type.Index(intersect, [key]) }), {} as TProperties) return Type.Object(properties, options) as TComposite } @@ -3159,25 +3267,14 @@ export class JsonTypeBuilder extends TypeBuilder { ) as TExtract } /** `[Json]` Returns an Indexed property type for the given keys */ - public Index>(schema: T, keys: I, options?: SchemaOptions): TIndex + public Index>(T: T, keys: I, options?: SchemaOptions): Accessor.Resolve /** `[Json]` Returns an Indexed property type for the given keys */ - public Index(schema: T, keys: [...K], options?: SchemaOptions): TIndex + public Index(T: T, K: [...K], options?: SchemaOptions): Accessor.Resolve /** `[Json]` Returns an Indexed property type for the given keys */ - public Index(schema: TSchema, unresolved: any, options: SchemaOptions = {}): any { - return ( - TypeGuard.TArray(schema) && TypeGuard.TNumber(unresolved) ? (() => { - return TypeClone.Type(schema.items, options) - })() : - TypeGuard.TTuple(schema) && TypeGuard.TNumber(unresolved) ? (() => { - const items = ValueGuard.IsUndefined(schema.items) ? [] : schema.items - const cloned = items.map((schema) => TypeClone.Type(schema)) - return this.Union(cloned, options) - })() : (() => { - const keys = KeyArrayResolver.Resolve(unresolved) - const clone = TypeClone.Type(schema) - return Accessor.Resolve(clone, keys, options) - })() - ) + public Index(T: TSchema, K: any, options: SchemaOptions = {}): any { + const keys = TypeGuard.TSchema(K) ? Indexer.Resolve(K) : K as string[] + const type = Accessor.Resolve(T, keys) + return TypeClone.Type(type, options) } /** `[Json]` Creates an Integer type */ public Integer(options: NumericOptions = {}): TInteger { @@ -3222,7 +3319,7 @@ export class JsonTypeBuilder extends TypeBuilder { TypeGuard.TArray(schema) ? (() => { return this.Number(options) })() : (() => { - const keys = Indexer.Keys(schema, { includePatterns: false }) + const keys = IndexerOld.Keys(schema, { includePatterns: false }) if (keys.length === 0) return this.Never(options) as TKeyOf const literals = keys.map((key) => this.Literal(key)) return this.Union(literals, options) diff --git a/src/value/check.ts b/src/value/check.ts index 33dde03a7..815670d32 100644 --- a/src/value/check.ts +++ b/src/value/check.ts @@ -164,11 +164,11 @@ function TInteger(schema: Types.TInteger, references: Types.TSchema[], value: an function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any): boolean { const check1 = schema.allOf.every((schema) => Visit(schema, references, value)) if (schema.unevaluatedProperties === false) { - const keyPattern = new RegExp(Types.Indexer.Pattern(schema)) + const keyPattern = new RegExp(Types.IndexerOld.Pattern(schema)) const check2 = Object.getOwnPropertyNames(value).every((key) => keyPattern.test(key)) return check1 && check2 } else if (Types.TypeGuard.TSchema(schema.unevaluatedProperties)) { - const keyCheck = new RegExp(Types.Indexer.Pattern(schema)) + const keyCheck = new RegExp(Types.IndexerOld.Pattern(schema)) const check2 = Object.getOwnPropertyNames(value).every((key) => keyCheck.test(key) || Visit(schema.unevaluatedProperties as Types.TSchema, references, value[key])) return check1 && check2 } else { diff --git a/src/value/transform.ts b/src/value/transform.ts index 9f69ad942..c01f66899 100644 --- a/src/value/transform.ts +++ b/src/value/transform.ts @@ -174,7 +174,7 @@ export namespace DecodeTransform { // prettier-ignore function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any) { if (!IsPlainObject(value) || IsValueType(value)) return Default(schema, value) - const knownKeys = Types.Indexer.Keys(schema, { includePatterns: false }) + const knownKeys = Types.IndexerOld.Keys(schema, { includePatterns: false }) const knownProperties = knownKeys.reduce((value, key) => { return (key in value) ? { ...value, [key]: Visit(Types.Accessor.Resolve(schema, [key]), references, value[key]) } @@ -198,7 +198,7 @@ export namespace DecodeTransform { // prettier-ignore function TObject(schema: Types.TObject, references: Types.TSchema[], value: any) { if (!IsPlainObject(value)) return Default(schema, value) - const knownKeys = Types.Indexer.Keys(schema, { includePatterns: false }) + const knownKeys = Types.IndexerOld.Keys(schema, { includePatterns: false }) const knownProperties = knownKeys.reduce((value, key) => { return (key in value) ? { ...value, [key]: Visit(schema.properties[key], references, value[key]) } @@ -315,7 +315,7 @@ export namespace EncodeTransform { function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: any) { const defaulted = Default(schema, value) if (!IsPlainObject(value) || IsValueType(value)) return defaulted - const knownKeys = Types.Indexer.Keys(schema, { includePatterns: false }) + const knownKeys = Types.IndexerOld.Keys(schema, { includePatterns: false }) const knownProperties = knownKeys.reduce((value, key) => { return key in defaulted ? { ...value, [key]: Visit(Types.Accessor.Resolve(schema, [key]), references, value[key]) } @@ -339,7 +339,7 @@ export namespace EncodeTransform { function TObject(schema: Types.TObject, references: Types.TSchema[], value: any) { const defaulted = Default(schema, value) if (!IsPlainObject(value)) return defaulted - const knownKeys = Types.Indexer.Keys(schema, { includePatterns: false }) + const knownKeys = Types.IndexerOld.Keys(schema, { includePatterns: false }) const knownProperties = knownKeys.reduce((value, key) => { return key in value ? { ...value, [key]: Visit(schema.properties[key], references, value[key]) }