From 16cb1ea0cfe396546861af6b2f08e0abf1ac809c Mon Sep 17 00:00:00 2001 From: Harry Whorlow Date: Thu, 22 Jan 2026 20:03:06 +0100 Subject: [PATCH 1/4] fix(deep-keys): fix recursive types --- packages/devtools-ui/tests/deep-keys.ts | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 packages/devtools-ui/tests/deep-keys.ts diff --git a/packages/devtools-ui/tests/deep-keys.ts b/packages/devtools-ui/tests/deep-keys.ts new file mode 100644 index 00000000..986cf3e7 --- /dev/null +++ b/packages/devtools-ui/tests/deep-keys.ts @@ -0,0 +1,67 @@ +import type { CollapsiblePaths } from '../src/utils/deep-keys' + +type WithDeeplyNestedObject = { + a: { + b: { + c: { + d: { + e: { + f: { + g: { + h: { + i: { + j: number + } + } + } + } + } + } + } + } + } +} + +type _DeeplyNestedObject = CollapsiblePaths + +type WithAny = { + errors?: any +} + +type _Any = CollapsiblePaths + +type ArrayRecursion = { arr: Array>>> } + +type _ArrayRecursion = CollapsiblePaths + +type WithUndefined = { + status?: { + valid: boolean + error?: { + message: string + } + } +} + +type _WithUndefined = CollapsiblePaths + +type WithUnknown = { + payload: unknown +} + +type _WithUnknown = CollapsiblePaths + +type WithRealisticState = { + canSubmit?: boolean + isSubmitting?: boolean + errors?: Array + errorMap?: Record +} + +type _WithRealisticState = CollapsiblePaths + +type WithGeneric = { + generic: TData +} + +type _WithGeneric = CollapsiblePaths> From 84c7c8452ee259847b595d05852ae030f3ccfc49 Mon Sep 17 00:00:00 2001 From: Harry Whorlow Date: Tue, 3 Mar 2026 22:00:11 +0100 Subject: [PATCH 2/4] chore: add test --- packages/devtools-ui/src/utils/deep-keys.ts | 38 +++++++++----- packages/devtools-ui/tests/deep-keys.ts | 55 +++++++++++++++++++++ 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/packages/devtools-ui/src/utils/deep-keys.ts b/packages/devtools-ui/src/utils/deep-keys.ts index 4d76f258..37d342b0 100644 --- a/packages/devtools-ui/src/utils/deep-keys.ts +++ b/packages/devtools-ui/src/utils/deep-keys.ts @@ -1,18 +1,30 @@ +type IsAny = 0 extends 1 & T ? true : false + +type Clean = Exclude + type CollapsibleKeys = - T extends ReadonlyArray - ? - | (TPrefix extends '' ? '' : TPrefix) - | CollapsibleKeys - : T extends object + IsAny extends true + ? TPrefix extends '' + ? never + : TPrefix + : T extends ReadonlyArray ? | (TPrefix extends '' ? '' : TPrefix) - | { - [K in Extract]: CollapsibleKeys< - T[K], - TPrefix extends '' ? `${K}` : `${TPrefix}.${K}` - > - }[Extract] - : never + | CollapsibleKeys + : T extends object + ? + | (TPrefix extends '' ? '' : TPrefix) + | { + [K in Extract]: CollapsibleKeys< + T[K], + TPrefix extends '' ? `${K}` : `${TPrefix}.${K}` + > + }[Extract] + : never export type CollapsiblePaths = - CollapsibleKeys extends string ? CollapsibleKeys : never + CollapsibleKeys> extends infer P + ? P extends string + ? P + : never + : never diff --git a/packages/devtools-ui/tests/deep-keys.ts b/packages/devtools-ui/tests/deep-keys.ts index 986cf3e7..c7471f85 100644 --- a/packages/devtools-ui/tests/deep-keys.ts +++ b/packages/devtools-ui/tests/deep-keys.ts @@ -1,3 +1,6 @@ +import { describe, expectTypeOf, it } from 'vitest' + +// types import type { CollapsiblePaths } from '../src/utils/deep-keys' type WithDeeplyNestedObject = { @@ -65,3 +68,55 @@ type WithGeneric = { } type _WithGeneric = CollapsiblePaths> + +describe('deep-keys', () => { + it('should type deeply nested keys', () => { + expectTypeOf<_DeeplyNestedObject>().toEqualTypeOf< + | '' + | 'a' + | 'a.b' + | 'a.b.c' + | 'a.b.c.d' + | 'a.b.c.d.e' + | 'a.b.c.d.e.f' + | 'a.b.c.d.e.f.g' + | 'a.b.c.d.e.f.g.h' + | 'a.b.c.d.e.f.g.h.i' + >() + }) + + it('should handle any', () => { + expectTypeOf<_Any>().toEqualTypeOf<'' | 'errors'>() + }) + + it('should handle array recursion', () => { + expectTypeOf<_ArrayRecursion>().toEqualTypeOf< + | '' + | 'arr' + | `arr[${number}]` + | `arr[${number}][${number}]` + | `arr[${number}][${number}][${number}]` + | `arr[${number}][${number}][${number}][${number}]` + >() + }) + + it('should handle undefined', () => { + expectTypeOf<_WithUndefined>().toEqualTypeOf< + '' | 'status' | 'status.error' + >() + }) + + it('should handle unknown', () => { + expectTypeOf<_WithUnknown>().toEqualTypeOf<''>() + }) + + it('should handle realistic state', () => { + expectTypeOf<_WithRealisticState>().toEqualTypeOf< + '' | 'errors' | 'errorMap' | `errors[${number}]` | `errorMap.${string}` + >() + }) + + it('should handle generics', () => { + expectTypeOf<_WithGeneric>().toEqualTypeOf<'' | 'generic' | 'generic.a'>() + }) +}) From c71c07dac39fe952ceb7b53f2c4241b849b5f10d Mon Sep 17 00:00:00 2001 From: Harry Whorlow Date: Tue, 3 Mar 2026 22:14:01 +0100 Subject: [PATCH 3/4] fix: knip --- knip.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/knip.json b/knip.json index e8af5e1d..24871a78 100644 --- a/knip.json +++ b/knip.json @@ -8,6 +8,9 @@ "entry": ["**/vite.config.solid.ts", "**/src/solid/**"], "project": ["**/vite.config.solid.ts", "**/src/solid/**"] }, + "packages/devtools-ui": { + "entry": ["**/tests/**/*.ts"] + }, "packages/solid-devtools": { "ignore": ["**/core.tsx"] } From 3a6d3ee7697b79e8294db334926ae4997be19843 Mon Sep 17 00:00:00 2001 From: Harry Whorlow Date: Tue, 3 Mar 2026 22:27:29 +0100 Subject: [PATCH 4/4] chore: changeset --- .changeset/slimy-ways-yawn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slimy-ways-yawn.md diff --git a/.changeset/slimy-ways-yawn.md b/.changeset/slimy-ways-yawn.md new file mode 100644 index 00000000..cac431e9 --- /dev/null +++ b/.changeset/slimy-ways-yawn.md @@ -0,0 +1,5 @@ +--- +'@tanstack/devtools-ui': patch +--- + +Fixes the deep-keys utils for the collapsePath prop, now handles any and unknown types.