diff --git a/package-lock.json b/package-lock.json index fb6e03401..88a7f79c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.31.15", + "version": "0.31.16", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.31.15", + "version": "0.31.16", "license": "MIT", "devDependencies": { "@sinclair/hammer": "^0.17.1", diff --git a/package.json b/package.json index 4ecd1237b..fe958029c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.31.15", + "version": "0.31.16", "description": "JSONSchema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/src/typebox.ts b/src/typebox.ts index 3e4f0c420..4514b9c80 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -656,11 +656,11 @@ export type TRecordFromUnionRest = K ext L extends TLiteralNumber ? TRecordFromUnionLiteralNumber & TRecordFromUnionRest, T> : {}) : {} export type TRecordFromUnion = Ensure>>>> -export type TRecordFromTemplateLiteralKeyInfinite = Ensure> +export type TRecordFromTemplateLiteralKeyInfinite = Ensure> export type TRecordFromTemplateLiteralKeyFinite> = Ensure]: T }>>> // prettier-ignore export type TRecordFromTemplateLiteralKey = IsTemplateLiteralFinite extends false - ? TRecordFromTemplateLiteralKeyInfinite + ? TRecordFromTemplateLiteralKeyInfinite : TRecordFromTemplateLiteralKeyFinite export type TRecordFromLiteralStringKey = Ensure> export type TRecordFromLiteralNumberKey = Ensure> diff --git a/test/runtime/compiler-ajv/record.ts b/test/runtime/compiler-ajv/record.ts index 082a58fb2..75b739b9c 100644 --- a/test/runtime/compiler-ajv/record.ts +++ b/test/runtime/compiler-ajv/record.ts @@ -175,4 +175,67 @@ describe('compiler-ajv/Record', () => { const T = Type.Record(Type.Number(), Type.String(), { additionalProperties: Type.Boolean() }) Ok(T, { 1: '', 2: '', x: true }) }) + // ---------------------------------------------------------------- + // TemplateLiteral + // ---------------------------------------------------------------- + it('TemplateLiteral 1', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number(), { additionalProperties: false }) + Ok(R, { + key0: 1, + key1: 1, + key2: 1, + }) + }) + it('TemplateLiteral 2', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + Ok(R, { keyA: 0 }) + }) + it('TemplateLiteral 3', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number(), { additionalProperties: false }) + Fail(R, { keyA: 0 }) + }) + it('TemplateLiteral 4', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + const T = Type.Object({ x: Type.Number(), y: Type.Number() }) + const I = Type.Intersect([R, T], { unevaluatedProperties: false }) + Ok(I, { + x: 1, + y: 2, + key0: 1, + key1: 1, + key2: 1, + }) + }) + it('TemplateLiteral 5', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + const T = Type.Object({ x: Type.Number(), y: Type.Number() }) + const I = Type.Intersect([R, T]) + Ok(I, { + x: 1, + y: 2, + z: 3, + key0: 1, + key1: 1, + key2: 1, + }) + }) + it('TemplateLiteral 6', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + const T = Type.Object({ x: Type.Number(), y: Type.Number() }) + const I = Type.Intersect([R, T], { unevaluatedProperties: false }) + Fail(I, { + x: 1, + y: 2, + z: 3, + key0: 1, + key1: 1, + key2: 1, + }) + }) }) diff --git a/test/runtime/compiler/record.ts b/test/runtime/compiler/record.ts index 89368d724..8b7cfbb65 100644 --- a/test/runtime/compiler/record.ts +++ b/test/runtime/compiler/record.ts @@ -206,4 +206,67 @@ describe('compiler/Record', () => { const T = Type.Record(Type.Number(), Type.String(), { additionalProperties: Type.Boolean() }) Ok(T, { 1: '', 2: '', x: true }) }) + // ---------------------------------------------------------------- + // TemplateLiteral + // ---------------------------------------------------------------- + it('TemplateLiteral 1', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number(), { additionalProperties: false }) + Ok(R, { + key0: 1, + key1: 1, + key2: 1, + }) + }) + it('TemplateLiteral 2', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + Ok(R, { keyA: 0 }) + }) + it('TemplateLiteral 3', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number(), { additionalProperties: false }) + Fail(R, { keyA: 0 }) + }) + it('TemplateLiteral 4', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + const T = Type.Object({ x: Type.Number(), y: Type.Number() }) + const I = Type.Intersect([R, T], { unevaluatedProperties: false }) + Ok(I, { + x: 1, + y: 2, + key0: 1, + key1: 1, + key2: 1, + }) + }) + it('TemplateLiteral 5', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + const T = Type.Object({ x: Type.Number(), y: Type.Number() }) + const I = Type.Intersect([R, T]) + Ok(I, { + x: 1, + y: 2, + z: 3, + key0: 1, + key1: 1, + key2: 1, + }) + }) + it('TemplateLiteral 6', () => { + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + const T = Type.Object({ x: Type.Number(), y: Type.Number() }) + const I = Type.Intersect([R, T], { unevaluatedProperties: false }) + Fail(I, { + x: 1, + y: 2, + z: 3, + key0: 1, + key1: 1, + key2: 1, + }) + }) }) diff --git a/test/static/record.ts b/test/static/record.ts index 9a2ccb639..819c0ad27 100644 --- a/test/static/record.ts +++ b/test/static/record.ts @@ -70,7 +70,6 @@ import { Type, Static } from '@sinclair/typebox' Expect(T).ToStatic>() } - { enum E { A = 'X', @@ -80,3 +79,19 @@ import { Type, Static } from '@sinclair/typebox' const T = Type.Record(Type.Enum(E), Type.Number()) Expect(T).ToStatic<{}>() } +{ + // should support infinite record keys + // https://github.com/sinclairzx81/typebox/issues/604 + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + Expect(R).ToStatic>() +} +{ + // should support infinite record keys with intersect + // https://github.com/sinclairzx81/typebox/issues/604 + const K = Type.TemplateLiteral('key${number}') + const R = Type.Record(K, Type.Number()) + const T = Type.Object({ x: Type.Number(), y: Type.Number() }) + const I = Type.Intersect([R, T]) + Expect(I).ToStatic & { x: number; y: number }>() +}