@dwtechs/antity-pgsql 0.12.0 → 0.14.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 CHANGED
@@ -190,6 +190,8 @@ class SQLEntity {
190
190
  get addOneSubstack(): SubstackTuple;
191
191
  get updateArraySubstack(): SubstackTuple;
192
192
  get updateOneSubstack(): SubstackTuple;
193
+ get upsertArraySubstack(): SubstackTuple;
194
+ get upsertOneSubstack(): SubstackTuple;
193
195
 
194
196
  query: {
195
197
  select: (
@@ -208,6 +210,13 @@ class SQLEntity {
208
210
  query: string;
209
211
  args: unknown[];
210
212
  };
213
+ archive: (
214
+ rows: Record<string, unknown>[],
215
+ consumerId?: number | string,
216
+ consumerName?: string) => {
217
+ query: string;
218
+ args: unknown[];
219
+ };
211
220
  insert: (
212
221
  rows: Record<string, unknown>[],
213
222
  consumerId?: number | string,
@@ -216,6 +225,15 @@ class SQLEntity {
216
225
  query: string;
217
226
  args: unknown[];
218
227
  };
228
+ upsert: (
229
+ rows: Record<string, unknown>[],
230
+ conflictTarget: string | string[],
231
+ consumerId?: number | string,
232
+ consumerName?: string,
233
+ rtn?: string) => {
234
+ query: string;
235
+ args: unknown[];
236
+ };
219
237
  delete: (ids: number[]) => {
220
238
  query: string;
221
239
  args: number[];
@@ -226,6 +244,7 @@ class SQLEntity {
226
244
  get: (req: Request, res: Response, next: NextFunction) => void;
227
245
  add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
228
246
  update: (req: Request, res: Response, next: NextFunction) => Promise<void>;
247
+ upsert: (req: Request, res: Response, next: NextFunction) => Promise<void>;
229
248
  archive: (req: Request, res: Response, next: NextFunction) => Promise<void>;
230
249
  delete: (req: Request, res: Response, next: NextFunction) => Promise<void>;
231
250
  deleteArchive: (req: Request, res: Response, next: NextFunction) => void;
@@ -252,9 +271,11 @@ function execute(
252
271
 
253
272
  ### Middleware Methods for Express.js
254
273
 
255
- get(), add(), update(), archive(), delete(), deleteArchive() and getHistory() methods are made to be used as Express.js middlewares.
274
+ get(), add(), update(), upsert(), archive(), delete(), deleteArchive() and getHistory() methods are made to be used as Express.js middlewares.
256
275
  Each method will look for data to work on in the **req.body.rows** parameter.
257
276
 
277
+ The upsert() method additionally requires **req.body.conflictTarget** to specify which column(s) define uniqueness.
278
+
258
279
  ### Schema Qualification
259
280
 
260
281
  All SQL queries generated by Antity-pgsql use schema-qualified table names (e.g., `schema.table`). This provides:
@@ -272,16 +293,123 @@ Substacks are pre-composed middleware chains that combine normalization, validat
272
293
  - **addOneSubstack**: Combines `normalizeOne`, `validateOne`, and `add`. Use this for POST routes with `req.body` containing a single object.
273
294
  - **updateArraySubstack**: Combines `normalizeArray`, `validateArray`, and `update`. Use this for PUT routes with `req.body.rows` containing multiple objects.
274
295
  - **updateOneSubstack**: Combines `normalizeOne`, `validateOne`, and `update`. Use this for PUT routes with `req.body` containing a single object.
296
+ - **upsertArraySubstack**: Combines `normalizeArray`, `validateArray`, and `upsert`. Use this for upsert routes with `req.body.rows` containing multiple objects. Requires `req.body.conflictTarget`.
297
+ - **upsertOneSubstack**: Combines `normalizeOne`, `validateOne`, and `upsert`. Use this for upsert routes with `req.body` containing a single object. Requires `req.body.conflictTarget`.
275
298
 
276
299
  Using substacks simplifies your route definitions and ensures consistent data processing.
277
300
 
278
301
  ### Query Methods
279
302
 
280
303
  - **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.
304
+ - **query.insert()**: Generates an INSERT query. Accepts an array of objects with properties matching the entity definition. Optionally appends `consumerId` and `consumerName` for history tracking. Supports `RETURNING` clause via the `rtn` parameter.
305
+ - **query.update()**: Generates an UPDATE query using CASE statements. Accepts an array of objects with `id` property. Optionally appends `consumerId` and `consumerName` for history tracking.
306
+ - **query.upsert()**: Generates an INSERT ... ON CONFLICT ... DO UPDATE query. Accepts an array of 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. Optionally appends `consumerId` and `consumerName` for history tracking. Supports `RETURNING` clause via the `rtn` parameter.
307
+ - **query.archive()**: Generates a simplified `UPDATE ... SET archived = true WHERE id IN (...)` query. Accepts an array of objects with `id` property. Optionally appends `consumerId` and `consumerName` for history tracking. Does not require an `archived` field in the rows — it is set directly in the SQL.
281
308
  - **delete()**: Deletes rows by their IDs. Expects `req.body.rows` to be an array of objects with `id` property: `[{id: 1}, {id: 2}]`
282
309
  - **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.
283
310
  - **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.
284
311
 
312
+ ### Upsert (Insert or Update)
313
+
314
+ The upsert functionality uses PostgreSQL's `INSERT ... ON CONFLICT ... DO UPDATE` syntax to insert rows or update them if they already exist based on a unique constraint.
315
+
316
+ #### How It Works
317
+
318
+ 1. **Conflict Target**: You specify which column(s) define uniqueness (e.g., `'id'`, `'email'`, or `['name', 'email']`)
319
+ 2. **Property Selection**: Properties are automatically included if they have **both** `INSERT` and `UPDATE` in their `operations` array
320
+ 3. **On Conflict**: When a conflict occurs, all columns except the conflict target are updated
321
+
322
+ #### Usage Examples
323
+
324
+ **Using the middleware with a single conflict target:**
325
+
326
+ ```javascript
327
+ // Route definition
328
+ router.post('/users/upsert', ...entity.upsertArraySubstack);
329
+
330
+ // Request body
331
+ {
332
+ rows: [
333
+ { id: 1, name: 'John Updated', email: 'john@example.com' },
334
+ { name: 'Jane New', email: 'jane@example.com' }
335
+ ],
336
+ conflictTarget: 'id'
337
+ }
338
+ ```
339
+
340
+ **Using email as conflict target:**
341
+
342
+ ```javascript
343
+ // If a user with this email exists, update their name; otherwise, insert
344
+ {
345
+ rows: [
346
+ { name: 'John', email: 'john@example.com', age: 30 }
347
+ ],
348
+ conflictTarget: 'email'
349
+ }
350
+ ```
351
+
352
+ **Using multiple columns as conflict target:**
353
+
354
+ ```javascript
355
+ // Unique constraint on combination of name and email
356
+ {
357
+ rows: [
358
+ { name: 'John', email: 'john@example.com', age: 30 }
359
+ ],
360
+ conflictTarget: ['name', 'email']
361
+ }
362
+ ```
363
+
364
+ **Using the query generator directly:**
365
+
366
+ ```javascript
367
+ const { query, args } = entity.query.upsert(
368
+ [{ id: 1, name: 'John', email: 'john@example.com' }],
369
+ 'id',
370
+ 1, // consumerId (optional)
371
+ 'admin', // consumerName (optional)
372
+ 'RETURNING id' // return clause (optional)
373
+ );
374
+ // Generates:
375
+ // INSERT INTO public.users (name, email, "consumerId", "consumerName")
376
+ // VALUES ($1, $2, $3, $4)
377
+ // ON CONFLICT (id) DO UPDATE SET
378
+ // name = EXCLUDED.name,
379
+ // email = EXCLUDED.email,
380
+ // "consumerId" = EXCLUDED."consumerId",
381
+ // "consumerName" = EXCLUDED."consumerName"
382
+ // RETURNING id
383
+ ```
384
+
385
+ #### Property Configuration for Upsert
386
+
387
+ Properties are automatically included in upsert if they have both INSERT and UPDATE operations:
388
+
389
+ ```javascript
390
+ {
391
+ key: 'name',
392
+ operations: ['SELECT', 'INSERT', 'UPDATE'] // Included in upsert
393
+ }
394
+
395
+ {
396
+ key: 'id',
397
+ operations: ['SELECT', 'UPDATE'] // NOT included (no INSERT)
398
+ }
399
+
400
+ {
401
+ key: 'createdAt',
402
+ operations: ['SELECT', 'INSERT'] // NOT included (no UPDATE)
403
+ }
404
+ ```
405
+
406
+ #### Important Notes
407
+
408
+ - **Conflict Target Required**: The `conflictTarget` parameter must specify an existing unique constraint or primary key
409
+ - **Mixed Rows**: You can upsert rows with and without IDs in the same request if your conflict target handles it (e.g., using `SERIAL` primary key)
410
+ - **Atomic Operation**: Unlike separate insert/update calls, upsert is a single atomic database operation
411
+ - **Concurrent Safety**: Prevents race conditions when multiple requests try to create the same record
412
+
285
413
  ### Filters
286
414
 
287
415
  Filters support two formats for maximum flexibility:
@@ -98,6 +98,8 @@ export declare class SQLEntity extends Entity {
98
98
  private sel: unknown;
99
99
  private ins: unknown;
100
100
  private upd: unknown;
101
+ private ups: unknown;
102
+ private arc: unknown;
101
103
 
102
104
  constructor(name: string, properties: Property[], schema?: string);
103
105
 
@@ -106,11 +108,15 @@ export declare class SQLEntity extends Entity {
106
108
 
107
109
  get schema(): string;
108
110
  set schema(schema: string);
109
-
111
+
112
+ get properties(): Property[];
113
+
110
114
  get addArraySubstack(): SubstackTuple;
111
115
  get addOneSubstack(): SubstackTuple;
112
116
  get updateArraySubstack(): SubstackTuple;
113
117
  get updateOneSubstack(): SubstackTuple;
118
+ get upsertArraySubstack(): SubstackTuple;
119
+ get upsertOneSubstack(): SubstackTuple;
114
120
 
115
121
  query: {
116
122
  select: (
@@ -133,6 +139,15 @@ export declare class SQLEntity extends Entity {
133
139
  args: unknown[];
134
140
  };
135
141
 
142
+ archive: (
143
+ rows: Record<string, unknown>[],
144
+ consumerId?: number | string,
145
+ consumerName?: string
146
+ ) => {
147
+ query: string;
148
+ args: unknown[];
149
+ };
150
+
136
151
  insert: (
137
152
  rows: Record<string, unknown>[],
138
153
  consumerId?: number | string,
@@ -143,6 +158,17 @@ export declare class SQLEntity extends Entity {
143
158
  args: unknown[];
144
159
  };
145
160
 
161
+ upsert: (
162
+ rows: Record<string, unknown>[],
163
+ conflictTarget: string | string[],
164
+ consumerId?: number | string,
165
+ consumerName?: string,
166
+ rtn?: string
167
+ ) => {
168
+ query: string;
169
+ args: unknown[];
170
+ };
171
+
146
172
  delete: (ids: number[]) => {
147
173
  query: string;
148
174
  args: number[];
@@ -156,6 +182,7 @@ export declare class SQLEntity extends Entity {
156
182
  get(req: Request, res: Response, next: NextFunction): void;
157
183
  add(req: Request, res: Response, next: NextFunction): Promise<void>;
158
184
  update(req: Request, res: Response, next: NextFunction): Promise<void>;
185
+ upsert(req: Request, res: Response, next: NextFunction): Promise<void>;
159
186
  archive(req: Request, res: Response, next: NextFunction): Promise<void>;
160
187
  delete(req: Request, res: Response, next: NextFunction): Promise<void>;
161
188
  deleteArchive(req: Request, res: Response, next: NextFunction): void;
@@ -320,7 +320,7 @@ class Insert {
320
320
  }
321
321
  }
322
322
 
323
- var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
323
+ var __awaiter$3 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
324
324
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
325
325
  return new (P || (P = Promise))(function (resolve, reject) {
326
326
  function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
@@ -362,7 +362,7 @@ class Update {
362
362
  return { query, args };
363
363
  }
364
364
  execute(query, args, client) {
365
- return __awaiter$2(this, void 0, void 0, function* () {
365
+ return __awaiter$3(this, void 0, void 0, function* () {
366
366
  return execute(query, args, client);
367
367
  });
368
368
  }
@@ -372,6 +372,104 @@ class Update {
372
372
  }
373
373
  }
374
374
 
375
+ class Upsert {
376
+ constructor() {
377
+ this._props = [];
378
+ this._quotedProps = [];
379
+ this._nbProps = 0;
380
+ this._cols = "";
381
+ }
382
+ addProp(prop) {
383
+ this._props.push(prop);
384
+ this._quotedProps.push(quoteIfUppercase(prop));
385
+ this._nbProps++;
386
+ this._cols = this._quotedProps.join(", ");
387
+ }
388
+ query(schema, table, rows, conflictTarget, consumerId, consumerName, rtn = "") {
389
+ if (!conflictTarget ||
390
+ (Array.isArray(conflictTarget) && conflictTarget.length === 0) ||
391
+ (typeof conflictTarget === 'string' && conflictTarget.trim() === '')) {
392
+ throw new Error('conflictTarget must be provided for upsert operation');
393
+ }
394
+ const propsToUse = [...this._props];
395
+ const quotedPropsToUse = [...this._quotedProps];
396
+ let nbProps = this._nbProps;
397
+ let cols = this._cols;
398
+ if (consumerId !== undefined && consumerName !== undefined) {
399
+ propsToUse.push("consumerId", "consumerName");
400
+ quotedPropsToUse.push(`"consumerId"`, `"consumerName"`);
401
+ nbProps += 2;
402
+ cols += `, "consumerId", "consumerName"`;
403
+ }
404
+ const conflictColumns = Array.isArray(conflictTarget)
405
+ ? conflictTarget.map(col => quoteIfUppercase(col)).join(", ")
406
+ : quoteIfUppercase(conflictTarget);
407
+ let query = `INSERT INTO ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} (${cols}) VALUES `;
408
+ const args = [];
409
+ let i = 0;
410
+ for (const row of rows) {
411
+ if (consumerId !== undefined && consumerName !== undefined) {
412
+ row.consumerId = consumerId;
413
+ row.consumerName = consumerName;
414
+ }
415
+ query += `${$i(nbProps, i)}, `;
416
+ for (const prop of propsToUse) {
417
+ args.push(row[prop]);
418
+ }
419
+ i += nbProps;
420
+ }
421
+ query = query.slice(0, -2);
422
+ query += ` ON CONFLICT (${conflictColumns}) DO UPDATE SET `;
423
+ const conflictTargetArray = Array.isArray(conflictTarget) ? conflictTarget : [conflictTarget];
424
+ const updateCols = quotedPropsToUse.filter((_, idx) => {
425
+ const propName = propsToUse[idx];
426
+ return !conflictTargetArray.includes(propName);
427
+ });
428
+ for (const col of updateCols) {
429
+ query += `${col} = EXCLUDED.${col}, `;
430
+ }
431
+ query = query.slice(0, -2);
432
+ if (rtn)
433
+ query += ` ${rtn}`;
434
+ return { query, args };
435
+ }
436
+ rtn(prop) {
437
+ return `RETURNING ${quoteIfUppercase(prop)}`;
438
+ }
439
+ execute(query, args, client) {
440
+ return execute(query, args, client);
441
+ }
442
+ }
443
+
444
+ var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
445
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
446
+ return new (P || (P = Promise))(function (resolve, reject) {
447
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
448
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
449
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
450
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
451
+ });
452
+ };
453
+ class Archive {
454
+ query(schema, table, rows, consumerId, consumerName) {
455
+ log.debug(`${LOGS_PREFIX}Archive query input rows: ${JSON.stringify(rows, null, 2)}`);
456
+ const l = rows.length;
457
+ const args = rows.map(row => row.id);
458
+ let query = `UPDATE ${quoteIfUppercase(schema)}.${quoteIfUppercase(table)} SET archived = true`;
459
+ if (consumerId !== undefined && consumerName !== undefined) {
460
+ query += `, ${quoteIfUppercase("consumerId")} = $${l + 1}, ${quoteIfUppercase("consumerName")} = $${l + 2}`;
461
+ args.push(consumerId, consumerName);
462
+ }
463
+ query += ` WHERE id IN ${$i(l, 0)}`;
464
+ return { query, args };
465
+ }
466
+ execute(query, args, client) {
467
+ return __awaiter$2(this, void 0, void 0, function* () {
468
+ return execute(query, args, client);
469
+ });
470
+ }
471
+ }
472
+
375
473
  var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
376
474
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
377
475
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -584,6 +682,8 @@ class SQLEntity extends Entity {
584
682
  this.sel = new Select();
585
683
  this.ins = new Insert();
586
684
  this.upd = new Update();
685
+ this.ups = new Upsert();
686
+ this.arc = new Archive();
587
687
  this.query = {
588
688
  select: (first = 0, rows = null, sortField = null, sortOrder = null, filters = null) => {
589
689
  return this.sel.query(this.schema, this.table, first, rows, sortField, sortOrder, filters);
@@ -594,9 +694,15 @@ class SQLEntity extends Entity {
594
694
  insert: (rows, consumerId, consumerName, rtn = "") => {
595
695
  return this.ins.query(this.schema, this.table, rows, consumerId, consumerName, rtn);
596
696
  },
697
+ upsert: (rows, conflictTarget, consumerId, consumerName, rtn = "") => {
698
+ return this.ups.query(this.schema, this.table, rows, conflictTarget, consumerId, consumerName, rtn);
699
+ },
597
700
  delete: (ids) => {
598
701
  return queryById(this.schema, this.table, ids);
599
702
  },
703
+ archive: (rows, consumerId, consumerName) => {
704
+ return this.arc.query(this.schema, this.table, rows, consumerId, consumerName);
705
+ },
600
706
  deleteArchive: () => {
601
707
  return queryByDate();
602
708
  },
@@ -612,7 +718,7 @@ class SQLEntity extends Entity {
612
718
  const rows = b.rows || null;
613
719
  const sortField = b.sortField || null;
614
720
  const sortOrder = b.sortOrder === -1 || b.sortOrder === "DESC" ? "DESC" : "ASC";
615
- const filters = cleanFilters(b.filters, this.properties || []) || null;
721
+ const filters = cleanFilters(b.filters, this.properties) || null;
616
722
  const pagination = b.pagination || false;
617
723
  const dbClient = l.dbClient || null;
618
724
  log.debug(`get(first='${first}', rows='${rows}',
@@ -673,17 +779,49 @@ class SQLEntity extends Entity {
673
779
  l.rows = r;
674
780
  next();
675
781
  });
676
- this.archive = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
782
+ this.upsert = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
677
783
  const l = res.locals;
678
- let rows = req.body.rows;
784
+ const rows = req.body.rows;
785
+ const conflictTarget = req.body.conflictTarget;
679
786
  const dbClient = l.dbClient || null;
680
787
  const cId = l.consumerId;
681
788
  const cName = l.consumerName;
682
- log.debug(`${LOGS_PREFIX}archive(rows=${rows.length}, consumerId=${cId})`);
683
- rows = rows.map((id) => (Object.assign(Object.assign({}, id), { archived: true })));
789
+ if (!conflictTarget) {
790
+ return next({ status: 400, msg: "Missing conflictTarget for upsert operation" });
791
+ }
792
+ if (!rows || !Array.isArray(rows) || rows.length === 0) {
793
+ return next({ status: 400, msg: "Missing or empty rows array for upsert operation" });
794
+ }
795
+ log.debug(`${LOGS_PREFIX}upsert(rows=${rows.length}, conflictTarget=${conflictTarget}, consumerId=${cId})`);
796
+ const rtn = this.ups.rtn("id");
684
797
  const chunks = chunk(rows);
685
798
  for (const c of chunks) {
686
- const { query, args } = this.upd.query(this._schema, this._table, c, cId, cName);
799
+ const { query, args } = this.ups.query(this._schema, this._table, c, conflictTarget, cId, cName, rtn);
800
+ let db;
801
+ try {
802
+ db = yield execute(query, args, dbClient);
803
+ }
804
+ catch (err) {
805
+ return next(err);
806
+ }
807
+ const r = db.rows;
808
+ for (let i = 0; i < c.length; i++) {
809
+ c[i].id = r[i].id;
810
+ }
811
+ }
812
+ l.rows = flatten(chunks);
813
+ next();
814
+ });
815
+ this.archive = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
816
+ const l = res.locals;
817
+ let r = req.body.rows;
818
+ const dbClient = l.dbClient || null;
819
+ const cId = l.consumerId;
820
+ const cName = l.consumerName;
821
+ log.debug(`${LOGS_PREFIX}archive(rows=${r.length}, consumerId=${cId})`);
822
+ const chunks = chunk(r);
823
+ for (const c of chunks) {
824
+ const { query, args } = this.arc.query(this._schema, this._table, c, cId, cName);
687
825
  try {
688
826
  yield execute(query, args, dbClient);
689
827
  }
@@ -766,6 +904,9 @@ class SQLEntity extends Entity {
766
904
  throw new Error(`${LOGS_PREFIX}schema must be a string of length > 0`);
767
905
  this._schema = schema;
768
906
  }
907
+ get properties() {
908
+ return super.properties;
909
+ }
769
910
  get addArraySubstack() {
770
911
  return [this.normalizeArray, this.validateArray, this.add];
771
912
  }
@@ -778,7 +919,15 @@ class SQLEntity extends Entity {
778
919
  get updateOneSubstack() {
779
920
  return [this.normalizeOne, this.validateOne, this.update];
780
921
  }
922
+ get upsertArraySubstack() {
923
+ return [this.normalizeArray, this.validateArray, this.upsert];
924
+ }
925
+ get upsertOneSubstack() {
926
+ return [this.normalizeOne, this.validateOne, this.upsert];
927
+ }
781
928
  mapProps(operations, key) {
929
+ let hasInsert = false;
930
+ let hasUpdate = false;
782
931
  for (const o of operations) {
783
932
  switch (o) {
784
933
  case "SELECT":
@@ -786,12 +935,17 @@ class SQLEntity extends Entity {
786
935
  break;
787
936
  case "UPDATE":
788
937
  this.upd.addProp(key);
938
+ hasUpdate = true;
789
939
  break;
790
940
  case "INSERT":
791
941
  this.ins.addProp(key);
942
+ hasInsert = true;
792
943
  break;
793
944
  }
794
945
  }
946
+ if (hasInsert && hasUpdate) {
947
+ this.ups.addProp(key);
948
+ }
795
949
  }
796
950
  }
797
951
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwtechs/antity-pgsql",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Open source library to add PostgreSQL support to @dwtechs/Antity entities.",
5
5
  "keywords": [
6
6
  "entities"