relation-matcher 1.1.0 → 1.1.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/README.md +8 -0
- package/dist/src/index.d.ts +10 -5
- package/dist/src/index.js +35 -51
- package/dist/src/types/generic-bases.d.ts +2 -1
- package/dist/src/types/generic-less.d.ts +7 -3
- package/dist/src/types/utils.d.ts +3 -0
- package/dist/src/utils/invertInput.d.ts +2 -1
- package/dist/src/utils/isNullable.d.ts +3 -0
- package/dist/src/utils/isNullable.js +6 -0
- package/dist/src/utils/joins.d.ts +5 -0
- package/dist/src/utils/joins.js +9 -0
- package/dist/src/utils/matcher.d.ts +3 -3
- package/dist/src/utils/matcher.js +2 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/index.ts +69 -105
- package/src/types/generic-bases.ts +2 -1
- package/src/types/generic-less.ts +13 -8
- package/src/types/utils.ts +5 -0
- package/src/utils/invertInput.ts +3 -1
- package/src/utils/isNullable.ts +15 -0
- package/src/utils/joins.ts +22 -0
- package/src/utils/matcher.ts +12 -4
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
1
|
+
import type { MaybeArray, MaybeNull, SRecord } from "./types";
|
|
2
|
+
import type { TInputBase, TInputBaseJoin } from "./types/generic-bases";
|
|
3
|
+
import type { Output, OutputRoot } from "./types/generic-less";
|
|
4
|
+
import type { RelationMapRoot as Root } from "./types/inputs";
|
|
5
|
+
import type { RelationMapperReturnRoot as ReturnRoot } from "./types/return";
|
|
5
6
|
import invertInput, { type InvertedInput } from "./utils/invertInput";
|
|
7
|
+
import { assertIsNullable } from "./utils/isNullable";
|
|
8
|
+
import { arrayJoinBase, singleJoinBase } from "./utils/joins";
|
|
6
9
|
import { getJoinFinalKey } from "./utils/keys";
|
|
7
|
-
import {
|
|
10
|
+
import { createMatcherFunc } from "./utils/matcher";
|
|
8
11
|
|
|
9
12
|
export const relationMatcherRoot = <
|
|
10
13
|
TInput extends TInputBase,
|
|
11
|
-
TOutputRoot extends
|
|
14
|
+
TOutputRoot extends Root<TInput>,
|
|
12
15
|
>(
|
|
13
16
|
inputs: TInput[],
|
|
14
17
|
output: TOutputRoot,
|
|
15
|
-
):
|
|
18
|
+
): SRecord<ReturnRoot<TInput, TOutputRoot>> => {
|
|
16
19
|
if (!inputs.length) return {};
|
|
17
20
|
|
|
18
21
|
if (!Array.isArray(inputs)) {
|
|
@@ -21,140 +24,101 @@ export const relationMatcherRoot = <
|
|
|
21
24
|
|
|
22
25
|
const invertedInput = invertInput<TInput>(inputs);
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const relations = Object.entries(output).reduce<
|
|
32
|
-
Record<string, unknown>
|
|
33
|
-
>((final, [key, value]) => {
|
|
34
|
-
if (typeof value === "object") {
|
|
35
|
-
const finalKey = getJoinFinalKey(key);
|
|
36
|
-
|
|
37
|
-
if (value.joinType === "array") {
|
|
38
|
-
const baseObjArr = invertedInput[value.base]!.filter(
|
|
39
|
-
matcherFunc(row, value as Output),
|
|
40
|
-
) as TInputBase[string][];
|
|
41
|
-
|
|
42
|
-
final[finalKey] = Object.values(
|
|
43
|
-
baseObjArr.reduce<
|
|
44
|
-
Record<string, NonNullable<TInputBase[string]>>
|
|
45
|
-
>((final, item) => {
|
|
46
|
-
if (!item) return final;
|
|
47
|
-
|
|
48
|
-
final[item[value.id as string] as string] = {
|
|
49
|
-
...item,
|
|
50
|
-
...relationMatcherJoiner(
|
|
51
|
-
invertedInput,
|
|
52
|
-
value as Output,
|
|
53
|
-
item,
|
|
54
|
-
),
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
return final;
|
|
58
|
-
}, {}),
|
|
59
|
-
);
|
|
60
|
-
} else {
|
|
61
|
-
const item =
|
|
62
|
-
(invertedInput[value.base]!.find(
|
|
63
|
-
matcherFunc(row, value as Output),
|
|
64
|
-
) as TInputBase[string] | undefined) ?? null;
|
|
65
|
-
|
|
66
|
-
if (item) {
|
|
67
|
-
final[finalKey] = {
|
|
68
|
-
...item,
|
|
69
|
-
...relationMatcherJoiner(
|
|
70
|
-
invertedInput,
|
|
71
|
-
value as Output,
|
|
72
|
-
item,
|
|
73
|
-
),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
27
|
+
type BaseItems = SRecord<TInput[TOutputRoot["base"]]>;
|
|
28
|
+
const baseItems = invertedInput[output.base].reduce<BaseItems>(
|
|
29
|
+
(final, inputRow) => {
|
|
30
|
+
if (!inputRow) {
|
|
31
|
+
return final;
|
|
77
32
|
}
|
|
78
33
|
|
|
79
|
-
|
|
80
|
-
|
|
34
|
+
const relations = joiner(
|
|
35
|
+
invertedInput,
|
|
36
|
+
output as OutputRoot,
|
|
37
|
+
inputRow,
|
|
38
|
+
);
|
|
81
39
|
|
|
82
|
-
|
|
40
|
+
const rowId = inputRow[output.id as string];
|
|
83
41
|
|
|
84
|
-
|
|
85
|
-
|
|
42
|
+
final[rowId as string] = {
|
|
43
|
+
...inputRow,
|
|
44
|
+
...relations,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return final;
|
|
48
|
+
},
|
|
49
|
+
{},
|
|
50
|
+
);
|
|
86
51
|
|
|
87
52
|
return baseItems as unknown as Record<
|
|
88
53
|
string,
|
|
89
|
-
|
|
54
|
+
ReturnRoot<TInput, TOutputRoot>
|
|
90
55
|
>;
|
|
91
56
|
};
|
|
92
57
|
|
|
93
58
|
export const relationMatcher = relationMatcherRoot;
|
|
94
59
|
export default relationMatcherRoot;
|
|
95
60
|
|
|
96
|
-
const
|
|
61
|
+
const joiner = (
|
|
97
62
|
input: InvertedInput,
|
|
98
|
-
output: Output,
|
|
99
|
-
joiningFrom:
|
|
63
|
+
output: Output | OutputRoot,
|
|
64
|
+
joiningFrom: SRecord<unknown>,
|
|
100
65
|
) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (typeof value === "object") {
|
|
105
|
-
const finalKey = getJoinFinalKey(key);
|
|
66
|
+
type Return = SRecord<MaybeArray<MaybeNull<TInputBaseJoin>>>;
|
|
67
|
+
return Object.entries(output).reduce<Return>((final, [key, value]) => {
|
|
68
|
+
if (typeof value !== "object") return final;
|
|
106
69
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
) => item?.[value.joinsTo] === joiningFrom[value.joinsFrom];
|
|
70
|
+
const finalKey = getJoinFinalKey(key);
|
|
71
|
+
const matcherFunc = createMatcherFunc(joiningFrom, value);
|
|
110
72
|
|
|
111
|
-
|
|
112
|
-
throw new Error(`Input is missing rows for ${value.base}.`);
|
|
73
|
+
const joinType = input[value.base];
|
|
113
74
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
matcherFunc,
|
|
117
|
-
) as InvertedInput[number];
|
|
75
|
+
if (!joinType)
|
|
76
|
+
throw new Error(`Input is missing rows for ${value.base}.`);
|
|
118
77
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
>((final, item) => {
|
|
123
|
-
if (!item) return final;
|
|
124
|
-
|
|
125
|
-
const propertyKey = item[value.id as string] as string;
|
|
78
|
+
switch (value.joinType) {
|
|
79
|
+
case "array": {
|
|
80
|
+
const baseObjArr = arrayJoinBase(joinType, matcherFunc);
|
|
126
81
|
|
|
127
|
-
|
|
82
|
+
type FinalRow = SRecord<NonNullable<TInputBase[string]>>;
|
|
83
|
+
final[finalKey] = Object.values(
|
|
84
|
+
baseObjArr.reduce<FinalRow>((final, item) => {
|
|
85
|
+
const propertyKey = item[value.id] as string;
|
|
128
86
|
|
|
129
|
-
final[propertyKey]
|
|
87
|
+
final[propertyKey] ??= {
|
|
130
88
|
...item,
|
|
131
|
-
...
|
|
89
|
+
...joiner(input, value, item),
|
|
132
90
|
};
|
|
133
91
|
|
|
134
92
|
return final;
|
|
135
93
|
}, {}),
|
|
136
94
|
);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
throw new Error(
|
|
145
|
-
"Value should exist as output schema defines it as nullable. Instead found null in array",
|
|
146
|
-
);
|
|
147
|
-
}
|
|
95
|
+
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "single": {
|
|
99
|
+
const item = singleJoinBase(joinType, matcherFunc);
|
|
100
|
+
|
|
101
|
+
assertIsNullable(item, value.isNullable, output, value.base);
|
|
148
102
|
|
|
149
103
|
if (item) {
|
|
150
104
|
final[finalKey] = {
|
|
151
105
|
...item,
|
|
152
|
-
...
|
|
106
|
+
...joiner(input, value, item),
|
|
153
107
|
};
|
|
154
108
|
}
|
|
109
|
+
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
default: {
|
|
113
|
+
value.joinType satisfies never;
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Unhandled join type. Relation matcher can not handle ${value.joinType}.`,
|
|
116
|
+
);
|
|
155
117
|
}
|
|
156
118
|
}
|
|
157
119
|
|
|
158
120
|
return final;
|
|
159
121
|
}, {});
|
|
160
122
|
};
|
|
123
|
+
|
|
124
|
+
export { joiner as relationMatcherJoiner };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RelationMapBase, RelationMapJoiner } from "./inputs";
|
|
2
2
|
|
|
3
|
-
export type
|
|
3
|
+
export type TInputBaseJoin = Record<string, unknown>;
|
|
4
|
+
export type TInputBase = Record<string, TInputBaseJoin | null>;
|
|
4
5
|
export type TJoinedFromBase = TInputBase[keyof TInputBase];
|
|
5
6
|
export type TOutputBase<
|
|
6
7
|
TInput extends TInputBase,
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { TInputBase } from "./generic-bases";
|
|
2
2
|
|
|
3
|
+
export type OutputBase<
|
|
4
|
+
TJoinType extends "single" | "array" = "single" | "array",
|
|
5
|
+
> = {
|
|
6
|
+
base: string;
|
|
7
|
+
id: string;
|
|
8
|
+
joinsTo: string;
|
|
9
|
+
joinsFrom: string;
|
|
10
|
+
joinType: TJoinType;
|
|
11
|
+
isNullable?: false;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type OutputRoot = OutputJoins & { base: string; id: string };
|
|
3
15
|
export type Output<TJoinType extends "single" | "array" = "single" | "array"> =
|
|
4
|
-
OutputJoins &
|
|
5
|
-
base: keyof TInputBase;
|
|
6
|
-
id: string;
|
|
7
|
-
joinsTo: string;
|
|
8
|
-
joinsFrom: string;
|
|
9
|
-
joinType: TJoinType;
|
|
10
|
-
isNullable?: false;
|
|
11
|
-
};
|
|
16
|
+
OutputJoins & OutputBase<TJoinType>;
|
|
12
17
|
|
|
13
18
|
export type OutputJoins = {
|
|
14
19
|
[k: `_${string}`]: Output;
|
package/src/types/utils.ts
CHANGED
|
@@ -26,3 +26,8 @@ export type IsNullIfNull<
|
|
|
26
26
|
> = TIsNull extends infer T ? (T & TReturn) | null : TIsNull & TReturn;
|
|
27
27
|
|
|
28
28
|
export type ExtendsNull<T> = null extends T ? true : false;
|
|
29
|
+
|
|
30
|
+
export type MaybeNull<T> = T | null;
|
|
31
|
+
export type MaybeArray<T> = T | T[];
|
|
32
|
+
|
|
33
|
+
export type SRecord<T> = Record<string, T>;
|
package/src/utils/invertInput.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { TInputBase } from "~/types/generic-bases";
|
|
2
2
|
|
|
3
|
+
export type InvertedInputRow<TInput extends TInputBase = TInputBase> =
|
|
4
|
+
TInput[keyof TInput][];
|
|
3
5
|
export type InvertedInput<TInput extends TInputBase = TInputBase> = Record<
|
|
4
6
|
keyof TInput,
|
|
5
|
-
TInput
|
|
7
|
+
InvertedInputRow<TInput>
|
|
6
8
|
>;
|
|
7
9
|
|
|
8
10
|
const invertInput = <TInput extends TInputBase>(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Output, OutputRoot } from "~/types";
|
|
2
|
+
|
|
3
|
+
/** Asserts that if `isNullable` is false then item is not null. */
|
|
4
|
+
export const assertIsNullable = (
|
|
5
|
+
item: Record<string, unknown> | null,
|
|
6
|
+
isNullable: false | undefined,
|
|
7
|
+
output: Output | OutputRoot,
|
|
8
|
+
base: string,
|
|
9
|
+
) => {
|
|
10
|
+
if (isNullable === false && item === null) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`Value should exist as output schema defines it as non-nullable. Instead found null in array. Found when joining ${base} onto ${output.base}.`,
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { OutputBase, TInputBase } from "~/types";
|
|
2
|
+
import type { InvertedInput, InvertedInputRow } from "./invertInput";
|
|
3
|
+
import { type MatcherFunc } from "./matcher";
|
|
4
|
+
|
|
5
|
+
export const singleJoinBase = <TValue extends OutputBase<"single">>(
|
|
6
|
+
input: InvertedInputRow,
|
|
7
|
+
matcherFunc: MatcherFunc,
|
|
8
|
+
): Record<string, unknown> | null => {
|
|
9
|
+
const item =
|
|
10
|
+
(input.find(matcherFunc) as TInputBase[string] | undefined) ?? null;
|
|
11
|
+
|
|
12
|
+
return item;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const arrayJoinBase = <TValue extends OutputBase<"single">>(
|
|
16
|
+
input: InvertedInputRow,
|
|
17
|
+
matcherFunc: MatcherFunc,
|
|
18
|
+
): Record<string, unknown>[] => {
|
|
19
|
+
const item = input.filter(matcherFunc);
|
|
20
|
+
|
|
21
|
+
return item;
|
|
22
|
+
};
|
package/src/utils/matcher.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import type { TInputBase } from "~/types/generic-bases";
|
|
1
|
+
import type { TInputBase, TInputBaseJoin } from "~/types/generic-bases";
|
|
2
2
|
import type { InvertedInput } from "./invertInput";
|
|
3
3
|
import type { Output } from "~/types/generic-less";
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export type MatcherFunc = (
|
|
6
|
+
item: TInputBaseJoin | null,
|
|
7
|
+
) => item is TInputBaseJoin;
|
|
8
|
+
|
|
9
|
+
export const createMatcherFunc =
|
|
10
|
+
<TInput extends TInputBase>(
|
|
11
|
+
row: TInput[keyof TInput],
|
|
12
|
+
value: Output,
|
|
13
|
+
): MatcherFunc =>
|
|
14
|
+
(item): item is TInputBaseJoin =>
|
|
15
|
+
!!item &&
|
|
8
16
|
row?.[value.joinsFrom as string] === item?.[value.joinsTo as string];
|