/
paths.d.ts
79 lines (68 loc) · 2.18 KB
/
paths.d.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import type {StaticPartOfArray, VariablePartOfArray, NonRecursiveType, ToString} from './internal';
import type {EmptyObject} from './empty-object';
import type {IsAny} from './is-any';
import type {IsNever} from './is-never';
import type {UnknownArray} from './unknown-array';
/**
Generate a union of all possible paths to properties in the given object.
It also works with arrays.
Use-case: You want a type-safe way to access deeply nested properties in an object.
@example
```
import type {Paths} from 'type-fest';
type Project = {
filename: string;
listA: string[];
listB: [{filename: string}];
folder: {
subfolder: {
filename: string;
};
};
};
type ProjectPaths = Paths<Project>;
//=> 'filename' | 'listA' | 'listB' | 'folder' | `listA.${number}` | 'listB.0' | 'listB.0.filename' | 'folder.subfolder' | 'folder.subfolder.filename'
declare function open<Path extends ProjectPaths>(path: Path): void;
open('filename'); // Pass
open('folder.subfolder'); // Pass
open('folder.subfolder.filename'); // Pass
open('foo'); // TypeError
// Also works with arrays
open('listA.1'); // Pass
open('listB.0'); // Pass
open('listB.1'); // TypeError. Because listB only has one element.
```
@category Object
@category Array
*/
export type Paths<T> =
T extends NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>
? never
: IsAny<T> extends true
? never
: T extends UnknownArray
? number extends T['length']
// We need to handle the fixed and non-fixed index part of the array separately.
? InternalPaths<StaticPartOfArray<T>>
| InternalPaths<Array<VariablePartOfArray<T>[number]>>
: InternalPaths<T>
: T extends object
? InternalPaths<T>
: never;
export type InternalPaths<_T, T = Required<_T>> =
T extends EmptyObject | readonly []
? never
: {
[Key in keyof T]:
Key extends string | number // Limit `Key` to string or number.
// If `Key` is a number, return `Key | `${Key}``, because both `array[0]` and `array['0']` work.
?
| Key
| ToString<Key>
| (
IsNever<Paths<T[Key]>> extends false
? `${Key}.${Paths<T[Key]>}`
: never
)
: never
}[keyof T & (T extends UnknownArray ? number : unknown)];