From 07b33771e3315a2aa77dad40348f27c72ec58f55 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:03:28 +0200 Subject: [PATCH 01/23] fix(form-core): fix DeepValue from record values being wrong --- packages/form-core/src/util-types.ts | 35 ++++++++++++------ packages/form-core/tests/util-types.test-d.ts | 36 +++++++++++++++++++ 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 4049e1cbe..9aa71ee57 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -150,8 +150,18 @@ export type DeepKeysAndValuesImpl< ? DeepKeyAndValueObject : TAcc -export type DeepRecord = { - [TRecord in DeepKeysAndValues as TRecord['key']]: TRecord['value'] +type DeepRecordWithDynamicSuffix = { + // DeepKeys uses a dot as reserved character, so the only way that it can be a suffix + // is if the suffix is a dynamic variable. + [TRecord in DeepKeysAndValues as `${TRecord['key']}.` extends TRecord['key'] + ? TRecord['key'] + : never]: TRecord['value'] +} + +type DeepRecordWithStaticSuffix = { + [TRecord in DeepKeysAndValues as `${TRecord['key']}.` extends TRecord['key'] + ? never + : TRecord['key']]: TRecord['value'] } /** @@ -161,15 +171,6 @@ export type DeepKeys = unknown extends T ? string : DeepKeysAndValues['key'] -/** - * Infer the type of a deeply nested property within an object or an array. - */ -export type DeepValue = unknown extends TValue - ? TValue - : TAccessor extends DeepKeys - ? DeepRecord[TAccessor] - : never - /** * The keys of an object or array, deeply nested and only with a value of TValue */ @@ -177,3 +178,15 @@ export type DeepKeysOfType = Extract< DeepKeysAndValues, AnyDeepKeyAndValue >['key'] + +type PickValue = T[K] +/** + * Infer the type of a deeply nested property within an object or an array. + */ +export type DeepValue = unknown extends TValue + ? TValue + : TAccessor extends keyof DeepRecordWithStaticSuffix + ? PickValue, TAccessor> + : TAccessor extends keyof DeepRecordWithDynamicSuffix + ? PickValue, TAccessor> + : never diff --git a/packages/form-core/tests/util-types.test-d.ts b/packages/form-core/tests/util-types.test-d.ts index 67086c745..813f6b50e 100644 --- a/packages/form-core/tests/util-types.test-d.ts +++ b/packages/form-core/tests/util-types.test-d.ts @@ -446,3 +446,39 @@ type AnyObjectExample4 = DeepValue expectTypeOf(0 as never as AnyObjectExample4).toEqualTypeOf() type AnyObjectExample5 = DeepValue expectTypeOf(0 as never as AnyObjectExample5).toEqualTypeOf() + +type RecordExample = { + records: Record +} + +expectTypeOf>().toEqualTypeOf< + | 'records' + | `records.${string}` + | `records.${string}.a` + | `records.${string}.b` + | `records.${string}.c` + | `records.${string}.c.d` +>() +expectTypeOf>().toEqualTypeOf< + `records.${string}.a` | `records.${string}.c.d` +>() +expectTypeOf>().toEqualTypeOf< + Record +>() +expectTypeOf>().toEqualTypeOf<{ + a: string + b: number + c: { d: string } +}>() +expectTypeOf< + DeepValue +>().toEqualTypeOf() +expectTypeOf< + DeepValue +>().toEqualTypeOf() +expectTypeOf>().toEqualTypeOf<{ + d: string +}>() +expectTypeOf< + DeepValue +>().toEqualTypeOf() From 4ae504895977539a9a885a532e7508fbc85b88ba Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:16:20 +0100 Subject: [PATCH 02/23] fix: make deepValue union instead of intersection --- packages/form-core/package.json | 4 +- packages/form-core/src/util-types.ts | 31 +++++++------- packages/form-core/tests/util-types.test-d.ts | 42 +++++++++++-------- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/packages/form-core/package.json b/packages/form-core/package.json index 87850df90..bea575c89 100644 --- a/packages/form-core/package.json +++ b/packages/form-core/package.json @@ -22,7 +22,7 @@ "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", - "test:types:ts58": "tsc", + "test:types:ts58": "tsc --extendedDiagnostics", "test:lib": "vitest", "test:lib:dev": "pnpm run test:lib --watch", "test:build": "publint --strict", @@ -60,4 +60,4 @@ "valibot": "^1.1.0", "zod": "^3.25.76" } -} +} \ No newline at end of file diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 11036d4f9..a5e90f30d 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -150,20 +150,6 @@ export type DeepKeysAndValuesImpl< ? DeepKeyAndValueObject : TAcc -type DeepRecordWithDynamicSuffix = { - // DeepKeys uses a dot as reserved character, so the only way that it can be a suffix - // is if the suffix is a dynamic variable. - [TRecord in DeepKeysAndValues as `${TRecord['key']}.` extends TRecord['key'] - ? TRecord['key'] - : never]: TRecord['value'] -} - -type DeepRecordWithStaticSuffix = { - [TRecord in DeepKeysAndValues as `${TRecord['key']}.` extends TRecord['key'] - ? never - : TRecord['key']]: TRecord['value'] -} - /** * The keys of an object or array, deeply nested. */ @@ -171,6 +157,23 @@ export type DeepKeys = unknown extends T ? string : DeepKeysAndValues['key'] +export type ValueOfKey< + TValue extends AnyDeepKeyAndValue, + TAccessor extends string, +> = + TValue extends AnyDeepKeyAndValue + ? TAccessor extends ValueKey + ? TValue['value'] + : never + : never + +/** + * Infer the type of a deeply nested property within an object or an array. + */ +export type DeepValue = unknown extends TValue + ? TValue + : ValueOfKey, TAccessor> + /** * The keys of an object or array, deeply nested and only with a value of TValue */ diff --git a/packages/form-core/tests/util-types.test-d.ts b/packages/form-core/tests/util-types.test-d.ts index ae10b5ffe..79616d6f3 100644 --- a/packages/form-core/tests/util-types.test-d.ts +++ b/packages/form-core/tests/util-types.test-d.ts @@ -452,8 +452,13 @@ expectTypeOf(0 as never as AnyObjectExample4).toEqualTypeOf() type AnyObjectExample5 = DeepValue expectTypeOf(0 as never as AnyObjectExample5).toEqualTypeOf() +type RecordValue = { + a: string + b: number + c: { d: string } +} type RecordExample = { - records: Record + records: Record } expectTypeOf>().toEqualTypeOf< @@ -468,25 +473,26 @@ expectTypeOf>().toEqualTypeOf< `records.${string}.a` | `records.${string}.c.d` >() expectTypeOf>().toEqualTypeOf< - Record + Record >() -expectTypeOf>().toEqualTypeOf<{ - a: string - b: number - c: { d: string } -}>() expectTypeOf< - DeepValue ->().toEqualTypeOf() -expectTypeOf< - DeepValue ->().toEqualTypeOf() -expectTypeOf>().toEqualTypeOf<{ - d: string -}>() -expectTypeOf< - DeepValue ->().toEqualTypeOf() + DeepValue +>().toEqualTypeOf() +expectTypeOf>().toEqualTypeOf< + string | RecordValue +>() +expectTypeOf>().toEqualTypeOf< + number | RecordValue +>() +expectTypeOf>().toEqualTypeOf< + | { + d: string + } + | RecordValue +>() +expectTypeOf>().toEqualTypeOf< + string | RecordValue +>() describe('FieldsMap', () => { it('should map to all available types', () => { From 1eff811ef52f40d66153eb795fc2f6305416ab01 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:17:13 +0000 Subject: [PATCH 03/23] ci: apply automated fixes and generate docs --- packages/form-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/form-core/package.json b/packages/form-core/package.json index bea575c89..5e523d426 100644 --- a/packages/form-core/package.json +++ b/packages/form-core/package.json @@ -60,4 +60,4 @@ "valibot": "^1.1.0", "zod": "^3.25.76" } -} \ No newline at end of file +} From 89d15649578550b0fc3fb1a72dacc834a681b7c4 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Fri, 7 Nov 2025 12:03:47 +0100 Subject: [PATCH 04/23] fix: properly analyze deep records --- packages/form-core/src/FieldGroupApi.ts | 5 +--- packages/form-core/src/util-types.ts | 25 ++++++++++++----- packages/form-core/tests/util-types.test-d.ts | 27 +++++++++---------- .../tests/createFormHook.test-d.tsx | 2 +- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/form-core/src/FieldGroupApi.ts b/packages/form-core/src/FieldGroupApi.ts index 0bc575d67..58639c690 100644 --- a/packages/form-core/src/FieldGroupApi.ts +++ b/packages/form-core/src/FieldGroupApi.ts @@ -353,10 +353,7 @@ export class FieldGroupApi< getFieldValue = >( field: TField, ): DeepValue => { - return this.form.getFieldValue(this.getFormFieldName(field)) as DeepValue< - TFieldGroupData, - TField - > + return this.form.getFieldValue(this.getFormFieldName(field)) as never } /** diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index a5e90f30d..d8912be5f 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -157,22 +157,35 @@ export type DeepKeys = unknown extends T ? string : DeepKeysAndValues['key'] -export type ValueOfKey< - TValue extends AnyDeepKeyAndValue, - TAccessor extends string, -> = +type ValueOfKey = TValue extends AnyDeepKeyAndValue ? TAccessor extends ValueKey - ? TValue['value'] + ? TValue : never : never +type MostSpecific< + T extends AnyDeepKeyAndValue, + All extends AnyDeepKeyAndValue = T, +> = T extends any + ? // If `Exclude` is `never`, `T` is the sole union member. + // If only the key was used as check, it would not work because our keys + // are compatible with each other and would collapse. + // This check, however, is stable because `All` includes distinct `value` types, + // preventing unrelated members from collapsing into each other. + Exclude extends never + ? T + : T['key'] extends Exclude['key'] + ? T + : never + : never + /** * Infer the type of a deeply nested property within an object or an array. */ export type DeepValue = unknown extends TValue ? TValue - : ValueOfKey, TAccessor> + : MostSpecific, TAccessor>>['value'] /** * The keys of an object or array, deeply nested and only with a value of TValue diff --git a/packages/form-core/tests/util-types.test-d.ts b/packages/form-core/tests/util-types.test-d.ts index 79616d6f3..d3e27e161 100644 --- a/packages/form-core/tests/util-types.test-d.ts +++ b/packages/form-core/tests/util-types.test-d.ts @@ -478,21 +478,18 @@ expectTypeOf>().toEqualTypeOf< expectTypeOf< DeepValue >().toEqualTypeOf() -expectTypeOf>().toEqualTypeOf< - string | RecordValue ->() -expectTypeOf>().toEqualTypeOf< - number | RecordValue ->() -expectTypeOf>().toEqualTypeOf< - | { - d: string - } - | RecordValue ->() -expectTypeOf>().toEqualTypeOf< - string | RecordValue ->() +expectTypeOf< + DeepValue +>().toEqualTypeOf() +expectTypeOf< + DeepValue +>().toEqualTypeOf() +expectTypeOf>().toEqualTypeOf<{ + d: string +}>() +expectTypeOf< + DeepValue +>().toEqualTypeOf() describe('FieldsMap', () => { it('should map to all available types', () => { diff --git a/packages/react-form/tests/createFormHook.test-d.tsx b/packages/react-form/tests/createFormHook.test-d.tsx index b8a9b3971..2055d2072 100644 --- a/packages/react-form/tests/createFormHook.test-d.tsx +++ b/packages/react-form/tests/createFormHook.test-d.tsx @@ -87,7 +87,7 @@ describe('createFormHook', () => { render: ({ form, initialValues }) => { return (
- + {(field) => { expectTypeOf(field.state.value).toExtend() return null From dd2f4d02554c3050c363dcfdeb81a2e9dd4c8a88 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:17:49 +0100 Subject: [PATCH 05/23] rework DeepValue to use priority value --- packages/form-core/src/util-types.ts | 71 ++++++++++++++++++---------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index d8912be5f..c608bcd34 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -22,9 +22,11 @@ export type Narrow = Try> export interface AnyDeepKeyAndValue< K extends string = string, V extends any = any, + P extends unknown[] = unknown[], > { key: K value: V + priority: P } export type ArrayAccessor = @@ -33,19 +35,22 @@ export type ArrayAccessor = export interface ArrayDeepKeyAndValue< in out TParent extends AnyDeepKeyAndValue, in out T extends ReadonlyArray, + in out TPriority extends unknown[], > extends AnyDeepKeyAndValue { key: ArrayAccessor value: T[number] | Nullable + priority: TPriority } export type DeepKeyAndValueArray< TParent extends AnyDeepKeyAndValue, T extends ReadonlyArray, TAcc, + TPriority extends unknown[], > = DeepKeysAndValuesImpl< NonNullable, - ArrayDeepKeyAndValue, - TAcc | ArrayDeepKeyAndValue + ArrayDeepKeyAndValue, + TAcc | ArrayDeepKeyAndValue > export type TupleAccessor< @@ -57,9 +62,11 @@ export interface TupleDeepKeyAndValue< in out TParent extends AnyDeepKeyAndValue, in out T, in out TKey extends AllTupleKeys, + in out TPriority extends unknown[], > extends AnyDeepKeyAndValue { key: TupleAccessor value: T[TKey] | Nullable + priority: TPriority } export type AllTupleKeys = T extends any ? keyof T & `${number}` : never @@ -68,12 +75,13 @@ export type DeepKeyAndValueTuple< TParent extends AnyDeepKeyAndValue, T extends ReadonlyArray, TAcc, + TPriority extends unknown[], TAllKeys extends AllTupleKeys = AllTupleKeys, > = TAllKeys extends any ? DeepKeysAndValuesImpl< NonNullable, - TupleDeepKeyAndValue, - TAcc | TupleDeepKeyAndValue + TupleDeepKeyAndValue, + TAcc | TupleDeepKeyAndValue > : never @@ -98,22 +106,37 @@ export interface ObjectDeepKeyAndValue< in out TParent extends AnyDeepKeyAndValue, in out T, in out TKey extends AllObjectKeys, + in out TPriority extends unknown[], > extends AnyDeepKeyAndValue { key: ObjectAccessor value: ObjectValue + priority: TPriority } export type DeepKeyAndValueObject< TParent extends AnyDeepKeyAndValue, T, TAcc, + TPriority extends unknown[], TAllKeys extends AllObjectKeys = AllObjectKeys, > = TAllKeys extends any - ? DeepKeysAndValuesImpl< - NonNullable, - ObjectDeepKeyAndValue, - TAcc | ObjectDeepKeyAndValue - > + ? string extends TAllKeys + ? DeepKeysAndValuesImpl< + NonNullable, + ObjectDeepKeyAndValue, + TAcc | ObjectDeepKeyAndValue, + // children of records risk causing mismatches because they are + // subtypes of the record parent. + // `foo.${string}.bar` is also assignable to `foo.${string}`, + // so we need higher priority for children. + [...TPriority, unknown] + > + : DeepKeysAndValuesImpl< + NonNullable, + ObjectDeepKeyAndValue, + TAcc | ObjectDeepKeyAndValue, + TPriority + > : never export type UnknownAccessor = @@ -123,6 +146,7 @@ export interface UnknownDeepKeyAndValue extends AnyDeepKeyAndValue { key: UnknownAccessor value: unknown + priority: unknown[] } export type DeepKeysAndValues = @@ -134,6 +158,7 @@ export type DeepKeysAndValuesImpl< T, TParent extends AnyDeepKeyAndValue = never, TAcc = never, + TPriority extends unknown[] = [], > = unknown extends T ? TAcc | UnknownDeepKeyAndValue : unknown extends T // this stops runaway recursion when T is any @@ -142,12 +167,12 @@ export type DeepKeysAndValuesImpl< ? TAcc : T extends ReadonlyArray ? number extends T['length'] - ? DeepKeyAndValueArray - : DeepKeyAndValueTuple + ? DeepKeyAndValueArray + : DeepKeyAndValueTuple : keyof T extends never ? TAcc | UnknownDeepKeyAndValue : T extends object - ? DeepKeyAndValueObject + ? DeepKeyAndValueObject : TAcc /** @@ -164,20 +189,18 @@ type ValueOfKey = : never : never -type MostSpecific< +type HighestPriority< T extends AnyDeepKeyAndValue, - All extends AnyDeepKeyAndValue = T, + TAll extends AnyDeepKeyAndValue = T, > = T extends any - ? // If `Exclude` is `never`, `T` is the sole union member. - // If only the key was used as check, it would not work because our keys - // are compatible with each other and would collapse. - // This check, however, is stable because `All` includes distinct `value` types, - // preventing unrelated members from collapsing into each other. - Exclude extends never + ? // Check if no other member of the union has a longer element. + // If the union consists of one member, this will always result in never + Extract< + TAll, + { priority: [...T['priority'], unknown, ...unknown[]] } + > extends never ? T - : T['key'] extends Exclude['key'] - ? T - : never + : never : never /** @@ -185,7 +208,7 @@ type MostSpecific< */ export type DeepValue = unknown extends TValue ? TValue - : MostSpecific, TAccessor>>['value'] + : HighestPriority, TAccessor>>['value'] /** * The keys of an object or array, deeply nested and only with a value of TValue From d586448be4bff98dab6fff6796cc5bebfc1c5a58 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:48:17 +0100 Subject: [PATCH 06/23] chore: cleanup --- packages/form-core/package.json | 2 +- packages/react-form/tests/createFormHook.test-d.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/form-core/package.json b/packages/form-core/package.json index 5e523d426..87850df90 100644 --- a/packages/form-core/package.json +++ b/packages/form-core/package.json @@ -22,7 +22,7 @@ "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js", "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js", - "test:types:ts58": "tsc --extendedDiagnostics", + "test:types:ts58": "tsc", "test:lib": "vitest", "test:lib:dev": "pnpm run test:lib --watch", "test:build": "publint --strict", diff --git a/packages/react-form/tests/createFormHook.test-d.tsx b/packages/react-form/tests/createFormHook.test-d.tsx index 2055d2072..db8245d52 100644 --- a/packages/react-form/tests/createFormHook.test-d.tsx +++ b/packages/react-form/tests/createFormHook.test-d.tsx @@ -89,7 +89,7 @@ describe('createFormHook', () => {
{(field) => { - expectTypeOf(field.state.value).toExtend() + expectTypeOf(field.state.value).toEqualTypeOf() return null }} From 273d43b828e94f9ced61adfbab2e1f2e216956be Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Sun, 9 Nov 2025 11:21:37 +0100 Subject: [PATCH 07/23] chore(form-core): refactor util type tests --- packages/form-core/tests/util-types.test-d.ts | 1155 ++++++++++------- 1 file changed, 672 insertions(+), 483 deletions(-) diff --git a/packages/form-core/tests/util-types.test-d.ts b/packages/form-core/tests/util-types.test-d.ts index d3e27e161..69ca0739f 100644 --- a/packages/form-core/tests/util-types.test-d.ts +++ b/packages/form-core/tests/util-types.test-d.ts @@ -1,4 +1,4 @@ -import { describe, expectTypeOf, it } from 'vitest' +import { describe, expect, expectTypeOf, it } from 'vitest' import type { DeepKeys, DeepKeysOfType, @@ -6,490 +6,679 @@ import type { FieldsMap, } from '../src/index' -/** - * Properly recognizes that `0` is not an object and should not have subkeys - */ -type TupleSupport = { topUsers: [User, 0, User] } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - | 'topUsers' - | 'topUsers[0]' - | 'topUsers[0].name' - | 'topUsers[0].id' - | 'topUsers[0].age' - | 'topUsers[1]' - | 'topUsers[2]' - | 'topUsers[2].name' - | 'topUsers[2].id' - | 'topUsers[2].age' ->() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'topUsers[0].age' | 'topUsers[1]' | 'topUsers[2].age' ->() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'topUsers[0].name' | 'topUsers[0].id' | 'topUsers[2].name' | 'topUsers[2].id' ->() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'topUsers[0]' | 'topUsers[2]' ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() - -/** - * Properly recognizes that a normal number index won't cut it and should be `[number]` prefixed instead - */ -type ArraySupport = { users: User[] } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - | 'users' - | `users[${number}]` - | `users[${number}].name` - | `users[${number}].id` - | `users[${number}].age` ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<`users[${number}].age`>() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - `users[${number}].name` | `users[${number}].id` ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<`users[${number}]`>() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() - -/** - * Properly handles deep object nesting like so: - */ -type NestedSupport = { meta: { mainUser: User } } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - | 'meta' - | 'meta.mainUser' - | 'meta.mainUser.name' - | 'meta.mainUser.id' - | 'meta.mainUser.age' ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<`meta.mainUser.age`>() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - `meta.mainUser.name` | `meta.mainUser.id` ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<`meta.mainUser`>() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() - -/** - * Properly handles deep partial object nesting like so: - */ -type NestedPartialSupport = { meta?: { mainUser?: User } } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - | 'meta' - | 'meta.mainUser' - | 'meta.mainUser.name' - | 'meta.mainUser.id' - | 'meta.mainUser.age' ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'meta.mainUser.age'>() - -/** - * Properly handles `object` edgecase nesting like so: - */ -type ObjectNestedEdgecase = { meta: { mainUser: object } } -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf( - 0 as never as 'meta' | 'meta.mainUser' | `meta.mainUser.${string}`, -) -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'meta' | 'meta.mainUser'>() - -/** - * Properly handles `object` edgecase like so: - */ -type ObjectEdgecase = DeepKeys -expectTypeOf(0 as never as ObjectEdgecase).toEqualTypeOf() - -/** - * Properly handles `object` edgecase nesting like so: - */ -type UnknownNestedEdgecase = { meta: { mainUser: unknown } } -expectTypeOf( - 0 as never as 'meta' | 'meta.mainUser' | `meta.mainUser.${string}`, -).toEqualTypeOf(0 as never as DeepKeys) -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'meta'>() - -/** - * Properly handles discriminated unions like so: - */ -type DiscriminatedUnion = { name: string } & ( - | { variant: 'foo' } - | { variant: 'bar'; baz: boolean } -) -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - 'name' | 'variant' | 'baz' ->() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'name' | 'variant'>() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf<'baz'>() - -type DiscriminatedUnionValueShared = DeepValue -expectTypeOf(0 as never as DiscriminatedUnionValueShared).toEqualTypeOf< - 'foo' | 'bar' ->() -type DiscriminatedUnionValueFixed = DeepValue -expectTypeOf( - 0 as never as DiscriminatedUnionValueFixed, -).toEqualTypeOf() - -/** - * Properly handles `unknown` edgecase like so: - */ -type UnknownEdgecase = DeepKeys -expectTypeOf(0 as never as UnknownEdgecase).toEqualTypeOf() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() -expectTypeOf( - 0 as never as DeepKeysOfType, -).toEqualTypeOf() - -type NestedKeysExample = DeepValue< - { meta: { mainUser: User } }, - 'meta.mainUser.age' -> -expectTypeOf(0 as never as NestedKeysExample).toEqualTypeOf() - -type NestedNullableObjectCase = { - null: { mainUser: 'name' } | null - undefined: { mainUser: 'name' } | undefined - optional?: { mainUser: 'name' } - mixed: { mainUser: 'name' } | null | undefined -} - -type NestedNullableObjectCaseNull = DeepValue< - NestedNullableObjectCase, - 'null.mainUser' -> -expectTypeOf(0 as never as NestedNullableObjectCaseNull).toEqualTypeOf< - 'name' | null ->() -type NestedNullableObjectCaseUndefined = DeepValue< - NestedNullableObjectCase, - 'undefined.mainUser' -> -expectTypeOf(0 as never as NestedNullableObjectCaseUndefined).toEqualTypeOf< - 'name' | undefined ->() -type NestedNullableObjectCaseOptional = DeepValue< - NestedNullableObjectCase, - 'undefined.mainUser' -> -expectTypeOf(0 as never as NestedNullableObjectCaseOptional).toEqualTypeOf< - 'name' | undefined ->() -type NestedNullableObjectCaseMixed = DeepValue< - NestedNullableObjectCase, - 'mixed.mainUser' -> -expectTypeOf(0 as never as 'name' | null | undefined).toEqualTypeOf( - 0 as never as NestedNullableObjectCaseMixed, -) - -type DoubleNestedNullableObjectCase = { - mixed?: { mainUser: { name: 'name' } } | null | undefined -} -type DoubleNestedNullableObjectA = DeepValue< - DoubleNestedNullableObjectCase, - 'mixed.mainUser' -> -expectTypeOf(0 as never as { name: 'name' } | null | undefined).toEqualTypeOf( - 0 as never as DoubleNestedNullableObjectA, -) -type DoubleNestedNullableObjectB = DeepValue< - DoubleNestedNullableObjectCase, - 'mixed.mainUser.name' -> -expectTypeOf(0 as never as DoubleNestedNullableObjectB).toEqualTypeOf< - 'name' | null | undefined ->() - -type NestedObjectUnionCase = { - normal: - | { a: User } - | { a: string } - | { b: string } - | { c: { user: User } | { user: number } } -} -type NestedObjectUnionA = DeepValue -expectTypeOf(0 as never as NestedObjectUnionA).toEqualTypeOf() -type NestedObjectUnionB = DeepValue -expectTypeOf(0 as never as NestedObjectUnionB).toEqualTypeOf() -type NestedObjectUnionC = DeepValue -expectTypeOf(0 as never as NestedObjectUnionC).toEqualTypeOf() - -type NestedNullableObjectUnionCase = { - nullable: - | { a?: number; b?: { c: boolean } | null } - | { b?: { c: string; e: number } } -} -type NestedNullableObjectUnionA = DeepValue< - NestedNullableObjectUnionCase, - 'nullable.a' -> -expectTypeOf(0 as never as NestedNullableObjectUnionA).toEqualTypeOf< - number | undefined ->() -type NestedNullableObjectUnionB = DeepValue< - NestedNullableObjectUnionCase, - 'nullable.b.c' -> -expectTypeOf(0 as never as string | boolean | null | undefined).toEqualTypeOf( - 0 as never as NestedNullableObjectUnionB, -) -type NestedNullableObjectUnionC = DeepValue< - NestedNullableObjectUnionCase, - 'nullable.b.e' -> -expectTypeOf(0 as never as NestedNullableObjectUnionC).toEqualTypeOf< - number | undefined ->() - -type NestedArrayExample = DeepValue<{ users: User[] }, 'users[0].age'> -expectTypeOf(0 as never as NestedArrayExample).toEqualTypeOf() - -type NestedLooseArrayExample = DeepValue< - { users: User[] }, - `users[${number}].age` -> -expectTypeOf(0 as never as NestedLooseArrayExample).toEqualTypeOf() - -type NestedArrayUnionExample = DeepValue< - { users: string | User[] }, - 'users[0].age' -> -expectTypeOf(0 as never as NestedArrayUnionExample).toEqualTypeOf() - -type NestedTupleExample = DeepValue< - { topUsers: [User, 0, User] }, - 'topUsers[0].age' -> -expectTypeOf(0 as never as NestedTupleExample).toEqualTypeOf() - -type NestedTupleBroadExample = DeepValue< - { topUsers: User[] }, - `topUsers[${number}].age` -> -expectTypeOf(0 as never as NestedTupleBroadExample).toEqualTypeOf() - -type DeeplyNestedTupleBroadExample = DeepValue< - { nested: { topUsers: User[] } }, - `nested.topUsers[${number}].age` -> -expectTypeOf( - 0 as never as DeeplyNestedTupleBroadExample, -).toEqualTypeOf() - -type SimpleArrayExample = DeepValue -expectTypeOf(0 as never as SimpleArrayExample).toEqualTypeOf() - -type SimpleNestedArrayExample = DeepValue -expectTypeOf(0 as never as SimpleNestedArrayExample).toEqualTypeOf() - -type NestedTupleItemExample = DeepValue< - { topUsers: [User, 0, User] }, - 'topUsers[1]' -> -expectTypeOf(0 as never as NestedTupleItemExample).toEqualTypeOf<0>() - -type ArrayExample = DeepValue<[1, 2, 3], '[1]'> -expectTypeOf(0 as never as ArrayExample).toEqualTypeOf<2>() - -type NonNestedObjExample = DeepValue<{ a: 1 }, 'a'> -expectTypeOf(0 as never as NonNestedObjExample).toEqualTypeOf<1>() - -interface User { - name: string - id: string - age: number -} - -type FormDefinition = { - nested: { - people: User[] - } -} - -type FormDefinitionValue = DeepValue< - FormDefinition, - `nested.people[${number}].name` -> - -expectTypeOf(0 as never as FormDefinitionValue).toEqualTypeOf() - -type DoubleDeepArray = DeepValue< - { - people: { - parents: { - name: string - age: number +describe('DeepKeys, DeepKeysOfType', () => { + it('should support tuples', () => { + type User = { + name: string + id: string + age: number + } + /** + * It should recognize that '0' does not have subkeys + */ + type Tuple = { topUsers: [User, 0, User] } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + type WithDate = DeepKeysOfType + type WithUser = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + | 'topUsers' + | 'topUsers[0]' + | 'topUsers[0].name' + | 'topUsers[0].id' + | 'topUsers[0].age' + | 'topUsers[1]' + | 'topUsers[2]' + | 'topUsers[2].name' + | 'topUsers[2].id' + | 'topUsers[2].age' + >() + + expectTypeOf().toEqualTypeOf< + 'topUsers[0].age' | 'topUsers[1]' | 'topUsers[2].age' + >() + + expectTypeOf().toEqualTypeOf< + | 'topUsers[0].name' + | 'topUsers[0].id' + | 'topUsers[2].name' + | 'topUsers[2].id' + >() + + expectTypeOf().toEqualTypeOf<'topUsers[0]' | 'topUsers[2]'>() + + expectTypeOf().toBeNever() + }) + + it('should support arrays', () => { + type User = { + name: string + id: string + age: number + } + /** + * Properly recognizes that a normal number index won't cut it and should be `[number]` prefixed instead + */ + type Array = { users: User[] } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + type WithUser = DeepKeysOfType + type WithDate = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + | 'users' + | `users[${number}]` + | `users[${number}].name` + | `users[${number}].id` + | `users[${number}].age` + >() + + expectTypeOf().toEqualTypeOf<`users[${number}].age`>() + + expectTypeOf().toEqualTypeOf< + `users[${number}].name` | `users[${number}].id` + >() + + expectTypeOf().toEqualTypeOf<`users[${number}]`>() + + expectTypeOf().toBeNever() + }) + + it('should support nested objects', () => { + type User = { + name: string + id: string + age: number + } + type Nested = { meta: { mainUser: User } } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + type WithUser = DeepKeysOfType + type WithDate = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + | 'meta' + | 'meta.mainUser' + | 'meta.mainUser.name' + | 'meta.mainUser.id' + | 'meta.mainUser.age' + >() + expectTypeOf().toEqualTypeOf<`meta.mainUser.age`>() + + expectTypeOf().toEqualTypeOf< + `meta.mainUser.name` | `meta.mainUser.id` + >() + + expectTypeOf().toEqualTypeOf<`meta.mainUser`>() + + expectTypeOf().toBeNever() + }) + + it('should support nested partial objects', () => { + type User = { + name: string + id: string + age: number + } + type NestedPartial = { meta?: { mainUser?: User } } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithMaybeNumber = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + | 'meta' + | 'meta.mainUser' + | 'meta.mainUser.name' + | 'meta.mainUser.id' + | 'meta.mainUser.age' + >() + + expectTypeOf().toBeNever() + + expectTypeOf().toEqualTypeOf<'meta.mainUser.age'>() + }) + + it('should handle a `object`', () => { + type NestedObject = { meta: { mainUser: object } } + + type Keys = DeepKeys + type WithObject = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + 'meta' | 'meta.mainUser' | `meta.mainUser.${string}` + >() + + expectTypeOf().toEqualTypeOf<'meta' | 'meta.mainUser'>() + + type ObjectKeys = DeepKeys + expectTypeOf().toBeString() + }) + + it('should handle `unknown`', () => { + type NestedUnknown = { meta: { mainUser: unknown } } + + type Keys = DeepKeys + type WithObject = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + 'meta' | 'meta.mainUser' | `meta.mainUser.${string}` + >() + + expectTypeOf().toEqualTypeOf<'meta'>() + + type UnknownKeys = DeepKeys + type UnknownWithUnknown = DeepKeysOfType + type UnknownWithObject = DeepKeysOfType + + expectTypeOf().toBeString() + + expectTypeOf().toBeString() + + expectTypeOf().toBeNever() + }) + + it('should handle discriminated unions', () => { + type DiscriminatedUnion = { name: string } & ( + | { variant: 'foo' } + | { variant: 'bar'; baz: boolean } + ) + + type Keys = DeepKeys + type WithString = DeepKeysOfType + type WithBoolean = DeepKeysOfType + + expectTypeOf().toEqualTypeOf<'name' | 'variant' | 'baz'>() + + expectTypeOf().toEqualTypeOf<'name' | 'variant'>() + + expectTypeOf().toEqualTypeOf<'baz'>() + }) + + it('should handle records', () => { + type Value = { + a: string + b: number + c: { d: string } + } + type RecordExample = { + records: Record + } + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + + expectTypeOf>().toEqualTypeOf< + | 'records' + | `records.${string}` + | `records.${string}.a` + | `records.${string}.b` + | `records.${string}.c` + | `records.${string}.c.d` + >() + + expectTypeOf().toEqualTypeOf<`records.${string}.b`>() + + expectTypeOf().toEqualTypeOf< + `records.${string}.a` | `records.${string}.c.d` + >() + }) + + it('should handle objects containing any', () => { + type ObjectWithAny = { + a: any + b: number + obj: { + c: any + d: number + } + } + + type Keys = DeepKeys + type WithNumber = DeepKeysOfType + type WithString = DeepKeysOfType + + expectTypeOf().toEqualTypeOf< + 'a' | 'b' | 'obj' | `a.${string}` | 'obj.c' | `obj.c.${string}` | 'obj.d' + >() + // since any can also be number, It's okay to be included + expectTypeOf().toEqualTypeOf<'a' | 'b' | 'obj.c' | 'obj.d'>() + expectTypeOf().toEqualTypeOf<'a' | 'obj.c'>() + }) +}) + +describe('DeepValue', () => { + it('should handle discriminated unions', () => { + type DiscriminatedUnion = { name: string } & ( + | { variant: 'foo' } + | { variant: 'bar'; baz: boolean } + ) + + type SharedValue = DeepValue + type FixedValue = DeepValue + type FixedValue2 = DeepValue + + expectTypeOf().toEqualTypeOf<'foo' | 'bar'>() + // TODO this might have implications for high-level nullable / undefinable not cascading. + expectTypeOf().toBeBoolean() + expectTypeOf().toBeString() + }) + + it('should handle nested objects', () => { + type User = { + name: string + id: string + age: number + } + type Nested = { meta: { mainUser: User } } + + type ExpectNumber = DeepValue + + expectTypeOf().toBeNumber() + }) + + it('should handle undefined / nullable in objects', () => { + type NestedNullableObjectCase = { + null: { mainUser: 'name' } | null + undefined: { mainUser: 'name' } | undefined + optional?: { mainUser: 'name' } + mixed: { mainUser: 'name' } | null | undefined + } + + type NameOrNullValue = DeepValue + type NameOrUndefinedValue = DeepValue< + NestedNullableObjectCase, + 'undefined.mainUser' + > + type NameOrUndefinedValue2 = DeepValue< + NestedNullableObjectCase, + 'optional.mainUser' + > + type NameOrNil = DeepValue + + expectTypeOf().toEqualTypeOf<'name' | null>() + + expectTypeOf().toEqualTypeOf<'name' | undefined>() + + expectTypeOf().toEqualTypeOf<'name' | undefined>() + + expectTypeOf().toEqualTypeOf<'name' | null | undefined>() + + type DoubleNestedNullableObjectCase = { + mixed?: { mainUser: { name: 'name' } } | null | undefined + } + type ObjectOrNil = DeepValue< + DoubleNestedNullableObjectCase, + 'mixed.mainUser' + > + type NameOrNil2 = DeepValue< + DoubleNestedNullableObjectCase, + 'mixed.mainUser.name' + > + + expectTypeOf().toEqualTypeOf< + { name: 'name' } | null | undefined + >() + + expectTypeOf().toEqualTypeOf<'name' | null | undefined>() + }) + + it('should handle unions in objects', () => { + type User = { + name: string + id: string + age: number + } + type NestedUnion = { + normal: + | { a: User } + | { a: string } + | { b: string } + | { c: { user: User } | { user: number } } + } + type NumberValue = DeepValue + type StringValue = DeepValue + type StringValue2 = DeepValue + + expectTypeOf().toBeNumber() + + expectTypeOf().toBeString() + + expectTypeOf().toBeString() + + type NestedNullableUnion = { + mixed: + | { a?: number; b?: { c: boolean } | null } + | { b?: { c: string; e: number } } + } + type NumberOptional = DeepValue + type MixedValue = DeepValue + type NumberOptional2 = DeepValue + + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toEqualTypeOf< + string | boolean | null | undefined + >() + + expectTypeOf().toEqualTypeOf() + }) + + it('should handle nested arrays', () => { + type User = { + name: string + id: string + age: number + } + type NestedArray = { users: User[] } + + type NumberValue = DeepValue + type NumberValue2 = DeepValue + + expectTypeOf().toBeNumber() + + expectTypeOf().toBeNumber() + }) + + it('should handle nested arrays with unions', () => { + type User = { + name: string + id: string + age: number + } + type NestedArrayUnion1 = { users: string | User[] } + type NestedArrayUnion2 = { users: (string | User)[] } + + type NumberValue = DeepValue + type NumberValue2 = DeepValue + type UserValue = DeepValue + type UserOrString = DeepValue + + expectTypeOf().toBeNumber() + + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toEqualTypeOf() + }) + + it('should handle nested tuples', () => { + type User = { + name: string + id: string + age: number + } + + type NestedTuple = { topUsers: [User, 0, User] } + + type NumberValue = DeepValue + type NeverValue = DeepValue + type NumberValue2 = DeepValue + type UserValue = DeepValue + type ZeroValue = DeepValue + type UserValue2 = DeepValue + + expectTypeOf().toBeNumber() + + expectTypeOf().toBeNever() + + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toEqualTypeOf(0 as const) + + expectTypeOf().toEqualTypeOf() + }) + + it('should handle top-level arrays', () => { + type User = { + name: string + id: string + age: number + } + + type UserValue = DeepValue + type NumberValue = DeepValue + + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toBeNumber() + }) + + it('should allow string and number literals', () => { + type Tuple = ['a', 'b', 'c'] + + type OneValue = DeepValue<{ a: 1 }, 'a'> + + type AValue = DeepValue + type BValue = DeepValue + type CValue = DeepValue + + expectTypeOf().toEqualTypeOf(1 as const) + expectTypeOf().toEqualTypeOf('a' as const) + expectTypeOf().toEqualTypeOf('b' as const) + expectTypeOf().toEqualTypeOf('c' as const) + }) + + it('should handle 2-dimensional arrays', () => { + type User = { + name: string + id: string + age: number + } + + type DoubleArray = { + people: { + parents: User[] }[] - }[] - }, - `people[${0}].parents[${0}].name` -> - -expectTypeOf(0 as never as DoubleDeepArray).toEqualTypeOf() - -// Deepness is infinite error check -type Cart = { - id: number - product: { - id: string - description?: string - price_internet?: number - price_dealer_region?: number - price_dealer?: number - stock: - | { - id: string - quantity: number - isChecked: boolean - }[] - | null - } - quantity: number - isChecked: boolean -}[] - -type Payment_types = { - id: string - title: string - name: string -}[] - -type Shipping_methods = { - id: string - title: string - name: string -}[] - -type Userr = { - id: string - first_name: string | null - email: string | null - avatar: - | string - | ({ - url?: string - } & { + } + + type StringValue = DeepValue< + DoubleArray, + `people[${number}].parents[${number}].name` + > + + expectTypeOf().toBeString() + }) + + it('should handle records', () => { + type Foo = { + a: string + b: number + c: { d: string } + } + type RecordExample = { + records: Record + } + + type RecordValue = DeepValue + type FooValue = DeepValue + type StringValue = DeepValue + type NumberValue = DeepValue + type ObjectValue = DeepValue + type StringValue2 = DeepValue + + type FooValue2 = DeepValue + type StringValue3 = DeepValue + type NumberValue2 = DeepValue + type ObjectValue2 = DeepValue + type StringValue4 = DeepValue + + expectTypeOf().toEqualTypeOf>() + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + + expectTypeOf().toBeNumber() + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + }) + + it('should handle nested records', () => { + type Foo = { + a: string + b: number + c: { d: string } + } + type NestedRecord = Record> + type RecordExample = { + records: NestedRecord + } + + type RecordValue = DeepValue + type RecordValue2 = DeepValue + type FooValue = DeepValue + type StringValue = DeepValue + type NumberValue = DeepValue + type ObjectValue = DeepValue + type StringValue2 = DeepValue< + RecordExample, + `records.${string}.${string}.c.d` + > + + type RecordValue3 = DeepValue + type RecordValue4 = DeepValue + type FooValue2 = DeepValue + type StringValue3 = DeepValue + type NumberValue2 = DeepValue + type ObjectValue2 = DeepValue + type StringValue4 = DeepValue + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf>() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf>() + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + + expectTypeOf().toBeNumber() + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + }) + + it('should not error for large objects', () => { + type Cart = { + id: number + product: { id: string - storage: string - filename_disk: string | null - filename_original: string | null - filename_download: string | null - filename_preview: string | null - filename_thumbnail: string | null - filename_medium: string | null - filename_large: string | null - filename_huge: string | null - filename_icon: string | null - filename_icon_large: string | null - focal_point_y: number | null - }) - | null - // Reference Cart, Payment_types, Shipping_methods - cart: Cart | null - payment_types: Payment_types | null - shipping_methods: Shipping_methods | null -} - -type UserKeys = DeepValue> - -type ObjectWithAny = { - a: any - b: number - obj: { - c: any - d: number - } -} - -expectTypeOf(0 as never as DeepKeys).toEqualTypeOf< - 'a' | 'b' | 'obj' | `a.${string}` | 'obj.c' | `obj.c.${string}` | 'obj.d' ->() -// since any can also be number, It's okay to be included -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'a' | 'b' | 'obj.c' | 'obj.d' ->() -expectTypeOf(0 as never as DeepKeysOfType).toEqualTypeOf< - 'a' | 'obj.c' ->() -type AnyObjectExample = DeepValue -expectTypeOf(0 as never as AnyObjectExample).toEqualTypeOf() -type AnyObjectExample2 = DeepValue -expectTypeOf(0 as never as AnyObjectExample2).toEqualTypeOf() -type AnyObjectExample3 = DeepValue -expectTypeOf(0 as never as AnyObjectExample3).toEqualTypeOf<{ - c: any - d: number -}> -type AnyObjectExample4 = DeepValue -expectTypeOf(0 as never as AnyObjectExample4).toEqualTypeOf() -type AnyObjectExample5 = DeepValue -expectTypeOf(0 as never as AnyObjectExample5).toEqualTypeOf() - -type RecordValue = { - a: string - b: number - c: { d: string } -} -type RecordExample = { - records: Record -} - -expectTypeOf>().toEqualTypeOf< - | 'records' - | `records.${string}` - | `records.${string}.a` - | `records.${string}.b` - | `records.${string}.c` - | `records.${string}.c.d` ->() -expectTypeOf>().toEqualTypeOf< - `records.${string}.a` | `records.${string}.c.d` ->() -expectTypeOf>().toEqualTypeOf< - Record ->() -expectTypeOf< - DeepValue ->().toEqualTypeOf() -expectTypeOf< - DeepValue ->().toEqualTypeOf() -expectTypeOf< - DeepValue ->().toEqualTypeOf() -expectTypeOf>().toEqualTypeOf<{ - d: string -}>() -expectTypeOf< - DeepValue ->().toEqualTypeOf() + description?: string + price_internet?: number + price_dealer_region?: number + price_dealer?: number + stock: + | { + id: string + quantity: number + isChecked: boolean + }[] + | null + } + quantity: number + isChecked: boolean + }[] + + type Payment_types = { + id: string + title: string + name: string + }[] + + type Shipping_methods = { + id: string + title: string + name: string + }[] + + type Userr = { + id: string + first_name: string | null + email: string | null + avatar: + | string + | ({ + url?: string + } & { + id: string + storage: string + filename_disk: string | null + filename_original: string | null + filename_download: string | null + filename_preview: string | null + filename_thumbnail: string | null + filename_medium: string | null + filename_large: string | null + filename_huge: string | null + filename_icon: string | null + filename_icon_large: string | null + focal_point_y: number | null + }) + | null + // Reference Cart, Payment_types, Shipping_methods + cart: Cart | null + payment_types: Payment_types | null + shipping_methods: Shipping_methods | null + } + + type UserKeys = DeepValue> + }) + + it('should handle objects containing any', () => { + type ObjectWithAny = { + a: any + b: number + obj: { + c: any + d: number + } + } + + type AnyValue = DeepValue + type NumberValue = DeepValue + type ObjectValue = DeepValue + type AnyValue2 = DeepValue + type NumberValue2 = DeepValue + + expectTypeOf().toBeAny() + expectTypeOf().toBeNumber() + expectTypeOf().toEqualTypeOf<{ + c: any + d: number + }> + expectTypeOf().toBeAny() + expectTypeOf().toBeNumber() + }) +}) describe('FieldsMap', () => { it('should map to all available types', () => { From ce35e490d84a59d8f584021ad28f033600b6551b Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Sun, 9 Nov 2025 11:23:16 +0100 Subject: [PATCH 08/23] Add neat-keys-invent.md --- .changeset/neat-keys-invent.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/neat-keys-invent.md diff --git a/.changeset/neat-keys-invent.md b/.changeset/neat-keys-invent.md new file mode 100644 index 000000000..e0b549c03 --- /dev/null +++ b/.changeset/neat-keys-invent.md @@ -0,0 +1,6 @@ +--- +"@tanstack/form-core": patch +"@tanstack/react-form": patch +--- + +fix(form-core): fix DeepValue from record values being wrong From 2cd4f4a8ba258cacdce87d5fcaeeca8107448867 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 9 Nov 2025 10:23:56 +0000 Subject: [PATCH 09/23] ci: apply automated fixes and generate docs --- .changeset/neat-keys-invent.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/neat-keys-invent.md b/.changeset/neat-keys-invent.md index e0b549c03..9404da9d5 100644 --- a/.changeset/neat-keys-invent.md +++ b/.changeset/neat-keys-invent.md @@ -1,6 +1,6 @@ --- -"@tanstack/form-core": patch -"@tanstack/react-form": patch +'@tanstack/form-core': patch +'@tanstack/react-form': patch --- fix(form-core): fix DeepValue from record values being wrong From 4f2182a19a578ee23dde3acca791e0c9f77d59eb Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 07:06:22 +0100 Subject: [PATCH 10/23] chore: try different approach for less instantiations --- packages/form-core/src/util-types.ts | 60 +++++++++------------------- 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index c608bcd34..e4bdaac57 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -22,11 +22,9 @@ export type Narrow = Try> export interface AnyDeepKeyAndValue< K extends string = string, V extends any = any, - P extends unknown[] = unknown[], > { key: K value: V - priority: P } export type ArrayAccessor = @@ -35,22 +33,19 @@ export type ArrayAccessor = export interface ArrayDeepKeyAndValue< in out TParent extends AnyDeepKeyAndValue, in out T extends ReadonlyArray, - in out TPriority extends unknown[], > extends AnyDeepKeyAndValue { key: ArrayAccessor value: T[number] | Nullable - priority: TPriority } export type DeepKeyAndValueArray< TParent extends AnyDeepKeyAndValue, T extends ReadonlyArray, TAcc, - TPriority extends unknown[], > = DeepKeysAndValuesImpl< NonNullable, - ArrayDeepKeyAndValue, - TAcc | ArrayDeepKeyAndValue + ArrayDeepKeyAndValue, + TAcc | ArrayDeepKeyAndValue > export type TupleAccessor< @@ -62,11 +57,9 @@ export interface TupleDeepKeyAndValue< in out TParent extends AnyDeepKeyAndValue, in out T, in out TKey extends AllTupleKeys, - in out TPriority extends unknown[], > extends AnyDeepKeyAndValue { key: TupleAccessor value: T[TKey] | Nullable - priority: TPriority } export type AllTupleKeys = T extends any ? keyof T & `${number}` : never @@ -75,13 +68,12 @@ export type DeepKeyAndValueTuple< TParent extends AnyDeepKeyAndValue, T extends ReadonlyArray, TAcc, - TPriority extends unknown[], TAllKeys extends AllTupleKeys = AllTupleKeys, > = TAllKeys extends any ? DeepKeysAndValuesImpl< NonNullable, - TupleDeepKeyAndValue, - TAcc | TupleDeepKeyAndValue + TupleDeepKeyAndValue, + TAcc | TupleDeepKeyAndValue > : never @@ -106,36 +98,27 @@ export interface ObjectDeepKeyAndValue< in out TParent extends AnyDeepKeyAndValue, in out T, in out TKey extends AllObjectKeys, - in out TPriority extends unknown[], > extends AnyDeepKeyAndValue { key: ObjectAccessor value: ObjectValue - priority: TPriority } export type DeepKeyAndValueObject< TParent extends AnyDeepKeyAndValue, T, TAcc, - TPriority extends unknown[], TAllKeys extends AllObjectKeys = AllObjectKeys, > = TAllKeys extends any ? string extends TAllKeys ? DeepKeysAndValuesImpl< NonNullable, - ObjectDeepKeyAndValue, - TAcc | ObjectDeepKeyAndValue, - // children of records risk causing mismatches because they are - // subtypes of the record parent. - // `foo.${string}.bar` is also assignable to `foo.${string}`, - // so we need higher priority for children. - [...TPriority, unknown] + ObjectDeepKeyAndValue, + TAcc | ObjectDeepKeyAndValue > : DeepKeysAndValuesImpl< NonNullable, - ObjectDeepKeyAndValue, - TAcc | ObjectDeepKeyAndValue, - TPriority + ObjectDeepKeyAndValue, + TAcc | ObjectDeepKeyAndValue > : never @@ -146,7 +129,6 @@ export interface UnknownDeepKeyAndValue extends AnyDeepKeyAndValue { key: UnknownAccessor value: unknown - priority: unknown[] } export type DeepKeysAndValues = @@ -158,7 +140,6 @@ export type DeepKeysAndValuesImpl< T, TParent extends AnyDeepKeyAndValue = never, TAcc = never, - TPriority extends unknown[] = [], > = unknown extends T ? TAcc | UnknownDeepKeyAndValue : unknown extends T // this stops runaway recursion when T is any @@ -167,12 +148,12 @@ export type DeepKeysAndValuesImpl< ? TAcc : T extends ReadonlyArray ? number extends T['length'] - ? DeepKeyAndValueArray - : DeepKeyAndValueTuple + ? DeepKeyAndValueArray + : DeepKeyAndValueTuple : keyof T extends never ? TAcc | UnknownDeepKeyAndValue : T extends object - ? DeepKeyAndValueObject + ? DeepKeyAndValueObject : TAcc /** @@ -189,17 +170,12 @@ type ValueOfKey = : never : never -type HighestPriority< - T extends AnyDeepKeyAndValue, - TAll extends AnyDeepKeyAndValue = T, -> = T extends any - ? // Check if no other member of the union has a longer element. - // If the union consists of one member, this will always result in never - Extract< - TAll, - { priority: [...T['priority'], unknown, ...unknown[]] } - > extends never - ? T +type Maximals< + U extends AnyDeepKeyAndValue, + All extends AnyDeepKeyAndValue = U, +> = U extends any + ? Extract extends never + ? U : never : never @@ -208,7 +184,7 @@ type HighestPriority< */ export type DeepValue = unknown extends TValue ? TValue - : HighestPriority, TAccessor>>['value'] + : Maximals, TAccessor>>['value'] /** * The keys of an object or array, deeply nested and only with a value of TValue From bd8b2edf8f5bb0927cd436405dd5a3df6e112980 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 07:40:36 +0100 Subject: [PATCH 11/23] chore: add early union return --- packages/form-core/src/util-types.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index e4bdaac57..db68b0861 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -174,9 +174,11 @@ type Maximals< U extends AnyDeepKeyAndValue, All extends AnyDeepKeyAndValue = U, > = U extends any - ? Extract extends never + ? [All] extends [U] ? U - : never + : Extract extends never + ? U + : never : never /** From ad74be5183815379830ee856507c09b4bb913a6a Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 07:52:24 +0100 Subject: [PATCH 12/23] chore: remove unnecessary branch --- packages/form-core/src/util-types.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index db68b0861..4a3fdd01a 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -109,17 +109,11 @@ export type DeepKeyAndValueObject< TAcc, TAllKeys extends AllObjectKeys = AllObjectKeys, > = TAllKeys extends any - ? string extends TAllKeys - ? DeepKeysAndValuesImpl< - NonNullable, - ObjectDeepKeyAndValue, - TAcc | ObjectDeepKeyAndValue - > - : DeepKeysAndValuesImpl< - NonNullable, - ObjectDeepKeyAndValue, - TAcc | ObjectDeepKeyAndValue - > + ? DeepKeysAndValuesImpl< + NonNullable, + ObjectDeepKeyAndValue, + TAcc | ObjectDeepKeyAndValue + > : never export type UnknownAccessor = From e144ac580e2d0b489f194ea43e9baf4afef321c5 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:08:21 +0100 Subject: [PATCH 13/23] chore: fix arrays being excluded from resolution --- packages/form-core/src/util-types.ts | 5 +- packages/form-core/tests/util-types.test-d.ts | 61 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 4a3fdd01a..229cc29fb 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -170,7 +170,10 @@ type Maximals< > = U extends any ? [All] extends [U] ? U - : Extract extends never + : Extract< + All, + { key: `${U['key']}.${string}` | `${U['key']}[${number}]${string}` } + > extends never ? U : never : never diff --git a/packages/form-core/tests/util-types.test-d.ts b/packages/form-core/tests/util-types.test-d.ts index 69ca0739f..43cd55d4d 100644 --- a/packages/form-core/tests/util-types.test-d.ts +++ b/packages/form-core/tests/util-types.test-d.ts @@ -533,6 +533,67 @@ describe('DeepValue', () => { }>() }) + it('should handle records with arrays as values', () => { + type Foo = { + a: string + b: number + c: { d: string } + } + type RecordExample = { + records: Record + } + + type RecordValue = DeepValue + type FooArrayValue = DeepValue + type FooValue = DeepValue + type StringValue = DeepValue< + RecordExample, + `records.${string}[${number}].a` + > + type NumberValue = DeepValue< + RecordExample, + `records.${string}[${number}].b` + > + type ObjectValue = DeepValue< + RecordExample, + `records.${string}[${number}].c` + > + type StringValue2 = DeepValue< + RecordExample, + `records.${string}[${number}].c.d` + > + + type FooArrayValue2 = DeepValue + type FooValue2 = DeepValue + type StringValue3 = DeepValue + type NumberValue2 = DeepValue + type ObjectValue2 = DeepValue + type StringValue4 = DeepValue + + expectTypeOf().toEqualTypeOf>() + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + expectTypeOf().toBeString() + + expectTypeOf().toBeNumber() + expectTypeOf().toBeNumber() + + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + expectTypeOf().toEqualTypeOf<{ + d: string + }>() + }) + it('should handle nested records', () => { type Foo = { a: string From 6a4b770699cc141c867c261e93dd1250880a00da Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:45:00 +0100 Subject: [PATCH 14/23] chore: simplify some types --- packages/form-core/src/util-types.ts | 28 +++++++++++-------- packages/form-core/tests/util-types.test-d.ts | 1 + 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 229cc29fb..02e584799 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -157,24 +157,28 @@ export type DeepKeys = unknown extends T ? string : DeepKeysAndValues['key'] -type ValueOfKey = - TValue extends AnyDeepKeyAndValue - ? TAccessor extends ValueKey - ? TValue - : never +type ValueOfKey< + TValue extends AnyDeepKeyAndValue, + TAccessor extends string, +> = TValue extends any + ? TAccessor extends TValue['key'] + ? TValue : never + : never type Maximals< - U extends AnyDeepKeyAndValue, - All extends AnyDeepKeyAndValue = U, -> = U extends any - ? [All] extends [U] - ? U + TValue extends AnyDeepKeyAndValue, + All extends AnyDeepKeyAndValue = TValue, +> = TValue extends any + ? [All] extends [TValue] + ? TValue : Extract< All, - { key: `${U['key']}.${string}` | `${U['key']}[${number}]${string}` } + { + key: `${TValue['key']}.${string}` | `${TValue['key']}[${string}` + } > extends never - ? U + ? TValue : never : never diff --git a/packages/form-core/tests/util-types.test-d.ts b/packages/form-core/tests/util-types.test-d.ts index 43cd55d4d..17178d8ff 100644 --- a/packages/form-core/tests/util-types.test-d.ts +++ b/packages/form-core/tests/util-types.test-d.ts @@ -446,6 +446,7 @@ describe('DeepValue', () => { } type UserValue = DeepValue + // ^? actual: never type NumberValue = DeepValue expectTypeOf().toEqualTypeOf() From 25c10c16dc2fe01f911995e9f09904a6f7cfa32a Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:57:44 +0100 Subject: [PATCH 15/23] chore: attempt 3 --- packages/form-core/src/util-types.ts | 53 ++++++++++--------- packages/form-core/tests/util-types.test-d.ts | 10 ++-- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 02e584799..2e4b05160 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -19,6 +19,11 @@ type Try = A1 extends A2 ? A1 : Catch */ export type Narrow = Try> +type END = '\0' +type StripEnd = T extends `${infer Rest}${END}` ? Rest : T +type AddEnd = `${T}${END}` +type AddEndIfDynamic = AddEnd extends T ? AddEnd : T + export interface AnyDeepKeyAndValue< K extends string = string, V extends any = any, @@ -157,37 +162,37 @@ export type DeepKeys = unknown extends T ? string : DeepKeysAndValues['key'] -type ValueOfKey< - TValue extends AnyDeepKeyAndValue, - TAccessor extends string, -> = TValue extends any - ? TAccessor extends TValue['key'] - ? TValue - : never - : never +type DeepRecord = { + [K in DeepKeysAndValues as AddEndIfDynamic]: K['value'] +} -type Maximals< - TValue extends AnyDeepKeyAndValue, - All extends AnyDeepKeyAndValue = TValue, -> = TValue extends any - ? [All] extends [TValue] - ? TValue - : Extract< - All, - { - key: `${TValue['key']}.${string}` | `${TValue['key']}[${string}` - } - > extends never - ? TValue - : never - : never +type DeepValueImpl< + TDeepRecord extends DeepRecord, + TAccessor extends string, +> = TAccessor extends keyof TDeepRecord + ? TDeepRecord[TAccessor] + : TDeepRecord[AddEnd] /** * Infer the type of a deeply nested property within an object or an array. */ export type DeepValue = unknown extends TValue ? TValue - : Maximals, TAccessor>>['value'] + : DeepValueImpl, TAccessor> + +type Foo = { + a: string + b: number + c: { d: string } +} +type RecordExample = { + records: Record +} + +type E1 = DeepKeys +type E2 = DeepRecord + +type FooValue2 = DeepValue /** * The keys of an object or array, deeply nested and only with a value of TValue diff --git a/packages/form-core/tests/util-types.test-d.ts b/packages/form-core/tests/util-types.test-d.ts index 17178d8ff..0d159cd76 100644 --- a/packages/form-core/tests/util-types.test-d.ts +++ b/packages/form-core/tests/util-types.test-d.ts @@ -419,7 +419,6 @@ describe('DeepValue', () => { type NestedTuple = { topUsers: [User, 0, User] } type NumberValue = DeepValue - type NeverValue = DeepValue type NumberValue2 = DeepValue type UserValue = DeepValue type ZeroValue = DeepValue @@ -427,8 +426,6 @@ describe('DeepValue', () => { expectTypeOf().toBeNumber() - expectTypeOf().toBeNever() - expectTypeOf().toBeNumber() expectTypeOf().toEqualTypeOf() @@ -630,8 +627,11 @@ describe('DeepValue', () => { expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf>() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() + // TODO double records have problems at the moment. Perhaps this can be + // solved in the future without blowing up type instantiations. + + // expectTypeOf().toEqualTypeOf() + // expectTypeOf().toEqualTypeOf() expectTypeOf().toBeString() expectTypeOf().toBeString() From a785c78694844bd5cd25524ccd816c2a210359ba Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:04:39 +0100 Subject: [PATCH 16/23] box keyof check --- packages/form-core/src/util-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 2e4b05160..ad61a8c0c 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -169,7 +169,7 @@ type DeepRecord = { type DeepValueImpl< TDeepRecord extends DeepRecord, TAccessor extends string, -> = TAccessor extends keyof TDeepRecord +> = [TAccessor] extends [keyof TDeepRecord] ? TDeepRecord[TAccessor] : TDeepRecord[AddEnd] From 9e9fd88f3471f062d0c5ba88de28047ea05adb59 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:28:43 +0100 Subject: [PATCH 17/23] chore: attempt 4 --- packages/form-core/src/util-types.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index ad61a8c0c..88b9a4481 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -27,8 +27,10 @@ type AddEndIfDynamic = AddEnd extends T ? AddEnd : T export interface AnyDeepKeyAndValue< K extends string = string, V extends any = any, + L extends string = string, > { key: K + lookup: L value: V } @@ -40,6 +42,7 @@ export interface ArrayDeepKeyAndValue< in out T extends ReadonlyArray, > extends AnyDeepKeyAndValue { key: ArrayAccessor + lookup: ArrayAccessor value: T[number] | Nullable } @@ -64,6 +67,7 @@ export interface TupleDeepKeyAndValue< in out TKey extends AllTupleKeys, > extends AnyDeepKeyAndValue { key: TupleAccessor + lookup: TupleAccessor value: T[TKey] | Nullable } @@ -105,6 +109,7 @@ export interface ObjectDeepKeyAndValue< in out TKey extends AllObjectKeys, > extends AnyDeepKeyAndValue { key: ObjectAccessor + lookup: AddEndIfDynamic> value: ObjectValue } @@ -127,6 +132,7 @@ export type UnknownAccessor = export interface UnknownDeepKeyAndValue extends AnyDeepKeyAndValue { key: UnknownAccessor + lookup: UnknownAccessor value: unknown } @@ -163,22 +169,24 @@ export type DeepKeys = unknown extends T : DeepKeysAndValues['key'] type DeepRecord = { - [K in DeepKeysAndValues as AddEndIfDynamic]: K['value'] + [K in DeepKeysAndValues as K['lookup']]: K['value'] } type DeepValueImpl< TDeepRecord extends DeepRecord, - TAccessor extends string, -> = [TAccessor] extends [keyof TDeepRecord] - ? TDeepRecord[TAccessor] - : TDeepRecord[AddEnd] + TAccessor extends DeepKeys, +> = TDeepRecord[TAccessor] extends never + ? TDeepRecord[AddEnd] + : TDeepRecord[TAccessor] /** * Infer the type of a deeply nested property within an object or an array. */ export type DeepValue = unknown extends TValue ? TValue - : DeepValueImpl, TAccessor> + : TAccessor extends DeepKeys + ? DeepValueImpl, TAccessor> + : never type Foo = { a: string From e7fbb2fa048f50416ac65acba2e0399470060dee Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:44:52 +0100 Subject: [PATCH 18/23] fix type errors --- packages/form-core/src/util-types.ts | 36 ++++++++++------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 88b9a4481..2d0fb6b36 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -172,35 +172,14 @@ type DeepRecord = { [K in DeepKeysAndValues as K['lookup']]: K['value'] } -type DeepValueImpl< - TDeepRecord extends DeepRecord, - TAccessor extends DeepKeys, -> = TDeepRecord[TAccessor] extends never - ? TDeepRecord[AddEnd] - : TDeepRecord[TAccessor] - /** * Infer the type of a deeply nested property within an object or an array. */ export type DeepValue = unknown extends TValue ? TValue - : TAccessor extends DeepKeys - ? DeepValueImpl, TAccessor> - : never - -type Foo = { - a: string - b: number - c: { d: string } -} -type RecordExample = { - records: Record -} - -type E1 = DeepKeys -type E2 = DeepRecord - -type FooValue2 = DeepValue + : TAccessor extends keyof DeepRecord + ? DeepRecord[TAccessor] + : DeepRecord[AddEnd] /** * The keys of an object or array, deeply nested and only with a value of TValue @@ -225,3 +204,12 @@ export type FieldsMap = TFieldGroupData[K] > } + +type Foo = { + a: string + b: number + c: { d: string } +} +type RecordExample = { + records: Record +} From 4b2ced87b7ed252930faea9a5134907547c1bc62 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:00:41 +0100 Subject: [PATCH 19/23] chore: this time for sure --- packages/form-core/src/util-types.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 2d0fb6b36..38b907f16 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -172,14 +172,19 @@ type DeepRecord = { [K in DeepKeysAndValues as K['lookup']]: K['value'] } +type DeepValueImpl = + TAccessor extends DeepKeys + ? TAccessor extends keyof DeepRecord + ? DeepRecord[TAccessor] + : DeepRecord[AddEnd] + : never + /** * Infer the type of a deeply nested property within an object or an array. */ export type DeepValue = unknown extends TValue ? TValue - : TAccessor extends keyof DeepRecord - ? DeepRecord[TAccessor] - : DeepRecord[AddEnd] + : DeepValueImpl /** * The keys of an object or array, deeply nested and only with a value of TValue From 23127fbb1564451dbf732c13a418611e11453913 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:12:44 +0100 Subject: [PATCH 20/23] chore: move DeepRecord into one call --- packages/form-core/src/util-types.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 38b907f16..809eee4d8 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -172,11 +172,16 @@ type DeepRecord = { [K in DeepKeysAndValues as K['lookup']]: K['value'] } +type GetRecord< + TRecord extends DeepRecord, + TAccessor extends string, +> = [TAccessor] extends [keyof TRecord] + ? TRecord[TAccessor] + : TRecord[AddEnd] + type DeepValueImpl = TAccessor extends DeepKeys - ? TAccessor extends keyof DeepRecord - ? DeepRecord[TAccessor] - : DeepRecord[AddEnd] + ? GetRecord, TAccessor> : never /** From 8f7913c28bd7b81cf2139c27bff4bc74ec48d876 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:15:09 +0100 Subject: [PATCH 21/23] chore: add alias for DeepRecord --- packages/form-core/src/util-types.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 809eee4d8..ead36f760 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -168,9 +168,10 @@ export type DeepKeys = unknown extends T ? string : DeepKeysAndValues['key'] -type DeepRecord = { +type DeepRecordImpl = { [K in DeepKeysAndValues as K['lookup']]: K['value'] } +type DeepRecord = DeepRecordImpl type GetRecord< TRecord extends DeepRecord, @@ -179,10 +180,11 @@ type GetRecord< ? TRecord[TAccessor] : TRecord[AddEnd] -type DeepValueImpl = - TAccessor extends DeepKeys - ? GetRecord, TAccessor> - : never +type DeepValueImpl = [TAccessor] extends [ + DeepKeys, +] + ? GetRecord, TAccessor> + : never /** * Infer the type of a deeply nested property within an object or an array. From afaf42ebc0faa0cf6b9f8a5c7c53e2ee4773710f Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Tue, 11 Nov 2025 08:10:27 +0100 Subject: [PATCH 22/23] chore: try string approach again --- packages/form-core/src/util-types.ts | 66 +++++++++---------- packages/form-core/tests/util-types.test-d.ts | 7 +- 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index ead36f760..b929b5ada 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -19,18 +19,11 @@ type Try = A1 extends A2 ? A1 : Catch */ export type Narrow = Try> -type END = '\0' -type StripEnd = T extends `${infer Rest}${END}` ? Rest : T -type AddEnd = `${T}${END}` -type AddEndIfDynamic = AddEnd extends T ? AddEnd : T - export interface AnyDeepKeyAndValue< K extends string = string, V extends any = any, - L extends string = string, > { key: K - lookup: L value: V } @@ -42,7 +35,6 @@ export interface ArrayDeepKeyAndValue< in out T extends ReadonlyArray, > extends AnyDeepKeyAndValue { key: ArrayAccessor - lookup: ArrayAccessor value: T[number] | Nullable } @@ -67,7 +59,6 @@ export interface TupleDeepKeyAndValue< in out TKey extends AllTupleKeys, > extends AnyDeepKeyAndValue { key: TupleAccessor - lookup: TupleAccessor value: T[TKey] | Nullable } @@ -109,7 +100,6 @@ export interface ObjectDeepKeyAndValue< in out TKey extends AllObjectKeys, > extends AnyDeepKeyAndValue { key: ObjectAccessor - lookup: AddEndIfDynamic> value: ObjectValue } @@ -132,7 +122,6 @@ export type UnknownAccessor = export interface UnknownDeepKeyAndValue extends AnyDeepKeyAndValue { key: UnknownAccessor - lookup: UnknownAccessor value: unknown } @@ -168,30 +157,44 @@ export type DeepKeys = unknown extends T ? string : DeepKeysAndValues['key'] -type DeepRecordImpl = { - [K in DeepKeysAndValues as K['lookup']]: K['value'] -} -type DeepRecord = DeepRecordImpl - -type GetRecord< - TRecord extends DeepRecord, +type ValueMatchingAccessor< + TValue extends AnyDeepKeyAndValue, TAccessor extends string, -> = [TAccessor] extends [keyof TRecord] - ? TRecord[TAccessor] - : TRecord[AddEnd] - -type DeepValueImpl = [TAccessor] extends [ - DeepKeys, -] - ? GetRecord, TAccessor> +> = TValue extends TValue + ? TAccessor extends TValue['key'] + ? TValue + : never + : never + +type MostSpecificKey = MostSpecificKeyImpl< + TValue, + TValue +> + +type LongerPrefix = `${K}.${string}` | `${K}[${string}` + +type HasLonger = + Extract }> extends never ? false : true + +type MostSpecificKeyImpl< + TValue extends AnyDeepKeyAndValue, + TAll extends AnyDeepKeyAndValue, +> = TValue extends TValue + ? HasLonger extends true + ? never + : TValue : never +type DeepValueImpl = MostSpecificKey< + ValueMatchingAccessor, TAccessor> +> + /** * Infer the type of a deeply nested property within an object or an array. */ export type DeepValue = unknown extends TValue ? TValue - : DeepValueImpl + : DeepValueImpl['value'] /** * The keys of an object or array, deeply nested and only with a value of TValue @@ -216,12 +219,3 @@ export type FieldsMap = TFieldGroupData[K] > } - -type Foo = { - a: string - b: number - c: { d: string } -} -type RecordExample = { - records: Record -} diff --git a/packages/form-core/tests/util-types.test-d.ts b/packages/form-core/tests/util-types.test-d.ts index 0d159cd76..d1ec7d676 100644 --- a/packages/form-core/tests/util-types.test-d.ts +++ b/packages/form-core/tests/util-types.test-d.ts @@ -627,11 +627,8 @@ describe('DeepValue', () => { expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf>() - // TODO double records have problems at the moment. Perhaps this can be - // solved in the future without blowing up type instantiations. - - // expectTypeOf().toEqualTypeOf() - // expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() expectTypeOf().toBeString() expectTypeOf().toBeString() From da26a5ca14eb244851ca333a88f6facc0ac06941 Mon Sep 17 00:00:00 2001 From: LeCarbonator <18158911+LeCarbonator@users.noreply.github.com> Date: Tue, 11 Nov 2025 08:53:26 +0100 Subject: [PATCH 23/23] reduce instantiations with early exit --- packages/form-core/src/util-types.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index b929b5ada..5bb957cf2 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -182,7 +182,7 @@ type MostSpecificKeyImpl< > = TValue extends TValue ? HasLonger extends true ? never - : TValue + : TValue['value'] : never type DeepValueImpl = MostSpecificKey< @@ -194,7 +194,9 @@ type DeepValueImpl = MostSpecificKey< */ export type DeepValue = unknown extends TValue ? TValue - : DeepValueImpl['value'] + : TAccessor extends DeepKeys + ? DeepValueImpl + : never /** * The keys of an object or array, deeply nested and only with a value of TValue