@dwtechs/antity-pgsql 0.8.1 → 0.9.1

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
@@ -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
- const entity = new Entity("consumers", [
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
- methods: ["GET", "PUT", "DELETE"],
57
- operations: ["SELECT", "UPDATE", "DELETE"],
58
- required: true,
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
- methods: ["GET", "POST", "PUT", "DELETE"],
75
- operations: ["SELECT", "UPDATE", "DELETE"],
76
- required: true,
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
- methods: ["GET", "POST", "PUT", "DELETE"],
93
- operations: ["SELECT", "UPDATE", "DELETE"],
94
- required: true,
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
- methods: ["GET", "POST", "PUT", "DELETE"],
111
- operations: ["SELECT", "UPDATE", "DELETE"],
112
- required: true,
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
- router.post("/", entity.normalizeArray, entity.validateArray, ..., entity.add);
125
- router.put("/", entity.normalizeArray, entity.validateArray, ..., entity.update);
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" | "DELETE";
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
- get(), add(), update(), archive(), delete() and deleteArchive() methods are made to be used as Express.js middlewares.
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
- | required | boolean | Property is required during validation | false
322
- | safe | boolean | Property is sent in the response | true
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
- | methods | Method[] | property is validated for the listed methods only | [ "GET", "POST", "PUT", "DELETE" ]
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 | Custop Normalizer function if normalize is true | null
332
- | validator | ((v:any, min:number, max:number, typeCheck:boolean) => any) \| null | validator function if validate is true | 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*
@@ -24,12 +24,12 @@ SOFTWARE.
24
24
  https://github.com/DWTechs/Antity-pgsql.js
25
25
  */
26
26
 
27
- import { Entity } from "@dwtechs/antity";
28
- import { Type } from "@dwtechs/antity";
29
- import { Property } from './property';
27
+ import { Entity, Property as BaseProperty } from "@dwtechs/antity";
28
+ import type { Type, Method } from "@dwtechs/antity";
30
29
  import type { Request, Response, NextFunction } from 'express';
30
+ import type { Pool, PoolClient } from 'pg';
31
31
 
32
- export type Operation = "SELECT" | "INSERT" | "UPDATE" | "DELETE";
32
+ export type Operation = "SELECT" | "INSERT" | "UPDATE";
33
33
  export type Sort = "ASC" | "DESC";
34
34
  export type Filters = {
35
35
  [key: string]: Filter;
@@ -39,7 +39,27 @@ export type Filter = {
39
39
  subProps?: string[];
40
40
  matchMode?: MatchMode;
41
41
  };
42
- export { Type };
42
+ export type { Type };
43
+
44
+ export declare class Property extends BaseProperty {
45
+ filter: boolean;
46
+ operations: Operation[];
47
+ constructor(
48
+ key: string,
49
+ type: Type,
50
+ min: number | Date | null,
51
+ max: number | Date | null,
52
+ need: Method[],
53
+ send: boolean,
54
+ typeCheck: boolean,
55
+ filter: boolean,
56
+ operations: Operation[] | undefined,
57
+ sanitizer: ((v: unknown) => unknown) | null,
58
+ normalizer: ((v: unknown) => unknown) | null,
59
+ validator: ((v: unknown) => unknown) | null
60
+ );
61
+ }
62
+
43
63
  export type LogicalOperator = "AND" | "OR";
44
64
  export type Comparator = "=" | "<" | ">" | "<=" | ">=" | "<>" | "IS" | "IS NOT" | "IN" | "LIKE" | "NOT LIKE";
45
65
  export type MatchMode = "startsWith" | "endsWith" | "contains" | "notContains" | "equals" | "notEquals" | "between" | "in" | "lt" | "lte" | "gt" | "gte" | "is" | "isNot" | "before" | "after" | "st_contains" | "st_dwithin";
@@ -68,49 +88,81 @@ export type PGResponse = {
68
88
  rowAsArray?: boolean;
69
89
  };
70
90
 
71
- declare class SQLEntity extends Entity {
72
- private _table;
73
- private sel;
74
- private ins;
75
- private upd;
76
- constructor(name: string, properties: Property[]);
91
+ type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
92
+ type ExpressMiddlewareAsync = (req: Request, res: Response, next: NextFunction) => Promise<void>;
93
+ type SubstackTuple = [ExpressMiddleware, ExpressMiddleware, ExpressMiddlewareAsync];
94
+
95
+ export declare class SQLEntity extends Entity {
96
+ private _table: string;
97
+ private _schema: string;
98
+ private sel: unknown;
99
+ private ins: unknown;
100
+ private upd: unknown;
101
+
102
+ constructor(name: string, properties: Property[], schema?: string);
103
+
77
104
  get table(): string;
78
105
  set table(table: string);
106
+
107
+ get schema(): string;
108
+ set schema(schema: string);
109
+
110
+ get addArraySubstack(): SubstackTuple;
111
+ get addOneSubstack(): SubstackTuple;
112
+ get updateArraySubstack(): SubstackTuple;
113
+ get updateOneSubstack(): SubstackTuple;
114
+
79
115
  query: {
80
116
  select: (
81
117
  first?: number,
82
118
  rows?: number | null,
83
119
  sortField?: string | null,
84
120
  sortOrder?: "ASC" | "DESC" | null,
85
- filters?: Filters | null) => {
86
- query: string;
87
- args: (Filter["value"])[];
121
+ filters?: Filters | null
122
+ ) => {
123
+ query: string;
124
+ args: (Filter["value"])[];
88
125
  };
89
- update: (rows: Record<string,
90
- unknown>[], consumerId?: number | string, consumerName?: string) => {
91
- query: string;
92
- args: unknown[];
126
+
127
+ update: (
128
+ rows: Record<string, unknown>[],
129
+ consumerId?: number | string,
130
+ consumerName?: string
131
+ ) => {
132
+ query: string;
133
+ args: unknown[];
93
134
  };
94
- insert: (rows: Record<string, unknown>[], consumerId?: number | string, consumerName?: string, rtn?: string) => {
95
- query: string;
96
- args: unknown[];
135
+
136
+ insert: (
137
+ rows: Record<string, unknown>[],
138
+ consumerId?: number | string,
139
+ consumerName?: string,
140
+ rtn?: string
141
+ ) => {
142
+ query: string;
143
+ args: unknown[];
97
144
  };
145
+
98
146
  delete: (ids: number[]) => {
99
147
  query: string;
100
148
  args: number[];
101
149
  };
150
+
102
151
  deleteArchive: () => string;
152
+
103
153
  return: (prop: string) => string;
104
154
  };
105
- get: (req: Request, res: Response, next: NextFunction) => void;
106
- add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
107
- update: (req: Request, res: Response, next: NextFunction) => Promise<void>;
108
- archive: (req: Request, res: Response, next: NextFunction) => Promise<void>;
109
- delete: (req: Request, res: Response, next: NextFunction) => Promise<void>;
110
- deleteArchive: (req: Request, res: Response, next: NextFunction) => void;
155
+
156
+ get(req: Request, res: Response, next: NextFunction): void;
157
+ add(req: Request, res: Response, next: NextFunction): Promise<void>;
158
+ update(req: Request, res: Response, next: NextFunction): Promise<void>;
159
+ archive(req: Request, res: Response, next: NextFunction): Promise<void>;
160
+ delete(req: Request, res: Response, next: NextFunction): Promise<void>;
161
+ deleteArchive(req: Request, res: Response, next: NextFunction): void;
162
+ getHistory(req: Request, res: Response, next: NextFunction): void;
111
163
  }
112
164
 
113
- declare function filter(
165
+ export declare function filter(
114
166
  first: number,
115
167
  rows: number | null,
116
168
  sortField: string | null,
@@ -118,16 +170,9 @@ declare function filter(
118
170
  filters: Filters | null,
119
171
  ): { filterClause: string, args: (Filter["value"])[] };
120
172
 
121
- declare function execute(
173
+ export declare function execute(
122
174
  query: string,
123
175
  args: (string | number | boolean | Date | number[])[],
124
- client: any
176
+ client: Pool | PoolClient | null
125
177
  ): Promise<PGResponse>;
126
178
 
127
- export {
128
- SQLEntity,
129
- Property,
130
- filter,
131
- execute,
132
- };
133
-
@@ -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 "${quoteIfUppercase(table)}" SET `;
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)
@@ -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 queryArchived(table) {
371
- return `DELETE FROM ${quoteIfUppercase(table)} WHERE "archivedAt" < $1`;
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(`│ │ ├─ Required: ${p.required}`);
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 queryArchived(this.table);
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
- const q = queryArchived(this._table);
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.8.1",
3
+ "version": "0.9.1",
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.14.0",
41
+ "@dwtechs/antity": "0.15.0",
42
42
  "@dwtechs/sparray": "0.2.1",
43
43
  "pg": "8.13.1"
44
44
  },