diff --git a/changelog/0.34.0.md b/changelog/0.34.0.md index b10199e2..f2d9ade1 100644 --- a/changelog/0.34.0.md +++ b/changelog/0.34.0.md @@ -1,6 +1,8 @@ ### 0.34.0 +- [Revision 0.34.21](https://github.com/sinclairzx81/typebox/pull/1168) + - Reimplement Computed Record Types - [Revision 0.34.20](https://github.com/sinclairzx81/typebox/pull/1167) - - Hotfix: Disable Computed Types on Record + - Hotfix: Disable Computed Record Types - [Revision 0.34.19](https://github.com/sinclairzx81/typebox/pull/1166) - Hotfix: Republished due to NPM error - [Revision 0.34.18](https://github.com/sinclairzx81/typebox/pull/1164) diff --git a/package-lock.json b/package-lock.json index d77b8c15..27a2d1f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.34.20", + "version": "0.34.21", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.34.20", + "version": "0.34.21", "license": "MIT", "devDependencies": { "@arethetypeswrong/cli": "^0.13.2", diff --git a/package.json b/package.json index 7c918efa..90ccd20e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.34.20", + "version": "0.34.21", "description": "Json Schema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/src/type/module/compute.ts b/src/type/module/compute.ts index 9ca66adf..abd138af 100644 --- a/src/type/module/compute.ts +++ b/src/type/module/compute.ts @@ -27,6 +27,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import { CreateType } from '../create/index' +import { CloneType } from '../clone/index' import { Discard } from '../discard/index' import { Ensure, Evaluate } from '../helpers/index' import { type TSchema } from '../schema/index' @@ -48,7 +49,7 @@ import { Pick, type TPick } from '../pick/index' import { Never, type TNever } from '../never/index' import { Partial, TPartial } from '../partial/index' import { type TReadonly } from '../readonly/index' -import { Record, type TRecordOrObject } from '../record/index' +import { RecordValue, RecordPattern, type TRecordOrObject, type TRecord } from '../record/index' import { type TRef } from '../ref/index' import { Required, TRequired } from '../required/index' import { Tuple, type TTuple } from '../tuple/index' @@ -177,17 +178,6 @@ function FromPick(parameters: Parameters): TFromPi return Pick(parameters[0], parameters[1]) as never } // ------------------------------------------------------------------ -// Record -// ------------------------------------------------------------------ -// prettier-ignore -type TFromRecord = ( - Parameters extends [infer T0 extends TSchema, infer T1 extends TSchema] ? TRecordOrObject : never -) -// prettier-ignore -function FromRecord(parameters: Parameters): TFromPick { - return Record(parameters[0], parameters[1]) as never -} -// ------------------------------------------------------------------ // Required // ------------------------------------------------------------------ // prettier-ignore @@ -211,7 +201,6 @@ type TFromComputed : Target extends 'Omit' ? TFromOmit : Target extends 'Pick' ? TFromPick : - Target extends 'Record' ? TFromRecord : Target extends 'Required' ? TFromRequired : TNever ) @@ -225,7 +214,6 @@ function FromComputed> +> = Result +// prettier-ignore +function FromRecord(moduleProperties: ModuleProperties, type: Type): never { + const [value, pattern] = [FromType(moduleProperties, RecordValue(type)), RecordPattern(type)] + const result = CloneType(type) + result.patternProperties[pattern] = value + return result as never +} +// ------------------------------------------------------------------ // Constructor // ------------------------------------------------------------------ // prettier-ignore @@ -363,6 +370,7 @@ export type TFromType ? TFromIntersect : Type extends TIterator ? TFromIterator : Type extends TObject ? TFromObject : + Type extends TRecord ? TFromRecord : Type extends TTuple ? TFromTuple : Type extends TEnum ? Type : // intercept enum before union Type extends TUnion ? TFromUnion : @@ -383,6 +391,7 @@ export function FromType = ( - Type extends TRecord - ? ( - Key extends TNumber ? TNumber : - Key extends TString ? TString : - TString - ) : TString -) +export type TRecordKey ? ( + Key extends TNumber ? TNumber : + Key extends TString ? TString : + TString + ) : TString +> = Result /** Gets the Records Key Type */ // prettier-ignore export function RecordKey(type: Type): TRecordKey { @@ -300,9 +299,13 @@ export function RecordKey(type: Type): TRecordKey { // ------------------------------------------------------------------ /** Gets a Record Value Type */ // prettier-ignore -export type TRecordValue = ( - Type extends TRecord ? Value : TNever -) +export type TRecordValue + ? Value + : TNever + ) +> = Result /** Gets a Record Value Type */ // prettier-ignore export function RecordValue(type: Type): TRecordValue { diff --git a/test/runtime/type/guard/kind/import.ts b/test/runtime/type/guard/kind/import.ts index 529cd5f6..03921fe9 100644 --- a/test/runtime/type/guard/kind/import.ts +++ b/test/runtime/type/guard/kind/import.ts @@ -151,17 +151,28 @@ describe('guard/kind/TImport', () => { Assert.IsTrue(KindGuard.IsRef(T.$defs['R'].patternProperties['^(.*)$'])) Assert.IsTrue(T.$defs['R'].patternProperties['^(.*)$'].$ref === 'T') }) - // it('Should compute for Record 2', () => { - // const Module = Type.Module({ - // T: Type.Number(), - // K: Type.Union([Type.Literal('x'), Type.Literal('y')]), - // R: Type.Record(Type.Ref('K'), Type.Ref('T')), - // }) - // const T = Module.Import('R') - // Assert.IsTrue(KindGuard.IsObject(T.$defs['R'])) - // Assert.IsTrue(KindGuard.IsNumber(T.$defs['R'].properties.x)) - // Assert.IsTrue(KindGuard.IsNumber(T.$defs['R'].properties.x)) - // }) + it('Should compute for Record 2', () => { + const Module = Type.Module({ + T: Type.Number(), + R: Type.Record(Type.Union([Type.Literal('x'), Type.Literal('y')]), Type.Ref('T')), + }) + // Retain reference if not computed + const T = Module.Import('R') + Assert.IsTrue(KindGuard.IsObject(T.$defs['R'])) + Assert.IsTrue(KindGuard.IsRef(T.$defs['R'].properties.x)) + Assert.IsTrue(KindGuard.IsRef(T.$defs['R'].properties.y)) + }) + it('Should compute for Record 3', () => { + const Module = Type.Module({ + T: Type.Object({ x: Type.Number() }), + R: Type.Record(Type.Union([Type.Literal('x'), Type.Literal('y')]), Type.Partial(Type.Ref('T'))), + }) + // Dereference if computed + const T = Module.Import('R') + Assert.IsTrue(KindGuard.IsObject(T.$defs['R'])) + Assert.IsTrue(KindGuard.IsObject(T.$defs['R'].properties.x)) + Assert.IsTrue(KindGuard.IsObject(T.$defs['R'].properties.y)) + }) // ---------------------------------------------------------------- // Computed: Required // ---------------------------------------------------------------- diff --git a/test/runtime/type/guard/type/import.ts b/test/runtime/type/guard/type/import.ts index 8b0f3b2d..91080764 100644 --- a/test/runtime/type/guard/type/import.ts +++ b/test/runtime/type/guard/type/import.ts @@ -151,17 +151,28 @@ describe('guard/type/TImport', () => { Assert.IsTrue(TypeGuard.IsRef(T.$defs['R'].patternProperties['^(.*)$'])) Assert.IsTrue(T.$defs['R'].patternProperties['^(.*)$'].$ref === 'T') }) - // it('Should compute for Record 2', () => { - // const Module = Type.Module({ - // T: Type.Number(), - // K: Type.Union([Type.Literal('x'), Type.Literal('y')]), - // R: Type.Record(Type.Ref('K'), Type.Ref('T')), - // }) - // const T = Module.Import('R') - // Assert.IsTrue(TypeGuard.IsObject(T.$defs['R'])) - // Assert.IsTrue(TypeGuard.IsNumber(T.$defs['R'].properties.x)) - // Assert.IsTrue(TypeGuard.IsNumber(T.$defs['R'].properties.x)) - // }) + it('Should compute for Record 2', () => { + const Module = Type.Module({ + T: Type.Number(), + R: Type.Record(Type.Union([Type.Literal('x'), Type.Literal('y')]), Type.Ref('T')), + }) + // Retain reference if not computed + const T = Module.Import('R') + Assert.IsTrue(TypeGuard.IsObject(T.$defs['R'])) + Assert.IsTrue(TypeGuard.IsRef(T.$defs['R'].properties.x)) + Assert.IsTrue(TypeGuard.IsRef(T.$defs['R'].properties.y)) + }) + it('Should compute for Record 3', () => { + const Module = Type.Module({ + T: Type.Object({ x: Type.Number() }), + R: Type.Record(Type.Union([Type.Literal('x'), Type.Literal('y')]), Type.Partial(Type.Ref('T'))), + }) + // Dereference if computed + const T = Module.Import('R') + Assert.IsTrue(TypeGuard.IsObject(T.$defs['R'])) + Assert.IsTrue(TypeGuard.IsObject(T.$defs['R'].properties.x)) + Assert.IsTrue(TypeGuard.IsObject(T.$defs['R'].properties.y)) + }) // ---------------------------------------------------------------- // Computed: Required // ---------------------------------------------------------------- diff --git a/test/static/import.ts b/test/static/import.ts index 010c3d64..0f30704f 100644 --- a/test/static/import.ts +++ b/test/static/import.ts @@ -58,50 +58,17 @@ import { Type, Static } from '@sinclair/typebox' // ------------------------------------------------------------------ // prettier-ignore { - // const T = Type.Module({ - // R: Type.Object({ x: Type.Number(), y: Type.Number() }), - // T: Type.Record(Type.String(), Type.Partial(Type.Ref('R'))), - // }).Import('T') - - // type T = Static - // Expect(T).ToStatic<{ - // [key: string]: { x?: number, y?: number } - // }>() -} -// ------------------------------------------------------------------ -// Record 3 -// ------------------------------------------------------------------ -// prettier-ignore -{ - // const T = Type.Module({ - // R: Type.Object({ x: Type.Number(), y: Type.Number() }), - // K: Type.Number(), - // T: Type.Record(Type.Ref('K'), Type.Partial(Type.Ref('R'))), - // }).Import('T') + const T = Type.Module({ + R: Type.Object({ x: Type.Number(), y: Type.Number() }), + T: Type.Record(Type.String(), Type.Partial(Type.Ref('R'))), + }).Import('T') - // type T = Static - // Expect(T).ToStatic<{ - // [key: number]: { x?: number, y?: number } - // }>() + type T = Static + Expect(T).ToStatic<{ + [key: string]: { x?: number, y?: number } + }>() } // ------------------------------------------------------------------ -// Record 4 -// ------------------------------------------------------------------ -// prettier-ignore -// { -// const T = Type.Module({ -// R: Type.Object({ x: Type.Number(), y: Type.Number() }), -// K: Type.TemplateLiteral('${A|B|C}'), -// T: Type.Record(Type.Ref('K'), Type.Partial(Type.Ref('R'))), -// }).Import('T') -// type T = Static -// Expect(T).ToStatic<{ -// A: { x?: number, y?: number }, -// B: { x?: number, y?: number }, -// C: { x?: number, y?: number } -// }>() -// } -// ------------------------------------------------------------------ // Modifiers 1 // ------------------------------------------------------------------ // prettier-ignore