@naturalcycles/db-lib 10.19.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 +36 -63
- package/dist/commondao/common.dao.js +138 -432
- package/dist/commondao/common.dao.model.d.ts +0 -13
- package/dist/commondao/commonDaoTransaction.d.ts +38 -0
- package/dist/commondao/commonDaoTransaction.js +78 -0
- package/dist/testing/commonDaoTest.js +0 -3
- package/package.json +1 -1
- package/src/commondao/common.dao.model.ts +4 -4
- package/src/commondao/common.dao.ts +188 -519
- package/src/commondao/commonDaoTransaction.ts +124 -0
- package/src/testing/commonDaoTest.ts +0 -3
|
@@ -2,18 +2,14 @@ import type { Transform } from 'node:stream'
|
|
|
2
2
|
import { _isTruthy } from '@naturalcycles/js-lib'
|
|
3
3
|
import { _uniqBy } from '@naturalcycles/js-lib/array/array.util.js'
|
|
4
4
|
import { localTime } from '@naturalcycles/js-lib/datetime/localTime.js'
|
|
5
|
-
import {
|
|
6
|
-
import { _assert, AppError, ErrorMode } from '@naturalcycles/js-lib/error'
|
|
5
|
+
import { _assert, ErrorMode } from '@naturalcycles/js-lib/error'
|
|
7
6
|
import type { JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib/json-schema'
|
|
8
|
-
import type { CommonLogger } from '@naturalcycles/js-lib/log'
|
|
9
7
|
import { _deepJsonEquals } from '@naturalcycles/js-lib/object/deepEquals.js'
|
|
10
8
|
import {
|
|
11
|
-
_deepCopy,
|
|
12
9
|
_filterUndefinedValues,
|
|
13
10
|
_objectAssignExact,
|
|
14
11
|
} from '@naturalcycles/js-lib/object/object.util.js'
|
|
15
12
|
import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'
|
|
16
|
-
import { _truncate } from '@naturalcycles/js-lib/string/string.util.js'
|
|
17
13
|
import {
|
|
18
14
|
_stringMapEntries,
|
|
19
15
|
_stringMapValues,
|
|
@@ -22,12 +18,15 @@ import {
|
|
|
22
18
|
type NonNegativeInteger,
|
|
23
19
|
type ObjectWithId,
|
|
24
20
|
type StringMap,
|
|
25
|
-
type UnixTimestampMillis,
|
|
26
21
|
type Unsaved,
|
|
27
22
|
} from '@naturalcycles/js-lib/types'
|
|
28
23
|
import { _passthroughPredicate, _typeCast, SKIP } from '@naturalcycles/js-lib/types'
|
|
29
24
|
import { stringId } from '@naturalcycles/nodejs-lib'
|
|
30
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
type ReadableTyped,
|
|
27
|
+
transformFlatten,
|
|
28
|
+
transformMapSync,
|
|
29
|
+
} from '@naturalcycles/nodejs-lib/stream'
|
|
31
30
|
import {
|
|
32
31
|
_pipeline,
|
|
33
32
|
transformChunk,
|
|
@@ -37,7 +36,11 @@ import {
|
|
|
37
36
|
writableVoid,
|
|
38
37
|
} from '@naturalcycles/nodejs-lib/stream'
|
|
39
38
|
import { DBLibError } from '../cnst.js'
|
|
40
|
-
import type {
|
|
39
|
+
import type {
|
|
40
|
+
CommonDBSaveOptions,
|
|
41
|
+
CommonDBTransactionOptions,
|
|
42
|
+
RunQueryResult,
|
|
43
|
+
} from '../db.model.js'
|
|
41
44
|
import type { DBQuery } from '../query/dbQuery.js'
|
|
42
45
|
import { RunnableDBQuery } from '../query/dbQuery.js'
|
|
43
46
|
import type {
|
|
@@ -55,7 +58,7 @@ import type {
|
|
|
55
58
|
CommonDaoStreamOptions,
|
|
56
59
|
CommonDaoStreamSaveOptions,
|
|
57
60
|
} from './common.dao.model.js'
|
|
58
|
-
import {
|
|
61
|
+
import { CommonDaoTransaction } from './commonDaoTransaction.js'
|
|
59
62
|
|
|
60
63
|
/**
|
|
61
64
|
* Lowest common denominator API between supported Databases.
|
|
@@ -71,7 +74,6 @@ export class CommonDao<
|
|
|
71
74
|
> {
|
|
72
75
|
constructor(public cfg: CommonDaoCfg<BM, DBM, ID>) {
|
|
73
76
|
this.cfg = {
|
|
74
|
-
logLevel: CommonDaoLogLevel.NONE,
|
|
75
77
|
generateId: true,
|
|
76
78
|
assignGeneratedIds: false,
|
|
77
79
|
useCreatedProperty: true,
|
|
@@ -105,23 +107,14 @@ export class CommonDao<
|
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
// GET
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (!id) return null
|
|
113
|
-
const op = `getById(${id})`
|
|
114
|
-
const table = opt.table || this.cfg.table
|
|
115
|
-
const started = this.logStarted(op, table)
|
|
116
|
-
|
|
117
|
-
let dbm = (await (opt.tx || this.cfg.db).getByIds<DBM>(table, [id as string], opt))[0]
|
|
118
|
-
if (dbm && this.cfg.hooks!.afterLoad) {
|
|
119
|
-
dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
|
|
120
|
-
}
|
|
110
|
+
async requireById(id: ID, opt: CommonDaoReadOptions = {}): Promise<BM> {
|
|
111
|
+
const bm = await this.getById(id, opt)
|
|
112
|
+
return this.ensureRequired(bm, id, opt)
|
|
113
|
+
}
|
|
121
114
|
|
|
122
|
-
|
|
123
|
-
this.
|
|
124
|
-
return
|
|
115
|
+
async requireByIdAsDBM(id: ID, opt: CommonDaoReadOptions = {}): Promise<DBM> {
|
|
116
|
+
const dbm = await this.getByIdAsDBM(id, opt)
|
|
117
|
+
return this.ensureRequired(dbm, id, opt)
|
|
125
118
|
}
|
|
126
119
|
|
|
127
120
|
async getByIdOrEmpty(id: ID, part: Partial<BM> = {}, opt?: CommonDaoReadOptions): Promise<BM> {
|
|
@@ -131,111 +124,33 @@ export class CommonDao<
|
|
|
131
124
|
return this.create({ ...part, id }, opt)
|
|
132
125
|
}
|
|
133
126
|
|
|
134
|
-
|
|
135
|
-
// async getByIdAsDBM(id?: ID | null, opt?: CommonDaoOptions): Promise<DBM | null>
|
|
136
|
-
async getByIdAsDBM(id?: ID | null, opt: CommonDaoReadOptions = {}): Promise<DBM | null> {
|
|
127
|
+
async getById(id?: ID | null, opt: CommonDaoReadOptions = {}): Promise<BM | null> {
|
|
137
128
|
if (!id) return null
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
let [dbm] = await (opt.tx || this.cfg.db).getByIds<DBM>(table, [id as string], opt)
|
|
142
|
-
if (dbm && this.cfg.hooks!.afterLoad) {
|
|
143
|
-
dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
|
|
144
|
-
}
|
|
129
|
+
const [dbm] = await this.loadByIds([id], opt)
|
|
130
|
+
return await this.dbmToBM(dbm, opt)
|
|
131
|
+
}
|
|
145
132
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
133
|
+
async getByIdAsDBM(id?: ID | null, opt: CommonDaoReadOptions = {}): Promise<DBM | null> {
|
|
134
|
+
if (!id) return null
|
|
135
|
+
const [row] = await this.loadByIds([id], opt)
|
|
136
|
+
return this.anyToDBM(row, opt) || null
|
|
149
137
|
}
|
|
150
138
|
|
|
151
139
|
async getByIds(ids: ID[], opt: CommonDaoReadOptions = {}): Promise<BM[]> {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const table = opt.table || this.cfg.table
|
|
155
|
-
const started = this.logStarted(op, table)
|
|
156
|
-
let dbms = await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids as string[], opt)
|
|
157
|
-
if (this.cfg.hooks!.afterLoad && dbms.length) {
|
|
158
|
-
dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
159
|
-
_isTruthy,
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const bms = await this.dbmsToBM(dbms, opt)
|
|
164
|
-
this.logResult(started, op, bms, table)
|
|
165
|
-
return bms
|
|
140
|
+
const dbms = await this.loadByIds(ids, opt)
|
|
141
|
+
return await this.dbmsToBM(dbms, opt)
|
|
166
142
|
}
|
|
167
143
|
|
|
168
144
|
async getByIdsAsDBM(ids: ID[], opt: CommonDaoReadOptions = {}): Promise<DBM[]> {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const table = opt.table || this.cfg.table
|
|
172
|
-
const started = this.logStarted(op, table)
|
|
173
|
-
let dbms = await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids as string[], opt)
|
|
174
|
-
if (this.cfg.hooks!.afterLoad && dbms.length) {
|
|
175
|
-
dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
176
|
-
_isTruthy,
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
this.logResult(started, op, dbms, table)
|
|
181
|
-
return dbms
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async requireById(id: ID, opt: CommonDaoReadOptions = {}): Promise<BM> {
|
|
185
|
-
const r = await this.getById(id, opt)
|
|
186
|
-
if (!r) {
|
|
187
|
-
this.throwRequiredError(id, opt)
|
|
188
|
-
}
|
|
189
|
-
return r
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async requireByIdAsDBM(id: ID, opt: CommonDaoReadOptions = {}): Promise<DBM> {
|
|
193
|
-
const r = await this.getByIdAsDBM(id, opt)
|
|
194
|
-
if (!r) {
|
|
195
|
-
this.throwRequiredError(id, opt)
|
|
196
|
-
}
|
|
197
|
-
return r
|
|
145
|
+
const rows = await this.loadByIds(ids, opt)
|
|
146
|
+
return this.anyToDBMs(rows)
|
|
198
147
|
}
|
|
199
148
|
|
|
200
|
-
|
|
149
|
+
// DRY private method
|
|
150
|
+
private async loadByIds(ids: ID[], opt: CommonDaoReadOptions = {}): Promise<DBM[]> {
|
|
151
|
+
if (!ids.length) return []
|
|
201
152
|
const table = opt.table || this.cfg.table
|
|
202
|
-
|
|
203
|
-
table,
|
|
204
|
-
id,
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Throws if readOnly is true
|
|
210
|
-
*/
|
|
211
|
-
private requireWriteAccess(): void {
|
|
212
|
-
if (this.cfg.readOnly) {
|
|
213
|
-
throw new AppError(DBLibError.DAO_IS_READ_ONLY, {
|
|
214
|
-
table: this.cfg.table,
|
|
215
|
-
})
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Throws if readOnly is true
|
|
221
|
-
*/
|
|
222
|
-
private requireObjectMutability(opt: CommonDaoOptions): void {
|
|
223
|
-
if (this.cfg.immutable && !opt.allowMutability) {
|
|
224
|
-
throw new AppError(DBLibError.OBJECT_IS_IMMUTABLE, {
|
|
225
|
-
table: this.cfg.table,
|
|
226
|
-
})
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
private async ensureUniqueId(table: string, dbm: DBM): Promise<void> {
|
|
231
|
-
// todo: retry N times
|
|
232
|
-
const existing = await this.cfg.db.getByIds<DBM>(table, [dbm.id])
|
|
233
|
-
if (existing.length) {
|
|
234
|
-
throw new AppError(DBLibError.NON_UNIQUE_ID, {
|
|
235
|
-
table,
|
|
236
|
-
ids: existing.map(i => i.id),
|
|
237
|
-
})
|
|
238
|
-
}
|
|
153
|
+
return await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids, opt)
|
|
239
154
|
}
|
|
240
155
|
|
|
241
156
|
async getBy(by: keyof DBM, value: any, limit = 0, opt?: CommonDaoReadOptions): Promise<BM[]> {
|
|
@@ -294,18 +209,9 @@ export class CommonDao<
|
|
|
294
209
|
): Promise<RunQueryResult<BM>> {
|
|
295
210
|
this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
|
|
296
211
|
q.table = opt.table || q.table
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
const partialQuery = !!q._selectedFieldNames
|
|
301
|
-
if (this.cfg.hooks!.afterLoad && rows.length) {
|
|
302
|
-
rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
303
|
-
_isTruthy,
|
|
304
|
-
)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const bms = partialQuery ? (rows as any[]) : await this.dbmsToBM(rows, opt)
|
|
308
|
-
this.logResult(started, op, bms, q.table)
|
|
212
|
+
const { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
|
|
213
|
+
const isPartialQuery = !!q._selectedFieldNames
|
|
214
|
+
const bms = isPartialQuery ? (rows as any[]) : await this.dbmsToBM(rows, opt)
|
|
309
215
|
return {
|
|
310
216
|
rows: bms,
|
|
311
217
|
...queryResult,
|
|
@@ -323,31 +229,16 @@ export class CommonDao<
|
|
|
323
229
|
): Promise<RunQueryResult<DBM>> {
|
|
324
230
|
this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
|
|
325
231
|
q.table = opt.table || q.table
|
|
326
|
-
const
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
if (this.cfg.hooks!.afterLoad && rows.length) {
|
|
330
|
-
rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
|
|
331
|
-
_isTruthy,
|
|
332
|
-
)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const partialQuery = !!q._selectedFieldNames
|
|
336
|
-
const dbms = partialQuery ? rows : this.anyToDBMs(rows, opt)
|
|
337
|
-
this.logResult(started, op, dbms, q.table)
|
|
232
|
+
const { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
|
|
233
|
+
const isPartialQuery = !!q._selectedFieldNames
|
|
234
|
+
const dbms = isPartialQuery ? rows : this.anyToDBMs(rows, opt)
|
|
338
235
|
return { rows: dbms, ...queryResult }
|
|
339
236
|
}
|
|
340
237
|
|
|
341
238
|
async runQueryCount(q: DBQuery<DBM>, opt: CommonDaoReadOptions = {}): Promise<number> {
|
|
342
239
|
this.validateQueryIndexes(q) // throws if query uses `excludeFromIndexes` property
|
|
343
240
|
q.table = opt.table || q.table
|
|
344
|
-
|
|
345
|
-
const started = this.logStarted(op, q.table)
|
|
346
|
-
const count = await this.cfg.db.runQueryCount(q, opt)
|
|
347
|
-
if (this.cfg.logLevel! >= CommonDaoLogLevel.OPERATIONS) {
|
|
348
|
-
this.cfg.logger?.log(`<< ${q.table}.${op}: ${count} row(s) in ${_since(started)}`)
|
|
349
|
-
}
|
|
350
|
-
return count
|
|
241
|
+
return await this.cfg.db.runQueryCount(q, opt)
|
|
351
242
|
}
|
|
352
243
|
|
|
353
244
|
async streamQueryForEach(
|
|
@@ -360,23 +251,13 @@ export class CommonDao<
|
|
|
360
251
|
opt.skipValidation = opt.skipValidation !== false // default true
|
|
361
252
|
opt.errorMode ||= ErrorMode.SUPPRESS
|
|
362
253
|
|
|
363
|
-
const
|
|
364
|
-
const op = `streamQueryForEach(${q.pretty()})`
|
|
365
|
-
const started = this.logStarted(op, q.table, true)
|
|
366
|
-
let count = 0
|
|
254
|
+
const isPartialQuery = !!q._selectedFieldNames
|
|
367
255
|
|
|
368
256
|
await _pipeline([
|
|
369
257
|
this.cfg.db.streamQuery<DBM>(q, opt),
|
|
370
258
|
transformMap<DBM, BM>(
|
|
371
259
|
async dbm => {
|
|
372
|
-
|
|
373
|
-
if (partialQuery) return dbm as any
|
|
374
|
-
|
|
375
|
-
if (this.cfg.hooks!.afterLoad) {
|
|
376
|
-
dbm = (await this.cfg.hooks!.afterLoad(dbm))!
|
|
377
|
-
if (dbm === null) return SKIP
|
|
378
|
-
}
|
|
379
|
-
|
|
260
|
+
if (isPartialQuery) return dbm as any
|
|
380
261
|
return await this.dbmToBM(dbm, opt)
|
|
381
262
|
},
|
|
382
263
|
{
|
|
@@ -394,10 +275,6 @@ export class CommonDao<
|
|
|
394
275
|
}),
|
|
395
276
|
writableVoid(),
|
|
396
277
|
])
|
|
397
|
-
|
|
398
|
-
if (this.cfg.logLevel! >= CommonDaoLogLevel.OPERATIONS) {
|
|
399
|
-
this.cfg.logger?.log(`<< ${q.table}.${op}: ${count} row(s) in ${_since(started)}`)
|
|
400
|
-
}
|
|
401
278
|
}
|
|
402
279
|
|
|
403
280
|
async streamQueryAsDBMForEach(
|
|
@@ -410,23 +287,13 @@ export class CommonDao<
|
|
|
410
287
|
opt.skipValidation = opt.skipValidation !== false // default true
|
|
411
288
|
opt.errorMode ||= ErrorMode.SUPPRESS
|
|
412
289
|
|
|
413
|
-
const
|
|
414
|
-
const op = `streamQueryAsDBMForEach(${q.pretty()})`
|
|
415
|
-
const started = this.logStarted(op, q.table, true)
|
|
416
|
-
let count = 0
|
|
290
|
+
const isPartialQuery = !!q._selectedFieldNames
|
|
417
291
|
|
|
418
292
|
await _pipeline([
|
|
419
293
|
this.cfg.db.streamQuery<any>(q, opt),
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (partialQuery) return dbm
|
|
424
|
-
|
|
425
|
-
if (this.cfg.hooks!.afterLoad) {
|
|
426
|
-
dbm = (await this.cfg.hooks!.afterLoad(dbm))!
|
|
427
|
-
if (dbm === null) return SKIP
|
|
428
|
-
}
|
|
429
|
-
|
|
294
|
+
transformMapSync<any, DBM>(
|
|
295
|
+
dbm => {
|
|
296
|
+
if (isPartialQuery) return dbm
|
|
430
297
|
return this.anyToDBM(dbm, opt)
|
|
431
298
|
},
|
|
432
299
|
{
|
|
@@ -444,10 +311,6 @@ export class CommonDao<
|
|
|
444
311
|
}),
|
|
445
312
|
writableVoid(),
|
|
446
313
|
])
|
|
447
|
-
|
|
448
|
-
if (this.cfg.logLevel! >= CommonDaoLogLevel.OPERATIONS) {
|
|
449
|
-
this.cfg.logger?.log(`<< ${q.table}.${op}: ${count} row(s) in ${_since(started)}`)
|
|
450
|
-
}
|
|
451
314
|
}
|
|
452
315
|
|
|
453
316
|
/**
|
|
@@ -459,23 +322,18 @@ export class CommonDao<
|
|
|
459
322
|
opt.skipValidation = opt.skipValidation !== false // default true
|
|
460
323
|
opt.errorMode ||= ErrorMode.SUPPRESS
|
|
461
324
|
|
|
462
|
-
const
|
|
325
|
+
const isPartialQuery = !!q._selectedFieldNames
|
|
463
326
|
|
|
464
327
|
const stream = this.cfg.db.streamQuery<DBM>(q, opt)
|
|
465
|
-
if (
|
|
328
|
+
if (isPartialQuery) return stream
|
|
466
329
|
|
|
467
330
|
return (
|
|
468
331
|
stream
|
|
469
332
|
// the commented out line was causing RangeError: Maximum call stack size exceeded
|
|
470
333
|
// .on('error', err => stream.emit('error', err))
|
|
471
334
|
.pipe(
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (this.cfg.hooks!.afterLoad) {
|
|
475
|
-
dbm = (await this.cfg.hooks!.afterLoad(dbm))!
|
|
476
|
-
if (dbm === null) return SKIP
|
|
477
|
-
}
|
|
478
|
-
|
|
335
|
+
transformMapSync<any, DBM>(
|
|
336
|
+
dbm => {
|
|
479
337
|
return this.anyToDBM(dbm, opt)
|
|
480
338
|
},
|
|
481
339
|
{
|
|
@@ -502,16 +360,11 @@ export class CommonDao<
|
|
|
502
360
|
opt.errorMode ||= ErrorMode.SUPPRESS
|
|
503
361
|
|
|
504
362
|
const stream = this.cfg.db.streamQuery<DBM>(q, opt)
|
|
505
|
-
const
|
|
506
|
-
if (
|
|
363
|
+
const isPartialQuery = !!q._selectedFieldNames
|
|
364
|
+
if (isPartialQuery) return stream as any
|
|
507
365
|
|
|
508
366
|
// This almost works, but hard to implement `errorMode: THROW_AGGREGATED` in this case
|
|
509
367
|
// return stream.flatMap(async (dbm: DBM) => {
|
|
510
|
-
// if (this.cfg.hooks!.afterLoad) {
|
|
511
|
-
// dbm = (await this.cfg.hooks!.afterLoad(dbm))!
|
|
512
|
-
// if (dbm === null) return [] // SKIP
|
|
513
|
-
// }
|
|
514
|
-
//
|
|
515
368
|
// return [await this.dbmToBM(dbm, opt)] satisfies BM[]
|
|
516
369
|
// }, {
|
|
517
370
|
// concurrency: 16,
|
|
@@ -527,11 +380,6 @@ export class CommonDao<
|
|
|
527
380
|
.pipe(
|
|
528
381
|
transformMap<DBM, BM>(
|
|
529
382
|
async dbm => {
|
|
530
|
-
if (this.cfg.hooks!.afterLoad) {
|
|
531
|
-
dbm = (await this.cfg.hooks!.afterLoad(dbm))!
|
|
532
|
-
if (dbm === null) return SKIP
|
|
533
|
-
}
|
|
534
|
-
|
|
535
383
|
return await this.dbmToBM(dbm, opt)
|
|
536
384
|
},
|
|
537
385
|
{
|
|
@@ -586,15 +434,8 @@ export class CommonDao<
|
|
|
586
434
|
q.table = opt.table || q.table
|
|
587
435
|
opt.errorMode ||= ErrorMode.SUPPRESS
|
|
588
436
|
|
|
589
|
-
const op = `streamQueryIdsForEach(${q.pretty()})`
|
|
590
|
-
const started = this.logStarted(op, q.table, true)
|
|
591
|
-
let count = 0
|
|
592
|
-
|
|
593
437
|
await _pipeline([
|
|
594
|
-
this.cfg.db.streamQuery<DBM>(q.select(['id']), opt).map(r =>
|
|
595
|
-
count++
|
|
596
|
-
return r.id
|
|
597
|
-
}),
|
|
438
|
+
this.cfg.db.streamQuery<DBM>(q.select(['id']), opt).map(r => r.id),
|
|
598
439
|
transformMap<ID, void>(mapper, {
|
|
599
440
|
...opt,
|
|
600
441
|
predicate: _passthroughPredicate,
|
|
@@ -606,17 +447,15 @@ export class CommonDao<
|
|
|
606
447
|
}),
|
|
607
448
|
writableVoid(),
|
|
608
449
|
])
|
|
609
|
-
|
|
610
|
-
if (this.cfg.logLevel! >= CommonDaoLogLevel.OPERATIONS) {
|
|
611
|
-
this.cfg.logger?.log(`<< ${q.table}.${op}: ${count} id(s) in ${_since(started)}`)
|
|
612
|
-
}
|
|
613
450
|
}
|
|
614
451
|
|
|
615
452
|
/**
|
|
616
453
|
* Mutates!
|
|
617
|
-
* "Returns", just to have a type of "Saved"
|
|
618
454
|
*/
|
|
619
|
-
assignIdCreatedUpdated<T extends BaseDBEntity>(
|
|
455
|
+
assignIdCreatedUpdated<T extends BaseDBEntity>(
|
|
456
|
+
obj: Partial<T>,
|
|
457
|
+
opt: CommonDaoOptions = {},
|
|
458
|
+
): void {
|
|
620
459
|
const now = localTime.nowUnix()
|
|
621
460
|
|
|
622
461
|
if (this.cfg.useCreatedProperty) {
|
|
@@ -631,8 +470,6 @@ export class CommonDao<
|
|
|
631
470
|
obj.id ||= (this.cfg.hooks!.createNaturalId?.(obj as any) ||
|
|
632
471
|
this.cfg.hooks!.createRandomId!()) as T['id']
|
|
633
472
|
}
|
|
634
|
-
|
|
635
|
-
return obj as T
|
|
636
473
|
}
|
|
637
474
|
|
|
638
475
|
// SAVE
|
|
@@ -777,7 +614,6 @@ export class CommonDao<
|
|
|
777
614
|
}
|
|
778
615
|
}
|
|
779
616
|
|
|
780
|
-
const idWasGenerated = !bm.id && this.cfg.generateId
|
|
781
617
|
this.assignIdCreatedUpdated(bm, opt) // mutates
|
|
782
618
|
_typeCast<BM>(bm)
|
|
783
619
|
let dbm = await this.bmToDBM(bm, opt) // validates BM
|
|
@@ -788,71 +624,43 @@ export class CommonDao<
|
|
|
788
624
|
}
|
|
789
625
|
|
|
790
626
|
const table = opt.table || this.cfg.table
|
|
791
|
-
|
|
792
|
-
if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
|
|
793
|
-
opt = { ...opt, saveMethod: 'insert' }
|
|
794
|
-
}
|
|
795
|
-
const op = `save(${dbm.id})`
|
|
796
|
-
const started = this.logSaveStarted(op, bm, table)
|
|
797
|
-
const { excludeFromIndexes } = this.cfg
|
|
798
|
-
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
|
|
627
|
+
const saveOptions = this.prepareSaveOptions(opt)
|
|
799
628
|
|
|
800
|
-
await (opt.tx || this.cfg.db).saveBatch(table, [dbm],
|
|
801
|
-
excludeFromIndexes,
|
|
802
|
-
assignGeneratedIds,
|
|
803
|
-
...opt,
|
|
804
|
-
})
|
|
629
|
+
await (opt.tx || this.cfg.db).saveBatch(table, [dbm], saveOptions)
|
|
805
630
|
|
|
806
|
-
if (assignGeneratedIds) {
|
|
631
|
+
if (saveOptions.assignGeneratedIds) {
|
|
807
632
|
bm.id = dbm.id
|
|
808
633
|
}
|
|
809
634
|
|
|
810
|
-
this.logSaveResult(started, op, table)
|
|
811
635
|
return bm
|
|
812
636
|
}
|
|
813
637
|
|
|
814
638
|
async saveAsDBM(dbm: Unsaved<DBM>, opt: CommonDaoSaveOptions<BM, DBM> = {}): Promise<DBM> {
|
|
815
639
|
this.requireWriteAccess()
|
|
816
|
-
const table = opt.table || this.cfg.table
|
|
817
640
|
|
|
818
|
-
// assigning id in case it misses the id
|
|
819
|
-
// will override/set `updated` field, unless opts.preserveUpdated is set
|
|
820
|
-
const idWasGenerated = !dbm.id && this.cfg.generateId
|
|
821
641
|
this.assignIdCreatedUpdated(dbm, opt) // mutates
|
|
822
642
|
let row = this.anyToDBM(dbm, opt)
|
|
823
|
-
if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, row)
|
|
824
|
-
|
|
825
|
-
if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
|
|
826
|
-
opt = { ...opt, saveMethod: 'insert' }
|
|
827
|
-
}
|
|
828
|
-
const op = `saveAsDBM(${row.id})`
|
|
829
|
-
const started = this.logSaveStarted(op, row, table)
|
|
830
|
-
const { excludeFromIndexes } = this.cfg
|
|
831
|
-
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
|
|
832
643
|
|
|
833
644
|
if (this.cfg.hooks!.beforeSave) {
|
|
834
645
|
row = (await this.cfg.hooks!.beforeSave(row))!
|
|
835
646
|
if (row === null) return dbm as DBM
|
|
836
647
|
}
|
|
837
648
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
})
|
|
649
|
+
const table = opt.table || this.cfg.table
|
|
650
|
+
const saveOptions = this.prepareSaveOptions(opt)
|
|
651
|
+
|
|
652
|
+
await (opt.tx || this.cfg.db).saveBatch(table, [row], saveOptions)
|
|
843
653
|
|
|
844
|
-
if (assignGeneratedIds) {
|
|
654
|
+
if (saveOptions.assignGeneratedIds) {
|
|
845
655
|
dbm.id = row.id
|
|
846
656
|
}
|
|
847
657
|
|
|
848
|
-
this.logSaveResult(started, op, table)
|
|
849
658
|
return row
|
|
850
659
|
}
|
|
851
660
|
|
|
852
661
|
async saveBatch(bms: Unsaved<BM>[], opt: CommonDaoSaveBatchOptions<DBM> = {}): Promise<BM[]> {
|
|
853
662
|
if (!bms.length) return []
|
|
854
663
|
this.requireWriteAccess()
|
|
855
|
-
const table = opt.table || this.cfg.table
|
|
856
664
|
bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt))
|
|
857
665
|
let dbms = await this.bmsToDBM(bms as BM[], opt)
|
|
858
666
|
|
|
@@ -862,34 +670,15 @@ export class CommonDao<
|
|
|
862
670
|
)
|
|
863
671
|
}
|
|
864
672
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
opt = { ...opt, saveMethod: 'insert' }
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
const op = `saveBatch ${dbms.length} row(s) (${_truncate(
|
|
871
|
-
dbms
|
|
872
|
-
.slice(0, 10)
|
|
873
|
-
.map(bm => bm.id)
|
|
874
|
-
.join(', '),
|
|
875
|
-
50,
|
|
876
|
-
)})`
|
|
877
|
-
const started = this.logSaveStarted(op, bms, table)
|
|
878
|
-
const { excludeFromIndexes } = this.cfg
|
|
879
|
-
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
|
|
673
|
+
const table = opt.table || this.cfg.table
|
|
674
|
+
const saveOptions = this.prepareSaveOptions(opt)
|
|
880
675
|
|
|
881
|
-
await (opt.tx || this.cfg.db).saveBatch(table, dbms,
|
|
882
|
-
excludeFromIndexes,
|
|
883
|
-
assignGeneratedIds,
|
|
884
|
-
...opt,
|
|
885
|
-
})
|
|
676
|
+
await (opt.tx || this.cfg.db).saveBatch(table, dbms, saveOptions)
|
|
886
677
|
|
|
887
|
-
if (assignGeneratedIds) {
|
|
678
|
+
if (saveOptions.assignGeneratedIds) {
|
|
888
679
|
dbms.forEach((dbm, i) => (bms[i]!.id = dbm.id))
|
|
889
680
|
}
|
|
890
681
|
|
|
891
|
-
this.logSaveResult(started, op, table)
|
|
892
|
-
|
|
893
682
|
return bms as BM[]
|
|
894
683
|
}
|
|
895
684
|
|
|
@@ -899,24 +688,8 @@ export class CommonDao<
|
|
|
899
688
|
): Promise<DBM[]> {
|
|
900
689
|
if (!dbms.length) return []
|
|
901
690
|
this.requireWriteAccess()
|
|
902
|
-
|
|
903
|
-
dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt)) // mutates
|
|
691
|
+
dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt))
|
|
904
692
|
let rows = this.anyToDBMs(dbms as DBM[], opt)
|
|
905
|
-
if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
|
|
906
|
-
|
|
907
|
-
if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
|
|
908
|
-
opt = { ...opt, saveMethod: 'insert' }
|
|
909
|
-
}
|
|
910
|
-
const op = `saveBatchAsDBM ${rows.length} row(s) (${_truncate(
|
|
911
|
-
rows
|
|
912
|
-
.slice(0, 10)
|
|
913
|
-
.map(bm => bm.id)
|
|
914
|
-
.join(', '),
|
|
915
|
-
50,
|
|
916
|
-
)})`
|
|
917
|
-
const started = this.logSaveStarted(op, rows, table)
|
|
918
|
-
const { excludeFromIndexes } = this.cfg
|
|
919
|
-
const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
|
|
920
693
|
|
|
921
694
|
if (this.cfg.hooks!.beforeSave && rows.length) {
|
|
922
695
|
rows = (await pMap(rows, async row => await this.cfg.hooks!.beforeSave!(row))).filter(
|
|
@@ -924,20 +697,36 @@ export class CommonDao<
|
|
|
924
697
|
)
|
|
925
698
|
}
|
|
926
699
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
assignGeneratedIds,
|
|
930
|
-
...opt,
|
|
931
|
-
})
|
|
700
|
+
const table = opt.table || this.cfg.table
|
|
701
|
+
const saveOptions = this.prepareSaveOptions(opt)
|
|
932
702
|
|
|
933
|
-
|
|
703
|
+
await (opt.tx || this.cfg.db).saveBatch(table, rows, saveOptions)
|
|
704
|
+
|
|
705
|
+
if (saveOptions.assignGeneratedIds) {
|
|
934
706
|
rows.forEach((row, i) => (dbms[i]!.id = row.id))
|
|
935
707
|
}
|
|
936
708
|
|
|
937
|
-
this.logSaveResult(started, op, table)
|
|
938
709
|
return rows
|
|
939
710
|
}
|
|
940
711
|
|
|
712
|
+
private prepareSaveOptions(opt: CommonDaoSaveOptions<BM, DBM>): CommonDBSaveOptions<DBM> {
|
|
713
|
+
let {
|
|
714
|
+
saveMethod,
|
|
715
|
+
assignGeneratedIds = this.cfg.assignGeneratedIds,
|
|
716
|
+
excludeFromIndexes = this.cfg.excludeFromIndexes,
|
|
717
|
+
} = opt
|
|
718
|
+
if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
|
|
719
|
+
saveMethod = 'insert'
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return {
|
|
723
|
+
...opt,
|
|
724
|
+
excludeFromIndexes,
|
|
725
|
+
saveMethod,
|
|
726
|
+
assignGeneratedIds,
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
941
730
|
/**
|
|
942
731
|
* "Streaming" is implemented by buffering incoming rows into **batches**
|
|
943
732
|
* (of size opt.chunkSize, which defaults to 500),
|
|
@@ -1016,12 +805,8 @@ export class CommonDao<
|
|
|
1016
805
|
if (!ids.length) return 0
|
|
1017
806
|
this.requireWriteAccess()
|
|
1018
807
|
this.requireObjectMutability(opt)
|
|
1019
|
-
const op = `deleteByIds(${ids.join(', ')})`
|
|
1020
808
|
const table = opt.table || this.cfg.table
|
|
1021
|
-
|
|
1022
|
-
const count = await (opt.tx || this.cfg.db).deleteByIds(table, ids as string[], opt)
|
|
1023
|
-
this.logSaveResult(started, op, table)
|
|
1024
|
-
return count
|
|
809
|
+
return await (opt.tx || this.cfg.db).deleteByIds(table, ids, opt)
|
|
1025
810
|
}
|
|
1026
811
|
|
|
1027
812
|
/**
|
|
@@ -1037,8 +822,6 @@ export class CommonDao<
|
|
|
1037
822
|
this.requireWriteAccess()
|
|
1038
823
|
this.requireObjectMutability(opt)
|
|
1039
824
|
q.table = opt.table || q.table
|
|
1040
|
-
const op = `deleteByQuery(${q.pretty()})`
|
|
1041
|
-
const started = this.logStarted(op, q.table)
|
|
1042
825
|
let deleted = 0
|
|
1043
826
|
|
|
1044
827
|
if (opt.chunkSize) {
|
|
@@ -1071,7 +854,6 @@ export class CommonDao<
|
|
|
1071
854
|
deleted = await this.cfg.db.deleteByQuery(q, opt)
|
|
1072
855
|
}
|
|
1073
856
|
|
|
1074
|
-
this.logSaveResult(started, op, q.table)
|
|
1075
857
|
return deleted
|
|
1076
858
|
}
|
|
1077
859
|
|
|
@@ -1089,11 +871,7 @@ export class CommonDao<
|
|
|
1089
871
|
this.requireWriteAccess()
|
|
1090
872
|
this.requireObjectMutability(opt)
|
|
1091
873
|
q.table = opt.table || q.table
|
|
1092
|
-
|
|
1093
|
-
const started = this.logStarted(op, q.table)
|
|
1094
|
-
const updated = await this.cfg.db.patchByQuery(q, patch, opt)
|
|
1095
|
-
this.logSaveResult(started, op, q.table)
|
|
1096
|
-
return updated
|
|
874
|
+
return await this.cfg.db.patchByQuery(q, patch, opt)
|
|
1097
875
|
}
|
|
1098
876
|
|
|
1099
877
|
/**
|
|
@@ -1105,13 +883,10 @@ export class CommonDao<
|
|
|
1105
883
|
this.requireWriteAccess()
|
|
1106
884
|
this.requireObjectMutability(opt)
|
|
1107
885
|
const { table } = this.cfg
|
|
1108
|
-
const op = `increment`
|
|
1109
|
-
const started = this.logStarted(op, table)
|
|
1110
886
|
const result = await this.cfg.db.incrementBatch(table, prop as string, {
|
|
1111
|
-
[id
|
|
887
|
+
[id]: by,
|
|
1112
888
|
})
|
|
1113
|
-
|
|
1114
|
-
return result[id as string]!
|
|
889
|
+
return result[id]!
|
|
1115
890
|
}
|
|
1116
891
|
|
|
1117
892
|
/**
|
|
@@ -1127,19 +902,15 @@ export class CommonDao<
|
|
|
1127
902
|
this.requireWriteAccess()
|
|
1128
903
|
this.requireObjectMutability(opt)
|
|
1129
904
|
const { table } = this.cfg
|
|
1130
|
-
|
|
1131
|
-
const started = this.logStarted(op, table)
|
|
1132
|
-
const result = await this.cfg.db.incrementBatch(table, prop as string, incrementMap)
|
|
1133
|
-
this.logSaveResult(started, op, table)
|
|
1134
|
-
return result
|
|
905
|
+
return await this.cfg.db.incrementBatch(table, prop as string, incrementMap)
|
|
1135
906
|
}
|
|
1136
907
|
|
|
1137
908
|
// CONVERSIONS
|
|
1138
909
|
|
|
1139
|
-
async dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): Promise<
|
|
910
|
+
async dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): Promise<null>
|
|
1140
911
|
async dbmToBM(_dbm?: DBM, opt?: CommonDaoOptions): Promise<BM>
|
|
1141
|
-
async dbmToBM(_dbm?: DBM, opt: CommonDaoOptions = {}): Promise<BM |
|
|
1142
|
-
if (!_dbm) return
|
|
912
|
+
async dbmToBM(_dbm?: DBM, opt: CommonDaoOptions = {}): Promise<BM | null> {
|
|
913
|
+
if (!_dbm) return null
|
|
1143
914
|
|
|
1144
915
|
// optimization: no need to run full joi DBM validation, cause BM validation will be run
|
|
1145
916
|
// const dbm = this.anyToDBM(_dbm, opt)
|
|
@@ -1164,10 +935,10 @@ export class CommonDao<
|
|
|
1164
935
|
* Mutates object with properties: id, created, updated.
|
|
1165
936
|
* Returns DBM (new reference).
|
|
1166
937
|
*/
|
|
1167
|
-
async bmToDBM(bm: undefined, opt?: CommonDaoOptions): Promise<
|
|
938
|
+
async bmToDBM(bm: undefined, opt?: CommonDaoOptions): Promise<null>
|
|
1168
939
|
async bmToDBM(bm?: BM, opt?: CommonDaoOptions): Promise<DBM>
|
|
1169
|
-
async bmToDBM(bm?: BM, opt?: CommonDaoOptions): Promise<DBM |
|
|
1170
|
-
if (bm === undefined) return
|
|
940
|
+
async bmToDBM(bm?: BM, opt?: CommonDaoOptions): Promise<DBM | null> {
|
|
941
|
+
if (bm === undefined) return null
|
|
1171
942
|
|
|
1172
943
|
// bm gets assigned to the new reference
|
|
1173
944
|
bm = this.validateAndConvert(bm, 'save', opt)
|
|
@@ -1181,10 +952,10 @@ export class CommonDao<
|
|
|
1181
952
|
return await pMap(bms, async bm => await this.bmToDBM(bm, opt))
|
|
1182
953
|
}
|
|
1183
954
|
|
|
1184
|
-
anyToDBM(dbm: undefined, opt?: CommonDaoOptions):
|
|
955
|
+
anyToDBM(dbm: undefined, opt?: CommonDaoOptions): null
|
|
1185
956
|
anyToDBM(dbm?: any, opt?: CommonDaoOptions): DBM
|
|
1186
|
-
anyToDBM(dbm?: DBM, opt: CommonDaoOptions = {}): DBM |
|
|
1187
|
-
if (!dbm) return
|
|
957
|
+
anyToDBM(dbm?: DBM, opt: CommonDaoOptions = {}): DBM | null {
|
|
958
|
+
if (!dbm) return null
|
|
1188
959
|
|
|
1189
960
|
// this shouldn't be happening on load! but should on save!
|
|
1190
961
|
// this.assignIdCreatedUpdated(dbm, opt)
|
|
@@ -1202,8 +973,8 @@ export class CommonDao<
|
|
|
1202
973
|
return dbm
|
|
1203
974
|
}
|
|
1204
975
|
|
|
1205
|
-
anyToDBMs(
|
|
1206
|
-
return
|
|
976
|
+
anyToDBMs(rows: DBM[], opt: CommonDaoOptions = {}): DBM[] {
|
|
977
|
+
return rows.map(entity => this.anyToDBM(entity, opt))
|
|
1207
978
|
}
|
|
1208
979
|
|
|
1209
980
|
/**
|
|
@@ -1278,10 +1049,18 @@ export class CommonDao<
|
|
|
1278
1049
|
}
|
|
1279
1050
|
}
|
|
1280
1051
|
|
|
1281
|
-
|
|
1052
|
+
withRows(rows: BM[]): DaoWithRows<typeof this> {
|
|
1053
|
+
return {
|
|
1054
|
+
dao: this,
|
|
1055
|
+
rows: rows as any,
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
withRow(row: BM, opt?: DaoWithRowOptions<BM>): DaoWithRow<typeof this> {
|
|
1282
1060
|
return {
|
|
1283
1061
|
dao: this,
|
|
1284
|
-
|
|
1062
|
+
row: row as any,
|
|
1063
|
+
opt: opt as any,
|
|
1285
1064
|
}
|
|
1286
1065
|
}
|
|
1287
1066
|
|
|
@@ -1402,16 +1181,39 @@ export class CommonDao<
|
|
|
1402
1181
|
}
|
|
1403
1182
|
|
|
1404
1183
|
static async multiSave(
|
|
1405
|
-
inputs: DaoWithRows<any>[],
|
|
1184
|
+
inputs: (DaoWithRows<any> | DaoWithRow<any>)[],
|
|
1406
1185
|
opt: CommonDaoSaveBatchOptions<any> = {},
|
|
1407
1186
|
): Promise<void> {
|
|
1408
1187
|
if (!inputs.length) return
|
|
1409
1188
|
const { db } = inputs[0]!.dao.cfg
|
|
1410
1189
|
const dbmsByTable: StringMap<any[]> = {}
|
|
1411
|
-
await pMap(inputs, async
|
|
1190
|
+
await pMap(inputs, async input => {
|
|
1191
|
+
const { dao } = input
|
|
1412
1192
|
const { table } = dao.cfg
|
|
1413
|
-
|
|
1414
|
-
|
|
1193
|
+
dbmsByTable[table] ||= []
|
|
1194
|
+
|
|
1195
|
+
if ('row' in input) {
|
|
1196
|
+
// Singular
|
|
1197
|
+
const { row } = input
|
|
1198
|
+
|
|
1199
|
+
if (input.opt?.skipIfEquals) {
|
|
1200
|
+
// We compare with convertedBM, to account for cases when some extra property is assigned to bm,
|
|
1201
|
+
// which should be removed post-validation, but it breaks the "equality check"
|
|
1202
|
+
// Post-validation the equality check should work as intended
|
|
1203
|
+
const convertedBM = dao.validateAndConvert(row, 'save', opt)
|
|
1204
|
+
if (_deepJsonEquals(convertedBM, input.opt.skipIfEquals)) {
|
|
1205
|
+
// Skipping the save operation
|
|
1206
|
+
return
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
dao.assignIdCreatedUpdated(row, opt)
|
|
1211
|
+
dbmsByTable[table].push(await dao.bmToDBM(row, opt))
|
|
1212
|
+
} else {
|
|
1213
|
+
// Plural
|
|
1214
|
+
input.rows.forEach(bm => dao.assignIdCreatedUpdated(bm, opt))
|
|
1215
|
+
dbmsByTable[table].push(...(await dao.bmsToDBM(input.rows, opt)))
|
|
1216
|
+
}
|
|
1415
1217
|
})
|
|
1416
1218
|
|
|
1417
1219
|
await db.multiSave(dbmsByTable)
|
|
@@ -1442,6 +1244,33 @@ export class CommonDao<
|
|
|
1442
1244
|
return r!
|
|
1443
1245
|
}
|
|
1444
1246
|
|
|
1247
|
+
private ensureRequired<ROW>(row: ROW, id: string, opt: CommonDaoOptions): NonNullable<ROW> {
|
|
1248
|
+
const table = opt.table || this.cfg.table
|
|
1249
|
+
_assert(row, `DB row required, but not found in ${table}`, {
|
|
1250
|
+
table,
|
|
1251
|
+
id,
|
|
1252
|
+
})
|
|
1253
|
+
return row // pass-through
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Throws if readOnly is true
|
|
1258
|
+
*/
|
|
1259
|
+
private requireWriteAccess(): void {
|
|
1260
|
+
_assert(!this.cfg.readOnly, DBLibError.DAO_IS_READ_ONLY, {
|
|
1261
|
+
table: this.cfg.table,
|
|
1262
|
+
})
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/**
|
|
1266
|
+
* Throws if readOnly is true
|
|
1267
|
+
*/
|
|
1268
|
+
private requireObjectMutability(opt: CommonDaoOptions): void {
|
|
1269
|
+
_assert(!this.cfg.immutable || opt.allowMutability, DBLibError.OBJECT_IS_IMMUTABLE, {
|
|
1270
|
+
table: this.cfg.table,
|
|
1271
|
+
})
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1445
1274
|
/**
|
|
1446
1275
|
* Throws if query uses a property that is in `excludeFromIndexes` list.
|
|
1447
1276
|
*/
|
|
@@ -1459,62 +1288,6 @@ export class CommonDao<
|
|
|
1459
1288
|
)
|
|
1460
1289
|
}
|
|
1461
1290
|
}
|
|
1462
|
-
|
|
1463
|
-
protected logResult(started: UnixTimestampMillis, op: string, res: any, table: string): void {
|
|
1464
|
-
if (!this.cfg.logLevel) return
|
|
1465
|
-
|
|
1466
|
-
let logRes: any
|
|
1467
|
-
const args: any[] = []
|
|
1468
|
-
|
|
1469
|
-
if (Array.isArray(res)) {
|
|
1470
|
-
logRes = `${res.length} row(s)`
|
|
1471
|
-
if (res.length && this.cfg.logLevel >= CommonDaoLogLevel.DATA_FULL) {
|
|
1472
|
-
args.push('\n', ...res.slice(0, 10)) // max 10 items
|
|
1473
|
-
}
|
|
1474
|
-
} else if (res) {
|
|
1475
|
-
logRes = `1 row`
|
|
1476
|
-
if (this.cfg.logLevel >= CommonDaoLogLevel.DATA_SINGLE) {
|
|
1477
|
-
args.push('\n', res)
|
|
1478
|
-
}
|
|
1479
|
-
} else {
|
|
1480
|
-
logRes = `undefined`
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
this.cfg.logger?.log(`<< ${table}.${op}: ${logRes} in ${_since(started)}`, ...args)
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
protected logSaveResult(started: UnixTimestampMillis, op: string, table: string): void {
|
|
1487
|
-
if (!this.cfg.logLevel) return
|
|
1488
|
-
this.cfg.logger?.log(`<< ${table}.${op} in ${_since(started)}`)
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
protected logStarted(op: string, table: string, force = false): UnixTimestampMillis {
|
|
1492
|
-
if (this.cfg.logStarted || force) {
|
|
1493
|
-
this.cfg.logger?.log(`>> ${table}.${op}`)
|
|
1494
|
-
}
|
|
1495
|
-
return localTime.nowUnixMillis()
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
protected logSaveStarted(op: string, items: any, table: string): UnixTimestampMillis {
|
|
1499
|
-
if (this.cfg.logStarted) {
|
|
1500
|
-
const args: any[] = [`>> ${table}.${op}`]
|
|
1501
|
-
if (Array.isArray(items)) {
|
|
1502
|
-
if (items.length && this.cfg.logLevel! >= CommonDaoLogLevel.DATA_FULL) {
|
|
1503
|
-
args.push('\n', ...items.slice(0, 10))
|
|
1504
|
-
} else {
|
|
1505
|
-
args.push(`${items.length} row(s)`)
|
|
1506
|
-
}
|
|
1507
|
-
} else {
|
|
1508
|
-
if (this.cfg.logLevel! >= CommonDaoLogLevel.DATA_SINGLE) {
|
|
1509
|
-
args.push(items)
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
this.cfg.logger?.log(...args)
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
return localTime.nowUnixMillis()
|
|
1517
|
-
}
|
|
1518
1291
|
}
|
|
1519
1292
|
|
|
1520
1293
|
/**
|
|
@@ -1524,120 +1297,6 @@ export class CommonDao<
|
|
|
1524
1297
|
*/
|
|
1525
1298
|
export type CommonDaoTransactionFn<T = void> = (tx: CommonDaoTransaction) => Promise<T>
|
|
1526
1299
|
|
|
1527
|
-
/**
|
|
1528
|
-
* Transaction context.
|
|
1529
|
-
* Has similar API than CommonDao, but all operations are performed in the context of the transaction.
|
|
1530
|
-
*/
|
|
1531
|
-
export class CommonDaoTransaction {
|
|
1532
|
-
constructor(
|
|
1533
|
-
public tx: DBTransaction,
|
|
1534
|
-
private logger: CommonLogger,
|
|
1535
|
-
) {}
|
|
1536
|
-
|
|
1537
|
-
/**
|
|
1538
|
-
* Commits the underlying DBTransaction.
|
|
1539
|
-
* May throw.
|
|
1540
|
-
*/
|
|
1541
|
-
async commit(): Promise<void> {
|
|
1542
|
-
await this.tx.commit()
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
/**
|
|
1546
|
-
* Perform a graceful rollback without throwing/re-throwing any error.
|
|
1547
|
-
* Never throws.
|
|
1548
|
-
*/
|
|
1549
|
-
async rollback(): Promise<void> {
|
|
1550
|
-
try {
|
|
1551
|
-
await this.tx.rollback()
|
|
1552
|
-
} catch (err) {
|
|
1553
|
-
// graceful rollback without re-throw
|
|
1554
|
-
this.logger.error(err)
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
async getById<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(
|
|
1559
|
-
dao: CommonDao<BM, DBM, ID>,
|
|
1560
|
-
id?: ID | null,
|
|
1561
|
-
opt?: CommonDaoReadOptions,
|
|
1562
|
-
): Promise<BM | null> {
|
|
1563
|
-
return await dao.getById(id, { ...opt, tx: this.tx })
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
async getByIds<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(
|
|
1567
|
-
dao: CommonDao<BM, DBM, ID>,
|
|
1568
|
-
ids: ID[],
|
|
1569
|
-
opt?: CommonDaoReadOptions,
|
|
1570
|
-
): Promise<BM[]> {
|
|
1571
|
-
return await dao.getByIds(ids, { ...opt, tx: this.tx })
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
// todo: Queries inside Transaction are not supported yet
|
|
1575
|
-
// async runQuery<BM extends PartialObjectWithId, DBM extends ObjectWithId>(
|
|
1576
|
-
// dao: CommonDao<BM, DBM, any>,
|
|
1577
|
-
// q: DBQuery<DBM>,
|
|
1578
|
-
// opt?: CommonDaoOptions,
|
|
1579
|
-
// ): Promise<BM[]> {
|
|
1580
|
-
// try {
|
|
1581
|
-
// return await dao.runQuery(q, { ...opt, tx: this.tx })
|
|
1582
|
-
// } catch (err) {
|
|
1583
|
-
// await this.rollback()
|
|
1584
|
-
// throw err
|
|
1585
|
-
// }
|
|
1586
|
-
// }
|
|
1587
|
-
|
|
1588
|
-
async save<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
|
|
1589
|
-
dao: CommonDao<BM, DBM>,
|
|
1590
|
-
bm: Unsaved<BM>,
|
|
1591
|
-
opt?: CommonDaoSaveOptions<BM, DBM>,
|
|
1592
|
-
): Promise<BM> {
|
|
1593
|
-
return await dao.save(bm, { ...opt, tx: this.tx })
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
async saveBatch<BM extends BaseDBEntity, DBM extends BaseDBEntity>(
|
|
1597
|
-
dao: CommonDao<BM, DBM>,
|
|
1598
|
-
bms: Unsaved<BM>[],
|
|
1599
|
-
opt?: CommonDaoSaveBatchOptions<DBM>,
|
|
1600
|
-
): Promise<BM[]> {
|
|
1601
|
-
return await dao.saveBatch(bms, { ...opt, tx: this.tx })
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
/**
|
|
1605
|
-
* DaoTransaction.patch does not load from DB.
|
|
1606
|
-
* It assumes the bm was previously loaded in the same Transaction, hence could not be
|
|
1607
|
-
* concurrently modified. Hence it's safe to not sync with DB.
|
|
1608
|
-
*
|
|
1609
|
-
* So, this method is a rather simple convenience "Object.assign and then save".
|
|
1610
|
-
*/
|
|
1611
|
-
async patch<BM extends BaseDBEntity, DBM extends BaseDBEntity, ID extends string = BM['id']>(
|
|
1612
|
-
dao: CommonDao<BM, DBM, ID>,
|
|
1613
|
-
bm: BM,
|
|
1614
|
-
patch: Partial<BM>,
|
|
1615
|
-
opt?: CommonDaoSaveOptions<BM, DBM>,
|
|
1616
|
-
): Promise<BM> {
|
|
1617
|
-
const skipIfEquals = _deepCopy(bm)
|
|
1618
|
-
Object.assign(bm, patch)
|
|
1619
|
-
return await dao.save(bm, { ...opt, skipIfEquals, tx: this.tx })
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
// todo: use AnyDao/Infer in other methods as well, if this works well
|
|
1623
|
-
async deleteById<DAO extends AnyDao>(
|
|
1624
|
-
dao: DAO,
|
|
1625
|
-
id?: InferID<DAO> | null,
|
|
1626
|
-
opt?: CommonDaoOptions,
|
|
1627
|
-
): Promise<number> {
|
|
1628
|
-
if (!id) return 0
|
|
1629
|
-
return await this.deleteByIds(dao, [id], opt)
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
async deleteByIds<DAO extends AnyDao>(
|
|
1633
|
-
dao: DAO,
|
|
1634
|
-
ids: InferID<DAO>[],
|
|
1635
|
-
opt?: CommonDaoOptions,
|
|
1636
|
-
): Promise<number> {
|
|
1637
|
-
return await dao.deleteByIds(ids, { ...opt, tx: this.tx })
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
1300
|
export interface DaoWithIds<DAO extends AnyDao> {
|
|
1642
1301
|
dao: DAO
|
|
1643
1302
|
ids: string[]
|
|
@@ -1653,8 +1312,18 @@ export interface DaoWithRows<DAO extends AnyDao> {
|
|
|
1653
1312
|
rows: InferBM<DAO>[]
|
|
1654
1313
|
}
|
|
1655
1314
|
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1315
|
+
export interface DaoWithRow<DAO extends AnyDao> {
|
|
1316
|
+
dao: DAO
|
|
1317
|
+
row: InferBM<DAO>
|
|
1318
|
+
opt?: DaoWithRowOptions<InferBM<DAO>>
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
interface DaoWithRowOptions<BM extends BaseDBEntity> {
|
|
1322
|
+
skipIfEquals?: BM
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
export type InferBM<DAO> = DAO extends CommonDao<infer BM> ? BM : never
|
|
1326
|
+
export type InferDBM<DAO> = DAO extends CommonDao<any, infer DBM> ? DBM : never
|
|
1327
|
+
export type InferID<DAO> = DAO extends CommonDao<any, any, infer ID> ? ID : never
|
|
1659
1328
|
|
|
1660
1329
|
export type AnyDao = CommonDao<any>
|