inferred-types 0.25.0 → 0.27.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 +1 -0
- package/dist/index.d.ts +308 -84
- package/dist/index.js +378 -262
- package/dist/index.mjs +370 -259
- package/package.json +1 -1
- package/src/types/alphabetic/Cardinality.ts +80 -0
- package/src/types/alphabetic/index.ts +1 -0
- package/src/types/dictionary/MapTo.ts +128 -15
- package/src/types/functions/LogicFunction.ts +4 -0
- package/src/types/functions/index.ts +1 -0
- package/src/types/index.ts +1 -0
- 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/utility/boolean-logic/and.ts +15 -0
- package/src/utility/boolean-logic/filter.ts +268 -0
- package/src/utility/boolean-logic/index.ts +4 -0
- package/src/utility/boolean-logic/not.ts +15 -0
- package/src/utility/boolean-logic/or.ts +15 -0
- package/src/utility/dictionary/mapTo.ts +117 -30
- package/src/utility/index.ts +1 -1
- package/src/utility/lists/asArray.ts +34 -0
- package/src/utility/lists/index.ts +1 -0
- package/tests/boolean-logic/boolean.spec.ts +21 -0
- package/tests/boolean-logic/filter.spec.ts +52 -0
- package/tests/createFnWithProps.spec.ts +1 -0
- package/tests/dictionary/mapTo.test.ts +159 -92
- package/tests/lists/asArray.test.ts +91 -0
- package/src/utility/map-reduce/filter.ts +0 -35
- package/src/utility/map-reduce/index.ts +0 -12
|
@@ -1,37 +1,124 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MapDirectionVal, MapFn, MapFnInput, MapTo } from "src/types/dictionary";
|
|
2
|
+
import { OptRequired } from "src/types/literal-unions";
|
|
3
|
+
import { createFnWithProps } from "../createFnWithProps";
|
|
4
|
+
|
|
5
|
+
export interface MapConfig<
|
|
6
|
+
IR extends OptRequired = "req",
|
|
7
|
+
D extends MapDirectionVal = "I -> O[]",
|
|
8
|
+
OR extends OptRequired = "opt"
|
|
9
|
+
> {
|
|
10
|
+
input?: IR;
|
|
11
|
+
output?: OR;
|
|
12
|
+
direction?: D;
|
|
13
|
+
}
|
|
2
14
|
|
|
3
15
|
/**
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
16
|
+
* Provides a mapper function using the default configuration
|
|
17
|
+
*/
|
|
18
|
+
export type MapperApiDefault = <I, O>(map: MapTo<I, O>) => MapFn<I, O>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Provides opportunity to configure _input_ and _output_ relationships
|
|
22
|
+
* prior to providing a mapping function.
|
|
23
|
+
*/
|
|
24
|
+
export type MapperApiConfigured = <
|
|
25
|
+
IR extends OptRequired = "req",
|
|
26
|
+
D extends MapDirectionVal = "I -> O[]",
|
|
27
|
+
OR extends OptRequired = "opt"
|
|
28
|
+
>(
|
|
29
|
+
config: MapConfig<IR, D, OR>
|
|
30
|
+
) => <I, O>(map: MapTo<I, O, IR, D, OR>) => MapFn<I, O, IR, D, OR>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The single implementation for all mapping
|
|
34
|
+
*/
|
|
35
|
+
const mapper =
|
|
36
|
+
<
|
|
37
|
+
IR extends OptRequired = "req",
|
|
38
|
+
D extends MapDirectionVal = "I -> O[]",
|
|
39
|
+
OR extends OptRequired = "opt"
|
|
40
|
+
>(
|
|
41
|
+
config: MapConfig<IR, D, OR> = {}
|
|
42
|
+
) =>
|
|
43
|
+
<I, O>(map: MapTo<I, O, IR, D, OR>) => {
|
|
44
|
+
// TODO
|
|
45
|
+
config = {
|
|
46
|
+
input: "req",
|
|
47
|
+
output: "opt",
|
|
48
|
+
direction: "I -> O[]",
|
|
49
|
+
...config,
|
|
50
|
+
} as Required<MapConfig<IR, D, OR>>;
|
|
51
|
+
|
|
52
|
+
const fn = <S extends MapFnInput<I, IR, D>>(source?: S) => {
|
|
53
|
+
const isArray =
|
|
54
|
+
config.direction === "I -> O[]" && Array.isArray(source)
|
|
55
|
+
? true
|
|
56
|
+
: config.direction === "I[] -> O" && Array.isArray(source) && Array.isArray(source[0])
|
|
57
|
+
? true
|
|
58
|
+
: false;
|
|
59
|
+
|
|
60
|
+
if (isArray) {
|
|
61
|
+
return (source as any).flatMap(map);
|
|
62
|
+
} else {
|
|
63
|
+
return map(source as any);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return fn as unknown as MapFn<I, O, IR, D, OR>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* **mapTo** _utility_
|
|
12
72
|
*
|
|
13
|
-
* This
|
|
73
|
+
* This utility creates a strongly typed data mapper which maps from one
|
|
74
|
+
* known source `I` to another `O`:
|
|
14
75
|
* ```ts
|
|
15
|
-
* const mapper = mapTo<I,O>(i =>
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* ]
|
|
19
|
-
* : null
|
|
20
|
-
* );
|
|
76
|
+
* const mapper = mapTo<I, O>(i => [{
|
|
77
|
+
* foo: i.bar
|
|
78
|
+
* }]);
|
|
21
79
|
* ```
|
|
80
|
+
*/
|
|
81
|
+
export const mapToFn = <I, O>(map: MapTo<I, O>) => {
|
|
82
|
+
return mapper()<I, O>(map);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Provides a `config` method which allows the relationships between _inputs_
|
|
87
|
+
* and _outputs_ to be configured.
|
|
88
|
+
*/
|
|
89
|
+
export const mapToDict = {
|
|
90
|
+
/** configure the relationship between the _inputs_ and _outputs_ */
|
|
91
|
+
config: <
|
|
92
|
+
IR extends OptRequired = "req",
|
|
93
|
+
D extends MapDirectionVal = "I -> O[]",
|
|
94
|
+
OR extends OptRequired = "opt"
|
|
95
|
+
>(
|
|
96
|
+
c: MapConfig<IR, D, OR> = { input: "req", output: "opt", direction: "I -> O[]" } as MapConfig<
|
|
97
|
+
IR,
|
|
98
|
+
D,
|
|
99
|
+
OR
|
|
100
|
+
>
|
|
101
|
+
) => ({
|
|
102
|
+
/**
|
|
103
|
+
* create your mapping with your recently setup configuration:
|
|
104
|
+
* ```ts
|
|
105
|
+
* const mapper = mapTo
|
|
106
|
+
* .config({ ... })
|
|
107
|
+
* .map<I, O>(i => [{
|
|
108
|
+
* foo: i.bar
|
|
109
|
+
* }]);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
map<I, O>(map: MapTo<I, O, IR, D, OR>): MapFn<I, O, IR, D, OR> {
|
|
113
|
+
return mapper(c)(map);
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* **mapTo** _utility_
|
|
22
120
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* function (source: I | I[]): O[]
|
|
26
|
-
* ```
|
|
121
|
+
* This utility creates a strongly typed data mapper which maps from one
|
|
122
|
+
* known source `I` to another `O`.
|
|
27
123
|
*/
|
|
28
|
-
export const mapTo =
|
|
29
|
-
<I extends {}, O extends {}>(cb: MapToWithFiltering<I, O>) =>
|
|
30
|
-
(source: I | I[]) => {
|
|
31
|
-
if (Array.isArray(source)) {
|
|
32
|
-
return source.flatMap((i) => cb(i)).filter((i) => i !== null) as O[];
|
|
33
|
-
} else {
|
|
34
|
-
const result = cb(source);
|
|
35
|
-
return result ? result : ([] as O[]);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
124
|
+
export const mapTo = createFnWithProps(mapToFn, mapToDict);
|
package/src/utility/index.ts
CHANGED
|
@@ -10,11 +10,11 @@ export * from "./keys";
|
|
|
10
10
|
export * from "./ruleset";
|
|
11
11
|
// directory exports
|
|
12
12
|
export * from "./api/index";
|
|
13
|
+
export * from "./boolean-logic/index";
|
|
13
14
|
export * from "./dictionary/index";
|
|
14
15
|
export * from "./errors/index";
|
|
15
16
|
export * from "./lists/index";
|
|
16
17
|
export * from "./literals/index";
|
|
17
|
-
export * from "./map-reduce/index";
|
|
18
18
|
export * from "./modelling/index";
|
|
19
19
|
export * from "./runtime/index";
|
|
20
20
|
export * from "./state/index";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Narrowable, Widen } from "src/types";
|
|
2
|
+
import { isArray } from "../runtime";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Type utility which converts `undefined[]` to `unknown[]`
|
|
6
|
+
*/
|
|
7
|
+
export type UndefinedArrayIsUnknown<T extends any[]> = undefined[] extends T ? unknown[] : T;
|
|
8
|
+
|
|
9
|
+
export type AsArray<T, W extends boolean = false> = T extends any[]
|
|
10
|
+
? W extends true
|
|
11
|
+
? Widen<T>
|
|
12
|
+
: T
|
|
13
|
+
: W extends true
|
|
14
|
+
? UndefinedArrayIsUnknown<Widen<T>[]>
|
|
15
|
+
: UndefinedArrayIsUnknown<T[]>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensures that any input passed in is passed back as an array:
|
|
19
|
+
*
|
|
20
|
+
* - if it was already an array than this just serves as an _identity_ function
|
|
21
|
+
* - if it was not then it wraps the element into a one element array of the
|
|
22
|
+
* given type
|
|
23
|
+
*
|
|
24
|
+
* Note: by default the _type_ of values will be intentionally widened so that the value "abc"
|
|
25
|
+
* is of type `string` not the literal `abc`. If you want to keep literal types then
|
|
26
|
+
* change the optional _widen_ parameter to _false_.
|
|
27
|
+
*/
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
export const asArray = <T extends Narrowable, W extends boolean = true>(thing: T, _widen?: W) => {
|
|
30
|
+
return (isArray(thing) ? thing : typeof thing === "undefined" ? ([] as T[]) : [thing]) as AsArray<
|
|
31
|
+
T,
|
|
32
|
+
W
|
|
33
|
+
>;
|
|
34
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
// import type { Equal, Expect } from "@type-challenges/utils";
|
|
3
|
+
import { and, filter } from "src/utility";
|
|
4
|
+
|
|
5
|
+
describe("boolean logic", () => {
|
|
6
|
+
it("AND operation", () => {
|
|
7
|
+
const a1 = and(
|
|
8
|
+
() => true,
|
|
9
|
+
() => false
|
|
10
|
+
);
|
|
11
|
+
const a2 = and(
|
|
12
|
+
() => true,
|
|
13
|
+
() => true
|
|
14
|
+
);
|
|
15
|
+
const md = and(filter({ endsWith: ".md" }), filter({ not: { startsWith: "_" } }));
|
|
16
|
+
|
|
17
|
+
expect(a1()).toBe(false);
|
|
18
|
+
expect(a2()).toBe(true);
|
|
19
|
+
expect(["foo.md", "bar.html", "_baz.md"].filter(md)).toEqual(["foo.md"]);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { filter } from "src/utility";
|
|
2
|
+
import { Equal, Expect } from "@type-challenges/utils";
|
|
3
|
+
import { describe, it, expect } from "vitest";
|
|
4
|
+
|
|
5
|
+
describe("filter() utility function", () => {
|
|
6
|
+
it("string filter built and gives proper types", () => {
|
|
7
|
+
const f = filter({ startsWith: "th" });
|
|
8
|
+
type P = Parameters<typeof f>;
|
|
9
|
+
type R = ReturnType<typeof f>;
|
|
10
|
+
const notPrivate = filter({ not: { startsWith: ["_", "."] } });
|
|
11
|
+
|
|
12
|
+
// runtime
|
|
13
|
+
expect(typeof f).toEqual("function");
|
|
14
|
+
expect(["one", "two", "three"].filter(f)).toEqual(["three"]);
|
|
15
|
+
expect(["foo.md", "bar.html", "_private.md", ".bobs-your-uncle.txt"].filter(notPrivate)) //
|
|
16
|
+
.toEqual(["foo.md", "bar.html"]);
|
|
17
|
+
|
|
18
|
+
// design time
|
|
19
|
+
type cases = [
|
|
20
|
+
Expect<Equal<P[0], string | undefined>>, //
|
|
21
|
+
Expect<Equal<R, boolean>>
|
|
22
|
+
];
|
|
23
|
+
const cases: cases = [true, true];
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("numeric filter built and gives proper types", () => {
|
|
27
|
+
const f = filter({ equals: 42 });
|
|
28
|
+
type P = Parameters<typeof f>;
|
|
29
|
+
type R = ReturnType<typeof f>;
|
|
30
|
+
|
|
31
|
+
// runtime
|
|
32
|
+
expect(typeof f).toEqual("function");
|
|
33
|
+
expect([1, 2, 3, 4, 5].filter(filter({ greaterThan: 3 }))) //
|
|
34
|
+
.toEqual([4, 5]);
|
|
35
|
+
expect([1, 2, 3, 4, 5].filter(filter({ not: { greaterThan: 3 } }))) //
|
|
36
|
+
.toEqual([1, 2, 3]);
|
|
37
|
+
|
|
38
|
+
// design time
|
|
39
|
+
type cases = [
|
|
40
|
+
Expect<Equal<P[0], number | undefined>>, //
|
|
41
|
+
Expect<Equal<R, boolean>>
|
|
42
|
+
];
|
|
43
|
+
const cases: cases = [true, true];
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("string filter's startsWith", () => {
|
|
47
|
+
const f = filter({ startsWith: "." });
|
|
48
|
+
const remaining = ["foo", "bar", ".baz"].filter(f);
|
|
49
|
+
|
|
50
|
+
expect(remaining).toEqual([".baz"]);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MapTo } from "src/types/dictionary";
|
|
1
|
+
import { MapDirection, MapFn, MapTo } from "src/types/dictionary";
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
|
|
4
4
|
import type { Expect, Equal } from "@type-challenges/utils";
|
|
@@ -10,115 +10,182 @@ const i2: I = { title: "i2", color: "green", products: ["foo", "bar", "baz"] };
|
|
|
10
10
|
|
|
11
11
|
type O = { title: string; count: number; unnecessary?: number };
|
|
12
12
|
|
|
13
|
-
describe("MapTo<
|
|
14
|
-
it("
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
describe("MapTo<I,O> and MapToFn<I,O>", () => {
|
|
14
|
+
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">;
|
|
19
|
+
|
|
20
|
+
/** userland mapping function */
|
|
21
|
+
type T = (source: I) => O[];
|
|
22
|
+
/** exposed via mapTo() util */
|
|
23
|
+
type WT = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
|
|
24
|
+
|
|
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>>
|
|
20
30
|
];
|
|
21
|
-
|
|
22
|
-
type cases = [Expect<Equal<M, MapTo<I, O>>>];
|
|
23
|
-
const cases: cases = [true];
|
|
24
|
-
expect(cases).toBe(cases);
|
|
31
|
+
const cases: cases = [true, true, true, true];
|
|
25
32
|
});
|
|
26
33
|
|
|
27
|
-
it("
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
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">;
|
|
39
|
+
|
|
40
|
+
type T0 = (source: I) => O[];
|
|
41
|
+
type T1 = (source: I) => [O, ...O[]];
|
|
42
|
+
type T2 = (source?: I) => O[];
|
|
43
|
+
type T3 = (source?: I) => [O, ...O[]];
|
|
44
|
+
|
|
45
|
+
type cases = [
|
|
46
|
+
Expect<Equal<Fn0, T0>>, //
|
|
47
|
+
Expect<Equal<Fn1, T1>>, //
|
|
48
|
+
Expect<Equal<Fn2, T2>>, //
|
|
49
|
+
Expect<Equal<Fn3, T3>> //
|
|
34
50
|
];
|
|
35
|
-
|
|
36
|
-
type cases = [Expect<Equal<M, MapTo<I, O>>>];
|
|
37
|
-
const cases: cases = [true];
|
|
38
|
-
expect(cases).toBe(cases);
|
|
51
|
+
const cases: cases = [true, true, true, true];
|
|
39
52
|
});
|
|
40
53
|
|
|
41
|
-
it("
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
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">;
|
|
59
|
+
|
|
60
|
+
type T0 = (source: I[]) => O | null;
|
|
61
|
+
type T1 = (source: I[]) => O;
|
|
62
|
+
type T2 = (source?: I[]) => O | null;
|
|
63
|
+
type T3 = (source?: I[]) => O;
|
|
64
|
+
|
|
65
|
+
type cases = [
|
|
66
|
+
Expect<Equal<Fn0, T0>>, //
|
|
67
|
+
Expect<Equal<Fn1, T1>>, //
|
|
68
|
+
Expect<Equal<Fn2, T2>>, //
|
|
69
|
+
Expect<Equal<Fn3, T3>> //
|
|
53
70
|
];
|
|
54
|
-
|
|
55
|
-
type cases = [Expect<Equal<M, MapTo<I, O>>>];
|
|
56
|
-
const cases: cases = [true];
|
|
57
|
-
expect(cases).toBe(cases);
|
|
71
|
+
const cases: cases = [true, true, true, true];
|
|
58
72
|
});
|
|
59
73
|
|
|
60
|
-
it("
|
|
61
|
-
|
|
62
|
-
type
|
|
63
|
-
type
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
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">;
|
|
79
|
+
|
|
80
|
+
type T0 = <S extends I | I[]>(source: S) => S extends I[] ? O[] : O[] | null;
|
|
81
|
+
type T1 = <S extends I | I[]>(source: S) => S extends I[] ? [O, ...O[]] : O[];
|
|
82
|
+
type T2 = <S extends I | I[] | undefined>(source?: S) => S extends I[] ? O[] : O[] | null;
|
|
83
|
+
type T3 = <S extends I | I[] | undefined>(source?: S) => S extends I[] ? [O, ...O[]] : O[];
|
|
84
|
+
|
|
85
|
+
type cases = [
|
|
86
|
+
Expect<Equal<Fn0, T0>>, //
|
|
87
|
+
Expect<Equal<Fn1, T1>>, //
|
|
88
|
+
Expect<Equal<Fn2, T2>>, //
|
|
89
|
+
Expect<Equal<Fn3, T3>> //
|
|
90
|
+
];
|
|
91
|
+
const cases: cases = [true, true, true, true];
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
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">;
|
|
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>> //
|
|
110
|
+
];
|
|
111
|
+
const cases: cases = [true, true, true, true];
|
|
112
|
+
});
|
|
113
|
+
|
|
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;
|
|
117
|
+
|
|
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;
|
|
122
|
+
|
|
123
|
+
type cases = [
|
|
124
|
+
Expect<Equal<T1, MapFn<I, O>>>, //
|
|
125
|
+
Expect<Equal<T1, A1>>
|
|
126
|
+
];
|
|
127
|
+
const cases: cases = [true, true];
|
|
66
128
|
});
|
|
67
129
|
});
|
|
68
130
|
|
|
69
131
|
describe("mapTo() utility function", () => {
|
|
70
|
-
it("
|
|
71
|
-
const m = mapTo
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
132
|
+
it("M:1 conversion", () => {
|
|
133
|
+
const m = mapTo
|
|
134
|
+
.config({
|
|
135
|
+
direction: "I[] -> O",
|
|
136
|
+
output: "req",
|
|
137
|
+
})
|
|
138
|
+
.map<I, O>((i) => ({
|
|
139
|
+
title: "count of products",
|
|
140
|
+
count: i.reduce((acc, i) => (acc = acc + i.products.length), 0),
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
const summary = m([i, i2]);
|
|
144
|
+
expect(summary.title).toBe("count of products");
|
|
145
|
+
expect(summary.count).toBe(6);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
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 }));
|
|
152
|
+
const o = m(i);
|
|
153
|
+
|
|
154
|
+
expect(o?.title).toBe(i.title);
|
|
155
|
+
expect(o?.count).toBe(3);
|
|
86
156
|
});
|
|
87
157
|
|
|
88
|
-
it("
|
|
89
|
-
const m = mapTo
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
expect(
|
|
158
|
+
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 }]);
|
|
162
|
+
const o = m(i);
|
|
163
|
+
|
|
164
|
+
expect(o.length).toBe(1);
|
|
165
|
+
expect(o[0]?.title).toBe(i.title);
|
|
166
|
+
expect(o[0]?.count).toBe(3);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("1:M conversion ", () => {
|
|
170
|
+
const m = mapTo
|
|
171
|
+
.config({ output: "req", direction: MapDirection.OneToMany })
|
|
172
|
+
.map<I, O>((i) => [{ title: i.title, count: i.products.length }]);
|
|
173
|
+
const o = m([i, i2]);
|
|
174
|
+
|
|
175
|
+
expect(o.length).toBe(2);
|
|
176
|
+
o.map((item) => expect("title" in item).toBeTruthy());
|
|
106
177
|
});
|
|
107
178
|
|
|
108
|
-
it("
|
|
109
|
-
const m = mapTo<I, O>((
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
:
|
|
113
|
-
|
|
114
|
-
title: i.title,
|
|
115
|
-
count: i.products.length,
|
|
116
|
-
},
|
|
117
|
-
];
|
|
179
|
+
it("M:1 conversion", () => {
|
|
180
|
+
const m = mapTo.config({ output: "req", direction: MapDirection.ManyToOne }).map<I, O>((i) => {
|
|
181
|
+
return {
|
|
182
|
+
title: "summary",
|
|
183
|
+
count: i.reduce((acc, i) => (acc = acc + i.products.length), 0),
|
|
184
|
+
};
|
|
118
185
|
});
|
|
119
|
-
const
|
|
186
|
+
const o = m([i, i2]);
|
|
120
187
|
|
|
121
|
-
expect(
|
|
122
|
-
expect(
|
|
188
|
+
expect(o.title).toBe("summary");
|
|
189
|
+
expect(o.count).toBe(6);
|
|
123
190
|
});
|
|
124
191
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { asArray } from "src/utility";
|
|
3
|
+
import { Equal, Expect } from "@type-challenges/utils";
|
|
4
|
+
|
|
5
|
+
describe("asArray() function", () => {
|
|
6
|
+
it("non-array is returned as an array", () => {
|
|
7
|
+
const i = "a";
|
|
8
|
+
const o = asArray(i);
|
|
9
|
+
type O = typeof o;
|
|
10
|
+
|
|
11
|
+
// run-time
|
|
12
|
+
expect(o).toEqual(["a"]);
|
|
13
|
+
// design-time
|
|
14
|
+
type cases = [Expect<Equal<O, string[]>>];
|
|
15
|
+
const cases: cases = [true];
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("array is returned as an array", () => {
|
|
19
|
+
const i = ["a"];
|
|
20
|
+
const o = asArray(i);
|
|
21
|
+
type O = typeof o;
|
|
22
|
+
|
|
23
|
+
// run-time
|
|
24
|
+
expect(o).toEqual(["a"]);
|
|
25
|
+
// design-time
|
|
26
|
+
type cases = [Expect<Equal<O, string[]>>];
|
|
27
|
+
const cases: cases = [true];
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("non-array literal is returned as an array", () => {
|
|
31
|
+
const i = "a" as const;
|
|
32
|
+
const o = asArray(i, false);
|
|
33
|
+
const o2 = asArray(i, true);
|
|
34
|
+
|
|
35
|
+
type O = typeof o;
|
|
36
|
+
type O2 = typeof o2;
|
|
37
|
+
|
|
38
|
+
// run-time
|
|
39
|
+
expect(o).toEqual(["a"]);
|
|
40
|
+
// design-time
|
|
41
|
+
type cases = [
|
|
42
|
+
Expect<Equal<O, "a"[]>>, //
|
|
43
|
+
Expect<Equal<O2, string[]>>
|
|
44
|
+
];
|
|
45
|
+
const cases: cases = [true, true];
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("handling non-array element which presents as undefined", () => {
|
|
49
|
+
type T = string | undefined;
|
|
50
|
+
const i = undefined;
|
|
51
|
+
const i2: T = undefined;
|
|
52
|
+
const o = asArray(i);
|
|
53
|
+
const o2 = asArray(i2 as T);
|
|
54
|
+
const o3 = asArray(i2 as T, false);
|
|
55
|
+
type O = typeof o;
|
|
56
|
+
type O2 = typeof o2;
|
|
57
|
+
type O3 = typeof o3;
|
|
58
|
+
|
|
59
|
+
// run-time
|
|
60
|
+
expect(o).toEqual([]);
|
|
61
|
+
expect(o2).toEqual([]);
|
|
62
|
+
// design-time
|
|
63
|
+
type cases = [
|
|
64
|
+
Expect<Equal<O, unknown[]>>, //
|
|
65
|
+
// TODO: would be nice to extract the unknown[] part of the union
|
|
66
|
+
Expect<Equal<O2, unknown[] | string[]>>,
|
|
67
|
+
Expect<Equal<O3, unknown[] | string[]>>
|
|
68
|
+
];
|
|
69
|
+
const cases: cases = [true, true, true];
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("handling array element which contains undefined is unaffected", () => {
|
|
73
|
+
type T = string | undefined;
|
|
74
|
+
const i = [undefined, "foobar"];
|
|
75
|
+
const i2: T[] = [undefined, "foobar"];
|
|
76
|
+
const o = asArray(i);
|
|
77
|
+
const o2 = asArray(i2 as T[]);
|
|
78
|
+
type O = typeof o;
|
|
79
|
+
type O2 = typeof o2;
|
|
80
|
+
|
|
81
|
+
// run-time
|
|
82
|
+
expect(o).toEqual([undefined, "foobar"]);
|
|
83
|
+
expect(o2).toEqual([undefined, "foobar"]);
|
|
84
|
+
// design-time
|
|
85
|
+
type cases = [
|
|
86
|
+
Expect<Equal<O, (string | undefined)[]>>, //
|
|
87
|
+
Expect<Equal<O2, T[]>>
|
|
88
|
+
];
|
|
89
|
+
const cases: cases = [true, true];
|
|
90
|
+
});
|
|
91
|
+
});
|