@dwtechs/antity-pgsql 0.17.2 → 0.17.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/dist/antity-pgsql.d.ts +4 -3
- package/dist/antity-pgsql.js +321 -369
- package/package.json +12 -16
package/README.md
CHANGED
|
@@ -150,6 +150,10 @@ type Operation = "SELECT" | "INSERT" | "UPDATE";
|
|
|
150
150
|
|
|
151
151
|
type Row = Record<string, string | number | boolean | Date | number[]>;
|
|
152
152
|
|
|
153
|
+
type Comparator =
|
|
154
|
+
"=" | "<" | ">" | "<=" | ">=" | "<>" |
|
|
155
|
+
"IS" | "IS NOT" | "IN" | "NOT IN" | "LIKE" | "NOT LIKE";
|
|
156
|
+
|
|
153
157
|
type MatchMode =
|
|
154
158
|
"startsWith" |
|
|
155
159
|
"endsWith" |
|
|
@@ -169,7 +173,8 @@ type MatchMode =
|
|
|
169
173
|
"before" |
|
|
170
174
|
"after" |
|
|
171
175
|
"st_contains" |
|
|
172
|
-
"st_dwithin"
|
|
176
|
+
"st_dwithin" |
|
|
177
|
+
Comparator; // direct SQL comparators are also accepted
|
|
173
178
|
|
|
174
179
|
|
|
175
180
|
type Filters = {
|
|
@@ -178,7 +183,7 @@ type Filters = {
|
|
|
178
183
|
|
|
179
184
|
type Filter = {
|
|
180
185
|
value: string | number | boolean | Date | number[];
|
|
181
|
-
matchMode?: MatchMode;
|
|
186
|
+
matchMode?: MatchMode; // semantic mode or direct SQL comparator
|
|
182
187
|
operator?: string; // 'and' | 'or' - Used when multiple filters apply to the same property
|
|
183
188
|
}
|
|
184
189
|
|
|
@@ -510,6 +515,12 @@ const filters = {
|
|
|
510
515
|
age: { value: 30, matchMode: 'equals' },
|
|
511
516
|
archived: { value: false, matchMode: 'equals' }
|
|
512
517
|
};
|
|
518
|
+
|
|
519
|
+
// Direct SQL comparators are also accepted
|
|
520
|
+
const filters = {
|
|
521
|
+
age: { value: 30, matchMode: '>=' },
|
|
522
|
+
status: { value: null, matchMode: 'IS NOT' }
|
|
523
|
+
};
|
|
513
524
|
```
|
|
514
525
|
|
|
515
526
|
#### Complex Format (Multiple Filters per Property)
|
|
@@ -549,7 +560,11 @@ WHERE (name LIKE '%John%' OR name LIKE '%Jane%')
|
|
|
549
560
|
|
|
550
561
|
## Match modes
|
|
551
562
|
|
|
552
|
-
|
|
563
|
+
`matchMode` accepts either a **semantic match mode** (listed below) or a **direct SQL comparator** (`=`, `<`, `>`, `<=`, `>=`, `<>`, `IS`, `IS NOT`, `IN`, `NOT IN`, `LIKE`, `NOT LIKE`).
|
|
564
|
+
|
|
565
|
+
Using a direct comparator bypasses the semantic layer. Note that when using `LIKE` or `NOT LIKE` directly, wildcard characters (`%`) must be included manually in the value.
|
|
566
|
+
|
|
567
|
+
List of possible semantic match modes :
|
|
553
568
|
|
|
554
569
|
| Name | alias | types | Description |
|
|
555
570
|
| :---------- | :---- | :---------------------- | :-------------------------------------------------------- |
|
package/dist/antity-pgsql.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ import type { Request, Response, NextFunction } from 'express';
|
|
|
31
31
|
export type Operation = "SELECT" | "INSERT" | "UPDATE";
|
|
32
32
|
export type Sort = "ASC" | "DESC";
|
|
33
33
|
export type Filters = {
|
|
34
|
-
[key: string]: Filter;
|
|
34
|
+
[key: string]: Filter | Filter[];
|
|
35
35
|
};
|
|
36
36
|
export type Filter = {
|
|
37
37
|
value: string | number | boolean | Date | number[];
|
|
@@ -60,8 +60,8 @@ export declare class Property extends BaseProperty {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export type LogicalOperator = "AND" | "OR";
|
|
63
|
-
export type Comparator = "=" | "<" | ">" | "<=" | ">=" | "<>" | "IS" | "IS NOT" | "IN" | "LIKE" | "NOT LIKE";
|
|
64
|
-
export type MatchMode = "startsWith" | "endsWith" | "contains" | "notContains" | "equals" | "notEquals" | "between" | "in" | "lt" | "lte" | "gt" | "gte" | "is" | "isNot" | "before" | "after" | "st_contains" | "st_dwithin";
|
|
63
|
+
export type Comparator = "=" | "<" | ">" | "<=" | ">=" | "<>" | "IS" | "IS NOT" | "IN" | "NOT IN" | "LIKE" | "NOT LIKE";
|
|
64
|
+
export type MatchMode = "startsWith" | "endsWith" | "contains" | "notContains" | "equals" | "notEquals" | "between" | "in" | "notIn" | "lt" | "lte" | "gt" | "gte" | "is" | "isNot" | "before" | "after" | "st_contains" | "st_dwithin" | Comparator;
|
|
65
65
|
export type MappedType = "string" | "number" | "date";
|
|
66
66
|
export type Geometry = {
|
|
67
67
|
lng: number;
|
|
@@ -96,6 +96,7 @@ export type PGResponse = {
|
|
|
96
96
|
_types?: unknown;
|
|
97
97
|
RowCtor?: unknown;
|
|
98
98
|
rowAsArray?: boolean;
|
|
99
|
+
_prebuiltEmptyResultObject?: Record<string, unknown>;
|
|
99
100
|
};
|
|
100
101
|
|
|
101
102
|
type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
package/dist/antity-pgsql.js
CHANGED
|
@@ -24,7 +24,7 @@ SOFTWARE.
|
|
|
24
24
|
https://github.com/DWTechs/Antity-pgsql.js
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import { isArray, isString
|
|
27
|
+
import { isArray, isString } from '@dwtechs/checkard';
|
|
28
28
|
import { deleteProps, chunk, flatten } from '@dwtechs/sparray';
|
|
29
29
|
import { log } from '@dwtechs/winstan';
|
|
30
30
|
import { Entity } from '@dwtechs/antity';
|
|
@@ -84,7 +84,7 @@ function deleteIdleProperties(res) {
|
|
|
84
84
|
res._prebuiltEmptyResultObject = undefined;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const reserved = [
|
|
87
|
+
const reserved = new Set([
|
|
88
88
|
'all', 'analyse', 'analyze', 'and', 'any', 'array', 'as', 'asc', 'asymmetric',
|
|
89
89
|
'authorization', 'between', 'binary', 'both', 'case', 'cast', 'check', 'collate',
|
|
90
90
|
'column', 'constraint', 'create', 'cross', 'current_catalog', 'current_date',
|
|
@@ -97,9 +97,9 @@ const reserved = [
|
|
|
97
97
|
'references', 'returning', 'right', 'select', 'session_user', 'similar', 'some', 'symmetric',
|
|
98
98
|
'table', 'tablesample', 'then', 'to', 'trailing', 'true', 'union', 'unique', 'user', 'using',
|
|
99
99
|
'variadic', 'verbose', 'when', 'where', 'window', 'with'
|
|
100
|
-
];
|
|
100
|
+
]);
|
|
101
101
|
function quoteIfUppercase(word) {
|
|
102
|
-
if (/[A-Z]/.test(word) || reserved.
|
|
102
|
+
if (/[A-Z]/.test(word) || reserved.has(word.toLowerCase()))
|
|
103
103
|
return `"${word}"`;
|
|
104
104
|
return word;
|
|
105
105
|
}
|
|
@@ -109,13 +109,18 @@ function index(index, matchMode) {
|
|
|
109
109
|
switch (matchMode) {
|
|
110
110
|
case "in":
|
|
111
111
|
case "notIn":
|
|
112
|
+
case "IN":
|
|
113
|
+
case "NOT IN":
|
|
112
114
|
return `(${i})`;
|
|
113
115
|
default:
|
|
114
116
|
return `${i}`;
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
|
|
120
|
+
const COMPARATORS = new Set(["=", "<", ">", "<=", ">=", "<>", "IS", "IS NOT", "IN", "NOT IN", "LIKE", "NOT LIKE"]);
|
|
118
121
|
function comparator(matchMode) {
|
|
122
|
+
if (matchMode && COMPARATORS.has(matchMode))
|
|
123
|
+
return matchMode;
|
|
119
124
|
switch (matchMode) {
|
|
120
125
|
case "startsWith":
|
|
121
126
|
return "LIKE";
|
|
@@ -174,12 +179,11 @@ function shouldSkipValue(value, matchMode) {
|
|
|
174
179
|
return true;
|
|
175
180
|
if (isArray(value, "0"))
|
|
176
181
|
return true;
|
|
177
|
-
if (value === null && matchMode !== 'is' && matchMode !== 'isNot')
|
|
182
|
+
if (value === null && matchMode !== 'is' && matchMode !== 'isNot' && matchMode !== 'IS' && matchMode !== 'IS NOT')
|
|
178
183
|
return true;
|
|
179
184
|
return false;
|
|
180
185
|
}
|
|
181
186
|
function add(filters) {
|
|
182
|
-
var _a, _b;
|
|
183
187
|
const conditions = [];
|
|
184
188
|
const args = [];
|
|
185
189
|
if (filters) {
|
|
@@ -203,7 +207,7 @@ function add(filters) {
|
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
if (groupConditions.length > 0) {
|
|
206
|
-
const operator =
|
|
210
|
+
const operator = filterArray[0]?.operator?.toUpperCase() || 'AND';
|
|
207
211
|
const combined = groupConditions.length > 1
|
|
208
212
|
? `(${groupConditions.join(` ${operator} `)})`
|
|
209
213
|
: groupConditions[0];
|
|
@@ -244,11 +248,9 @@ function limit(rows, first) {
|
|
|
244
248
|
}
|
|
245
249
|
|
|
246
250
|
class Select {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
this._count = ", COUNT(*) OVER () AS total";
|
|
251
|
-
}
|
|
251
|
+
_props = [];
|
|
252
|
+
_cols = "";
|
|
253
|
+
_count = ", COUNT(*) OVER () AS total";
|
|
252
254
|
addProp(prop) {
|
|
253
255
|
this._props.push(quoteIfUppercase(prop));
|
|
254
256
|
this._cols = this._props.join(", ");
|
|
@@ -286,12 +288,10 @@ function $i(qty, start) {
|
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
class Insert {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
this._cols = "";
|
|
294
|
-
}
|
|
291
|
+
_props = [];
|
|
292
|
+
_quotedProps = [];
|
|
293
|
+
_nbProps = 0;
|
|
294
|
+
_cols = "";
|
|
295
295
|
addProp(prop) {
|
|
296
296
|
this._props.push(prop);
|
|
297
297
|
this._quotedProps.push(quoteIfUppercase(prop));
|
|
@@ -335,27 +335,15 @@ class Insert {
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
var __awaiter$3 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
339
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
340
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
341
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
342
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
343
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
344
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
345
|
-
});
|
|
346
|
-
};
|
|
347
338
|
class Update {
|
|
348
|
-
|
|
349
|
-
this._props = [];
|
|
350
|
-
}
|
|
339
|
+
_props = [];
|
|
351
340
|
addProp(prop) {
|
|
352
341
|
this._props.push(prop);
|
|
353
342
|
}
|
|
354
343
|
query(schema, table, rows, consumerId, consumerName) {
|
|
355
|
-
|
|
356
|
-
rows = this.addConsumer(rows, consumerId, consumerName);
|
|
344
|
+
const hasConsumer = consumerId !== undefined && consumerName !== undefined;
|
|
357
345
|
const propsToUse = [...this._props];
|
|
358
|
-
if (
|
|
346
|
+
if (hasConsumer)
|
|
359
347
|
propsToUse.push("consumerId", "consumerName");
|
|
360
348
|
log.debug(() => `${LOGS_PREFIX}Update query input rows: ${JSON.stringify(rows, null, 2)}`);
|
|
361
349
|
const l = rows.length;
|
|
@@ -363,38 +351,37 @@ class Update {
|
|
|
363
351
|
let query = `UPDATE ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} SET `;
|
|
364
352
|
let i = args.length + 1;
|
|
365
353
|
for (const p of propsToUse) {
|
|
366
|
-
|
|
354
|
+
const isConsumerId = p === "consumerId";
|
|
355
|
+
const isConsumerName = p === "consumerName";
|
|
356
|
+
const isConsumerProp = isConsumerId || isConsumerName;
|
|
357
|
+
if (!isConsumerProp && rows[0][p] === undefined)
|
|
367
358
|
continue;
|
|
368
|
-
const colName =
|
|
359
|
+
const colName = isConsumerId ? '"updaterId"' : isConsumerName ? '"updaterName"' : quoteIfUppercase(p);
|
|
369
360
|
query += `${colName} = CASE `;
|
|
370
361
|
for (let j = 0; j < l; j++) {
|
|
371
|
-
const row = rows[j];
|
|
372
362
|
query += `WHEN id = $${j + 1} THEN $${i++} `;
|
|
373
|
-
|
|
363
|
+
if (isConsumerId)
|
|
364
|
+
args.push(consumerId);
|
|
365
|
+
else if (isConsumerName)
|
|
366
|
+
args.push(consumerName);
|
|
367
|
+
else
|
|
368
|
+
args.push(rows[j][p]);
|
|
374
369
|
}
|
|
375
370
|
query += `ELSE ${colName} END, `;
|
|
376
371
|
}
|
|
377
372
|
query = `${query.slice(0, -2)} WHERE id IN ${$i(l, 0)}`;
|
|
378
373
|
return { query, args };
|
|
379
374
|
}
|
|
380
|
-
execute(query, args, client) {
|
|
381
|
-
return
|
|
382
|
-
return execute(query, args, client);
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
addConsumer(rows, consumerId, consumerName) {
|
|
386
|
-
return rows.map((row) => (Object.assign(Object.assign({}, row), { consumerId,
|
|
387
|
-
consumerName })));
|
|
375
|
+
async execute(query, args, client) {
|
|
376
|
+
return execute(query, args, client);
|
|
388
377
|
}
|
|
389
378
|
}
|
|
390
379
|
|
|
391
380
|
class Upsert {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
this._cols = "";
|
|
397
|
-
}
|
|
381
|
+
_props = [];
|
|
382
|
+
_quotedProps = [];
|
|
383
|
+
_nbProps = 0;
|
|
384
|
+
_cols = "";
|
|
398
385
|
addProp(prop) {
|
|
399
386
|
this._props.push(prop);
|
|
400
387
|
this._quotedProps.push(quoteIfUppercase(prop));
|
|
@@ -456,15 +443,6 @@ class Upsert {
|
|
|
456
443
|
}
|
|
457
444
|
}
|
|
458
445
|
|
|
459
|
-
var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
460
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
461
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
462
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
463
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
464
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
465
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
466
|
-
});
|
|
467
|
-
};
|
|
468
446
|
class Archive {
|
|
469
447
|
query(schema, table, rows, consumerId, consumerName) {
|
|
470
448
|
log.debug(() => `${LOGS_PREFIX}Archive query input rows: ${JSON.stringify(rows, null, 2)}`);
|
|
@@ -478,22 +456,11 @@ class Archive {
|
|
|
478
456
|
query += ` WHERE id IN ${$i(l, 0)}`;
|
|
479
457
|
return { query, args };
|
|
480
458
|
}
|
|
481
|
-
execute(query, args, client) {
|
|
482
|
-
return
|
|
483
|
-
return execute(query, args, client);
|
|
484
|
-
});
|
|
459
|
+
async execute(query, args, client) {
|
|
460
|
+
return execute(query, args, client);
|
|
485
461
|
}
|
|
486
462
|
}
|
|
487
463
|
|
|
488
|
-
var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
489
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
490
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
491
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
492
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
493
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
494
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
495
|
-
});
|
|
496
|
-
};
|
|
497
464
|
function queryById(schema, table, ids) {
|
|
498
465
|
return {
|
|
499
466
|
query: `DELETE FROM ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} WHERE id = ANY($1)`,
|
|
@@ -503,17 +470,15 @@ function queryById(schema, table, ids) {
|
|
|
503
470
|
function queryByDate() {
|
|
504
471
|
return `SELECT hard_delete($1, $2, $3)`;
|
|
505
472
|
}
|
|
506
|
-
function executeArchived(schema, table, date, query, client) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
return db;
|
|
516
|
-
});
|
|
473
|
+
async function executeArchived(schema, table, date, query, client) {
|
|
474
|
+
let db;
|
|
475
|
+
try {
|
|
476
|
+
db = await execute(query, [schema, table, date], client);
|
|
477
|
+
}
|
|
478
|
+
catch (err) {
|
|
479
|
+
throw err;
|
|
480
|
+
}
|
|
481
|
+
return db;
|
|
517
482
|
}
|
|
518
483
|
|
|
519
484
|
function type(type) {
|
|
@@ -575,12 +540,12 @@ function type(type) {
|
|
|
575
540
|
}
|
|
576
541
|
|
|
577
542
|
const matchModes = {
|
|
578
|
-
string: ["startsWith", "contains", "endsWith", "notContains", "equals", "notEquals", "lt", "lte", "gt", "gte", "in", "notIn"],
|
|
579
|
-
number: ["equals", "notEquals", "lt", "lte", "gt", "gte", "in", "notIn"],
|
|
580
|
-
date: ["is", "isNot", "dateAfter"],
|
|
543
|
+
string: new Set(["startsWith", "contains", "endsWith", "notContains", "equals", "notEquals", "lt", "lte", "gt", "gte", "in", "notIn"]),
|
|
544
|
+
number: new Set(["equals", "notEquals", "lt", "lte", "gt", "gte", "in", "notIn"]),
|
|
545
|
+
date: new Set(["is", "isNot", "dateAfter"]),
|
|
581
546
|
};
|
|
582
547
|
function matchMode(type, matchMode) {
|
|
583
|
-
return
|
|
548
|
+
return COMPARATORS.has(matchMode) || matchModes[type].has(matchMode);
|
|
584
549
|
}
|
|
585
550
|
|
|
586
551
|
function cleanFilters(filters, properties) {
|
|
@@ -681,290 +646,16 @@ function getCrudMappings(properties) {
|
|
|
681
646
|
return mappings;
|
|
682
647
|
}
|
|
683
648
|
|
|
684
|
-
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
685
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
686
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
687
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
688
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
689
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
690
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
691
|
-
});
|
|
692
|
-
};
|
|
693
649
|
class SQLEntity extends Entity {
|
|
650
|
+
_table;
|
|
651
|
+
_schema;
|
|
652
|
+
sel = new Select();
|
|
653
|
+
ins = new Insert();
|
|
654
|
+
upd = new Update();
|
|
655
|
+
ups = new Upsert();
|
|
656
|
+
arc = new Archive();
|
|
694
657
|
constructor(name, properties, schema = 'public') {
|
|
695
658
|
super(name, properties);
|
|
696
|
-
this.sel = new Select();
|
|
697
|
-
this.ins = new Insert();
|
|
698
|
-
this.upd = new Update();
|
|
699
|
-
this.ups = new Upsert();
|
|
700
|
-
this.arc = new Archive();
|
|
701
|
-
this.query = {
|
|
702
|
-
select: (first = 0, rows = null, sortField = null, sortOrder = null, filters = null) => {
|
|
703
|
-
return this.sel.query(this.schema, this.table, first, rows, sortField, sortOrder, filters);
|
|
704
|
-
},
|
|
705
|
-
update: (rows, consumer) => {
|
|
706
|
-
return this.upd.query(this.schema, this.table, rows, consumer === null || consumer === void 0 ? void 0 : consumer.id, consumer === null || consumer === void 0 ? void 0 : consumer.nickname);
|
|
707
|
-
},
|
|
708
|
-
insert: (rows, consumer, rtn = "") => {
|
|
709
|
-
return this.ins.query(this.schema, this.table, rows, consumer === null || consumer === void 0 ? void 0 : consumer.id, consumer === null || consumer === void 0 ? void 0 : consumer.nickname, rtn);
|
|
710
|
-
},
|
|
711
|
-
upsert: (rows, conflictTarget, consumer, rtn = "") => {
|
|
712
|
-
return this.ups.query(this.schema, this.table, rows, conflictTarget, consumer === null || consumer === void 0 ? void 0 : consumer.id, consumer === null || consumer === void 0 ? void 0 : consumer.nickname, rtn);
|
|
713
|
-
},
|
|
714
|
-
delete: (ids) => {
|
|
715
|
-
return queryById(this.schema, this.table, ids);
|
|
716
|
-
},
|
|
717
|
-
archive: (rows, consumer) => {
|
|
718
|
-
return this.arc.query(this.schema, this.table, rows, consumer === null || consumer === void 0 ? void 0 : consumer.id, consumer === null || consumer === void 0 ? void 0 : consumer.nickname);
|
|
719
|
-
},
|
|
720
|
-
deleteArchive: () => {
|
|
721
|
-
return queryByDate();
|
|
722
|
-
},
|
|
723
|
-
return: (prop) => {
|
|
724
|
-
return this.ins.rtn(prop);
|
|
725
|
-
}
|
|
726
|
-
};
|
|
727
|
-
this.get = (req, res, next) => {
|
|
728
|
-
var _a;
|
|
729
|
-
const l = res.locals;
|
|
730
|
-
const b = req.body;
|
|
731
|
-
const first = (_a = b === null || b === void 0 ? void 0 : b.first) !== null && _a !== void 0 ? _a : 0;
|
|
732
|
-
const rows = b.rows || null;
|
|
733
|
-
const sortField = b.sortField || null;
|
|
734
|
-
const sortOrder = b.sortOrder === -1 || b.sortOrder === "DESC" ? "DESC" : "ASC";
|
|
735
|
-
const filters = cleanFilters(b.filters, this.properties) || null;
|
|
736
|
-
const pagination = b.pagination || false;
|
|
737
|
-
const dbClient = l.dbClient || null;
|
|
738
|
-
log.debug(() => `get(first='${first}', rows='${rows}', sortOrder='${sortOrder}', sortField='${sortField}', pagination=${pagination}, filters=${JSON.stringify(filters)}`);
|
|
739
|
-
const { query, args } = this.sel.query(this._schema, this._table, first, rows, sortField, sortOrder, filters);
|
|
740
|
-
this.sel.execute(query, args, dbClient)
|
|
741
|
-
.then((r) => {
|
|
742
|
-
l.rows = r.rows;
|
|
743
|
-
l.total = r.total;
|
|
744
|
-
next();
|
|
745
|
-
})
|
|
746
|
-
.catch((err) => next(err));
|
|
747
|
-
};
|
|
748
|
-
this.add = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
749
|
-
var _a, _b;
|
|
750
|
-
const l = res.locals;
|
|
751
|
-
const rows = req.body.rows;
|
|
752
|
-
const dbClient = l.dbClient || null;
|
|
753
|
-
const cId = (_a = l.consumer) === null || _a === void 0 ? void 0 : _a.id;
|
|
754
|
-
const cName = (_b = l.consumer) === null || _b === void 0 ? void 0 : _b.nickname;
|
|
755
|
-
log.debug(() => `${LOGS_PREFIX}addMany(rows=${rows.length}, consumerId=${cId})`);
|
|
756
|
-
const rtn = this.ins.rtn("id");
|
|
757
|
-
const chunks = chunk(rows);
|
|
758
|
-
for (const c of chunks) {
|
|
759
|
-
const { query, args } = this.ins.query(this._schema, this._table, c, cId, cName, rtn);
|
|
760
|
-
let db;
|
|
761
|
-
try {
|
|
762
|
-
db = yield execute(query, args, dbClient);
|
|
763
|
-
}
|
|
764
|
-
catch (err) {
|
|
765
|
-
return next(err);
|
|
766
|
-
}
|
|
767
|
-
const r = db.rows;
|
|
768
|
-
for (let i = 0; i < c.length; i++) {
|
|
769
|
-
c[i].id = r[i].id;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
l.rows = flatten(chunks);
|
|
773
|
-
next();
|
|
774
|
-
});
|
|
775
|
-
this.update = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
776
|
-
var _a, _b;
|
|
777
|
-
const l = res.locals;
|
|
778
|
-
const r = req.body.rows;
|
|
779
|
-
const dbClient = l.dbClient || null;
|
|
780
|
-
const cId = (_a = l.consumer) === null || _a === void 0 ? void 0 : _a.id;
|
|
781
|
-
const cName = (_b = l.consumer) === null || _b === void 0 ? void 0 : _b.nickname;
|
|
782
|
-
log.debug(() => `${LOGS_PREFIX}update(rows=${r.length}, consumerId=${cId})`);
|
|
783
|
-
const chunks = chunk(r);
|
|
784
|
-
for (const c of chunks) {
|
|
785
|
-
const { query, args } = this.upd.query(this._schema, this._table, c, cId, cName);
|
|
786
|
-
try {
|
|
787
|
-
yield execute(query, args, dbClient);
|
|
788
|
-
}
|
|
789
|
-
catch (err) {
|
|
790
|
-
return next(err);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
l.rows = r;
|
|
794
|
-
next();
|
|
795
|
-
});
|
|
796
|
-
this.upsert = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
797
|
-
var _a, _b;
|
|
798
|
-
const l = res.locals;
|
|
799
|
-
const rows = req.body.rows;
|
|
800
|
-
const conflictTarget = req.body.conflictTarget;
|
|
801
|
-
const dbClient = l.dbClient || null;
|
|
802
|
-
const cId = (_a = l.consumer) === null || _a === void 0 ? void 0 : _a.id;
|
|
803
|
-
const cName = (_b = l.consumer) === null || _b === void 0 ? void 0 : _b.nickname;
|
|
804
|
-
if (!conflictTarget) {
|
|
805
|
-
return next({ status: 400, msg: "Missing conflictTarget for upsert operation" });
|
|
806
|
-
}
|
|
807
|
-
if (!rows || !Array.isArray(rows) || rows.length === 0) {
|
|
808
|
-
return next({ status: 400, msg: "Missing or empty rows array for upsert operation" });
|
|
809
|
-
}
|
|
810
|
-
log.debug(() => `${LOGS_PREFIX}upsert(rows=${rows.length}, conflictTarget=${conflictTarget}, consumerId=${cId})`);
|
|
811
|
-
const rtn = this.ups.rtn("id");
|
|
812
|
-
const chunks = chunk(rows);
|
|
813
|
-
for (const c of chunks) {
|
|
814
|
-
const { query, args } = this.ups.query(this._schema, this._table, c, conflictTarget, cId, cName, rtn);
|
|
815
|
-
let db;
|
|
816
|
-
try {
|
|
817
|
-
db = yield execute(query, args, dbClient);
|
|
818
|
-
}
|
|
819
|
-
catch (err) {
|
|
820
|
-
return next(err);
|
|
821
|
-
}
|
|
822
|
-
const r = db.rows;
|
|
823
|
-
for (let i = 0; i < c.length; i++) {
|
|
824
|
-
c[i].id = r[i].id;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
l.rows = flatten(chunks);
|
|
828
|
-
next();
|
|
829
|
-
});
|
|
830
|
-
this.archive = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
831
|
-
var _a, _b;
|
|
832
|
-
const l = res.locals;
|
|
833
|
-
const r = req.body.rows;
|
|
834
|
-
const dbClient = l.dbClient || null;
|
|
835
|
-
const cId = (_a = l.consumer) === null || _a === void 0 ? void 0 : _a.id;
|
|
836
|
-
const cName = (_b = l.consumer) === null || _b === void 0 ? void 0 : _b.nickname;
|
|
837
|
-
log.debug(() => `${LOGS_PREFIX}archive(rows=${r.length}, consumerId=${cId})`);
|
|
838
|
-
const chunks = chunk(r);
|
|
839
|
-
for (const c of chunks) {
|
|
840
|
-
const { query, args } = this.arc.query(this._schema, this._table, c, cId, cName);
|
|
841
|
-
try {
|
|
842
|
-
yield execute(query, args, dbClient);
|
|
843
|
-
}
|
|
844
|
-
catch (err) {
|
|
845
|
-
return next(err);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
next();
|
|
849
|
-
});
|
|
850
|
-
this.delete = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
851
|
-
const rows = req.body.rows;
|
|
852
|
-
const dbClient = res.locals.dbClient || null;
|
|
853
|
-
const ids = rows.map((row) => row.id);
|
|
854
|
-
log.debug(() => `${LOGS_PREFIX}delete ${rows.length} rows : (${ids.join(", ")})`);
|
|
855
|
-
const { query, args } = queryById(this._schema, this._table, ids);
|
|
856
|
-
try {
|
|
857
|
-
yield execute(query, args, dbClient);
|
|
858
|
-
}
|
|
859
|
-
catch (err) {
|
|
860
|
-
return next(err);
|
|
861
|
-
}
|
|
862
|
-
next();
|
|
863
|
-
});
|
|
864
|
-
this.deleteArchive = (req, res, next) => {
|
|
865
|
-
const date = req.body.date;
|
|
866
|
-
const dbClient = res.locals.dbClient || null;
|
|
867
|
-
log.debug(() => `${LOGS_PREFIX}deleteArchive(schema=${this._schema}, table=${this._table}, date=${date})`);
|
|
868
|
-
executeArchived(this._schema, this._table, date, queryByDate(), dbClient)
|
|
869
|
-
.then(() => next())
|
|
870
|
-
.catch((err) => next(err));
|
|
871
|
-
};
|
|
872
|
-
this.getHistory = (req, res, next) => {
|
|
873
|
-
const id = req.params.id;
|
|
874
|
-
const dbClient = res.locals.dbClient || null;
|
|
875
|
-
if (!id) {
|
|
876
|
-
next({ status: 400, msg: "Missing id" });
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
log.debug(() => `${LOGS_PREFIX}getHistory(schema=${this._schema}, table=${this._table}, id=${id})`);
|
|
880
|
-
const sql = `
|
|
881
|
-
SELECT id, tstamp, operation, "consumerId", "consumerName"
|
|
882
|
-
FROM log.history
|
|
883
|
-
WHERE "schemaName" = $1
|
|
884
|
-
AND "tableName" = $2
|
|
885
|
-
AND CAST(record->>'id' AS INT) = $3
|
|
886
|
-
ORDER BY tstamp ASC
|
|
887
|
-
`;
|
|
888
|
-
execute(sql, [this._schema, this._table, id], dbClient)
|
|
889
|
-
.then((r) => {
|
|
890
|
-
const { rowCount, rows } = r;
|
|
891
|
-
if (!rowCount) {
|
|
892
|
-
return next({ status: 404, msg: "History not found" });
|
|
893
|
-
}
|
|
894
|
-
res.locals.history = rows;
|
|
895
|
-
res.locals.total = rowCount;
|
|
896
|
-
next();
|
|
897
|
-
})
|
|
898
|
-
.catch((err) => next(err));
|
|
899
|
-
};
|
|
900
|
-
this.sync = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
901
|
-
var _a, _b, _c;
|
|
902
|
-
const l = res.locals;
|
|
903
|
-
const rows = req.body.rows;
|
|
904
|
-
const idField = (_a = req.body.idField) !== null && _a !== void 0 ? _a : 'id';
|
|
905
|
-
const cId = (_b = l.consumer) === null || _b === void 0 ? void 0 : _b.id;
|
|
906
|
-
const cName = (_c = l.consumer) === null || _c === void 0 ? void 0 : _c.nickname;
|
|
907
|
-
if (!rows || !Array.isArray(rows)) {
|
|
908
|
-
return next({ status: 400, msg: "Missing or invalid rows array for sync operation" });
|
|
909
|
-
}
|
|
910
|
-
log.debug(() => `${LOGS_PREFIX}sync(rows=${rows.length}, idField=${idField}, consumerId=${cId})`);
|
|
911
|
-
const cleanedFilters = cleanFilters(req.body.filters, this.properties) || null;
|
|
912
|
-
const { conditions, args: filterArgs } = add(cleanedFilters);
|
|
913
|
-
const whereClause = conditions.length ? ` WHERE ${conditions.join(' AND ')}` : '';
|
|
914
|
-
const txClient = l.dbClient || (yield pool.connect());
|
|
915
|
-
let toInsert = [];
|
|
916
|
-
let toUpdate = [];
|
|
917
|
-
let idsToDelete = [];
|
|
918
|
-
try {
|
|
919
|
-
yield txClient.query('BEGIN');
|
|
920
|
-
const selectIdQuery = `SELECT ${quoteIfUppercase(idField)} FROM ${quoteIfUppercase(this._schema)}.${quoteIfUppercase(this._table)}${whereClause}`;
|
|
921
|
-
const existingDb = yield execute(selectIdQuery, filterArgs, txClient);
|
|
922
|
-
const existingIds = new Set(existingDb.rows.map(r => r[idField]));
|
|
923
|
-
const incomingIds = new Set(rows.filter(r => r[idField] != null).map(r => r[idField]));
|
|
924
|
-
toInsert = rows.filter(r => r[idField] == null || !existingIds.has(r[idField]));
|
|
925
|
-
toUpdate = rows.filter(r => r[idField] != null && existingIds.has(r[idField]));
|
|
926
|
-
idsToDelete = [...existingIds].filter(id => !incomingIds.has(id));
|
|
927
|
-
if (toInsert.length > 0) {
|
|
928
|
-
const rtn = this.ins.rtn(idField);
|
|
929
|
-
const chunks = chunk(toInsert);
|
|
930
|
-
for (const c of chunks) {
|
|
931
|
-
const { query, args } = this.ins.query(this._schema, this._table, c, cId, cName, rtn);
|
|
932
|
-
const db = yield execute(query, args, txClient);
|
|
933
|
-
const r = db.rows;
|
|
934
|
-
for (let i = 0; i < c.length; i++) {
|
|
935
|
-
c[i][idField] = r[i][idField];
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
if (toUpdate.length > 0) {
|
|
940
|
-
const chunks = chunk(toUpdate);
|
|
941
|
-
for (const c of chunks) {
|
|
942
|
-
const { query, args } = this.upd.query(this._schema, this._table, c, cId, cName);
|
|
943
|
-
yield execute(query, args, txClient);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
if (idsToDelete.length > 0) {
|
|
947
|
-
const deleteArgs = [idsToDelete, ...filterArgs];
|
|
948
|
-
const scopedWhere = conditions.length
|
|
949
|
-
? ` AND ${conditions.map(c => c.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + 1}`)).join(' AND ')}`
|
|
950
|
-
: '';
|
|
951
|
-
const deleteQuery = `DELETE FROM ${quoteIfUppercase(this._schema)}.${quoteIfUppercase(this._table)} WHERE ${quoteIfUppercase(idField)} = ANY($1)${scopedWhere}`;
|
|
952
|
-
yield execute(deleteQuery, deleteArgs, txClient);
|
|
953
|
-
}
|
|
954
|
-
yield txClient.query('COMMIT');
|
|
955
|
-
}
|
|
956
|
-
catch (err) {
|
|
957
|
-
yield txClient.query('ROLLBACK');
|
|
958
|
-
return next(err);
|
|
959
|
-
}
|
|
960
|
-
finally {
|
|
961
|
-
if (!l.dbClient)
|
|
962
|
-
txClient.release();
|
|
963
|
-
}
|
|
964
|
-
l.rows = rows;
|
|
965
|
-
l.sync = { inserted: toInsert.length, updated: toUpdate.length, deleted: idsToDelete.length };
|
|
966
|
-
next();
|
|
967
|
-
});
|
|
968
659
|
this._table = name;
|
|
969
660
|
this._schema = schema;
|
|
970
661
|
log.info(() => `${LOGS_PREFIX}Creating SQLEntity: "${name}"`);
|
|
@@ -1013,6 +704,267 @@ class SQLEntity extends Entity {
|
|
|
1013
704
|
get syncArraySubstack() {
|
|
1014
705
|
return [this.normalizeArray, this.validateArray, this.sync];
|
|
1015
706
|
}
|
|
707
|
+
query = {
|
|
708
|
+
select: (first = 0, rows = null, sortField = null, sortOrder = null, filters = null) => {
|
|
709
|
+
return this.sel.query(this.schema, this.table, first, rows, sortField, sortOrder, filters);
|
|
710
|
+
},
|
|
711
|
+
update: (rows, consumer) => {
|
|
712
|
+
return this.upd.query(this.schema, this.table, rows, consumer?.id, consumer?.nickname);
|
|
713
|
+
},
|
|
714
|
+
insert: (rows, consumer, rtn = "") => {
|
|
715
|
+
return this.ins.query(this.schema, this.table, rows, consumer?.id, consumer?.nickname, rtn);
|
|
716
|
+
},
|
|
717
|
+
upsert: (rows, conflictTarget, consumer, rtn = "") => {
|
|
718
|
+
return this.ups.query(this.schema, this.table, rows, conflictTarget, consumer?.id, consumer?.nickname, rtn);
|
|
719
|
+
},
|
|
720
|
+
delete: (ids) => {
|
|
721
|
+
return queryById(this.schema, this.table, ids);
|
|
722
|
+
},
|
|
723
|
+
archive: (rows, consumer) => {
|
|
724
|
+
return this.arc.query(this.schema, this.table, rows, consumer?.id, consumer?.nickname);
|
|
725
|
+
},
|
|
726
|
+
deleteArchive: () => {
|
|
727
|
+
return queryByDate();
|
|
728
|
+
},
|
|
729
|
+
return: (prop) => {
|
|
730
|
+
return this.ins.rtn(prop);
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
get = (req, res, next) => {
|
|
734
|
+
const l = res.locals;
|
|
735
|
+
const b = req.body;
|
|
736
|
+
const first = b?.first ?? 0;
|
|
737
|
+
const rows = b.rows || null;
|
|
738
|
+
const sortField = b.sortField || null;
|
|
739
|
+
const sortOrder = b.sortOrder === -1 || b.sortOrder === "DESC" ? "DESC" : "ASC";
|
|
740
|
+
const filters = cleanFilters(b.filters, this.properties) || null;
|
|
741
|
+
const pagination = b.pagination || false;
|
|
742
|
+
const dbClient = l.dbClient || null;
|
|
743
|
+
log.debug(() => `get(first='${first}', rows='${rows}', sortOrder='${sortOrder}', sortField='${sortField}', pagination=${pagination}, filters=${JSON.stringify(filters)}`);
|
|
744
|
+
const { query, args } = this.sel.query(this._schema, this._table, first, rows, sortField, sortOrder, filters);
|
|
745
|
+
this.sel.execute(query, args, dbClient)
|
|
746
|
+
.then((r) => {
|
|
747
|
+
l.rows = r.rows;
|
|
748
|
+
l.total = r.total;
|
|
749
|
+
next();
|
|
750
|
+
})
|
|
751
|
+
.catch((err) => next(err));
|
|
752
|
+
};
|
|
753
|
+
add = async (req, res, next) => {
|
|
754
|
+
const l = res.locals;
|
|
755
|
+
const rows = req.body.rows;
|
|
756
|
+
const dbClient = l.dbClient || null;
|
|
757
|
+
const cId = l.consumer?.id;
|
|
758
|
+
const cName = l.consumer?.nickname;
|
|
759
|
+
log.debug(() => `${LOGS_PREFIX}addMany(rows=${rows.length}, consumerId=${cId})`);
|
|
760
|
+
const rtn = this.ins.rtn("id");
|
|
761
|
+
const chunks = chunk(rows);
|
|
762
|
+
for (const c of chunks) {
|
|
763
|
+
const { query, args } = this.ins.query(this._schema, this._table, c, cId, cName, rtn);
|
|
764
|
+
let db;
|
|
765
|
+
try {
|
|
766
|
+
db = await execute(query, args, dbClient);
|
|
767
|
+
}
|
|
768
|
+
catch (err) {
|
|
769
|
+
return next(err);
|
|
770
|
+
}
|
|
771
|
+
const r = db.rows;
|
|
772
|
+
for (let i = 0; i < c.length; i++) {
|
|
773
|
+
c[i].id = r[i].id;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
l.rows = flatten(chunks);
|
|
777
|
+
next();
|
|
778
|
+
};
|
|
779
|
+
update = async (req, res, next) => {
|
|
780
|
+
const l = res.locals;
|
|
781
|
+
const r = req.body.rows;
|
|
782
|
+
const dbClient = l.dbClient || null;
|
|
783
|
+
const cId = l.consumer?.id;
|
|
784
|
+
const cName = l.consumer?.nickname;
|
|
785
|
+
log.debug(() => `${LOGS_PREFIX}update(rows=${r.length}, consumerId=${cId})`);
|
|
786
|
+
const chunks = chunk(r);
|
|
787
|
+
for (const c of chunks) {
|
|
788
|
+
const { query, args } = this.upd.query(this._schema, this._table, c, cId, cName);
|
|
789
|
+
try {
|
|
790
|
+
await execute(query, args, dbClient);
|
|
791
|
+
}
|
|
792
|
+
catch (err) {
|
|
793
|
+
return next(err);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
l.rows = r;
|
|
797
|
+
next();
|
|
798
|
+
};
|
|
799
|
+
upsert = async (req, res, next) => {
|
|
800
|
+
const l = res.locals;
|
|
801
|
+
const rows = req.body.rows;
|
|
802
|
+
const conflictTarget = req.body.conflictTarget;
|
|
803
|
+
const dbClient = l.dbClient || null;
|
|
804
|
+
const cId = l.consumer?.id;
|
|
805
|
+
const cName = l.consumer?.nickname;
|
|
806
|
+
if (!conflictTarget) {
|
|
807
|
+
return next({ status: 400, msg: "Missing conflictTarget for upsert operation" });
|
|
808
|
+
}
|
|
809
|
+
if (!rows || !Array.isArray(rows) || rows.length === 0) {
|
|
810
|
+
return next({ status: 400, msg: "Missing or empty rows array for upsert operation" });
|
|
811
|
+
}
|
|
812
|
+
log.debug(() => `${LOGS_PREFIX}upsert(rows=${rows.length}, conflictTarget=${conflictTarget}, consumerId=${cId})`);
|
|
813
|
+
const rtn = this.ups.rtn("id");
|
|
814
|
+
const chunks = chunk(rows);
|
|
815
|
+
for (const c of chunks) {
|
|
816
|
+
const { query, args } = this.ups.query(this._schema, this._table, c, conflictTarget, cId, cName, rtn);
|
|
817
|
+
let db;
|
|
818
|
+
try {
|
|
819
|
+
db = await execute(query, args, dbClient);
|
|
820
|
+
}
|
|
821
|
+
catch (err) {
|
|
822
|
+
return next(err);
|
|
823
|
+
}
|
|
824
|
+
const r = db.rows;
|
|
825
|
+
for (let i = 0; i < c.length; i++) {
|
|
826
|
+
c[i].id = r[i].id;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
l.rows = flatten(chunks);
|
|
830
|
+
next();
|
|
831
|
+
};
|
|
832
|
+
archive = async (req, res, next) => {
|
|
833
|
+
const l = res.locals;
|
|
834
|
+
const r = req.body.rows;
|
|
835
|
+
const dbClient = l.dbClient || null;
|
|
836
|
+
const cId = l.consumer?.id;
|
|
837
|
+
const cName = l.consumer?.nickname;
|
|
838
|
+
log.debug(() => `${LOGS_PREFIX}archive(rows=${r.length}, consumerId=${cId})`);
|
|
839
|
+
const chunks = chunk(r);
|
|
840
|
+
for (const c of chunks) {
|
|
841
|
+
const { query, args } = this.arc.query(this._schema, this._table, c, cId, cName);
|
|
842
|
+
try {
|
|
843
|
+
await execute(query, args, dbClient);
|
|
844
|
+
}
|
|
845
|
+
catch (err) {
|
|
846
|
+
return next(err);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
next();
|
|
850
|
+
};
|
|
851
|
+
delete = async (req, res, next) => {
|
|
852
|
+
const rows = req.body.rows;
|
|
853
|
+
const dbClient = res.locals.dbClient || null;
|
|
854
|
+
const ids = rows.map((row) => row.id);
|
|
855
|
+
log.debug(() => `${LOGS_PREFIX}delete ${rows.length} rows : (${ids.join(", ")})`);
|
|
856
|
+
const { query, args } = queryById(this._schema, this._table, ids);
|
|
857
|
+
try {
|
|
858
|
+
await execute(query, args, dbClient);
|
|
859
|
+
}
|
|
860
|
+
catch (err) {
|
|
861
|
+
return next(err);
|
|
862
|
+
}
|
|
863
|
+
next();
|
|
864
|
+
};
|
|
865
|
+
deleteArchive = (req, res, next) => {
|
|
866
|
+
const date = req.body.date;
|
|
867
|
+
const dbClient = res.locals.dbClient || null;
|
|
868
|
+
log.debug(() => `${LOGS_PREFIX}deleteArchive(schema=${this._schema}, table=${this._table}, date=${date})`);
|
|
869
|
+
executeArchived(this._schema, this._table, date, queryByDate(), dbClient)
|
|
870
|
+
.then(() => next())
|
|
871
|
+
.catch((err) => next(err));
|
|
872
|
+
};
|
|
873
|
+
getHistory = (req, res, next) => {
|
|
874
|
+
const id = req.params.id;
|
|
875
|
+
const dbClient = res.locals.dbClient || null;
|
|
876
|
+
if (!id) {
|
|
877
|
+
next({ status: 400, msg: "Missing id" });
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
log.debug(() => `${LOGS_PREFIX}getHistory(schema=${this._schema}, table=${this._table}, id=${id})`);
|
|
881
|
+
const sql = `
|
|
882
|
+
SELECT id, tstamp, operation, "consumerId", "consumerName"
|
|
883
|
+
FROM log.history
|
|
884
|
+
WHERE "schemaName" = $1
|
|
885
|
+
AND "tableName" = $2
|
|
886
|
+
AND CAST(record->>'id' AS INT) = $3
|
|
887
|
+
ORDER BY tstamp ASC
|
|
888
|
+
`;
|
|
889
|
+
execute(sql, [this._schema, this._table, id], dbClient)
|
|
890
|
+
.then((r) => {
|
|
891
|
+
const { rowCount, rows } = r;
|
|
892
|
+
if (!rowCount) {
|
|
893
|
+
return next({ status: 404, msg: "History not found" });
|
|
894
|
+
}
|
|
895
|
+
res.locals.history = rows;
|
|
896
|
+
res.locals.total = rowCount;
|
|
897
|
+
next();
|
|
898
|
+
})
|
|
899
|
+
.catch((err) => next(err));
|
|
900
|
+
};
|
|
901
|
+
sync = async (req, res, next) => {
|
|
902
|
+
const l = res.locals;
|
|
903
|
+
const rows = req.body.rows;
|
|
904
|
+
const idField = req.body.idField ?? 'id';
|
|
905
|
+
const cId = l.consumer?.id;
|
|
906
|
+
const cName = l.consumer?.nickname;
|
|
907
|
+
if (!rows || !Array.isArray(rows)) {
|
|
908
|
+
return next({ status: 400, msg: "Missing or invalid rows array for sync operation" });
|
|
909
|
+
}
|
|
910
|
+
log.debug(() => `${LOGS_PREFIX}sync(rows=${rows.length}, idField=${idField}, consumerId=${cId})`);
|
|
911
|
+
const cleanedFilters = cleanFilters(req.body.filters, this.properties) || null;
|
|
912
|
+
const { conditions, args: filterArgs } = add(cleanedFilters);
|
|
913
|
+
const whereClause = conditions.length ? ` WHERE ${conditions.join(' AND ')}` : '';
|
|
914
|
+
const txClient = l.dbClient || await pool.connect();
|
|
915
|
+
let toInsert = [];
|
|
916
|
+
let toUpdate = [];
|
|
917
|
+
let idsToDelete = [];
|
|
918
|
+
try {
|
|
919
|
+
await txClient.query('BEGIN');
|
|
920
|
+
const selectIdQuery = `SELECT ${quoteIfUppercase(idField)} FROM ${quoteIfUppercase(this._schema)}.${quoteIfUppercase(this._table)}${whereClause}`;
|
|
921
|
+
const existingDb = await execute(selectIdQuery, filterArgs, txClient);
|
|
922
|
+
const existingIds = new Set(existingDb.rows.map(r => r[idField]));
|
|
923
|
+
const incomingIds = new Set(rows.filter(r => r[idField] != null).map(r => r[idField]));
|
|
924
|
+
toInsert = rows.filter(r => r[idField] == null || !existingIds.has(r[idField]));
|
|
925
|
+
toUpdate = rows.filter(r => r[idField] != null && existingIds.has(r[idField]));
|
|
926
|
+
idsToDelete = [...existingIds].filter(id => !incomingIds.has(id));
|
|
927
|
+
if (toInsert.length > 0) {
|
|
928
|
+
const rtn = this.ins.rtn(idField);
|
|
929
|
+
const chunks = chunk(toInsert);
|
|
930
|
+
for (const c of chunks) {
|
|
931
|
+
const { query, args } = this.ins.query(this._schema, this._table, c, cId, cName, rtn);
|
|
932
|
+
const db = await execute(query, args, txClient);
|
|
933
|
+
const r = db.rows;
|
|
934
|
+
for (let i = 0; i < c.length; i++) {
|
|
935
|
+
c[i][idField] = r[i][idField];
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (toUpdate.length > 0) {
|
|
940
|
+
const chunks = chunk(toUpdate);
|
|
941
|
+
for (const c of chunks) {
|
|
942
|
+
const { query, args } = this.upd.query(this._schema, this._table, c, cId, cName);
|
|
943
|
+
await execute(query, args, txClient);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (idsToDelete.length > 0) {
|
|
947
|
+
const deleteArgs = [idsToDelete, ...filterArgs];
|
|
948
|
+
const scopedWhere = conditions.length
|
|
949
|
+
? ` AND ${conditions.map(c => c.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + 1}`)).join(' AND ')}`
|
|
950
|
+
: '';
|
|
951
|
+
const deleteQuery = `DELETE FROM ${quoteIfUppercase(this._schema)}.${quoteIfUppercase(this._table)} WHERE ${quoteIfUppercase(idField)} = ANY($1)${scopedWhere}`;
|
|
952
|
+
await execute(deleteQuery, deleteArgs, txClient);
|
|
953
|
+
}
|
|
954
|
+
await txClient.query('COMMIT');
|
|
955
|
+
}
|
|
956
|
+
catch (err) {
|
|
957
|
+
await txClient.query('ROLLBACK');
|
|
958
|
+
return next(err);
|
|
959
|
+
}
|
|
960
|
+
finally {
|
|
961
|
+
if (!l.dbClient)
|
|
962
|
+
txClient.release();
|
|
963
|
+
}
|
|
964
|
+
l.rows = rows;
|
|
965
|
+
l.sync = { inserted: toInsert.length, updated: toUpdate.length, deleted: idsToDelete.length };
|
|
966
|
+
next();
|
|
967
|
+
};
|
|
1016
968
|
mapProps(operations, key) {
|
|
1017
969
|
let hasInsert = false;
|
|
1018
970
|
let hasUpdate = false;
|
package/package.json
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dwtechs/antity-pgsql",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
4
4
|
"description": "Open source library to add PostgreSQL support to @dwtechs/Antity entities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"entities"
|
|
7
7
|
],
|
|
8
8
|
"homepage": "https://github.com/DWTechs/Antity-pgsql.js",
|
|
9
|
-
"main": "dist/antity-pgsql",
|
|
10
|
-
"types": "dist/antity-pgsql",
|
|
9
|
+
"main": "dist/antity-pgsql.js",
|
|
10
|
+
"types": "dist/antity-pgsql.d.ts",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
13
|
"url": "https://github.com/DWTechs/Antity-pgsql.js"
|
|
14
14
|
},
|
|
15
15
|
"bugs": {
|
|
16
|
-
"url": "https://github.com/DWTechs/Antity-pgsql.js/issues"
|
|
17
|
-
"email": ""
|
|
16
|
+
"url": "https://github.com/DWTechs/Antity-pgsql.js/issues"
|
|
18
17
|
},
|
|
19
18
|
"license": "MIT",
|
|
20
19
|
"author": {
|
|
@@ -22,19 +21,18 @@
|
|
|
22
21
|
"email": "http://www.lcluber.com/contact",
|
|
23
22
|
"url": "http://www.lcluber.com"
|
|
24
23
|
},
|
|
25
|
-
"contributors": [],
|
|
26
24
|
"scripts": {
|
|
27
|
-
"start": "",
|
|
28
25
|
"prebuild": "npm install",
|
|
29
26
|
"build": "node ./scripts/clear && tsc && npm run rollup && node ./scripts/copy && npm run test",
|
|
30
|
-
"rollup
|
|
31
|
-
"rollup:cjs": "rollup --config rollup.config.cjs.mjs",
|
|
32
|
-
"rollup": "npm run rollup:mjs",
|
|
27
|
+
"rollup": "rollup --config rollup.config.mjs",
|
|
33
28
|
"test": "jest --coverage"
|
|
34
29
|
},
|
|
35
30
|
"files": [
|
|
36
31
|
"dist/"
|
|
37
32
|
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">= 22"
|
|
35
|
+
},
|
|
38
36
|
"dependencies": {
|
|
39
37
|
"@dwtechs/checkard": "3.6.0",
|
|
40
38
|
"@dwtechs/winstan": "0.7.0",
|
|
@@ -45,16 +43,14 @@
|
|
|
45
43
|
"devDependencies": {
|
|
46
44
|
"@babel/core": "7.26.0",
|
|
47
45
|
"@babel/preset-env": "7.26.0",
|
|
48
|
-
"@rollup/plugin-commonjs": "28.0.1",
|
|
49
|
-
"@rollup/plugin-node-resolve": "15.3.0",
|
|
50
46
|
"@types/jest": "29.5.14",
|
|
51
|
-
"@types/node": "
|
|
52
|
-
"@types/pg-pool": "2.0.
|
|
53
|
-
"@types/express": "5.0.
|
|
47
|
+
"@types/node": "25.6.0",
|
|
48
|
+
"@types/pg-pool": "2.0.7",
|
|
49
|
+
"@types/express": "5.0.6",
|
|
54
50
|
"babel-jest": "29.7.0",
|
|
55
51
|
"core-js": "3.33.0",
|
|
56
52
|
"jest": "29.7.0",
|
|
57
53
|
"rollup": "4.24.0",
|
|
58
|
-
"typescript": "
|
|
54
|
+
"typescript": "6.0.3"
|
|
59
55
|
}
|
|
60
56
|
}
|