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.
Files changed (37) hide show
  1. package/.vscode/settings.json +5 -0
  2. package/dist/index.d.ts +441 -126
  3. package/dist/index.js +80 -6
  4. package/dist/index.mjs +74 -6
  5. package/package.json +1 -1
  6. package/src/types/Mutable.ts +3 -1
  7. package/src/types/TypeInfo/Extends.ts +14 -0
  8. package/src/types/TypeInfo/IsBooleanLiteral.ts +9 -0
  9. package/src/types/TypeInfo/IsLiteral.ts +36 -2
  10. package/src/types/TypeInfo/IsNumericLiteral.ts +9 -0
  11. package/src/types/TypeInfo/IsObject.ts +27 -0
  12. package/src/types/TypeInfo/IsScalar.ts +14 -0
  13. package/src/types/TypeInfo/IsStringLiteral.ts +9 -0
  14. package/src/types/TypeInfo/IsUndefined.ts +14 -0
  15. package/src/types/TypeInfo/TypeDefault.ts +58 -0
  16. package/src/types/TypeInfo/index.ts +4 -0
  17. package/src/types/alphabetic/Cardinality.ts +80 -0
  18. package/src/types/alphabetic/index.ts +1 -0
  19. package/src/types/dictionary/MapTo.ts +332 -13
  20. package/src/types/dictionary/props.ts +11 -0
  21. package/src/types/index.ts +1 -0
  22. package/src/types/kv/DictFromKv.ts +1 -1
  23. package/src/types/literal-unions/OptRequired.ts +4 -0
  24. package/src/types/literal-unions/index.ts +1 -0
  25. package/src/types/string-literals/Replace.ts +8 -4
  26. package/src/types/type-testing.ts +2 -5
  27. package/src/utility/dictionary/kv/kvToDict.ts +1 -2
  28. package/src/utility/dictionary/mapTo.ts +160 -28
  29. package/src/utility/dictionary/merge.ts +36 -0
  30. package/src/utility/runtime/conditions/isObject.ts +2 -16
  31. package/tests/TypeInfo/IsLiteral.spec.ts +17 -1
  32. package/tests/createFnWithProps.spec.ts +1 -0
  33. package/tests/dictionary/IntersectingKeys.test.ts +42 -0
  34. package/tests/dictionary/TypeDefault.test.ts +76 -0
  35. package/tests/dictionary/mapTo.test.ts +327 -94
  36. package/tests/dictionary/merge.test.ts +41 -0
  37. 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
- * **MapTo**
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
- * Maps from one type `I` to another `O[]`
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
- * **Note:** because the output is an array you can easily support `1:1` and `1:M` mappings
7
- * but not a filtering operation (e.g., `1:0`); if you need this then use `MapToWithFiltering`
8
- * instead.
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 MapTo<I extends {}, O extends {}> = (i: I) => O[];
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
- * **MapToWithFiltering**
253
+ * **MapTo<I, O>**
14
254
  *
15
- * Maps from one type `I` to another `(O | null)[]`
255
+ * A mapping function between an input type `I` and output type `O`.
16
256
  *
17
- * **Note:** because the output is an array you can easily support `1:1` and `1:M` mappings
18
- * and the allowance of the conversion to result in a `null` value also means that filter
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 MapToWithFiltering<I extends {}, O extends {}> = (i: I) => O[] | null;
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.
@@ -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";
@@ -1,3 +1,3 @@
1
- export type DictFromKv<T extends readonly Readonly<{ key: string; value: unknown }>[]> = {
1
+ export type DictFromKv<T extends readonly { key: string; value: unknown }[]> = {
2
2
  [R in T[number] as R["key"]]: R["value"];
3
3
  };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Expresses whether an option is "opt" (optional) or "req" (required)
3
+ */
4
+ export type OptRequired = "opt" | "req";
@@ -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
- S extends "" ? "" : W extends "" ? S :
13
- S extends `${infer F}${W}${infer E}` ? `${F}${P}${E}` : S;
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<Mutable<T>>;
23
+ return out as DictFromKv<T>;
25
24
  }
@@ -1,37 +1,169 @@
1
- import { MapToWithFiltering } from "src/types/dictionary";
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
- * **mapTo**
5
- *
6
- * A utility function which maps one dictionary structure `I` to another `O`; which allows
7
- * the transform to:
8
- *
9
- * - _split_ inputs into multiple outputs
10
- * - _map_ 1:1 between input and output
11
- * - _filter_ inputs which don't meet certain criteria (by returning `null`)
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 higher order function first asks for the mapping criteria:
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 => i.name
16
- * ? [
17
- * { name: i.name, a: "static text", b: i.products.length }
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
- * and now you'll have a _mapper_ variable will be assigned as a mapping fn:
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
- * function (source: I | I[]): O[]
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
+ };