@naturalcycles/db-lib 8.52.0 → 8.54.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.
@@ -1,4 +1,4 @@
1
- import { AnyObject, AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Promisable, Saved, Unsaved, ZodSchema } from '@naturalcycles/js-lib';
1
+ import { AnyObject, AsyncMapper, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId, Promisable, Saved, UnixTimestampMillisNumber, Unsaved, ZodSchema } from '@naturalcycles/js-lib';
2
2
  import { AjvSchema, ObjectSchemaTyped, ReadableTyped } from '@naturalcycles/nodejs-lib';
3
3
  import { DBDeleteByIdsOperation, DBModelType, DBOperation, DBPatch, DBSaveBatchOperation, RunQueryResult } from '../db.model';
4
4
  import { DBQuery, RunnableDBQuery } from '../query/dbQuery';
@@ -84,7 +84,7 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
84
84
  assignIdCreatedUpdated(obj: BM, opt?: CommonDaoOptions): Saved<BM>;
85
85
  assignIdCreatedUpdated(obj: Unsaved<BM>, opt?: CommonDaoOptions): Saved<BM>;
86
86
  tx: {
87
- save: (bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<DBM>) => Promise<DBSaveBatchOperation>;
87
+ save: (bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<DBM>) => Promise<DBSaveBatchOperation | undefined>;
88
88
  saveBatch: (bms: Unsaved<BM>[], opt?: CommonDaoSaveOptions<DBM>) => Promise<DBSaveBatchOperation | undefined>;
89
89
  deleteByIds: (ids: ID[], opt?: CommonDaoOptions) => Promise<DBDeleteByIdsOperation | undefined>;
90
90
  deleteById: (id: ID | null | undefined, opt?: CommonDaoOptions) => Promise<DBDeleteByIdsOperation | undefined>;
@@ -139,9 +139,6 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
139
139
  bmToTM(bm: undefined, opt?: CommonDaoOptions): TM | undefined;
140
140
  bmToTM(bm?: Saved<BM>, opt?: CommonDaoOptions): TM;
141
141
  bmsToTM(bms: Saved<BM>[], opt?: CommonDaoOptions): TM[];
142
- tmToBM(tm: undefined, opt?: CommonDaoOptions): undefined;
143
- tmToBM(tm?: TM, opt?: CommonDaoOptions): BM;
144
- tmsToBM(tms: TM[], opt?: CommonDaoOptions): BM[];
145
142
  /**
146
143
  * Returns *converted value*.
147
144
  * Validates (unless `skipValidation=true` passed).
@@ -158,6 +155,6 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
158
155
  runInTransaction(ops: Promisable<DBOperation | undefined>[]): Promise<void>;
159
156
  protected logResult(started: number, op: string, res: any, table: string): void;
160
157
  protected logSaveResult(started: number, op: string, table: string): void;
161
- protected logStarted(op: string, table: string, force?: boolean): number;
162
- protected logSaveStarted(op: string, items: any, table: string): number;
158
+ protected logStarted(op: string, table: string, force?: boolean): UnixTimestampMillisNumber;
159
+ protected logSaveStarted(op: string, items: any, table: string): UnixTimestampMillisNumber;
163
160
  }
@@ -22,7 +22,10 @@ class CommonDao {
22
22
  this.cfg = cfg;
23
23
  this.tx = {
24
24
  save: async (bm, opt = {}) => {
25
+ // .save actually returns DBM (not BM) when it detects `opt.tx === true`
25
26
  const row = (await this.save(bm, { ...opt, tx: true }));
27
+ if (row === null)
28
+ return;
26
29
  return {
27
30
  type: 'saveBatch',
28
31
  table: this.cfg.table,
@@ -34,9 +37,9 @@ class CommonDao {
34
37
  };
35
38
  },
36
39
  saveBatch: async (bms, opt = {}) => {
37
- if (!bms.length)
38
- return;
39
40
  const rows = (await this.saveBatch(bms, { ...opt, tx: true }));
41
+ if (!rows.length)
42
+ return;
40
43
  return {
41
44
  type: 'saveBatch',
42
45
  table: this.cfg.table,
@@ -86,7 +89,6 @@ class CommonDao {
86
89
  beforeDBMValidate: dbm => dbm,
87
90
  beforeDBMToBM: dbm => dbm,
88
91
  beforeBMToDBM: bm => bm,
89
- beforeTMToBM: tm => tm,
90
92
  beforeBMToTM: bm => bm,
91
93
  anonymize: dbm => dbm,
92
94
  onValidationError: err => err,
@@ -114,7 +116,10 @@ class CommonDao {
114
116
  const op = `getById(${id})`;
115
117
  const table = opt.table || this.cfg.table;
116
118
  const started = this.logStarted(op, table);
117
- const dbm = (await this.cfg.db.getByIds(table, [id]))[0];
119
+ let dbm = (await this.cfg.db.getByIds(table, [id]))[0];
120
+ if (dbm && !opt.raw && this.cfg.hooks.afterLoad) {
121
+ dbm = (await this.cfg.hooks.afterLoad(dbm)) || undefined;
122
+ }
118
123
  const bm = opt.raw ? dbm : await this.dbmToBM(dbm, opt);
119
124
  this.logResult(started, op, bm, table);
120
125
  return bm || null;
@@ -139,6 +144,9 @@ class CommonDao {
139
144
  const table = opt.table || this.cfg.table;
140
145
  const started = this.logStarted(op, table);
141
146
  let [dbm] = await this.cfg.db.getByIds(table, [id]);
147
+ if (dbm && !opt.raw && this.cfg.hooks.afterLoad) {
148
+ dbm = (await this.cfg.hooks.afterLoad(dbm)) || undefined;
149
+ }
142
150
  if (!opt.raw) {
143
151
  dbm = this.anyToDBM(dbm, opt);
144
152
  }
@@ -151,7 +159,10 @@ class CommonDao {
151
159
  const op = `getByIdAsTM(${id})`;
152
160
  const table = opt.table || this.cfg.table;
153
161
  const started = this.logStarted(op, table);
154
- const [dbm] = await this.cfg.db.getByIds(table, [id]);
162
+ let [dbm] = await this.cfg.db.getByIds(table, [id]);
163
+ if (dbm && !opt.raw && this.cfg.hooks.afterLoad) {
164
+ dbm = (await this.cfg.hooks.afterLoad(dbm)) || undefined;
165
+ }
155
166
  if (opt.raw) {
156
167
  this.logResult(started, op, dbm, table);
157
168
  return dbm || null;
@@ -162,19 +173,29 @@ class CommonDao {
162
173
  return tm || null;
163
174
  }
164
175
  async getByIds(ids, opt = {}) {
176
+ if (!ids.length)
177
+ return [];
165
178
  const op = `getByIds ${ids.length} id(s) (${(0, js_lib_1._truncate)(ids.slice(0, 10).join(', '), 50)})`;
166
179
  const table = opt.table || this.cfg.table;
167
180
  const started = this.logStarted(op, table);
168
- const dbms = await this.cfg.db.getByIds(table, ids);
181
+ let dbms = await this.cfg.db.getByIds(table, ids);
182
+ if (!opt.raw && this.cfg.hooks.afterLoad && dbms.length) {
183
+ dbms = (await (0, js_lib_1.pMap)(dbms, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
184
+ }
169
185
  const bms = opt.raw ? dbms : await this.dbmsToBM(dbms, opt);
170
186
  this.logResult(started, op, bms, table);
171
187
  return bms;
172
188
  }
173
189
  async getByIdsAsDBM(ids, opt = {}) {
190
+ if (!ids.length)
191
+ return [];
174
192
  const op = `getByIdsAsDBM ${ids.length} id(s) (${(0, js_lib_1._truncate)(ids.slice(0, 10).join(', '), 50)})`;
175
193
  const table = opt.table || this.cfg.table;
176
194
  const started = this.logStarted(op, table);
177
- const dbms = await this.cfg.db.getByIds(table, ids);
195
+ let dbms = await this.cfg.db.getByIds(table, ids);
196
+ if (!opt.raw && this.cfg.hooks.afterLoad && dbms.length) {
197
+ dbms = (await (0, js_lib_1.pMap)(dbms, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
198
+ }
178
199
  this.logResult(started, op, dbms, table);
179
200
  return dbms;
180
201
  }
@@ -273,8 +294,11 @@ class CommonDao {
273
294
  q.table = opt.table || q.table;
274
295
  const op = `runQuery(${q.pretty()})`;
275
296
  const started = this.logStarted(op, q.table);
276
- const { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
297
+ let { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
277
298
  const partialQuery = !!q._selectedFieldNames;
299
+ if (!opt.raw && this.cfg.hooks.afterLoad && rows.length) {
300
+ rows = (await (0, js_lib_1.pMap)(rows, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
301
+ }
278
302
  const bms = partialQuery || opt.raw ? rows : await this.dbmsToBM(rows, opt);
279
303
  this.logResult(started, op, bms, q.table);
280
304
  return {
@@ -290,7 +314,10 @@ class CommonDao {
290
314
  q.table = opt.table || q.table;
291
315
  const op = `runQueryAsDBM(${q.pretty()})`;
292
316
  const started = this.logStarted(op, q.table);
293
- const { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
317
+ let { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
318
+ if (!opt.raw && this.cfg.hooks.afterLoad && rows.length) {
319
+ rows = (await (0, js_lib_1.pMap)(rows, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
320
+ }
294
321
  const partialQuery = !!q._selectedFieldNames;
295
322
  const dbms = partialQuery || opt.raw ? rows : this.anyToDBMs(rows, opt);
296
323
  this.logResult(started, op, dbms, q.table);
@@ -304,7 +331,10 @@ class CommonDao {
304
331
  q.table = opt.table || q.table;
305
332
  const op = `runQueryAsTM(${q.pretty()})`;
306
333
  const started = this.logStarted(op, q.table);
307
- const { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
334
+ let { rows, ...queryResult } = await this.cfg.db.runQuery(q, opt);
335
+ if (!opt.raw && this.cfg.hooks.afterLoad && rows.length) {
336
+ rows = (await (0, js_lib_1.pMap)(rows, async (dbm) => await this.cfg.hooks.afterLoad(dbm))).filter(js_lib_1._isTruthy);
337
+ }
308
338
  const partialQuery = !!q._selectedFieldNames;
309
339
  const tms = partialQuery || opt.raw ? rows : this.bmsToTM(await this.dbmsToBM(rows, opt), opt);
310
340
  this.logResult(started, op, tms, q.table);
@@ -334,11 +364,16 @@ class CommonDao {
334
364
  let count = 0;
335
365
  await (0, nodejs_lib_1._pipeline)([
336
366
  this.cfg.db.streamQuery(q, opt),
337
- // optimization: 1 validation is enough
338
- // transformMap<any, DBM>(dbm => (partialQuery || opt.raw ? dbm : this.anyToDBM(dbm, opt)), opt),
339
367
  (0, nodejs_lib_1.transformMap)(async (dbm) => {
340
368
  count++;
341
- return partialQuery || opt.raw ? dbm : await this.dbmToBM(dbm, opt);
369
+ if (partialQuery || opt.raw)
370
+ return dbm;
371
+ if (this.cfg.hooks.afterLoad) {
372
+ dbm = (await this.cfg.hooks.afterLoad(dbm));
373
+ if (dbm === null)
374
+ return js_lib_1.SKIP;
375
+ }
376
+ return await this.dbmToBM(dbm, opt);
342
377
  }, {
343
378
  errorMode: opt.errorMode,
344
379
  }),
@@ -368,9 +403,16 @@ class CommonDao {
368
403
  let count = 0;
369
404
  await (0, nodejs_lib_1._pipeline)([
370
405
  this.cfg.db.streamQuery(q, opt),
371
- (0, nodejs_lib_1.transformMapSync)(dbm => {
406
+ (0, nodejs_lib_1.transformMap)(async (dbm) => {
372
407
  count++;
373
- return partialQuery || opt.raw ? dbm : this.anyToDBM(dbm, opt);
408
+ if (partialQuery || opt.raw)
409
+ return dbm;
410
+ if (this.cfg.hooks.afterLoad) {
411
+ dbm = (await this.cfg.hooks.afterLoad(dbm));
412
+ if (dbm === null)
413
+ return js_lib_1.SKIP;
414
+ }
415
+ return this.anyToDBM(dbm, opt);
374
416
  }, {
375
417
  errorMode: opt.errorMode,
376
418
  }),
@@ -403,8 +445,15 @@ class CommonDao {
403
445
  return stream;
404
446
  return stream
405
447
  .on('error', err => stream.emit('error', err))
406
- .pipe((0, nodejs_lib_1.transformMapSimple)(dbm => this.anyToDBM(dbm, opt), {
407
- errorMode: js_lib_1.ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
448
+ .pipe((0, nodejs_lib_1.transformMap)(async (dbm) => {
449
+ if (this.cfg.hooks.afterLoad) {
450
+ dbm = (await this.cfg.hooks.afterLoad(dbm));
451
+ if (dbm === null)
452
+ return js_lib_1.SKIP;
453
+ }
454
+ return this.anyToDBM(dbm, opt);
455
+ }, {
456
+ errorMode: opt.errorMode,
408
457
  }));
409
458
  }
410
459
  /**
@@ -430,8 +479,15 @@ class CommonDao {
430
479
  // .pipe(transformMap<any, DBM>(dbm => this.anyToDBM(dbm, opt), safeOpt))
431
480
  // .pipe(transformMap<DBM, Saved<BM>>(dbm => this.dbmToBM(dbm, opt), safeOpt))
432
481
  .on('error', err => stream.emit('error', err))
433
- .pipe((0, nodejs_lib_1.transformMap)(async (dbm) => await this.dbmToBM(dbm, opt), {
434
- errorMode: js_lib_1.ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
482
+ .pipe((0, nodejs_lib_1.transformMap)(async (dbm) => {
483
+ if (this.cfg.hooks.afterLoad) {
484
+ dbm = (await this.cfg.hooks.afterLoad(dbm));
485
+ if (dbm === null)
486
+ return js_lib_1.SKIP;
487
+ }
488
+ return await this.dbmToBM(dbm, opt);
489
+ }, {
490
+ errorMode: opt.errorMode,
435
491
  }))
436
492
  // this can make the stream async-iteration-friendly
437
493
  // but not applying it now for perf reasons
@@ -462,8 +518,10 @@ class CommonDao {
462
518
  let count = 0;
463
519
  await (0, nodejs_lib_1._pipeline)([
464
520
  this.cfg.db.streamQuery(q.select(['id']), opt),
465
- (0, nodejs_lib_1.transformMapSimple)(objectWithId => objectWithId.id),
466
- (0, nodejs_lib_1.transformTap)(() => count++),
521
+ (0, nodejs_lib_1.transformMapSimple)(objectWithId => {
522
+ count++;
523
+ return objectWithId.id;
524
+ }),
467
525
  (0, nodejs_lib_1.transformMap)(mapper, {
468
526
  ...opt,
469
527
  predicate: js_lib_1._passthroughPredicate,
@@ -503,8 +561,14 @@ class CommonDao {
503
561
  this.requireWriteAccess();
504
562
  const idWasGenerated = !bm.id && this.cfg.createId;
505
563
  this.assignIdCreatedUpdated(bm, opt); // mutates
506
- const dbm = await this.bmToDBM(bm, opt);
564
+ let dbm = await this.bmToDBM(bm, opt);
565
+ if (this.cfg.hooks.beforeSave) {
566
+ dbm = (await this.cfg.hooks.beforeSave(dbm));
567
+ if (dbm === null && !opt.tx)
568
+ return bm;
569
+ }
507
570
  if (opt.tx) {
571
+ // May return `null`, in which case it'll be skipped
508
572
  return dbm;
509
573
  }
510
574
  const table = opt.table || this.cfg.table;
@@ -570,6 +634,11 @@ class CommonDao {
570
634
  const started = this.logSaveStarted(op, row, table);
571
635
  const { excludeFromIndexes } = this.cfg;
572
636
  const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
637
+ if (this.cfg.hooks.beforeSave) {
638
+ row = (await this.cfg.hooks.beforeSave(row));
639
+ if (row === null)
640
+ return dbm;
641
+ }
573
642
  await this.cfg.db.saveBatch(table, [row], {
574
643
  excludeFromIndexes,
575
644
  assignGeneratedIds,
@@ -582,10 +651,15 @@ class CommonDao {
582
651
  return row;
583
652
  }
584
653
  async saveBatch(bms, opt = {}) {
654
+ if (!bms.length)
655
+ return [];
585
656
  this.requireWriteAccess();
586
657
  const table = opt.table || this.cfg.table;
587
658
  bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt));
588
- const dbms = await this.bmsToDBM(bms, opt);
659
+ let dbms = await this.bmsToDBM(bms, opt);
660
+ if (this.cfg.hooks.beforeSave && dbms.length) {
661
+ dbms = (await (0, js_lib_1.pMap)(dbms, async (dbm) => await this.cfg.hooks.beforeSave(dbm))).filter(js_lib_1._isTruthy);
662
+ }
589
663
  if (opt.tx) {
590
664
  return dbms;
591
665
  }
@@ -613,6 +687,8 @@ class CommonDao {
613
687
  return bms;
614
688
  }
615
689
  async saveBatchAsDBM(dbms, opt = {}) {
690
+ if (!dbms.length)
691
+ return [];
616
692
  this.requireWriteAccess();
617
693
  const table = opt.table || this.cfg.table;
618
694
  let rows = dbms;
@@ -632,6 +708,9 @@ class CommonDao {
632
708
  const started = this.logSaveStarted(op, rows, table);
633
709
  const { excludeFromIndexes } = this.cfg;
634
710
  const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
711
+ if (this.cfg.hooks.beforeSave && rows.length) {
712
+ rows = (await (0, js_lib_1.pMap)(rows, async (row) => await this.cfg.hooks.beforeSave(row))).filter(js_lib_1._isTruthy);
713
+ }
635
714
  await this.cfg.db.saveBatch(table, rows, {
636
715
  excludeFromIndexes,
637
716
  assignGeneratedIds,
@@ -656,6 +735,8 @@ class CommonDao {
656
735
  return count;
657
736
  }
658
737
  async deleteByIds(ids, opt = {}) {
738
+ if (!ids.length)
739
+ return 0;
659
740
  this.requireWriteAccess();
660
741
  this.requireObjectMutability(opt);
661
742
  const op = `deleteByIds(${ids.join(', ')})`;
@@ -710,6 +791,8 @@ class CommonDao {
710
791
  return await this.updateByQuery(this.query().filterEq('id', id), patch, opt);
711
792
  }
712
793
  async updateByIds(ids, patch, opt = {}) {
794
+ if (!ids.length)
795
+ return 0;
713
796
  return await this.updateByQuery(this.query().filterIn('id', ids), patch, opt);
714
797
  }
715
798
  async updateByQuery(q, patch, opt = {}) {
@@ -790,22 +873,6 @@ class CommonDao {
790
873
  // try/catch?
791
874
  return bms.map(bm => this.bmToTM(bm, opt));
792
875
  }
793
- tmToBM(tm, opt = {}) {
794
- if (!tm)
795
- return;
796
- // optimization: 1 validation is enough
797
- // Validate/convert TM
798
- // bm gets assigned to the new reference
799
- // tm = this.validateAndConvert(tm, this.cfg.tmSchema, DBModelType.TM, opt)
800
- // TM > BM
801
- const bm = this.cfg.hooks.beforeTMToBM(tm);
802
- // Validate/convert BM
803
- return this.validateAndConvert(bm, this.cfg.bmSchema, db_model_1.DBModelType.BM, opt);
804
- }
805
- tmsToBM(tms, opt = {}) {
806
- // try/catch?
807
- return tms.map(tm => this.tmToBM(tm, opt));
808
- }
809
876
  /**
810
877
  * Returns *converted value*.
811
878
  * Validates (unless `skipValidation=true` passed).
@@ -1,21 +1,82 @@
1
- import { CommonLogger, ErrorMode, ObjectWithId, Saved, ZodError, ZodSchema } from '@naturalcycles/js-lib';
1
+ import { CommonLogger, ErrorMode, ObjectWithId, Promisable, Saved, ZodError, ZodSchema } from '@naturalcycles/js-lib';
2
2
  import { AjvSchema, AjvValidationError, JoiValidationError, ObjectSchemaTyped, TransformLogProgressOptions, TransformMapOptions } from '@naturalcycles/nodejs-lib';
3
3
  import { CommonDB } from '../common.db';
4
4
  import { CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions } from '../db.model';
5
5
  export interface CommonDaoHooks<BM extends Partial<ObjectWithId<ID>>, DBM extends ObjectWithId<ID>, TM, ID extends string | number> {
6
+ /**
7
+ * Allows to override the id generation function.
8
+ * By default it uses `stringId` from nodejs-lib
9
+ * (which uses lowercase alphanumberic alphabet and the size of 16).
10
+ */
6
11
  createRandomId: () => ID;
7
12
  /**
8
13
  * createNaturalId hook is called (tried) first.
9
14
  * If it doesn't exist - createRandomId is called.
10
15
  */
11
16
  createNaturalId: (obj: DBM | BM) => ID;
17
+ /**
18
+ * It's a counter-part of `createNaturalId`.
19
+ * Allows to provide a parser function to parse "natural id" into
20
+ * DBM components (e.g accountId and some other property that is part of the id).
21
+ */
12
22
  parseNaturalId: (id: ID) => Partial<DBM>;
23
+ /**
24
+ * It is called only on `dao.create` method.
25
+ * Dao.create method is called in:
26
+ *
27
+ * - getByIdOrEmpty, getByIdAsDBMOrEmpty
28
+ * - patch, patchAsDBM
29
+ */
13
30
  beforeCreate: (bm: Partial<BM>) => Partial<BM>;
31
+ /**
32
+ * Called when loading things "as DBM" and validation is not skipped.
33
+ * When loading things as BM/TM - other hooks get involved instead:
34
+ * - beforeDBMToBM
35
+ * - beforeBMToTM
36
+ *
37
+ * TODO: maybe rename those to `validateAs(model)`
38
+ * as it only validates "final state", not intermediate
39
+ */
14
40
  beforeDBMValidate: (dbm: Partial<DBM>) => Partial<DBM>;
15
41
  beforeDBMToBM: (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>;
16
42
  beforeBMToDBM: (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>;
17
- beforeTMToBM: (tm: TM) => Partial<BM>;
18
43
  beforeBMToTM: (bm: BM) => Partial<TM>;
44
+ /**
45
+ * Allows to access the DBM just after it has been loaded from the DB.
46
+ *
47
+ * Normally does nothing.
48
+ *
49
+ * You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
50
+ * to pass it further.
51
+ *
52
+ * You can return `null` to make it look "not found".
53
+ *
54
+ * You can do validations as needed here and throw errors, they will be propagated.
55
+ */
56
+ afterLoad?: (dbm: DBM) => Promisable<DBM | null>;
57
+ /**
58
+ * Allows to access the DBM just before it's supposed to be saved to the DB.
59
+ *
60
+ * Normally does nothing.
61
+ *
62
+ * You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
63
+ * to pass it further.
64
+ *
65
+ * You can return `null` to prevent it from being saved, without throwing an error.
66
+ * `.save` method will then return the BM/DBM as it has entered the method (it **won't** return the null value!).
67
+ *
68
+ * You can do validations as needed here and throw errors, they will be propagated.
69
+ */
70
+ beforeSave?: (dbm: DBM) => Promisable<DBM | null>;
71
+ /**
72
+ * Called in:
73
+ * - dbmToBM (applied before DBM becomes BM)
74
+ * - anyToDBM
75
+ *
76
+ * Hook only allows to apply anonymization to DBM (not to BM).
77
+ * It still applies to BM "transitively", during dbmToBM
78
+ * (e.g after loaded from the Database).
79
+ */
19
80
  anonymize: (dbm: DBM) => DBM;
20
81
  /**
21
82
  * If hook is defined - allows to prevent or modify the error thrown.
package/package.json CHANGED
@@ -41,7 +41,7 @@
41
41
  "engines": {
42
42
  "node": ">=18.12"
43
43
  },
44
- "version": "8.52.0",
44
+ "version": "8.54.0",
45
45
  "description": "Lowest Common Denominator API to supported Databases",
46
46
  "keywords": [
47
47
  "db",
@@ -2,6 +2,7 @@ import {
2
2
  CommonLogger,
3
3
  ErrorMode,
4
4
  ObjectWithId,
5
+ Promisable,
5
6
  Saved,
6
7
  ZodError,
7
8
  ZodSchema,
@@ -23,19 +24,88 @@ export interface CommonDaoHooks<
23
24
  TM,
24
25
  ID extends string | number,
25
26
  > {
27
+ /**
28
+ * Allows to override the id generation function.
29
+ * By default it uses `stringId` from nodejs-lib
30
+ * (which uses lowercase alphanumberic alphabet and the size of 16).
31
+ */
26
32
  createRandomId: () => ID
33
+
27
34
  /**
28
35
  * createNaturalId hook is called (tried) first.
29
36
  * If it doesn't exist - createRandomId is called.
30
37
  */
31
38
  createNaturalId: (obj: DBM | BM) => ID
39
+
40
+ /**
41
+ * It's a counter-part of `createNaturalId`.
42
+ * Allows to provide a parser function to parse "natural id" into
43
+ * DBM components (e.g accountId and some other property that is part of the id).
44
+ */
32
45
  parseNaturalId: (id: ID) => Partial<DBM>
46
+
47
+ /**
48
+ * It is called only on `dao.create` method.
49
+ * Dao.create method is called in:
50
+ *
51
+ * - getByIdOrEmpty, getByIdAsDBMOrEmpty
52
+ * - patch, patchAsDBM
53
+ */
33
54
  beforeCreate: (bm: Partial<BM>) => Partial<BM>
55
+
56
+ /**
57
+ * Called when loading things "as DBM" and validation is not skipped.
58
+ * When loading things as BM/TM - other hooks get involved instead:
59
+ * - beforeDBMToBM
60
+ * - beforeBMToTM
61
+ *
62
+ * TODO: maybe rename those to `validateAs(model)`
63
+ * as it only validates "final state", not intermediate
64
+ */
34
65
  beforeDBMValidate: (dbm: Partial<DBM>) => Partial<DBM>
66
+
35
67
  beforeDBMToBM: (dbm: DBM) => Partial<BM> | Promise<Partial<BM>>
36
68
  beforeBMToDBM: (bm: BM) => Partial<DBM> | Promise<Partial<DBM>>
37
- beforeTMToBM: (tm: TM) => Partial<BM>
38
69
  beforeBMToTM: (bm: BM) => Partial<TM>
70
+
71
+ /**
72
+ * Allows to access the DBM just after it has been loaded from the DB.
73
+ *
74
+ * Normally does nothing.
75
+ *
76
+ * You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
77
+ * to pass it further.
78
+ *
79
+ * You can return `null` to make it look "not found".
80
+ *
81
+ * You can do validations as needed here and throw errors, they will be propagated.
82
+ */
83
+ afterLoad?: (dbm: DBM) => Promisable<DBM | null>
84
+
85
+ /**
86
+ * Allows to access the DBM just before it's supposed to be saved to the DB.
87
+ *
88
+ * Normally does nothing.
89
+ *
90
+ * You can change the DBM as you want here: ok to mutate or not, but you need to return the DBM
91
+ * to pass it further.
92
+ *
93
+ * You can return `null` to prevent it from being saved, without throwing an error.
94
+ * `.save` method will then return the BM/DBM as it has entered the method (it **won't** return the null value!).
95
+ *
96
+ * You can do validations as needed here and throw errors, they will be propagated.
97
+ */
98
+ beforeSave?: (dbm: DBM) => Promisable<DBM | null>
99
+
100
+ /**
101
+ * Called in:
102
+ * - dbmToBM (applied before DBM becomes BM)
103
+ * - anyToDBM
104
+ *
105
+ * Hook only allows to apply anonymization to DBM (not to BM).
106
+ * It still applies to BM "transitively", during dbmToBM
107
+ * (e.g after loaded from the Database).
108
+ */
39
109
  anonymize: (dbm: DBM) => DBM
40
110
 
41
111
  /**
@@ -17,6 +17,8 @@ import {
17
17
  pMap,
18
18
  Promisable,
19
19
  Saved,
20
+ SKIP,
21
+ UnixTimestampMillisNumber,
20
22
  Unsaved,
21
23
  ZodSchema,
22
24
  ZodValidationError,
@@ -35,8 +37,6 @@ import {
35
37
  transformLogProgress,
36
38
  transformMap,
37
39
  transformMapSimple,
38
- transformMapSync,
39
- transformTap,
40
40
  writableVoid,
41
41
  } from '@naturalcycles/nodejs-lib'
42
42
  import { DBLibError } from '../cnst'
@@ -53,6 +53,7 @@ import { DBTransaction } from '../transaction/dbTransaction'
53
53
  import {
54
54
  CommonDaoCfg,
55
55
  CommonDaoCreateOptions,
56
+ CommonDaoHooks,
56
57
  CommonDaoLogLevel,
57
58
  CommonDaoOptions,
58
59
  CommonDaoSaveOptions,
@@ -95,12 +96,11 @@ export class CommonDao<
95
96
  beforeDBMValidate: dbm => dbm,
96
97
  beforeDBMToBM: dbm => dbm as any,
97
98
  beforeBMToDBM: bm => bm as any,
98
- beforeTMToBM: tm => tm as any,
99
99
  beforeBMToTM: bm => bm as any,
100
100
  anonymize: dbm => dbm,
101
101
  onValidationError: err => err,
102
102
  ...cfg.hooks,
103
- },
103
+ } satisfies Partial<CommonDaoHooks<BM, DBM, TM, ID>>,
104
104
  }
105
105
 
106
106
  if (this.cfg.createId) {
@@ -132,7 +132,10 @@ export class CommonDao<
132
132
  const table = opt.table || this.cfg.table
133
133
  const started = this.logStarted(op, table)
134
134
 
135
- const dbm = (await this.cfg.db.getByIds<DBM>(table, [id]))[0]
135
+ let dbm = (await this.cfg.db.getByIds<DBM>(table, [id]))[0]
136
+ if (dbm && !opt.raw && this.cfg.hooks!.afterLoad) {
137
+ dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
138
+ }
136
139
 
137
140
  const bm = opt.raw ? (dbm as any) : await this.dbmToBM(dbm, opt)
138
141
  this.logResult(started, op, bm, table)
@@ -162,6 +165,10 @@ export class CommonDao<
162
165
  const table = opt.table || this.cfg.table
163
166
  const started = this.logStarted(op, table)
164
167
  let [dbm] = await this.cfg.db.getByIds<DBM>(table, [id])
168
+ if (dbm && !opt.raw && this.cfg.hooks!.afterLoad) {
169
+ dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
170
+ }
171
+
165
172
  if (!opt.raw) {
166
173
  dbm = this.anyToDBM(dbm!, opt)
167
174
  }
@@ -176,7 +183,11 @@ export class CommonDao<
176
183
  const op = `getByIdAsTM(${id})`
177
184
  const table = opt.table || this.cfg.table
178
185
  const started = this.logStarted(op, table)
179
- const [dbm] = await this.cfg.db.getByIds<DBM>(table, [id])
186
+ let [dbm] = await this.cfg.db.getByIds<DBM>(table, [id])
187
+ if (dbm && !opt.raw && this.cfg.hooks!.afterLoad) {
188
+ dbm = (await this.cfg.hooks!.afterLoad(dbm)) || undefined
189
+ }
190
+
180
191
  if (opt.raw) {
181
192
  this.logResult(started, op, dbm, table)
182
193
  return (dbm as any) || null
@@ -188,20 +199,34 @@ export class CommonDao<
188
199
  }
189
200
 
190
201
  async getByIds(ids: ID[], opt: CommonDaoOptions = {}): Promise<Saved<BM>[]> {
202
+ if (!ids.length) return []
191
203
  const op = `getByIds ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
192
204
  const table = opt.table || this.cfg.table
193
205
  const started = this.logStarted(op, table)
194
- const dbms = await this.cfg.db.getByIds<DBM>(table, ids)
206
+ let dbms = await this.cfg.db.getByIds<DBM>(table, ids)
207
+ if (!opt.raw && this.cfg.hooks!.afterLoad && dbms.length) {
208
+ dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
209
+ _isTruthy,
210
+ )
211
+ }
212
+
195
213
  const bms = opt.raw ? (dbms as any) : await this.dbmsToBM(dbms, opt)
196
214
  this.logResult(started, op, bms, table)
197
215
  return bms
198
216
  }
199
217
 
200
218
  async getByIdsAsDBM(ids: ID[], opt: CommonDaoOptions = {}): Promise<DBM[]> {
219
+ if (!ids.length) return []
201
220
  const op = `getByIdsAsDBM ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
202
221
  const table = opt.table || this.cfg.table
203
222
  const started = this.logStarted(op, table)
204
- const dbms = await this.cfg.db.getByIds<DBM>(table, ids)
223
+ let dbms = await this.cfg.db.getByIds<DBM>(table, ids)
224
+ if (!opt.raw && this.cfg.hooks!.afterLoad && dbms.length) {
225
+ dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
226
+ _isTruthy,
227
+ )
228
+ }
229
+
205
230
  this.logResult(started, op, dbms, table)
206
231
  return dbms
207
232
  }
@@ -324,8 +349,14 @@ export class CommonDao<
324
349
  q.table = opt.table || q.table
325
350
  const op = `runQuery(${q.pretty()})`
326
351
  const started = this.logStarted(op, q.table)
327
- const { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
352
+ let { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
328
353
  const partialQuery = !!q._selectedFieldNames
354
+ if (!opt.raw && this.cfg.hooks!.afterLoad && rows.length) {
355
+ rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
356
+ _isTruthy,
357
+ )
358
+ }
359
+
329
360
  const bms = partialQuery || opt.raw ? (rows as any[]) : await this.dbmsToBM(rows, opt)
330
361
  this.logResult(started, op, bms, q.table)
331
362
  return {
@@ -346,7 +377,13 @@ export class CommonDao<
346
377
  q.table = opt.table || q.table
347
378
  const op = `runQueryAsDBM(${q.pretty()})`
348
379
  const started = this.logStarted(op, q.table)
349
- const { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
380
+ let { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
381
+ if (!opt.raw && this.cfg.hooks!.afterLoad && rows.length) {
382
+ rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
383
+ _isTruthy,
384
+ )
385
+ }
386
+
350
387
  const partialQuery = !!q._selectedFieldNames
351
388
  const dbms = partialQuery || opt.raw ? rows : this.anyToDBMs(rows, opt)
352
389
  this.logResult(started, op, dbms, q.table)
@@ -365,7 +402,13 @@ export class CommonDao<
365
402
  q.table = opt.table || q.table
366
403
  const op = `runQueryAsTM(${q.pretty()})`
367
404
  const started = this.logStarted(op, q.table)
368
- const { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
405
+ let { rows, ...queryResult } = await this.cfg.db.runQuery<DBM>(q, opt)
406
+ if (!opt.raw && this.cfg.hooks!.afterLoad && rows.length) {
407
+ rows = (await pMap(rows, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
408
+ _isTruthy,
409
+ )
410
+ }
411
+
369
412
  const partialQuery = !!q._selectedFieldNames
370
413
  const tms =
371
414
  partialQuery || opt.raw ? (rows as any[]) : this.bmsToTM(await this.dbmsToBM(rows, opt), opt)
@@ -404,12 +447,17 @@ export class CommonDao<
404
447
 
405
448
  await _pipeline([
406
449
  this.cfg.db.streamQuery<DBM>(q, opt),
407
- // optimization: 1 validation is enough
408
- // transformMap<any, DBM>(dbm => (partialQuery || opt.raw ? dbm : this.anyToDBM(dbm, opt)), opt),
409
450
  transformMap<DBM, Saved<BM>>(
410
451
  async dbm => {
411
452
  count++
412
- return partialQuery || opt.raw ? (dbm as any) : await this.dbmToBM(dbm, opt)
453
+ if (partialQuery || opt.raw) return dbm as any
454
+
455
+ if (this.cfg.hooks!.afterLoad) {
456
+ dbm = (await this.cfg.hooks!.afterLoad(dbm)) as DBM
457
+ if (dbm === null) return SKIP
458
+ }
459
+
460
+ return await this.dbmToBM(dbm, opt)
413
461
  },
414
462
  {
415
463
  errorMode: opt.errorMode,
@@ -449,10 +497,17 @@ export class CommonDao<
449
497
 
450
498
  await _pipeline([
451
499
  this.cfg.db.streamQuery<any>(q, opt),
452
- transformMapSync<any, DBM>(
453
- dbm => {
500
+ transformMap<any, DBM>(
501
+ async dbm => {
454
502
  count++
455
- return partialQuery || opt.raw ? dbm : this.anyToDBM(dbm, opt)
503
+ if (partialQuery || opt.raw) return dbm
504
+
505
+ if (this.cfg.hooks!.afterLoad) {
506
+ dbm = (await this.cfg.hooks!.afterLoad(dbm)) as DBM
507
+ if (dbm === null) return SKIP
508
+ }
509
+
510
+ return this.anyToDBM(dbm, opt)
456
511
  },
457
512
  {
458
513
  errorMode: opt.errorMode,
@@ -492,9 +547,19 @@ export class CommonDao<
492
547
  return stream
493
548
  .on('error', err => stream.emit('error', err))
494
549
  .pipe(
495
- transformMapSimple<any, DBM>(dbm => this.anyToDBM(dbm, opt), {
496
- errorMode: ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
497
- }),
550
+ transformMap<any, DBM>(
551
+ async dbm => {
552
+ if (this.cfg.hooks!.afterLoad) {
553
+ dbm = (await this.cfg.hooks!.afterLoad(dbm)) as DBM
554
+ if (dbm === null) return SKIP
555
+ }
556
+
557
+ return this.anyToDBM(dbm, opt)
558
+ },
559
+ {
560
+ errorMode: opt.errorMode,
561
+ },
562
+ ),
498
563
  )
499
564
  }
500
565
 
@@ -524,9 +589,19 @@ export class CommonDao<
524
589
  // .pipe(transformMap<DBM, Saved<BM>>(dbm => this.dbmToBM(dbm, opt), safeOpt))
525
590
  .on('error', err => stream.emit('error', err))
526
591
  .pipe(
527
- transformMap<DBM, Saved<BM>>(async dbm => await this.dbmToBM(dbm, opt), {
528
- errorMode: ErrorMode.SUPPRESS, // cause .pipe() cannot propagate errors
529
- }),
592
+ transformMap<DBM, Saved<BM>>(
593
+ async dbm => {
594
+ if (this.cfg.hooks!.afterLoad) {
595
+ dbm = (await this.cfg.hooks!.afterLoad(dbm)) as DBM
596
+ if (dbm === null) return SKIP
597
+ }
598
+
599
+ return await this.dbmToBM(dbm, opt)
600
+ },
601
+ {
602
+ errorMode: opt.errorMode,
603
+ },
604
+ ),
530
605
  )
531
606
  // this can make the stream async-iteration-friendly
532
607
  // but not applying it now for perf reasons
@@ -570,8 +645,10 @@ export class CommonDao<
570
645
 
571
646
  await _pipeline([
572
647
  this.cfg.db.streamQuery<DBM>(q.select(['id']), opt),
573
- transformMapSimple<DBM, ID>(objectWithId => objectWithId.id),
574
- transformTap(() => count++),
648
+ transformMapSimple<DBM, ID>(objectWithId => {
649
+ count++
650
+ return objectWithId.id
651
+ }),
575
652
  transformMap<ID, void>(mapper, {
576
653
  ...opt,
577
654
  predicate: _passthroughPredicate,
@@ -619,8 +696,10 @@ export class CommonDao<
619
696
  save: async (
620
697
  bm: Unsaved<BM>,
621
698
  opt: CommonDaoSaveOptions<DBM> = {},
622
- ): Promise<DBSaveBatchOperation> => {
623
- const row: DBM = (await this.save(bm, { ...opt, tx: true })) as any
699
+ ): Promise<DBSaveBatchOperation | undefined> => {
700
+ // .save actually returns DBM (not BM) when it detects `opt.tx === true`
701
+ const row: DBM | null = (await this.save(bm, { ...opt, tx: true })) as any
702
+ if (row === null) return
624
703
 
625
704
  return {
626
705
  type: 'saveBatch',
@@ -636,8 +715,8 @@ export class CommonDao<
636
715
  bms: Unsaved<BM>[],
637
716
  opt: CommonDaoSaveOptions<DBM> = {},
638
717
  ): Promise<DBSaveBatchOperation | undefined> => {
639
- if (!bms.length) return
640
718
  const rows: DBM[] = (await this.saveBatch(bms, { ...opt, tx: true })) as any
719
+ if (!rows.length) return
641
720
 
642
721
  return {
643
722
  type: 'saveBatch',
@@ -683,9 +762,15 @@ export class CommonDao<
683
762
  this.requireWriteAccess()
684
763
  const idWasGenerated = !bm.id && this.cfg.createId
685
764
  this.assignIdCreatedUpdated(bm, opt) // mutates
686
- const dbm = await this.bmToDBM(bm as BM, opt)
765
+ let dbm = await this.bmToDBM(bm as BM, opt)
766
+
767
+ if (this.cfg.hooks!.beforeSave) {
768
+ dbm = (await this.cfg.hooks!.beforeSave(dbm)) as DBM
769
+ if (dbm === null && !opt.tx) return bm as any
770
+ }
687
771
 
688
772
  if (opt.tx) {
773
+ // May return `null`, in which case it'll be skipped
689
774
  return dbm as any
690
775
  }
691
776
 
@@ -698,6 +783,7 @@ export class CommonDao<
698
783
  const started = this.logSaveStarted(op, bm, table)
699
784
  const { excludeFromIndexes } = this.cfg
700
785
  const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
786
+
701
787
  await this.cfg.db.saveBatch(table, [dbm], {
702
788
  excludeFromIndexes,
703
789
  assignGeneratedIds,
@@ -764,6 +850,12 @@ export class CommonDao<
764
850
  const started = this.logSaveStarted(op, row, table)
765
851
  const { excludeFromIndexes } = this.cfg
766
852
  const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
853
+
854
+ if (this.cfg.hooks!.beforeSave) {
855
+ row = (await this.cfg.hooks!.beforeSave(row)) as DBM
856
+ if (row === null) return dbm
857
+ }
858
+
767
859
  await this.cfg.db.saveBatch(table, [row], {
768
860
  excludeFromIndexes,
769
861
  assignGeneratedIds,
@@ -779,10 +871,17 @@ export class CommonDao<
779
871
  }
780
872
 
781
873
  async saveBatch(bms: Unsaved<BM>[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>[]> {
874
+ if (!bms.length) return []
782
875
  this.requireWriteAccess()
783
876
  const table = opt.table || this.cfg.table
784
877
  bms.forEach(bm => this.assignIdCreatedUpdated(bm, opt))
785
- const dbms = await this.bmsToDBM(bms as BM[], opt)
878
+ let dbms = await this.bmsToDBM(bms as BM[], opt)
879
+
880
+ if (this.cfg.hooks!.beforeSave && dbms.length) {
881
+ dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.beforeSave!(dbm))).filter(
882
+ _isTruthy,
883
+ )
884
+ }
786
885
 
787
886
  if (opt.tx) {
788
887
  return dbms as any
@@ -803,6 +902,7 @@ export class CommonDao<
803
902
  const started = this.logSaveStarted(op, bms, table)
804
903
  const { excludeFromIndexes } = this.cfg
805
904
  const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
905
+
806
906
  await this.cfg.db.saveBatch(table, dbms, {
807
907
  excludeFromIndexes,
808
908
  assignGeneratedIds,
@@ -819,6 +919,7 @@ export class CommonDao<
819
919
  }
820
920
 
821
921
  async saveBatchAsDBM(dbms: DBM[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<DBM[]> {
922
+ if (!dbms.length) return []
822
923
  this.requireWriteAccess()
823
924
  const table = opt.table || this.cfg.table
824
925
  let rows = dbms
@@ -841,6 +942,12 @@ export class CommonDao<
841
942
  const { excludeFromIndexes } = this.cfg
842
943
  const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
843
944
 
945
+ if (this.cfg.hooks!.beforeSave && rows.length) {
946
+ rows = (await pMap(rows, async row => await this.cfg.hooks!.beforeSave!(row))).filter(
947
+ _isTruthy,
948
+ )
949
+ }
950
+
844
951
  await this.cfg.db.saveBatch(table, rows, {
845
952
  excludeFromIndexes,
846
953
  assignGeneratedIds,
@@ -874,6 +981,7 @@ export class CommonDao<
874
981
  }
875
982
 
876
983
  async deleteByIds(ids: ID[], opt: CommonDaoOptions = {}): Promise<number> {
984
+ if (!ids.length) return 0
877
985
  this.requireWriteAccess()
878
986
  this.requireObjectMutability(opt)
879
987
  const op = `deleteByIds(${ids.join(', ')})`
@@ -942,6 +1050,7 @@ export class CommonDao<
942
1050
  }
943
1051
 
944
1052
  async updateByIds(ids: ID[], patch: DBPatch<DBM>, opt: CommonDaoOptions = {}): Promise<number> {
1053
+ if (!ids.length) return 0
945
1054
  return await this.updateByQuery(this.query().filterIn('id', ids), patch, opt)
946
1055
  }
947
1056
 
@@ -1061,28 +1170,6 @@ export class CommonDao<
1061
1170
  return bms.map(bm => this.bmToTM(bm, opt))
1062
1171
  }
1063
1172
 
1064
- tmToBM(tm: undefined, opt?: CommonDaoOptions): undefined
1065
- tmToBM(tm?: TM, opt?: CommonDaoOptions): BM
1066
- tmToBM(tm?: TM, opt: CommonDaoOptions = {}): BM | undefined {
1067
- if (!tm) return
1068
-
1069
- // optimization: 1 validation is enough
1070
- // Validate/convert TM
1071
- // bm gets assigned to the new reference
1072
- // tm = this.validateAndConvert(tm, this.cfg.tmSchema, DBModelType.TM, opt)
1073
-
1074
- // TM > BM
1075
- const bm = this.cfg.hooks!.beforeTMToBM!(tm) as BM
1076
-
1077
- // Validate/convert BM
1078
- return this.validateAndConvert<BM>(bm, this.cfg.bmSchema, DBModelType.BM, opt)
1079
- }
1080
-
1081
- tmsToBM(tms: TM[], opt: CommonDaoOptions = {}): BM[] {
1082
- // try/catch?
1083
- return tms.map(tm => this.tmToBM(tm, opt))
1084
- }
1085
-
1086
1173
  /**
1087
1174
  * Returns *converted value*.
1088
1175
  * Validates (unless `skipValidation=true` passed).
@@ -1210,14 +1297,14 @@ export class CommonDao<
1210
1297
  this.cfg.logger?.log(`<< ${table}.${op} in ${_since(started)}`)
1211
1298
  }
1212
1299
 
1213
- protected logStarted(op: string, table: string, force = false): number {
1300
+ protected logStarted(op: string, table: string, force = false): UnixTimestampMillisNumber {
1214
1301
  if (this.cfg.logStarted || force) {
1215
1302
  this.cfg.logger?.log(`>> ${table}.${op}`)
1216
1303
  }
1217
1304
  return Date.now()
1218
1305
  }
1219
1306
 
1220
- protected logSaveStarted(op: string, items: any, table: string): number {
1307
+ protected logSaveStarted(op: string, items: any, table: string): UnixTimestampMillisNumber {
1221
1308
  if (this.cfg.logStarted) {
1222
1309
  const args: any[] = [`>> ${table}.${op}`]
1223
1310
  if (Array.isArray(items)) {
package/src/db.model.ts CHANGED
@@ -10,7 +10,6 @@ import { ObjectWithId } from '@naturalcycles/js-lib'
10
10
  */
11
11
  export type CommonDBSaveMethod = 'upsert' | 'insert' | 'update'
12
12
 
13
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
14
13
  export interface CommonDBOptions {}
15
14
 
16
15
  /**