relation-matcher 1.0.13 → 1.1.1

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 (40) hide show
  1. package/.vscode/settings.json +4 -4
  2. package/.yarn/install-state.gz +0 -0
  3. package/.yarn/releases/yarn-4.13.0.cjs +940 -940
  4. package/.yarnrc.yml +3 -3
  5. package/README.md +185 -150
  6. package/dist/src/index.d.ts +10 -5
  7. package/dist/src/index.js +35 -46
  8. package/dist/src/types/generic-bases.d.ts +2 -1
  9. package/dist/src/types/generic-less.d.ts +8 -3
  10. package/dist/src/types/inputs.d.ts +14 -7
  11. package/dist/src/types/return.d.ts +5 -2
  12. package/dist/src/types/typetest.d.ts +1 -0
  13. package/dist/src/types/utils.d.ts +3 -0
  14. package/dist/src/utils/invertInput.d.ts +2 -1
  15. package/dist/src/utils/invertInput.js +4 -9
  16. package/dist/src/utils/isNullable.d.ts +2 -0
  17. package/dist/src/utils/isNullable.js +6 -0
  18. package/dist/src/utils/joins.d.ts +5 -0
  19. package/dist/src/utils/joins.js +9 -0
  20. package/dist/src/utils/keys.js +6 -1
  21. package/dist/src/utils/matcher.d.ts +3 -3
  22. package/dist/src/utils/matcher.js +2 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +36 -36
  25. package/src/index.test.ts +105 -105
  26. package/src/index.ts +124 -151
  27. package/src/testing/file-output.ts +15 -15
  28. package/src/testing/test-data.ts +172 -172
  29. package/src/types/generic-bases.ts +10 -9
  30. package/src/types/generic-less.ts +20 -14
  31. package/src/types/index.ts +5 -5
  32. package/src/types/inputs.ts +36 -30
  33. package/src/types/return.ts +65 -67
  34. package/src/types/typetest.ts +87 -84
  35. package/src/types/utils.ts +33 -28
  36. package/src/utils/invertInput.ts +27 -28
  37. package/src/utils/isNullable.ts +11 -0
  38. package/src/utils/joins.ts +22 -0
  39. package/src/utils/keys.ts +7 -1
  40. package/src/utils/matcher.ts +16 -8
