feat(typescript-types): Fully rewrite KeyPaths<T,O,F>

Again to solve 'Too deep instantiation', but this time using tail
recursion optimization on conditions.
This commit is contained in:
T. R. Bernstein
2025-07-28 22:30:48 +02:00
parent 9170f6856a
commit eba47b51bd
2 changed files with 28 additions and 8 deletions

View File

@@ -4,6 +4,7 @@ import type { And } from './and.js'
import type { Not } from './not.js' import type { Not } from './not.js'
import type { IsEmptyString } from './is-empty-string.js' import type { IsEmptyString } from './is-empty-string.js'
import type { Extends } from './extends.js' import type { Extends } from './extends.js'
import type { ExtendsExactly } from './extends-exactly.js'
import type { Concat } from './concat.js' import type { Concat } from './concat.js'
import type { Assign } from './assign.js' import type { Assign } from './assign.js'
import type { NonContainerType } from './non-container-type.js' import type { NonContainerType } from './non-container-type.js'
@@ -22,8 +23,18 @@ interface KeyPaths_DefaultOptions {
invertFilter: false invertFilter: false
} }
type ExtendsFilter<Obj, Key extends keyof Obj, Filter> = type ExtendsFilter<
Extends<Obj[Key], Filter> extends false ? false : true Obj,
Key extends keyof Obj,
Filter,
MatchExactly extends boolean = false
> = MatchExactly extends false
? Extends<Obj[Key], Filter> extends false
? false
: true
: ExtendsExactly<Obj[Key], Filter> extends false
? false
: true
type IncludeElement<Obj, Key extends keyof Obj, Filter, Options extends RequiredOptions> = Obj extends never type IncludeElement<Obj, Key extends keyof Obj, Filter, Options extends RequiredOptions> = Obj extends never
? false ? false
@@ -33,10 +44,16 @@ type IncludeElement<Obj, Key extends keyof Obj, Filter, Options extends Required
? false ? false
: Not< : Not<
Or< Or<
And<ExtendsFilter<Obj, Key, Filter>, Not<Options['invertFilter']>>,
And< And<
And<Not<ExtendsFilter<Obj, Key, Filter>>, Options['invertFilter']>, ExtendsFilter<Obj, Key, Filter, Options['invertFilter']>,
Not<Obj[Key] extends object ? true : false> Not<Options['invertFilter']>
>,
And<
And<
Not<ExtendsFilter<Obj, Key, Filter, Options['invertFilter']>>,
Options['invertFilter']
>,
Not<Obj[Key] extends object | any[] ? true : false>
> >
> >
> >
@@ -80,4 +97,5 @@ export type KeyPaths<
Obj, Obj,
Options extends KeyPaths_Options = {}, Options extends KeyPaths_Options = {},
Filter = null | undefined Filter = null | undefined
> = KeyPathsOfStringKeys<Obj, keyof Obj, Assign<KeyPaths_Options, KeyPaths_DefaultOptions, Options>, Filter> > = KeyPathsOfStringKeys<Obj, keyof Obj, Assign<KeyPaths_Options, KeyPaths_DefaultOptions, Options>, Filter> &
string

View File

@@ -70,8 +70,9 @@ type KeysWithLeavesOnlyDisabled =
| 'arrayKey.0.age' | 'arrayKey.0.age'
| 'arrayKey.1' | 'arrayKey.1'
// type T = any extends never ? true : false type KeysWithStringValuesOnly = 'simplevalue' | 'objectKey.name' | 'arrayKey.0.name'
// type A = KeyPaths<ExampleObject, { invertFilter: true }>
// type A = KeyPaths<ExampleObject, { invertFilter: true }, string>
expect<KeyPaths<ExampleObject>>().type.toBe<KeysWithDefaultSettings>() expect<KeyPaths<ExampleObject>>().type.toBe<KeysWithDefaultSettings>()
expect<KeyPaths<ExampleObject, { invertFilter: true }>>().type.toBe<KeysWithInvertFilterSettings>() expect<KeyPaths<ExampleObject, { invertFilter: true }>>().type.toBe<KeysWithInvertFilterSettings>()
expect<KeyPaths<ExampleObject, { separator: '-' }>>().type.toBe<KeysWithSpecialSeparator>() expect<KeyPaths<ExampleObject, { separator: '-' }>>().type.toBe<KeysWithSpecialSeparator>()
@@ -82,3 +83,4 @@ expect<KeyPaths<ExampleObject, {}, any>>().type.toBeAssignableWith<never>()
expect<KeyPaths<ExampleObject, { invertFilter: true }, never>>().type.toBeAssignableWith<never>() expect<KeyPaths<ExampleObject, { invertFilter: true }, never>>().type.toBeAssignableWith<never>()
expect<KeyPaths<ExampleObject, { invertFilter: true }, any>>().type.toBe<KeysWhenFilterIsSetToNever>() expect<KeyPaths<ExampleObject, { invertFilter: true }, any>>().type.toBe<KeysWhenFilterIsSetToNever>()
expect<KeyPaths<ExampleObject, { invertFilter: true }, string>>().type.toBe<KeysWithStringValuesOnly>()