/
pathOr.ts
165 lines (150 loc) · 4.62 KB
/
pathOr.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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { purry } from "./purry";
/**
* Given a union of indexable types `T`, we derive an indexable type
* containing all of the keys of each variant of `T`. If a key is
* present in multiple variants of `T`, then the corresponding type in
* `Pathable<T>` will be the intersection of all types for that key.
*
* @example
* type T1 = Pathable<{a: number} | {a: string; b: boolean}>
* // {a: number | string; b: boolean}
*
* type T2 = Pathable<{a?: {b: string}}
* // {a: {b: string} | undefined}
*
* type T3 = Pathable<{a: string} | number>
* // {a: string}
*
* type T4 = Pathable<{a: number} | {a: string} | {b: boolean}>
* // {a: number | string; b: boolean}
*
* This type lets us answer the questions:
* - Given some object of type `T`, what keys might this object have?
* - If this object did happen to have a particular key, what values
* might that key have?
*/
type Pathable<T> = { [K in AllKeys<T>]: TypesForKey<T, K> };
type AllKeys<T> = T extends infer I ? keyof I : never;
type TypesForKey<T, K extends PropertyKey> = T extends infer I
? K extends keyof I
? I[K]
: never
: never;
// Like Required<T>, but also removes explicit null and undefined typings */
type StrictlyRequired<T> = { [K in keyof T]-?: NonNullable<T[K]> };
/**
* Given some `A` which is a key of at least one variant of `T`, derive
* `T[A]` for the cases where `A` is present in `T`, and `T[A]` is not
* null or undefined.
*/
type PathValue1<T, A extends keyof Pathable<T>> = StrictlyRequired<
Pathable<T>
>[A];
/** All possible options after successfully reaching `T[A]`. */
type Pathable1<T, A extends keyof Pathable<T>> = Pathable<PathValue1<T, A>>;
/** As `PathValue1`, but for `T[A][B]`. */
type PathValue2<
T,
A extends keyof Pathable<T>,
B extends keyof Pathable1<T, A>,
> = StrictlyRequired<Pathable1<T, A>>[B];
/** As `Pathable1`, but for `T[A][B]`. */
type Pathable2<
T,
A extends keyof Pathable<T>,
B extends keyof Pathable1<T, A>,
> = Pathable<PathValue2<T, A, B>>;
/** As `PathValue1`, but for `T[A][B][C]`. */
type PathValue3<
T,
A extends keyof Pathable<T>,
B extends keyof Pathable1<T, A>,
C extends keyof Pathable2<T, A, B>,
> = StrictlyRequired<Pathable2<T, A, B>>[C];
/**
* Gets the value at `path` of `object`. If the resolved value is `null` or `undefined`, the `defaultValue` is returned in its place.
*
* @param object - The target object.
* @param path - The path of the property to get.
* @param defaultValue - The default value.
* @signature R.pathOr(object, array, defaultValue)
* @example
* R.pathOr({x: 10}, ['y'], 2) // 2
* R.pathOr({y: 10}, ['y'], 2) // 10
* @dataFirst
* @category Object
*/
export function pathOr<T, A extends keyof Pathable<T>>(
object: T,
path: readonly [A],
defaultValue: PathValue1<T, A>,
): PathValue1<T, A>;
export function pathOr<
T,
A extends keyof Pathable<T>,
B extends keyof Pathable1<T, A>,
>(
object: T,
path: readonly [A, B],
defaultValue: PathValue2<T, A, B>,
): PathValue2<T, A, B>;
export function pathOr<
T,
A extends keyof Pathable<T>,
B extends keyof Pathable1<T, A>,
C extends keyof Pathable2<T, A, B>,
>(
object: T,
path: readonly [A, B, C],
defaultValue: PathValue3<T, A, B, C>,
): PathValue3<T, A, B, C>;
/**
* Gets the value at `path` of `object`. If the resolved value is `undefined`, the `defaultValue` is returned in its place.
*
* @param path - The path of the property to get.
* @param defaultValue - The default value.
* @signature R.pathOr(array, defaultValue)(object)
* @example
* R.pipe({x: 10}, R.pathOr(['y'], 2)) // 2
* R.pipe({y: 10}, R.pathOr(['y'], 2)) // 10
* @dataLast
* @category Object
*/
export function pathOr<T, A extends keyof Pathable<T>>(
path: readonly [A],
defaultValue: PathValue1<T, A>,
): (object: T) => PathValue1<T, A>;
export function pathOr<
T,
A extends keyof Pathable<T>,
B extends keyof Pathable1<T, A>,
>(
path: readonly [A, B],
defaultValue: PathValue2<T, A, B>,
): (object: T) => PathValue2<T, A, B>;
export function pathOr<
T,
A extends keyof Pathable<T>,
B extends keyof Pathable1<T, A>,
C extends keyof Pathable2<T, A, B>,
>(
path: readonly [A, B, C],
defaultValue: PathValue3<T, A, B, C>,
): (object: T) => PathValue3<T, A, B, C>;
export function pathOr(): unknown {
return purry(_pathOr, arguments);
}
function _pathOr(
data: unknown,
path: ReadonlyArray<PropertyKey>,
defaultValue: unknown,
): unknown {
let current = data;
for (const prop of path) {
if (current === null || current === undefined) {
break;
}
current = (current as Record<PropertyKey, unknown>)[prop];
}
return current ?? defaultValue;
}