inferred-types 0.27.0 → 0.28.2

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.
@@ -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
+ };
@@ -1,19 +1,5 @@
1
- import { FunctionType } from "src/types/FunctionType";
2
- import { Mutable } from "src/types/Mutable";
3
- import { Narrowable } from "src/types/Narrowable";
4
- import { Not } from "src/types/Not";
5
-
6
- export type IsObject<T> = Mutable<T> extends Record<string, any>
7
- ? // an object of some type
8
- T extends FunctionType
9
- ? // when a function with props is found, categorize as a function not object
10
- false
11
- : Mutable<T> extends any[]
12
- ? // Array's are objects too but in our narrower definition we're looking only
13
- // dictionary based arrays.
14
- false
15
- : true
16
- : false;
1
+ import { FunctionType, Narrowable, Not } from "src/types";
2
+ import { IsObject } from "src/types/TypeInfo";
17
3
 
18
4
  export type ObjectType = Not<Record<string, Narrowable>, FunctionType>;
19
5
 
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { Equal, Expect } from "@type-challenges/utils";
3
- import { IsBooleanLiteral, IsLiteral } from "src/types";
3
+ import { IsBooleanLiteral, IsLiteral, IsOptionalLiteral } from "src/types";
4
4
 
5
5
  describe("IsLiteral<T> type utility", () => {
6
6
  it("string values", () => {
@@ -46,4 +46,20 @@ describe("IsLiteral<T> type utility", () => {
46
46
  expect(typeof v).toBe("boolean");
47
47
  expect(typeof vl).toBe("boolean");
48
48
  });
49
+
50
+ it("union with undefined", () => {
51
+ const vb = true as true | undefined;
52
+ const vs = "foo" as "foo" | undefined;
53
+ const vn = 42 as 42 | undefined;
54
+
55
+ type cases = [
56
+ Expect<Equal<IsLiteral<Exclude<typeof vb, undefined>>, true>>, //
57
+ Expect<Equal<IsLiteral<Exclude<typeof vs, undefined>>, true>>, //
58
+ Expect<Equal<IsLiteral<Exclude<typeof vn, undefined>>, true>>, //
59
+ Expect<Equal<IsOptionalLiteral<typeof vb>, true>>, //
60
+ Expect<Equal<IsOptionalLiteral<typeof vs>, true>>, //
61
+ Expect<Equal<IsOptionalLiteral<typeof vn>, true>> //
62
+ ];
63
+ const cases: cases = [true, true, true, true, true, true];
64
+ });
49
65
  });
@@ -0,0 +1,42 @@
1
+ import { describe, it } from "vitest";
2
+
3
+ import type { Expect, Equal } from "@type-challenges/utils";
4
+ import { IntersectingKeys } from "src/types/dictionary";
5
+
6
+ describe("IntersectingKeys<T,U>", () => {
7
+ it("with overlap", () => {
8
+ type T = { foo: string; bar: number; baz: any };
9
+ type U = { foo: string; bar: number };
10
+ type V = IntersectingKeys<T, U>;
11
+
12
+ type cases = [Expect<Equal<V, "foo" | "bar">>];
13
+ const cases: cases = [true];
14
+ });
15
+
16
+ it("no overlap", () => {
17
+ type T = { foo1: string; bar1: number; baz1: any };
18
+ type U = { foo: string; bar: number };
19
+ type V = IntersectingKeys<T, U>;
20
+ type VU = Exclude<U, V>;
21
+
22
+ type cases = [
23
+ // intersection is "never"
24
+ Expect<Equal<VU, U>>,
25
+ // Excluding "never" results in no change
26
+ Expect<Equal<VU, U>>
27
+ ];
28
+ const cases: cases = [true, true];
29
+ });
30
+
31
+ it("using string[] instead of object", () => {
32
+ type T = { foo: string; bar: number; baz: any };
33
+ type U = readonly ["foo", "bart"];
34
+ type V = IntersectingKeys<T, U>;
35
+
36
+ type cases = [
37
+ //
38
+ Expect<Equal<V, "foo">>
39
+ ];
40
+ const cases: cases = [true];
41
+ });
42
+ });
@@ -0,0 +1,76 @@
1
+ import { describe, it } from "vitest";
2
+ import { Equal, Expect } from "@type-challenges/utils";
3
+ import { TypeDefault } from "src/types/TypeInfo/TypeDefault";
4
+ import { literal } from "src/utility/literals";
5
+
6
+ describe("TypeDefault<T,D>", () => {
7
+ it("T/D as dictionary", () => {
8
+ type T = { foo?: "foo" | "fooy" | undefined; bar?: 42 | 53 | undefined };
9
+ type D = { foo: "foo"; bar: 42 };
10
+ type V = TypeDefault<T, D>;
11
+ const t1 = literal({ foo: undefined, bar: 56 });
12
+ type V1 = TypeDefault<typeof t1, D>;
13
+ const t2 = { foo: undefined, bar: 56 };
14
+ type V2 = TypeDefault<typeof t2, D>;
15
+ // const t3 = { bar: 56, baz: "hello" } as const;
16
+ // type _V3 = TypeDefault<typeof t3, D>;
17
+
18
+ type cases = [
19
+ // D's props all extend T
20
+ Expect<Equal<V, D>>,
21
+ // foo is inherited from default, bar keeps literal type from T
22
+ Expect<Equal<V1, { foo: "foo"; bar: 56 }>>,
23
+ // bar is widened to "number" because that's the resolution T had
24
+ Expect<Equal<V2, { foo: "foo"; bar: number }>>
25
+ // the T prop has the prop "baz" which did not exist on D
26
+ // TODO: this last test SHOULD work but doesn't yet
27
+ // Expect<Equal<V3, { foo: "foo"; bar: 56; baz: "hello" }>>
28
+ ];
29
+ const cases: cases = [true, true, true];
30
+ });
31
+ it("T is undefined, D is a dictionary", () => {
32
+ type T = undefined;
33
+ type D = { foo: "foo"; bar: 42 };
34
+ type V = TypeDefault<T, D>;
35
+
36
+ type cases = [
37
+ // the value's type is converted to the default
38
+ Expect<Equal<V, D>>
39
+ ];
40
+ const cases: cases = [true];
41
+ });
42
+
43
+ it("T and D are scalars", () => {
44
+ type T1 = 5;
45
+ type D1 = 8;
46
+ type V1 = TypeDefault<T1, D1>;
47
+
48
+ type T2 = "foo";
49
+ type D2 = "bar";
50
+ type V2 = TypeDefault<T2, D2>;
51
+
52
+ type T3 = undefined;
53
+ type D3 = "bar";
54
+ type V3 = TypeDefault<T3, D3>;
55
+
56
+ type cases = [
57
+ Expect<Equal<V1, 5>>, //
58
+ Expect<Equal<V2, "foo">>,
59
+ Expect<Equal<V3, "bar">>
60
+ ];
61
+
62
+ const cases: cases = [true, true, true];
63
+ });
64
+
65
+ it("Property includes a function", () => {
66
+ type T1 = { foo: "foo"; bar: undefined };
67
+ type D1 = { foo: "bar"; bar: () => "hello" };
68
+ type V1 = TypeDefault<T1, D1>;
69
+
70
+ type cases = [
71
+ Expect<Equal<V1["foo"], "foo">>, //
72
+ Expect<Equal<V1["bar"], () => "hello">>
73
+ ];
74
+ const cases: cases = [true, true];
75
+ });
76
+ });
@@ -1,8 +1,19 @@
1
- import { MapDirection, MapFn, MapTo } from "src/types/dictionary";
1
+ import {
2
+ FinalizedMapConfig,
3
+ MapCardinality,
4
+ MapFn,
5
+ MapTo,
6
+ AsFinalizedConfig,
7
+ MapConfig,
8
+ ConfiguredMap,
9
+ } from "src/types/dictionary";
2
10
  import { describe, expect, it } from "vitest";
3
-
4
11
  import type { Expect, Equal } from "@type-challenges/utils";
5
- import { mapTo } from "src/utility/dictionary/mapTo";
12
+ import {
13
+ DEFAULT_MANY_TO_ONE_MAPPING,
14
+ DEFAULT_ONE_TO_MANY_MAPPING,
15
+ mapTo,
16
+ } from "src/utility/dictionary/mapTo";
6
17
 
7
18
  type I = { title: string; color: string; products: string[] };
8
19
  const i: I = { title: "Test", color: "green", products: ["foo", "bar", "baz"] };
@@ -12,30 +23,29 @@ type O = { title: string; count: number; unnecessary?: number };
12
23
 
13
24
  describe("MapTo<I,O> and MapToFn<I,O>", () => {
14
25
  it("MapTo with 0:M, 0:M cardinality", () => {
15
- type Fn = MapTo<I, O>;
16
- type Fn2 = MapTo<I, O, "req", MapDirection.OneToMany, "opt">;
17
- type WFn = MapFn<I, O>;
18
- type WFn2 = MapFn<I, O, "req", MapDirection.OneToMany, "opt">;
26
+ type C = FinalizedMapConfig<"req", "I -> O[]", "opt">;
27
+ type Fn = MapTo<I, O, C>;
28
+ type WFn = MapFn<I, O, C>;
19
29
 
20
30
  /** userland mapping function */
21
31
  type T = (source: I) => O[];
22
32
  /** exposed via mapTo() util */
23
33
  type WT = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
24
34
 
25
- type cases = [
26
- Expect<Equal<Fn, Fn2>>, // implicit default = explicit values
27
- Expect<Equal<WFn, WFn2>>, // implicit default = explicit values
28
- Expect<Equal<Fn, T>>,
29
- Expect<Equal<WFn, WT>>
30
- ];
31
- const cases: cases = [true, true, true, true];
35
+ type cases = [Expect<Equal<Fn, T>>, Expect<Equal<WFn, WT>>];
36
+ const cases: cases = [true, true];
32
37
  });
33
38
 
34
39
  it("MapTo<X> with I -> O[] variable config", () => {
35
- type Fn0 = MapTo<I, O, "req", MapDirection.OneToMany, "opt">;
36
- type Fn1 = MapTo<I, O, "req", MapDirection.OneToMany, "req">;
37
- type Fn2 = MapTo<I, O, "opt", MapDirection.OneToMany, "opt">;
38
- type Fn3 = MapTo<I, O, "opt", "I -> O[]", "req">;
40
+ type C0 = FinalizedMapConfig<"req", "I -> O[]", "opt">;
41
+ type C1 = FinalizedMapConfig<"req", "I -> O[]", "req">;
42
+ type C2 = FinalizedMapConfig<"opt", "I -> O[]", "opt">;
43
+ type C3 = FinalizedMapConfig<"opt", "I -> O[]", "req">;
44
+
45
+ type Fn0 = MapTo<I, O, C0>;
46
+ type Fn1 = MapTo<I, O, C1>;
47
+ type Fn2 = MapTo<I, O, C2>;
48
+ type Fn3 = MapTo<I, O, C3>;
39
49
 
40
50
  type T0 = (source: I) => O[];
41
51
  type T1 = (source: I) => [O, ...O[]];
@@ -52,10 +62,15 @@ describe("MapTo<I,O> and MapToFn<I,O>", () => {
52
62
  });
53
63
 
54
64
  it("MapTo<X> with I[] -> O variable config", () => {
55
- type Fn0 = MapTo<I, O, "req", "I[] -> O", "opt">;
56
- type Fn1 = MapTo<I, O, "req", "I[] -> O", "req">;
57
- type Fn2 = MapTo<I, O, "opt", "I[] -> O", "opt">;
58
- type Fn3 = MapTo<I, O, "opt", "I[] -> O", "req">;
65
+ type C0 = FinalizedMapConfig<"req", "I[] -> O", "opt">;
66
+ type C1 = FinalizedMapConfig<"req", "I[] -> O", "req">;
67
+ type C2 = FinalizedMapConfig<"opt", "I[] -> O", "opt">;
68
+ type C3 = FinalizedMapConfig<"opt", "I[] -> O", "req">;
69
+
70
+ type Fn0 = MapTo<I, O, C0>;
71
+ type Fn1 = MapTo<I, O, C1>;
72
+ type Fn2 = MapTo<I, O, C2>;
73
+ type Fn3 = MapTo<I, O, C3>;
59
74
 
60
75
  type T0 = (source: I[]) => O | null;
61
76
  type T1 = (source: I[]) => O;
@@ -72,10 +87,15 @@ describe("MapTo<I,O> and MapToFn<I,O>", () => {
72
87
  });
73
88
 
74
89
  it("MapFn<X> with I -> O[] variable config", () => {
75
- type Fn0 = MapFn<I, O, "req", "I -> O[]", "opt">;
76
- type Fn1 = MapFn<I, O, "req", "I -> O[]", "req">;
77
- type Fn2 = MapFn<I, O, "opt", "I -> O[]", "opt">;
78
- type Fn3 = MapFn<I, O, "opt", "I -> O[]", "req">;
90
+ type C0 = FinalizedMapConfig<"req", "I -> O[]", "opt">;
91
+ type C1 = FinalizedMapConfig<"req", "I -> O[]", "req">;
92
+ type C2 = FinalizedMapConfig<"opt", "I -> O[]", "opt">;
93
+ type C3 = FinalizedMapConfig<"opt", "I -> O[]", "req">;
94
+
95
+ type Fn0 = MapFn<I, O, C0>;
96
+ type Fn1 = MapFn<I, O, C1>;
97
+ type Fn2 = MapFn<I, O, C2>;
98
+ type Fn3 = MapFn<I, O, C3>;
79
99
 
80
100
  type T0 = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
81
101
  type T1 = <S extends I | I[]>(source: S) => S extends I[] ? [O, ...O[]] : O[];
@@ -92,10 +112,15 @@ describe("MapTo<I,O> and MapToFn<I,O>", () => {
92
112
  });
93
113
 
94
114
  it("MapFn<X> with I[] -> O variable config", () => {
95
- type Fn0 = MapFn<I, O, "req", "I[] -> O", "opt">;
96
- type Fn1 = MapFn<I, O, "req", "I[] -> O", "req">;
97
- type Fn2 = MapFn<I, O, "opt", "I[] -> O", "opt">;
98
- type Fn3 = MapFn<I, O, "opt", "I[] -> O", "req">;
115
+ type C0 = FinalizedMapConfig<"req", "I[] -> O", "opt">;
116
+ type C1 = FinalizedMapConfig<"req", "I[] -> O", "req">;
117
+ type C2 = FinalizedMapConfig<"opt", "I[] -> O", "opt">;
118
+ type C3 = FinalizedMapConfig<"opt", "I[] -> O", "req">;
119
+
120
+ type Fn0 = MapFn<I, O, C0>;
121
+ type Fn1 = MapFn<I, O, C1>;
122
+ type Fn2 = MapFn<I, O, C2>;
123
+ type Fn3 = MapFn<I, O, C3>;
99
124
 
100
125
  type T0 = <S extends I[] | I[][]>(source: S) => S extends I[][] ? O[] : O | null;
101
126
  type T1 = <S extends I[] | I[][]>(source: S) => S extends I[][] ? [O, ...O[]] : O;
@@ -111,28 +136,143 @@ describe("MapTo<I,O> and MapToFn<I,O>", () => {
111
136
  const cases: cases = [true, true, true, true];
112
137
  });
113
138
 
114
- it("walk through of mapTo types", () => {
115
- const t1 = mapTo<I, O>((i) => [{ title: i.title, count: i.products.length }]);
116
- type T1 = typeof t1;
139
+ it("ToConfiguredMap<U,D> type utility", () => {
140
+ // userland overrides cardinality
141
+ type U1 = MapConfig<undefined, "I -> O", undefined>;
142
+ type D1 = typeof DEFAULT_ONE_TO_MANY_MAPPING;
143
+ type CD1 = AsFinalizedConfig<U1, D1>;
144
+ type CM1 = ConfiguredMap<CD1>;
145
+ type M1 = CM1["map"];
146
+ type M1P = Parameters<M1>;
147
+ type M1R = ReturnType<M1>;
117
148
 
118
- const a1 = mapTo
119
- .config({ input: "req" })
120
- .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
121
- type A1 = typeof a1;
149
+ // userland provides nothing; leaves default value
150
+ type U2 = MapConfig<undefined, undefined, undefined>;
151
+ type D2 = typeof DEFAULT_MANY_TO_ONE_MAPPING;
152
+ type C2 = AsFinalizedConfig<U2, D2>;
153
+ type CM2 = ConfiguredMap<C2>;
154
+ type M2 = CM2["map"];
155
+ type M2P = Parameters<M2>;
156
+ type M2R = ReturnType<M2>;
157
+
158
+ type U3 = MapConfig<undefined, undefined, "opt">;
159
+ type D3 = typeof DEFAULT_MANY_TO_ONE_MAPPING;
160
+ type C3 = AsFinalizedConfig<U3, D3>;
161
+ type CM3 = ConfiguredMap<C3>;
162
+ type M3 = CM3["map"];
163
+ type M3P = Parameters<M3>;
164
+ type M3R = ReturnType<M3>;
122
165
 
123
166
  type cases = [
124
- Expect<Equal<T1, MapFn<I, O>>>, //
125
- Expect<Equal<T1, A1>>
167
+ // merged configuration
168
+ Expect<Equal<CD1, FinalizedMapConfig<"req", "I -> O", "opt">>>,
169
+ // map() function's parameters (unknown is I)
170
+ Expect<Equal<[map: (source: unknown) => unknown], M1P>>,
171
+ // map() function's return value
172
+ Expect<
173
+ Equal<<S extends unknown>(source: S) => S extends unknown[] ? unknown[] : unknown, M1R>
174
+ >,
175
+
176
+ // merged configuration is same as default for M:1
177
+ Expect<Equal<C2, FinalizedMapConfig<"req", "I[] -> O", "req">>>,
178
+ // map() function's parameters (unknown is I)
179
+ Expect<Equal<[map: (source: unknown[]) => unknown], M2P>>,
180
+ // map() function's return value
181
+ Expect<
182
+ Equal<
183
+ <S extends unknown[] | unknown[][]>(
184
+ source: S
185
+ ) => S extends unknown[][] ? [unknown, ...unknown[]] : unknown,
186
+ M2R
187
+ >
188
+ >,
189
+
190
+ // merged configuration should be M:1 default with an "opt" for output
191
+ Expect<Equal<C3, FinalizedMapConfig<"req", "I[] -> O", "opt">>>,
192
+ // map() function's parameters (unknown is I)
193
+ Expect<Equal<[map: (source: unknown[]) => unknown | null], M3P>>,
194
+ // map() function's return value
195
+ Expect<
196
+ Equal<
197
+ <S extends unknown[] | unknown[][]>(
198
+ source: S
199
+ ) => S extends unknown[][] ? unknown[] : unknown,
200
+ M3R
201
+ >
202
+ >
203
+ ];
204
+
205
+ const cases: cases = [true, true, true, true, true, true, true, true, true];
206
+ });
207
+
208
+ it("config() matches cardinality configs", () => {
209
+ const a1 = mapTo.oneToMany();
210
+ const b1 = mapTo.config({
211
+ input: "req",
212
+ output: "opt",
213
+ cardinality: "I -> O[]",
214
+ });
215
+
216
+ expect(a1.input).toBe(b1.input);
217
+ expect(a1.output).toBe(b1.output);
218
+ expect(a1.cardinality).toBe(b1.cardinality);
219
+
220
+ const a2 = mapTo.oneToOne();
221
+ const b2 = mapTo.config({
222
+ input: "req",
223
+ output: "req",
224
+ cardinality: "I -> O",
225
+ });
226
+
227
+ expect(a2.input).toBe(b2.input);
228
+ expect(a2.output).toBe(b2.output);
229
+ expect(a2.cardinality).toBe(b2.cardinality);
230
+
231
+ type cases = [
232
+ Expect<Equal<typeof a1, typeof a2>>, //
233
+ Expect<Equal<typeof b1, typeof b2>>
126
234
  ];
127
235
  const cases: cases = [true, true];
128
236
  });
129
237
  });
130
238
 
131
239
  describe("mapTo() utility function", () => {
240
+ it("Partial application of mapTo utility", () => {
241
+ const m1 = mapTo.manyToOne();
242
+
243
+ expect(m1.input).toBe("req");
244
+ expect(m1.output).toBe("req");
245
+ expect(m1.cardinality).toBe(MapCardinality.ManyToOne);
246
+
247
+ const m2 = mapTo.manyToOne({ output: "opt" });
248
+
249
+ expect(m2.input).toBe("req");
250
+ expect(m2.output).toBe("opt");
251
+ expect(m2.cardinality).toBe(MapCardinality.ManyToOne);
252
+
253
+ const o1 = mapTo.oneToOne();
254
+
255
+ expect(o1.input).toBe("req");
256
+ expect(o1.output).toBe("req");
257
+ expect(o1.cardinality).toBe(MapCardinality.OneToOne);
258
+
259
+ const o2 = mapTo.oneToOne({ output: "opt" });
260
+
261
+ expect(o2.input).toBe("req");
262
+ expect(o2.output).toBe("opt");
263
+ expect(o2.cardinality).toBe(MapCardinality.OneToOne);
264
+
265
+ const c1 = mapTo.config({ input: "opt" });
266
+
267
+ expect(c1.input).toBe("opt");
268
+ expect(c1.output).toBe("opt");
269
+ expect(c1.cardinality).toBe(MapCardinality.OneToMany);
270
+ });
271
+
132
272
  it("M:1 conversion", () => {
133
273
  const m = mapTo
134
274
  .config({
135
- direction: "I[] -> O",
275
+ cardinality: "I[] -> O",
136
276
  output: "req",
137
277
  })
138
278
  .map<I, O>((i) => ({
@@ -146,9 +286,10 @@ describe("mapTo() utility function", () => {
146
286
  });
147
287
 
148
288
  it("1:1 conversion", () => {
149
- const m = mapTo
150
- .config({ output: "req", direction: MapDirection.OneToOne })
151
- .map<I, O>((i) => ({ title: i.title, count: i.products.length }));
289
+ const m = mapTo.oneToOne().map<I, O>((i) => ({
290
+ title: i.title,
291
+ count: i.products.length,
292
+ }));
152
293
  const o = m(i);
153
294
 
154
295
  expect(o?.title).toBe(i.title);
@@ -156,9 +297,11 @@ describe("mapTo() utility function", () => {
156
297
  });
157
298
 
158
299
  it("1:1 with 1:M conversion setting", () => {
159
- const m = mapTo
160
- .config({ output: "req", direction: MapDirection.OneToMany })
161
- .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
300
+ const mc = mapTo.config({
301
+ output: "req",
302
+ cardinality: "I -> O[]",
303
+ });
304
+ const m = mc.map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
162
305
  const o = m(i);
163
306
 
164
307
  expect(o.length).toBe(1);
@@ -168,7 +311,7 @@ describe("mapTo() utility function", () => {
168
311
 
169
312
  it("1:M conversion ", () => {
170
313
  const m = mapTo
171
- .config({ output: "req", direction: MapDirection.OneToMany })
314
+ .config({ output: "req", cardinality: "I -> O[]" })
172
315
  .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
173
316
  const o = m([i, i2]);
174
317
 
@@ -177,7 +320,7 @@ describe("mapTo() utility function", () => {
177
320
  });
178
321
 
179
322
  it("M:1 conversion", () => {
180
- const m = mapTo.config({ output: "req", direction: MapDirection.ManyToOne }).map<I, O>((i) => {
323
+ const m = mapTo.config({ output: "req", cardinality: "I[] -> O" }).map<I, O>((i) => {
181
324
  return {
182
325
  title: "summary",
183
326
  count: i.reduce((acc, i) => (acc = acc + i.products.length), 0),
@@ -188,4 +331,27 @@ describe("mapTo() utility function", () => {
188
331
  expect(o.title).toBe("summary");
189
332
  expect(o.count).toBe(6);
190
333
  });
334
+
335
+ it("1:1 conversion using oneToOne() configurator", () => {
336
+ const m = mapTo.oneToOne().map<I, O>((i) => ({ title: i.title, count: i.products.length }));
337
+ const o = m(i);
338
+
339
+ expect(o.title).toBe(i.title);
340
+ expect(o.count).toBe(i.products.length);
341
+ });
342
+
343
+ it("M:1 conversion using manyToOne() configurator", () => {
344
+ const m = mapTo //
345
+ .manyToOne()
346
+ .map<I, O>((i) => {
347
+ return {
348
+ title: "summary",
349
+ count: i.reduce((acc, cur) => (acc = acc + cur.products.length), 0),
350
+ };
351
+ });
352
+ const o = m([i, i2]);
353
+
354
+ expect(o.title).toBe("summary");
355
+ expect(o.count).toBe(6);
356
+ });
191
357
  });
@@ -0,0 +1,41 @@
1
+ import { Equal, Expect } from "@type-challenges/utils";
2
+ import { merge } from "src/utility/dictionary/merge";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ describe("merge() utility", () => {
6
+ it("merge to scalars/undefined", () => {
7
+ const m1 = merge(undefined, 6);
8
+ const m2 = merge(42, 6);
9
+
10
+ expect(m1).toBe(6);
11
+ expect(m2).toBe(42);
12
+
13
+ type cases = [
14
+ Expect<Equal<typeof m1, 6>>, //
15
+ Expect<Equal<typeof m2, 42>>
16
+ ];
17
+ const cases: cases = [true, true];
18
+ });
19
+
20
+ it("merge two objects", () => {
21
+ const o1 = merge(
22
+ { foo: "foo", baz: false, color: { fav: undefined, next: "green" } } as const,
23
+ { foo: "bar", bar: 42, color: { fav: "red", next: "" as string } } as const
24
+ );
25
+ type O1 = typeof o1;
26
+
27
+ // runtime
28
+ expect(o1.foo).toBe("foo");
29
+ expect(o1.bar).toBe(42);
30
+ expect(o1.color.fav).toBe("red");
31
+ expect(o1.color.next).toBe("green");
32
+
33
+ // design time
34
+ type cases = [
35
+ Expect<Equal<O1["foo"], "foo">>, //
36
+ Expect<Equal<O1["bar"], 42>>,
37
+ Expect<Equal<O1["color"], { fav: "red"; next: "green" }>>
38
+ ];
39
+ const cases: cases = [true, true, true];
40
+ });
41
+ });
@@ -1,6 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import type { Expect, Equal } from "@type-challenges/utils";
3
-
4
3
  import { createFnWithProps, type, withValue } from "../src/utility";
5
4
  import { FunctionType, WithValue } from "../src";
6
5