@naturalcycles/db-lib 10.18.0 → 10.20.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/dist/commondao/common.dao.d.ts +64 -71
- package/dist/commondao/common.dao.js +207 -466
- package/dist/commondao/common.dao.model.d.ts +1 -27
- package/dist/commondao/commonDaoTransaction.d.ts +38 -0
- package/dist/commondao/commonDaoTransaction.js +78 -0
- package/dist/commondb/base.common.db.d.ts +3 -3
- package/dist/commondb/base.common.db.js +3 -3
- package/dist/commondb/common.db.d.ts +7 -11
- package/dist/inmemory/inMemory.db.d.ts +3 -3
- package/dist/inmemory/inMemory.db.js +3 -3
- package/dist/query/dbQuery.d.ts +1 -1
- package/dist/testing/commonDaoTest.js +0 -3
- package/package.json +1 -1
- package/src/commondao/common.dao.model.ts +5 -26
- package/src/commondao/common.dao.ts +302 -562
- package/src/commondao/commonDaoTransaction.ts +124 -0
- package/src/commondb/base.common.db.ts +3 -3
- package/src/commondb/common.db.ts +8 -13
- package/src/inmemory/inMemory.db.ts +3 -3
- package/src/query/dbQuery.ts +1 -1
- package/src/testing/commonDaoTest.ts +0 -3
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { _isTruthy } from '@naturalcycles/js-lib';
|
|
2
2
|
import { _uniqBy } from '@naturalcycles/js-lib/array/array.util.js';
|
|
3
3
|
import { localTime } from '@naturalcycles/js-lib/datetime/localTime.js';
|
|
4
|
-
import {
|
|
5
|
-
import { _assert, AppError, ErrorMode } from '@naturalcycles/js-lib/error';
|
|
4
|
+
import { _assert, ErrorMode } from '@naturalcycles/js-lib/error';
|
|
6
5
|
import { _deepJsonEquals } from '@naturalcycles/js-lib/object/deepEquals.js';
|
|
7
|
-
import {
|
|
6
|
+
import { _filterUndefinedValues, _objectAssignExact, } from '@naturalcycles/js-lib/object/object.util.js';
|
|
8
7
|
import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
|
|
9
|
-
import {
|
|
8
|
+
import { _stringMapEntries, _stringMapValues, } from '@naturalcycles/js-lib/types';
|
|
10
9
|
import { _passthroughPredicate, _typeCast, SKIP } from '@naturalcycles/js-lib/types';
|
|
11
10
|
import { stringId } from '@naturalcycles/nodejs-lib';
|
|
12
|
-
import { transformFlatten } from '@naturalcycles/nodejs-lib/stream';
|
|
11
|
+
import { transformFlatten, transformMapSync, } from '@naturalcycles/nodejs-lib/stream';
|
|
13
12
|
import { _pipeline, transformChunk, transformLogProgress, transformMap, transformNoOp, writableVoid, } from '@naturalcycles/nodejs-lib/stream';
|
|
14
13
|
import { DBLibError } from '../cnst.js';
|
|
15
14
|
import { RunnableDBQuery } from '../query/dbQuery.js';
|
|
16
|
-
import {
|
|
15
|
+
import { CommonDaoTransaction } from './commonDaoTransaction.js';
|
|
17
16
|
/**
|
|
18
17
|
* Lowest common denominator API between supported Databases.
|
|
19
18
|
*
|
|
@@ -26,7 +25,6 @@ export class CommonDao {
|
|
|
26
25
|
constructor(cfg) {
|
|
27
26
|
this.cfg = cfg;
|
|
28
27
|
this.cfg = {
|
|
29
|
-
logLevel: CommonDaoLogLevel.NONE,
|
|
30
28
|
generateId: true,
|
|
31
29
|
assignGeneratedIds: false,
|
|
32
30
|
useCreatedProperty: true,
|
|
@@ -58,22 +56,13 @@ export class CommonDao {
|
|
|
58
56
|
return this.validateAndConvert(bm, undefined, opt);
|
|
59
57
|
}
|
|
60
58
|
// GET
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const table = opt.table || this.cfg.table;
|
|
69
|
-
const started = this.logStarted(op, table);
|
|
70
|
-
let dbm = (await (opt.tx || this.cfg.db).getByIds(table, [id], opt))[0];
|
|
71
|
-
if (dbm && this.cfg.hooks.afterLoad) {
|
|
72
|
-
dbm = (await this.cfg.hooks.afterLoad(dbm)) || undefined;
|
|
73
|
-
}
|
|
74
|
-
const bm = await this.dbmToBM(dbm, opt);
|
|
75
|
-
this.logResult(started, op, bm, table);
|
|
76
|
-
return bm || null;
|
|
59
|
+
async requireById(id, opt = {}) {
|
|
60
|
+
const bm = await this.getById(id, opt);
|
|
61
|
+
return this.ensureRequired(bm, id, opt);
|
|
62
|
+
}
|
|
63
|
+
async requireByIdAsDBM(id, opt = {}) {
|
|
64
|
+
const dbm = await this.getByIdAsDBM(id, opt);
|
|
65
|
+
return this.ensureRequired(dbm, id, opt);
|
|
77
66
|
}
|
|
78
67
|
async getByIdOrEmpty(id, part = {}, opt) {
|
|
79
68
|
const bm = await this.getById(id, opt);
|
|
@@ -81,99 +70,32 @@ export class CommonDao {
|
|
|
81
70
|
return bm;
|
|
82
71
|
return this.create({ ...part, id }, opt);
|
|
83
72
|
}
|
|
84
|
-
|
|
85
|
-
|
|
73
|
+
async getById(id, opt = {}) {
|
|
74
|
+
if (!id)
|
|
75
|
+
return null;
|
|
76
|
+
const [dbm] = await this.loadByIds([id], opt);
|
|
77
|
+
return await this.dbmToBM(dbm, opt);
|
|
78
|
+
}
|
|
86
79
|
async getByIdAsDBM(id, opt = {}) {
|
|
87
80
|
if (!id)
|
|
88
81
|
return null;
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
const started = this.logStarted(op, table);
|
|
92
|
-
let [dbm] = await (opt.tx || this.cfg.db).getByIds(table, [id], opt);
|
|
93
|
-
if (dbm && this.cfg.hooks.afterLoad) {
|
|
94
|
-
dbm = (await this.cfg.hooks.afterLoad(dbm)) || undefined;
|
|
95
|
-
}
|
|
96
|
-
dbm = this.anyToDBM(dbm, opt);
|
|
97
|
-
this.logResult(started, op, dbm, table);
|
|
98
|
-
return dbm || null;
|
|
82
|
+
const [row] = await this.loadByIds([id], opt);
|
|
83
|
+
return this.anyToDBM(row, opt) || null;
|
|
99
84
|
}
|
|
100
85
|
async getByIds(ids, opt = {}) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const op = `getByIds ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`;
|
|
104
|
-
const table = opt.table || this.cfg.table;
|
|
105
|
-
const started = this.logStarted(op, table);
|
|
106
|
-
let dbms = await (opt.tx || this.cfg.db).getByIds(table, ids, opt);
|
|
107
|
-
if (this.cfg.hooks.afterLoad && dbms.length) {
|
|
108
|
-
dbms = (await pMap(dbms, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(_isTruthy);
|
|
109
|
-
}
|
|
110
|
-
const bms = await this.dbmsToBM(dbms, opt);
|
|
111
|
-
this.logResult(started, op, bms, table);
|
|
112
|
-
return bms;
|
|
86
|
+
const dbms = await this.loadByIds(ids, opt);
|
|
87
|
+
return await this.dbmsToBM(dbms, opt);
|
|
113
88
|
}
|
|
114
89
|
async getByIdsAsDBM(ids, opt = {}) {
|
|
90
|
+
const rows = await this.loadByIds(ids, opt);
|
|
91
|
+
return this.anyToDBMs(rows);
|
|
92
|
+
}
|
|
93
|
+
// DRY private method
|
|
94
|
+
async loadByIds(ids, opt = {}) {
|
|
115
95
|
if (!ids.length)
|
|
116
96
|
return [];
|
|
117
|
-
const op = `getByIdsAsDBM ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`;
|
|
118
97
|
const table = opt.table || this.cfg.table;
|
|
119
|
-
|
|
120
|
-
let dbms = await (opt.tx || this.cfg.db).getByIds(table, ids, opt);
|
|
121
|
-
if (this.cfg.hooks.afterLoad && dbms.length) {
|
|
122
|
-
dbms = (await pMap(dbms, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(_isTruthy);
|
|
123
|
-
}
|
|
124
|
-
this.logResult(started, op, dbms, table);
|
|
125
|
-
return dbms;
|
|
126
|
-
}
|
|
127
|
-
async requireById(id, opt = {}) {
|
|
128
|
-
const r = await this.getById(id, opt);
|
|
129
|
-
if (!r) {
|
|
130
|
-
this.throwRequiredError(id, opt);
|
|
131
|
-
}
|
|
132
|
-
return r;
|
|
133
|
-
}
|
|
134
|
-
async requireByIdAsDBM(id, opt = {}) {
|
|
135
|
-
const r = await this.getByIdAsDBM(id, opt);
|
|
136
|
-
if (!r) {
|
|
137
|
-
this.throwRequiredError(id, opt);
|
|
138
|
-
}
|
|
139
|
-
return r;
|
|
140
|
-
}
|
|
141
|
-
throwRequiredError(id, opt) {
|
|
142
|
-
const table = opt.table || this.cfg.table;
|
|
143
|
-
throw new AppError(`DB row required, but not found in ${table}`, {
|
|
144
|
-
table,
|
|
145
|
-
id,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Throws if readOnly is true
|
|
150
|
-
*/
|
|
151
|
-
requireWriteAccess() {
|
|
152
|
-
if (this.cfg.readOnly) {
|
|
153
|
-
throw new AppError(DBLibError.DAO_IS_READ_ONLY, {
|
|
154
|
-
table: this.cfg.table,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Throws if readOnly is true
|
|
160
|
-
*/
|
|
161
|
-
requireObjectMutability(opt) {
|
|
162
|
-
if (this.cfg.immutable && !opt.allowMutability) {
|
|
163
|
-
throw new AppError(DBLibError.OBJECT_IS_IMMUTABLE, {
|
|
164
|
-
table: this.cfg.table,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
async ensureUniqueId(table, dbm) {
|
|
169
|
-
// todo: retry N times
|
|
170
|
-
const existing = await this.cfg.db.getByIds(table, [dbm.id]);
|
|
171
|
-
if (existing.length) {
|
|
172
|
-
throw new AppError(DBLibError.NON_UNIQUE_ID, {
|
|
173
|
-
table,
|
|
174
|
-
ids: existing.map(i => i.id),
|
|
175
|
-
});
|
|
176
|
-
}
|
|
98
|
+
return await (opt.tx || this.cfg.db).getByIds(table, ids, opt);
|
|
177
99
|
}
|
|
178
100
|
async getBy(by, value, limit = 0, opt) {
|
|
179
101
|
return await this.query().filterEq(by, value).limit(limit).runQuery(opt);
|
|
@@ -214,15 +136,9 @@ export class CommonDao {
|
|
|
214
136
|
async runQueryExtended(q, opt = {}) {
|
|
215
137
|
this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
|
|
216
138
|
q.table = opt.table || q.table;
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
const partialQuery = !!q._selectedFieldNames;
|
|
221
|
-
if (this.cfg.hooks.afterLoad && rows.length) {
|
|
222
|
-
rows = (await pMap(rows, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(_isTruthy);
|
|
223
|
-
}
|
|
224
|
-
const bms = partialQuery ? rows : await this.dbmsToBM(rows, opt);
|
|
225
|
-
this.logResult(started, op, bms, q.table);
|
|
139
|
+
const { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
|
|
140
|
+
const isPartialQuery = !!q._selectedFieldNames;
|
|
141
|
+
const bms = isPartialQuery ? rows : await this.dbmsToBM(rows, opt);
|
|
226
142
|
return {
|
|
227
143
|
rows: bms,
|
|
228
144
|
...queryResult,
|
|
@@ -235,48 +151,27 @@ export class CommonDao {
|
|
|
235
151
|
async runQueryExtendedAsDBM(q, opt = {}) {
|
|
236
152
|
this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
|
|
237
153
|
q.table = opt.table || q.table;
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
if (this.cfg.hooks.afterLoad && rows.length) {
|
|
242
|
-
rows = (await pMap(rows, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(_isTruthy);
|
|
243
|
-
}
|
|
244
|
-
const partialQuery = !!q._selectedFieldNames;
|
|
245
|
-
const dbms = partialQuery ? rows : this.anyToDBMs(rows, opt);
|
|
246
|
-
this.logResult(started, op, dbms, q.table);
|
|
154
|
+
const { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
|
|
155
|
+
const isPartialQuery = !!q._selectedFieldNames;
|
|
156
|
+
const dbms = isPartialQuery ? rows : this.anyToDBMs(rows, opt);
|
|
247
157
|
return { rows: dbms, ...queryResult };
|
|
248
158
|
}
|
|
249
159
|
async runQueryCount(q, opt = {}) {
|
|
250
160
|
this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
|
|
251
161
|
q.table = opt.table || q.table;
|
|
252
|
-
|
|
253
|
-
const started = this.logStarted(op, q.table);
|
|
254
|
-
const count = await this.cfg.db.runQueryCount(q, opt);
|
|
255
|
-
if (this.cfg.logLevel >= CommonDaoLogLevel.OPERATIONS) {
|
|
256
|
-
this.cfg.logger?.log(`<< ${q.table}.${op}: ${count} row(s) in ${_since(started)}`);
|
|
257
|
-
}
|
|
258
|
-
return count;
|
|
162
|
+
return await this.cfg.db.runQueryCount(q, opt);
|
|
259
163
|
}
|
|
260
164
|
async streamQueryForEach(q, mapper, opt = {}) {
|
|
261
165
|
this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
|
|
262
166
|
q.table = opt.table || q.table;
|
|
263
167
|
opt.skipValidation = opt.skipValidation !== false; // default true
|
|
264
168
|
opt.errorMode ||= ErrorMode.SUPPRESS;
|
|
265
|
-
const
|
|
266
|
-
const op = `streamQueryForEach(${q.pretty()})`;
|
|
267
|
-
const started = this.logStarted(op, q.table, true);
|
|
268
|
-
let count = 0;
|
|
169
|
+
const isPartialQuery = !!q._selectedFieldNames;
|
|
269
170
|
await _pipeline([
|
|
270
171
|
this.cfg.db.streamQuery(q, opt),
|
|
271
172
|
transformMap(async (dbm) => {
|
|
272
|
-
|
|
273
|
-
if (partialQuery)
|
|
173
|
+
if (isPartialQuery)
|
|
274
174
|
return dbm;
|
|
275
|
-
if (this.cfg.hooks.afterLoad) {
|
|
276
|
-
dbm = (await this.cfg.hooks.afterLoad(dbm));
|
|
277
|
-
if (dbm === null)
|
|
278
|
-
return SKIP;
|
|
279
|
-
}
|
|
280
175
|
return await this.dbmToBM(dbm, opt);
|
|
281
176
|
}, {
|
|
282
177
|
errorMode: opt.errorMode,
|
|
@@ -292,30 +187,18 @@ export class CommonDao {
|
|
|
292
187
|
}),
|
|
293
188
|
writableVoid(),
|
|
294
189
|
]);
|
|
295
|
-
if (this.cfg.logLevel >= CommonDaoLogLevel.OPERATIONS) {
|
|
296
|
-
this.cfg.logger?.log(`<< ${q.table}.${op}: ${count} row(s) in ${_since(started)}`);
|
|
297
|
-
}
|
|
298
190
|
}
|
|
299
191
|
async streamQueryAsDBMForEach(q, mapper, opt = {}) {
|
|
300
192
|
this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
|
|
301
193
|
q.table = opt.table || q.table;
|
|
302
194
|
opt.skipValidation = opt.skipValidation !== false; // default true
|
|
303
195
|
opt.errorMode ||= ErrorMode.SUPPRESS;
|
|
304
|
-
const
|
|
305
|
-
const op = `streamQueryAsDBMForEach(${q.pretty()})`;
|
|
306
|
-
const started = this.logStarted(op, q.table, true);
|
|
307
|
-
let count = 0;
|
|
196
|
+
const isPartialQuery = !!q._selectedFieldNames;
|
|
308
197
|
await _pipeline([
|
|
309
198
|
this.cfg.db.streamQuery(q, opt),
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (partialQuery)
|
|
199
|
+
transformMapSync(dbm => {
|
|
200
|
+
if (isPartialQuery)
|
|
313
201
|
return dbm;
|
|
314
|
-
if (this.cfg.hooks.afterLoad) {
|
|
315
|
-
dbm = (await this.cfg.hooks.afterLoad(dbm));
|
|
316
|
-
if (dbm === null)
|
|
317
|
-
return SKIP;
|
|
318
|
-
}
|
|
319
202
|
return this.anyToDBM(dbm, opt);
|
|
320
203
|
}, {
|
|
321
204
|
errorMode: opt.errorMode,
|
|
@@ -331,9 +214,6 @@ export class CommonDao {
|
|
|
331
214
|
}),
|
|
332
215
|
writableVoid(),
|
|
333
216
|
]);
|
|
334
|
-
if (this.cfg.logLevel >= CommonDaoLogLevel.OPERATIONS) {
|
|
335
|
-
this.cfg.logger?.log(`<< ${q.table}.${op}: ${count} row(s) in ${_since(started)}`);
|
|
336
|
-
}
|
|
337
217
|
}
|
|
338
218
|
/**
|
|
339
219
|
* Stream as Readable, to be able to .pipe() it further with support of backpressure.
|
|
@@ -343,19 +223,14 @@ export class CommonDao {
|
|
|
343
223
|
q.table = opt.table || q.table;
|
|
344
224
|
opt.skipValidation = opt.skipValidation !== false; // default true
|
|
345
225
|
opt.errorMode ||= ErrorMode.SUPPRESS;
|
|
346
|
-
const
|
|
226
|
+
const isPartialQuery = !!q._selectedFieldNames;
|
|
347
227
|
const stream = this.cfg.db.streamQuery(q, opt);
|
|
348
|
-
if (
|
|
228
|
+
if (isPartialQuery)
|
|
349
229
|
return stream;
|
|
350
230
|
return (stream
|
|
351
231
|
// the commented out line was causing RangeError: Maximum call stack size exceeded
|
|
352
232
|
// .on('error', err => stream.emit('error', err))
|
|
353
|
-
.pipe(
|
|
354
|
-
if (this.cfg.hooks.afterLoad) {
|
|
355
|
-
dbm = (await this.cfg.hooks.afterLoad(dbm));
|
|
356
|
-
if (dbm === null)
|
|
357
|
-
return SKIP;
|
|
358
|
-
}
|
|
233
|
+
.pipe(transformMapSync(dbm => {
|
|
359
234
|
return this.anyToDBM(dbm, opt);
|
|
360
235
|
}, {
|
|
361
236
|
errorMode: opt.errorMode,
|
|
@@ -376,16 +251,11 @@ export class CommonDao {
|
|
|
376
251
|
opt.skipValidation = opt.skipValidation !== false; // default true
|
|
377
252
|
opt.errorMode ||= ErrorMode.SUPPRESS;
|
|
378
253
|
const stream = this.cfg.db.streamQuery(q, opt);
|
|
379
|
-
const
|
|
380
|
-
if (
|
|
254
|
+
const isPartialQuery = !!q._selectedFieldNames;
|
|
255
|
+
if (isPartialQuery)
|
|
381
256
|
return stream;
|
|
382
257
|
// This almost works, but hard to implement `errorMode: THROW_AGGREGATED` in this case
|
|
383
258
|
// return stream.flatMap(async (dbm: DBM) => {
|
|
384
|
-
// if (this.cfg.hooks!.afterLoad) {
|
|
385
|
-
// dbm = (await this.cfg.hooks!.afterLoad(dbm))!
|
|
386
|
-
// if (dbm === null) return [] // SKIP
|
|
387
|
-
// }
|
|
388
|
-
//
|
|
389
259
|
// return [await this.dbmToBM(dbm, opt)] satisfies BM[]
|
|
390
260
|
// }, {
|
|
391
261
|
// concurrency: 16,
|
|
@@ -397,11 +267,6 @@ export class CommonDao {
|
|
|
397
267
|
// the commented out line was causing RangeError: Maximum call stack size exceeded
|
|
398
268
|
// .on('error', err => stream.emit('error', err))
|
|
399
269
|
.pipe(transformMap(async (dbm) => {
|
|
400
|
-
if (this.cfg.hooks.afterLoad) {
|
|
401
|
-
dbm = (await this.cfg.hooks.afterLoad(dbm));
|
|
402
|
-
if (dbm === null)
|
|
403
|
-
return SKIP;
|
|
404
|
-
}
|
|
405
270
|
return await this.dbmToBM(dbm, opt);
|
|
406
271
|
}, {
|
|
407
272
|
errorMode: opt.errorMode,
|
|
@@ -441,14 +306,8 @@ export class CommonDao {
|
|
|
441
306
|
this.validateQueryIndexes(q); // throws if query uses `excludeFromIndexes` property
|
|
442
307
|
q.table = opt.table || q.table;
|
|
443
308
|
opt.errorMode ||= ErrorMode.SUPPRESS;
|
|
444
|
-
const op = `streamQueryIdsForEach(${q.pretty()})`;
|
|
445
|
-
const started = this.logStarted(op, q.table, true);
|
|
446
|
-
let count = 0;
|
|
447
309
|
await _pipeline([
|
|
448
|
-
this.cfg.db.streamQuery(q.select(['id']), opt).map(r =>
|
|
449
|
-
count++;
|
|
450
|
-
return r.id;
|
|
451
|
-
}),
|
|
310
|
+
this.cfg.db.streamQuery(q.select(['id']), opt).map(r => r.id),
|
|
452
311
|
transformMap(mapper, {
|
|
453
312
|
...opt,
|
|
454
313
|
predicate: _passthroughPredicate,
|
|
@@ -460,13 +319,9 @@ export class CommonDao {
|
|
|
460
319
|
}),
|
|
461
320
|
writableVoid(),
|
|
462
321
|
]);
|
|
463
|
-
if (this.cfg.logLevel >= CommonDaoLogLevel.OPERATIONS) {
|
|
464
|
-
this.cfg.logger?.log(`<< ${q.table}.${op}: ${count} id(s) in ${_since(started)}`);
|
|
465
|
-
}
|
|
466
322
|
}
|
|
467
323
|
/**
|
|
468
324
|
* Mutates!
|
|
469
|
-
* "Returns", just to have a type of "Saved"
|
|
470
325
|
*/
|
|
471
326
|
assignIdCreatedUpdated(obj, opt = {}) {
|
|
472
327
|
const now = localTime.nowUnix();
|
|
@@ -480,7 +335,6 @@ export class CommonDao {
|
|
|
480
335
|
obj.id ||= (this.cfg.hooks.createNaturalId?.(obj) ||
|
|
481
336
|
this.cfg.hooks.createRandomId());
|
|
482
337
|
}
|
|
483
|
-
return obj;
|
|
484
338
|
}
|
|
485
339
|
// SAVE
|
|
486
340
|
/**
|
|
@@ -598,7 +452,6 @@ export class CommonDao {
|
|
|
598
452
|
return bm;
|
|
599
453
|
}
|
|
600
454
|
}
|
|
601
|
-
const idWasGenerated = !bm.id && this.cfg.generateId;
|
|
602
455
|
this.assignIdCreatedUpdated(bm, opt); // mutates
|
|
603
456
|
_typeCast(bm);
|
|
604
457
|
let dbm = await this.bmToDBM(bm, opt); // validates BM
|
|
@@ -608,125 +461,76 @@ export class CommonDao {
|
|
|
608
461
|
return bm;
|
|
609
462
|
}
|
|
610
463
|
const table = opt.table || this.cfg.table;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (
|
|
614
|
-
opt = { ...opt, saveMethod: 'insert' };
|
|
615
|
-
}
|
|
616
|
-
const op = `save(${dbm.id})`;
|
|
617
|
-
const started = this.logSaveStarted(op, bm, table);
|
|
618
|
-
const { excludeFromIndexes } = this.cfg;
|
|
619
|
-
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
|
|
620
|
-
await (opt.tx || this.cfg.db).saveBatch(table, [dbm], {
|
|
621
|
-
excludeFromIndexes,
|
|
622
|
-
assignGeneratedIds,
|
|
623
|
-
...opt,
|
|
624
|
-
});
|
|
625
|
-
if (assignGeneratedIds) {
|
|
464
|
+
const saveOptions = this.prepareSaveOptions(opt);
|
|
465
|
+
await (opt.tx || this.cfg.db).saveBatch(table, [dbm], saveOptions);
|
|
466
|
+
if (saveOptions.assignGeneratedIds) {
|
|
626
467
|
bm.id = dbm.id;
|
|
627
468
|
}
|
|
628
|
-
this.logSaveResult(started, op, table);
|
|
629
469
|
return bm;
|
|
630
470
|
}
|
|
631
471
|
async saveAsDBM(dbm, opt = {}) {
|
|
632
472
|
this.requireWriteAccess();
|
|
633
|
-
const table = opt.table || this.cfg.table;
|
|
634
|
-
// assigning id in case it misses the id
|
|
635
|
-
// will override/set `updated` field, unless opts.preserveUpdated is set
|
|
636
|
-
const idWasGenerated = !dbm.id && this.cfg.generateId;
|
|
637
473
|
this.assignIdCreatedUpdated(dbm, opt); // mutates
|
|
638
474
|
let row = this.anyToDBM(dbm, opt);
|
|
639
|
-
if (opt.ensureUniqueId && idWasGenerated)
|
|
640
|
-
await this.ensureUniqueId(table, row);
|
|
641
|
-
if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
|
|
642
|
-
opt = { ...opt, saveMethod: 'insert' };
|
|
643
|
-
}
|
|
644
|
-
const op = `saveAsDBM(${row.id})`;
|
|
645
|
-
const started = this.logSaveStarted(op, row, table);
|
|
646
|
-
const { excludeFromIndexes } = this.cfg;
|
|
647
|
-
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
|
|
648
475
|
if (this.cfg.hooks.beforeSave) {
|
|
649
476
|
row = (await this.cfg.hooks.beforeSave(row));
|
|
650
477
|
if (row === null)
|
|
651
478
|
return dbm;
|
|
652
479
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
});
|
|
658
|
-
if (assignGeneratedIds) {
|
|
480
|
+
const table = opt.table || this.cfg.table;
|
|
481
|
+
const saveOptions = this.prepareSaveOptions(opt);
|
|
482
|
+
await (opt.tx || this.cfg.db).saveBatch(table, [row], saveOptions);
|
|
483
|
+
if (saveOptions.assignGeneratedIds) {
|
|
659
484
|
dbm.id = row.id;
|
|
660
485
|
}
|
|
661
|
-
this.logSaveResult(started, op, table);
|
|
662
486
|
return row;
|
|
663
487
|
}
|
|
664
488
|
async saveBatch(bms, opt = {}) {
|
|
665
489
|
if (!bms.length)
|
|
666
490
|
return [];
|
|
667
491
|
this.requireWriteAccess();
|
|
668
|
-
const table = opt.table || this.cfg.table;
|
|
669
492
|
bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt));
|
|
670
493
|
let dbms = await this.bmsToDBM(bms, opt);
|
|
671
494
|
if (this.cfg.hooks.beforeSave && dbms.length) {
|
|
672
495
|
dbms = (await pMap(dbms, async (dbm) => await this.cfg.hooks.beforeSave(dbm))).filter(_isTruthy);
|
|
673
496
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
const op = `saveBatch ${dbms.length} row(s) (${_truncate(dbms
|
|
680
|
-
.slice(0, 10)
|
|
681
|
-
.map(bm => bm.id)
|
|
682
|
-
.join(', '), 50)})`;
|
|
683
|
-
const started = this.logSaveStarted(op, bms, table);
|
|
684
|
-
const { excludeFromIndexes } = this.cfg;
|
|
685
|
-
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
|
|
686
|
-
await (opt.tx || this.cfg.db).saveBatch(table, dbms, {
|
|
687
|
-
excludeFromIndexes,
|
|
688
|
-
assignGeneratedIds,
|
|
689
|
-
...opt,
|
|
690
|
-
});
|
|
691
|
-
if (assignGeneratedIds) {
|
|
497
|
+
const table = opt.table || this.cfg.table;
|
|
498
|
+
const saveOptions = this.prepareSaveOptions(opt);
|
|
499
|
+
await (opt.tx || this.cfg.db).saveBatch(table, dbms, saveOptions);
|
|
500
|
+
if (saveOptions.assignGeneratedIds) {
|
|
692
501
|
dbms.forEach((dbm, i) => (bms[i].id = dbm.id));
|
|
693
502
|
}
|
|
694
|
-
this.logSaveResult(started, op, table);
|
|
695
503
|
return bms;
|
|
696
504
|
}
|
|
697
505
|
async saveBatchAsDBM(dbms, opt = {}) {
|
|
698
506
|
if (!dbms.length)
|
|
699
507
|
return [];
|
|
700
508
|
this.requireWriteAccess();
|
|
701
|
-
|
|
702
|
-
dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt)); // mutates
|
|
509
|
+
dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt));
|
|
703
510
|
let rows = this.anyToDBMs(dbms, opt);
|
|
704
|
-
if (opt.ensureUniqueId)
|
|
705
|
-
throw new AppError('ensureUniqueId is not supported in saveBatch');
|
|
706
|
-
if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
|
|
707
|
-
opt = { ...opt, saveMethod: 'insert' };
|
|
708
|
-
}
|
|
709
|
-
const op = `saveBatchAsDBM ${rows.length} row(s) (${_truncate(rows
|
|
710
|
-
.slice(0, 10)
|
|
711
|
-
.map(bm => bm.id)
|
|
712
|
-
.join(', '), 50)})`;
|
|
713
|
-
const started = this.logSaveStarted(op, rows, table);
|
|
714
|
-
const { excludeFromIndexes } = this.cfg;
|
|
715
|
-
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
|
|
716
511
|
if (this.cfg.hooks.beforeSave && rows.length) {
|
|
717
512
|
rows = (await pMap(rows, async (row) => await this.cfg.hooks.beforeSave(row))).filter(_isTruthy);
|
|
718
513
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
});
|
|
724
|
-
if (assignGeneratedIds) {
|
|
514
|
+
const table = opt.table || this.cfg.table;
|
|
515
|
+
const saveOptions = this.prepareSaveOptions(opt);
|
|
516
|
+
await (opt.tx || this.cfg.db).saveBatch(table, rows, saveOptions);
|
|
517
|
+
if (saveOptions.assignGeneratedIds) {
|
|
725
518
|
rows.forEach((row, i) => (dbms[i].id = row.id));
|
|
726
519
|
}
|
|
727
|
-
this.logSaveResult(started, op, table);
|
|
728
520
|
return rows;
|
|
729
521
|
}
|
|
522
|
+
prepareSaveOptions(opt) {
|
|
523
|
+
let { saveMethod, assignGeneratedIds = this.cfg.assignGeneratedIds, excludeFromIndexes = this.cfg.excludeFromIndexes, } = opt;
|
|
524
|
+
if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
|
|
525
|
+
saveMethod = 'insert';
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
...opt,
|
|
529
|
+
excludeFromIndexes,
|
|
530
|
+
saveMethod,
|
|
531
|
+
assignGeneratedIds,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
730
534
|
/**
|
|
731
535
|
* "Streaming" is implemented by buffering incoming rows into **batches**
|
|
732
536
|
* (of size opt.chunkSize, which defaults to 500),
|
|
@@ -792,12 +596,8 @@ export class CommonDao {
|
|
|
792
596
|
return 0;
|
|
793
597
|
this.requireWriteAccess();
|
|
794
598
|
this.requireObjectMutability(opt);
|
|
795
|
-
const op = `deleteByIds(${ids.join(', ')})`;
|
|
796
599
|
const table = opt.table || this.cfg.table;
|
|
797
|
-
|
|
798
|
-
const count = await (opt.tx || this.cfg.db).deleteByIds(table, ids, opt);
|
|
799
|
-
this.logSaveResult(started, op, table);
|
|
800
|
-
return count;
|
|
600
|
+
return await (opt.tx || this.cfg.db).deleteByIds(table, ids, opt);
|
|
801
601
|
}
|
|
802
602
|
/**
|
|
803
603
|
* Pass `chunkSize: number` (e.g 500) option to use Streaming: it will Stream the query, chunk by 500, and execute
|
|
@@ -809,8 +609,6 @@ export class CommonDao {
|
|
|
809
609
|
this.requireWriteAccess();
|
|
810
610
|
this.requireObjectMutability(opt);
|
|
811
611
|
q.table = opt.table || q.table;
|
|
812
|
-
const op = `deleteByQuery(${q.pretty()})`;
|
|
813
|
-
const started = this.logStarted(op, q.table);
|
|
814
612
|
let deleted = 0;
|
|
815
613
|
if (opt.chunkSize) {
|
|
816
614
|
const { chunkSize, chunkConcurrency = 8 } = opt;
|
|
@@ -838,7 +636,6 @@ export class CommonDao {
|
|
|
838
636
|
else {
|
|
839
637
|
deleted = await this.cfg.db.deleteByQuery(q, opt);
|
|
840
638
|
}
|
|
841
|
-
this.logSaveResult(started, op, q.table);
|
|
842
639
|
return deleted;
|
|
843
640
|
}
|
|
844
641
|
async patchByIds(ids, patch, opt = {}) {
|
|
@@ -851,11 +648,7 @@ export class CommonDao {
|
|
|
851
648
|
this.requireWriteAccess();
|
|
852
649
|
this.requireObjectMutability(opt);
|
|
853
650
|
q.table = opt.table || q.table;
|
|
854
|
-
|
|
855
|
-
const started = this.logStarted(op, q.table);
|
|
856
|
-
const updated = await this.cfg.db.patchByQuery(q, patch, opt);
|
|
857
|
-
this.logSaveResult(started, op, q.table);
|
|
858
|
-
return updated;
|
|
651
|
+
return await this.cfg.db.patchByQuery(q, patch, opt);
|
|
859
652
|
}
|
|
860
653
|
/**
|
|
861
654
|
* Caveat: it doesn't update created/updated props.
|
|
@@ -866,12 +659,9 @@ export class CommonDao {
|
|
|
866
659
|
this.requireWriteAccess();
|
|
867
660
|
this.requireObjectMutability(opt);
|
|
868
661
|
const { table } = this.cfg;
|
|
869
|
-
const op = `increment`;
|
|
870
|
-
const started = this.logStarted(op, table);
|
|
871
662
|
const result = await this.cfg.db.incrementBatch(table, prop, {
|
|
872
663
|
[id]: by,
|
|
873
664
|
});
|
|
874
|
-
this.logSaveResult(started, op, table);
|
|
875
665
|
return result[id];
|
|
876
666
|
}
|
|
877
667
|
/**
|
|
@@ -883,15 +673,11 @@ export class CommonDao {
|
|
|
883
673
|
this.requireWriteAccess();
|
|
884
674
|
this.requireObjectMutability(opt);
|
|
885
675
|
const { table } = this.cfg;
|
|
886
|
-
|
|
887
|
-
const started = this.logStarted(op, table);
|
|
888
|
-
const result = await this.cfg.db.incrementBatch(table, prop, incrementMap);
|
|
889
|
-
this.logSaveResult(started, op, table);
|
|
890
|
-
return result;
|
|
676
|
+
return await this.cfg.db.incrementBatch(table, prop, incrementMap);
|
|
891
677
|
}
|
|
892
678
|
async dbmToBM(_dbm, opt = {}) {
|
|
893
679
|
if (!_dbm)
|
|
894
|
-
return;
|
|
680
|
+
return null;
|
|
895
681
|
// optimization: no need to run full joi DBM validation, cause BM validation will be run
|
|
896
682
|
// const dbm = this.anyToDBM(_dbm, opt)
|
|
897
683
|
let dbm = { ..._dbm, ...this.cfg.hooks.parseNaturalId(_dbm.id) };
|
|
@@ -908,7 +694,7 @@ export class CommonDao {
|
|
|
908
694
|
}
|
|
909
695
|
async bmToDBM(bm, opt) {
|
|
910
696
|
if (bm === undefined)
|
|
911
|
-
return;
|
|
697
|
+
return null;
|
|
912
698
|
// bm gets assigned to the new reference
|
|
913
699
|
bm = this.validateAndConvert(bm, 'save', opt);
|
|
914
700
|
// BM > DBM
|
|
@@ -920,7 +706,7 @@ export class CommonDao {
|
|
|
920
706
|
}
|
|
921
707
|
anyToDBM(dbm, opt = {}) {
|
|
922
708
|
if (!dbm)
|
|
923
|
-
return;
|
|
709
|
+
return null;
|
|
924
710
|
// this shouldn't be happening on load! but should on save!
|
|
925
711
|
// this.assignIdCreatedUpdated(dbm, opt)
|
|
926
712
|
dbm = { ...dbm, ...this.cfg.hooks.parseNaturalId(dbm.id) };
|
|
@@ -933,8 +719,8 @@ export class CommonDao {
|
|
|
933
719
|
// return this.validateAndConvert(dbm, this.cfg.dbmSchema, DBModelType.DBM, opt)
|
|
934
720
|
return dbm;
|
|
935
721
|
}
|
|
936
|
-
anyToDBMs(
|
|
937
|
-
return
|
|
722
|
+
anyToDBMs(rows, opt = {}) {
|
|
723
|
+
return rows.map(entity => this.anyToDBM(entity, opt));
|
|
938
724
|
}
|
|
939
725
|
/**
|
|
940
726
|
* Returns *converted value* (NOT the same reference).
|
|
@@ -981,63 +767,103 @@ export class CommonDao {
|
|
|
981
767
|
async ping() {
|
|
982
768
|
await this.cfg.db.ping();
|
|
983
769
|
}
|
|
984
|
-
|
|
770
|
+
withId(id) {
|
|
985
771
|
return {
|
|
986
772
|
dao: this,
|
|
987
773
|
id,
|
|
988
774
|
};
|
|
989
775
|
}
|
|
990
|
-
|
|
776
|
+
withIds(ids) {
|
|
991
777
|
return {
|
|
992
778
|
dao: this,
|
|
993
779
|
ids,
|
|
994
780
|
};
|
|
995
781
|
}
|
|
996
|
-
|
|
782
|
+
withRows(rows) {
|
|
997
783
|
return {
|
|
998
784
|
dao: this,
|
|
999
|
-
rows,
|
|
785
|
+
rows: rows,
|
|
1000
786
|
};
|
|
1001
787
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
ids: [input.id],
|
|
1009
|
-
}));
|
|
1010
|
-
const rowsByTable = await CommonDao.multiGetByIds(inputs2, opt);
|
|
1011
|
-
const results = inputs.map(({ dao }) => {
|
|
1012
|
-
const { table } = dao.cfg;
|
|
1013
|
-
return rowsByTable[table]?.[0] || null;
|
|
1014
|
-
});
|
|
1015
|
-
return results;
|
|
788
|
+
withRow(row, opt) {
|
|
789
|
+
return {
|
|
790
|
+
dao: this,
|
|
791
|
+
row: row,
|
|
792
|
+
opt: opt,
|
|
793
|
+
};
|
|
1016
794
|
}
|
|
1017
795
|
/**
|
|
1018
|
-
*
|
|
796
|
+
* Load rows (by their ids) from Multiple tables at once.
|
|
797
|
+
* An optimized way to load data, minimizing DB round-trips.
|
|
798
|
+
*
|
|
799
|
+
* @experimental.
|
|
1019
800
|
*/
|
|
1020
|
-
static async
|
|
1021
|
-
|
|
801
|
+
static async multiGet(inputMap, opt = {}) {
|
|
802
|
+
const db = Object.values(inputMap)[0]?.dao.cfg.db;
|
|
803
|
+
if (!db) {
|
|
1022
804
|
return {};
|
|
1023
|
-
const { db } = inputs[0].dao.cfg;
|
|
1024
|
-
const idsByTable = {};
|
|
1025
|
-
for (const { dao, ids } of inputs) {
|
|
1026
|
-
const { table } = dao.cfg;
|
|
1027
|
-
idsByTable[table] = ids;
|
|
1028
805
|
}
|
|
806
|
+
const idsByTable = CommonDao.prepareMultiGetIds(inputMap);
|
|
1029
807
|
// todo: support tx
|
|
1030
|
-
const dbmsByTable = await db.
|
|
1031
|
-
const
|
|
1032
|
-
await
|
|
808
|
+
const dbmsByTable = await db.multiGet(idsByTable, opt);
|
|
809
|
+
const dbmByTableById = CommonDao.multiGetMapByTableById(dbmsByTable);
|
|
810
|
+
return (await CommonDao.prepareMultiGetOutput(inputMap, dbmByTableById, opt));
|
|
811
|
+
}
|
|
812
|
+
static prepareMultiGetIds(inputMap) {
|
|
813
|
+
const idSetByTable = {};
|
|
814
|
+
for (const input of _stringMapValues(inputMap)) {
|
|
815
|
+
const { table } = input.dao.cfg;
|
|
816
|
+
idSetByTable[table] ||= new Set();
|
|
817
|
+
if ('id' in input) {
|
|
818
|
+
// Singular
|
|
819
|
+
idSetByTable[table].add(input.id);
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
// Plural
|
|
823
|
+
for (const id of input.ids) {
|
|
824
|
+
idSetByTable[table].add(id);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const idsByTable = {};
|
|
829
|
+
for (const [table, idSet] of _stringMapEntries(idSetByTable)) {
|
|
830
|
+
idsByTable[table] = [...idSet];
|
|
831
|
+
}
|
|
832
|
+
return idsByTable;
|
|
833
|
+
}
|
|
834
|
+
static multiGetMapByTableById(dbmsByTable) {
|
|
835
|
+
// We create this "map of maps", to be able to track the results back to the input props
|
|
836
|
+
// This is needed to support:
|
|
837
|
+
// - having multiple props from the same table
|
|
838
|
+
const dbmByTableById = {};
|
|
839
|
+
for (const [table, dbms] of _stringMapEntries(dbmsByTable)) {
|
|
840
|
+
dbmByTableById[table] ||= {};
|
|
841
|
+
for (const dbm of dbms) {
|
|
842
|
+
dbmByTableById[table][dbm.id] = dbm;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return dbmByTableById;
|
|
846
|
+
}
|
|
847
|
+
static async prepareMultiGetOutput(inputMap, dbmByTableById, opt = {}) {
|
|
848
|
+
const bmsByProp = {};
|
|
849
|
+
// Loop over input props again, to produce the output of the same shape as requested
|
|
850
|
+
await pMap(_stringMapEntries(inputMap), async ([prop, input]) => {
|
|
851
|
+
const { dao } = input;
|
|
1033
852
|
const { table } = dao.cfg;
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
853
|
+
if ('id' in input) {
|
|
854
|
+
// Singular
|
|
855
|
+
const dbm = dbmByTableById[table][input.id];
|
|
856
|
+
bmsByProp[prop] = (await dao.dbmToBM(dbm, opt)) || null;
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
// Plural
|
|
860
|
+
// We apply filtering, to be able to support multiple input props fetching from the same table.
|
|
861
|
+
// Without filtering - every prop will get ALL rows from that table.
|
|
862
|
+
const dbms = input.ids.map(id => dbmByTableById[table][id]).filter(_isTruthy);
|
|
863
|
+
bmsByProp[prop] = await dao.dbmsToBM(dbms, opt);
|
|
1037
864
|
}
|
|
1038
|
-
bmsByTable[table] = await dao.dbmsToBM(dbms, opt);
|
|
1039
865
|
});
|
|
1040
|
-
return
|
|
866
|
+
return bmsByProp;
|
|
1041
867
|
}
|
|
1042
868
|
/**
|
|
1043
869
|
* Very @experimental.
|
|
@@ -1050,19 +876,40 @@ export class CommonDao {
|
|
|
1050
876
|
for (const { dao, ids } of inputs) {
|
|
1051
877
|
idsByTable[dao.cfg.table] = ids;
|
|
1052
878
|
}
|
|
1053
|
-
return await db.
|
|
879
|
+
return await db.multiDelete(idsByTable);
|
|
1054
880
|
}
|
|
1055
|
-
static async
|
|
881
|
+
static async multiSave(inputs, opt = {}) {
|
|
1056
882
|
if (!inputs.length)
|
|
1057
883
|
return;
|
|
1058
884
|
const { db } = inputs[0].dao.cfg;
|
|
1059
885
|
const dbmsByTable = {};
|
|
1060
|
-
await pMap(inputs, async (
|
|
886
|
+
await pMap(inputs, async (input) => {
|
|
887
|
+
const { dao } = input;
|
|
1061
888
|
const { table } = dao.cfg;
|
|
1062
|
-
|
|
1063
|
-
|
|
889
|
+
dbmsByTable[table] ||= [];
|
|
890
|
+
if ('row' in input) {
|
|
891
|
+
// Singular
|
|
892
|
+
const { row } = input;
|
|
893
|
+
if (input.opt?.skipIfEquals) {
|
|
894
|
+
// We compare with convertedBM, to account for cases when some extra property is assigned to bm,
|
|
895
|
+
// which should be removed post-validation, but it breaks the "equality check"
|
|
896
|
+
// Post-validation the equality check should work as intended
|
|
897
|
+
const convertedBM = dao.validateAndConvert(row, 'save', opt);
|
|
898
|
+
if (_deepJsonEquals(convertedBM, input.opt.skipIfEquals)) {
|
|
899
|
+
// Skipping the save operation
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
dao.assignIdCreatedUpdated(row, opt);
|
|
904
|
+
dbmsByTable[table].push(await dao.bmToDBM(row, opt));
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
// Plural
|
|
908
|
+
input.rows.forEach(bm => dao.assignIdCreatedUpdated(bm, opt));
|
|
909
|
+
dbmsByTable[table].push(...(await dao.bmsToDBM(input.rows, opt)));
|
|
910
|
+
}
|
|
1064
911
|
});
|
|
1065
|
-
await db.
|
|
912
|
+
await db.multiSave(dbmsByTable);
|
|
1066
913
|
}
|
|
1067
914
|
async createTransaction(opt) {
|
|
1068
915
|
const tx = await this.cfg.db.createTransaction(opt);
|
|
@@ -1082,6 +929,30 @@ export class CommonDao {
|
|
|
1082
929
|
}, opt);
|
|
1083
930
|
return r;
|
|
1084
931
|
}
|
|
932
|
+
ensureRequired(row, id, opt) {
|
|
933
|
+
const table = opt.table || this.cfg.table;
|
|
934
|
+
_assert(row, `DB row required, but not found in ${table}`, {
|
|
935
|
+
table,
|
|
936
|
+
id,
|
|
937
|
+
});
|
|
938
|
+
return row; // pass-through
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Throws if readOnly is true
|
|
942
|
+
*/
|
|
943
|
+
requireWriteAccess() {
|
|
944
|
+
_assert(!this.cfg.readOnly, DBLibError.DAO_IS_READ_ONLY, {
|
|
945
|
+
table: this.cfg.table,
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Throws if readOnly is true
|
|
950
|
+
*/
|
|
951
|
+
requireObjectMutability(opt) {
|
|
952
|
+
_assert(!this.cfg.immutable || opt.allowMutability, DBLibError.OBJECT_IS_IMMUTABLE, {
|
|
953
|
+
table: this.cfg.table,
|
|
954
|
+
});
|
|
955
|
+
}
|
|
1085
956
|
/**
|
|
1086
957
|
* Throws if query uses a property that is in `excludeFromIndexes` list.
|
|
1087
958
|
*/
|
|
@@ -1095,134 +966,4 @@ export class CommonDao {
|
|
|
1095
966
|
});
|
|
1096
967
|
}
|
|
1097
968
|
}
|
|
1098
|
-
logResult(started, op, res, table) {
|
|
1099
|
-
if (!this.cfg.logLevel)
|
|
1100
|
-
return;
|
|
1101
|
-
let logRes;
|
|
1102
|
-
const args = [];
|
|
1103
|
-
if (Array.isArray(res)) {
|
|
1104
|
-
logRes = `${res.length} row(s)`;
|
|
1105
|
-
if (res.length && this.cfg.logLevel >= CommonDaoLogLevel.DATA_FULL) {
|
|
1106
|
-
args.push('\n', ...res.slice(0, 10)); // max 10 items
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
else if (res) {
|
|
1110
|
-
logRes = `1 row`;
|
|
1111
|
-
if (this.cfg.logLevel >= CommonDaoLogLevel.DATA_SINGLE) {
|
|
1112
|
-
args.push('\n', res);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
else {
|
|
1116
|
-
logRes = `undefined`;
|
|
1117
|
-
}
|
|
1118
|
-
this.cfg.logger?.log(`<< ${table}.${op}: ${logRes} in ${_since(started)}`, ...args);
|
|
1119
|
-
}
|
|
1120
|
-
logSaveResult(started, op, table) {
|
|
1121
|
-
if (!this.cfg.logLevel)
|
|
1122
|
-
return;
|
|
1123
|
-
this.cfg.logger?.log(`<< ${table}.${op} in ${_since(started)}`);
|
|
1124
|
-
}
|
|
1125
|
-
logStarted(op, table, force = false) {
|
|
1126
|
-
if (this.cfg.logStarted || force) {
|
|
1127
|
-
this.cfg.logger?.log(`>> ${table}.${op}`);
|
|
1128
|
-
}
|
|
1129
|
-
return localTime.nowUnixMillis();
|
|
1130
|
-
}
|
|
1131
|
-
logSaveStarted(op, items, table) {
|
|
1132
|
-
if (this.cfg.logStarted) {
|
|
1133
|
-
const args = [`>> ${table}.${op}`];
|
|
1134
|
-
if (Array.isArray(items)) {
|
|
1135
|
-
if (items.length && this.cfg.logLevel >= CommonDaoLogLevel.DATA_FULL) {
|
|
1136
|
-
args.push('\n', ...items.slice(0, 10));
|
|
1137
|
-
}
|
|
1138
|
-
else {
|
|
1139
|
-
args.push(`${items.length} row(s)`);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
else {
|
|
1143
|
-
if (this.cfg.logLevel >= CommonDaoLogLevel.DATA_SINGLE) {
|
|
1144
|
-
args.push(items);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
this.cfg.logger?.log(...args);
|
|
1148
|
-
}
|
|
1149
|
-
return localTime.nowUnixMillis();
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
/**
|
|
1153
|
-
* Transaction context.
|
|
1154
|
-
* Has similar API than CommonDao, but all operations are performed in the context of the transaction.
|
|
1155
|
-
*/
|
|
1156
|
-
export class CommonDaoTransaction {
|
|
1157
|
-
tx;
|
|
1158
|
-
logger;
|
|
1159
|
-
constructor(tx, logger) {
|
|
1160
|
-
this.tx = tx;
|
|
1161
|
-
this.logger = logger;
|
|
1162
|
-
}
|
|
1163
|
-
/**
|
|
1164
|
-
* Commits the underlying DBTransaction.
|
|
1165
|
-
* May throw.
|
|
1166
|
-
*/
|
|
1167
|
-
async commit() {
|
|
1168
|
-
await this.tx.commit();
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Perform a graceful rollback without throwing/re-throwing any error.
|
|
1172
|
-
* Never throws.
|
|
1173
|
-
*/
|
|
1174
|
-
async rollback() {
|
|
1175
|
-
try {
|
|
1176
|
-
await this.tx.rollback();
|
|
1177
|
-
}
|
|
1178
|
-
catch (err) {
|
|
1179
|
-
// graceful rollback without re-throw
|
|
1180
|
-
this.logger.error(err);
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
async getById(dao, id, opt) {
|
|
1184
|
-
return await dao.getById(id, { ...opt, tx: this.tx });
|
|
1185
|
-
}
|
|
1186
|
-
async getByIds(dao, ids, opt) {
|
|
1187
|
-
return await dao.getByIds(ids, { ...opt, tx: this.tx });
|
|
1188
|
-
}
|
|
1189
|
-
// todo: Queries inside Transaction are not supported yet
|
|
1190
|
-
// async runQuery<BM extends PartialObjectWithId, DBM extends ObjectWithId>(
|
|
1191
|
-
// dao: CommonDao<BM, DBM, any>,
|
|
1192
|
-
// q: DBQuery<DBM>,
|
|
1193
|
-
// opt?: CommonDaoOptions,
|
|
1194
|
-
// ): Promise<BM[]> {
|
|
1195
|
-
// try {
|
|
1196
|
-
// return await dao.runQuery(q, { ...opt, tx: this.tx })
|
|
1197
|
-
// } catch (err) {
|
|
1198
|
-
// await this.rollback()
|
|
1199
|
-
// throw err
|
|
1200
|
-
// }
|
|
1201
|
-
// }
|
|
1202
|
-
async save(dao, bm, opt) {
|
|
1203
|
-
return await dao.save(bm, { ...opt, tx: this.tx });
|
|
1204
|
-
}
|
|
1205
|
-
async saveBatch(dao, bms, opt) {
|
|
1206
|
-
return await dao.saveBatch(bms, { ...opt, tx: this.tx });
|
|
1207
|
-
}
|
|
1208
|
-
/**
|
|
1209
|
-
* DaoTransaction.patch does not load from DB.
|
|
1210
|
-
* It assumes the bm was previously loaded in the same Transaction, hence could not be
|
|
1211
|
-
* concurrently modified. Hence it's safe to not sync with DB.
|
|
1212
|
-
*
|
|
1213
|
-
* So, this method is a rather simple convenience "Object.assign and then save".
|
|
1214
|
-
*/
|
|
1215
|
-
async patch(dao, bm, patch, opt) {
|
|
1216
|
-
const skipIfEquals = _deepCopy(bm);
|
|
1217
|
-
Object.assign(bm, patch);
|
|
1218
|
-
return await dao.save(bm, { ...opt, skipIfEquals, tx: this.tx });
|
|
1219
|
-
}
|
|
1220
|
-
async deleteById(dao, id, opt) {
|
|
1221
|
-
if (!id)
|
|
1222
|
-
return 0;
|
|
1223
|
-
return await this.deleteByIds(dao, [id], opt);
|
|
1224
|
-
}
|
|
1225
|
-
async deleteByIds(dao, ids, opt) {
|
|
1226
|
-
return await dao.deleteByIds(ids, { ...opt, tx: this.tx });
|
|
1227
|
-
}
|
|
1228
969
|
}
|