@loomcore/api 0.1.76 → 0.1.77

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.
@@ -146,30 +146,7 @@ export const getPostgresTestSchema = (config) => {
146
146
  }
147
147
  });
148
148
  migrations.push({
149
- name: '00000000000105_6_schema-policies',
150
- up: async ({ context: pool }) => {
151
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
152
- await pool.query(`
153
- CREATE TABLE IF NOT EXISTS "policies" (
154
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
155
- ${orgColumnDef}
156
- "amount" NUMERIC NOT NULL,
157
- "frequency" VARCHAR NOT NULL,
158
- "_created" TIMESTAMPTZ NOT NULL,
159
- "_createdBy" INTEGER NOT NULL,
160
- "_updated" TIMESTAMPTZ NOT NULL,
161
- "_updatedBy" INTEGER NOT NULL,
162
- "_deleted" TIMESTAMPTZ,
163
- "_deletedBy" INTEGER
164
- )
165
- `);
166
- },
167
- down: async ({ context: pool }) => {
168
- await pool.query('DROP TABLE IF EXISTS "policies"');
169
- }
170
- });
171
- migrations.push({
172
- name: '00000000000105_7_schema-clients',
149
+ name: '00000000000105_6_schema-clients',
173
150
  up: async ({ context: pool }) => {
174
151
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
175
152
  await pool.query(`
@@ -195,29 +172,28 @@ export const getPostgresTestSchema = (config) => {
195
172
  }
196
173
  });
197
174
  migrations.push({
198
- name: '00000000000105_8_schema-clients-policies',
175
+ name: '00000000000105_7_schema-policies',
199
176
  up: async ({ context: pool }) => {
200
177
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
201
178
  await pool.query(`
202
- CREATE TABLE IF NOT EXISTS clients_policies (
179
+ CREATE TABLE IF NOT EXISTS "policies" (
203
180
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
204
181
  ${orgColumnDef}
205
182
  "client_id" INTEGER NOT NULL,
206
- "policy_id" INTEGER NOT NULL,
183
+ "amount" NUMERIC NOT NULL,
184
+ "frequency" VARCHAR NOT NULL,
207
185
  "_created" TIMESTAMPTZ NOT NULL,
208
186
  "_createdBy" INTEGER NOT NULL,
209
187
  "_updated" TIMESTAMPTZ NOT NULL,
210
188
  "_updatedBy" INTEGER NOT NULL,
211
189
  "_deleted" TIMESTAMPTZ,
212
190
  "_deletedBy" INTEGER,
213
- CONSTRAINT fk_clients_policies_client_id FOREIGN KEY ("client_id") REFERENCES clients("_id") ON DELETE CASCADE,
214
- CONSTRAINT fk_clients_policies_policy_id FOREIGN KEY ("policy_id") REFERENCES policies("_id") ON DELETE CASCADE,
215
- CONSTRAINT uk_clients_policies_client_policy UNIQUE ("client_id", "policy_id")
191
+ CONSTRAINT fk_policies_client_id FOREIGN KEY ("client_id") REFERENCES clients("_id") ON DELETE CASCADE
216
192
  )
217
193
  `);
218
194
  },
219
195
  down: async ({ context: pool }) => {
220
- await pool.query('DROP TABLE IF EXISTS "clients_policies"');
196
+ await pool.query('DROP TABLE IF EXISTS "policies"');
221
197
  }
222
198
  });
223
199
  migrations.push({
@@ -42,6 +42,7 @@ export declare const clientReportsSchema: import("@sinclair/typebox").TObject<{
42
42
  }>>;
43
43
  }>>;
44
44
  policies: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
45
+ client_id: import("@sinclair/typebox").TNumber;
45
46
  amount: import("@sinclair/typebox").TNumber;
46
47
  frequency: import("@sinclair/typebox").TString;
47
48
  agents: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
@@ -1,11 +1,13 @@
1
1
  import type { IAuditable, IEntity } from "@loomcore/common/models";
2
2
  import { IAgentModel } from "./agent.model.js";
3
3
  export interface IPolicyModel extends IEntity, IAuditable {
4
+ client_id: number;
4
5
  amount: number;
5
6
  frequency: string;
6
7
  agents?: IAgentModel[];
7
8
  }
8
9
  export declare const policySchema: import("@sinclair/typebox").TObject<{
10
+ client_id: import("@sinclair/typebox").TNumber;
9
11
  amount: import("@sinclair/typebox").TNumber;
10
12
  frequency: import("@sinclair/typebox").TString;
11
13
  agents: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
@@ -2,6 +2,7 @@ import { entityUtils } from "@loomcore/common/utils";
2
2
  import { Type } from "@sinclair/typebox";
3
3
  import { agentSchema } from "./agent.model.js";
4
4
  export const policySchema = Type.Object({
5
+ client_id: Type.Number(),
5
6
  amount: Type.Number(),
6
7
  frequency: Type.String(),
7
8
  agents: Type.Optional(Type.Array(agentSchema))
@@ -8,13 +8,17 @@ export function buildJoinClauses(operations, mainTableName) {
8
8
  const processedAliases = new Set();
9
9
  const aliasesToSkip = new Set();
10
10
  for (const operation of operations) {
11
- if (operation instanceof JoinThroughMany && operation.localField.includes('.')) {
11
+ if ((operation instanceof JoinThroughMany || operation instanceof JoinMany) && operation.localField.includes('.')) {
12
12
  const [tableAlias] = operation.localField.split('.');
13
13
  const referencedJoinThroughMany = operations
14
14
  .filter(op => op instanceof JoinThroughMany)
15
15
  .find(jtm => jtm.as === tableAlias);
16
- if (referencedJoinThroughMany) {
17
- const referencedIndex = operations.indexOf(referencedJoinThroughMany);
16
+ const referencedJoinMany = operations
17
+ .filter(op => op instanceof JoinMany)
18
+ .find(jm => jm.as === tableAlias);
19
+ if (referencedJoinThroughMany || referencedJoinMany) {
20
+ const referencedJoin = referencedJoinThroughMany || referencedJoinMany;
21
+ const referencedIndex = operations.indexOf(referencedJoin);
18
22
  const currentIndex = operations.indexOf(operation);
19
23
  if (referencedIndex < currentIndex) {
20
24
  aliasesToSkip.add(tableAlias);
@@ -90,58 +94,104 @@ export function buildJoinClauses(operations, mainTableName) {
90
94
  const referencedJoinThroughMany = operations
91
95
  .filter(op => op instanceof JoinThroughMany)
92
96
  .find(jtm => jtm.as === tableAlias);
93
- if (referencedJoinThroughMany) {
94
- const referencedIndex = operations.indexOf(referencedJoinThroughMany);
97
+ const referencedJoinMany = operations
98
+ .filter(op => op instanceof JoinMany)
99
+ .find(jm => jm.as === tableAlias);
100
+ if (referencedJoinThroughMany || referencedJoinMany) {
101
+ const referencedJoin = referencedJoinThroughMany || referencedJoinMany;
102
+ const referencedIndex = operations.indexOf(referencedJoin);
95
103
  const currentIndex = operations.indexOf(operation);
96
104
  if (referencedIndex < currentIndex) {
97
105
  shouldSkipOriginalJoin = true;
98
106
  const originalJoinWasSkipped = aliasesToSkip.has(tableAlias);
99
- const originalJoin = referencedJoinThroughMany;
100
107
  const mainTableRef = mainTableName ? `"${mainTableName}"."_id"` : '"_id"';
101
108
  const isAgentsJoin = operation.from === 'agents';
109
+ const isJoinMany = referencedJoinMany !== undefined;
102
110
  if (isAgentsJoin) {
103
111
  if (originalJoinWasSkipped) {
104
- joinClauses += ` LEFT JOIN LATERAL (
105
- SELECT COALESCE(
106
- JSON_AGG(
107
- policy_elem.value || jsonb_build_object(
108
- 'agents',
109
- COALESCE(
110
- (SELECT JSON_AGG(agent_elem.value || jsonb_build_object('agent_person', person_data.value))
111
- FROM jsonb_array_elements(COALESCE(agents_agg.agents, '[]'::json)::jsonb) AS agent_elem
112
- LEFT JOIN LATERAL (
113
- SELECT row_to_json(p) AS value
114
- FROM "persons" AS p
115
- WHERE p."_id" = (agent_elem.value->>'person_id')::integer
116
- AND p."_deleted" IS NULL
117
- LIMIT 1
118
- ) AS person_data ON true),
119
- '[]'::json
112
+ if (isJoinMany) {
113
+ joinClauses += ` LEFT JOIN LATERAL (
114
+ SELECT COALESCE(
115
+ JSON_AGG(
116
+ policy_elem.value || jsonb_build_object(
117
+ 'agents',
118
+ COALESCE(
119
+ (SELECT JSON_AGG(agent_elem.value || jsonb_build_object('agent_person', person_data.value))
120
+ FROM jsonb_array_elements(COALESCE(agents_agg.agents, '[]'::json)::jsonb) AS agent_elem
121
+ LEFT JOIN LATERAL (
122
+ SELECT row_to_json(p) AS value
123
+ FROM "persons" AS p
124
+ WHERE p."_id" = (agent_elem.value->>'person_id')::integer
125
+ AND p."_deleted" IS NULL
126
+ LIMIT 1
127
+ ) AS person_data ON true),
128
+ '[]'::json
129
+ )
120
130
  )
121
- )
122
- ),
123
- '[]'::json
124
- ) AS aggregated
125
- FROM (
126
- SELECT COALESCE(JSON_AGG(row_to_json(${tableAlias})), '[]'::json) AS aggregated
127
- FROM "${originalJoin.through}"
128
- INNER JOIN "${originalJoin.from}" AS ${tableAlias}
129
- ON ${tableAlias}."${originalJoin.foreignField}" = "${originalJoin.through}"."${originalJoin.throughForeignField}"
130
- WHERE "${originalJoin.through}"."${originalJoin.throughLocalField}" = ${mainTableRef}
131
- AND "${originalJoin.through}"."_deleted" IS NULL
132
- AND ${tableAlias}."_deleted" IS NULL
133
- ) AS policies_subquery
134
- CROSS JOIN LATERAL jsonb_array_elements(policies_subquery.aggregated::jsonb) AS policy_elem
135
- LEFT JOIN LATERAL (
136
- SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
137
- FROM "${operation.through}"
138
- INNER JOIN "${operation.from}" AS ${operation.as}
139
- ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
140
- WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
141
- AND "${operation.through}"."_deleted" IS NULL
142
- AND ${operation.as}."_deleted" IS NULL
143
- ) AS agents_agg ON true
144
- ) AS ${operation.as} ON true`;
131
+ ),
132
+ '[]'::json
133
+ ) AS aggregated
134
+ FROM (
135
+ SELECT COALESCE(JSON_AGG(row_to_json(${tableAlias})), '[]'::json) AS aggregated
136
+ FROM "${referencedJoinMany.from}" AS ${tableAlias}
137
+ WHERE ${tableAlias}."${referencedJoinMany.foreignField}" = ${mainTableRef}
138
+ AND ${tableAlias}."_deleted" IS NULL
139
+ ) AS policies_subquery
140
+ CROSS JOIN LATERAL jsonb_array_elements(policies_subquery.aggregated::jsonb) AS policy_elem
141
+ LEFT JOIN LATERAL (
142
+ SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
143
+ FROM "${operation.through}"
144
+ INNER JOIN "${operation.from}" AS ${operation.as}
145
+ ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
146
+ WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
147
+ AND "${operation.through}"."_deleted" IS NULL
148
+ AND ${operation.as}."_deleted" IS NULL
149
+ ) AS agents_agg ON true
150
+ ) AS ${operation.as} ON true`;
151
+ }
152
+ else {
153
+ joinClauses += ` LEFT JOIN LATERAL (
154
+ SELECT COALESCE(
155
+ JSON_AGG(
156
+ policy_elem.value || jsonb_build_object(
157
+ 'agents',
158
+ COALESCE(
159
+ (SELECT JSON_AGG(agent_elem.value || jsonb_build_object('agent_person', person_data.value))
160
+ FROM jsonb_array_elements(COALESCE(agents_agg.agents, '[]'::json)::jsonb) AS agent_elem
161
+ LEFT JOIN LATERAL (
162
+ SELECT row_to_json(p) AS value
163
+ FROM "persons" AS p
164
+ WHERE p."_id" = (agent_elem.value->>'person_id')::integer
165
+ AND p."_deleted" IS NULL
166
+ LIMIT 1
167
+ ) AS person_data ON true),
168
+ '[]'::json
169
+ )
170
+ )
171
+ ),
172
+ '[]'::json
173
+ ) AS aggregated
174
+ FROM (
175
+ SELECT COALESCE(JSON_AGG(row_to_json(${tableAlias})), '[]'::json) AS aggregated
176
+ FROM "${referencedJoinThroughMany.through}"
177
+ INNER JOIN "${referencedJoinThroughMany.from}" AS ${tableAlias}
178
+ ON ${tableAlias}."${referencedJoinThroughMany.foreignField}" = "${referencedJoinThroughMany.through}"."${referencedJoinThroughMany.throughForeignField}"
179
+ WHERE "${referencedJoinThroughMany.through}"."${referencedJoinThroughMany.throughLocalField}" = ${mainTableRef}
180
+ AND "${referencedJoinThroughMany.through}"."_deleted" IS NULL
181
+ AND ${tableAlias}."_deleted" IS NULL
182
+ ) AS policies_subquery
183
+ CROSS JOIN LATERAL jsonb_array_elements(policies_subquery.aggregated::jsonb) AS policy_elem
184
+ LEFT JOIN LATERAL (
185
+ SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
186
+ FROM "${operation.through}"
187
+ INNER JOIN "${operation.from}" AS ${operation.as}
188
+ ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
189
+ WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
190
+ AND "${operation.through}"."_deleted" IS NULL
191
+ AND ${operation.as}."_deleted" IS NULL
192
+ ) AS agents_agg ON true
193
+ ) AS ${operation.as} ON true`;
194
+ }
145
195
  }
146
196
  else {
147
197
  joinClauses += ` LEFT JOIN LATERAL (
@@ -180,33 +230,61 @@ export function buildJoinClauses(operations, mainTableName) {
180
230
  }
181
231
  else {
182
232
  if (originalJoinWasSkipped) {
183
- joinClauses += ` LEFT JOIN LATERAL (
184
- SELECT COALESCE(
185
- JSON_AGG(
186
- policy_elem.value || jsonb_build_object('agents', COALESCE(agents_agg.agents, '[]'::json))
187
- ),
188
- '[]'::json
189
- ) AS aggregated
190
- FROM (
191
- SELECT COALESCE(JSON_AGG(row_to_json(${tableAlias})), '[]'::json) AS aggregated
192
- FROM "${originalJoin.through}"
193
- INNER JOIN "${originalJoin.from}" AS ${tableAlias}
194
- ON ${tableAlias}."${originalJoin.foreignField}" = "${originalJoin.through}"."${originalJoin.throughForeignField}"
195
- WHERE "${originalJoin.through}"."${originalJoin.throughLocalField}" = ${mainTableRef}
196
- AND "${originalJoin.through}"."_deleted" IS NULL
197
- AND ${tableAlias}."_deleted" IS NULL
198
- ) AS policies_subquery
199
- CROSS JOIN LATERAL jsonb_array_elements(policies_subquery.aggregated::jsonb) AS policy_elem
200
- LEFT JOIN LATERAL (
201
- SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
202
- FROM "${operation.through}"
203
- INNER JOIN "${operation.from}" AS ${operation.as}
204
- ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
205
- WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
206
- AND "${operation.through}"."_deleted" IS NULL
207
- AND ${operation.as}."_deleted" IS NULL
208
- ) AS agents_agg ON true
209
- ) AS ${operation.as} ON true`;
233
+ if (isJoinMany) {
234
+ joinClauses += ` LEFT JOIN LATERAL (
235
+ SELECT COALESCE(
236
+ JSON_AGG(
237
+ policy_elem.value || jsonb_build_object('agents', COALESCE(agents_agg.agents, '[]'::json))
238
+ ),
239
+ '[]'::json
240
+ ) AS aggregated
241
+ FROM (
242
+ SELECT COALESCE(JSON_AGG(row_to_json(${tableAlias})), '[]'::json) AS aggregated
243
+ FROM "${referencedJoinMany.from}" AS ${tableAlias}
244
+ WHERE ${tableAlias}."${referencedJoinMany.foreignField}" = ${mainTableRef}
245
+ AND ${tableAlias}."_deleted" IS NULL
246
+ ) AS policies_subquery
247
+ CROSS JOIN LATERAL jsonb_array_elements(policies_subquery.aggregated::jsonb) AS policy_elem
248
+ LEFT JOIN LATERAL (
249
+ SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
250
+ FROM "${operation.through}"
251
+ INNER JOIN "${operation.from}" AS ${operation.as}
252
+ ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
253
+ WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
254
+ AND "${operation.through}"."_deleted" IS NULL
255
+ AND ${operation.as}."_deleted" IS NULL
256
+ ) AS agents_agg ON true
257
+ ) AS ${operation.as} ON true`;
258
+ }
259
+ else {
260
+ joinClauses += ` LEFT JOIN LATERAL (
261
+ SELECT COALESCE(
262
+ JSON_AGG(
263
+ policy_elem.value || jsonb_build_object('agents', COALESCE(agents_agg.agents, '[]'::json))
264
+ ),
265
+ '[]'::json
266
+ ) AS aggregated
267
+ FROM (
268
+ SELECT COALESCE(JSON_AGG(row_to_json(${tableAlias})), '[]'::json) AS aggregated
269
+ FROM "${referencedJoinThroughMany.through}"
270
+ INNER JOIN "${referencedJoinThroughMany.from}" AS ${tableAlias}
271
+ ON ${tableAlias}."${referencedJoinThroughMany.foreignField}" = "${referencedJoinThroughMany.through}"."${referencedJoinThroughMany.throughForeignField}"
272
+ WHERE "${referencedJoinThroughMany.through}"."${referencedJoinThroughMany.throughLocalField}" = ${mainTableRef}
273
+ AND "${referencedJoinThroughMany.through}"."_deleted" IS NULL
274
+ AND ${tableAlias}."_deleted" IS NULL
275
+ ) AS policies_subquery
276
+ CROSS JOIN LATERAL jsonb_array_elements(policies_subquery.aggregated::jsonb) AS policy_elem
277
+ LEFT JOIN LATERAL (
278
+ SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS agents
279
+ FROM "${operation.through}"
280
+ INNER JOIN "${operation.from}" AS ${operation.as}
281
+ ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
282
+ WHERE "${operation.through}"."${operation.throughLocalField}" = (policy_elem.value->>'${columnName}')::integer
283
+ AND "${operation.through}"."_deleted" IS NULL
284
+ AND ${operation.as}."_deleted" IS NULL
285
+ ) AS agents_agg ON true
286
+ ) AS ${operation.as} ON true`;
287
+ }
210
288
  }
211
289
  else {
212
290
  joinClauses += ` LEFT JOIN LATERAL (
@@ -39,7 +39,9 @@ export async function buildSelectClause(client, mainTableName, mainTableAlias, o
39
39
  for (const joinThroughMany of joinThroughManyOperations) {
40
40
  if (joinThroughMany.localField.includes('.')) {
41
41
  const [tableAlias] = joinThroughMany.localField.split('.');
42
- const referencedJoin = joinThroughManyOperations.find(j => j.as === tableAlias);
42
+ const referencedJoinThroughMany = joinThroughManyOperations.find(j => j.as === tableAlias);
43
+ const referencedJoinMany = joinManyOperations.find(j => j.as === tableAlias);
44
+ const referencedJoin = referencedJoinThroughMany || referencedJoinMany;
43
45
  if (referencedJoin) {
44
46
  const referencedIndex = operations.indexOf(referencedJoin);
45
47
  const currentIndex = operations.indexOf(joinThroughMany);
@@ -49,6 +51,15 @@ export async function buildSelectClause(client, mainTableName, mainTableAlias, o
49
51
  }
50
52
  }
51
53
  }
54
+ for (const joinMany of joinManyOperations) {
55
+ if (replacedJoins.has(joinMany.as)) {
56
+ const replacingAlias = replacedJoins.get(joinMany.as);
57
+ joinSelects.push(`${replacingAlias}.aggregated AS "${joinMany.as}"`);
58
+ }
59
+ else {
60
+ joinSelects.push(`${joinMany.as}.aggregated AS "${joinMany.as}"`);
61
+ }
62
+ }
52
63
  for (const joinThroughMany of joinThroughManyOperations) {
53
64
  if (replacedJoins.has(joinThroughMany.as)) {
54
65
  const replacingAlias = replacedJoins.get(joinThroughMany.as);
@@ -149,7 +149,9 @@ export function transformJoinResults(rows, operations) {
149
149
  for (const joinThroughMany of joinThroughManyOperations) {
150
150
  if (joinThroughMany.localField.includes('.')) {
151
151
  const [tableAlias] = joinThroughMany.localField.split('.');
152
- const referencedJoin = joinThroughManyOperations.find(j => j.as === tableAlias);
152
+ const referencedJoinThroughMany = joinThroughManyOperations.find(j => j.as === tableAlias);
153
+ const referencedJoinMany = joinManyOperations.find(j => j.as === tableAlias);
154
+ const referencedJoin = referencedJoinThroughMany || referencedJoinMany;
153
155
  if (referencedJoin) {
154
156
  const referencedIndex = operations.indexOf(referencedJoin);
155
157
  const currentIndex = operations.indexOf(joinThroughMany);
@@ -159,6 +161,21 @@ export function transformJoinResults(rows, operations) {
159
161
  }
160
162
  }
161
163
  }
164
+ for (const joinMany of joinManyOperations) {
165
+ if (joinMany.localField.includes('.')) {
166
+ const [tableAlias] = joinMany.localField.split('.');
167
+ const referencedJoinThroughMany = joinThroughManyOperations.find(j => j.as === tableAlias);
168
+ const referencedJoinMany = joinManyOperations.find(j => j.as === tableAlias);
169
+ const referencedJoin = referencedJoinThroughMany || referencedJoinMany;
170
+ if (referencedJoin) {
171
+ const referencedIndex = operations.indexOf(referencedJoin);
172
+ const currentIndex = operations.indexOf(joinMany);
173
+ if (referencedIndex < currentIndex) {
174
+ replacedJoins.set(tableAlias, joinMany.as);
175
+ }
176
+ }
177
+ }
178
+ }
162
179
  for (const joinThroughMany of joinThroughManyOperations) {
163
180
  if (replacedJoins.has(joinThroughMany.as)) {
164
181
  continue;
@@ -180,11 +197,14 @@ export function transformJoinResults(rows, operations) {
180
197
  const relatedJoin = joinOperations.find(j => j.as === tableAlias);
181
198
  const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
182
199
  const relatedJoinThroughMany = joinThroughManyOperations.find(j => j.as === tableAlias);
200
+ const relatedJoinMany = joinManyOperations.find(j => j.as === tableAlias);
183
201
  if ((relatedJoin && transformed[relatedJoin.as]) ||
184
202
  (relatedJoinThrough && transformed[relatedJoinThrough.as]) ||
185
- (relatedJoinThroughMany && transformed[relatedJoinThroughMany.as])) {
203
+ (relatedJoinThroughMany && transformed[relatedJoinThroughMany.as]) ||
204
+ (relatedJoinMany && transformed[relatedJoinMany.as])) {
186
205
  const targetAlias = relatedJoin ? relatedJoin.as :
187
- (relatedJoinThrough ? relatedJoinThrough.as : relatedJoinThroughMany.as);
206
+ (relatedJoinThrough ? relatedJoinThrough.as :
207
+ (relatedJoinThroughMany ? relatedJoinThroughMany.as : relatedJoinMany.as));
188
208
  if (replacedJoins.get(targetAlias) === joinThroughMany.as) {
189
209
  transformed[targetAlias] = parsedValue;
190
210
  }
@@ -204,6 +224,50 @@ export function transformJoinResults(rows, operations) {
204
224
  transformed[aliasToUse] = parsedValue;
205
225
  }
206
226
  }
227
+ for (const joinMany of joinManyOperations) {
228
+ if (replacedJoins.has(joinMany.as)) {
229
+ continue;
230
+ }
231
+ const originalAlias = Array.from(replacedJoins.entries()).find(([_, replacing]) => replacing === joinMany.as)?.[0];
232
+ const aliasToUse = originalAlias || joinMany.as;
233
+ const jsonValue = row[aliasToUse];
234
+ let parsedValue;
235
+ if (jsonValue !== null && jsonValue !== undefined) {
236
+ parsedValue = typeof jsonValue === 'string'
237
+ ? JSON.parse(jsonValue)
238
+ : jsonValue;
239
+ }
240
+ else {
241
+ parsedValue = [];
242
+ }
243
+ if (joinMany.localField.includes('.')) {
244
+ const [tableAlias] = joinMany.localField.split('.');
245
+ const relatedJoin = joinOperations.find(j => j.as === tableAlias);
246
+ const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
247
+ const relatedJoinThroughMany = joinThroughManyOperations.find(j => j.as === tableAlias);
248
+ const relatedJoinManyOther = joinManyOperations.find(j => j.as === tableAlias);
249
+ if ((relatedJoin && transformed[relatedJoin.as]) ||
250
+ (relatedJoinThrough && transformed[relatedJoinThrough.as]) ||
251
+ (relatedJoinThroughMany && transformed[relatedJoinThroughMany.as]) ||
252
+ (relatedJoinManyOther && transformed[relatedJoinManyOther.as])) {
253
+ const targetAlias = relatedJoin ? relatedJoin.as :
254
+ (relatedJoinThrough ? relatedJoinThrough.as :
255
+ (relatedJoinThroughMany ? relatedJoinThroughMany.as : relatedJoinManyOther.as));
256
+ if (replacedJoins.get(targetAlias) === joinMany.as) {
257
+ transformed[targetAlias] = parsedValue;
258
+ }
259
+ else {
260
+ transformed[targetAlias][joinMany.as] = parsedValue;
261
+ }
262
+ }
263
+ else {
264
+ transformed[aliasToUse] = parsedValue;
265
+ }
266
+ }
267
+ else {
268
+ transformed[aliasToUse] = parsedValue;
269
+ }
270
+ }
207
271
  return transformed;
208
272
  });
209
273
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.1.76",
3
+ "version": "0.1.77",
4
4
  "private": false,
5
5
  "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
6
6
  "scripts": {