@dwtechs/antity-pgsql 0.17.2 → 0.17.4

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 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
- List of possible match modes :
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
  | :---------- | :---- | :---------------------- | :-------------------------------------------------------- |
@@ -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;
@@ -24,7 +24,7 @@ SOFTWARE.
24
24
  https://github.com/DWTechs/Antity-pgsql.js
25
25
  */
26
26
 
27
- import { isArray, isString, isIn } from '@dwtechs/checkard';
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.includes(word.toLowerCase()))
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 = ((_b = (_a = filterArray[0]) === null || _a === void 0 ? void 0 : _a.operator) === null || _b === void 0 ? void 0 : _b.toUpperCase()) || 'AND';
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
- constructor() {
248
- this._props = [];
249
- this._cols = "";
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
- constructor() {
290
- this._props = [];
291
- this._quotedProps = [];
292
- this._nbProps = 0;
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
- constructor() {
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
- if (consumerId !== undefined && consumerName !== undefined)
356
- rows = this.addConsumer(rows, consumerId, consumerName);
344
+ const hasConsumer = consumerId !== undefined && consumerName !== undefined;
357
345
  const propsToUse = [...this._props];
358
- if (consumerId !== undefined && consumerName !== undefined)
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
- if (rows[0][p] === undefined)
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 = p === "consumerId" ? '"updaterId"' : p === "consumerName" ? '"updaterName"' : quoteIfUppercase(p);
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
- args.push(row[p]);
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 __awaiter$3(this, void 0, void 0, function* () {
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
- constructor() {
393
- this._props = [];
394
- this._quotedProps = [];
395
- this._nbProps = 0;
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 __awaiter$2(this, void 0, void 0, function* () {
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
- return __awaiter$1(this, void 0, void 0, function* () {
508
- let db;
509
- try {
510
- db = yield execute(query, [schema, table, date], client);
511
- }
512
- catch (err) {
513
- throw err;
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 isIn(matchModes[type], matchMode);
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.2",
3
+ "version": "0.17.4",
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,39 +21,36 @@
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:mjs": "rollup --config rollup.config.mjs",
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",
41
- "@dwtechs/antity": "0.16.0",
39
+ "@dwtechs/antity": "0.17.0",
42
40
  "@dwtechs/sparray": "0.2.1",
43
41
  "pg": "8.20.0"
44
42
  },
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": "22.10.1",
52
- "@types/pg-pool": "2.0.6",
53
- "@types/express": "5.0.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": "5.6.3"
54
+ "typescript": "6.0.3"
59
55
  }
60
56
  }