inferred-types 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +5 -0
- package/dist/index.d.ts +441 -126
- package/dist/index.js +80 -6
- package/dist/index.mjs +74 -6
- package/package.json +1 -1
- package/src/types/Mutable.ts +3 -1
- package/src/types/TypeInfo/Extends.ts +14 -0
- package/src/types/TypeInfo/IsBooleanLiteral.ts +9 -0
- package/src/types/TypeInfo/IsLiteral.ts +36 -2
- package/src/types/TypeInfo/IsNumericLiteral.ts +9 -0
- package/src/types/TypeInfo/IsObject.ts +27 -0
- package/src/types/TypeInfo/IsScalar.ts +14 -0
- package/src/types/TypeInfo/IsStringLiteral.ts +9 -0
- package/src/types/TypeInfo/IsUndefined.ts +14 -0
- package/src/types/TypeInfo/TypeDefault.ts +58 -0
- package/src/types/TypeInfo/index.ts +4 -0
- package/src/types/alphabetic/Cardinality.ts +80 -0
- package/src/types/alphabetic/index.ts +1 -0
- package/src/types/dictionary/MapTo.ts +332 -13
- package/src/types/dictionary/props.ts +11 -0
- package/src/types/index.ts +1 -0
- package/src/types/kv/DictFromKv.ts +1 -1
- package/src/types/literal-unions/OptRequired.ts +4 -0
- package/src/types/literal-unions/index.ts +1 -0
- package/src/types/string-literals/Replace.ts +8 -4
- package/src/types/type-testing.ts +2 -5
- package/src/utility/dictionary/kv/kvToDict.ts +1 -2
- package/src/utility/dictionary/mapTo.ts +160 -28
- package/src/utility/dictionary/merge.ts +36 -0
- package/src/utility/runtime/conditions/isObject.ts +2 -16
- package/tests/TypeInfo/IsLiteral.spec.ts +17 -1
- package/tests/createFnWithProps.spec.ts +1 -0
- package/tests/dictionary/IntersectingKeys.test.ts +42 -0
- package/tests/dictionary/TypeDefault.test.ts +76 -0
- package/tests/dictionary/mapTo.test.ts +327 -94
- package/tests/dictionary/merge.test.ts +41 -0
- package/tests/withValue.spec.ts +0 -1
|
@@ -1,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"] };
|
|
@@ -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<
|
|
14
|
-
it("
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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("
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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("
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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("
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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("
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
expect(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
expect(
|
|
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("
|
|
89
|
-
const m = mapTo
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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("
|
|
109
|
-
const m = mapTo<I, O>((
|
|
110
|
-
return
|
|
111
|
-
|
|
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
|
|
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(
|
|
122
|
-
expect(
|
|
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
|
+
});
|
package/tests/withValue.spec.ts
CHANGED