UNPKG

2.55 kBTypeScriptView Raw
1import type {StaticPartOfArray, VariablePartOfArray, NonRecursiveType, ToString} from './internal';
2import type {EmptyObject} from './empty-object';
3import type {IsAny} from './is-any';
4import type {IsNever} from './is-never';
5import type {UnknownArray} from './unknown-array';
6import type {Sum} from './sum';
7import type {LessThan} from './less-than';
8
9/**
10Generate a union of all possible paths to properties in the given object.
11
12It also works with arrays.
13
14Use-case: You want a type-safe way to access deeply nested properties in an object.
15
16@example
17```
18import type {Paths} from 'type-fest';
19
20type Project = {
21 filename: string;
22 listA: string[];
23 listB: [{filename: string}];
24 folder: {
25 subfolder: {
26 filename: string;
27 };
28 };
29};
30
31type ProjectPaths = Paths<Project>;
32//=> 'filename' | 'listA' | 'listB' | 'folder' | `listA.${number}` | 'listB.0' | 'listB.0.filename' | 'folder.subfolder' | 'folder.subfolder.filename'
33
34declare function open<Path extends ProjectPaths>(path: Path): void;
35
36open('filename'); // Pass
37open('folder.subfolder'); // Pass
38open('folder.subfolder.filename'); // Pass
39open('foo'); // TypeError
40
41// Also works with arrays
42open('listA.1'); // Pass
43open('listB.0'); // Pass
44open('listB.1'); // TypeError. Because listB only has one element.
45```
46
47@category Object
48@category Array
49*/
50export type Paths<T> = Paths_<T>;
51
52type 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
67export 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)];