@dwtechs/antity-pgsql 0.17.4 → 0.17.6
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 +4 -3
- package/dist/antity-pgsql.d.ts +3 -3
- package/dist/antity-pgsql.js +30 -25
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
[](https://opensource.org/licenses/MIT)
|
|
3
3
|
[](https://www.npmjs.com/package/@dwtechs/antity-pgsql)
|
|
4
4
|
[](https://www.npmjs.com/package/@dwtechs/antity-pgsql)
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
- [Synopsis](#synopsis)
|
|
8
8
|
- [Support](#support)
|
|
@@ -152,7 +152,7 @@ type Row = Record<string, string | number | boolean | Date | number[]>;
|
|
|
152
152
|
|
|
153
153
|
type Comparator =
|
|
154
154
|
"=" | "<" | ">" | "<=" | ">=" | "<>" |
|
|
155
|
-
"IS" | "IS NOT" | "IN" | "NOT IN" | "LIKE" | "NOT LIKE";
|
|
155
|
+
"IS" | "IS NOT" | "IN" | "NOT IN" | "LIKE" | "NOT LIKE" | "&&";
|
|
156
156
|
|
|
157
157
|
type MatchMode =
|
|
158
158
|
"startsWith" |
|
|
@@ -164,6 +164,7 @@ type MatchMode =
|
|
|
164
164
|
"between" |
|
|
165
165
|
"in" |
|
|
166
166
|
"notIn" |
|
|
167
|
+
"&&" | // array overlap — use with array-typed columns; generates: column && ARRAY[$1,$2]
|
|
167
168
|
"lt" |
|
|
168
169
|
"lte" |
|
|
169
170
|
"gt" |
|
|
@@ -333,7 +334,7 @@ Using substacks simplifies your route definitions and ensures consistent data pr
|
|
|
333
334
|
|
|
334
335
|
### Query Methods
|
|
335
336
|
|
|
336
|
-
- **query.select()**: Generates a SELECT query. When the `rows` parameter is provided (not null), pagination is automatically enabled and the query includes `COUNT(*) OVER () AS total` to return the total number of rows. The total count is extracted from results and returned separately from the row data.
|
|
337
|
+
- **query.select()**: Generates a SELECT query. When the `rows` parameter is provided (not null), pagination is automatically enabled and the query includes `COUNT(*) OVER () AS total` to return the total number of rows. The total count is extracted from results and returned separately from the row data. The `sortField` parameter is validated against the entity's known properties; an unrecognised value is silently dropped.
|
|
337
338
|
- **query.insert()**: Generates an INSERT query. Accepts an array of `Row` objects with properties matching the entity definition. Consumer fields are appended directly to the query arguments — row objects are **not mutated**. Optionally appends `consumer.id` as `creatorId` and `consumer.nickname` as `creatorName` for audit tracking. Supports `RETURNING` clause via the `rtn` parameter.
|
|
338
339
|
- **query.update()**: Generates an UPDATE query using CASE statements. Accepts an array of `Row` objects with `id` property. Optionally appends `consumer.id` as `updaterId` and `consumer.nickname` as `updaterName` for audit tracking.
|
|
339
340
|
- **query.upsert()**: Generates an INSERT ... ON CONFLICT ... DO UPDATE query. (See [Upsert](#upsert-insert-or-update) section below.) Accepts an array of `Row` objects and a `conflictTarget` (single column name or array of column names) that defines uniqueness. If a conflict occurs on the specified column(s), the row is updated; otherwise, it is inserted. Properties are automatically included if they have both INSERT and UPDATE operations. Consumer fields are appended directly to the query arguments — row objects are **not mutated**. Optionally appends `consumer.id` as `creatorId` and `consumer.nickname` as `creatorName` for audit tracking. Supports `RETURNING` clause via the `rtn` parameter.
|
package/dist/antity-pgsql.d.ts
CHANGED
|
@@ -60,9 +60,9 @@ 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" | "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
|
-
export type MappedType = "string" | "number" | "date";
|
|
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
|
+
export type MappedType = "string" | "number" | "date" | "array";
|
|
66
66
|
export type Geometry = {
|
|
67
67
|
lng: number;
|
|
68
68
|
lat: number;
|
package/dist/antity-pgsql.js
CHANGED
|
@@ -100,7 +100,7 @@ const reserved = new Set([
|
|
|
100
100
|
]);
|
|
101
101
|
function quoteIfUppercase(word) {
|
|
102
102
|
if (/[A-Z]/.test(word) || reserved.has(word.toLowerCase()))
|
|
103
|
-
return `"${word}"`;
|
|
103
|
+
return `"${word.replace(/"/g, '""')}"`;
|
|
104
104
|
return word;
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -112,12 +112,14 @@ function index(index, matchMode) {
|
|
|
112
112
|
case "IN":
|
|
113
113
|
case "NOT IN":
|
|
114
114
|
return `(${i})`;
|
|
115
|
+
case "&&":
|
|
116
|
+
return `ARRAY[${i}]`;
|
|
115
117
|
default:
|
|
116
118
|
return `${i}`;
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
const COMPARATORS = new Set(["=", "<", ">", "<=", ">=", "<>", "IS", "IS NOT", "IN", "NOT IN", "LIKE", "NOT LIKE"]);
|
|
122
|
+
const COMPARATORS = new Set(["=", "<", ">", "<=", ">=", "<>", "IS", "IS NOT", "IN", "NOT IN", "LIKE", "NOT LIKE", "&&"]);
|
|
121
123
|
function comparator(matchMode) {
|
|
122
124
|
if (matchMode && COMPARATORS.has(matchMode))
|
|
123
125
|
return matchMode;
|
|
@@ -154,6 +156,8 @@ function comparator(matchMode) {
|
|
|
154
156
|
return "<";
|
|
155
157
|
case "after":
|
|
156
158
|
return ">";
|
|
159
|
+
case "&&":
|
|
160
|
+
return "&&";
|
|
157
161
|
default:
|
|
158
162
|
return null;
|
|
159
163
|
}
|
|
@@ -307,11 +311,11 @@ class Insert {
|
|
|
307
311
|
nbProps += 2;
|
|
308
312
|
cols += `, "creatorId", "creatorName"`;
|
|
309
313
|
}
|
|
310
|
-
let query = `INSERT INTO ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} (${cols}) VALUES `;
|
|
311
314
|
const args = [];
|
|
315
|
+
const valueParts = [];
|
|
312
316
|
let i = 0;
|
|
313
317
|
for (const row of rows) {
|
|
314
|
-
|
|
318
|
+
valueParts.push($i(nbProps, i));
|
|
315
319
|
for (const prop of propsToUse) {
|
|
316
320
|
if (prop === "consumerId")
|
|
317
321
|
args.push(consumerId);
|
|
@@ -322,7 +326,7 @@ class Insert {
|
|
|
322
326
|
}
|
|
323
327
|
i += nbProps;
|
|
324
328
|
}
|
|
325
|
-
query =
|
|
329
|
+
let query = `INSERT INTO ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} (${cols}) VALUES ${valueParts.join(", ")}`;
|
|
326
330
|
if (rtn)
|
|
327
331
|
query += ` ${rtn}`;
|
|
328
332
|
return { query, args };
|
|
@@ -348,8 +352,8 @@ class Update {
|
|
|
348
352
|
log.debug(() => `${LOGS_PREFIX}Update query input rows: ${JSON.stringify(rows, null, 2)}`);
|
|
349
353
|
const l = rows.length;
|
|
350
354
|
const args = rows.map(row => row.id);
|
|
351
|
-
let query = `UPDATE ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} SET `;
|
|
352
355
|
let i = args.length + 1;
|
|
356
|
+
const setClauses = [];
|
|
353
357
|
for (const p of propsToUse) {
|
|
354
358
|
const isConsumerId = p === "consumerId";
|
|
355
359
|
const isConsumerName = p === "consumerName";
|
|
@@ -357,9 +361,9 @@ class Update {
|
|
|
357
361
|
if (!isConsumerProp && rows[0][p] === undefined)
|
|
358
362
|
continue;
|
|
359
363
|
const colName = isConsumerId ? '"updaterId"' : isConsumerName ? '"updaterName"' : quoteIfUppercase(p);
|
|
360
|
-
|
|
364
|
+
const whenParts = [];
|
|
361
365
|
for (let j = 0; j < l; j++) {
|
|
362
|
-
|
|
366
|
+
whenParts.push(`WHEN id = $${j + 1} THEN $${i++}`);
|
|
363
367
|
if (isConsumerId)
|
|
364
368
|
args.push(consumerId);
|
|
365
369
|
else if (isConsumerName)
|
|
@@ -367,9 +371,9 @@ class Update {
|
|
|
367
371
|
else
|
|
368
372
|
args.push(rows[j][p]);
|
|
369
373
|
}
|
|
370
|
-
|
|
374
|
+
setClauses.push(`${colName} = CASE ${whenParts.join(" ")} ELSE ${colName} END`);
|
|
371
375
|
}
|
|
372
|
-
query =
|
|
376
|
+
const query = `UPDATE ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} SET ${setClauses.join(", ")} WHERE id IN ${$i(l, 0)}`;
|
|
373
377
|
return { query, args };
|
|
374
378
|
}
|
|
375
379
|
async execute(query, args, client) {
|
|
@@ -421,16 +425,13 @@ class Upsert {
|
|
|
421
425
|
i += nbProps;
|
|
422
426
|
}
|
|
423
427
|
query = query.slice(0, -2);
|
|
424
|
-
query += ` ON CONFLICT (${conflictColumns}) DO UPDATE SET `;
|
|
425
428
|
const conflictTargetArray = Array.isArray(conflictTarget) ? conflictTarget : [conflictTarget];
|
|
426
429
|
const updateCols = quotedPropsToUse.filter((_, idx) => {
|
|
427
430
|
const propName = propsToUse[idx];
|
|
428
431
|
return !conflictTargetArray.includes(propName);
|
|
429
432
|
});
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
query = query.slice(0, -2);
|
|
433
|
+
const updateSetClause = updateCols.map(col => `${col} = EXCLUDED.${col}`).join(", ");
|
|
434
|
+
query += ` ON CONFLICT (${conflictColumns}) DO UPDATE SET ${updateSetClause}`;
|
|
434
435
|
if (rtn)
|
|
435
436
|
query += ` ${rtn}`;
|
|
436
437
|
return { query, args };
|
|
@@ -471,14 +472,7 @@ function queryByDate() {
|
|
|
471
472
|
return `SELECT hard_delete($1, $2, $3)`;
|
|
472
473
|
}
|
|
473
474
|
async function executeArchived(schema, table, date, query, client) {
|
|
474
|
-
|
|
475
|
-
try {
|
|
476
|
-
db = await execute(query, [schema, table, date], client);
|
|
477
|
-
}
|
|
478
|
-
catch (err) {
|
|
479
|
-
throw err;
|
|
480
|
-
}
|
|
481
|
-
return db;
|
|
475
|
+
return execute(query, [schema, table, date], client);
|
|
482
476
|
}
|
|
483
477
|
|
|
484
478
|
function type(type) {
|
|
@@ -534,6 +528,8 @@ function type(type) {
|
|
|
534
528
|
return s;
|
|
535
529
|
case "object":
|
|
536
530
|
return s;
|
|
531
|
+
case "array":
|
|
532
|
+
return "array";
|
|
537
533
|
default:
|
|
538
534
|
return s;
|
|
539
535
|
}
|
|
@@ -543,6 +539,7 @@ const matchModes = {
|
|
|
543
539
|
string: new Set(["startsWith", "contains", "endsWith", "notContains", "equals", "notEquals", "lt", "lte", "gt", "gte", "in", "notIn"]),
|
|
544
540
|
number: new Set(["equals", "notEquals", "lt", "lte", "gt", "gte", "in", "notIn"]),
|
|
545
541
|
date: new Set(["is", "isNot", "dateAfter"]),
|
|
542
|
+
array: new Set(["&&"]),
|
|
546
543
|
};
|
|
547
544
|
function matchMode(type, matchMode) {
|
|
548
545
|
return COMPARATORS.has(matchMode) || matchModes[type].has(matchMode);
|
|
@@ -565,6 +562,12 @@ function cleanFilters(filters, properties) {
|
|
|
565
562
|
const type$1 = type(prop.type);
|
|
566
563
|
const filterValue = filters[k];
|
|
567
564
|
const filterArray = isArray(filterValue) ? filterValue : [filterValue];
|
|
565
|
+
if (type$1 === "array") {
|
|
566
|
+
for (const f of filterArray) {
|
|
567
|
+
if (f.matchMode === "in")
|
|
568
|
+
f.matchMode = "&&";
|
|
569
|
+
}
|
|
570
|
+
}
|
|
568
571
|
const validFilters = filterArray.filter((f) => {
|
|
569
572
|
const { matchMode: matchMode$1 } = f;
|
|
570
573
|
if (!matchMode$1 || !matchMode(type$1, matchMode$1)) {
|
|
@@ -706,7 +709,8 @@ class SQLEntity extends Entity {
|
|
|
706
709
|
}
|
|
707
710
|
query = {
|
|
708
711
|
select: (first = 0, rows = null, sortField = null, sortOrder = null, filters = null) => {
|
|
709
|
-
|
|
712
|
+
const validatedSortField = sortField && this.properties.some(p => p.key === sortField) ? sortField : null;
|
|
713
|
+
return this.sel.query(this.schema, this.table, first, rows, validatedSortField, sortOrder, filters);
|
|
710
714
|
},
|
|
711
715
|
update: (rows, consumer) => {
|
|
712
716
|
return this.upd.query(this.schema, this.table, rows, consumer?.id, consumer?.nickname);
|
|
@@ -735,7 +739,8 @@ class SQLEntity extends Entity {
|
|
|
735
739
|
const b = req.body;
|
|
736
740
|
const first = b?.first ?? 0;
|
|
737
741
|
const rows = b.rows || null;
|
|
738
|
-
const
|
|
742
|
+
const rawSortField = b.sortField || null;
|
|
743
|
+
const sortField = rawSortField && this.properties.some(p => p.key === rawSortField) ? rawSortField : null;
|
|
739
744
|
const sortOrder = b.sortOrder === -1 || b.sortOrder === "DESC" ? "DESC" : "ASC";
|
|
740
745
|
const filters = cleanFilters(b.filters, this.properties) || null;
|
|
741
746
|
const pagination = b.pagination || false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dwtechs/antity-pgsql",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.6",
|
|
4
4
|
"description": "Open source library to add PostgreSQL support to @dwtechs/Antity entities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"entities"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@dwtechs/checkard": "3.6.0",
|
|
38
38
|
"@dwtechs/winstan": "0.7.0",
|
|
39
|
-
"@dwtechs/antity": "0.
|
|
39
|
+
"@dwtechs/antity": "0.18.0",
|
|
40
40
|
"@dwtechs/sparray": "0.2.1",
|
|
41
41
|
"pg": "8.20.0"
|
|
42
42
|
},
|