Skip to content

Commit 07b3377

Browse files
committed
fix(form-core): fix DeepValue from record values being wrong
1 parent 77acb33 commit 07b3377

File tree

2 files changed

+60
-11
lines changed

2 files changed

+60
-11
lines changed

packages/form-core/src/util-types.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,18 @@ export type DeepKeysAndValuesImpl<
150150
? DeepKeyAndValueObject<TParent, T, TAcc>
151151
: TAcc
152152

153-
export type DeepRecord<T> = {
154-
[TRecord in DeepKeysAndValues<T> as TRecord['key']]: TRecord['value']
153+
type DeepRecordWithDynamicSuffix<T> = {
154+
// DeepKeys uses a dot as reserved character, so the only way that it can be a suffix
155+
// is if the suffix is a dynamic variable.
156+
[TRecord in DeepKeysAndValues<T> as `${TRecord['key']}.` extends TRecord['key']
157+
? TRecord['key']
158+
: never]: TRecord['value']
159+
}
160+
161+
type DeepRecordWithStaticSuffix<T> = {
162+
[TRecord in DeepKeysAndValues<T> as `${TRecord['key']}.` extends TRecord['key']
163+
? never
164+
: TRecord['key']]: TRecord['value']
155165
}
156166

157167
/**
@@ -161,19 +171,22 @@ export type DeepKeys<T> = unknown extends T
161171
? string
162172
: DeepKeysAndValues<T>['key']
163173

164-
/**
165-
* Infer the type of a deeply nested property within an object or an array.
166-
*/
167-
export type DeepValue<TValue, TAccessor> = unknown extends TValue
168-
? TValue
169-
: TAccessor extends DeepKeys<TValue>
170-
? DeepRecord<TValue>[TAccessor]
171-
: never
172-
173174
/**
174175
* The keys of an object or array, deeply nested and only with a value of TValue
175176
*/
176177
export type DeepKeysOfType<TData, TValue> = Extract<
177178
DeepKeysAndValues<TData>,
178179
AnyDeepKeyAndValue<string, TValue>
179180
>['key']
181+
182+
type PickValue<T, K extends keyof T> = T[K]
183+
/**
184+
* Infer the type of a deeply nested property within an object or an array.
185+
*/
186+
export type DeepValue<TValue, TAccessor> = unknown extends TValue
187+
? TValue
188+
: TAccessor extends keyof DeepRecordWithStaticSuffix<TValue>
189+
? PickValue<DeepRecordWithStaticSuffix<TValue>, TAccessor>
190+
: TAccessor extends keyof DeepRecordWithDynamicSuffix<TValue>
191+
? PickValue<DeepRecordWithDynamicSuffix<TValue>, TAccessor>
192+
: never

packages/form-core/tests/util-types.test-d.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,39 @@ type AnyObjectExample4 = DeepValue<ObjectWithAny, 'obj.c'>
446446
expectTypeOf(0 as never as AnyObjectExample4).toEqualTypeOf<any>()
447447
type AnyObjectExample5 = DeepValue<ObjectWithAny, 'obj.d'>
448448
expectTypeOf(0 as never as AnyObjectExample5).toEqualTypeOf<number>()
449+
450+
type RecordExample = {
451+
records: Record<string, { a: string; b: number; c: { d: string } }>
452+
}
453+
454+
expectTypeOf<DeepKeys<RecordExample>>().toEqualTypeOf<
455+
| 'records'
456+
| `records.${string}`
457+
| `records.${string}.a`
458+
| `records.${string}.b`
459+
| `records.${string}.c`
460+
| `records.${string}.c.d`
461+
>()
462+
expectTypeOf<DeepKeysOfType<RecordExample, string>>().toEqualTypeOf<
463+
`records.${string}.a` | `records.${string}.c.d`
464+
>()
465+
expectTypeOf<DeepValue<RecordExample, 'records'>>().toEqualTypeOf<
466+
Record<string, { a: string; b: number; c: { d: string } }>
467+
>()
468+
expectTypeOf<DeepValue<RecordExample, 'records.something'>>().toEqualTypeOf<{
469+
a: string
470+
b: number
471+
c: { d: string }
472+
}>()
473+
expectTypeOf<
474+
DeepValue<RecordExample, 'records.something.a'>
475+
>().toEqualTypeOf<string>()
476+
expectTypeOf<
477+
DeepValue<RecordExample, 'records.something.b'>
478+
>().toEqualTypeOf<number>()
479+
expectTypeOf<DeepValue<RecordExample, 'records.something.c'>>().toEqualTypeOf<{
480+
d: string
481+
}>()
482+
expectTypeOf<
483+
DeepValue<RecordExample, 'records.something.c.d'>
484+
>().toEqualTypeOf<string>()

0 commit comments

Comments
 (0)