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

Rewrite KeyPaths to eleminate 'Too deep instantiations' error of
TypeScript.
This commit is contained in:
T. R. Bernstein
2025-07-22 11:49:08 +02:00
parent 0e7238998d
commit 434d91ff9d
18 changed files with 163 additions and 75 deletions

View File

@@ -0,0 +1,7 @@
import type { And } from '@/and.js'
import { expect } from 'tstyche'
expect<And<true, true>>().type.toBe<true>()
expect<And<true, false>>().type.toBe<false>()
expect<And<false, true>>().type.toBe<false>()
expect<And<false, false>>().type.toBe<false>()

View File

@@ -0,0 +1,8 @@
import type { Concat } from '@/concat.js'
import { expect } from 'tstyche'
expect<Concat<'', '', ''>>().type.toBe<''>()
expect<Concat<'Parent', '', ''>>().type.toBe<'Parent'>()
expect<Concat<'Parent', 'Child', ''>>().type.toBe<'ParentChild'>()
expect<Concat<'Parent', 'Child', '.'>>().type.toBe<'Parent.Child'>()
expect<Concat<'', 'Child', '.'>>().type.toBe<'Child'>()

View File

@@ -0,0 +1,7 @@
import type { ExtendsExactly } from '@/extends-exactly.js'
import { expect } from 'tstyche'
expect<ExtendsExactly<true, true>>().type.toBe<true>()
expect<ExtendsExactly<true, boolean>>().type.toBe<true>()
expect<ExtendsExactly<boolean, true>>().type.toBe<false>()
expect<ExtendsExactly<false, true>>().type.toBe<false>()

View File

@@ -0,0 +1,10 @@
import type { Extends } from '@/extends.js'
import { expect } from 'tstyche'
expect<Extends<true, true>>().type.toBe<true>()
expect<Extends<true, boolean>>().type.toBe<true>()
expect<Extends<boolean, true>>().type.toBe<boolean>()
expect<Extends<false, true>>().type.toBe<false>()
expect<Extends<false, never>>().type.toBe<false>()
expect<Extends<null, never>>().type.toBe<false>()
expect<Extends<undefined, never>>().type.toBe<false>()

View File

@@ -4,50 +4,31 @@ import { expect } from 'tstyche'
interface ExampleObject {
nullvalue: null
simplevalue: string
optionalKey?: {
nullvalue: null
simplevalue: string
}
unknownKey: unknown
neverKey: never
config: {
nullvalue: null
simplevalue: string
}
undefinedKey: undefined
undefinedOptionalKey?: undefined
optionalKey?: string
arrayKey: [{ name: string; age: number }, 1]
optionalArrayKey?: [{ name: string }, { age?: number }]
}
type AllKeys =
type KeysWhenFilterIsSetToNever =
| 'nullvalue'
| 'simplevalue'
| 'optionalKey.nullvalue'
| 'optionalKey.simplevalue'
| 'unknownKey'
| 'config.nullvalue'
| 'config.simplevalue'
| 'undefinedKey'
| 'undefinedOptionalKey'
| 'optionalKey?'
| 'arrayKey.0.name'
| 'arrayKey.0.age'
| 'arrayKey.1'
| 'optionalArrayKey?.0.name'
| 'optionalArrayKey?.1.age?'
// type T = any extends never ? true : false
// type A = KeyPaths<ExampleObject, {}, never>
expect<KeyPaths<ExampleObject>>().type.toBe<
'simplevalue' | 'optionalKey.simplevalue' | 'unknownKey' | 'config.simplevalue'
'simplevalue' | 'arrayKey.0.name' | 'arrayKey.0.age' | 'arrayKey.1'
>()
expect<KeyPaths<ExampleObject, {}, any>>().type.toBeAssignableTo<never>()
expect<KeyPaths<ExampleObject, {}, unknown>>().type.toBeAssignableTo<never>()
expect<KeyPaths<ExampleObject, {}, never>>().type.toBe<AllKeys>()
expect<KeyPaths<ExampleObject, { invertFilter: true }, any>>().type.toBe<AllKeys>()
expect<KeyPaths<ExampleObject, {}, null>>().type.toBe<
'simplevalue' | 'optionalKey.simplevalue' | 'unknownKey' | 'config.simplevalue'
>()
expect<KeyPaths<ExampleObject, {}, string>>().type.toBe<
'nullvalue' | 'optionalKey.nullvalue' | 'unknownKey' | 'config.nullvalue'
>()
expect<KeyPaths<ExampleObject, { separator: '-' }>>().type.toBe<
'simplevalue' | 'optionalKey-simplevalue' | 'unknownKey' | 'config-simplevalue'
>()
expect<KeyPaths<ExampleObject, { leavesOnly: false }>>().type.toBe<
'simplevalue' | 'optionalKey' | 'optionalKey.simplevalue' | 'unknownKey' | 'config' | 'config.simplevalue'
>()
expect<KeyPaths<any, {}, any>>().type.toBeAssignableTo<never>()
expect<KeyPaths<never, {}, any>>().type.toBeAssignableTo<never>()
expect<KeyPaths<never, {}, never>>().type.toBeAssignableTo<never>()
expect<KeyPaths<any, {}, never>>().type.toBe<unknown>()
expect<KeyPaths<any, {}, string>>().type.toBeAssignableTo<unknown>()
expect<KeyPaths<ExampleObject, {}, never>>().type.toBe<KeysWhenFilterIsSetToNever>()
expect<KeyPaths<ExampleObject, {}, any>>().type.toBeAssignableWith<never>()

View File

@@ -0,0 +1,5 @@
import type { Not } from '@/not.js'
import { expect } from 'tstyche'
expect<Not<true>>().type.toBe<false>()
expect<Not<false>>().type.toBe<true>()

View File

@@ -0,0 +1,7 @@
import type { Or } from '@/or.js'
import { expect } from 'tstyche'
expect<Or<true, true>>().type.toBe<true>()
expect<Or<true, false>>().type.toBe<true>()
expect<Or<false, true>>().type.toBe<true>()
expect<Or<false, false>>().type.toBe<false>()

View File

@@ -0,0 +1,7 @@
import type { Xor } from '@/xor.js'
import { expect } from 'tstyche'
expect<Xor<true, true>>().type.toBe<false>()
expect<Xor<true, false>>().type.toBe<true>()
expect<Xor<false, true>>().type.toBe<true>()
expect<Xor<false, false>>().type.toBe<false>()