Skip to content

Commit

Permalink
Revision 0.34.21 (#1168)
Browse files Browse the repository at this point in the history
* Reimplement Computed Record Type

* Version
  • Loading branch information
sinclairzx81 authored Feb 13, 2025
1 parent bf4391f commit 1bd8f78
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 92 deletions.
4 changes: 3 additions & 1 deletion changelog/0.34.0.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
37 changes: 23 additions & 14 deletions src/type/module/compute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -177,17 +178,6 @@ function FromPick<Parameters extends TSchema[]>(parameters: Parameters): TFromPi
return Pick(parameters[0], parameters[1]) as never
}
// ------------------------------------------------------------------
// Record
// ------------------------------------------------------------------
// prettier-ignore
type TFromRecord<Parameters extends TSchema[]> = (
Parameters extends [infer T0 extends TSchema, infer T1 extends TSchema] ? TRecordOrObject<T0, T1> : never
)
// prettier-ignore
function FromRecord<Parameters extends TSchema[]>(parameters: Parameters): TFromPick<Parameters> {
return Record(parameters[0], parameters[1]) as never
}
// ------------------------------------------------------------------
// Required
// ------------------------------------------------------------------
// prettier-ignore
Expand All @@ -211,7 +201,6 @@ type TFromComputed<ModuleProperties extends TProperties, Target extends string,
Target extends 'Partial' ? TFromPartial<Dereferenced> :
Target extends 'Omit' ? TFromOmit<Dereferenced> :
Target extends 'Pick' ? TFromPick<Dereferenced> :
Target extends 'Record' ? TFromRecord<Dereferenced> :
Target extends 'Required' ? TFromRequired<Dereferenced> :
TNever
)
Expand All @@ -225,7 +214,6 @@ function FromComputed<ModuleProperties extends TProperties, Target extends strin
target === 'Partial' ? FromPartial(dereferenced) :
target === 'Omit' ? FromOmit(dereferenced) :
target === 'Pick' ? FromPick(dereferenced) :
target === 'Record' ? FromRecord(dereferenced) :
target === 'Required' ? FromRequired(dereferenced) :
Never()
) as never
Expand All @@ -245,6 +233,25 @@ function FromObject<ModuleProperties extends TProperties, Properties extends TPr
) as never
}
// ------------------------------------------------------------------
// Record
//
// Note: Varying Runtime and Static path here as we need to retain
// constraints on the Record. This requires remapping the entire
// Record in the Runtime path but where the Static path is merely
// a facade for the patternProperties regular expression.
// ------------------------------------------------------------------
// prettier-ignore
type TFromRecord<ModuleProperties extends TProperties, Key extends TSchema, Value extends TSchema,
Result extends TSchema = TRecordOrObject<Key, TFromType<ModuleProperties, Value>>
> = Result
// prettier-ignore
function FromRecord<ModuleProperties extends TProperties, Type extends TRecord>(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
Expand Down Expand Up @@ -363,6 +370,7 @@ export type TFromType<ModuleProperties extends TProperties, Type extends TSchema
Type extends TIntersect<infer Types extends TSchema[]> ? TFromIntersect<ModuleProperties, Types> :
Type extends TIterator<infer Type extends TSchema> ? TFromIterator<ModuleProperties, Type> :
Type extends TObject<infer Properties extends TProperties> ? TFromObject<ModuleProperties, Properties> :
Type extends TRecord<infer Key extends TSchema, infer Value extends TSchema> ? TFromRecord<ModuleProperties, Key, Value> :
Type extends TTuple<infer Types extends TSchema[]> ? TFromTuple<ModuleProperties, Types> :
Type extends TEnum<infer _ extends TEnumRecord> ? Type : // intercept enum before union
Type extends TUnion<infer Types extends TSchema[]> ? TFromUnion<ModuleProperties, Types> :
Expand All @@ -383,6 +391,7 @@ export function FromType<ModuleProperties extends TProperties, Type extends TSch
KindGuard.IsIntersect(type) ? CreateType(FromIntersect(moduleProperties, type.allOf), type) :
KindGuard.IsIterator(type) ? CreateType(FromIterator(moduleProperties, type.items), type) :
KindGuard.IsObject(type) ? CreateType(FromObject(moduleProperties, type.properties), type) :
KindGuard.IsRecord(type) ? CreateType(FromRecord(moduleProperties, type)) :
KindGuard.IsTuple(type)? CreateType(FromTuple(moduleProperties, type.items || []), type) :
KindGuard.IsUnion(type) ? CreateType(FromUnion(moduleProperties, type.anyOf), type) :
type
Expand Down
25 changes: 14 additions & 11 deletions src/type/record/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,13 @@ export function RecordPattern(record: TRecord): string {
// ------------------------------------------------------------------
/** Gets the Records Key Type */
// prettier-ignore
export type TRecordKey<Type extends TRecord> = (
Type extends TRecord<infer Key extends TSchema, TSchema>
? (
Key extends TNumber ? TNumber :
Key extends TString ? TString :
TString
) : TString
)
export type TRecordKey<Type extends TRecord,
Result extends TSchema = Type extends TRecord<infer Key extends TSchema, TSchema> ? (
Key extends TNumber ? TNumber :
Key extends TString ? TString :
TString
) : TString
> = Result
/** Gets the Records Key Type */
// prettier-ignore
export function RecordKey<Type extends TRecord>(type: Type): TRecordKey<Type> {
Expand All @@ -300,9 +299,13 @@ export function RecordKey<Type extends TRecord>(type: Type): TRecordKey<Type> {
// ------------------------------------------------------------------
/** Gets a Record Value Type */
// prettier-ignore
export type TRecordValue<Type extends TRecord> = (
Type extends TRecord<TSchema, infer Value extends TSchema> ? Value : TNever
)
export type TRecordValue<Type extends TRecord,
Result extends TSchema = (
Type extends TRecord<TSchema, infer Value extends TSchema>
? Value
: TNever
)
> = Result
/** Gets a Record Value Type */
// prettier-ignore
export function RecordValue<Type extends TRecord>(type: Type): TRecordValue<Type> {
Expand Down
33 changes: 22 additions & 11 deletions test/runtime/type/guard/kind/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ----------------------------------------------------------------
Expand Down
33 changes: 22 additions & 11 deletions test/runtime/type/guard/type/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ----------------------------------------------------------------
Expand Down
49 changes: 8 additions & 41 deletions test/static/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof T>
// 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<typeof T>
// Expect(T).ToStatic<{
// [key: number]: { x?: number, y?: number }
// }>()
type T = Static<typeof T>
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<typeof T>
// Expect(T).ToStatic<{
// A: { x?: number, y?: number },
// B: { x?: number, y?: number },
// C: { x?: number, y?: number }
// }>()
// }
// ------------------------------------------------------------------
// Modifiers 1
// ------------------------------------------------------------------
// prettier-ignore
Expand Down

0 comments on commit 1bd8f78

Please sign in to comment.