@dwtechs/antity-pgsql 0.8.0 → 0.9.0
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 +82 -44
- package/dist/antity-pgsql.d.ts +54 -22
- package/dist/antity-pgsql.js +75 -29
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -45,7 +45,18 @@ $ npm i @dwtechs/antity-pgsql
|
|
|
45
45
|
import { SQLEntity } from "@dwtechs/antity-pgsql";
|
|
46
46
|
import { normalizeName, normalizeNickname } from "@dwtechs/checkard";
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
// Create entity with default 'public' schema
|
|
49
|
+
const entity = new SQLEntity("consumers", [
|
|
50
|
+
// properties...
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
// Or specify a custom schema
|
|
54
|
+
const customEntity = new SQLEntity("consumers", [
|
|
55
|
+
// properties...
|
|
56
|
+
], "myschema");
|
|
57
|
+
|
|
58
|
+
// Example with all properties
|
|
59
|
+
const entity = new SQLEntity("consumers", [
|
|
49
60
|
{
|
|
50
61
|
key: "id",
|
|
51
62
|
type: "integer",
|
|
@@ -53,13 +64,9 @@ const entity = new Entity("consumers", [
|
|
|
53
64
|
max: 120,
|
|
54
65
|
typeCheck: true,
|
|
55
66
|
filter: true,
|
|
56
|
-
|
|
57
|
-
operations: ["SELECT", "UPDATE"
|
|
58
|
-
|
|
59
|
-
safe: true,
|
|
60
|
-
sanitize: true,
|
|
61
|
-
normalize: true,
|
|
62
|
-
validate: true,
|
|
67
|
+
need: ["PUT"],
|
|
68
|
+
operations: ["SELECT", "UPDATE"],
|
|
69
|
+
send: true,
|
|
63
70
|
sanitizer: null,
|
|
64
71
|
normalizer: null,
|
|
65
72
|
validator: null,
|
|
@@ -71,13 +78,9 @@ const entity = new Entity("consumers", [
|
|
|
71
78
|
max: 255,
|
|
72
79
|
typeCheck: true,
|
|
73
80
|
filter: false,
|
|
74
|
-
|
|
75
|
-
operations: ["SELECT", "UPDATE"
|
|
76
|
-
|
|
77
|
-
safe: true,
|
|
78
|
-
sanitize: true,
|
|
79
|
-
normalize: true,
|
|
80
|
-
validate: true,
|
|
81
|
+
need: ["POST", "PUT"],
|
|
82
|
+
operations: ["SELECT", "UPDATE"],
|
|
83
|
+
send: true,
|
|
81
84
|
sanitizer: null,
|
|
82
85
|
normalizer: normalizeName,
|
|
83
86
|
validator: null,
|
|
@@ -89,13 +92,9 @@ const entity = new Entity("consumers", [
|
|
|
89
92
|
max: 255,
|
|
90
93
|
typeCheck: true,
|
|
91
94
|
filter: false,
|
|
92
|
-
|
|
93
|
-
operations: ["SELECT", "UPDATE"
|
|
94
|
-
|
|
95
|
-
safe: true,
|
|
96
|
-
sanitize: true,
|
|
97
|
-
normalize: true,
|
|
98
|
-
validate: true,
|
|
95
|
+
need: ["POST", "PUT"],
|
|
96
|
+
operations: ["SELECT", "UPDATE"],
|
|
97
|
+
send: true,
|
|
99
98
|
sanitizer: null,
|
|
100
99
|
normalizer: normalizeName,
|
|
101
100
|
validator: null,
|
|
@@ -107,13 +106,9 @@ const entity = new Entity("consumers", [
|
|
|
107
106
|
max: 255,
|
|
108
107
|
typeCheck: true,
|
|
109
108
|
filter: true,
|
|
110
|
-
|
|
111
|
-
operations: ["SELECT", "UPDATE"
|
|
112
|
-
|
|
113
|
-
safe: true,
|
|
114
|
-
sanitize: true,
|
|
115
|
-
normalize: true,
|
|
116
|
-
validate: true,
|
|
109
|
+
need: ["POST", "PUT"],
|
|
110
|
+
operations: ["SELECT", "UPDATE"],
|
|
111
|
+
send: true,
|
|
117
112
|
sanitizer: null,
|
|
118
113
|
normalizer: normalizeNickname,
|
|
119
114
|
validator: null,
|
|
@@ -121,11 +116,19 @@ const entity = new Entity("consumers", [
|
|
|
121
116
|
]);
|
|
122
117
|
|
|
123
118
|
router.get("/", ..., entity.get);
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
|
|
120
|
+
// Using substacks (recommended) - combines normalize, validate, and database operation
|
|
121
|
+
router.post("/", ...entity.addArraySubstack);
|
|
122
|
+
router.put("/", ...entity.updateArraySubstack);
|
|
123
|
+
|
|
124
|
+
// Or manually chain middlewares
|
|
125
|
+
router.post("/manual", entity.normalizeArray, entity.validateArray, ..., entity.add);
|
|
126
|
+
router.put("/manual", entity.normalizeArray, entity.validateArray, ..., entity.update);
|
|
127
|
+
|
|
126
128
|
router.put("/archive", ..., entity.archive);
|
|
127
129
|
router.delete("/", ..., entity.delete);
|
|
128
130
|
router.delete("/archived", ..., entity.deleteArchive);
|
|
131
|
+
router.get("/history", ..., entity.getHistory);
|
|
129
132
|
|
|
130
133
|
```
|
|
131
134
|
|
|
@@ -134,7 +137,7 @@ router.delete("/archived", ..., entity.deleteArchive);
|
|
|
134
137
|
|
|
135
138
|
```javascript
|
|
136
139
|
|
|
137
|
-
type Operation = "SELECT" | "INSERT" | "UPDATE"
|
|
140
|
+
type Operation = "SELECT" | "INSERT" | "UPDATE";
|
|
138
141
|
|
|
139
142
|
type MatchMode =
|
|
140
143
|
"startsWith" |
|
|
@@ -163,14 +166,26 @@ type Filter = {
|
|
|
163
166
|
matchMode?: MatchMode;
|
|
164
167
|
}
|
|
165
168
|
|
|
169
|
+
type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
170
|
+
type ExpressMiddlewareAsync = (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
171
|
+
type SubstackTuple = [ExpressMiddleware, ExpressMiddleware, ExpressMiddlewareAsync];
|
|
172
|
+
|
|
166
173
|
class SQLEntity {
|
|
167
|
-
constructor(name: string, properties: Property[]);
|
|
174
|
+
constructor(name: string, properties: Property[], schema?: string);
|
|
168
175
|
get name(): string;
|
|
169
176
|
get table(): string;
|
|
177
|
+
get schema(): string;
|
|
170
178
|
get unsafeProps(): string[];
|
|
171
179
|
get properties(): Property[];
|
|
172
180
|
set name(name: string);
|
|
173
181
|
set table(table: string);
|
|
182
|
+
set schema(schema: string);
|
|
183
|
+
|
|
184
|
+
// Middleware substacks (combine normalize, validate, and operation)
|
|
185
|
+
get addArraySubstack(): SubstackTuple;
|
|
186
|
+
get addOneSubstack(): SubstackTuple;
|
|
187
|
+
get updateArraySubstack(): SubstackTuple;
|
|
188
|
+
get updateOneSubstack(): SubstackTuple;
|
|
174
189
|
|
|
175
190
|
query: {
|
|
176
191
|
select: (
|
|
@@ -210,6 +225,7 @@ class SQLEntity {
|
|
|
210
225
|
archive: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
211
226
|
delete: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
212
227
|
deleteArchive: (req: Request, res: Response, next: NextFunction) => void;
|
|
228
|
+
getHistory: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
213
229
|
|
|
214
230
|
}
|
|
215
231
|
|
|
@@ -229,12 +245,38 @@ function execute(
|
|
|
229
245
|
|
|
230
246
|
|
|
231
247
|
```
|
|
232
|
-
|
|
248
|
+
|
|
249
|
+
### Middleware Methods for Express.js
|
|
250
|
+
|
|
251
|
+
get(), add(), update(), archive(), delete(), deleteArchive() and getHistory() methods are made to be used as Express.js middlewares.
|
|
233
252
|
Each method will look for data to work on in the **req.body.rows** parameter.
|
|
234
253
|
|
|
254
|
+
### Schema Qualification
|
|
255
|
+
|
|
256
|
+
All SQL queries generated by Antity-pgsql use schema-qualified table names (e.g., `schema.table`). This provides:
|
|
257
|
+
- **Security**: Protection against search_path manipulation attacks, especially important when using SECURITY DEFINER functions
|
|
258
|
+
- **Clarity**: Explicit schema references make queries more readable and maintainable
|
|
259
|
+
- **Flexibility**: Easy multi-schema support within the same application
|
|
260
|
+
|
|
261
|
+
The default schema is `'public'` but can be customized via the constructor's third parameter or the `schema` setter.
|
|
262
|
+
|
|
263
|
+
### Middleware Substacks
|
|
264
|
+
|
|
265
|
+
Substacks are pre-composed middleware chains that combine normalization, validation, and database operations:
|
|
266
|
+
|
|
267
|
+
- **addArraySubstack**: Combines `normalizeArray`, `validateArray`, and `add`. Use this for POST routes with `req.body.rows` containing multiple objects.
|
|
268
|
+
- **addOneSubstack**: Combines `normalizeOne`, `validateOne`, and `add`. Use this for POST routes with `req.body` containing a single object.
|
|
269
|
+
- **updateArraySubstack**: Combines `normalizeArray`, `validateArray`, and `update`. Use this for PUT routes with `req.body.rows` containing multiple objects.
|
|
270
|
+
- **updateOneSubstack**: Combines `normalizeOne`, `validateOne`, and `update`. Use this for PUT routes with `req.body` containing a single object.
|
|
271
|
+
|
|
272
|
+
Using substacks simplifies your route definitions and ensures consistent data processing.
|
|
273
|
+
|
|
274
|
+
### Query Methods
|
|
275
|
+
|
|
235
276
|
- **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.
|
|
236
277
|
- **delete()**: Deletes rows by their IDs. Expects `req.body.rows` to be an array of objects with `id` property: `[{id: 1}, {id: 2}]`
|
|
237
|
-
- **deleteArchive()**: Deletes archived rows that were archived before a specific date. Expects `req.body.date` to be a Date object.
|
|
278
|
+
- **deleteArchive()**: Deletes archived rows that were archived before a specific date using a PostgreSQL SECURITY DEFINER function. Expects `req.body.date` to be a Date object.
|
|
279
|
+
- **getHistory()**: Retrieves modification history for rows from the `log.history` table. Expects `req.body.rows` to be an array of objects with `id` property. Returns all historical records for the specified entity IDs.
|
|
238
280
|
|
|
239
281
|
|
|
240
282
|
## Match modes
|
|
@@ -318,18 +360,14 @@ Any of these can be passed into the options object for each function.
|
|
|
318
360
|
| type | Type | Type of the property |
|
|
319
361
|
| min | number \| Date | Minimum value | 0 \| 1900-01-01
|
|
320
362
|
| max | number \| Date | Maximum value | 999999999 \| 2200-12-31
|
|
321
|
-
|
|
|
322
|
-
|
|
|
363
|
+
| need | Method[] | property is validated for the listed methods only | ["PATCH", "PUT", "POST"]
|
|
364
|
+
| send | boolean | Property is sent in the response | true
|
|
323
365
|
| typeCheck | boolean | Type is checked during validation | false
|
|
324
366
|
| filter | boolean | property is filterable in a SELECT operation | true
|
|
325
|
-
|
|
|
326
|
-
| operations | Operation[] | SQL DML operations for the property | [ "SELECT", "INSERT", "UPDATE", "DELETE" ]
|
|
327
|
-
| sanitize | boolean | Sanitize the property if true | true
|
|
328
|
-
| normalize | boolean | Normalize the property if true | false
|
|
329
|
-
| validate | boolean | validate the property if true | true
|
|
367
|
+
| operations | Operation[] | Property is used for the DML operations only | ["SELECT", "INSERT", "UPDATE"]
|
|
330
368
|
| sanitizer | ((v:any) => any) \| null | Custom sanitizer function if sanitize is true | null
|
|
331
|
-
| normalizer | ((v:any) => any) \| null |
|
|
332
|
-
| validator | ((v:any, min:number, max:number, typeCheck:boolean) => any) \| null
|
|
369
|
+
| normalizer | ((v:any) => any) \| null | Custom Normalizer function if normalize is true | null
|
|
370
|
+
| validator | ((v:any, min:number, max:number, typeCheck:boolean) => any) \| null | validator function if validate is true | null
|
|
333
371
|
|
|
334
372
|
|
|
335
373
|
* *Min and max parameters are not used for boolean type*
|
package/dist/antity-pgsql.d.ts
CHANGED
|
@@ -68,46 +68,78 @@ export type PGResponse = {
|
|
|
68
68
|
rowAsArray?: boolean;
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
72
|
+
type ExpressMiddlewareAsync = (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
73
|
+
type SubstackTuple = [ExpressMiddleware, ExpressMiddleware, ExpressMiddlewareAsync];
|
|
74
|
+
|
|
75
|
+
export class SQLEntity extends Entity {
|
|
76
|
+
private _table: string;
|
|
77
|
+
private _schema: string;
|
|
78
|
+
private sel: any;
|
|
79
|
+
private ins: any;
|
|
80
|
+
private upd: any;
|
|
81
|
+
|
|
82
|
+
constructor(name: string, properties: Property[], schema?: string);
|
|
83
|
+
|
|
77
84
|
get table(): string;
|
|
78
85
|
set table(table: string);
|
|
86
|
+
|
|
87
|
+
get schema(): string;
|
|
88
|
+
set schema(schema: string);
|
|
89
|
+
|
|
90
|
+
get addArraySubstack(): SubstackTuple;
|
|
91
|
+
get addOneSubstack(): SubstackTuple;
|
|
92
|
+
get updateArraySubstack(): SubstackTuple;
|
|
93
|
+
get updateOneSubstack(): SubstackTuple;
|
|
94
|
+
|
|
79
95
|
query: {
|
|
80
96
|
select: (
|
|
81
97
|
first?: number,
|
|
82
98
|
rows?: number | null,
|
|
83
99
|
sortField?: string | null,
|
|
84
100
|
sortOrder?: "ASC" | "DESC" | null,
|
|
85
|
-
filters?: Filters | null
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
filters?: Filters | null
|
|
102
|
+
) => {
|
|
103
|
+
query: string;
|
|
104
|
+
args: (Filter["value"])[];
|
|
88
105
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
|
|
107
|
+
update: (
|
|
108
|
+
rows: Record<string, unknown>[],
|
|
109
|
+
consumerId?: number | string,
|
|
110
|
+
consumerName?: string
|
|
111
|
+
) => {
|
|
112
|
+
query: string;
|
|
113
|
+
args: unknown[];
|
|
93
114
|
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
|
|
116
|
+
insert: (
|
|
117
|
+
rows: Record<string, unknown>[],
|
|
118
|
+
consumerId?: number | string,
|
|
119
|
+
consumerName?: string,
|
|
120
|
+
rtn?: string
|
|
121
|
+
) => {
|
|
122
|
+
query: string;
|
|
123
|
+
args: unknown[];
|
|
97
124
|
};
|
|
125
|
+
|
|
98
126
|
delete: (ids: number[]) => {
|
|
99
127
|
query: string;
|
|
100
128
|
args: number[];
|
|
101
129
|
};
|
|
130
|
+
|
|
102
131
|
deleteArchive: () => string;
|
|
132
|
+
|
|
103
133
|
return: (prop: string) => string;
|
|
104
134
|
};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
135
|
+
|
|
136
|
+
get(req: Request, res: Response, next: NextFunction): void;
|
|
137
|
+
add(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
138
|
+
update(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
139
|
+
archive(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
140
|
+
delete(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
141
|
+
deleteArchive(req: Request, res: Response, next: NextFunction): void;
|
|
142
|
+
getHistory(req: Request, res: Response, next: NextFunction): void;
|
|
111
143
|
}
|
|
112
144
|
|
|
113
145
|
declare function filter(
|
package/dist/antity-pgsql.js
CHANGED
|
@@ -222,10 +222,10 @@ class Select {
|
|
|
222
222
|
get props() {
|
|
223
223
|
return this._cols;
|
|
224
224
|
}
|
|
225
|
-
query(table, first = 0, rows = null, sortField = null, sortOrder = null, filters = null) {
|
|
225
|
+
query(schema, table, first = 0, rows = null, sortField = null, sortOrder = null, filters = null) {
|
|
226
226
|
const p = rows ? this._count : '';
|
|
227
227
|
const c = this._cols ? this._cols : '*';
|
|
228
|
-
const baseQuery = `SELECT ${c}${p} FROM ${quoteIfUppercase(table)}`;
|
|
228
|
+
const baseQuery = `SELECT ${c}${p} FROM ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)}`;
|
|
229
229
|
const { filterClause, args } = filter(first, rows, sortField, sortOrder, filters);
|
|
230
230
|
return {
|
|
231
231
|
query: baseQuery + filterClause,
|
|
@@ -264,7 +264,7 @@ class Insert {
|
|
|
264
264
|
this._nbProps++;
|
|
265
265
|
this._cols = this._quotedProps.join(", ");
|
|
266
266
|
}
|
|
267
|
-
query(table, rows, consumerId, consumerName, rtn = "") {
|
|
267
|
+
query(schema, table, rows, consumerId, consumerName, rtn = "") {
|
|
268
268
|
const propsToUse = [...this._props];
|
|
269
269
|
let nbProps = this._nbProps;
|
|
270
270
|
let cols = this._cols;
|
|
@@ -273,7 +273,7 @@ class Insert {
|
|
|
273
273
|
nbProps += 2;
|
|
274
274
|
cols += `, "consumerId", "consumerName"`;
|
|
275
275
|
}
|
|
276
|
-
let query = `INSERT INTO ${quoteIfUppercase(table)} (${cols}) VALUES `;
|
|
276
|
+
let query = `INSERT INTO ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} (${cols}) VALUES `;
|
|
277
277
|
const args = [];
|
|
278
278
|
let i = 0;
|
|
279
279
|
for (const row of rows) {
|
|
@@ -316,7 +316,7 @@ class Update {
|
|
|
316
316
|
addProp(prop) {
|
|
317
317
|
this._props.push(prop);
|
|
318
318
|
}
|
|
319
|
-
query(table, rows, consumerId, consumerName) {
|
|
319
|
+
query(schema, table, rows, consumerId, consumerName) {
|
|
320
320
|
if (consumerId !== undefined && consumerName !== undefined)
|
|
321
321
|
rows = this.addConsumer(rows, consumerId, consumerName);
|
|
322
322
|
const propsToUse = [...this._props];
|
|
@@ -325,7 +325,7 @@ class Update {
|
|
|
325
325
|
log.debug(`${LOGS_PREFIX}Update query input rows: ${JSON.stringify(rows, null, 2)}`);
|
|
326
326
|
const l = rows.length;
|
|
327
327
|
const args = rows.map(row => row.id);
|
|
328
|
-
let query = `UPDATE
|
|
328
|
+
let query = `UPDATE ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} SET `;
|
|
329
329
|
let i = args.length + 1;
|
|
330
330
|
for (const p of propsToUse) {
|
|
331
331
|
if (rows[0][p] === undefined)
|
|
@@ -336,7 +336,7 @@ class Update {
|
|
|
336
336
|
query += `WHEN id = $${j + 1} THEN $${i++} `;
|
|
337
337
|
args.push(row[p]);
|
|
338
338
|
}
|
|
339
|
-
query += `END, `;
|
|
339
|
+
query += `ELSE ${quoteIfUppercase(p)} END, `;
|
|
340
340
|
}
|
|
341
341
|
query = `${query.slice(0, -2)} WHERE id IN ${$i(l, 0)}`;
|
|
342
342
|
return { query, args };
|
|
@@ -361,20 +361,20 @@ var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _argu
|
|
|
361
361
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
362
362
|
});
|
|
363
363
|
};
|
|
364
|
-
function queryById(table, ids) {
|
|
364
|
+
function queryById(schema, table, ids) {
|
|
365
365
|
return {
|
|
366
|
-
query: `DELETE FROM ${quoteIfUppercase(table)} WHERE id = ANY($1)`,
|
|
366
|
+
query: `DELETE FROM ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} WHERE id = ANY($1)`,
|
|
367
367
|
args: [ids]
|
|
368
368
|
};
|
|
369
369
|
}
|
|
370
|
-
function
|
|
371
|
-
return `
|
|
370
|
+
function queryByDate() {
|
|
371
|
+
return `SELECT hard_delete($1, $2, $3)`;
|
|
372
372
|
}
|
|
373
|
-
function executeArchived(date, query, client) {
|
|
373
|
+
function executeArchived(schema, table, date, query, client) {
|
|
374
374
|
return __awaiter$1(this, void 0, void 0, function* () {
|
|
375
375
|
let db;
|
|
376
376
|
try {
|
|
377
|
-
db = yield execute(query, [date], client);
|
|
377
|
+
db = yield execute(query, [schema, table, date], client);
|
|
378
378
|
}
|
|
379
379
|
catch (err) {
|
|
380
380
|
throw err;
|
|
@@ -497,11 +497,10 @@ function generateSummary(name, table, properties) {
|
|
|
497
497
|
lines.push(`│ │ ├─ Type: ${p.type}`);
|
|
498
498
|
lines.push(`│ │ ├─ Min: ${p.min}`);
|
|
499
499
|
lines.push(`│ │ ├─ Max: ${p.max}`);
|
|
500
|
-
lines.push(`│ │ ├─
|
|
500
|
+
lines.push(`│ │ ├─ need: ${p.need}`);
|
|
501
501
|
lines.push(`│ │ ├─ Safe: ${p.safe}`);
|
|
502
502
|
lines.push(`│ │ ├─ TypeCheck: ${p.typeCheck}`);
|
|
503
503
|
lines.push(`│ │ ├─ Filter: ${p.filter}`);
|
|
504
|
-
lines.push(`│ │ ├─ Methods: [${p.methods.join(', ')}]`);
|
|
505
504
|
lines.push(`│ │ ├─ Operations: [${p.operations.join(', ')}]`);
|
|
506
505
|
lines.push(`│ │ ├─ Sanitize: ${p.sanitize}`);
|
|
507
506
|
lines.push(`│ │ ├─ Normalize: ${p.normalize}`);
|
|
@@ -552,26 +551,26 @@ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argume
|
|
|
552
551
|
});
|
|
553
552
|
};
|
|
554
553
|
class SQLEntity extends Entity {
|
|
555
|
-
constructor(name, properties) {
|
|
554
|
+
constructor(name, properties, schema = 'public') {
|
|
556
555
|
super(name, properties);
|
|
557
556
|
this.sel = new Select();
|
|
558
557
|
this.ins = new Insert();
|
|
559
558
|
this.upd = new Update();
|
|
560
559
|
this.query = {
|
|
561
560
|
select: (first = 0, rows = null, sortField = null, sortOrder = null, filters = null) => {
|
|
562
|
-
return this.sel.query(this.table, first, rows, sortField, sortOrder, filters);
|
|
561
|
+
return this.sel.query(this.schema, this.table, first, rows, sortField, sortOrder, filters);
|
|
563
562
|
},
|
|
564
563
|
update: (rows, consumerId, consumerName) => {
|
|
565
|
-
return this.upd.query(this.table, rows, consumerId, consumerName);
|
|
564
|
+
return this.upd.query(this.schema, this.table, rows, consumerId, consumerName);
|
|
566
565
|
},
|
|
567
566
|
insert: (rows, consumerId, consumerName, rtn = "") => {
|
|
568
|
-
return this.ins.query(this.table, rows, consumerId, consumerName, rtn);
|
|
567
|
+
return this.ins.query(this.schema, this.table, rows, consumerId, consumerName, rtn);
|
|
569
568
|
},
|
|
570
569
|
delete: (ids) => {
|
|
571
|
-
return queryById(this.table, ids);
|
|
570
|
+
return queryById(this.schema, this.table, ids);
|
|
572
571
|
},
|
|
573
572
|
deleteArchive: () => {
|
|
574
|
-
return
|
|
573
|
+
return queryByDate();
|
|
575
574
|
},
|
|
576
575
|
return: (prop) => {
|
|
577
576
|
return this.ins.rtn(prop);
|
|
@@ -591,7 +590,7 @@ class SQLEntity extends Entity {
|
|
|
591
590
|
log.debug(`get(first='${first}', rows='${rows}',
|
|
592
591
|
sortOrder='${sortOrder}', sortField='${sortField}',
|
|
593
592
|
pagination=${pagination}, filters=${JSON.stringify(filters)}`);
|
|
594
|
-
const { query, args } = this.sel.query(this._table, first, rows, sortField, sortOrder, filters);
|
|
593
|
+
const { query, args } = this.sel.query(this._schema, this._table, first, rows, sortField, sortOrder, filters);
|
|
595
594
|
this.sel.execute(query, args, dbClient)
|
|
596
595
|
.then((r) => {
|
|
597
596
|
l.rows = r.rows;
|
|
@@ -610,7 +609,7 @@ class SQLEntity extends Entity {
|
|
|
610
609
|
const rtn = this.ins.rtn("id");
|
|
611
610
|
const chunks = chunk(rows);
|
|
612
611
|
for (const c of chunks) {
|
|
613
|
-
const { query, args } = this.ins.query(this._table, c, cId, cName, rtn);
|
|
612
|
+
const { query, args } = this.ins.query(this._schema, this._table, c, cId, cName, rtn);
|
|
614
613
|
let db;
|
|
615
614
|
try {
|
|
616
615
|
db = yield execute(query, args, dbClient);
|
|
@@ -635,7 +634,7 @@ class SQLEntity extends Entity {
|
|
|
635
634
|
log.debug(`${LOGS_PREFIX}update(rows=${rows.length}, consumerId=${cId})`);
|
|
636
635
|
const chunks = chunk(rows);
|
|
637
636
|
for (const c of chunks) {
|
|
638
|
-
const { query, args } = this.upd.query(this._table, c, cId, cName);
|
|
637
|
+
const { query, args } = this.upd.query(this._schema, this._table, c, cId, cName);
|
|
639
638
|
try {
|
|
640
639
|
yield execute(query, args, dbClient);
|
|
641
640
|
}
|
|
@@ -655,7 +654,7 @@ class SQLEntity extends Entity {
|
|
|
655
654
|
rows = rows.map((id) => (Object.assign(Object.assign({}, id), { archived: true })));
|
|
656
655
|
const chunks = chunk(rows);
|
|
657
656
|
for (const c of chunks) {
|
|
658
|
-
const { query, args } = this.upd.query(this._table, c, cId, cName);
|
|
657
|
+
const { query, args } = this.upd.query(this._schema, this._table, c, cId, cName);
|
|
659
658
|
try {
|
|
660
659
|
yield execute(query, args, dbClient);
|
|
661
660
|
}
|
|
@@ -670,7 +669,7 @@ class SQLEntity extends Entity {
|
|
|
670
669
|
const dbClient = res.locals.dbClient || null;
|
|
671
670
|
const ids = rows.map((row) => row.id);
|
|
672
671
|
log.debug(`${LOGS_PREFIX}delete ${rows.length} rows : (${ids.join(", ")})`);
|
|
673
|
-
const { query, args } = queryById(this._table, ids);
|
|
672
|
+
const { query, args } = queryById(this._schema, this._table, ids);
|
|
674
673
|
try {
|
|
675
674
|
yield execute(query, args, dbClient);
|
|
676
675
|
}
|
|
@@ -682,13 +681,40 @@ class SQLEntity extends Entity {
|
|
|
682
681
|
this.deleteArchive = (req, res, next) => {
|
|
683
682
|
const date = req.body.date;
|
|
684
683
|
const dbClient = res.locals.dbClient || null;
|
|
685
|
-
log.debug(`${LOGS_PREFIX}deleteArchive(date=${date})`);
|
|
686
|
-
|
|
687
|
-
executeArchived(date, q, dbClient)
|
|
684
|
+
log.debug(`${LOGS_PREFIX}deleteArchive(schema=${this._schema}, table=${this._table}, date=${date})`);
|
|
685
|
+
executeArchived(this._schema, this._table, date, queryByDate(), dbClient)
|
|
688
686
|
.then(() => next())
|
|
689
687
|
.catch((err) => next(err));
|
|
690
688
|
};
|
|
689
|
+
this.getHistory = (req, res, next) => {
|
|
690
|
+
const id = req.params.id;
|
|
691
|
+
const dbClient = res.locals.dbClient || null;
|
|
692
|
+
if (!id) {
|
|
693
|
+
return next({ status: 400, msg: "Missing id" });
|
|
694
|
+
}
|
|
695
|
+
log.debug(`${LOGS_PREFIX}getHistory(schema=${this._schema}, table=${this._table}, id=${id})`);
|
|
696
|
+
const sql = `
|
|
697
|
+
SELECT id, tstamp, operation, "consumerId", "consumerName"
|
|
698
|
+
FROM log.history
|
|
699
|
+
WHERE "schemaName" = $1
|
|
700
|
+
AND "tableName" = $2
|
|
701
|
+
AND CAST(record->>'id' AS INT) = $3
|
|
702
|
+
ORDER BY tstamp ASC
|
|
703
|
+
`;
|
|
704
|
+
execute(sql, [this._schema, this._table, id], dbClient)
|
|
705
|
+
.then((r) => {
|
|
706
|
+
const { rowCount, rows } = r;
|
|
707
|
+
if (!rowCount) {
|
|
708
|
+
return next({ status: 404, msg: "History not found" });
|
|
709
|
+
}
|
|
710
|
+
res.locals.history = rows;
|
|
711
|
+
res.locals.total = rowCount;
|
|
712
|
+
next();
|
|
713
|
+
})
|
|
714
|
+
.catch((err) => next(err));
|
|
715
|
+
};
|
|
691
716
|
this._table = name;
|
|
717
|
+
this._schema = schema;
|
|
692
718
|
log.info(`${LOGS_PREFIX}Creating SQLEntity: "${name}"`);
|
|
693
719
|
for (const p of properties) {
|
|
694
720
|
this.mapProps(p.operations, p.key);
|
|
@@ -703,6 +729,26 @@ class SQLEntity extends Entity {
|
|
|
703
729
|
throw new Error(`${LOGS_PREFIX}table must be a string of length > 0`);
|
|
704
730
|
this._table = table;
|
|
705
731
|
}
|
|
732
|
+
get schema() {
|
|
733
|
+
return this._schema;
|
|
734
|
+
}
|
|
735
|
+
set schema(schema) {
|
|
736
|
+
if (!isString(schema, "!0"))
|
|
737
|
+
throw new Error(`${LOGS_PREFIX}schema must be a string of length > 0`);
|
|
738
|
+
this._schema = schema;
|
|
739
|
+
}
|
|
740
|
+
get addArraySubstack() {
|
|
741
|
+
return [this.normalizeArray, this.validateArray, this.add];
|
|
742
|
+
}
|
|
743
|
+
get addOneSubstack() {
|
|
744
|
+
return [this.normalizeOne, this.validateOne, this.add];
|
|
745
|
+
}
|
|
746
|
+
get updateArraySubstack() {
|
|
747
|
+
return [this.normalizeArray, this.validateArray, this.update];
|
|
748
|
+
}
|
|
749
|
+
get updateOneSubstack() {
|
|
750
|
+
return [this.normalizeOne, this.validateOne, this.update];
|
|
751
|
+
}
|
|
706
752
|
mapProps(operations, key) {
|
|
707
753
|
for (const o of operations) {
|
|
708
754
|
switch (o) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dwtechs/antity-pgsql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Open source library to add PostgreSQL support to @dwtechs/Antity entities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"entities"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@dwtechs/checkard": "3.6.0",
|
|
40
40
|
"@dwtechs/winstan": "0.5.0",
|
|
41
|
-
"@dwtechs/antity": "0.
|
|
41
|
+
"@dwtechs/antity": "0.15.0",
|
|
42
42
|
"@dwtechs/sparray": "0.2.1",
|
|
43
43
|
"pg": "8.13.1"
|
|
44
44
|
},
|