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