inferred-types 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +5 -0
- package/dist/index.d.ts +441 -126
- package/dist/index.js +80 -6
- package/dist/index.mjs +74 -6
- package/package.json +1 -1
- package/src/types/Mutable.ts +3 -1
- package/src/types/TypeInfo/Extends.ts +14 -0
- package/src/types/TypeInfo/IsBooleanLiteral.ts +9 -0
- package/src/types/TypeInfo/IsLiteral.ts +36 -2
- package/src/types/TypeInfo/IsNumericLiteral.ts +9 -0
- package/src/types/TypeInfo/IsObject.ts +27 -0
- package/src/types/TypeInfo/IsScalar.ts +14 -0
- package/src/types/TypeInfo/IsStringLiteral.ts +9 -0
- package/src/types/TypeInfo/IsUndefined.ts +14 -0
- package/src/types/TypeInfo/TypeDefault.ts +58 -0
- package/src/types/TypeInfo/index.ts +4 -0
- package/src/types/alphabetic/Cardinality.ts +80 -0
- package/src/types/alphabetic/index.ts +1 -0
- package/src/types/dictionary/MapTo.ts +332 -13
- package/src/types/dictionary/props.ts +11 -0
- package/src/types/index.ts +1 -0
- package/src/types/kv/DictFromKv.ts +1 -1
- package/src/types/literal-unions/OptRequired.ts +4 -0
- package/src/types/literal-unions/index.ts +1 -0
- package/src/types/string-literals/Replace.ts +8 -4
- package/src/types/type-testing.ts +2 -5
- package/src/utility/dictionary/kv/kvToDict.ts +1 -2
- package/src/utility/dictionary/mapTo.ts +160 -28
- package/src/utility/dictionary/merge.ts +36 -0
- package/src/utility/runtime/conditions/isObject.ts +2 -16
- package/tests/TypeInfo/IsLiteral.spec.ts +17 -1
- package/tests/createFnWithProps.spec.ts +1 -0
- package/tests/dictionary/IntersectingKeys.test.ts +42 -0
- package/tests/dictionary/TypeDefault.test.ts +76 -0
- package/tests/dictionary/mapTo.test.ts +327 -94
- package/tests/dictionary/merge.test.ts +41 -0
- package/tests/withValue.spec.ts +0 -1
|
@@ -1,23 +1,342 @@
|
|
|
1
|
+
/* eslint-disable no-use-before-define */
|
|
2
|
+
import { OptRequired } from "src/types/literal-unions";
|
|
3
|
+
import {
|
|
4
|
+
DefaultManyToOneMapping,
|
|
5
|
+
DefaultOneToManyMapping,
|
|
6
|
+
DefaultOneToOneMapping,
|
|
7
|
+
} from "src/utility/dictionary/mapTo";
|
|
8
|
+
import { EnumValues } from "../EnumValues";
|
|
9
|
+
import { TypeDefault } from "../TypeInfo/TypeDefault";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Expresses relationship between inputs/outputs:
|
|
13
|
+
*/
|
|
14
|
+
export enum MapCardinality {
|
|
15
|
+
/** every input results in 0:M outputs */
|
|
16
|
+
OneToMany = "I -> O[]",
|
|
17
|
+
/** every input results in 0:1 outputs */
|
|
18
|
+
OneToOne = "I -> O",
|
|
19
|
+
/** every input is an array of type I and reduced to a single O */
|
|
20
|
+
ManyToOne = "I[] -> O",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type MapCardinalityIllustrated = EnumValues<MapCardinality>;
|
|
24
|
+
|
|
1
25
|
/**
|
|
2
|
-
* **
|
|
26
|
+
* The _user_ configuration of a **mapTo** mapper function
|
|
27
|
+
* which will be finalized by merging it with the appropriate
|
|
28
|
+
* default mapping type.
|
|
29
|
+
*/
|
|
30
|
+
export interface MapConfig<
|
|
31
|
+
IR extends OptRequired | undefined = undefined,
|
|
32
|
+
D extends MapCardinalityIllustrated | undefined = undefined,
|
|
33
|
+
OR extends OptRequired | undefined = undefined
|
|
34
|
+
> {
|
|
35
|
+
input?: IR;
|
|
36
|
+
output?: OR;
|
|
37
|
+
cardinality?: D;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A finalized configuration of a **mapTo** mapper functions cardinality
|
|
42
|
+
* relationships between _inputs_ and _outputs_.
|
|
3
43
|
*
|
|
4
|
-
*
|
|
44
|
+
* Note: _this configuration does _not_ yet include the actual mapping
|
|
45
|
+
* configuration between the input and output._
|
|
46
|
+
*/
|
|
47
|
+
export type FinalizedMapConfig<
|
|
48
|
+
IR extends OptRequired,
|
|
49
|
+
D extends MapCardinalityIllustrated,
|
|
50
|
+
OR extends OptRequired
|
|
51
|
+
> = Required<MapConfig<IR, D, OR>> & { finalized: true };
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* User configuration exposed by a config function which specifies the
|
|
55
|
+
* cardinality already (e.g., `oneToMany()`, `manyToOne()`)
|
|
56
|
+
*/
|
|
57
|
+
type MapCardinalityConfig<
|
|
58
|
+
IR extends OptRequired | undefined,
|
|
59
|
+
OR extends OptRequired | undefined
|
|
60
|
+
> = {
|
|
61
|
+
input?: IR;
|
|
62
|
+
output?: OR;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* **ConfiguredMap**
|
|
5
67
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
68
|
+
* A partial application of the mapTo() utility which has expressed the
|
|
69
|
+
* configuration of the _inputs_ and _outputs_ and provides a `.map()`
|
|
70
|
+
* method which allows the user configure the specifics of the mapping.
|
|
71
|
+
*/
|
|
72
|
+
export type ConfiguredMap<
|
|
73
|
+
C extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>
|
|
74
|
+
> = {
|
|
75
|
+
map: <I, O>(map: MapTo<I, O, C>) => MapFn<I, O, C>;
|
|
76
|
+
input: MapIR<C>;
|
|
77
|
+
cardinality: MapCard<C>;
|
|
78
|
+
output: MapOR<C>;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extracts the IR, Cardinality, and OR generics from a FinalizedMapConfig
|
|
83
|
+
*/
|
|
84
|
+
export type DecomposeMapConfig<
|
|
85
|
+
M extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>
|
|
86
|
+
> = M extends MapConfig<infer IR, infer D, infer OR>
|
|
87
|
+
? IR extends OptRequired | undefined
|
|
88
|
+
? D extends MapCardinalityIllustrated | undefined
|
|
89
|
+
? OR extends OptRequired | undefined
|
|
90
|
+
? [IR, D, OR]
|
|
91
|
+
: never
|
|
92
|
+
: never
|
|
93
|
+
: never
|
|
94
|
+
: M extends FinalizedMapConfig<infer IR, infer D, infer OR>
|
|
95
|
+
? IR extends OptRequired
|
|
96
|
+
? D extends MapCardinalityIllustrated
|
|
97
|
+
? OR extends OptRequired
|
|
98
|
+
? [IR, D, OR]
|
|
99
|
+
: never
|
|
100
|
+
: never
|
|
101
|
+
: never
|
|
102
|
+
: never;
|
|
103
|
+
|
|
104
|
+
/** extracts IR from a `FinalizedMapConfig` */
|
|
105
|
+
export type MapIR<
|
|
106
|
+
T extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>
|
|
107
|
+
> = DecomposeMapConfig<T>[0];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* extracts the MapCardinality from a `FinalizedMapConfig`
|
|
9
111
|
*/
|
|
10
|
-
export type
|
|
112
|
+
export type MapCard<
|
|
113
|
+
T extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>
|
|
114
|
+
> = DecomposeMapConfig<T>[1];
|
|
115
|
+
|
|
116
|
+
/** extracts OR from a `FinalizedMapConfig` */
|
|
117
|
+
export type MapOR<
|
|
118
|
+
T extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>
|
|
119
|
+
> = DecomposeMapConfig<T>[2];
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Merges the types of a userland configuration with a default configuration
|
|
123
|
+
*/
|
|
124
|
+
export type AsFinalizedConfig<
|
|
125
|
+
U extends MapConfig<
|
|
126
|
+
OptRequired | undefined,
|
|
127
|
+
MapCardinalityIllustrated | undefined,
|
|
128
|
+
OptRequired | undefined
|
|
129
|
+
>,
|
|
130
|
+
D extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>
|
|
131
|
+
> = TypeDefault<U, D> extends FinalizedMapConfig<infer IR, infer C, infer OR>
|
|
132
|
+
? FinalizedMapConfig<IR, C, OR>
|
|
133
|
+
: never;
|
|
134
|
+
|
|
135
|
+
export type MapperApi = {
|
|
136
|
+
/**
|
|
137
|
+
* Provides opportunity to configure _input_, _output_, and _cardinality_
|
|
138
|
+
* prior to providing a mapping function.
|
|
139
|
+
*
|
|
140
|
+
* Note: _the defaults for configuration are defined by the_ `DEFAULT_ONE_TO_MANY_MAPPING`
|
|
141
|
+
* _constant made available as a symbol from this library._
|
|
142
|
+
*/
|
|
143
|
+
config: <
|
|
144
|
+
C extends MapConfig<
|
|
145
|
+
OptRequired, //
|
|
146
|
+
MapCardinalityIllustrated,
|
|
147
|
+
OptRequired
|
|
148
|
+
>
|
|
149
|
+
>(
|
|
150
|
+
config: C
|
|
151
|
+
) => ConfiguredMap<
|
|
152
|
+
AsFinalizedConfig<
|
|
153
|
+
C, //
|
|
154
|
+
DefaultOneToManyMapping
|
|
155
|
+
>
|
|
156
|
+
>;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Provides a nice 1:1 ratio between the input and output.
|
|
160
|
+
*
|
|
161
|
+
* By default the input and output are considered to be _required_
|
|
162
|
+
* properties but this can be changed with the options hash provided.
|
|
163
|
+
* ```ts
|
|
164
|
+
* const mapper = mapTo.oneToOne().map(...);
|
|
165
|
+
* // add in ability to filter out some inputs
|
|
166
|
+
* const mapAndFilter = mapTo.oneToOne({ output: "opt" })
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
oneToOne: <C extends MapCardinalityConfig<OptRequired, OptRequired>>(
|
|
170
|
+
config?: C
|
|
171
|
+
) => ConfiguredMap<
|
|
172
|
+
AsFinalizedConfig<
|
|
173
|
+
C, //
|
|
174
|
+
DefaultOneToOneMapping
|
|
175
|
+
>
|
|
176
|
+
>;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* **manyToOne** _mapping_
|
|
180
|
+
*
|
|
181
|
+
* Provides a configuration where multiple inputs `I[]` will be mapped to a
|
|
182
|
+
* single output `O`.
|
|
183
|
+
*
|
|
184
|
+
* Choosing this configuration will, by default, set both input and output
|
|
185
|
+
* to be "required" but you can change this default if you so choose.
|
|
186
|
+
*/
|
|
187
|
+
manyToOne: <
|
|
188
|
+
C extends MapCardinalityConfig<
|
|
189
|
+
OptRequired | undefined, //
|
|
190
|
+
OptRequired | undefined
|
|
191
|
+
>
|
|
192
|
+
>(
|
|
193
|
+
config?: C
|
|
194
|
+
) => ConfiguredMap<
|
|
195
|
+
AsFinalizedConfig<
|
|
196
|
+
C, //
|
|
197
|
+
DefaultManyToOneMapping
|
|
198
|
+
>
|
|
199
|
+
>;
|
|
200
|
+
|
|
201
|
+
oneToMany: <
|
|
202
|
+
C extends MapCardinalityConfig<
|
|
203
|
+
OptRequired | undefined, //
|
|
204
|
+
OptRequired | undefined
|
|
205
|
+
>
|
|
206
|
+
>(
|
|
207
|
+
config?: C
|
|
208
|
+
) => ConfiguredMap<
|
|
209
|
+
AsFinalizedConfig<
|
|
210
|
+
C, //
|
|
211
|
+
DefaultOneToManyMapping
|
|
212
|
+
>
|
|
213
|
+
>;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export type MapInput<
|
|
217
|
+
I, //
|
|
218
|
+
IR extends OptRequired,
|
|
219
|
+
C extends MapCardinalityIllustrated
|
|
220
|
+
> = C extends MapCardinality.OneToMany | "I -> O[]"
|
|
221
|
+
? IR extends "opt"
|
|
222
|
+
? I | undefined
|
|
223
|
+
: I
|
|
224
|
+
: C extends MapCardinality.OneToOne | "I -> O"
|
|
225
|
+
? IR extends "opt"
|
|
226
|
+
? I | undefined
|
|
227
|
+
: I
|
|
228
|
+
: C extends MapCardinality.ManyToOne | "I[] -> O"
|
|
229
|
+
? IR extends "opt"
|
|
230
|
+
? I[] | undefined
|
|
231
|
+
: I[]
|
|
232
|
+
: never;
|
|
233
|
+
|
|
234
|
+
export type MapOutput<
|
|
235
|
+
O, //
|
|
236
|
+
OR extends OptRequired,
|
|
237
|
+
C extends MapCardinalityIllustrated
|
|
238
|
+
> = C extends MapCardinality.OneToMany | "I -> O[]"
|
|
239
|
+
? OR extends "opt"
|
|
240
|
+
? O[]
|
|
241
|
+
: [O, ...O[]]
|
|
242
|
+
: C extends MapCardinality.OneToOne | "I -> O"
|
|
243
|
+
? OR extends "opt"
|
|
244
|
+
? O | null
|
|
245
|
+
: O
|
|
246
|
+
: C extends MapCardinality.ManyToOne | "I[] -> O"
|
|
247
|
+
? OR extends "opt"
|
|
248
|
+
? O | null
|
|
249
|
+
: O
|
|
250
|
+
: never;
|
|
11
251
|
|
|
12
252
|
/**
|
|
13
|
-
* **
|
|
253
|
+
* **MapTo<I, O>**
|
|
14
254
|
*
|
|
15
|
-
*
|
|
255
|
+
* A mapping function between an input type `I` and output type `O`.
|
|
16
256
|
*
|
|
17
|
-
* **Note:**
|
|
18
|
-
*
|
|
19
|
-
* out an input value entirely is possible. If you don't need _filtering_ then use the
|
|
20
|
-
* `MapTo` type instead.
|
|
257
|
+
* **Note:** this type is designed to guide the userland mapping; refer
|
|
258
|
+
* to `MapFn` if you want the type output by the `mapFn()` utility.
|
|
21
259
|
*/
|
|
260
|
+
export type MapTo<
|
|
261
|
+
I,
|
|
262
|
+
O,
|
|
263
|
+
C extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>
|
|
264
|
+
> = MapIR<C> extends "opt"
|
|
265
|
+
? (source?: MapInput<I, MapIR<C>, MapCard<C>>) => MapOutput<O, MapOR<C>, MapCard<C>>
|
|
266
|
+
: (source: MapInput<I, MapIR<C>, MapCard<C>>) => MapOutput<O, MapOR<C>, MapCard<C>>;
|
|
267
|
+
|
|
268
|
+
export type MapFnOutput<
|
|
269
|
+
I,
|
|
270
|
+
O,
|
|
271
|
+
S,
|
|
272
|
+
OR extends OptRequired,
|
|
273
|
+
C extends MapCardinalityIllustrated
|
|
274
|
+
> = C extends "I -> O[]" | MapCardinality.OneToMany
|
|
275
|
+
? S extends I[]
|
|
276
|
+
? OR extends "opt"
|
|
277
|
+
? O[]
|
|
278
|
+
: [O, ...O[]]
|
|
279
|
+
: OR extends "opt"
|
|
280
|
+
? O[] | null
|
|
281
|
+
: O[]
|
|
282
|
+
: C extends MapCardinality.OneToOne | "I -> O"
|
|
283
|
+
? S extends I[]
|
|
284
|
+
? OR extends "opt"
|
|
285
|
+
? O[]
|
|
286
|
+
: [O, ...O[]]
|
|
287
|
+
: OR extends "opt"
|
|
288
|
+
? O | null
|
|
289
|
+
: O
|
|
290
|
+
: C extends MapCardinality.ManyToOne | "I[] -> O"
|
|
291
|
+
? S extends I[][]
|
|
292
|
+
? OR extends "opt"
|
|
293
|
+
? O[]
|
|
294
|
+
: [O, ...O[]]
|
|
295
|
+
: OR extends "opt"
|
|
296
|
+
? O | null
|
|
297
|
+
: O
|
|
298
|
+
: never;
|
|
22
299
|
|
|
23
|
-
export type
|
|
300
|
+
export type MapFnInput<I, IR extends OptRequired, D extends MapCardinalityIllustrated> = D extends
|
|
301
|
+
| "I -> O[]"
|
|
302
|
+
| MapCardinality.OneToMany
|
|
303
|
+
? IR extends "opt"
|
|
304
|
+
? I | I[] | undefined
|
|
305
|
+
: I | I[]
|
|
306
|
+
: D extends MapCardinality.OneToOne | "I -> O"
|
|
307
|
+
? IR extends "opt"
|
|
308
|
+
? I | I[] | undefined
|
|
309
|
+
: I | I[]
|
|
310
|
+
: D extends MapCardinality.ManyToOne | "I[] -> O"
|
|
311
|
+
? IR extends "opt"
|
|
312
|
+
? I[] | I[][] | undefined
|
|
313
|
+
: I[] | I[][]
|
|
314
|
+
: never;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The mapping function provided by the `mapFn()` utility. This _fn_
|
|
318
|
+
* is intended to be used in two ways:
|
|
319
|
+
*
|
|
320
|
+
* 1. Iterative:
|
|
321
|
+
* ```ts
|
|
322
|
+
* const m = mapTo<I,O>(i => [ ... ]);
|
|
323
|
+
* // maps inputs to outputs
|
|
324
|
+
* const out = inputs.map(m);
|
|
325
|
+
* ```
|
|
326
|
+
* 2. Block:
|
|
327
|
+
* ```ts
|
|
328
|
+
* // maps inputs to outputs (filtering or splitting where appr.)
|
|
329
|
+
* const out2 = m(inputs);
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
export type MapFn<
|
|
333
|
+
I,
|
|
334
|
+
O,
|
|
335
|
+
C extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>
|
|
336
|
+
> = MapIR<C> extends "opt"
|
|
337
|
+
? <S extends MapFnInput<I, MapIR<C>, MapCard<C>>>(
|
|
338
|
+
source?: S
|
|
339
|
+
) => MapFnOutput<I, O, S, MapOR<C>, MapCard<C>>
|
|
340
|
+
: <S extends MapFnInput<I, MapIR<C>, MapCard<C>>>(
|
|
341
|
+
source: S
|
|
342
|
+
) => MapFnOutput<I, O, S, MapOR<C>, MapCard<C>>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Alpha } from "../alphabetic/alpha-characters";
|
|
2
|
+
import { Keys } from "../Keys";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Extracts the _required_ keys in the object's type. You also may
|
|
@@ -12,6 +13,16 @@ export type RequiredKeys<T extends object, V extends any = any> = {
|
|
|
12
13
|
: never;
|
|
13
14
|
}[keyof T];
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Extracts the intersecting/common keys to two objects
|
|
18
|
+
*/
|
|
19
|
+
export type IntersectingKeys<
|
|
20
|
+
T extends Record<string, any> | readonly string[],
|
|
21
|
+
U extends Record<string, any> | readonly string[]
|
|
22
|
+
> = {
|
|
23
|
+
[K in keyof T]: K extends Keys<U> ? K : never;
|
|
24
|
+
}[keyof T];
|
|
25
|
+
|
|
15
26
|
/**
|
|
16
27
|
* Extracts the _optional_ keys in the object's type. You also may
|
|
17
28
|
* optionally filter by the _value_ of the key.
|
package/src/types/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from "./fluent/index";
|
|
|
37
37
|
export * from "./functions/index";
|
|
38
38
|
export * from "./kv/index";
|
|
39
39
|
export * from "./lists/index";
|
|
40
|
+
export * from "./literal-unions/index";
|
|
40
41
|
export * from "./string-literals/index";
|
|
41
42
|
export * from "./tuples/index";
|
|
42
43
|
export * from "./type-conversion/index";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./OptRequired";
|
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
* // "Foo"
|
|
6
6
|
* type Foo = Replace<typeof fooy, "y", "">;
|
|
7
7
|
* ```
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* Note: _the first match is replaced and all subsequent matches are ignored_
|
|
10
10
|
*/
|
|
11
|
-
export type Replace<S extends string, W extends string, P extends string> =
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export type Replace<S extends string, W extends string, P extends string> = S extends ""
|
|
12
|
+
? ""
|
|
13
|
+
: W extends ""
|
|
14
|
+
? S
|
|
15
|
+
: S extends `${infer F}${W}${infer E}`
|
|
16
|
+
? `${F}${P}${E}`
|
|
17
|
+
: S;
|
|
@@ -8,10 +8,7 @@ export type ExpectExtends<VALUE, EXPECTED> = EXPECTED extends VALUE ? true : fal
|
|
|
8
8
|
*/
|
|
9
9
|
export type AssertExtends<VALUE, EXPECTED> = EXPECTED extends VALUE ? any : never;
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
export type IfExtends<VALUE, EXPECTED> = EXPECTED extends VALUE ? VALUE : never;
|
|
13
|
-
|
|
14
11
|
/**
|
|
15
|
-
* Give a type `TValue` and a comparison type `TExtends`
|
|
12
|
+
* Give a type `TValue` and a comparison type `TExtends`
|
|
16
13
|
*/
|
|
17
|
-
export type IfExtendsThen<VALUE, EXPECTED, THEN> = EXPECTED extends VALUE ? THEN : never;
|
|
14
|
+
export type IfExtendsThen<VALUE, EXPECTED, THEN> = EXPECTED extends VALUE ? THEN : never;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DictFromKv } from "src/types/kv";
|
|
2
|
-
import { Mutable } from "src/types/Mutable";
|
|
3
2
|
import { Narrowable } from "src/types/Narrowable";
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -21,5 +20,5 @@ export function kvToDict<
|
|
|
21
20
|
out[kv.key as keyof DictFromKv<T>] = kv.value;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
return out as DictFromKv<
|
|
23
|
+
return out as DictFromKv<T>;
|
|
25
24
|
}
|
|
@@ -1,37 +1,169 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
FinalizedMapConfig,
|
|
3
|
+
MapCardinalityIllustrated,
|
|
4
|
+
MapFnInput,
|
|
5
|
+
MapperApi,
|
|
6
|
+
MapTo,
|
|
7
|
+
AsFinalizedConfig,
|
|
8
|
+
MapConfig,
|
|
9
|
+
ConfiguredMap,
|
|
10
|
+
MapFnOutput,
|
|
11
|
+
MapIR,
|
|
12
|
+
MapCard,
|
|
13
|
+
MapOR,
|
|
14
|
+
} from "src/types/dictionary";
|
|
15
|
+
import { OptRequired } from "src/types/literal-unions";
|
|
16
|
+
import { createFnWithProps } from "../createFnWithProps";
|
|
2
17
|
|
|
3
18
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
* utility function to take a fully-qualified _user_ config
|
|
20
|
+
* and make it into a FinalizedMapConfig
|
|
21
|
+
*/
|
|
22
|
+
const toFinalizedConfig = <
|
|
23
|
+
IR extends OptRequired,
|
|
24
|
+
D extends MapCardinalityIllustrated,
|
|
25
|
+
OR extends OptRequired
|
|
26
|
+
>(
|
|
27
|
+
config: MapConfig<IR, D, OR>
|
|
28
|
+
) => {
|
|
29
|
+
return { ...config, finalized: true } as FinalizedMapConfig<IR, D, OR>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const DEFAULT_ONE_TO_MANY_MAPPING = toFinalizedConfig({
|
|
33
|
+
input: "req",
|
|
34
|
+
output: "opt",
|
|
35
|
+
cardinality: "I -> O[]",
|
|
36
|
+
});
|
|
37
|
+
export const DEFAULT_ONE_TO_ONE_MAPPING = toFinalizedConfig({
|
|
38
|
+
input: "req",
|
|
39
|
+
output: "req",
|
|
40
|
+
cardinality: "I -> O",
|
|
41
|
+
});
|
|
42
|
+
export const DEFAULT_MANY_TO_ONE_MAPPING = toFinalizedConfig({
|
|
43
|
+
input: "req",
|
|
44
|
+
output: "req",
|
|
45
|
+
cardinality: "I[] -> O",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export type DefaultOneToManyMapping = typeof DEFAULT_ONE_TO_MANY_MAPPING;
|
|
49
|
+
export type DefaultOneToOneMapping = typeof DEFAULT_ONE_TO_ONE_MAPPING;
|
|
50
|
+
export type DefaultManyToOneMapping = typeof DEFAULT_MANY_TO_ONE_MAPPING;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The single implementation for all mapping
|
|
54
|
+
*/
|
|
55
|
+
const mapper =
|
|
56
|
+
<C extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>>(config: C) =>
|
|
57
|
+
<I, O>(map: MapTo<I, O, C>) => {
|
|
58
|
+
return <S extends MapFnInput<I, MapIR<C>, MapCard<C>>>(
|
|
59
|
+
source?: S
|
|
60
|
+
): MapFnOutput<I, O, S, MapOR<C>, MapCard<C>> => {
|
|
61
|
+
/**
|
|
62
|
+
* Determine whether input is an array; this will be true
|
|
63
|
+
*/
|
|
64
|
+
const isArray =
|
|
65
|
+
config.cardinality === "I -> O[]" && Array.isArray(source)
|
|
66
|
+
? true
|
|
67
|
+
: config.cardinality === "I[] -> O" && Array.isArray(source) && Array.isArray(source[0])
|
|
68
|
+
? true
|
|
69
|
+
: false;
|
|
70
|
+
|
|
71
|
+
if (isArray) {
|
|
72
|
+
// iterate over inputs with flatMap to return mapped values as
|
|
73
|
+
// well as supporting filtering functionality. We could achieve
|
|
74
|
+
// the same results by simply passing in all inputs to our mapper
|
|
75
|
+
// in most cardinalities but when cardinality is M:1 we need to
|
|
76
|
+
// make sure that the first array of elements is passed as a single
|
|
77
|
+
// item and this approach achieves this.
|
|
78
|
+
// TODO: we should check that the approach below doesn't work for M:1 here
|
|
79
|
+
// as well
|
|
80
|
+
return (source as any).flatMap(map) as MapFnOutput<I, O, S, MapOR<C>, MapCard<C>>;
|
|
81
|
+
} else {
|
|
82
|
+
// receive _all_ inputs provided as pass into ; this is just a single input unless the
|
|
83
|
+
// cardinality is
|
|
84
|
+
return map(source as any) as MapFnOutput<I, O, S, MapOR<C>, MapCard<C>>;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Takes a userland configuration and a default configuration and then produces
|
|
91
|
+
* the appropriately typed `ConfiguredMap` API surface.
|
|
92
|
+
*/
|
|
93
|
+
const setMapper = <
|
|
94
|
+
U extends MapConfig<
|
|
95
|
+
OptRequired | undefined,
|
|
96
|
+
MapCardinalityIllustrated | undefined,
|
|
97
|
+
OptRequired | undefined
|
|
98
|
+
>,
|
|
99
|
+
D extends FinalizedMapConfig<OptRequired, MapCardinalityIllustrated, OptRequired>
|
|
100
|
+
>(
|
|
101
|
+
config: U = { input: undefined, output: undefined, cardinality: undefined } as U,
|
|
102
|
+
defaultValue: D
|
|
103
|
+
): ConfiguredMap<AsFinalizedConfig<U, D>> => ({
|
|
104
|
+
map: (source) => {
|
|
105
|
+
// merge userland config with defaults for a cardinality
|
|
106
|
+
const c = {
|
|
107
|
+
...defaultValue,
|
|
108
|
+
...config,
|
|
109
|
+
} as unknown as AsFinalizedConfig<U, D>;
|
|
110
|
+
return mapper(c)(source);
|
|
111
|
+
},
|
|
112
|
+
input: config?.input || defaultValue.input,
|
|
113
|
+
output: config?.output || defaultValue.output,
|
|
114
|
+
cardinality: config?.cardinality || defaultValue.cardinality,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* **mapTo** _utility_
|
|
12
119
|
*
|
|
13
|
-
* This
|
|
120
|
+
* This utility -- by default -- creates a strongly typed 1:M data mapper which maps from one
|
|
121
|
+
* known source `I` to any array of another `O[]`:
|
|
14
122
|
* ```ts
|
|
15
|
-
* const mapper = mapTo<I,O>(i =>
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* ]
|
|
19
|
-
* : null
|
|
20
|
-
* );
|
|
123
|
+
* const mapper = mapTo<I, O>(i => [{
|
|
124
|
+
* foo: i.bar
|
|
125
|
+
* }]);
|
|
21
126
|
* ```
|
|
127
|
+
*/
|
|
128
|
+
export const mapToFn: ConfiguredMap<DefaultOneToManyMapping>["map"] = (map) => {
|
|
129
|
+
return mapper(DEFAULT_ONE_TO_MANY_MAPPING)(map);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Provides a `config` method which allows the relationships between _inputs_
|
|
134
|
+
* and _outputs_ to be configured.
|
|
135
|
+
*/
|
|
136
|
+
export const mapToDict: MapperApi = {
|
|
137
|
+
config(config) {
|
|
138
|
+
const c = { ...DEFAULT_ONE_TO_MANY_MAPPING, ...config };
|
|
139
|
+
return setMapper(c, DEFAULT_ONE_TO_MANY_MAPPING);
|
|
140
|
+
},
|
|
141
|
+
oneToOne(config) {
|
|
142
|
+
const c = { ...DEFAULT_ONE_TO_ONE_MAPPING, ...config };
|
|
143
|
+
return setMapper(c, DEFAULT_ONE_TO_ONE_MAPPING);
|
|
144
|
+
},
|
|
145
|
+
manyToOne(config) {
|
|
146
|
+
const c = { ...DEFAULT_MANY_TO_ONE_MAPPING, ...config };
|
|
147
|
+
return setMapper(c, DEFAULT_MANY_TO_ONE_MAPPING);
|
|
148
|
+
},
|
|
149
|
+
oneToMany(config) {
|
|
150
|
+
const c = { ...DEFAULT_ONE_TO_MANY_MAPPING, ...config };
|
|
151
|
+
return setMapper(c, DEFAULT_ONE_TO_MANY_MAPPING);
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* **mapTo** _utility_
|
|
22
157
|
*
|
|
23
|
-
*
|
|
158
|
+
* This utility creates a strongly typed data mapper which maps from one
|
|
159
|
+
* known source `I` to another `O`.
|
|
160
|
+
*
|
|
161
|
+
* Signatures:
|
|
24
162
|
* ```ts
|
|
25
|
-
*
|
|
163
|
+
* const defMap = mapTo<I,O>( ... );
|
|
164
|
+
* const configured = mapTo.config({ output: "req" }).map<I,O>( ... );
|
|
165
|
+
* const one2one = mapTo.oneToOne().map<I,O>( ... );
|
|
166
|
+
* const many2one = mapTo.manyToOne().map<I,O>( ... );
|
|
26
167
|
* ```
|
|
27
168
|
*/
|
|
28
|
-
export const mapTo =
|
|
29
|
-
<I extends {}, O extends {}>(cb: MapToWithFiltering<I, O>) =>
|
|
30
|
-
(source: I | I[]) => {
|
|
31
|
-
if (Array.isArray(source)) {
|
|
32
|
-
return source.flatMap((i) => cb(i)).filter((i) => i !== null) as O[];
|
|
33
|
-
} else {
|
|
34
|
-
const result = cb(source);
|
|
35
|
-
return result ? result : ([] as O[]);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
169
|
+
export const mapTo = createFnWithProps(mapToFn, mapToDict);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Mutable, Narrowable, SimplifyObject, TypeDefault } from "src/types";
|
|
2
|
+
import { keys } from "../keys";
|
|
3
|
+
import { isObject } from "../runtime";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* **merge**(val, defaultVal)
|
|
7
|
+
*
|
|
8
|
+
* Merges two values together where the second property is considered the
|
|
9
|
+
* the "default value". This means that in any cases where the primary value
|
|
10
|
+
* os _undefined_ it will be replaced by the default value. The merging will
|
|
11
|
+
* also recurse into object types to do matching at a property-by-property
|
|
12
|
+
* level.
|
|
13
|
+
*
|
|
14
|
+
* As much as is possible, this utility will maintain strong and narrow types
|
|
15
|
+
* across the merge process.
|
|
16
|
+
*/
|
|
17
|
+
export const merge = <T extends Narrowable, D extends Narrowable>(
|
|
18
|
+
val: T,
|
|
19
|
+
defVal: D
|
|
20
|
+
): SimplifyObject<Mutable<TypeDefault<T, D>>> => {
|
|
21
|
+
const result =
|
|
22
|
+
val === undefined
|
|
23
|
+
? // value is undefined; use default value
|
|
24
|
+
defVal
|
|
25
|
+
: isObject(defVal) && isObject(val)
|
|
26
|
+
? // value and and default value are objects; recurse via default values
|
|
27
|
+
keys(defVal as Record<string, any>).reduce((acc, key) => {
|
|
28
|
+
const v = (val as Record<string, any>)[key];
|
|
29
|
+
const dv = (defVal as Record<string, any>)[key];
|
|
30
|
+
|
|
31
|
+
return { ...acc, [key]: merge(v, dv) };
|
|
32
|
+
}, {} as Record<string, any>)
|
|
33
|
+
: val;
|
|
34
|
+
|
|
35
|
+
return result as SimplifyObject<Mutable<TypeDefault<T, D>>>;
|
|
36
|
+
};
|