package/.yarnrc.yml CHANGED
@@ -1,3 +1,3 @@
1
- nodeLinker: node-modules
2
-
3
- yarnPath: .yarn/releases/yarn-4.13.0.cjs
1
+ nodeLinker: node-modules
2
+
3
+ yarnPath: .yarn/releases/yarn-4.13.0.cjs
package/README.md CHANGED
@@ -1,150 +1,185 @@
1
- # Relation Matcher
2
-
3
- This is a utility to convert unstructured data from a database into structured relational json data.
4
-
5
- ## Contents
6
-
7
- - [Installation](#installation)
8
- - [Usage](#usage)
9
- - [Data](#data)
10
- - [Schema](#schema)
11
- - [Output](#output)
12
-
13
- ## Installation
14
-
15
- | | Installation Command |
16
- | ---- | --------------------------- |
17
- | npm | `npm i relation-matcher` |
18
- | yarn | `yarn add relation-matcher` |
19
- | pnpm | `pnpm add relation-matcher` |
20
-
21
- ## Usage
22
-
23
- To use the relation matcher you feed in your `data` from your database and a `schema`. For example with drizzle:
24
-
25
- ```ts
26
- import relationMatcher from "relation-matcher";
27
- import db from "~/db/client";
28
-
29
- /* Get your data from the database */
30
- const dbData = await db.select().fro...
31
-
32
- const data /* Output */ = relationMatcher(
33
- dbData /* Data */,
34
- {...} /* Schema */
35
- );
36
-
37
- return data;
38
- ```
39
-
40
- ### `data`
41
-
42
- The `data`'s shape should extend:
43
-
44
- ```ts
45
- Array<Record<string, Record<string, unknown> | null>>;
46
- ```
47
-
48
- #### Example
49
-
50
- The data could have the following type:
51
-
52
- ```ts
53
- {
54
- user: {
55
- id: string;
56
- email: string;
57
- password: string;
58
- ...otherColumns
59
- };
60
-
61
- post: {
62
- id: string;
63
- title: string;
64
- content: string;
65
-
66
- author_id: string; // < Same as user.id
67
- }
68
- }[]
69
- ```
70
-
71
- ### `schema`
72
-
73
- The `schema`'s shape should be:
74
-
75
- ```ts
76
- {
77
- base: "name of table",
78
- id: "distinct column in above table",
79
-
80
- _yourJoinedTableName: {
81
- base: "name of table to join",
82
- id: "distinct column of above table",
83
- joinsFrom: "column of parent table",
84
- joinsTo: "column of current table",
85
- joinType: "single" | "array"
86
- }
87
- }
88
- ```
89
-
90
- > [!NOTE]
91
- > Keys for joins start with an underscore; the final property key will have the leading underscore removed.
92
-
93
- #### Example
94
-
95
- With the above `data`, the `schema`'s shape could be:
96
-
97
- ```ts
98
- {
99
- base: "user",
100
- id: "id",
101
-
102
- _posts: {
103
- base: "post",
104
- id: "id",
105
- joinsFrom: "id",
106
- joinsTo: "author_id",
107
- joinType: "array",
108
- }
109
- }
110
- ```
111
-
112
- ### `output`
113
-
114
- The outputted data will take the shape of your schema.
115
-
116
- #### Example
117
-
118
- For the `data` and `schema` provided above, the `output` will have the following type:
119
-
120
- ```ts
121
- Record<
122
- string, // The id specified in the root of your schema.
123
- {
124
- id: string;
125
- email: string;
126
- password: string;
127
- ...otherColumns;
128
-
129
- posts: Array<
130
- {
131
- id: string;
132
- title: string;
133
- content: string;
134
-
135
- author_id: string;
136
- }
137
- >;
138
- }
139
- >;
140
- ```
141
-
142
- ## Changelog
143
-
144
- ### 1.0.13
145
-
146
- - Added readme.
147
-
148
- ### 1.0.12
149
-
150
- - Fixed type issue where return type would be repeated union of expected `output`.
1
+ # Relation Matcher
2
+
3
+ This is a utility to convert unstructured data from a database into structured relational json data.
4
+
5
+ ## Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Usage](#usage)
9
+ - [Data](#data)
10
+ - [Schema](#schema)
11
+ - [Output](#output)
12
+ - [Changelog](#changelog)
13
+
14
+ ## Installation
15
+
16
+ | | Installation Command |
17
+ | ---- | --------------------------- |
18
+ | npm | `npm i relation-matcher` |
19
+ | yarn | `yarn add relation-matcher` |
20
+ | pnpm | `pnpm add relation-matcher` |
21
+
22
+ ## Usage
23
+
24
+ To use the relation matcher you feed in your `data` from your database and a `schema`. For example with drizzle:
25
+
26
+ ```ts
27
+ // import db from "~/db/client";
28
+ // ...otherImports
29
+ import relationMatcher from "relation-matcher";
30
+
31
+ /* Get your data from the database */
32
+ const dbData = await db.select().fro...
33
+
34
+ const data /* Output */ = relationMatcher(
35
+ dbData /* Data */,
36
+ {...} /* Schema */
37
+ );
38
+
39
+ return data;
40
+ ```
41
+
42
+ ### `data`
43
+
44
+ The `data`'s shape should extend:
45
+
46
+ ```ts
47
+ Array<Record<string, Record<string, unknown> | null>>;
48
+ ```
49
+
50
+ #### Example
51
+
52
+ The data could have the following type:
53
+
54
+ ```ts
55
+ {
56
+ user: {
57
+ id: string;
58
+ email: string;
59
+ password: string;
60
+ ...otherColumns
61
+ };
62
+
63
+ post: {
64
+ id: string;
65
+ title: string;
66
+ content: string;
67
+
68
+ author_id: string; // < Same as user.id
69
+ post_category_id: string; // < Same as post_category.id
70
+ };
71
+
72
+ post_category: {
73
+ id: string;
74
+ name: string;
75
+ } | null;
76
+ }[]
77
+ ```
78
+
79
+ ### `schema`
80
+
81
+ The `schema`'s shape should be:
82
+
83
+ ```ts
84
+ {
85
+ base: "name of table",
86
+ id: "distinct column in above table",
87
+
88
+ _yourJoinedTableName: {
89
+ base: "name of table to join",
90
+ id: "distinct column of above table",
91
+ joinsFrom: "column of parent table",
92
+ joinsTo: "column of current table",
93
+ joinType: "single" | "array",
94
+ isNullable?: false | undefined, // No-op on array join types.
95
+ },
96
+ }
97
+ ```
98
+
99
+ > [!NOTE]
100
+ > Keys for joins start with an underscore; the final property key will have the leading underscore removed.
101
+
102
+ #### Example
103
+
104
+ With the above `data`, the `schema`'s shape could be:
105
+
106
+ ```ts
107
+ {
108
+ base: "user",
109
+ id: "id",
110
+
111
+ _posts: {
112
+ base: "post",
113
+ id: "id",
114
+ joinsFrom: "id",
115
+ joinsTo: "author_id",
116
+ joinType: "array",
117
+
118
+ _postCategory: {
119
+ base: "post_category",
120
+ id: "id",
121
+ joinsFrom: "post_category_id",
122
+ joinsTo: "id",
123
+ joinType: "single",
124
+ isNullable: false,
125
+ },
126
+ },
127
+ }
128
+ ```
129
+
130
+ ### `output`
131
+
132
+ The outputted data will take the shape of your schema.
133
+
134
+ #### Example
135
+
136
+ For the `data` and `schema` provided above, the `output` will have the following type:
137
+
138
+ ```ts
139
+ Record<
140
+ string, // The id specified in the root of your schema.
141
+ {
142
+ id: string;
143
+ email: string;
144
+ password: string;
145
+ ...otherColumns;
146
+
147
+ posts: Array<
148
+ {
149
+ id: string;
150
+ title: string;
151
+ content: string;
152
+
153
+ author_id: string;
154
+ post_category_id: string;
155
+
156
+ postCategory: {
157
+ id: string;
158
+ name: string;
159
+ } // | null;
160
+ // Note this is not nullable as the schema defined it as such.
161
+ }
162
+ >;
163
+ }
164
+ >;
165
+ ```
166
+
167
+ ## Changelog
168
+
169
+ ### 1.1.1
170
+
171
+ - Major refactor.
172
+
173
+ ### 1.1.0
174
+
175
+ - Added ability to mark join as `isNullable: false`. This allows for manual marking of single joins as `NonNullable`.
176
+
177
+ Will throw `Error` if value is not found.
178
+
179
+ ### 1.0.13
180
+
181
+ - Added readme.
182
+
183
+ ### 1.0.12
184
+
185
+ - Fixed type issue where return type would be repeated union of expected `output`.
@@ -1,6 +1,11 @@
1
- import type { TInputBase } from "./types/generic-bases";
2
- import type { RelationMapRoot } from "./types/inputs";
3
- import type { RelationMapperReturnRoot } from "./types/return";
4
- export declare const relationMatcherRoot: <TInput extends TInputBase, TOutputRoot extends RelationMapRoot<TInput>>(inputs: TInput[], output: TOutputRoot) => Record<string, RelationMapperReturnRoot<TInput, TOutputRoot>>;
5
- export declare const relationMatcher: <TInput extends TInputBase, TOutputRoot extends RelationMapRoot<TInput>>(inputs: TInput[], output: TOutputRoot) => Record<string, RelationMapperReturnRoot<TInput, TOutputRoot>>;
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";
6
+ import { type InvertedInput } from "./utils/invertInput";
7
+ export declare const relationMatcherRoot: <TInput extends TInputBase, TOutputRoot extends Root<TInput>>(inputs: TInput[], output: TOutputRoot) => SRecord<ReturnRoot<TInput, TOutputRoot>>;
8
+ export declare const relationMatcher: <TInput extends TInputBase, TOutputRoot extends Root<TInput>>(inputs: TInput[], output: TOutputRoot) => SRecord<ReturnRoot<TInput, TOutputRoot>>;
6
9
  export default relationMatcherRoot;
10
+ declare const joiner: (input: InvertedInput, output: Output | OutputRoot, joiningFrom: SRecord<unknown>) => SRecord<MaybeArray<MaybeNull<TInputBaseJoin>>>;
11
+ export { joiner as relationMatcherJoiner };
package/dist/src/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import invertInput, {} from "./utils/invertInput";
2
+ import { assertIsNullable } from "./utils/isNullable";
3
+ import { arrayJoinBase, singleJoinBase } from "./utils/joins";
2
4
  import { getJoinFinalKey } from "./utils/keys";
3
- import { matcherFunc } from "./utils/matcher";
5
+ import { createMatcherFunc } from "./utils/matcher";
4
6
  export const relationMatcherRoot = (inputs, output) => {
5
7
  if (!inputs.length)
6
8
  return {};
@@ -8,74 +10,61 @@ export const relationMatcherRoot = (inputs, output) => {
8
10
  throw new Error("Input must be an array.");
9
11
  }
10
12
  const invertedInput = invertInput(inputs);
11
- const baseItems = invertedInput[output.base].reduce((final, row) => {
12
- if (!row) {
13
+ const baseItems = invertedInput[output.base].reduce((final, inputRow) => {
14
+ if (!inputRow) {
13
15
  return final;
14
16
  }
15
- const relations = Object.entries(output).reduce((final, [key, value]) => {
16
- if (typeof value === "object") {
17
- const finalKey = getJoinFinalKey(key);
18
- if (value.joinType === "array") {
19
- const baseObjArr = invertedInput[value.base].filter(matcherFunc(row, value));
20
- final[finalKey] = Object.values(baseObjArr.reduce((final, item) => {
21
- if (!item)
22
- return final;
23
- final[item[value.id]] = {
24
- ...item,
25
- ...relationMatcherJoiner(invertedInput, value, item),
26
- };
27
- return final;
28
- }, {}));
29
- }
30
- else {
31
- const item = invertedInput[value.base].find(matcherFunc(row, value)) ?? null;
32
- if (item) {
33
- final[finalKey] = {
34
- ...item,
35
- ...relationMatcherJoiner(invertedInput, value, item),
36
- };
37
- }
38
- }
39
- }
40
- return final;
41
- }, {});
42
- final[row[output.id]] = { ...row, ...relations };
17
+ const relations = joiner(invertedInput, output, inputRow);
18
+ const rowId = inputRow[output.id];
19
+ final[rowId] = {
20
+ ...inputRow,
21
+ ...relations,
22
+ };
43
23
  return final;
44
24
  }, {});
45
25
  return baseItems;
46
26
  };
47
27
  export const relationMatcher = relationMatcherRoot;
48
28
  export default relationMatcherRoot;
49
- const relationMatcherJoiner = (input, output, joiningFrom) => {
29
+ const joiner = (input, output, joiningFrom) => {
50
30
  return Object.entries(output).reduce((final, [key, value]) => {
51
- if (typeof value === "object") {
52
- const finalKey = key.replace(/^_/, "");
53
- const matcherFunc = (item) => item?.[value.joinsTo] === joiningFrom[value.joinsFrom];
54
- if (value.joinType === "array") {
55
- const baseObjArr = input[value.base].filter(matcherFunc);
31
+ if (typeof value !== "object")
32
+ return final;
33
+ const finalKey = getJoinFinalKey(key);
34
+ const matcherFunc = createMatcherFunc(joiningFrom, value);
35
+ const joinType = input[value.base];
36
+ if (!joinType)
37
+ throw new Error(`Input is missing rows for ${value.base}.`);
38
+ switch (value.joinType) {
39
+ case "array": {
40
+ const baseObjArr = arrayJoinBase(joinType, matcherFunc);
56
41
  final[finalKey] = Object.values(baseObjArr.reduce((final, item) => {
57
- if (!item)
58
- return final;
59
42
  const propertyKey = item[value.id];
60
- if (final[propertyKey])
61
- return final;
62
- final[propertyKey] = {
43
+ final[propertyKey] ??= {
63
44
  ...item,
64
- ...relationMatcherJoiner(input, value, item),
45
+ ...joiner(input, value, item),
65
46
  };
66
47
  return final;
67
48
  }, {}));
49
+ break;
68
50
  }
69
- else {
70
- const item = input[value.base].find(matcherFunc) ?? null;
51
+ case "single": {
52
+ const item = singleJoinBase(joinType, matcherFunc);
53
+ assertIsNullable(item, value.isNullable);
71
54
  if (item) {
72
55
  final[finalKey] = {
73
56
  ...item,
74
- ...relationMatcherJoiner(input, value, item),
57
+ ...joiner(input, value, item),
75
58
  };
76
59
  }
60
+ break;
61
+ }
62
+ default: {
63
+ value.joinType;
64
+ throw new Error(`Unhandled join type. Relation matcher can not handle ${value.joinType}.`);
77
65
  }
78
66
  }
79
67
  return final;
80
68
  }, {});
81
69
  };
70
+ export { joiner as relationMatcherJoiner };
@@ -1,4 +1,5 @@
1
1
  import type { RelationMapBase, RelationMapJoiner } from "./inputs";
2
- export type TInputBase = Record<string, Record<string, unknown> | null>;
2
+ export type TInputBaseJoin = Record<string, unknown>;
3
+ export type TInputBase = Record<string, TInputBaseJoin | null>;
3
4
  export type TJoinedFromBase = TInputBase[keyof TInputBase];
4
5
  export type TOutputBase<TInput extends TInputBase, TJoinedFrom extends TJoinedFromBase> = RelationMapBase<TInput, TJoinedFrom> & RelationMapJoiner<TInput, TJoinedFrom>;
@@ -1,11 +1,16 @@
1
- import type { TInputBase } from "./generic-bases";
2
- export type Output<TJoinType extends "single" | "array" = "single" | "array"> = OutputJoins & {
3
- base: keyof TInputBase;
1
+ export type OutputBase<TJoinType extends "single" | "array" = "single" | "array"> = {
2
+ base: string;
4
3
  id: string;
5
4
  joinsTo: string;
6
5
  joinsFrom: string;
7
6
  joinType: TJoinType;
7
+ isNullable?: false;
8
8
  };
9
+ export type OutputRoot = OutputJoins & {
10
+ base: string;
11
+ id: string;
12
+ };
13
+ export type Output<TJoinType extends "single" | "array" = "single" | "array"> = OutputJoins & OutputBase<TJoinType>;
9
14
  export type OutputJoins = {
10
15
  [k: `_${string}`]: Output;
11
16
  };
@@ -5,15 +5,22 @@ export type RelationMapRoot<TInput extends TInputBase> = {
5
5
  id: keyof NonNullable<TInput[k]>;
6
6
  } & RelationMapJoiner<TInput, NonNullable<TInput[k]>>;
7
7
  }[keyof TInput];
8
+ export type IsNullable = {
9
+ false: false;
10
+ undefined: undefined;
11
+ };
8
12
  export type RelationMapBase<TInput extends TInputBase, TJoinedFrom extends TJoinedFromBase> = {
9
13
  [k in keyof TInput]: {
10
- base: k;
11
- /** The id that uniqueness is checked against. Either the primary key of the table, or the id you are joining **to** on a join table. */
12
- id: keyof NonNullable<TInput[k]>;
13
- joinsTo: keyof NonNullable<TInput[k]>;
14
- joinsFrom: keyof NonNullable<TJoinedFrom>;
15
- joinType: "single" | "array";
16
- } & RelationMapJoiner<TInput, TInput[k]>;
14
+ [n in keyof IsNullable]: {
15
+ base: k;
16
+ /** The id that uniqueness is checked against. Either the primary key of the table, or the id you are joining **to** on a join table. */
17
+ id: keyof NonNullable<TInput[k]>;
18
+ joinsTo: keyof NonNullable<TInput[k]>;
19
+ joinsFrom: keyof NonNullable<TJoinedFrom>;
20
+ joinType: "single" | "array";
21
+ isNullable?: IsNullable[n];
22
+ };
23
+ }[keyof IsNullable] & RelationMapJoiner<TInput, TInput[k]>;
17
24
  }[keyof TInput];
18
25
  export type RelationMapJoiner<TInput extends TInputBase, TJoinedFrom extends TJoinedFromBase> = {
19
26
  [k: `_${string}`]: RelationMapBase<TInput, TJoinedFrom> & RelationMapJoiner<TInput, TJoinedFrom>;
@@ -1,13 +1,16 @@
1
1
  import type { TInputBase, TJoinedFromBase, TOutputBase } from "./generic-bases";
2
2
  import type { RelationMapRoot } from "./inputs";
3
3
  import type { ExtendsNull, NoUnderscore, Simplify } from "./utils";
4
- export type RelationMapperReturnRoot<TInput extends TInputBase, TOutput extends RelationMapRoot<TInput>> = Simplify<ExtendsNull<TInput[TOutput["base"]]> extends true ? (TInput[TOutput["base"]] & RelationMapperReturnJoinRoot<TInput, TOutput, TOutput["base"]>) | null : TInput[TOutput["base"]] & RelationMapperReturnJoinRoot<TInput, TOutput, TOutput["base"]>>;
4
+ type JoinedRoot<TInput extends TInputBase, TOutput extends RelationMapRoot<TInput>> = TInput[TOutput["base"]] & RelationMapperReturnJoinRoot<TInput, TOutput, TOutput["base"]>;
5
+ export type RelationMapperReturnRoot<TInput extends TInputBase, TOutput extends RelationMapRoot<TInput>> = Simplify<ExtendsNull<TInput[TOutput["base"]]> extends true ? JoinedRoot<TInput, TOutput> | null : JoinedRoot<TInput, TOutput>>;
5
6
  export type RelationMapperReturnJoinRoot<TInput extends TInputBase, TOutput extends RelationMapRoot<TInput>, TInputKey extends keyof TInput> = {
6
7
  [outputKey in NoUnderscore<keyof TOutput>]: RelationMapperReturn<TInput, TInput[TInputKey], TOutput[`_${outputKey}`]>;
7
8
  };
9
+ export type Joined<TInput extends TInputBase, TJoinedFrom extends TJoinedFromBase, TOutput extends TOutputBase<TInput, TJoinedFrom>> = Simplify<TInput[TOutput["base"]] & RelationMapperReturnJoin<TInput, TJoinedFrom, TOutput>>;
8
10
  /** Fill current object with value specified in base */
9
- export type RelationMapperReturn<TInput extends TInputBase, TJoinedFrom extends TJoinedFromBase, TOutput extends TOutputBase<TInput, TJoinedFrom>> = TOutput["joinType"] extends "array" ? Array<Simplify<TInput[TOutput["base"]] & RelationMapperReturnJoin<TInput, TJoinedFrom, TOutput>>> : ExtendsNull<TInput[TOutput["base"]]> extends true ? Simplify<TInput[TOutput["base"]] & RelationMapperReturnJoin<TInput, TJoinedFrom, TOutput>> | null : Simplify<TInput[TOutput["base"]] & RelationMapperReturnJoin<TInput, TJoinedFrom, TOutput>>;
11
+ export type RelationMapperReturn<TInput extends TInputBase, TJoinedFrom extends TJoinedFromBase, TOutput extends TOutputBase<TInput, TJoinedFrom>> = TOutput["joinType"] extends "array" ? Array<Joined<TInput, TJoinedFrom, TOutput>> : ExtendsNull<TInput[TOutput["base"]]> extends true ? TOutput["isNullable"] extends false ? Joined<TInput, TJoinedFrom, TOutput> : Joined<TInput, TJoinedFrom, TOutput> | null : Joined<TInput, TJoinedFrom, TOutput>;
10
12
  /** Takes underscored values (joins) and replaces their values with the actual output. */
11
13
  export type RelationMapperReturnJoin<TInput extends TInputBase, TJoinedFrom extends TJoinedFromBase, TOutput extends TOutputBase<TInput, TJoinedFrom>> = {
12
14
  [k in NoUnderscore<keyof TOutput>]: RelationMapperReturn<TInput, TJoinedFrom, TOutput[`_${k}`]>;
13
15
  };
16
+ export {};
@@ -24,6 +24,7 @@ export type A = DeepSimplify<RelationMapperReturnRoot<{
24
24
  joinsTo: "team_id";
25
25
  joinsFrom: "id";
26
26
  joinType: "single";
27
+ isNullable: false;
27
28
  _team: {
28
29
  base: "team";
29
30
  id: "id";
@@ -11,3 +11,6 @@ export type DeepNonNullable<T extends object> = {
11
11
  export type ArrayOrSingle<T, TType extends "single" | "array"> = TType extends "array" ? Array<T> : T;
12
12
  export type IsNullIfNull<TIsNull extends object | null, TReturn extends object | never = never> = TIsNull extends infer T ? (T & TReturn) | null : TIsNull & TReturn;
13
13
  export type ExtendsNull<T> = null extends T ? true : false;
14
+ export type MaybeNull<T> = T | null;
15
+ export type MaybeArray<T> = T | T[];
16
+ export type SRecord<T> = Record<string, T>;
@@ -1,4 +1,5 @@
1
1
  import type { TInputBase } from "~/types/generic-bases";
2
- export type InvertedInput<TInput extends TInputBase = TInputBase> = Record<keyof TInput, TInput[keyof TInput][]>;
2
+ export type InvertedInputRow<TInput extends TInputBase = TInputBase> = TInput[keyof TInput][];
3
+ export type InvertedInput<TInput extends TInputBase = TInputBase> = Record<keyof TInput, InvertedInputRow<TInput>>;
3
4
  declare const invertInput: <TInput extends TInputBase>(inputs: TInputBase[]) => InvertedInput<TInput>;
4
5
  export default invertInput;
@@ -1,15 +1,10 @@
1
1
  const invertInput = (inputs) => {
2
2
  const invertedInput = inputs.reduce((final, row) => {
3
3
  Object.entries(row).forEach(([header, value]) => {
4
- if (final[header]) {
5
- if (value)
6
- final[header].push(value);
7
- }
8
- else {
9
- final[header] = value
10
- ? [value]
11
- : [];
12
- }
4
+ final[header] ??= [];
5
+ // Instantiated above.
6
+ if (value)
7
+ final[header].push(value);
13
8
  });
14
9
  return final;
15
10
  }, {});
@@ -0,0 +1,2 @@
1
+ /** Asserts that if `isNullable` is false then item is not null. */
2
+ export declare const assertIsNullable: (item: Record<string, unknown> | null, isNullable: false | undefined) => void;
@@ -0,0 +1,6 @@
1
+ /** Asserts that if `isNullable` is false then item is not null. */
2
+ export const assertIsNullable = (item, isNullable) => {
3
+ if (isNullable === false && item === null) {
4
+ throw new Error("Value should exist as output schema defines it as nullable. Instead found null in array");
5
+ }
6
+ };
@@ -0,0 +1,5 @@
1
+ import type { OutputBase } from "~/types";
2
+ import type { InvertedInputRow } from "./invertInput";
3
+ import { type MatcherFunc } from "./matcher";
4
+ export declare const singleJoinBase: <TValue extends OutputBase<"single">>(input: InvertedInputRow, matcherFunc: MatcherFunc) => Record<string, unknown> | null;
5
+ export declare const arrayJoinBase: <TValue extends OutputBase<"single">>(input: InvertedInputRow, matcherFunc: MatcherFunc) => Record<string, unknown>[];
@@ -0,0 +1,9 @@
1
+ import {} from "./matcher";
2
+ export const singleJoinBase = (input, matcherFunc) => {
3
+ const item = input.find(matcherFunc) ?? null;
4
+ return item;
5
+ };
6
+ export const arrayJoinBase = (input, matcherFunc) => {
7
+ const item = input.filter(matcherFunc);
8
+ return item;
9
+ };
@@ -1 +1,6 @@
1
- export const getJoinFinalKey = (key) => key.substring(1);
1
+ export const getJoinFinalKey = (key) => {
2
+ if (key[0] !== "_") {
3
+ throw new Error("Joining key must start with underscore.");
4
+ }
5
+ return key.substring(1);
6
+ };