From 5a2dfaee560a1639f782f456c747ecea2c75d07b Mon Sep 17 00:00:00 2001 From: sinclair Date: Sat, 25 Nov 2023 17:24:19 +0900 Subject: [PATCH] Reimplement Index Access --- examples/index.ts | 65 +- examples/next/accessor.ts | 114 --- examples/next/distinct.ts | 33 - examples/next/indexer.ts | 59 -- package.json | 1 + src/compiler/compiler.ts | 5 +- src/errors/errors.ts | 5 +- src/resolvers/counter.ts | 39 + src/resolvers/index.ts | 8 + src/resolvers/indexed/index.ts | 30 + src/resolvers/indexed/indexed-access.ts | 201 ++++ src/resolvers/indexed/indexed-key.ts | 103 ++ src/resolvers/intersect.ts | 46 + src/resolvers/keyof/index.ts | 30 + src/resolvers/keyof/keyof-string.ts | 159 +++ src/resolvers/keyof/keyof.ts | 66 ++ src/resolvers/modifiers.ts | 163 +++ src/resolvers/property.ts | 154 +++ src/resolvers/record/index.ts | 1 + src/resolvers/record/record.ts | 4 + src/resolvers/template-literal/index.ts | 30 + src/resolvers/template-literal/runtime.ts | 315 ++++++ src/resolvers/template-literal/static.ts | 95 ++ src/resolvers/union.ts | 46 + src/tsconfig.json | 2 +- src/typebox.ts | 1118 +-------------------- src/value/check.ts | 5 +- src/value/create.ts | 7 +- src/value/transform.ts | 13 +- test/runtime/type/template/finite.ts | 3 +- test/runtime/type/template/generate.ts | 2 +- test/runtime/type/template/parser.ts | 2 +- tsconfig.json | 3 + 33 files changed, 1577 insertions(+), 1350 deletions(-) delete mode 100644 examples/next/accessor.ts delete mode 100644 examples/next/distinct.ts delete mode 100644 examples/next/indexer.ts create mode 100644 src/resolvers/counter.ts create mode 100644 src/resolvers/index.ts create mode 100644 src/resolvers/indexed/index.ts create mode 100644 src/resolvers/indexed/indexed-access.ts create mode 100644 src/resolvers/indexed/indexed-key.ts create mode 100644 src/resolvers/intersect.ts create mode 100644 src/resolvers/keyof/index.ts create mode 100644 src/resolvers/keyof/keyof-string.ts create mode 100644 src/resolvers/keyof/keyof.ts create mode 100644 src/resolvers/modifiers.ts create mode 100644 src/resolvers/property.ts create mode 100644 src/resolvers/record/index.ts create mode 100644 src/resolvers/record/record.ts create mode 100644 src/resolvers/template-literal/index.ts create mode 100644 src/resolvers/template-literal/runtime.ts create mode 100644 src/resolvers/template-literal/static.ts create mode 100644 src/resolvers/union.ts diff --git a/examples/index.ts b/examples/index.ts index 675955697..16d89ea9d 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -1,58 +1,15 @@ -// -------------------------------------------------------------------------- -// Accessor -// -------------------------------------------------------------------------- -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, - Counter, - IndexAccessResolver, - IndexResolver, - Assert, - UnionToIntersect, - KeyResolver, - Ensure, - KeyOfResolver, -} from '@sinclair/typebox' +import { Type, Static } from '@sinclair/typebox' + +const T = Type.Object({ + x: Type.Number(), + y: Type.String(), + z: Type.Boolean() +}) + +const A = Type.TemplateLiteral([Type.Literal('a'), Type.Union([Type.Literal('b'), Type.Literal('c')])]) + +const R = Type.Index(T, ['x', 'y']) -const R = KeyOfResolver.Resolve(Type.Union([Type.Object({ x: Type.Number(), y: Type.Number() }), Type.Object({ x: Type.Number(), z: Type.Number() })])) -const T = Type.Union([Type.Object({ x: Type.Number() }), Type.Object({ z: Type.Number() })]) -const K = Type.KeyOf(T) diff --git a/examples/next/accessor.ts b/examples/next/accessor.ts deleted file mode 100644 index b52626eb8..000000000 --- a/examples/next/accessor.ts +++ /dev/null @@ -1,114 +0,0 @@ -// ------------------------------------------------------------------ -// Accessor -// ------------------------------------------------------------------ - -import { IntersectType, Into, TArray, TIntersect, TNever, TObject, TProperties, TRecursive, TSchema, TTuple, TUnion, Type, TypeGuard, UnionType } from "@sinclair/typebox" - -// 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/distinct.ts b/examples/next/distinct.ts deleted file mode 100644 index 6bea27e08..000000000 --- a/examples/next/distinct.ts +++ /dev/null @@ -1,33 +0,0 @@ -// ------------------------------------------------------------------ -// 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 deleted file mode 100644 index 715f19fce..000000000 --- a/examples/next/indexer.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { TypeSystem } from '@sinclair/typebox/system' -import { TypeCompiler } from '@sinclair/typebox/compiler' -import { Value, ValuePointer } from '@sinclair/typebox/value' -import { Type, IndexAccessResolver, TemplateLiteralParser, TemplateLiteralFinite, TemplateLiteralResolver, TypeGuard, TIntersect, TUnion, TTemplateLiteral, IsTemplateLiteralFiniteCheck, UnionToTuple, Static, TSchema, TLiteralValue, TLiteral, TNumber, TInteger, TBigInt, TString, PatternNumber } from '@sinclair/typebox' -import { TObject } from '@sinclair/typebox' - -const A = Type.Index(Type.Object({ - x: Type.Number() -}), ['x']) - -// // -------------------------------------------------------------------------- -// // 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/package.json b/package.json index f3c144a3d..a91ff82b7 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "exports": { "./compiler": "./compiler/index.js", "./errors": "./errors/index.js", + "./resolvers": "./resolvers/index.js", "./system": "./system/index.js", "./value/cast": "./value/cast.js", "./value/check": "./value/check.js", diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index fe03f6d2e..38f21d4ef 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -28,6 +28,7 @@ THE SOFTWARE. import { EncodeTransform, DecodeTransform, HasTransform, TransformDecodeCheckError, TransformEncodeCheckError } from '../value/transform' import { IsArray, IsString, IsNumber, IsBigInt } from '../value/guard' +import { KeyOfStringResolver } from '../resolvers' import { Errors, ValueErrorIterator } from '../errors/errors' import { TypeSystemPolicy } from '../system/index' import { Deref } from '../value/deref' @@ -253,11 +254,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.KeyResolver.ResolvePattern(schema))};`) + const keyCheck = CreateVariable(`${new RegExp(KeyOfStringResolver.ResolvePattern(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.KeyResolver.ResolvePattern(schema))};`) + const keyCheck = CreateVariable(`${new RegExp(KeyOfStringResolver.ResolvePattern(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 9f09e1f55..01850c5eb 100644 --- a/src/errors/errors.ts +++ b/src/errors/errors.ts @@ -28,6 +28,7 @@ THE SOFTWARE. import { IsArray, IsUint8Array, IsDate, IsPromise, IsFunction, IsAsyncIterator, IsIterator, IsBoolean, IsNumber, IsBigInt, IsString, IsSymbol, IsInteger, IsNull, IsUndefined } from '../value/guard' import { TypeSystemPolicy, TypeSystemErrorFunction } from '../system/system' +import { KeyOfStringResolver } from '../resolvers' import { Deref } from '../value/deref' import { Hash } from '../value/hash' import * as Types from '../typebox' @@ -262,7 +263,7 @@ function* TIntersect(schema: Types.TIntersect, references: Types.TSchema[], path } } if (schema.unevaluatedProperties === false) { - const keyCheck = new RegExp(Types.KeyResolver.ResolvePattern(schema)) + const keyCheck = new RegExp(KeyOfStringResolver.ResolvePattern(schema)) for (const valueKey of Object.getOwnPropertyNames(value)) { if (!keyCheck.test(valueKey)) { yield Create(ValueErrorType.IntersectUnevaluatedProperties, schema, `${path}/${valueKey}`, value) @@ -270,7 +271,7 @@ function* TIntersect(schema: Types.TIntersect, references: Types.TSchema[], path } } if (typeof schema.unevaluatedProperties === 'object') { - const keyCheck = new RegExp(Types.KeyResolver.ResolvePattern(schema)) + const keyCheck = new RegExp(KeyOfStringResolver.ResolvePattern(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/resolvers/counter.ts b/src/resolvers/counter.ts new file mode 100644 index 000000000..ea0710eb2 --- /dev/null +++ b/src/resolvers/counter.ts @@ -0,0 +1,39 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +export namespace Counter { + export type Base = { m: '9'; t: '01'; '0': '1'; '1': '2'; '2': '3'; '3': '4'; '4': '5'; '5': '6'; '6': '7'; '7': '8'; '8': '9'; '9': '0' } + export type Take = Base[T] + export type Next = T extends Base['m'] ? Base['t'] : T extends `${infer L extends keyof Base}${infer R}` ? (L extends Base['m'] ? `${Take}${Next}` : `${Take}${R}`) : never + export type Reverse = T extends `${infer L}${infer R}` ? `${Reverse}${L}` : T + export type Increment = Reverse>> + /** Increments the given string value + 1 */ + export function Increment(T: T): Next { + return (parseInt(T) + 1).toString() as Next + } +} \ No newline at end of file diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts new file mode 100644 index 000000000..f0bfaf2ac --- /dev/null +++ b/src/resolvers/index.ts @@ -0,0 +1,8 @@ +export * from './indexed/index' +export * from './keyof/index' +export * from './template-literal/index' +export * from './counter' +export * from './intersect' +export * from './modifiers' +export * from './property' +export * from './union' \ No newline at end of file diff --git a/src/resolvers/indexed/index.ts b/src/resolvers/indexed/index.ts new file mode 100644 index 000000000..99e59dc70 --- /dev/null +++ b/src/resolvers/indexed/index.ts @@ -0,0 +1,30 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +export * from './indexed-access' +export * from './indexed-key' \ No newline at end of file diff --git a/src/resolvers/indexed/indexed-access.ts b/src/resolvers/indexed/indexed-access.ts new file mode 100644 index 000000000..197629c10 --- /dev/null +++ b/src/resolvers/indexed/indexed-access.ts @@ -0,0 +1,201 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Type, TypeGuard, TSchema, TNever, TRecursive, TIntersect, TUnion, TTuple, TArray, TObject, TProperties } from '../../typebox' +import { IntersectResolver } from '../intersect' +import { UnionResolver } from '../union' + +// prettier-ignore +export namespace IndexedAccessResolver { + // ---------------------------------------------------------------- + // Collect + // ---------------------------------------------------------------- + export type Collect = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? [Key, ...Collect] + : [] + ) + export function Collect(T: [...T], K: K): Collect { + const [L, ...R] = T + return ( + T.length > 0 + ? [Key(L, K), ...Collect(R, K)] + : [] + ) as Collect + } + // ---------------------------------------------------------------- + // Intersect + // ---------------------------------------------------------------- + export type IntersectEvaluate = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? L extends TNever + ? [...IntersectEvaluate] + : [L, ...IntersectEvaluate] + : [] + ) + export function IntersectEvaluate(T: [...T]): IntersectEvaluate { + const [L, ...R] = T + return ( + T.length > 0 + ? TypeGuard.TNever(L) + ? [...IntersectEvaluate(R)] + : [L, ...IntersectEvaluate(R)] + : [] + ) as IntersectEvaluate + } + export type Intersect = ( + IntersectResolver.Resolve>> + ) + export function Intersect(T: [...T], K: K): Intersect { + return ( + IntersectResolver.Resolve(IntersectEvaluate(Collect(T as TSchema[], K))) + ) as Intersect + } + // ---------------------------------------------------------------- + // Union + // ---------------------------------------------------------------- + export type UnionEvaluate = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? L extends TNever + ? [] + : UnionEvaluate + : S + ) + export function UnionEvaluate(T: [...T], S = T): UnionEvaluate { + const [L, ...R] = T + return ( + T.length > 0 + ? TypeGuard.TNever(L) + ? [] + : UnionEvaluate(R, S) + : S + ) as UnionEvaluate + } + export type Union = ( + UnionResolver.Resolve>> + ) + export function Union(T: [...T], K: K): Union { + return ( + UnionResolver.Resolve(UnionEvaluate(Collect(T as TSchema[], K))) + ) as Union + } + // ---------------------------------------------------------------- + // Tuple + // ---------------------------------------------------------------- + export type Tuple = ( + K extends keyof T ? T[K] : + K extends '[number]' ? UnionResolver.Resolve : + TNever + ) + export function Tuple(T: [...T], K: K): Tuple { + return ( + K in T ? T[K as number] : + K === '[number]' ? UnionResolver.Resolve(T) : + Type.Never() + ) as Tuple + } + // ---------------------------------------------------------------- + // Array + // ---------------------------------------------------------------- + export type Array = ( + K extends number + ? T + : TNever + ) + export function Array(T: T, K: K): Array { + return ( + K === '[number]' + ? T + : Type.Never() + ) as Array + } + // ---------------------------------------------------------------- + // Property + // ---------------------------------------------------------------- + export type Property = ( + K extends keyof T + ? T[K] + : TNever + ) + export function Property(T: T, K: K): Property { + return ( + K in T + ? T[K as string] + : Type.Never() + ) as Property + } + // ---------------------------------------------------------------- + // Key + // ---------------------------------------------------------------- + export type Key = ( + T extends TRecursive ? Key : + T extends TIntersect ? Intersect : + T extends TUnion ? Union : + T extends TTuple ? Tuple : + T extends TArray ? Array : + T extends TObject ? Property : + TNever + ) + export function Key(T: T, K: K): Key { + return ( + TypeGuard.TIntersect(T) ? Intersect(T.allOf, K) : + TypeGuard.TUnion(T) ? Union(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() + ) as Key + } + // ---------------------------------------------------------------- + // Keys + // ---------------------------------------------------------------- + export type Keys = ( + K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] + ? [Key, ...Keys] + : [] + ) + export function Keys(T: T, K: [...K]): Keys { + const [L, ...R] = K + return ( + K.length > 0 + ? [Key(T, L), ...Keys(T, R)] + : [] + ) as Keys + } + // ---------------------------------------------------------------- + // Resolve + // ---------------------------------------------------------------- + export type Resolve = ( + UnionResolver.Resolve> + ) + export function Resolve(T: T, K: [...K]): Resolve { + return ( + UnionResolver.Resolve(Keys(T, K)) + ) as Resolve + } +} \ No newline at end of file diff --git a/src/resolvers/indexed/indexed-key.ts b/src/resolvers/indexed/indexed-key.ts new file mode 100644 index 000000000..5757de9c3 --- /dev/null +++ b/src/resolvers/indexed/indexed-key.ts @@ -0,0 +1,103 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Static, TInteger, TLiteral, TLiteralValue, TNumber, TSchema, TTemplateLiteral, TUnion, TypeGuard, UnionToTuple } from '../../typebox' +import { TemplateLiteralParser, TemplateLiteralFinite, TemplateLiteralResolver, IsTemplateLiteralFiniteCheck } from '../template-literal' +// prettier-ignore +export namespace IndexedKeyResolver { + // ---------------------------------------------------------------- + // TemplateLiteral + // ---------------------------------------------------------------- + export type TemplateLiteral> = ( + F extends true + ? UnionToTuple> extends infer R extends string[] ? R : [] + : [] + ) + export function TemplateLiteral(T: T): TemplateLiteral { + 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()) + : [] + : [] + ) as unknown as TemplateLiteral + } + // ---------------------------------------------------------------- + // Union + // ---------------------------------------------------------------- + export type Union = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? [...Resolve, ...Union] + : [] + ) + export function Union(T: T): Union{ + const [L, ...R] = T + return ( + T.length > 0 + ? [...Resolve(L), ...Union(R)] + : [] + ) as Union + } + // ---------------------------------------------------------------- + // Literal + // ---------------------------------------------------------------- + export type Literal = ( + T extends PropertyKey + ? [`${T}`] + : [] + ) + export function TLiteral(T: T): Literal { + return ( + [T.toString()] + ) as Literal + } + // ---------------------------------------------------------------- + // Resolve + // ---------------------------------------------------------------- + export type Resolve = ( + T extends TTemplateLiteral ? TemplateLiteral : + T extends TUnion ? Union : + T extends TLiteral ? Literal : + T extends TNumber ? ['[number]'] : + T extends TInteger ? ['[number]'] : + [] + ) + export function Resolve(T: T): Resolve { + return [...new Set(( + TypeGuard.TTemplateLiteral(T) ? TemplateLiteral(T) : + TypeGuard.TUnion(T) ? Union(T.anyOf) : + TypeGuard.TLiteral(T) ? TLiteral(T.const) : + TypeGuard.TNumber(T) ? ['[number]'] : + TypeGuard.TInteger(T) ? ['[number]'] : + [] + ))] as unknown as Resolve + } +} diff --git a/src/resolvers/intersect.ts b/src/resolvers/intersect.ts new file mode 100644 index 000000000..d431c7574 --- /dev/null +++ b/src/resolvers/intersect.ts @@ -0,0 +1,46 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Type, TSchema, TNever } from '../typebox' +import { Modifiers } from './modifiers' + +// prettier-ignore +export namespace IntersectResolver { + export type Resolve = ( + T extends [] ? TNever : + T extends [TSchema] ? T[0] : + Modifiers.ResolveOptionalIntersect + ) + export function Resolve(T: T): Resolve { + return ( + T.length === 0 ? Type.Never() : + T.length === 1 ? T[0] : + Modifiers.ResolveOptionalIntersect(T) + ) as Resolve + } +} \ No newline at end of file diff --git a/src/resolvers/keyof/index.ts b/src/resolvers/keyof/index.ts new file mode 100644 index 000000000..2904c90fb --- /dev/null +++ b/src/resolvers/keyof/index.ts @@ -0,0 +1,30 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +export * from './keyof-string' +export * from './keyof' \ No newline at end of file diff --git a/src/resolvers/keyof/keyof-string.ts b/src/resolvers/keyof/keyof-string.ts new file mode 100644 index 000000000..4662b2420 --- /dev/null +++ b/src/resolvers/keyof/keyof-string.ts @@ -0,0 +1,159 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { TypeGuard, TSchema, TProperties, TRecursive, ZeroSring, UnionToTuple, TIntersect, TUnion, TTuple, TArray, TObject } from '../../typebox' +import { PropertySet } from '../property' +import { Counter } from '../counter' + +// prettier-ignore +export namespace KeyOfStringResolver { + // ---------------------------------------------------------------- + // Collect + // ---------------------------------------------------------------- + export type Collect = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? [Resolve, ...Collect] + : [] + ) + export function Collect(T: [...T]): Collect { + const [L, ...R] = T + return ( + T.length > 0 + ? [Resolve(L), ...Collect(R)] + : [] + ) as Collect + } + // ---------------------------------------------------------------- + // Intersect + // ---------------------------------------------------------------- + export type Intersect> = ( + PropertySet.UnionMany + ) + export function Intersect(T: [...T], C = Collect(T)): Intersect { + return ( + PropertySet.UnionMany(C as PropertyKey[][]) as Intersect + ) + } + // ---------------------------------------------------------------- + // Union + // ---------------------------------------------------------------- + export type Union> = ( + PropertySet.IntersectMany + ) + export function Union(T: [...T]): Union { + const C = Collect(T) as PropertyKey[][] + return ( + PropertySet.IntersectMany(C) as Union + ) + } + // ---------------------------------------------------------------- + // Tuple + // ---------------------------------------------------------------- + export type Tuple = + T extends [infer _ extends TSchema, ...infer R extends TSchema[]] + ? [I, ...Tuple>] + : [] + export function Tuple(T: [...T], I = '0'): Tuple { + const [_, ...R] = T + return ( + T.length > 0 + ? [I, ...Tuple(R, Counter.Increment(I))] + : [] + ) as Tuple + } + // ---------------------------------------------------------------- + // Array + // ---------------------------------------------------------------- + export type Array<_ extends TSchema> = ( + ['[number]'] + ) + export function Array<_ extends TSchema>(_: _): Array<_> { + return ( + ['[number]'] + ) + } + // ---------------------------------------------------------------- + // Properties + // ---------------------------------------------------------------- + export type Properties = ( + UnionToTuple + ) + export function Properties(T: T): Properties { + return ( + globalThis.Object.getOwnPropertyNames(T) + ) as Properties + } + // ---------------------------------------------------------------- + // Pattern + // ---------------------------------------------------------------- + function PatternProperties(patternProperties: Record): string[] { + if(!includePatternProperties) return [] + const patternPropertyKeys = globalThis.Object.getOwnPropertyNames(patternProperties) + return patternPropertyKeys.map(key => { + return (key[0] === '^' && key[key.length - 1] === '$') + ? key.slice(1, key.length - 1) + : key + }) + } + // ---------------------------------------------------------------- + // Resolve + // ---------------------------------------------------------------- + export type Resolve = ( + T extends TRecursive ? Resolve : + T extends TIntersect ? Intersect : + T extends TUnion ? Union : + T extends TTuple ? Tuple : + T extends TArray ? Array : + T extends TObject ? Properties : + [] + ) + /** Resolves finite keys from this type */ + export function Resolve(T: T): Resolve { + return ( + TypeGuard.TIntersect(T) ? Intersect(T.allOf) : + TypeGuard.TUnion(T) ? Union(T.anyOf) : + TypeGuard.TTuple(T) ? Tuple(T.items ?? []) : + TypeGuard.TArray(T) ? Array(T.items) : + TypeGuard.TObject(T) ? Properties(T.properties) : + TypeGuard.TRecord(T) ? PatternProperties(T.patternProperties) : + [] + ) as Resolve + } + // ---------------------------------------------------------------- + // ResolvePattern + // ---------------------------------------------------------------- + let includePatternProperties = false + /** Resolves keys as a regular expression, including pattern property keys */ + export function ResolvePattern(schema: TSchema): string { + includePatternProperties = true + const keys = Resolve(schema) + includePatternProperties = false + const pattern = keys.map((key) => `(${key})`) + return `^(${pattern.join('|')})$` + } +} \ No newline at end of file diff --git a/src/resolvers/keyof/keyof.ts b/src/resolvers/keyof/keyof.ts new file mode 100644 index 000000000..b093e18c4 --- /dev/null +++ b/src/resolvers/keyof/keyof.ts @@ -0,0 +1,66 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Type, TSchema, TLiteral, TLiteralValue, TNumber, Ensure } from "../../typebox" +import { KeyOfStringResolver } from './keyof-string' +import { UnionResolver } from '../union' + +// prettier-ignore +export namespace KeyOfResolver { + // ---------------------------------------------------------------- + // Literals + // ---------------------------------------------------------------- + export type Literals = ( + T extends [infer L extends TLiteralValue, ...infer R extends TLiteralValue[]] + ? L extends '[number]' + ? [TNumber, ...Literals] + : [TLiteral, ...Literals] + : [] + ) + export function Literals(T: [...T]): Literals { + const [L, ...R] = T + return ( + T.length > 0 + ? L === '[number]' + ? [Type.Number(), ...Literals(R)] + : [Type.Literal(L as TLiteralValue), ...Literals(R)] + : [] + ) as Literals + } + // ---------------------------------------------------------------- + // Resolve + // ---------------------------------------------------------------- + export type Resolve = ( + Ensure>>> + ) + export function Resolve(T: T): Resolve { + return ( + UnionResolver.Resolve(Literals(KeyOfStringResolver.Resolve(T) as TLiteralValue[])) + ) as unknown as Resolve + } +} \ No newline at end of file diff --git a/src/resolvers/modifiers.ts b/src/resolvers/modifiers.ts new file mode 100644 index 000000000..6f2161863 --- /dev/null +++ b/src/resolvers/modifiers.ts @@ -0,0 +1,163 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Type, Discard, TypeGuard, TSchema, TIntersect, TUnion, TOptional, TReadonly, Readonly, Optional } from '../typebox' + +// ---------------------------------------------------------------- +// 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 + ) + export function IsOptionalIntersect(T: T): IsOptionalIntersect { + const [L, ...R] = T + return ( + T.length > 0 + ? TypeGuard.TOptional(L) + ? IsOptionalIntersect(R) + : false + : true + ) as IsOptionalIntersect + } + // ---------------------------------------------------------------- + // IsOptionalUnion + // ---------------------------------------------------------------- + export type IsOptionalUnion = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] ? + L extends TOptional + ? true + : IsOptionalUnion + : false + ) + function IsOptionalUnion(T: T): IsOptionalUnion { + const [L, ...R] = T + return ( + T.length > 0 + ? TypeGuard.TOptional(L) + ? true + : IsOptionalUnion(R) + : false + ) as IsOptionalUnion + } + // ---------------------------------------------------------------- + // RemoveReadonlyRest + // ---------------------------------------------------------------- + export type RemoveReadonlyRest = + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? L extends TReadonly + ? [RemoveReadonly, ...RemoveReadonlyRest] + : [L, ...RemoveReadonlyRest] + : [] + export function RemoveReadonlyRest(T: T): RemoveReadonlyRest { + return ( + T.map(T => RemoveReadonly(T)) + ) as RemoveReadonlyRest + } + // ---------------------------------------------------------------- + // RemoveReadonly + // ---------------------------------------------------------------- + export type RemoveReadonly = ( + T extends TReadonly ? RemoveReadonly : + T extends TOptional ? TOptional> : + T + ) + export function RemoveReadonly(T: T): RemoveReadonly { + return ( + Discard.Keys(T, [Readonly]) + ) as RemoveReadonly + } + // ---------------------------------------------------------------- + // RemoveOptionalRest + // ---------------------------------------------------------------- + export type RemoveOptionalRest = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? L extends TOptional + ? [RemoveOptional, ...RemoveOptionalRest] + : [L, ...RemoveOptionalRest] + : [] + ) + export function RemoveOptionalRest(T: T): RemoveOptionalRest { + return ( + T.map(T => RemoveOptional(T)) + ) as RemoveOptionalRest + } + // ---------------------------------------------------------------- + // RemoveOptional + // ---------------------------------------------------------------- + export type RemoveOptional = ( + T extends TReadonly ? TReadonly> : + T extends TOptional ? RemoveOptional : + T + ) + export function RemoveOptional(T: T): RemoveOptional { + return ( + Discard.Keys(T, [Optional]) + ) as RemoveOptional + } + // ---------------------------------------------------------------- + // ResolveOptionalIntersect + // ---------------------------------------------------------------- + export type ResolveOptionalIntersect> = ( + IsOptionalIntersect extends true + ? TOptional> + : TIntersect + ) + export function ResolveOptionalIntersect(T: T): ResolveOptionalIntersect { + const D = RemoveOptionalRest(T) as TSchema[] + return ( + IsOptionalIntersect(T) + ? Type.Optional(Type.Intersect(D)) + : Type.Intersect(T) + ) as ResolveOptionalIntersect + } + // ---------------------------------------------------------------- + // ResolveOptionalUnion + // ---------------------------------------------------------------- + export type ResolveOptionalUnion> = ( + IsOptionalUnion extends true + ? TOptional> + : TUnion + ) + export function ResolveOptionalUnion(T: T): ResolveOptionalUnion { + const D = RemoveOptionalRest(T) as TSchema[] + return ( + IsOptionalUnion(T) + ? Type.Optional(Type.Union(D)) + : Type.Union(T) + ) as ResolveOptionalUnion + } +} diff --git a/src/resolvers/property.ts b/src/resolvers/property.ts new file mode 100644 index 000000000..cee101a8c --- /dev/null +++ b/src/resolvers/property.ts @@ -0,0 +1,154 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +// ---------------------------------------------------------------- +// PropertySet +// ---------------------------------------------------------------- +// prettier-ignore +export namespace PropertySet { + // ---------------------------------------------------------------- + // FromLeft + // ---------------------------------------------------------------- + function FromLeft(T: T[], A: (L: T, R: T[]) => unknown, B: () => unknown) { + const [L, ...R] = T + return T.length > 0 ? A(L, R) : B() + } + // ---------------------------------------------------------------- + // Includes + // ---------------------------------------------------------------- + export type Includes = ( + T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] + ? S extends L ? true : Includes + : false + ) + /** Returns true if this set contains the given property. */ + export function Includes(T: [...T], S: S): Includes { + return FromLeft(T, + (L, R) => S === L ? true : Includes(R, S), + () => false + ) as Includes + } + // ---------------------------------------------------------------- + // Distinct + // ---------------------------------------------------------------- + export type Distinct = + T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] + ? Includes extends false + ? Distinct + : Distinct + : Acc + /** Returns a new set containing only distinct elements */ + export function Distinct(T: [...T], Acc: PropertyKey[] = []): Distinct { + return FromLeft(T, + (L, R) => Includes(Acc, L) === false + ? Distinct(R, [...Acc, L]) + : Distinct(R, [...Acc]), + () => Acc + ) as Distinct + } + // ---------------------------------------------------------------- + // Intersect + // ---------------------------------------------------------------- + export type Intersect = ( + T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] + ? Includes extends true + ? [L, ...Intersect] + : [...Intersect] + : [] + ) + /** Returns the Intersect of the given PropertyKey sets */ + export function Intersect(T: [...T], S: [...S]): Intersect { + return FromLeft(T, + (L, R) => Includes(S, L) === true + ? [L, ...Intersect(R, S)] + : [...Intersect(R, S)], + () => [] + ) as Intersect + } + // ---------------------------------------------------------------- + // Union + // ---------------------------------------------------------------- + export type Union = ( + T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] + ? Includes extends true + ? [...Union] + : [L, ...Union] + : S + ) + /** Returns the Union of the given PropertyKey sets */ + export function Union(T: [...T], S: [...S]): Union { + return FromLeft(T, + (L, R) => Includes(S, L) === true + ? [...Union(R, S)] + : [L, ...Union(R, S)], + () => S + ) as Union + } + // ---------------------------------------------------------------- + // IntersectMany + // ---------------------------------------------------------------- + export type IntersectMany = ( + T extends [infer L extends PropertyKey[]] + ? L + : T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]] + ? Intersect> + : [] + ) + /** Returns the Intersect of multiple PropertyKey sets */ + export function IntersectMany(T: [...T]): IntersectMany { + return ( + T.length === 1 + ? T[0] + : FromLeft(T, + (L, R) => L.length > 0 ? Intersect(L, IntersectMany(R)) : [], + () => [] + ) + ) as IntersectMany + } + // ---------------------------------------------------------------- + // UnionMany + // ---------------------------------------------------------------- + export type UnionMany = ( + T extends [infer L extends PropertyKey[]] + ? L + : T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]] + ? Union> + : [] + ) + /** Returns the Union of multiple PropertyKey sets */ + export function UnionMany(T: [...T]): UnionMany { + return ( + T.length === 1 + ? T[0] + : FromLeft(T, + (L, R) => Union(L, UnionMany(R)), + () => [] + ) + ) as UnionMany + } +} diff --git a/src/resolvers/record/index.ts b/src/resolvers/record/index.ts new file mode 100644 index 000000000..e915912a3 --- /dev/null +++ b/src/resolvers/record/index.ts @@ -0,0 +1 @@ +export * from './record' \ No newline at end of file diff --git a/src/resolvers/record/record.ts b/src/resolvers/record/record.ts new file mode 100644 index 000000000..237f66673 --- /dev/null +++ b/src/resolvers/record/record.ts @@ -0,0 +1,4 @@ + +export namespace RecordResolver { + +} \ No newline at end of file diff --git a/src/resolvers/template-literal/index.ts b/src/resolvers/template-literal/index.ts new file mode 100644 index 000000000..31905cf90 --- /dev/null +++ b/src/resolvers/template-literal/index.ts @@ -0,0 +1,30 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +export * from './runtime' +export * from './static' \ No newline at end of file diff --git a/src/resolvers/template-literal/runtime.ts b/src/resolvers/template-literal/runtime.ts new file mode 100644 index 000000000..9a94422cc --- /dev/null +++ b/src/resolvers/template-literal/runtime.ts @@ -0,0 +1,315 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Kind, PatternBoolean, PatternNumber, PatternString, TLiteral, TNever, TSchema, TString, TTemplateLiteral, TTemplateLiteralKind, TUnion, Type, TypeBoxError, TypeGuard } from '../../typebox' +// ------------------------------------------------------------------ +// TemplateLiteralPattern +// ------------------------------------------------------------------ +export class TemplateLiteralPatternError extends Error {} +// prettier-ignore +export namespace TemplateLiteralPattern { + function Throw(message: string): never { + throw new TemplateLiteralPatternError(message) + } + function Escape(value: string) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + } + function Visit(schema: TSchema, acc: string): string { + return ( + TypeGuard.TTemplateLiteral(schema) ? schema.pattern.slice(1, schema.pattern.length - 1) : + TypeGuard.TUnion(schema) ? `(${schema.anyOf.map((schema) => Visit(schema, acc)).join('|')})` : + TypeGuard.TNumber(schema) ? `${acc}${PatternNumber}` : + TypeGuard.TInteger(schema) ? `${acc}${PatternNumber}` : + TypeGuard.TBigInt(schema) ? `${acc}${PatternNumber}` : + TypeGuard.TString(schema) ? `${acc}${PatternString}` : + TypeGuard.TLiteral(schema) ? `${acc}${Escape(schema.const.toString())}` : + TypeGuard.TBoolean(schema) ? `${acc}${PatternBoolean}` : + Throw(`Unexpected Kind '${schema[Kind]}'`) + ) + } + export function Create(kinds: TTemplateLiteralKind[]): string { + return `^${kinds.map((schema) => Visit(schema, '')).join('')}\$` + } +} +// ------------------------------------------------------------------------------ +// TemplateLiteralResolver +// ------------------------------------------------------------------------------ +export namespace TemplateLiteralResolver { + /** Resolves a template literal as a TUnion */ + export function Resolve(template: TTemplateLiteral): TNever | TString | TUnion { + const expression = TemplateLiteralParser.ParseExact(template.pattern) + if (!TemplateLiteralFinite.Check(expression)) return Type.String() + const literals = [...TemplateLiteralGenerator.Generate(expression)].map((value) => Type.Literal(value)) + return Type.Union(literals) + } +} +// ------------------------------------------------------------------------------ +// TemplateLiteralParser +// ------------------------------------------------------------------------------ +export class TemplateLiteralParserError extends Error {} +// prettier-ignore +export namespace TemplateLiteralParser { + export type Expression = And | Or | Const + export type Const = { type: 'const'; const: string } + export type And = { type: 'and'; expr: Expression[] } + export type Or = { type: 'or'; expr: Expression[] } + function IsNonEscaped(pattern: string, index: number, char: string) { + return pattern[index] === char && pattern.charCodeAt(index - 1) !== 92 + } + function IsOpenParen(pattern: string, index: number) { + return IsNonEscaped(pattern, index, '(') + } + function IsCloseParen(pattern: string, index: number) { + return IsNonEscaped(pattern, index, ')') + } + function IsSeparator(pattern: string, index: number) { + return IsNonEscaped(pattern, index, '|') + } + function IsGroup(pattern: string) { + if (!(IsOpenParen(pattern, 0) && IsCloseParen(pattern, pattern.length - 1))) return false + let count = 0 + for (let index = 0; index < pattern.length; index++) { + if (IsOpenParen(pattern, index)) count += 1 + if (IsCloseParen(pattern, index)) count -= 1 + if (count === 0 && index !== pattern.length - 1) return false + } + return true + } + function InGroup(pattern: string) { + return pattern.slice(1, pattern.length - 1) + } + function IsPrecedenceOr(pattern: string) { + let count = 0 + for (let index = 0; index < pattern.length; index++) { + if (IsOpenParen(pattern, index)) count += 1 + if (IsCloseParen(pattern, index)) count -= 1 + if (IsSeparator(pattern, index) && count === 0) return true + } + return false + } + function IsPrecedenceAnd(pattern: string) { + for (let index = 0; index < pattern.length; index++) { + if (IsOpenParen(pattern, index)) return true + } + return false + } + function Or(pattern: string): Expression { + let [count, start] = [0, 0] + const expressions: Expression[] = [] + for (let index = 0; index < pattern.length; index++) { + if (IsOpenParen(pattern, index)) count += 1 + if (IsCloseParen(pattern, index)) count -= 1 + if (IsSeparator(pattern, index) && count === 0) { + const range = pattern.slice(start, index) + if (range.length > 0) expressions.push(Parse(range)) + start = index + 1 + } + } + const range = pattern.slice(start) + if (range.length > 0) expressions.push(Parse(range)) + if (expressions.length === 0) return { type: 'const', const: '' } + if (expressions.length === 1) return expressions[0] + return { type: 'or', expr: expressions } + } + function And(pattern: string): Expression { + function Group(value: string, index: number): [number, number] { + if (!IsOpenParen(value, index)) throw new TemplateLiteralParserError(`TemplateLiteralParser: Index must point to open parens`) + let count = 0 + for (let scan = index; scan < value.length; scan++) { + if (IsOpenParen(value, scan)) count += 1 + if (IsCloseParen(value, scan)) count -= 1 + if (count === 0) return [index, scan] + } + throw new TemplateLiteralParserError(`TemplateLiteralParser: Unclosed group parens in expression`) + } + function Range(pattern: string, index: number): [number, number] { + for (let scan = index; scan < pattern.length; scan++) { + if (IsOpenParen(pattern, scan)) return [index, scan] + } + return [index, pattern.length] + } + const expressions: Expression[] = [] + for (let index = 0; index < pattern.length; index++) { + if (IsOpenParen(pattern, index)) { + const [start, end] = Group(pattern, index) + const range = pattern.slice(start, end + 1) + expressions.push(Parse(range)) + index = end + } else { + const [start, end] = Range(pattern, index) + const range = pattern.slice(start, end) + if (range.length > 0) expressions.push(Parse(range)) + index = end - 1 + } + } + return ( + (expressions.length === 0) ? { type: 'const', const: '' } : + (expressions.length === 1) ? expressions[0] : + { type: 'and', expr: expressions } + ) + } + /** Parses a pattern and returns an expression tree */ + export function Parse(pattern: string): Expression { + return ( + IsGroup(pattern) ? Parse(InGroup(pattern)) : + IsPrecedenceOr(pattern) ? Or(pattern) : + IsPrecedenceAnd(pattern) ? And(pattern) : + { type: 'const', const: pattern } + ) + } + /** Parses a pattern and strips forward and trailing ^ and $ */ + export function ParseExact(pattern: string): Expression { + return Parse(pattern.slice(1, pattern.length - 1)) + } +} +// ------------------------------------------------------------------------------ +// TemplateLiteralFinite +// ------------------------------------------------------------------------------ +export class TemplateLiteralFiniteError extends Error{} +// prettier-ignore +export namespace TemplateLiteralFinite { + function Throw(message: string): never { + throw new TemplateLiteralFiniteError(message) + } + function IsNumber(expression: TemplateLiteralParser.Expression): boolean { + return ( + expression.type === 'or' && + expression.expr.length === 2 && + expression.expr[0].type === 'const' && + expression.expr[0].const === '0' && + expression.expr[1].type === 'const' && + expression.expr[1].const === '[1-9][0-9]*' + ) + } + function IsBoolean(expression: TemplateLiteralParser.Expression): boolean { + return ( + expression.type === 'or' && + expression.expr.length === 2 && + expression.expr[0].type === 'const' && + expression.expr[0].const === 'true' && + expression.expr[1].type === 'const' && + expression.expr[1].const === 'false' + ) + } + function IsString(expression: TemplateLiteralParser.Expression) { + return expression.type === 'const' && expression.const === '.*' + } + export function Check(expression: TemplateLiteralParser.Expression): boolean { + return ( + IsBoolean(expression) ? true : + IsNumber(expression) || IsString(expression) ? false : + (expression.type === 'and') ? expression.expr.every((expr) => Check(expr)) : + (expression.type === 'or') ? expression.expr.every((expr) => Check(expr)) : + (expression.type === 'const') ? true : + Throw(`Unknown expression type`) + ) + } +} +// ------------------------------------------------------------------------------ +// TemplateLiteralGenerator +// ------------------------------------------------------------------------------ +export class TemplateLiteralGeneratorError extends Error{} +// prettier-ignore +export namespace TemplateLiteralGenerator { + function* Reduce(buffer: string[][]): IterableIterator { + if (buffer.length === 1) return yield* buffer[0] + for (const left of buffer[0]) { + for (const right of Reduce(buffer.slice(1))) { + yield `${left}${right}` + } + } + } + function* And(expression: TemplateLiteralParser.And): IterableIterator { + return yield* Reduce(expression.expr.map((expr) => [...Generate(expr)])) + } + function* Or(expression: TemplateLiteralParser.Or): IterableIterator { + for (const expr of expression.expr) yield* Generate(expr) + } + function* Const(expression: TemplateLiteralParser.Const): IterableIterator { + return yield expression.const + } + export function* Generate(expression: TemplateLiteralParser.Expression): IterableIterator { + return ( + expression.type === 'and' ? yield* And(expression) : + expression.type === 'or' ? yield* Or(expression) : + expression.type === 'const' ? yield* Const(expression) : + (() => { throw new TemplateLiteralGeneratorError('Unknown expression') })() + ) + } +} +// --------------------------------------------------------------------- +// TemplateLiteralDslParser +// --------------------------------------------------------------------- +// prettier-ignore +export namespace TemplateLiteralDslParser { + function* ParseUnion(template: string): IterableIterator { + const trim = template.trim().replace(/"|'/g, '') + return ( + trim === 'boolean' ? yield Type.Boolean() : + trim === 'number' ? yield Type.Number() : + trim === 'bigint' ? yield Type.BigInt() : + trim === 'string' ? yield Type.String() : + yield (() => { + const literals = trim.split('|').map((literal) => Type.Literal(literal.trim())) + return ( + literals.length === 0 ? Type.Never() : + literals.length === 1 ? literals[0] : + Type.Union(literals) + ) + })() + ) + } + function* ParseTerminal(template: string): IterableIterator { + if (template[1] !== '{') { + const L = Type.Literal('$') + const R = ParseLiteral(template.slice(1)) + return yield* [L, ...R] + } + for (let i = 2; i < template.length; i++) { + if (template[i] === '}') { + const L = ParseUnion(template.slice(2, i)) + const R = ParseLiteral(template.slice(i + 1)) + return yield* [...L, ...R] + } + } + yield Type.Literal(template) + } + function* ParseLiteral(template: string): IterableIterator { + for (let i = 0; i < template.length; i++) { + if (template[i] === '$') { + const L = Type.Literal(template.slice(0, i)) + const R = ParseTerminal(template.slice(i)) + return yield* [L, ...R] + } + } + yield Type.Literal(template) + } + export function Parse(template_dsl: string): TTemplateLiteralKind[] { + return [...ParseLiteral(template_dsl)] + } +} \ No newline at end of file diff --git a/src/resolvers/template-literal/static.ts b/src/resolvers/template-literal/static.ts new file mode 100644 index 000000000..a1fd9a0de --- /dev/null +++ b/src/resolvers/template-literal/static.ts @@ -0,0 +1,95 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Kind, UnionToTuple, Static, TUnion, TLiteral, TBoolean, TInteger, TBigInt, TNumber, TString, Trim, Assert, Ensure, TPropertyKey, EmptyString, TSchema, TNever } from '../../typebox' +import { UnionResolver } from '../union' + +// ------------------------------------------------------------------------- +// TTemplateLiteralParserDsl +// ------------------------------------------------------------------------- +// prettier-ignore +export type TTemplateLiteralDslParserUnionLiteral = + T extends `${infer L}|${infer R}` ? [TLiteral>, ...TTemplateLiteralDslParserUnionLiteral] : + T extends `${infer L}` ? [TLiteral>] : + [] +export type TTemplateLiteralDslParserUnion = UnionResolver.Resolve> +// prettier-ignore +export type TTemplateLiteralDslParserTerminal = + T extends 'boolean' ? TBoolean : + T extends 'bigint' ? TBigInt : + T extends 'number' ? TNumber : + T extends 'string' ? TString : + TTemplateLiteralDslParserUnion +// prettier-ignore +export type TTemplateLiteralDslParserTemplate = + T extends `{${infer L}}${infer R}` ? [TTemplateLiteralDslParserTerminal, ...TTemplateLiteralDslParserTemplate] : + T extends `${infer L}$${infer R}` ? [TLiteral, ...TTemplateLiteralDslParserTemplate] : + T extends `${infer L}` ? [TLiteral] : + [] +export type TTemplateLiteralDslParser = Ensure, TTemplateLiteralKind[]>>> +// ------------------------------------------------------------------ +// TTemplateLiteral +// ------------------------------------------------------------------ +// prettier-ignore +export type IsTemplateLiteralFiniteCheck = + T extends TTemplateLiteral ? IsTemplateLiteralFiniteArray> : + T extends TUnion ? IsTemplateLiteralFiniteArray> : + T extends TString ? false : + T extends TBoolean ? false : + T extends TNumber ? false : + T extends TInteger ? false : + T extends TBigInt ? false : + T extends TLiteral ? true : + false +// prettier-ignore +export type IsTemplateLiteralFiniteArray = + T extends [infer L, ...infer R] ? IsTemplateLiteralFiniteCheck extends false ? false : IsTemplateLiteralFiniteArray> : + true +export type IsTemplateLiteralFinite = T extends TTemplateLiteral ? IsTemplateLiteralFiniteArray : false +export type TTemplateLiteralKind = TUnion | TLiteral | TInteger | TTemplateLiteral | TNumber | TBigInt | TString | TBoolean | TNever +// prettier-ignore +export type TTemplateLiteralConst = + T extends TUnion ? { [K in keyof U]: TTemplateLiteralUnion, Acc> }[number] : + T extends TTemplateLiteral ? `${Static}` : + T extends TLiteral ? `${U}` : + T extends TString ? `${string}` : + T extends TNumber ? `${number}` : + T extends TBigInt ? `${bigint}` : + T extends TBoolean ? `${boolean}` : + never +// prettier-ignore +export type TTemplateLiteralUnion = + T extends [infer L, ...infer R] ? `${TTemplateLiteralConst}${TTemplateLiteralUnion, Acc>}` : + Acc +export type TTemplateLiteralKeyRest = Assert>, TPropertyKey[]> +export interface TTemplateLiteral extends TSchema { + [Kind]: 'TemplateLiteral' + static: TTemplateLiteralUnion + type: 'string' + pattern: string // todo: it may be possible to infer this pattern +} \ No newline at end of file diff --git a/src/resolvers/union.ts b/src/resolvers/union.ts new file mode 100644 index 000000000..2394b6b86 --- /dev/null +++ b/src/resolvers/union.ts @@ -0,0 +1,46 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox + +The MIT License (MIT) + +Copyright (c) 2017-2023 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Type, TSchema, TNever } from '../typebox' +import { Modifiers } from './modifiers' + +// prettier-ignore +export namespace UnionResolver { + export type Resolve = ( + T extends [] ? TNever : + T extends [TSchema] ? T[0] : + Modifiers.ResolveOptionalUnion + ) + export function Resolve(T: T): Resolve { + return ( + T.length === 0 ? Type.Never() : + T.length === 1 ? T[0] : + Modifiers.ResolveOptionalUnion(T) + ) as Resolve + } +} \ No newline at end of file diff --git a/src/tsconfig.json b/src/tsconfig.json index c78307cfa..bf63ccade 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../tsconfig.json", - "files": ["compiler/index.ts", "errors/index.ts", "system/index.ts", "value/index.ts", "typebox.ts"] + "files": ["compiler/index.ts", "errors/index.ts", "resolvers/index.ts", "system/index.ts", "value/index.ts", "typebox.ts"] } diff --git a/src/typebox.ts b/src/typebox.ts index 8c64bfebe..93badb8b8 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -26,6 +26,20 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ +import { + IndexedAccessResolver, + IndexedKeyResolver, + KeyOfResolver, + KeyOfStringResolver, + TemplateLiteralDslParser, + TemplateLiteralFinite, + TemplateLiteralGenerator, + TemplateLiteralParser, + TemplateLiteralPattern, + TemplateLiteralResolver, + UnionResolver +} from './resolvers/index' + // ------------------------------------------------------------------ // Symbols // ------------------------------------------------------------------ @@ -44,12 +58,6 @@ export const PatternBooleanExact = `^${PatternBoolean}$` export const PatternNumberExact = `^${PatternNumber}$` export const PatternStringExact = `^${PatternString}$` // ------------------------------------------------------------------ -// Into -// ------------------------------------------------------------------ -export function Into(func: () => U): U { - return func() -} -// ------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------ export type TupleToIntersect = T extends [infer I] ? I : T extends [infer I, ...infer R] ? I & TupleToIntersect : never @@ -57,12 +65,12 @@ export type TupleToUnion = { [K in keyof T]: T[K] }[number] export type UnionToIntersect = (U extends unknown ? (arg: U) => 0 : never) extends (arg: infer I) => 0 ? I : never export type UnionLast = UnionToIntersect 0 : never> extends (x: infer L) => 0 ? L : never export type UnionToTuple> = [U] extends [never] ? [] : [...UnionToTuple>, L] -export type Reverse = T extends [infer L extends unknown, ...infer R extends unknown[]] ? [...Reverse, L] : [] export type Trim = T extends `${' '}${infer U}` ? Trim : T extends `${infer U}${' '}` ? Trim : T export type Assert = T extends E ? T : never export type Evaluate = T extends infer O ? { [K in keyof O]: O[K] } : never export type Ensure = T extends infer U ? U : never export type EmptyString = '' +export type ZeroSring = '0' // ------------------------------------------------------------------ // Type Asserts // ------------------------------------------------------------------ @@ -307,12 +315,12 @@ export type TExtends = UnionType.Resolve }[T]>>> +export type TExcludeTemplateLiteralResult = UnionResolver.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.Resolve : never +}[number]>> extends infer R extends TSchema[] ? UnionResolver.Resolve : never // prettier-ignore export type TExclude = T extends TTemplateLiteral ? TExcludeTemplateLiteral : @@ -321,12 +329,12 @@ export type TExclude = // ------------------------------------------------------------------ // TExtract // ------------------------------------------------------------------ -export type TExtractTemplateLiteralResult = UnionType.Resolve }[T]>>> +export type TExtractTemplateLiteralResult = UnionResolver.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.Resolve : never + }[number]>> extends infer R extends TSchema[] ? UnionResolver.Resolve : never // prettier-ignore export type TExtract = T extends TTemplateLiteral ? TExtractTemplateLiteral : @@ -364,34 +372,8 @@ 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.Resolve : TNever -export type TIndexArray = K extends number ? T : TNever -// prettier-ignore -export type TIndexRest = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? TIndexKey extends infer N extends TSchema - ? N extends TNever - ? [...TIndexRest] - : [N, ...TIndexRest] - : [] - : [] -// prettier-ignore -export type TIndexKey = - T extends TRecursive ? TIndexKey : - T extends TIntersect ? IntersectType.Resolve> : - T extends TUnion ? UnionType.Resolve> : - T extends TTuple ? TIndexTuple : - T extends TArray ? TIndexArray : - T extends TObject ? TIndexProperty : - TNever -// prettier-ignore -export type TIndexKeys = - K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? [TIndexKey, ...TIndexKeys] - : [] -export type TIndex = UnionType.Resolve> +export type TIndex = IndexedAccessResolver.Resolve // ------------------------------------------------------------------ // TIntrinsic // ------------------------------------------------------------------ @@ -460,7 +442,7 @@ export interface TIterator extends TSchema { // ------------------------------------------------------------------ // TKeyOf // ------------------------------------------------------------------ - +export type TKeyOf = KeyOfResolver.Resolve // ------------------------------------------------------------------ // TLiteral // ------------------------------------------------------------------ @@ -765,7 +747,7 @@ export type TTemplateLiteralDslParserUnionLiteral = T extends `${infer L}|${infer R}` ? [TLiteral>, ...TTemplateLiteralDslParserUnionLiteral] : T extends `${infer L}` ? [TLiteral>] : [] -export type TTemplateLiteralDslParserUnion = UnionType.Resolve> +export type TTemplateLiteralDslParserUnion = UnionResolver.Resolve> // prettier-ignore export type TTemplateLiteralDslParserTerminal = T extends 'boolean' ? TBoolean : @@ -901,7 +883,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] @@ -1603,173 +1585,24 @@ export namespace Distinct { : Acc } } -// ---------------------------------------------------------------- -// 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 - ) - export function IsOptionalIntersect(T: T): IsOptionalIntersect { - const [L, ...R] = T - return ( - T.length > 0 - ? TypeGuard.TOptional(L) - ? IsOptionalIntersect(R) - : false - : true - ) as IsOptionalIntersect - } - // ---------------------------------------------------------------- - // IsOptionalUnion - // ---------------------------------------------------------------- - export type IsOptionalUnion = ( - T extends [infer L extends TSchema, ...infer R extends TSchema[]] ? - L extends TOptional - ? true - : IsOptionalUnion - : false - ) - function IsOptionalUnion(T: T): IsOptionalUnion { - const [L, ...R] = T - return ( - T.length > 0 - ? TypeGuard.TOptional(L) - ? true - : IsOptionalUnion(R) - : false - ) as IsOptionalUnion - } - // ---------------------------------------------------------------- - // RemoveReadonlyRest - // ---------------------------------------------------------------- - export type RemoveReadonlyRest = - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? L extends TReadonly - ? [RemoveReadonly, ...RemoveReadonlyRest] - : [L, ...RemoveReadonlyRest] - : [] - export function RemoveReadonlyRest(T: T): RemoveReadonlyRest { - return ( - T.map(T => RemoveReadonly(T)) - ) as RemoveReadonlyRest - } - // ---------------------------------------------------------------- - // RemoveReadonly - // ---------------------------------------------------------------- - export type RemoveReadonly = ( - T extends TReadonly ? RemoveReadonly : - T extends TOptional ? TOptional> : - T - ) - export function RemoveReadonly(T: T): RemoveReadonly { - return ( - Discard.Keys(T, [Readonly]) - ) as RemoveReadonly - } - // ---------------------------------------------------------------- - // RemoveOptionalRest - // ---------------------------------------------------------------- - export type RemoveOptionalRest = ( - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? L extends TOptional - ? [RemoveOptional, ...RemoveOptionalRest] - : [L, ...RemoveOptionalRest] - : [] - ) - export function RemoveOptionalRest(T: T): RemoveOptionalRest { - return ( - T.map(T => RemoveOptional(T)) - ) as RemoveOptionalRest - } - // ---------------------------------------------------------------- - // RemoveOptional - // ---------------------------------------------------------------- - export type RemoveOptional = ( - T extends TReadonly ? TReadonly> : - T extends TOptional ? RemoveOptional : - T - ) - export function RemoveOptional(T: T): RemoveOptional { - return ( - Discard.Keys(T, [Optional]) - ) as RemoveOptional - } - // ---------------------------------------------------------------- - // ResolveOptionalIntersect - // ---------------------------------------------------------------- - export type ResolveOptionalIntersect> = ( - IsOptionalIntersect extends true - ? TOptional> - : TIntersect - ) - export function ResolveOptionalIntersect(T: T): ResolveOptionalIntersect { - const D = RemoveOptionalRest(T) as TSchema[] - return ( - IsOptionalIntersect(T) - ? Type.Optional(Type.Intersect(D)) - : Type.Intersect(T) - ) as ResolveOptionalIntersect - } - // ---------------------------------------------------------------- - // ResolveOptionalUnion - // ---------------------------------------------------------------- - export type ResolveOptionalUnion> = ( - IsOptionalUnion extends true - ? TOptional> - : TUnion - ) - export function ResolveOptionalUnion(T: T): ResolveOptionalUnion { - const D = RemoveOptionalRest(T) as TSchema[] - return ( - IsOptionalUnion(T) - ? Type.Optional(Type.Union(D)) - : Type.Union(T) - ) as ResolveOptionalUnion - } -} + + // ------------------------------------------------------------------ -// Intersect +// UnionResolverRecord (Need to get rid of this) // ------------------------------------------------------------------ -// prettier-ignore -export namespace IntersectType { - export type Resolve = ( - T extends [] ? TNever : - T extends [TSchema] ? T[0] : - Modifiers.ResolveOptionalIntersect - ) - export function Resolve(T: T): Resolve { - return ( - T.length === 0 ? Type.Never() : - T.length === 1 ? T[0] : - Modifiers.ResolveOptionalIntersect(T) - ) as Resolve +export namespace UnionResolverRecord { + function* TUnion(union: TUnion): IterableIterator { + for (const schema of union.anyOf) { + if (schema[Kind] === 'Union') { + yield* TUnion(schema as TUnion) + } else { + yield schema + } + } } -} -// ------------------------------------------------------------------ -// Union -// ------------------------------------------------------------------ -// prettier-ignore -export namespace UnionType { - export type Resolve = ( - T extends [] ? TNever : - T extends [TSchema] ? T[0] : - Modifiers.ResolveOptionalUnion - ) - export function Resolve(T: T): Resolve { - return ( - T.length === 0 ? Type.Never() : - T.length === 1 ? T[0] : - Modifiers.ResolveOptionalUnion(T) - ) as Resolve + /** Returns a resolved union with interior unions flattened */ + export function Resolve(union: TUnion): TUnion { + return Type.Union([...TUnion(union)], { ...union }) } } // ------------------------------------------------------------------ @@ -2576,561 +2409,6 @@ export namespace ObjectMap { return { ...Visit(TypeClone.Type(schema), callback), ...options } as unknown as T } } -// ------------------------------------------------------------------ -// Counter -// ------------------------------------------------------------------ -export namespace Counter { - export type ReverseString = T extends `${infer L}${infer R}` ? `${ReverseString}${L}` : T - export type Base = { m: '9'; t: '01'; '0': '1'; '1': '2'; '2': '3'; '3': '4'; '4': '5'; '5': '6'; '6': '7'; '7': '8'; '8': '9'; '9': '0' } - export type Next = ReverseString, B>> - export type Tick = B[Assert] - export type Increment = T extends B['m'] ? B['t'] : T extends `${infer L}${infer R}` ? (L extends B['m'] ? `${Assert, string>}${Increment}` : `${Assert, string>}${R}`) : never -} -// ---------------------------------------------------------------- -// Resolver: PropertyKeys -// ---------------------------------------------------------------- -// prettier-ignore -export namespace PropertyKeys { - // ---------------------------------------------------------------- - // From - // ---------------------------------------------------------------- - function TakeLeft(T: T[], A: (L: T, R: T[]) => unknown, B: () => unknown) { - const [L, ...R] = T - return T.length > 0 ? A(L, R) : B() - } - // ---------------------------------------------------------------- - // Includes - // ---------------------------------------------------------------- - export type Includes = ( - T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? S extends L ? true : Includes - : false - ) - /** Returns true if this set contains the given property. */ - export function Includes(T: [...T], S: S): Includes { - return TakeLeft(T, - (L, R) => S === L ? true : Includes(R, S), - () => false - ) as Includes - } - // ---------------------------------------------------------------- - // Distinct - // ---------------------------------------------------------------- - export type Distinct = - T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? Includes extends false - ? Distinct - : Distinct - : Acc - /** Returns a new set containing only distinct elements */ - export function Distinct(T: [...T], Acc: PropertyKey[] = []): Distinct { - return TakeLeft(T, - (L, R) => Includes(Acc, L) === false - ? Distinct(R, [...Acc, L]) - : Distinct(R, [...Acc]), - () => Acc - ) as Distinct - } - // ---------------------------------------------------------------- - // Intersect - // ---------------------------------------------------------------- - export type Intersect = ( - T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? Includes extends true - ? [L, ...Intersect] - : [...Intersect] - : [] - ) - /** Returns the Intersect of the given PropertyKey sets */ - export function Intersect(T: [...T], S: [...S]): Intersect { - return TakeLeft(T, - (L, R) => Includes(S, L) === true - ? [L, ...Intersect(R, S)] - : [...Intersect(R, S)], - () => [] - ) as Intersect - } - // ---------------------------------------------------------------- - // Union - // ---------------------------------------------------------------- - export type Union = ( - T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? Includes extends true - ? [...Union] - : [L, ...Union] - : S - ) - /** Returns the Union of the given PropertyKey sets */ - export function Union(T: [...T], S: [...S]): Union { - return TakeLeft(T, - (L, R) => Includes(S, L) === true - ? [...Union(R, S)] - : [L, ...Union(R, S)], - () => S - ) as Union - } - // ---------------------------------------------------------------- - // IntersectMany - // ---------------------------------------------------------------- - export type IntersectMany = ( - T extends [infer L extends PropertyKey[]] - ? L - : T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]] - ? Intersect> - : [] - ) - /** Returns the Intersect of multiple PropertyKey sets */ - export function IntersectMany(T: [...T]): IntersectMany { - return ( - T.length === 1 - ? T[0] - : TakeLeft(T, - (L, R) => L.length > 0 ? Intersect(L, IntersectMany(R)) : [], - () => [] - ) - ) as IntersectMany - } - // ---------------------------------------------------------------- - // UnionMany - // ---------------------------------------------------------------- - export type UnionMany = ( - T extends [infer L extends PropertyKey[]] - ? L - : T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]] - ? Union> - : [] - ) - /** Returns the Union of multiple PropertyKey sets */ - export function UnionMany(T: [...T]): UnionMany { - return ( - T.length === 1 - ? T[0] - : TakeLeft(T, - (L, R) => Union(L, UnionMany(R)), - () => [] - ) - ) as UnionMany - } -} -// ---------------------------------------------------------------- -// Resolver: KeyResolver -// ---------------------------------------------------------------- -// prettier-ignore -export namespace KeyResolver { - // ---------------------------------------------------------------- - // Collect - // ---------------------------------------------------------------- - export type Collect = ( - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? [Resolve, ...Collect] - : [] - ) - export function Collect(T: [...T]): Collect { - const [L, ...R] = T - return ( - T.length > 0 - ? [Resolve(L), ...Collect(R)] - : [] - ) as Collect - } - // ---------------------------------------------------------------- - // Intersect - // ---------------------------------------------------------------- - export type Intersect> = ( - PropertyKeys.UnionMany - ) - export function Intersect(T: [...T], C = Collect(T)): Intersect { - return ( - PropertyKeys.UnionMany(C as PropertyKey[][]) as Intersect - ) - } - // ---------------------------------------------------------------- - // Union - // ---------------------------------------------------------------- - export type Union> = ( - PropertyKeys.IntersectMany - ) - export function Union(T: [...T], C = Collect(T)): Union { - return ( - PropertyKeys.IntersectMany(C as PropertyKey[][]) as Union - ) - } - // ---------------------------------------------------------------- - // Tuple - // ---------------------------------------------------------------- - export type TupleNext = ( - I extends [infer L extends string, ...infer _] - ? Counter.Next - : '0' - ) - export type TupleCollect = ( - T extends [infer _, ...infer R extends TSchema[]] - ? TupleCollect, ...I]> - : I - ) - export type Tuple = Reverse> - - export function Tuple(T: [...T]): Tuple { - return ( - T.map((_, index) => index.toString()) - ) as Tuple - } - // ---------------------------------------------------------------- - // Array - // ---------------------------------------------------------------- - export type Array<_ extends TSchema> = ( - ['[number]'] - ) - export function Array<_ extends TSchema>(_: _): Array<_> { - return ( - ['[number]'] - ) - } - // ---------------------------------------------------------------- - // Properties - // ---------------------------------------------------------------- - export type Properties = ( - UnionToTuple - ) - export function Properties(T: T): Properties { - return ( - globalThis.Object.getOwnPropertyNames(T) - ) as Properties - } - // ---------------------------------------------------------------- - // Pattern - // ---------------------------------------------------------------- - function PatternProperties(patternProperties: Record): string[] { - if(!includePatternProperties) return [] - const patternPropertyKeys = globalThis.Object.getOwnPropertyNames(patternProperties) - return patternPropertyKeys.map(key => { - return (key[0] === '^' && key[key.length - 1] === '$') - ? key.slice(1, key.length - 1) - : key - }) - } - // ---------------------------------------------------------------- - // Resolve - // ---------------------------------------------------------------- - export type Resolve = ( - T extends TRecursive ? Resolve : - T extends TIntersect ? Intersect : - T extends TUnion ? Union : - T extends TTuple ? Tuple : - T extends TArray ? Array : - T extends TObject ? Properties : - [] - ) - /** Resolves finite keys from this type */ - export function Resolve(T: T): Resolve { - return ( - TypeGuard.TIntersect(T) ? Intersect(T.allOf) : - TypeGuard.TUnion(T) ? Union(T.anyOf) : - TypeGuard.TTuple(T) ? Tuple(T.items ?? []) : - TypeGuard.TArray(T) ? Array(T.items) : - TypeGuard.TObject(T) ? Properties(T.properties) : - TypeGuard.TRecord(T) ? PatternProperties(T.patternProperties) : - [] - ) as Resolve - } - // ---------------------------------------------------------------- - // ResolvePattern - // ---------------------------------------------------------------- - let includePatternProperties = false - /** Resolves keys as a regular expression, including pattern property keys */ - export function ResolvePattern(schema: TSchema): string { - includePatternProperties = true - const keys = Resolve(schema) - includePatternProperties = false - const pattern = keys.map((key) => `(${key})`) - return `^(${pattern.join('|')})$` - } -} -// ------------------------------------------------------------------ -// Resolver: IndexResolver -// ------------------------------------------------------------------ -// prettier-ignore -export namespace IndexResolver { - // ---------------------------------------------------------------- - // TemplateLiteral - // ---------------------------------------------------------------- - export type TemplateLiteral> = ( - F extends true - ? UnionToTuple> extends infer R extends string[] ? R : [] - : [] - ) - export function TemplateLiteral(T: T): TemplateLiteral { - 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()) - : [] - : [] - ) as unknown as TemplateLiteral - } - // ---------------------------------------------------------------- - // Union - // ---------------------------------------------------------------- - export type Union = ( - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? [...Resolve, ...Union] - : [] - ) - export function Union(T: T): Union{ - const [L, ...R] = T - return ( - T.length > 0 - ? [...Resolve(L), ...Union(R)] - : [] - ) as Union - } - // ---------------------------------------------------------------- - // Literal - // ---------------------------------------------------------------- - export type Literal = ( - T extends PropertyKey - ? [`${T}`] - : [] - ) - export function TLiteral(T: T): Literal { - return ( - [T.toString()] - ) as Literal - } - // ---------------------------------------------------------------- - // Resolve - // ---------------------------------------------------------------- - export type Resolve = ( - T extends TTemplateLiteral ? TemplateLiteral : - T extends TUnion ? Union : - T extends TLiteral ? Literal : - T extends TNumber ? ['[number]'] : - T extends TInteger ? ['[number]'] : - [] - ) - export function Resolve(T: T): Resolve { - return [...new Set(( - TypeGuard.TTemplateLiteral(T) ? TemplateLiteral(T) : - TypeGuard.TUnion(T) ? Union(T.anyOf) : - TypeGuard.TLiteral(T) ? TLiteral(T.const) : - TypeGuard.TNumber(T) ? ['[number]'] : - TypeGuard.TInteger(T) ? ['[number]'] : - [] - ))] as unknown as Resolve - } -} -// ------------------------------------------------------------------ -// Resolver: IndexAccessResolver -// ------------------------------------------------------------------ -// prettier-ignore -export namespace IndexAccessResolver { - // ---------------------------------------------------------------- - // Collect - // ---------------------------------------------------------------- - export type Collect = ( - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? [Key, ...Collect] - : [] - ) - export function Collect(T: [...T], K: K): Collect { - const [L, ...R] = T - return ( - T.length > 0 - ? [Key(L, K), ...Collect(R, K)] - : [] - ) as Collect - } - // ---------------------------------------------------------------- - // Intersect - // ---------------------------------------------------------------- - export type IntersectEvaluate = ( - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? L extends TNever - ? [...IntersectEvaluate] - : [L, ...IntersectEvaluate] - : [] - ) - export function IntersectEvaluate(T: [...T]): IntersectEvaluate { - const [L, ...R] = T - return ( - T.length > 0 - ? TypeGuard.TNever(L) - ? [...IntersectEvaluate(R)] - : [L, ...IntersectEvaluate(R)] - : [] - ) as IntersectEvaluate - } - export type Intersect = ( - IntersectType.Resolve>> - ) - export function Intersect(T: [...T], K: K): Intersect { - return ( - IntersectType.Resolve(IntersectEvaluate(Collect(T as TSchema[], K))) - ) as Intersect - } - // ---------------------------------------------------------------- - // Union - // ---------------------------------------------------------------- - export type UnionEvaluate = ( - T extends [infer L extends TSchema, ...infer R extends TSchema[]] - ? L extends TNever - ? [] - : UnionEvaluate - : S - ) - export function UnionEvaluate(T: [...T], S = T): UnionEvaluate { - const [L, ...R] = T - return ( - T.length > 0 - ? TypeGuard.TNever(L) - ? [] - : UnionEvaluate(R, S) - : S - ) as UnionEvaluate - } - export type Union = ( - UnionType.Resolve>> - ) - export function Union(T: [...T], K: K): Union { - return ( - UnionType.Resolve(UnionEvaluate(Collect(T as TSchema[], K))) - ) as Union - } - // ---------------------------------------------------------------- - // Tuple - // ---------------------------------------------------------------- - export type Tuple = ( - K extends keyof T ? T[K] : - K extends '[number]' ? UnionType.Resolve : - TNever - ) - export function Tuple(T: [...T], K: K): Tuple { - return ( - K in T ? T[K as number] : - K === '[number]' ? UnionType.Resolve(T) : - Type.Never() - ) as Tuple - } - // ---------------------------------------------------------------- - // Array - // ---------------------------------------------------------------- - export type Array = ( - K extends number - ? T - : TNever - ) - export function Array(T: T, K: K): Array { - return ( - K === '[number]' - ? T - : Type.Never() - ) as Array - } - // ---------------------------------------------------------------- - // Property - // ---------------------------------------------------------------- - export type Property = ( - K extends keyof T - ? T[K] - : TNever - ) - export function Property(T: T, K: K): Property { - return ( - K in T - ? T[K as string] - : Type.Never() - ) as Property - } - // ---------------------------------------------------------------- - // Key - // ---------------------------------------------------------------- - export type Key = ( - T extends TRecursive ? Key : - T extends TIntersect ? Intersect : - T extends TUnion ? Union : - T extends TTuple ? Tuple : - T extends TArray ? Array : - T extends TObject ? Property : - TNever - ) - export function Key(T: T, K: K): Key { - return ( - TypeGuard.TIntersect(T) ? Intersect(T.allOf, K) : - TypeGuard.TUnion(T) ? Union(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() - ) as Key - } - // ---------------------------------------------------------------- - // Keys - // ---------------------------------------------------------------- - export type Keys = ( - K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? [Key, ...Keys] - : [] - ) - export function Keys(T: T, K: [...K]): Keys { - const [L, ...R] = K - return ( - K.length > 0 - ? [Key(T, L), ...Keys(T, R)] - : [] - ) as Keys - } - // ---------------------------------------------------------------- - // Resolve - // ---------------------------------------------------------------- - export type Resolve = ( - UnionType.Resolve> - ) - export function Resolve(T: T, K: [...K]): Resolve { - return ( - UnionType.Resolve(Keys(T, K)) - ) as Resolve - } -} -// ------------------------------------------------------------------ -// Resolver: KeyOfResolver -// ------------------------------------------------------------------ -// prettier-ignore -export namespace KeyOfResolver { - // ---------------------------------------------------------------- - // Literals - // ---------------------------------------------------------------- - export type Literals = ( - T extends [infer L extends TLiteralValue, ...infer R extends TLiteralValue[]] - ? L extends '[number]' - ? [TNumber, ...Literals] - : [TLiteral, ...Literals] - : [] - ) - export function Literals(T: [...T]): Literals { - const [L, ...R] = T - return ( - T.length > 0 - ? L === '[number]' - ? [Type.Number(), ...Literals(R)] - : [Type.Literal(L as TLiteralValue), ...Literals(R)] - : [] - ) as Literals - } - // ---------------------------------------------------------------- - // Resolve - // ---------------------------------------------------------------- - export type Resolve = ( - Ensure>>> - ) - export function Resolve(T: T): Resolve { - return ( - UnionType.Resolve(Literals(KeyResolver.Resolve(T) as TLiteralValue[])) - ) as unknown as Resolve - } -} // ------------------------------------------------------------------ // KeyArrayResolver @@ -3152,310 +2430,8 @@ export namespace KeyArrayResolver { ) } } -// ------------------------------------------------------------------ -// UnionResolver -// ------------------------------------------------------------------ -export namespace UnionResolver { - function* TUnion(union: TUnion): IterableIterator { - for (const schema of union.anyOf) { - if (schema[Kind] === 'Union') { - yield* TUnion(schema as TUnion) - } else { - yield schema - } - } - } - /** Returns a resolved union with interior unions flattened */ - export function Resolve(union: TUnion): TUnion { - return Type.Union([...TUnion(union)], { ...union }) - } -} -// ------------------------------------------------------------------ -// TemplateLiteralPattern -// ------------------------------------------------------------------ -export class TemplateLiteralPatternError extends TypeBoxError {} -// prettier-ignore -export namespace TemplateLiteralPattern { - function Throw(message: string): never { - throw new TemplateLiteralPatternError(message) - } - function Escape(value: string) { - return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - } - function Visit(schema: TSchema, acc: string): string { - return ( - TypeGuard.TTemplateLiteral(schema) ? schema.pattern.slice(1, schema.pattern.length - 1) : - TypeGuard.TUnion(schema) ? `(${schema.anyOf.map((schema) => Visit(schema, acc)).join('|')})` : - TypeGuard.TNumber(schema) ? `${acc}${PatternNumber}` : - TypeGuard.TInteger(schema) ? `${acc}${PatternNumber}` : - TypeGuard.TBigInt(schema) ? `${acc}${PatternNumber}` : - TypeGuard.TString(schema) ? `${acc}${PatternString}` : - TypeGuard.TLiteral(schema) ? `${acc}${Escape(schema.const.toString())}` : - TypeGuard.TBoolean(schema) ? `${acc}${PatternBoolean}` : - Throw(`Unexpected Kind '${schema[Kind]}'`) - ) - } - export function Create(kinds: TTemplateLiteralKind[]): string { - return `^${kinds.map((schema) => Visit(schema, '')).join('')}\$` - } -} -// ------------------------------------------------------------------------------ -// TemplateLiteralResolver -// ------------------------------------------------------------------------------ -export namespace TemplateLiteralResolver { - /** Resolves a template literal as a TUnion */ - export function Resolve(template: TTemplateLiteral): TNever | TString | TUnion { - const expression = TemplateLiteralParser.ParseExact(template.pattern) - if (!TemplateLiteralFinite.Check(expression)) return Type.String() - const literals = [...TemplateLiteralGenerator.Generate(expression)].map((value) => Type.Literal(value)) - return Type.Union(literals) - } -} -// ------------------------------------------------------------------------------ -// TemplateLiteralParser -// ------------------------------------------------------------------------------ -export class TemplateLiteralParserError extends TypeBoxError {} -// prettier-ignore -export namespace TemplateLiteralParser { - export type Expression = And | Or | Const - export type Const = { type: 'const'; const: string } - export type And = { type: 'and'; expr: Expression[] } - export type Or = { type: 'or'; expr: Expression[] } - function IsNonEscaped(pattern: string, index: number, char: string) { - return pattern[index] === char && pattern.charCodeAt(index - 1) !== 92 - } - function IsOpenParen(pattern: string, index: number) { - return IsNonEscaped(pattern, index, '(') - } - function IsCloseParen(pattern: string, index: number) { - return IsNonEscaped(pattern, index, ')') - } - function IsSeparator(pattern: string, index: number) { - return IsNonEscaped(pattern, index, '|') - } - function IsGroup(pattern: string) { - if (!(IsOpenParen(pattern, 0) && IsCloseParen(pattern, pattern.length - 1))) return false - let count = 0 - for (let index = 0; index < pattern.length; index++) { - if (IsOpenParen(pattern, index)) count += 1 - if (IsCloseParen(pattern, index)) count -= 1 - if (count === 0 && index !== pattern.length - 1) return false - } - return true - } - function InGroup(pattern: string) { - return pattern.slice(1, pattern.length - 1) - } - function IsPrecedenceOr(pattern: string) { - let count = 0 - for (let index = 0; index < pattern.length; index++) { - if (IsOpenParen(pattern, index)) count += 1 - if (IsCloseParen(pattern, index)) count -= 1 - if (IsSeparator(pattern, index) && count === 0) return true - } - return false - } - function IsPrecedenceAnd(pattern: string) { - for (let index = 0; index < pattern.length; index++) { - if (IsOpenParen(pattern, index)) return true - } - return false - } - function Or(pattern: string): Expression { - let [count, start] = [0, 0] - const expressions: Expression[] = [] - for (let index = 0; index < pattern.length; index++) { - if (IsOpenParen(pattern, index)) count += 1 - if (IsCloseParen(pattern, index)) count -= 1 - if (IsSeparator(pattern, index) && count === 0) { - const range = pattern.slice(start, index) - if (range.length > 0) expressions.push(Parse(range)) - start = index + 1 - } - } - const range = pattern.slice(start) - if (range.length > 0) expressions.push(Parse(range)) - if (expressions.length === 0) return { type: 'const', const: '' } - if (expressions.length === 1) return expressions[0] - return { type: 'or', expr: expressions } - } - function And(pattern: string): Expression { - function Group(value: string, index: number): [number, number] { - if (!IsOpenParen(value, index)) throw new TemplateLiteralParserError(`TemplateLiteralParser: Index must point to open parens`) - let count = 0 - for (let scan = index; scan < value.length; scan++) { - if (IsOpenParen(value, scan)) count += 1 - if (IsCloseParen(value, scan)) count -= 1 - if (count === 0) return [index, scan] - } - throw new TemplateLiteralParserError(`TemplateLiteralParser: Unclosed group parens in expression`) - } - function Range(pattern: string, index: number): [number, number] { - for (let scan = index; scan < pattern.length; scan++) { - if (IsOpenParen(pattern, scan)) return [index, scan] - } - return [index, pattern.length] - } - const expressions: Expression[] = [] - for (let index = 0; index < pattern.length; index++) { - if (IsOpenParen(pattern, index)) { - const [start, end] = Group(pattern, index) - const range = pattern.slice(start, end + 1) - expressions.push(Parse(range)) - index = end - } else { - const [start, end] = Range(pattern, index) - const range = pattern.slice(start, end) - if (range.length > 0) expressions.push(Parse(range)) - index = end - 1 - } - } - return ( - (expressions.length === 0) ? { type: 'const', const: '' } : - (expressions.length === 1) ? expressions[0] : - { type: 'and', expr: expressions } - ) - } - /** Parses a pattern and returns an expression tree */ - export function Parse(pattern: string): Expression { - return ( - IsGroup(pattern) ? Parse(InGroup(pattern)) : - IsPrecedenceOr(pattern) ? Or(pattern) : - IsPrecedenceAnd(pattern) ? And(pattern) : - { type: 'const', const: pattern } - ) - } - /** Parses a pattern and strips forward and trailing ^ and $ */ - export function ParseExact(pattern: string): Expression { - return Parse(pattern.slice(1, pattern.length - 1)) - } -} -// ------------------------------------------------------------------------------ -// TemplateLiteralFinite -// ------------------------------------------------------------------------------ -export class TemplateLiteralFiniteError extends TypeBoxError {} -// prettier-ignore -export namespace TemplateLiteralFinite { - function Throw(message: string): never { - throw new TemplateLiteralFiniteError(message) - } - function IsNumber(expression: TemplateLiteralParser.Expression): boolean { - return ( - expression.type === 'or' && - expression.expr.length === 2 && - expression.expr[0].type === 'const' && - expression.expr[0].const === '0' && - expression.expr[1].type === 'const' && - expression.expr[1].const === '[1-9][0-9]*' - ) - } - function IsBoolean(expression: TemplateLiteralParser.Expression): boolean { - return ( - expression.type === 'or' && - expression.expr.length === 2 && - expression.expr[0].type === 'const' && - expression.expr[0].const === 'true' && - expression.expr[1].type === 'const' && - expression.expr[1].const === 'false' - ) - } - function IsString(expression: TemplateLiteralParser.Expression) { - return expression.type === 'const' && expression.const === '.*' - } - export function Check(expression: TemplateLiteralParser.Expression): boolean { - return ( - IsBoolean(expression) ? true : - IsNumber(expression) || IsString(expression) ? false : - (expression.type === 'and') ? expression.expr.every((expr) => Check(expr)) : - (expression.type === 'or') ? expression.expr.every((expr) => Check(expr)) : - (expression.type === 'const') ? true : - Throw(`Unknown expression type`) - ) - } -} -// ------------------------------------------------------------------------------ -// TemplateLiteralGenerator -// ------------------------------------------------------------------------------ -export class TemplateLiteralGeneratorError extends TypeBoxError {} -// prettier-ignore -export namespace TemplateLiteralGenerator { - function* Reduce(buffer: string[][]): IterableIterator { - if (buffer.length === 1) return yield* buffer[0] - for (const left of buffer[0]) { - for (const right of Reduce(buffer.slice(1))) { - yield `${left}${right}` - } - } - } - function* And(expression: TemplateLiteralParser.And): IterableIterator { - return yield* Reduce(expression.expr.map((expr) => [...Generate(expr)])) - } - function* Or(expression: TemplateLiteralParser.Or): IterableIterator { - for (const expr of expression.expr) yield* Generate(expr) - } - function* Const(expression: TemplateLiteralParser.Const): IterableIterator { - return yield expression.const - } - export function* Generate(expression: TemplateLiteralParser.Expression): IterableIterator { - return ( - expression.type === 'and' ? yield* And(expression) : - expression.type === 'or' ? yield* Or(expression) : - expression.type === 'const' ? yield* Const(expression) : - (() => { throw new TemplateLiteralGeneratorError('Unknown expression') })() - ) - } -} -// --------------------------------------------------------------------- -// TemplateLiteralDslParser -// --------------------------------------------------------------------- -// prettier-ignore -export namespace TemplateLiteralDslParser { - function* ParseUnion(template: string): IterableIterator { - const trim = template.trim().replace(/"|'/g, '') - return ( - trim === 'boolean' ? yield Type.Boolean() : - trim === 'number' ? yield Type.Number() : - trim === 'bigint' ? yield Type.BigInt() : - trim === 'string' ? yield Type.String() : - yield (() => { - const literals = trim.split('|').map((literal) => Type.Literal(literal.trim())) - return ( - literals.length === 0 ? Type.Never() : - literals.length === 1 ? literals[0] : - Type.Union(literals) - ) - })() - ) - } - function* ParseTerminal(template: string): IterableIterator { - if (template[1] !== '{') { - const L = Type.Literal('$') - const R = ParseLiteral(template.slice(1)) - return yield* [L, ...R] - } - for (let i = 2; i < template.length; i++) { - if (template[i] === '}') { - const L = ParseUnion(template.slice(2, i)) - const R = ParseLiteral(template.slice(i + 1)) - return yield* [...L, ...R] - } - } - yield Type.Literal(template) - } - function* ParseLiteral(template: string): IterableIterator { - for (let i = 0; i < template.length; i++) { - if (template[i] === '$') { - const L = Type.Literal(template.slice(0, i)) - const R = ParseTerminal(template.slice(i)) - return yield* [L, ...R] - } - } - yield Type.Literal(template) - } - export function Parse(template_dsl: string): TTemplateLiteralKind[] { - return [...ParseLiteral(template_dsl)] - } -} + + // --------------------------------------------------------------------- // TransformBuilder // --------------------------------------------------------------------- @@ -3554,7 +2530,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 = KeyResolver.Resolve(intersect) + const keys = KeyOfStringResolver.Resolve(intersect) const properties = keys.reduce((acc, key) => ({ ...acc, [key]: Type.Index(intersect, [key]) }), {} as TProperties) return Type.Object(properties, options) as TComposite } @@ -3604,13 +2580,13 @@ export class JsonTypeBuilder extends TypeBuilder { ) as TExtract } /** `[Json]` Returns an Indexed property type for the given keys */ - public Index>(T: T, keys: K, options?: SchemaOptions): IndexAccessResolver.Resolve + public Index>(T: T, keys: K, options?: SchemaOptions): TIndex /** `[Json]` Returns an Indexed property type for the given keys */ - public Index(T: T, K: [...K], options?: SchemaOptions): IndexAccessResolver.Resolve + public Index(T: T, K: [...K], options?: SchemaOptions): TIndex /** `[Json]` Returns an Indexed property type for the given keys */ public Index(T: TSchema, K: unknown, options: SchemaOptions = {}): any { - const keys = TypeGuard.TSchema(K) ? IndexResolver.Resolve(K) : K as string[] - const type = IndexAccessResolver.Resolve(T, keys) + const keys = TypeGuard.TSchema(K) ? IndexedKeyResolver.Resolve(K) : K as string[] + const type = IndexedAccessResolver.Resolve(T, keys) return TypeClone.Type(type, options) } /** `[Json]` Creates an Integer type */ @@ -3638,7 +2614,7 @@ export class JsonTypeBuilder extends TypeBuilder { : this.Create({ ...options, ...clonedUnevaluatedProperties, [Kind]: 'Intersect', allOf: cloned }) } /** `[Json]` Creates a KeyOf type */ - public KeyOf(schema: T, options: SchemaOptions = {}): KeyOfResolver.Resolve { + public KeyOf(schema: T, options: SchemaOptions = {}): TKeyOf { const type = KeyOfResolver.Resolve(schema) return TypeClone.Type(type, options) } @@ -3744,7 +2720,7 @@ export class JsonTypeBuilder extends TypeBuilder { : this.Create({ ...options, [Kind]: 'Record', type: 'object', patternProperties: { [key.pattern]: TypeClone.Type(schema) } }) })() : TypeGuard.TUnion(key) ? (() => { - const union = UnionResolver.Resolve(key) + const union = UnionResolverRecord.Resolve(key) if (TypeGuard.TUnionLiteral(union)) { const properties = union.anyOf.reduce((acc: any, literal: any) => ({ ...acc, [literal.const]: TypeClone.Type(schema) }), {} as TProperties) return this.Object(properties, { ...options, [Hint]: 'Record' }) diff --git a/src/value/check.ts b/src/value/check.ts index 7e987708c..640322f15 100644 --- a/src/value/check.ts +++ b/src/value/check.ts @@ -27,6 +27,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import { IsArray, IsUint8Array, IsDate, IsPromise, IsFunction, IsAsyncIterator, IsIterator, IsBoolean, IsNumber, IsBigInt, IsString, IsSymbol, IsInteger, IsNull, IsUndefined } from './guard' +import { KeyOfStringResolver } from '../resolvers' import { TypeSystemPolicy } from '../system/index' import { Deref } from './deref' import { Hash } from './hash' @@ -164,11 +165,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.KeyResolver.ResolvePattern(schema)) + const keyPattern = new RegExp(KeyOfStringResolver.ResolvePattern(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.KeyResolver.ResolvePattern(schema)) + const keyCheck = new RegExp(KeyOfStringResolver.ResolvePattern(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/create.ts b/src/value/create.ts index b36b42f41..d7f5e11d5 100644 --- a/src/value/create.ts +++ b/src/value/create.ts @@ -26,6 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ +import { TemplateLiteralFinite, TemplateLiteralGenerator, TemplateLiteralParser } from '../resolvers' import { HasPropertyKey, IsString } from './guard' import { Check } from './check' import { Deref } from './deref' @@ -287,9 +288,9 @@ function TTemplateLiteral(schema: Types.TTemplateLiteral, references: Types.TSch if (HasPropertyKey(schema, 'default')) { return schema.default } - const expression = Types.TemplateLiteralParser.ParseExact(schema.pattern) - if (!Types.TemplateLiteralFinite.Check(expression)) throw new ValueCreateTempateLiteralTypeError(schema) - const sequence = Types.TemplateLiteralGenerator.Generate(expression) + const expression = TemplateLiteralParser.ParseExact(schema.pattern) + if (!TemplateLiteralFinite.Check(expression)) throw new ValueCreateTempateLiteralTypeError(schema) + const sequence = TemplateLiteralGenerator.Generate(expression) return sequence.next().value } function TThis(schema: Types.TThis, references: Types.TSchema[]): any { diff --git a/src/value/transform.ts b/src/value/transform.ts index 33545e963..6c1ca4f09 100644 --- a/src/value/transform.ts +++ b/src/value/transform.ts @@ -27,6 +27,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import { IsString, IsPlainObject, IsArray, IsValueType, IsUndefined } from './guard' +import { IndexedAccessResolver, KeyOfStringResolver } from '../resolvers/index' import { ValueError } from '../errors/errors' import { Deref } from './deref' import { Check } from './check' @@ -174,10 +175,10 @@ 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.KeyResolver.Resolve(schema) as string[] + const knownKeys = KeyOfStringResolver.Resolve(schema) as string[] const knownProperties = knownKeys.reduce((value, key) => { return (key in value) - ? { ...value, [key]: Visit(Types.IndexAccessResolver.Resolve(schema, [key]), references, value[key]) } + ? { ...value, [key]: Visit(IndexedAccessResolver.Resolve(schema, [key]), references, value[key]) } : value }, value) if (!Types.TypeGuard.TTransform(schema.unevaluatedProperties)) { @@ -198,7 +199,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.KeyResolver.Resolve(schema) + const knownKeys = KeyOfStringResolver.Resolve(schema) const knownProperties = knownKeys.reduce((value, key) => { return (key in value) ? { ...value, [key]: Visit(schema.properties[key], references, value[key]) } @@ -315,10 +316,10 @@ 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.KeyResolver.Resolve(schema) as string[] + const knownKeys = KeyOfStringResolver.Resolve(schema) as string[] const knownProperties = knownKeys.reduce((value, key) => { return key in defaulted - ? { ...value, [key]: Visit(Types.IndexAccessResolver.Resolve(schema, [key]), references, value[key]) } + ? { ...value, [key]: Visit(IndexedAccessResolver.Resolve(schema, [key]), references, value[key]) } : value }, defaulted) if (!Types.TypeGuard.TTransform(schema.unevaluatedProperties)) { @@ -339,7 +340,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.KeyResolver.Resolve(schema) as string[] + const knownKeys = KeyOfStringResolver.Resolve(schema) as string[] const knownProperties = knownKeys.reduce((value, key) => { return key in value ? { ...value, [key]: Visit(schema.properties[key], references, value[key]) } diff --git a/test/runtime/type/template/finite.ts b/test/runtime/type/template/finite.ts index 53028dff9..ca5f55ba7 100644 --- a/test/runtime/type/template/finite.ts +++ b/test/runtime/type/template/finite.ts @@ -1,4 +1,5 @@ -import { PatternString, PatternBoolean, PatternNumber, TemplateLiteralParser, TemplateLiteralFinite } from '@sinclair/typebox' +import { PatternString, PatternBoolean, PatternNumber } from '@sinclair/typebox' +import { TemplateLiteralParser, TemplateLiteralFinite } from '@sinclair/typebox/resolvers' import { Assert } from '../../assert/index' describe('type/templateliteral/TemplateLiteralFinite', () => { diff --git a/test/runtime/type/template/generate.ts b/test/runtime/type/template/generate.ts index 73aa9cfc3..7c8b86ab3 100644 --- a/test/runtime/type/template/generate.ts +++ b/test/runtime/type/template/generate.ts @@ -1,4 +1,4 @@ -import { TemplateLiteralParser, TemplateLiteralGenerator } from '@sinclair/typebox' +import { TemplateLiteralParser, TemplateLiteralGenerator } from '@sinclair/typebox/resolvers' import { Assert } from '../../assert/index' describe('type/templateliteral/TemplateLiteralGenerator', () => { diff --git a/test/runtime/type/template/parser.ts b/test/runtime/type/template/parser.ts index 5ab50afbf..75b9f15ff 100644 --- a/test/runtime/type/template/parser.ts +++ b/test/runtime/type/template/parser.ts @@ -1,4 +1,4 @@ -import { TemplateLiteralParser } from '@sinclair/typebox' +import { TemplateLiteralParser } from '@sinclair/typebox/resolvers' import { Assert } from '../../assert/index' describe('type/templateliteral/TemplateLiteralParser', () => { diff --git a/tsconfig.json b/tsconfig.json index ec148af39..056e55a86 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,9 @@ "@sinclair/typebox/errors": [ "src/errors/index.ts" ], + "@sinclair/typebox/resolvers": [ + "src/resolvers/index.ts" + ], "@sinclair/typebox/system": [ "src/system/index.ts" ],