@loomcore/api 0.1.70 → 0.1.73
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/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +123 -2
- package/dist/databases/mongo-db/utils/convert-operations-to-pipeline.util.js +190 -2
- package/dist/databases/operations/__tests__/models/agent.model.d.ts +25 -0
- package/dist/databases/operations/__tests__/models/agent.model.js +8 -0
- package/dist/databases/operations/__tests__/models/client-report.model.d.ts +22 -2
- package/dist/databases/operations/__tests__/models/client-report.model.js +3 -1
- package/dist/databases/operations/__tests__/models/district.model.d.ts +12 -0
- package/dist/databases/operations/__tests__/models/district.model.js +7 -0
- package/dist/databases/operations/__tests__/models/person.model.d.ts +2 -0
- package/dist/databases/operations/__tests__/models/school.model.d.ts +12 -0
- package/dist/databases/operations/__tests__/models/school.model.js +7 -0
- package/dist/databases/operations/__tests__/models/state.model.d.ts +8 -0
- package/dist/databases/operations/__tests__/models/state.model.js +6 -0
- package/dist/databases/operations/index.d.ts +1 -0
- package/dist/databases/operations/index.js +1 -0
- package/dist/databases/operations/join-through-many.operation.d.ts +10 -0
- package/dist/databases/operations/join-through-many.operation.js +18 -0
- package/dist/databases/operations/operation.d.ts +2 -1
- package/dist/databases/postgres/commands/postgres-batch-update.command.js +2 -1
- package/dist/databases/postgres/queries/postgres-get-all.query.js +2 -1
- package/dist/databases/postgres/queries/postgres-get-by-id.query.js +2 -1
- package/dist/databases/postgres/queries/postgres-get.query.js +2 -1
- package/dist/databases/postgres/utils/build-join-clauses.js +78 -42
- package/dist/databases/postgres/utils/build-select-clause.js +5 -0
- package/dist/databases/postgres/utils/transform-join-results.js +106 -22
- package/package.json +1 -1
|
@@ -123,7 +123,30 @@ export const getPostgresTestSchema = (config) => {
|
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
125
|
migrations.push({
|
|
126
|
-
name: '
|
|
126
|
+
name: '00000000000105_5_schema-agents',
|
|
127
|
+
up: async ({ context: pool }) => {
|
|
128
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
129
|
+
await pool.query(`
|
|
130
|
+
CREATE TABLE IF NOT EXISTS "agents" (
|
|
131
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
132
|
+
${orgColumnDef}
|
|
133
|
+
"person_id" INTEGER NOT NULL UNIQUE,
|
|
134
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
135
|
+
"_createdBy" INTEGER NOT NULL,
|
|
136
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
137
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
138
|
+
"_deleted" TIMESTAMPTZ,
|
|
139
|
+
"_deletedBy" INTEGER,
|
|
140
|
+
CONSTRAINT fk_agents_person_id FOREIGN KEY ("person_id") REFERENCES persons("_id") ON DELETE CASCADE
|
|
141
|
+
)
|
|
142
|
+
`);
|
|
143
|
+
},
|
|
144
|
+
down: async ({ context: pool }) => {
|
|
145
|
+
await pool.query('DROP TABLE IF EXISTS "agents"');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
migrations.push({
|
|
149
|
+
name: '00000000000105_6_schema-clients',
|
|
127
150
|
up: async ({ context: pool }) => {
|
|
128
151
|
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
129
152
|
await pool.query(`
|
|
@@ -132,13 +155,15 @@ export const getPostgresTestSchema = (config) => {
|
|
|
132
155
|
${orgColumnDef}
|
|
133
156
|
"external_id" VARCHAR UNIQUE,
|
|
134
157
|
"person_id" INTEGER NOT NULL UNIQUE,
|
|
158
|
+
"agent_id" INTEGER,
|
|
135
159
|
"_created" TIMESTAMPTZ NOT NULL,
|
|
136
160
|
"_createdBy" INTEGER NOT NULL,
|
|
137
161
|
"_updated" TIMESTAMPTZ NOT NULL,
|
|
138
162
|
"_updatedBy" INTEGER NOT NULL,
|
|
139
163
|
"_deleted" TIMESTAMPTZ,
|
|
140
164
|
"_deletedBy" INTEGER,
|
|
141
|
-
CONSTRAINT fk_clients_person_id FOREIGN KEY ("person_id") REFERENCES persons("_id") ON DELETE CASCADE
|
|
165
|
+
CONSTRAINT fk_clients_person_id FOREIGN KEY ("person_id") REFERENCES persons("_id") ON DELETE CASCADE,
|
|
166
|
+
CONSTRAINT fk_clients_agent_id FOREIGN KEY ("agent_id") REFERENCES agents("_id") ON DELETE SET NULL
|
|
142
167
|
)
|
|
143
168
|
`);
|
|
144
169
|
},
|
|
@@ -279,5 +304,101 @@ export const getPostgresTestSchema = (config) => {
|
|
|
279
304
|
await pool.query('DROP TABLE IF EXISTS "persons_phone_numbers"');
|
|
280
305
|
}
|
|
281
306
|
});
|
|
307
|
+
migrations.push({
|
|
308
|
+
name: '00000000000111_schema-states',
|
|
309
|
+
up: async ({ context: pool }) => {
|
|
310
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
311
|
+
await pool.query(`
|
|
312
|
+
CREATE TABLE IF NOT EXISTS states (
|
|
313
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
314
|
+
${orgColumnDef}
|
|
315
|
+
"name" VARCHAR NOT NULL,
|
|
316
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
317
|
+
"_createdBy" INTEGER NOT NULL,
|
|
318
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
319
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
320
|
+
"_deleted" TIMESTAMPTZ,
|
|
321
|
+
"_deletedBy" INTEGER
|
|
322
|
+
)
|
|
323
|
+
`);
|
|
324
|
+
},
|
|
325
|
+
down: async ({ context: pool }) => {
|
|
326
|
+
await pool.query('DROP TABLE IF EXISTS "states"');
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
migrations.push({
|
|
330
|
+
name: '00000000000112_schema-districts',
|
|
331
|
+
up: async ({ context: pool }) => {
|
|
332
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
333
|
+
await pool.query(`
|
|
334
|
+
CREATE TABLE IF NOT EXISTS districts (
|
|
335
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
336
|
+
${orgColumnDef}
|
|
337
|
+
"name" VARCHAR NOT NULL,
|
|
338
|
+
"state_id" INTEGER NOT NULL,
|
|
339
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
340
|
+
"_createdBy" INTEGER NOT NULL,
|
|
341
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
342
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
343
|
+
"_deleted" TIMESTAMPTZ,
|
|
344
|
+
"_deletedBy" INTEGER,
|
|
345
|
+
CONSTRAINT fk_districts_state_id FOREIGN KEY ("state_id") REFERENCES states("_id") ON DELETE CASCADE
|
|
346
|
+
)
|
|
347
|
+
`);
|
|
348
|
+
},
|
|
349
|
+
down: async ({ context: pool }) => {
|
|
350
|
+
await pool.query('DROP TABLE IF EXISTS "districts"');
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
migrations.push({
|
|
354
|
+
name: '00000000000113_schema-schools',
|
|
355
|
+
up: async ({ context: pool }) => {
|
|
356
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
357
|
+
await pool.query(`
|
|
358
|
+
CREATE TABLE IF NOT EXISTS schools (
|
|
359
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
360
|
+
${orgColumnDef}
|
|
361
|
+
"name" VARCHAR NOT NULL,
|
|
362
|
+
"district_id" INTEGER NOT NULL,
|
|
363
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
364
|
+
"_createdBy" INTEGER NOT NULL,
|
|
365
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
366
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
367
|
+
"_deleted" TIMESTAMPTZ,
|
|
368
|
+
"_deletedBy" INTEGER,
|
|
369
|
+
CONSTRAINT fk_schools_district_id FOREIGN KEY ("district_id") REFERENCES districts("_id") ON DELETE CASCADE
|
|
370
|
+
)
|
|
371
|
+
`);
|
|
372
|
+
},
|
|
373
|
+
down: async ({ context: pool }) => {
|
|
374
|
+
await pool.query('DROP TABLE IF EXISTS "schools"');
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
migrations.push({
|
|
378
|
+
name: '00000000000114_schema-person-schools',
|
|
379
|
+
up: async ({ context: pool }) => {
|
|
380
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
381
|
+
await pool.query(`
|
|
382
|
+
CREATE TABLE IF NOT EXISTS persons_schools (
|
|
383
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
384
|
+
${orgColumnDef}
|
|
385
|
+
"person_id" INTEGER NOT NULL,
|
|
386
|
+
"school_id" INTEGER NOT NULL,
|
|
387
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
388
|
+
"_createdBy" INTEGER NOT NULL,
|
|
389
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
390
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
391
|
+
"_deleted" TIMESTAMPTZ,
|
|
392
|
+
"_deletedBy" INTEGER,
|
|
393
|
+
CONSTRAINT fk_persons_schools_person_id FOREIGN KEY ("person_id") REFERENCES persons("_id") ON DELETE CASCADE,
|
|
394
|
+
CONSTRAINT fk_persons_schools_school_id FOREIGN KEY ("school_id") REFERENCES schools("_id") ON DELETE CASCADE,
|
|
395
|
+
CONSTRAINT uk_persons_schools_person_school UNIQUE ("person_id", "school_id")
|
|
396
|
+
)
|
|
397
|
+
`);
|
|
398
|
+
},
|
|
399
|
+
down: async ({ context: pool }) => {
|
|
400
|
+
await pool.query('DROP TABLE IF EXISTS "persons_schools"');
|
|
401
|
+
}
|
|
402
|
+
});
|
|
282
403
|
return migrations;
|
|
283
404
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Join } from '../../operations/join.operation.js';
|
|
2
2
|
import { JoinMany } from '../../operations/join-many.operation.js';
|
|
3
3
|
import { JoinThrough } from '../../operations/join-through.operation.js';
|
|
4
|
+
import { JoinThroughMany } from '../../operations/join-through-many.operation.js';
|
|
4
5
|
export function convertOperationsToPipeline(operations) {
|
|
5
6
|
let pipeline = [];
|
|
6
7
|
const processedOperations = [];
|
|
@@ -124,6 +125,193 @@ export function convertOperationsToPipeline(operations) {
|
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
else if (operation instanceof JoinThrough) {
|
|
128
|
+
const needsObjectIdConversion = operation.foreignField === '_id';
|
|
129
|
+
const isNestedField = operation.localField.includes('.');
|
|
130
|
+
if (needsObjectIdConversion || isNestedField) {
|
|
131
|
+
pipeline.push({
|
|
132
|
+
$lookup: {
|
|
133
|
+
from: operation.through,
|
|
134
|
+
let: { localId: { $cond: [
|
|
135
|
+
{ $eq: [{ $type: `$${operation.localField}` }, 'string'] },
|
|
136
|
+
{ $toObjectId: `$${operation.localField}` },
|
|
137
|
+
`$${operation.localField}`
|
|
138
|
+
] } },
|
|
139
|
+
pipeline: [
|
|
140
|
+
{
|
|
141
|
+
$match: {
|
|
142
|
+
$expr: {
|
|
143
|
+
$eq: [
|
|
144
|
+
{
|
|
145
|
+
$cond: [
|
|
146
|
+
{ $eq: [{ $type: `$${operation.throughLocalField}` }, 'string'] },
|
|
147
|
+
{ $toObjectId: `$${operation.throughLocalField}` },
|
|
148
|
+
`$${operation.throughLocalField}`
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
'$$localId'
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{ $limit: 1 }
|
|
157
|
+
],
|
|
158
|
+
as: `${operation.as}_through`
|
|
159
|
+
}
|
|
160
|
+
}, {
|
|
161
|
+
$unwind: {
|
|
162
|
+
path: `$${operation.as}_through`,
|
|
163
|
+
preserveNullAndEmptyArrays: true
|
|
164
|
+
}
|
|
165
|
+
}, {
|
|
166
|
+
$lookup: {
|
|
167
|
+
from: operation.from,
|
|
168
|
+
let: { foreignId: { $cond: [
|
|
169
|
+
{ $eq: [{ $type: `$${operation.as}_through.${operation.throughForeignField}` }, 'string'] },
|
|
170
|
+
{ $toObjectId: `$${operation.as}_through.${operation.throughForeignField}` },
|
|
171
|
+
`$${operation.as}_through.${operation.throughForeignField}`
|
|
172
|
+
] } },
|
|
173
|
+
pipeline: [
|
|
174
|
+
{
|
|
175
|
+
$match: {
|
|
176
|
+
$expr: {
|
|
177
|
+
$eq: [
|
|
178
|
+
{
|
|
179
|
+
$cond: [
|
|
180
|
+
{ $eq: [{ $type: `$${operation.foreignField}` }, 'string'] },
|
|
181
|
+
{ $toObjectId: `$${operation.foreignField}` },
|
|
182
|
+
`$${operation.foreignField}`
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
'$$foreignId'
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
as: `${operation.as}_temp`
|
|
192
|
+
}
|
|
193
|
+
}, {
|
|
194
|
+
$unwind: {
|
|
195
|
+
path: `$${operation.as}_temp`,
|
|
196
|
+
preserveNullAndEmptyArrays: true
|
|
197
|
+
}
|
|
198
|
+
}, {
|
|
199
|
+
$addFields: {
|
|
200
|
+
[operation.as]: `$${operation.as}_temp`
|
|
201
|
+
}
|
|
202
|
+
}, {
|
|
203
|
+
$project: {
|
|
204
|
+
[`${operation.as}_through`]: 0,
|
|
205
|
+
[`${operation.as}_temp`]: 0
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
if (isNestedField) {
|
|
209
|
+
const [parentAlias] = operation.localField.split('.');
|
|
210
|
+
const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
|
|
211
|
+
if (parentJoin) {
|
|
212
|
+
pipeline.push({
|
|
213
|
+
$set: {
|
|
214
|
+
[parentAlias]: {
|
|
215
|
+
$mergeObjects: [
|
|
216
|
+
{ $ifNull: [`$${parentAlias}`, {}] },
|
|
217
|
+
{ [operation.as]: `$${operation.as}` }
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
pipeline.push({
|
|
223
|
+
$unset: operation.as
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const isNestedFieldElse = operation.localField.includes('.');
|
|
230
|
+
if (isNestedFieldElse) {
|
|
231
|
+
pipeline.push({
|
|
232
|
+
$lookup: {
|
|
233
|
+
from: operation.through,
|
|
234
|
+
let: { localId: `$${operation.localField}` },
|
|
235
|
+
pipeline: [
|
|
236
|
+
{
|
|
237
|
+
$match: {
|
|
238
|
+
$expr: {
|
|
239
|
+
$eq: [`$${operation.throughLocalField}`, '$$localId']
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{ $limit: 1 }
|
|
244
|
+
],
|
|
245
|
+
as: `${operation.as}_through`
|
|
246
|
+
}
|
|
247
|
+
}, {
|
|
248
|
+
$unwind: {
|
|
249
|
+
path: `$${operation.as}_through`,
|
|
250
|
+
preserveNullAndEmptyArrays: true
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
pipeline.push({
|
|
256
|
+
$lookup: {
|
|
257
|
+
from: operation.through,
|
|
258
|
+
localField: operation.localField,
|
|
259
|
+
foreignField: operation.throughLocalField,
|
|
260
|
+
as: `${operation.as}_through`
|
|
261
|
+
}
|
|
262
|
+
}, {
|
|
263
|
+
$unwind: {
|
|
264
|
+
path: `$${operation.as}_through`,
|
|
265
|
+
preserveNullAndEmptyArrays: true
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
pipeline.push({
|
|
270
|
+
$lookup: {
|
|
271
|
+
from: operation.from,
|
|
272
|
+
localField: `${operation.as}_through.${operation.throughForeignField}`,
|
|
273
|
+
foreignField: operation.foreignField,
|
|
274
|
+
as: `${operation.as}_temp`
|
|
275
|
+
}
|
|
276
|
+
}, {
|
|
277
|
+
$unwind: {
|
|
278
|
+
path: `$${operation.as}_temp`,
|
|
279
|
+
preserveNullAndEmptyArrays: true
|
|
280
|
+
}
|
|
281
|
+
}, {
|
|
282
|
+
$addFields: {
|
|
283
|
+
[operation.as]: `$${operation.as}_temp`
|
|
284
|
+
}
|
|
285
|
+
}, {
|
|
286
|
+
$project: {
|
|
287
|
+
[`${operation.as}_through`]: 0,
|
|
288
|
+
[`${operation.as}_temp`]: 0
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
if (isNestedFieldElse) {
|
|
292
|
+
const [parentAlias] = operation.localField.split('.');
|
|
293
|
+
const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
|
|
294
|
+
if (parentJoin) {
|
|
295
|
+
pipeline.push({
|
|
296
|
+
$addFields: {
|
|
297
|
+
[parentAlias]: {
|
|
298
|
+
$mergeObjects: [
|
|
299
|
+
`$${parentAlias}`,
|
|
300
|
+
{ [operation.as]: `$${operation.as}` }
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
pipeline.push({
|
|
306
|
+
$project: {
|
|
307
|
+
[operation.as]: 0
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else if (operation instanceof JoinThroughMany) {
|
|
127
315
|
const needsObjectIdConversion = operation.foreignField === '_id';
|
|
128
316
|
const isNestedField = operation.localField.includes('.');
|
|
129
317
|
if (needsObjectIdConversion || isNestedField) {
|
|
@@ -208,7 +396,7 @@ export function convertOperationsToPipeline(operations) {
|
|
|
208
396
|
});
|
|
209
397
|
if (isNestedField) {
|
|
210
398
|
const [parentAlias] = operation.localField.split('.');
|
|
211
|
-
const parentJoin = processedOperations.find(op => op instanceof Join && op.as === parentAlias);
|
|
399
|
+
const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
|
|
212
400
|
if (parentJoin) {
|
|
213
401
|
pipeline.push({
|
|
214
402
|
$set: {
|
|
@@ -293,7 +481,7 @@ export function convertOperationsToPipeline(operations) {
|
|
|
293
481
|
});
|
|
294
482
|
if (isNestedFieldElse) {
|
|
295
483
|
const [parentAlias] = operation.localField.split('.');
|
|
296
|
-
const parentJoin = processedOperations.find(op => op instanceof Join && op.as === parentAlias);
|
|
484
|
+
const parentJoin = processedOperations.find(op => (op instanceof Join || op instanceof JoinThrough) && op.as === parentAlias);
|
|
297
485
|
if (parentJoin) {
|
|
298
486
|
pipeline.push({
|
|
299
487
|
$addFields: {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IAuditable, IEntity } from "@loomcore/common/models";
|
|
2
|
+
import { IPersonModel } from "./person.model.js";
|
|
3
|
+
export interface IAgentModel extends IEntity, IAuditable {
|
|
4
|
+
person_id: number;
|
|
5
|
+
agent_person?: IPersonModel;
|
|
6
|
+
}
|
|
7
|
+
export declare const agentSchema: import("@sinclair/typebox").TObject<{
|
|
8
|
+
person_id: import("@sinclair/typebox").TNumber;
|
|
9
|
+
agent_person: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
10
|
+
first_name: import("@sinclair/typebox").TString;
|
|
11
|
+
middle_name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
12
|
+
last_name: import("@sinclair/typebox").TString;
|
|
13
|
+
phone_numbers: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
14
|
+
phone_number: import("@sinclair/typebox").TString;
|
|
15
|
+
phone_number_type: import("@sinclair/typebox").TString;
|
|
16
|
+
is_default: import("@sinclair/typebox").TBoolean;
|
|
17
|
+
}>>;
|
|
18
|
+
email_addresses: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
19
|
+
person_id: import("@sinclair/typebox").TNumber;
|
|
20
|
+
email_address: import("@sinclair/typebox").TString;
|
|
21
|
+
is_default: import("@sinclair/typebox").TBoolean;
|
|
22
|
+
}>>;
|
|
23
|
+
}>>;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const agentModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { entityUtils } from "@loomcore/common/utils";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { personSchema } from "./person.model.js";
|
|
4
|
+
export const agentSchema = Type.Object({
|
|
5
|
+
person_id: Type.Number(),
|
|
6
|
+
agent_person: Type.Optional(personSchema),
|
|
7
|
+
});
|
|
8
|
+
export const agentModelSpec = entityUtils.getModelSpec(agentSchema, { isAuditable: true });
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { IPersonModel } from "./person.model.js";
|
|
2
|
+
import { IAgentModel } from "./agent.model.js";
|
|
2
3
|
import type { IAuditable, IEntity } from "@loomcore/common/models";
|
|
3
4
|
export interface IClientReportsModel extends IEntity, IAuditable {
|
|
4
|
-
|
|
5
|
+
client_person: IPersonModel;
|
|
6
|
+
agent?: IAgentModel;
|
|
5
7
|
}
|
|
6
8
|
export declare const clientReportsSchema: import("@sinclair/typebox").TObject<{
|
|
7
|
-
|
|
9
|
+
client_person: import("@sinclair/typebox").TObject<{
|
|
8
10
|
first_name: import("@sinclair/typebox").TString;
|
|
9
11
|
middle_name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
10
12
|
last_name: import("@sinclair/typebox").TString;
|
|
@@ -19,5 +21,23 @@ export declare const clientReportsSchema: import("@sinclair/typebox").TObject<{
|
|
|
19
21
|
is_default: import("@sinclair/typebox").TBoolean;
|
|
20
22
|
}>>;
|
|
21
23
|
}>;
|
|
24
|
+
agent: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
25
|
+
person_id: import("@sinclair/typebox").TNumber;
|
|
26
|
+
agent_person: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
27
|
+
first_name: import("@sinclair/typebox").TString;
|
|
28
|
+
middle_name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
29
|
+
last_name: import("@sinclair/typebox").TString;
|
|
30
|
+
phone_numbers: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
31
|
+
phone_number: import("@sinclair/typebox").TString;
|
|
32
|
+
phone_number_type: import("@sinclair/typebox").TString;
|
|
33
|
+
is_default: import("@sinclair/typebox").TBoolean;
|
|
34
|
+
}>>;
|
|
35
|
+
email_addresses: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
36
|
+
person_id: import("@sinclair/typebox").TNumber;
|
|
37
|
+
email_address: import("@sinclair/typebox").TString;
|
|
38
|
+
is_default: import("@sinclair/typebox").TBoolean;
|
|
39
|
+
}>>;
|
|
40
|
+
}>>;
|
|
41
|
+
}>>;
|
|
22
42
|
}>;
|
|
23
43
|
export declare const clientReportsModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { personSchema } from "./person.model.js";
|
|
2
|
+
import { agentSchema } from "./agent.model.js";
|
|
2
3
|
import { entityUtils } from "@loomcore/common/utils";
|
|
3
4
|
import { Type } from "@sinclair/typebox";
|
|
4
5
|
export const clientReportsSchema = Type.Object({
|
|
5
|
-
|
|
6
|
+
client_person: personSchema,
|
|
7
|
+
agent: Type.Optional(agentSchema)
|
|
6
8
|
});
|
|
7
9
|
export const clientReportsModelSpec = entityUtils.getModelSpec(clientReportsSchema, { isAuditable: true });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IAuditable, IEntity } from "@loomcore/common/models";
|
|
2
|
+
import { IStateModel } from "./state.model.js";
|
|
3
|
+
export interface IDistrictModel extends IEntity, IAuditable {
|
|
4
|
+
name: string;
|
|
5
|
+
state_id: number;
|
|
6
|
+
state?: IStateModel;
|
|
7
|
+
}
|
|
8
|
+
export declare const districtSchema: import("@sinclair/typebox").TObject<{
|
|
9
|
+
name: import("@sinclair/typebox").TString;
|
|
10
|
+
state_id: import("@sinclair/typebox").TNumber;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const districtModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { entityUtils } from "@loomcore/common/utils";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
export const districtSchema = Type.Object({
|
|
4
|
+
name: Type.String(),
|
|
5
|
+
state_id: Type.Number(),
|
|
6
|
+
});
|
|
7
|
+
export const districtModelSpec = entityUtils.getModelSpec(districtSchema, { isAuditable: true });
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { IAuditable, IEntity } from "@loomcore/common/models";
|
|
2
2
|
import { IEmailAddressModel } from "./email-address.model.js";
|
|
3
3
|
import { IPhoneNumberModel } from "./phone-number.model.js";
|
|
4
|
+
import { ISchoolModel } from "./school.model.js";
|
|
4
5
|
export interface IPersonModel extends IEntity, IAuditable {
|
|
5
6
|
first_name: string;
|
|
6
7
|
middle_name: string | null;
|
|
7
8
|
last_name: string;
|
|
8
9
|
email_addresses: IEmailAddressModel[];
|
|
9
10
|
phone_numbers: IPhoneNumberModel[];
|
|
11
|
+
school?: ISchoolModel;
|
|
10
12
|
}
|
|
11
13
|
export declare const personSchema: import("@sinclair/typebox").TObject<{
|
|
12
14
|
first_name: import("@sinclair/typebox").TString;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IAuditable, IEntity } from "@loomcore/common/models";
|
|
2
|
+
import { IDistrictModel } from "./district.model.js";
|
|
3
|
+
export interface ISchoolModel extends IEntity, IAuditable {
|
|
4
|
+
name: string;
|
|
5
|
+
district_id: number;
|
|
6
|
+
district?: IDistrictModel;
|
|
7
|
+
}
|
|
8
|
+
export declare const schoolSchema: import("@sinclair/typebox").TObject<{
|
|
9
|
+
name: import("@sinclair/typebox").TString;
|
|
10
|
+
district_id: import("@sinclair/typebox").TNumber;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const schoolModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { entityUtils } from "@loomcore/common/utils";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
export const schoolSchema = Type.Object({
|
|
4
|
+
name: Type.String(),
|
|
5
|
+
district_id: Type.Number(),
|
|
6
|
+
});
|
|
7
|
+
export const schoolModelSpec = entityUtils.getModelSpec(schoolSchema, { isAuditable: true });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IAuditable, IEntity } from "@loomcore/common/models";
|
|
2
|
+
export interface IStateModel extends IEntity, IAuditable {
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const stateSchema: import("@sinclair/typebox").TObject<{
|
|
6
|
+
name: import("@sinclair/typebox").TString;
|
|
7
|
+
}>;
|
|
8
|
+
export declare const stateModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class JoinThroughMany {
|
|
2
|
+
from: string;
|
|
3
|
+
through: string;
|
|
4
|
+
localField: string;
|
|
5
|
+
throughLocalField: string;
|
|
6
|
+
throughForeignField: string;
|
|
7
|
+
foreignField: string;
|
|
8
|
+
as: string;
|
|
9
|
+
constructor(from: string, through: string, localField: string, throughLocalField: string, throughForeignField: string, foreignField: string, as: string);
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class JoinThroughMany {
|
|
2
|
+
from;
|
|
3
|
+
through;
|
|
4
|
+
localField;
|
|
5
|
+
throughLocalField;
|
|
6
|
+
throughForeignField;
|
|
7
|
+
foreignField;
|
|
8
|
+
as;
|
|
9
|
+
constructor(from, through, localField, throughLocalField, throughForeignField, foreignField, as) {
|
|
10
|
+
this.from = from;
|
|
11
|
+
this.through = through;
|
|
12
|
+
this.localField = localField;
|
|
13
|
+
this.throughLocalField = throughLocalField;
|
|
14
|
+
this.throughForeignField = throughForeignField;
|
|
15
|
+
this.foreignField = foreignField;
|
|
16
|
+
this.as = as;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Join } from "./join.operation.js";
|
|
2
2
|
import { JoinMany } from "./join-many.operation.js";
|
|
3
3
|
import { JoinThrough } from "./join-through.operation.js";
|
|
4
|
-
|
|
4
|
+
import { JoinThroughMany } from "./join-through-many.operation.js";
|
|
5
|
+
export type Operation = Join | JoinMany | JoinThrough | JoinThroughMany;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Join } from "../../operations/join.operation.js";
|
|
2
2
|
import { JoinMany } from "../../operations/join-many.operation.js";
|
|
3
3
|
import { JoinThrough } from "../../operations/join-through.operation.js";
|
|
4
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
import { BadRequestError } from "../../../errors/index.js";
|
|
5
6
|
import { buildJoinClauses } from '../utils/build-join-clauses.js';
|
|
6
7
|
import { buildSelectClause } from '../utils/build-select-clause.js';
|
|
@@ -39,7 +40,7 @@ export async function batchUpdate(client, entities, operations, queryObject, plu
|
|
|
39
40
|
}
|
|
40
41
|
await client.query('COMMIT');
|
|
41
42
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
42
|
-
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough);
|
|
43
|
+
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
|
|
43
44
|
const tablePrefix = hasJoins ? pluralResourceName : undefined;
|
|
44
45
|
queryObject.filters._id = { in: entityIds };
|
|
45
46
|
const { whereClause, values } = buildWhereClause(queryObject, [], tablePrefix);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Join } from "../../operations/join.operation.js";
|
|
2
2
|
import { JoinMany } from "../../operations/join-many.operation.js";
|
|
3
3
|
import { JoinThrough } from "../../operations/join-through.operation.js";
|
|
4
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
import { buildJoinClauses } from '../utils/build-join-clauses.js';
|
|
5
6
|
import { buildSelectClause } from '../utils/build-select-clause.js';
|
|
6
7
|
import { transformJoinResults } from '../utils/transform-join-results.js';
|
|
7
8
|
export async function getAll(client, operations, pluralResourceName) {
|
|
8
9
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
9
|
-
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough);
|
|
10
|
+
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
|
|
10
11
|
const selectClause = hasJoins
|
|
11
12
|
? await buildSelectClause(client, pluralResourceName, pluralResourceName, operations)
|
|
12
13
|
: '*';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Join } from "../../operations/join.operation.js";
|
|
2
2
|
import { JoinMany } from "../../operations/join-many.operation.js";
|
|
3
3
|
import { JoinThrough } from "../../operations/join-through.operation.js";
|
|
4
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
import { buildJoinClauses } from "../utils/build-join-clauses.js";
|
|
5
6
|
import { buildSelectClause } from "../utils/build-select-clause.js";
|
|
6
7
|
import { transformJoinResults } from "../utils/transform-join-results.js";
|
|
@@ -10,7 +11,7 @@ export async function getById(client, operations, queryObject, id, pluralResourc
|
|
|
10
11
|
if (!id)
|
|
11
12
|
throw new BadRequestError('id is required');
|
|
12
13
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
13
|
-
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough);
|
|
14
|
+
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
|
|
14
15
|
const selectClause = hasJoins
|
|
15
16
|
? await buildSelectClause(client, pluralResourceName, pluralResourceName, operations)
|
|
16
17
|
: '*';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Join } from "../../operations/join.operation.js";
|
|
2
2
|
import { JoinMany } from "../../operations/join-many.operation.js";
|
|
3
3
|
import { JoinThrough } from "../../operations/join-through.operation.js";
|
|
4
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
import { buildWhereClause } from "../utils/build-where-clause.js";
|
|
5
6
|
import { buildOrderByClause } from "../utils/build-order-by-clause.js";
|
|
6
7
|
import { buildJoinClauses } from "../utils/build-join-clauses.js";
|
|
@@ -13,7 +14,7 @@ export async function get(client, operations, queryOptions, pluralResourceName)
|
|
|
13
14
|
const joinClauses = buildJoinClauses(operations, pluralResourceName);
|
|
14
15
|
const orderByClause = buildOrderByClause(queryOptions);
|
|
15
16
|
const paginationClause = buildPaginationClause(queryOptions);
|
|
16
|
-
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough);
|
|
17
|
+
const hasJoins = operations.some(op => op instanceof Join || op instanceof JoinMany || op instanceof JoinThrough || op instanceof JoinThroughMany);
|
|
17
18
|
const selectClause = hasJoins
|
|
18
19
|
? await buildSelectClause(client, pluralResourceName, pluralResourceName, operations)
|
|
19
20
|
: '*';
|
|
@@ -1,55 +1,91 @@
|
|
|
1
1
|
import { Join } from "../../operations/join.operation.js";
|
|
2
2
|
import { JoinMany } from "../../operations/join-many.operation.js";
|
|
3
3
|
import { JoinThrough } from "../../operations/join-through.operation.js";
|
|
4
|
+
import { JoinThroughMany } from "../../operations/join-through-many.operation.js";
|
|
4
5
|
export function buildJoinClauses(operations, mainTableName) {
|
|
5
6
|
let joinClauses = '';
|
|
6
|
-
const joinOperations = operations.filter(op => op instanceof Join);
|
|
7
|
-
const joinManyOperations = operations.filter(op => op instanceof JoinMany);
|
|
8
7
|
const joinThroughOperations = operations.filter(op => op instanceof JoinThrough);
|
|
9
|
-
for (const operation of
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
for (const operation of operations) {
|
|
9
|
+
if (operation instanceof Join) {
|
|
10
|
+
let localFieldRef;
|
|
11
|
+
if (operation.localField.includes('.')) {
|
|
12
|
+
const [tableAlias, columnName] = operation.localField.split('.');
|
|
13
|
+
const referencedJoinThrough = joinThroughOperations.find(jt => jt.as === tableAlias);
|
|
14
|
+
if (referencedJoinThrough) {
|
|
15
|
+
localFieldRef = `(${tableAlias}.aggregated->>'${columnName}')::integer`;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
localFieldRef = `${tableAlias}."${columnName}"`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
localFieldRef = mainTableName
|
|
23
|
+
? `"${mainTableName}"."${operation.localField}"`
|
|
24
|
+
: `"${operation.localField}"`;
|
|
25
|
+
}
|
|
26
|
+
joinClauses += ` LEFT JOIN "${operation.from}" AS ${operation.as} ON ${localFieldRef} = "${operation.as}"."${operation.foreignField}"`;
|
|
20
27
|
}
|
|
21
|
-
else {
|
|
22
|
-
localFieldRef
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
else if (operation instanceof JoinMany) {
|
|
29
|
+
let localFieldRef;
|
|
30
|
+
if (operation.localField.includes('.')) {
|
|
31
|
+
const [tableAlias, columnName] = operation.localField.split('.');
|
|
32
|
+
localFieldRef = `${tableAlias}."${columnName}"`;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
localFieldRef = mainTableName
|
|
36
|
+
? `"${mainTableName}"."${operation.localField}"`
|
|
37
|
+
: `"${operation.localField}"`;
|
|
38
|
+
}
|
|
39
|
+
joinClauses += ` LEFT JOIN LATERAL (
|
|
40
|
+
SELECT COALESCE(JSON_AGG(row_to_json("${operation.from}")), '[]'::json) AS aggregated
|
|
41
|
+
FROM "${operation.from}"
|
|
42
|
+
WHERE "${operation.from}"."${operation.foreignField}" = ${localFieldRef}
|
|
43
|
+
AND "${operation.from}"."_deleted" IS NULL
|
|
44
|
+
) AS ${operation.as} ON true`;
|
|
25
45
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
else if (operation instanceof JoinThrough) {
|
|
47
|
+
let localFieldRef;
|
|
48
|
+
if (operation.localField.includes('.')) {
|
|
49
|
+
const [tableAlias, columnName] = operation.localField.split('.');
|
|
50
|
+
localFieldRef = `${tableAlias}."${columnName}"`;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
localFieldRef = mainTableName
|
|
54
|
+
? `"${mainTableName}"."${operation.localField}"`
|
|
55
|
+
: `"${operation.localField}"`;
|
|
56
|
+
}
|
|
57
|
+
joinClauses += ` LEFT JOIN LATERAL (
|
|
58
|
+
SELECT row_to_json(${operation.as}) AS aggregated
|
|
59
|
+
FROM "${operation.through}"
|
|
60
|
+
INNER JOIN "${operation.from}" AS ${operation.as}
|
|
61
|
+
ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
|
|
62
|
+
WHERE "${operation.through}"."${operation.throughLocalField}" = ${localFieldRef}
|
|
63
|
+
AND "${operation.through}"."_deleted" IS NULL
|
|
64
|
+
AND ${operation.as}."_deleted" IS NULL
|
|
65
|
+
LIMIT 1
|
|
66
|
+
) AS ${operation.as} ON true`;
|
|
38
67
|
}
|
|
39
|
-
else {
|
|
40
|
-
localFieldRef
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
else if (operation instanceof JoinThroughMany) {
|
|
69
|
+
let localFieldRef;
|
|
70
|
+
if (operation.localField.includes('.')) {
|
|
71
|
+
const [tableAlias, columnName] = operation.localField.split('.');
|
|
72
|
+
localFieldRef = `${tableAlias}."${columnName}"`;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
localFieldRef = mainTableName
|
|
76
|
+
? `"${mainTableName}"."${operation.localField}"`
|
|
77
|
+
: `"${operation.localField}"`;
|
|
78
|
+
}
|
|
79
|
+
joinClauses += ` LEFT JOIN LATERAL (
|
|
80
|
+
SELECT COALESCE(JSON_AGG(row_to_json(${operation.as})), '[]'::json) AS aggregated
|
|
81
|
+
FROM "${operation.through}"
|
|
82
|
+
INNER JOIN "${operation.from}" AS ${operation.as}
|
|
83
|
+
ON ${operation.as}."${operation.foreignField}" = "${operation.through}"."${operation.throughForeignField}"
|
|
84
|
+
WHERE "${operation.through}"."${operation.throughLocalField}" = ${localFieldRef}
|
|
85
|
+
AND "${operation.through}"."_deleted" IS NULL
|
|
86
|
+
AND ${operation.as}."_deleted" IS NULL
|
|
87
|
+
) AS ${operation.as} ON true`;
|
|
43
88
|
}
|
|
44
|
-
joinClauses += ` LEFT JOIN LATERAL (
|
|
45
|
-
SELECT COALESCE(JSON_AGG(row_to_json(${joinThrough.as})), '[]'::json) AS aggregated
|
|
46
|
-
FROM "${joinThrough.through}"
|
|
47
|
-
INNER JOIN "${joinThrough.from}" AS ${joinThrough.as}
|
|
48
|
-
ON ${joinThrough.as}."${joinThrough.foreignField}" = "${joinThrough.through}"."${joinThrough.throughForeignField}"
|
|
49
|
-
WHERE "${joinThrough.through}"."${joinThrough.throughLocalField}" = ${localFieldRef}
|
|
50
|
-
AND "${joinThrough.through}"."_deleted" IS NULL
|
|
51
|
-
AND ${joinThrough.as}."_deleted" IS NULL
|
|
52
|
-
) AS ${joinThrough.as} ON true`;
|
|
53
89
|
}
|
|
54
90
|
return joinClauses;
|
|
55
91
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Join } from '../../operations/join.operation.js';
|
|
2
2
|
import { JoinMany } from '../../operations/join-many.operation.js';
|
|
3
3
|
import { JoinThrough } from '../../operations/join-through.operation.js';
|
|
4
|
+
import { JoinThroughMany } from '../../operations/join-through-many.operation.js';
|
|
4
5
|
async function getTableColumns(client, tableName) {
|
|
5
6
|
const result = await client.query(`
|
|
6
7
|
SELECT column_name
|
|
@@ -15,6 +16,7 @@ export async function buildSelectClause(client, mainTableName, mainTableAlias, o
|
|
|
15
16
|
const joinOperations = operations.filter(op => op instanceof Join);
|
|
16
17
|
const joinManyOperations = operations.filter(op => op instanceof JoinMany);
|
|
17
18
|
const joinThroughOperations = operations.filter(op => op instanceof JoinThrough);
|
|
19
|
+
const joinThroughManyOperations = operations.filter(op => op instanceof JoinThroughMany);
|
|
18
20
|
const mainTableColumns = await getTableColumns(client, mainTableName);
|
|
19
21
|
const mainSelects = mainTableColumns.map(col => `"${mainTableName}"."${col}" AS "${col}"`);
|
|
20
22
|
const joinSelects = [];
|
|
@@ -33,6 +35,9 @@ export async function buildSelectClause(client, mainTableName, mainTableAlias, o
|
|
|
33
35
|
for (const joinThrough of joinThroughOperations) {
|
|
34
36
|
joinSelects.push(`${joinThrough.as}.aggregated AS "${joinThrough.as}"`);
|
|
35
37
|
}
|
|
38
|
+
for (const joinThroughMany of joinThroughManyOperations) {
|
|
39
|
+
joinSelects.push(`${joinThroughMany.as}.aggregated AS "${joinThroughMany.as}"`);
|
|
40
|
+
}
|
|
36
41
|
const allSelects = [...mainSelects, ...joinSelects];
|
|
37
42
|
return allSelects.join(', ');
|
|
38
43
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Join } from '../../operations/join.operation.js';
|
|
2
2
|
import { JoinMany } from '../../operations/join-many.operation.js';
|
|
3
3
|
import { JoinThrough } from '../../operations/join-through.operation.js';
|
|
4
|
+
import { JoinThroughMany } from '../../operations/join-through-many.operation.js';
|
|
4
5
|
export function transformJoinResults(rows, operations) {
|
|
5
6
|
const joinOperations = operations.filter(op => op instanceof Join);
|
|
6
7
|
const joinManyOperations = operations.filter(op => op instanceof JoinMany);
|
|
7
8
|
const joinThroughOperations = operations.filter(op => op instanceof JoinThrough);
|
|
8
|
-
|
|
9
|
+
const joinThroughManyOperations = operations.filter(op => op instanceof JoinThroughMany);
|
|
10
|
+
if (joinOperations.length === 0 && joinManyOperations.length === 0 && joinThroughOperations.length === 0 && joinThroughManyOperations.length === 0) {
|
|
9
11
|
return rows;
|
|
10
12
|
}
|
|
11
13
|
return rows.map(row => {
|
|
@@ -13,7 +15,8 @@ export function transformJoinResults(rows, operations) {
|
|
|
13
15
|
const allJoinAliases = [
|
|
14
16
|
...joinOperations.map(j => j.as),
|
|
15
17
|
...joinManyOperations.map(j => j.as),
|
|
16
|
-
...joinThroughOperations.map(j => j.as)
|
|
18
|
+
...joinThroughOperations.map(j => j.as),
|
|
19
|
+
...joinThroughManyOperations.map(j => j.as)
|
|
17
20
|
];
|
|
18
21
|
for (const key of Object.keys(row)) {
|
|
19
22
|
const hasJoinPrefix = joinOperations.some(join => key.startsWith(`${join.as}__`));
|
|
@@ -22,21 +25,100 @@ export function transformJoinResults(rows, operations) {
|
|
|
22
25
|
transformed[key] = row[key];
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
|
-
for (const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
for (const operation of operations) {
|
|
29
|
+
if (operation instanceof JoinThrough) {
|
|
30
|
+
const jsonValue = row[operation.as];
|
|
31
|
+
let parsedValue;
|
|
32
|
+
if (jsonValue !== null && jsonValue !== undefined) {
|
|
33
|
+
parsedValue = typeof jsonValue === 'string'
|
|
34
|
+
? JSON.parse(jsonValue)
|
|
35
|
+
: jsonValue;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
parsedValue = null;
|
|
39
|
+
}
|
|
40
|
+
if (operation.localField.includes('.')) {
|
|
41
|
+
const [tableAlias] = operation.localField.split('.');
|
|
42
|
+
const relatedJoin = joinOperations.find(j => j.as === tableAlias);
|
|
43
|
+
const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
|
|
44
|
+
if ((relatedJoin && transformed[relatedJoin.as]) || (relatedJoinThrough && transformed[relatedJoinThrough.as])) {
|
|
45
|
+
const targetAlias = relatedJoin ? relatedJoin.as : relatedJoinThrough.as;
|
|
46
|
+
transformed[targetAlias][operation.as] = parsedValue;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
transformed[operation.as] = parsedValue;
|
|
36
50
|
}
|
|
37
51
|
}
|
|
52
|
+
else {
|
|
53
|
+
transformed[operation.as] = parsedValue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (operation instanceof Join) {
|
|
57
|
+
const prefix = `${operation.as}__`;
|
|
58
|
+
const joinedData = {};
|
|
59
|
+
let hasAnyData = false;
|
|
60
|
+
for (const key of Object.keys(row)) {
|
|
61
|
+
if (key.startsWith(prefix)) {
|
|
62
|
+
const columnName = key.substring(prefix.length);
|
|
63
|
+
const value = row[key];
|
|
64
|
+
joinedData[columnName] = value;
|
|
65
|
+
if (value !== null && value !== undefined) {
|
|
66
|
+
hasAnyData = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (operation.localField.includes('.')) {
|
|
71
|
+
const [tableAlias] = operation.localField.split('.');
|
|
72
|
+
const relatedJoin = joinOperations.find(j => j.as === tableAlias);
|
|
73
|
+
const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
|
|
74
|
+
const findNestedObject = (obj, alias, path = []) => {
|
|
75
|
+
if (obj[alias] !== undefined && obj[alias] !== null) {
|
|
76
|
+
return { obj: obj[alias], path: [...path, alias] };
|
|
77
|
+
}
|
|
78
|
+
for (const key in obj) {
|
|
79
|
+
if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
|
80
|
+
const found = findNestedObject(obj[key], alias, [...path, key]);
|
|
81
|
+
if (found !== null) {
|
|
82
|
+
return found;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
88
|
+
let targetObject = null;
|
|
89
|
+
if (relatedJoin) {
|
|
90
|
+
if (transformed[relatedJoin.as] !== undefined && transformed[relatedJoin.as] !== null) {
|
|
91
|
+
targetObject = transformed[relatedJoin.as];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const found = findNestedObject(transformed, relatedJoin.as);
|
|
95
|
+
if (found !== null && found.obj !== undefined && found.obj !== null) {
|
|
96
|
+
targetObject = found.obj;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (relatedJoinThrough) {
|
|
101
|
+
const found = findNestedObject(transformed, relatedJoinThrough.as);
|
|
102
|
+
if (found !== null && found.obj !== undefined && found.obj !== null) {
|
|
103
|
+
targetObject = found.obj;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (targetObject) {
|
|
107
|
+
if (hasAnyData) {
|
|
108
|
+
targetObject[operation.as] = joinedData;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
targetObject[operation.as] = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
transformed[operation.as] = hasAnyData ? joinedData : null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
transformed[operation.as] = hasAnyData ? joinedData : null;
|
|
120
|
+
}
|
|
38
121
|
}
|
|
39
|
-
transformed[join.as] = hasAnyData ? joinedData : null;
|
|
40
122
|
}
|
|
41
123
|
for (const joinMany of joinManyOperations) {
|
|
42
124
|
const jsonValue = row[joinMany.as];
|
|
@@ -63,8 +145,8 @@ export function transformJoinResults(rows, operations) {
|
|
|
63
145
|
transformed[joinMany.as] = parsedValue;
|
|
64
146
|
}
|
|
65
147
|
}
|
|
66
|
-
for (const
|
|
67
|
-
const jsonValue = row[
|
|
148
|
+
for (const joinThroughMany of joinThroughManyOperations) {
|
|
149
|
+
const jsonValue = row[joinThroughMany.as];
|
|
68
150
|
let parsedValue;
|
|
69
151
|
if (jsonValue !== null && jsonValue !== undefined) {
|
|
70
152
|
parsedValue = typeof jsonValue === 'string'
|
|
@@ -74,18 +156,20 @@ export function transformJoinResults(rows, operations) {
|
|
|
74
156
|
else {
|
|
75
157
|
parsedValue = [];
|
|
76
158
|
}
|
|
77
|
-
if (
|
|
78
|
-
const [tableAlias] =
|
|
159
|
+
if (joinThroughMany.localField.includes('.')) {
|
|
160
|
+
const [tableAlias] = joinThroughMany.localField.split('.');
|
|
79
161
|
const relatedJoin = joinOperations.find(j => j.as === tableAlias);
|
|
80
|
-
|
|
81
|
-
|
|
162
|
+
const relatedJoinThrough = joinThroughOperations.find(j => j.as === tableAlias);
|
|
163
|
+
if ((relatedJoin && transformed[relatedJoin.as]) || (relatedJoinThrough && transformed[relatedJoinThrough.as])) {
|
|
164
|
+
const targetAlias = relatedJoin ? relatedJoin.as : relatedJoinThrough.as;
|
|
165
|
+
transformed[targetAlias][joinThroughMany.as] = parsedValue;
|
|
82
166
|
}
|
|
83
167
|
else {
|
|
84
|
-
transformed[
|
|
168
|
+
transformed[joinThroughMany.as] = parsedValue;
|
|
85
169
|
}
|
|
86
170
|
}
|
|
87
171
|
else {
|
|
88
|
-
transformed[
|
|
172
|
+
transformed[joinThroughMany.as] = parsedValue;
|
|
89
173
|
}
|
|
90
174
|
}
|
|
91
175
|
return transformed;
|