feat(typescript-types): Add KeyPaths<T, F, O>
This commit is contained in:
@@ -44,11 +44,13 @@ The types included in this library are categorized by their purpose.
|
||||
|
||||
| Type | Description |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`KeyPaths<T, F, O>`][] | A union of all keypaths of `T` where the value does not extend `F`. Separator can be set via `O['separator']`. |
|
||||
| [`OptionalKeysOf<T>`][] | A union of all keys of `T` that are marked as optional. If `T` is a union, a union of the optional keys of all union members of `T` is returned[^optional-keys-of_remark]. |
|
||||
| [`PickAssignable<T, K>`][] | Return a mapped type with all keys of `T` that extend `K`. If no key does extend `K` an empty type is returned. |
|
||||
|
||||
[^optional-keys-of_remark]: If `T` is `any`, it returns a union of string and number 'string | number'.
|
||||
|
||||
[`KeyPaths<T, F, O>`]: src/key-paths.ts
|
||||
[`OptionalKeysOf<T>`]: src/optional-keys-of.ts
|
||||
[`PickAssignable<T, K>`]: src/pick-assignable.ts
|
||||
|
||||
|
||||
47
packages/typescript-types/src/key-paths.ts
Normal file
47
packages/typescript-types/src/key-paths.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { If } from './if.js'
|
||||
import type { IsEmptyString } from './is-empty-string.js'
|
||||
import type { Assign } from './assign.js'
|
||||
|
||||
interface KeyPaths_Options {
|
||||
separator?: string
|
||||
leavesOnly?: boolean
|
||||
}
|
||||
|
||||
interface KeyPaths_DefaultOptions {
|
||||
separator: '.'
|
||||
leavesOnly: true
|
||||
}
|
||||
|
||||
type GetPrefixedKey<
|
||||
Parent extends string,
|
||||
Key extends string,
|
||||
Separator extends string
|
||||
> = `${If<IsEmptyString<Parent>, '', `${Parent}${Separator}`>}${Key}`
|
||||
|
||||
type PrefixIfNot<Cond, Prefix, T> = If<Cond, T, Prefix | T>
|
||||
|
||||
type KeyPathOf<
|
||||
Obj,
|
||||
Options extends Required<KeyPaths_Options>,
|
||||
Parent extends string,
|
||||
Filter = never
|
||||
> = Obj extends object
|
||||
? PrefixIfNot<Options['leavesOnly'], Parent, KeyPathsOfStringKeys<Obj, Options, Filter, Parent>>
|
||||
: Parent
|
||||
|
||||
type KeyPathsOfStringKeys<
|
||||
Obj extends object,
|
||||
Options extends Required<KeyPaths_Options>,
|
||||
Filter = never,
|
||||
Parent extends string = ''
|
||||
> = {
|
||||
[Key in keyof Obj & string]: Obj[Key] extends Filter
|
||||
? never
|
||||
: KeyPathOf<Obj[Key], Options, GetPrefixedKey<Parent, Key, Options['separator']>, Filter>
|
||||
}[keyof Obj & string]
|
||||
|
||||
export type KeyPaths<
|
||||
Obj extends object,
|
||||
Filter = null | undefined | never,
|
||||
Options extends KeyPaths_Options = {}
|
||||
> = KeyPathsOfStringKeys<Obj, Assign<KeyPaths_Options, KeyPaths_DefaultOptions, Options>, Filter>
|
||||
32
packages/typescript-types/test/key-paths.tst.ts
Normal file
32
packages/typescript-types/test/key-paths.tst.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { KeyPaths } from '@/key-paths.js'
|
||||
import { expect } from 'tstyche'
|
||||
|
||||
interface ExampleObject {
|
||||
nullvalue: null
|
||||
simplevalue: string
|
||||
config: {
|
||||
nullvalue: null
|
||||
simplevalue: string
|
||||
}
|
||||
}
|
||||
|
||||
expect<KeyPaths<ExampleObject>>().type.toBe<'simplevalue' | 'config.simplevalue'>()
|
||||
expect<KeyPaths<ExampleObject, never>>().type.toBe<
|
||||
'nullvalue' | 'simplevalue' | 'config.nullvalue' | 'config.simplevalue'
|
||||
>()
|
||||
expect<KeyPaths<ExampleObject, null>>().type.toBe<'simplevalue' | 'config.simplevalue'>()
|
||||
expect<KeyPaths<ExampleObject, string>>().type.toBe<'nullvalue' | 'config.nullvalue'>()
|
||||
|
||||
expect<KeyPaths<ExampleObject, null, { separator: '-' }>>().type.toBe<'simplevalue' | 'config-simplevalue'>()
|
||||
expect<KeyPaths<ExampleObject, null, { leavesOnly: false }>>().type.toBe<
|
||||
'simplevalue' | 'config' | 'config.simplevalue'
|
||||
>()
|
||||
|
||||
expect<KeyPaths<any, string>>().type.toBe<unknown>()
|
||||
expect<KeyPaths<any, never>>().type.toBe<unknown>()
|
||||
|
||||
expect<KeyPaths<ExampleObject, any>>().type.toBeAssignableTo<never>()
|
||||
expect<KeyPaths<ExampleObject, unknown>>().type.toBeAssignableTo<never>()
|
||||
expect<KeyPaths<any, any>>().type.toBeAssignableTo<never>()
|
||||
expect<KeyPaths<never, any>>().type.toBeAssignableTo<never>()
|
||||
expect<KeyPaths<never, never>>().type.toBeAssignableTo<never>()
|
||||
Reference in New Issue
Block a user