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.
- package/.vscode/settings.json +4 -0
- package/dist/index.d.ts +363 -165
- package/dist/index.js +64 -24
- package/dist/index.mjs +60 -23
- 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/dictionary/MapTo.ts +247 -36
- package/src/types/dictionary/props.ts +11 -0
- package/src/types/kv/DictFromKv.ts +1 -1
- package/src/types/type-testing.ts +2 -5
- package/src/utility/dictionary/kv/kvToDict.ts +1 -2
- package/src/utility/dictionary/mapTo.ts +122 -77
- 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/dictionary/IntersectingKeys.test.ts +42 -0
- package/tests/dictionary/TypeDefault.test.ts +76 -0
- package/tests/dictionary/mapTo.test.ts +214 -48
- package/tests/dictionary/merge.test.ts +41 -0
- package/tests/withValue.spec.ts +0 -1
|
@@ -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
|
|
2
|
-
import {
|
|
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 {
|
|
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 {
|
|
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
|
|
16
|
-
type
|
|
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
|
-
|
|
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
|
|
36
|
-
type
|
|
37
|
-
type
|
|
38
|
-
type
|
|
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
|
|
56
|
-
type
|
|
57
|
-
type
|
|
58
|
-
type
|
|
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
|
|
76
|
-
type
|
|
77
|
-
type
|
|
78
|
-
type
|
|
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
|
|
96
|
-
type
|
|
97
|
-
type
|
|
98
|
-
type
|
|
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("
|
|
115
|
-
|
|
116
|
-
type
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
type
|
|
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
|
-
|
|
125
|
-
Expect<Equal<
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
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",
|
|
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",
|
|
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
|
+
});
|
package/tests/withValue.spec.ts
CHANGED