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,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
  });
@@ -5,6 +5,7 @@ describe("createFnWithProps()", () => {
5
5
  it("simple fn and prop are combined, type is retained", () => {
6
6
  const fn = () => "hi";
7
7
  const props = { foo: "bar" };
8
+ /** combo */
8
9
  const combo = createFnWithProps(fn, props);
9
10
 
10
11
  expect(combo.foo).toBe("bar");
@@ -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 { 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"] };
@@ -10,115 +21,337 @@ const i2: I = { title: "i2", color: "green", products: ["foo", "bar", "baz"] };
10
21
 
11
22
  type O = { title: string; count: number; unnecessary?: number };
12
23
 
13
- describe("MapTo<T> type util", () => {
14
- it("1:1 conversion", () => {
15
- const m: MapTo<I, O> = (i) => [
16
- {
17
- title: i.title,
18
- count: i.products.length,
19
- },
24
+ describe("MapTo<I,O> and MapToFn<I,O>", () => {
25
+ it("MapTo with 0:M, 0:M cardinality", () => {
26
+ type C = FinalizedMapConfig<"req", "I -> O[]", "opt">;
27
+ type Fn = MapTo<I, O, C>;
28
+ type WFn = MapFn<I, O, C>;
29
+
30
+ /** userland mapping function */
31
+ type T = (source: I) => O[];
32
+ /** exposed via mapTo() util */
33
+ type WT = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
34
+
35
+ type cases = [Expect<Equal<Fn, T>>, Expect<Equal<WFn, WT>>];
36
+ const cases: cases = [true, true];
37
+ });
38
+
39
+ it("MapTo<X> with I -> O[] variable config", () => {
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>;
49
+
50
+ type T0 = (source: I) => O[];
51
+ type T1 = (source: I) => [O, ...O[]];
52
+ type T2 = (source?: I) => O[];
53
+ type T3 = (source?: I) => [O, ...O[]];
54
+
55
+ type cases = [
56
+ Expect<Equal<Fn0, T0>>, //
57
+ Expect<Equal<Fn1, T1>>, //
58
+ Expect<Equal<Fn2, T2>>, //
59
+ Expect<Equal<Fn3, T3>> //
60
+ ];
61
+ const cases: cases = [true, true, true, true];
62
+ });
63
+
64
+ it("MapTo<X> with I[] -> O variable config", () => {
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>;
74
+
75
+ type T0 = (source: I[]) => O | null;
76
+ type T1 = (source: I[]) => O;
77
+ type T2 = (source?: I[]) => O | null;
78
+ type T3 = (source?: I[]) => O;
79
+
80
+ type cases = [
81
+ Expect<Equal<Fn0, T0>>, //
82
+ Expect<Equal<Fn1, T1>>, //
83
+ Expect<Equal<Fn2, T2>>, //
84
+ Expect<Equal<Fn3, T3>> //
85
+ ];
86
+ const cases: cases = [true, true, true, true];
87
+ });
88
+
89
+ it("MapFn<X> with I -> O[] variable config", () => {
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>;
99
+
100
+ type T0 = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
101
+ type T1 = <S extends I | I[]>(source: S) => S extends I[] ? [O, ...O[]] : O[];
102
+ type T2 = <S extends I | I[] | undefined>(source?: S) => S extends I[] ? O[] : O[] | null;
103
+ type T3 = <S extends I | I[] | undefined>(source?: S) => S extends I[] ? [O, ...O[]] : O[];
104
+
105
+ type cases = [
106
+ Expect<Equal<Fn0, T0>>, //
107
+ Expect<Equal<Fn1, T1>>, //
108
+ Expect<Equal<Fn2, T2>>, //
109
+ Expect<Equal<Fn3, T3>> //
20
110
  ];
21
- type M = typeof m;
22
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
23
- const cases: cases = [true];
24
- expect(cases).toBe(cases);
111
+ const cases: cases = [true, true, true, true];
25
112
  });
26
113
 
27
- it("1:1 conversion with non-required", () => {
28
- const m: MapTo<I, O> = (i) => [
29
- {
30
- title: i.title,
31
- count: i.products.length,
32
- unnecessary: 42,
33
- },
114
+ it("MapFn<X> with I[] -> O variable config", () => {
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>;
124
+
125
+ type T0 = <S extends I[] | I[][]>(source: S) => S extends I[][] ? O[] : O | null;
126
+ type T1 = <S extends I[] | I[][]>(source: S) => S extends I[][] ? [O, ...O[]] : O;
127
+ type T2 = <S extends I[] | I[][] | undefined>(source?: S) => S extends I[][] ? O[] : O | null;
128
+ type T3 = <S extends I[] | I[][] | undefined>(source?: S) => S extends I[][] ? [O, ...O[]] : O;
129
+
130
+ type cases = [
131
+ Expect<Equal<Fn0, T0>>, //
132
+ Expect<Equal<Fn1, T1>>, //
133
+ Expect<Equal<Fn2, T2>>, //
134
+ Expect<Equal<Fn3, T3>> //
34
135
  ];
35
- type M = typeof m;
36
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
37
- const cases: cases = [true];
38
- expect(cases).toBe(cases);
136
+ const cases: cases = [true, true, true, true];
39
137
  });
40
138
 
41
- it("1:M conversion", () => {
42
- const m: MapTo<I, O> = (i) => [
43
- {
44
- title: i.title,
45
- count: i.products.length,
46
- unnecessary: 1,
47
- },
48
- {
49
- title: i.title,
50
- count: i.products.length + 1,
51
- unnecessary: 2,
52
- },
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>;
148
+
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>;
165
+
166
+ type cases = [
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
+ >
53
203
  ];
54
- type M = typeof m;
55
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
56
- const cases: cases = [true];
57
- expect(cases).toBe(cases);
204
+
205
+ const cases: cases = [true, true, true, true, true, true, true, true, true];
58
206
  });
59
207
 
60
- it("filter input as 1:0 cardinality", () => {
61
- const m: MapTo<I, O> = (_i) => [];
62
- type M = typeof m;
63
- type cases = [Expect<Equal<M, MapTo<I, O>>>];
64
- const cases: cases = [true];
65
- expect(cases).toBe(cases);
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>>
234
+ ];
235
+ const cases: cases = [true, true];
66
236
  });
67
237
  });
68
238
 
69
239
  describe("mapTo() utility function", () => {
70
- it("test 1:1 mapping", () => {
71
- const m = mapTo<I, O>((i) => [
72
- {
73
- title: i.title,
74
- count: i.products.length,
75
- },
76
- ]);
77
- type M = typeof m;
78
- type R = ReturnType<M>;
79
-
80
- // runtime
81
- expect(m(i)).toEqual([{ title: i.title, count: i.products.length }]);
82
- // types
83
- type cases = [Expect<Equal<R, O[]>>];
84
- const cases: cases = [true];
85
- expect(cases).toBe(cases);
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
+
272
+ it("M:1 conversion", () => {
273
+ const m = mapTo
274
+ .config({
275
+ cardinality: "I[] -> O",
276
+ output: "req",
277
+ })
278
+ .map<I, O>((i) => ({
279
+ title: "count of products",
280
+ count: i.reduce((acc, i) => (acc = acc + i.products.length), 0),
281
+ }));
282
+
283
+ const summary = m([i, i2]);
284
+ expect(summary.title).toBe("count of products");
285
+ expect(summary.count).toBe(6);
286
+ });
287
+
288
+ it("1:1 conversion", () => {
289
+ const m = mapTo.oneToOne().map<I, O>((i) => ({
290
+ title: i.title,
291
+ count: i.products.length,
292
+ }));
293
+ const o = m(i);
294
+
295
+ expect(o?.title).toBe(i.title);
296
+ expect(o?.count).toBe(3);
297
+ });
298
+
299
+ it("1:1 with 1:M conversion setting", () => {
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 }]);
305
+ const o = m(i);
306
+
307
+ expect(o.length).toBe(1);
308
+ expect(o[0]?.title).toBe(i.title);
309
+ expect(o[0]?.count).toBe(3);
86
310
  });
87
311
 
88
- it("test 1:1 mapping with extraneous prop", () => {
89
- const m = mapTo<I, O>((i) => [
90
- {
91
- title: i.title,
92
- count: i.products.length,
93
- extraneous: 42,
94
- },
95
- ]);
96
- type M = typeof m;
97
- type R = ReturnType<M>;
98
-
99
- // runtime
100
- // TODO: extraneous props are cut out of the type but still allows us to return `O` values with extraneous props!
101
- // expect(m(i)).toEqual([{ title: i.title, count: i.products.length }]);
102
- // types
103
- type cases = [Expect<Equal<R, O[]>>];
104
- const cases: cases = [true];
105
- expect(cases).toBe(cases);
312
+ it("1:M conversion ", () => {
313
+ const m = mapTo
314
+ .config({ output: "req", cardinality: "I -> O[]" })
315
+ .map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
316
+ const o = m([i, i2]);
317
+
318
+ expect(o.length).toBe(2);
319
+ o.map((item) => expect("title" in item).toBeTruthy());
106
320
  });
107
321
 
108
- it("test input filtering", () => {
109
- const m = mapTo<I, O>((x) => {
110
- return x.title === "i2"
111
- ? null
112
- : [
113
- {
114
- title: i.title,
115
- count: i.products.length,
116
- },
117
- ];
322
+ it("M:1 conversion", () => {
323
+ const m = mapTo.config({ output: "req", cardinality: "I[] -> O" }).map<I, O>((i) => {
324
+ return {
325
+ title: "summary",
326
+ count: i.reduce((acc, i) => (acc = acc + i.products.length), 0),
327
+ };
118
328
  });
119
- const results = m([i, i2]);
329
+ const o = m([i, i2]);
330
+
331
+ expect(o.title).toBe("summary");
332
+ expect(o.count).toBe(6);
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]);
120
353
 
121
- expect(results).toHaveLength(1);
122
- expect(results[0].title).toBe("Test");
354
+ expect(o.title).toBe("summary");
355
+ expect(o.count).toBe(6);
123
356
  });
124
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