@naturalcycles/db-lib 8.39.0 → 8.41.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.
@@ -36,7 +36,7 @@ export declare class FileDB extends BaseCommonDB implements CommonDB {
36
36
  loadFile<ROW extends ObjectWithId>(table: string): Promise<ROW[]>;
37
37
  saveFile<ROW extends ObjectWithId>(table: string, _rows: ROW[]): Promise<void>;
38
38
  saveFiles<ROW extends ObjectWithId>(ops: DBSaveBatchOperation<ROW>[]): Promise<void>;
39
- sortRows<ROW>(rows: ROW[]): ROW[];
39
+ private sortRows;
40
40
  private logStarted;
41
41
  private logFinished;
42
42
  }
@@ -14,14 +14,14 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
14
14
  cfg: CommonDaoCfg<BM, DBM, TM, ID>;
15
15
  constructor(cfg: CommonDaoCfg<BM, DBM, TM, ID>);
16
16
  create(part?: Partial<BM>, opt?: CommonDaoOptions): Saved<BM>;
17
- getById(id: undefined, opt?: CommonDaoOptions): Promise<null>;
18
- getById(id?: ID, opt?: CommonDaoOptions): Promise<Saved<BM> | null>;
17
+ getById(id: undefined | null, opt?: CommonDaoOptions): Promise<null>;
18
+ getById(id?: ID | null, opt?: CommonDaoOptions): Promise<Saved<BM> | null>;
19
19
  getByIdOrEmpty(id: ID, part?: Partial<BM>, opt?: CommonDaoOptions): Promise<Saved<BM>>;
20
20
  getByIdAsDBMOrEmpty(id: ID, part?: Partial<BM>, opt?: CommonDaoOptions): Promise<DBM>;
21
- getByIdAsDBM(id: undefined, opt?: CommonDaoOptions): Promise<null>;
22
- getByIdAsDBM(id?: ID, opt?: CommonDaoOptions): Promise<DBM | null>;
23
- getByIdAsTM(id: undefined, opt?: CommonDaoOptions): Promise<null>;
24
- getByIdAsTM(id?: ID, opt?: CommonDaoOptions): Promise<TM | null>;
21
+ getByIdAsDBM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>;
22
+ getByIdAsDBM(id?: ID | null, opt?: CommonDaoOptions): Promise<DBM | null>;
23
+ getByIdAsTM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>;
24
+ getByIdAsTM(id?: ID | null, opt?: CommonDaoOptions): Promise<TM | null>;
25
25
  getByIds(ids: ID[], opt?: CommonDaoOptions): Promise<Saved<BM>[]>;
26
26
  getByIdsAsDBM(ids: ID[], opt?: CommonDaoOptions): Promise<DBM[]>;
27
27
  requireById(id: ID, opt?: CommonDaoOptions): Promise<Saved<BM>>;
@@ -103,8 +103,8 @@ export declare class CommonDao<BM extends Partial<ObjectWithId<ID>>, DBM extends
103
103
  /**
104
104
  * @returns number of deleted items
105
105
  */
106
- deleteById(id: undefined, opt?: CommonDaoOptions): Promise<0>;
107
- deleteById(id?: ID, opt?: CommonDaoOptions): Promise<number>;
106
+ deleteById(id: undefined | null, opt?: CommonDaoOptions): Promise<0>;
107
+ deleteById(id?: ID | null, opt?: CommonDaoOptions): Promise<number>;
108
108
  deleteByIds(ids: ID[], opt?: CommonDaoOptions): Promise<number>;
109
109
  /**
110
110
  * Pass `stream: true` option to use Streaming: it will Stream the query, batch by 500, and execute
@@ -28,6 +28,7 @@ class CommonDao {
28
28
  logLevel: isGAE || isCI ? common_dao_model_1.CommonDaoLogLevel.NONE : common_dao_model_1.CommonDaoLogLevel.OPERATIONS,
29
29
  idType: 'string',
30
30
  createId: true,
31
+ assignGeneratedIds: false,
31
32
  created: true,
32
33
  updated: true,
33
34
  logger: console,
@@ -219,7 +220,7 @@ class CommonDao {
219
220
  (0, js_lib_1._assert)(q._selectedFieldNames?.length === 1, `runQuerySingleColumn requires exactly 1 column to be selected: ${q.pretty()}`);
220
221
  const col = q._selectedFieldNames[0];
221
222
  const { rows } = await this.runQueryExtended(q, opt);
222
- return rows.map(r => r[col]);
223
+ return rows.map((r) => r[col]);
223
224
  }
224
225
  /**
225
226
  * Convenience method that runs multiple queries in parallel and then merges their results together.
@@ -434,13 +435,17 @@ class CommonDao {
434
435
  }
435
436
  }
436
437
  assignIdCreatedUpdated(obj, opt = {}) {
438
+ var _a;
437
439
  const now = Math.floor(Date.now() / 1000);
438
440
  obj.id || (obj.id = this.cfg.hooks.createId?.(obj));
439
441
  if (this.cfg.created) {
440
- obj['created'] || (obj['created'] = obj['updated'] || now);
442
+ ;
443
+ (_a = obj)['created'] || (_a['created'] = obj['updated'] || now);
441
444
  }
442
445
  if (this.cfg.updated) {
443
- obj['updated'] = opt.preserveUpdatedCreated && obj['updated'] ? obj['updated'] : now;
446
+ ;
447
+ obj['updated'] =
448
+ opt.preserveUpdatedCreated && obj['updated'] ? obj['updated'] : now;
444
449
  }
445
450
  return obj;
446
451
  }
@@ -450,7 +455,7 @@ class CommonDao {
450
455
  */
451
456
  async save(bm, opt = {}) {
452
457
  this.requireWriteAccess();
453
- const idWasGenerated = !bm.id;
458
+ const idWasGenerated = !bm.id && this.cfg.createId;
454
459
  this.assignIdCreatedUpdated(bm, opt); // mutates
455
460
  const dbm = await this.bmToDBM(bm, opt);
456
461
  const table = opt.table || this.cfg.table;
@@ -461,10 +466,16 @@ class CommonDao {
461
466
  }
462
467
  const op = `save(${dbm.id})`;
463
468
  const started = this.logSaveStarted(op, bm, table);
469
+ const { excludeFromIndexes } = this.cfg;
470
+ const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
464
471
  await this.cfg.db.saveBatch(table, [dbm], {
465
- excludeFromIndexes: this.cfg.excludeFromIndexes,
472
+ excludeFromIndexes,
473
+ assignGeneratedIds,
466
474
  ...opt,
467
475
  });
476
+ if (assignGeneratedIds) {
477
+ bm.id = dbm.id;
478
+ }
468
479
  this.logSaveResult(started, op, table);
469
480
  return bm;
470
481
  }
@@ -495,24 +506,31 @@ class CommonDao {
495
506
  const table = opt.table || this.cfg.table;
496
507
  // assigning id in case it misses the id
497
508
  // will override/set `updated` field, unless opts.preserveUpdated is set
509
+ let row = dbm;
498
510
  if (!opt.raw) {
499
- const idWasGenerated = !dbm.id;
511
+ const idWasGenerated = !dbm.id && this.cfg.createId;
500
512
  this.assignIdCreatedUpdated(dbm, opt); // mutates
501
- dbm = this.anyToDBM(dbm, opt);
513
+ row = this.anyToDBM(dbm, opt);
502
514
  if (opt.ensureUniqueId && idWasGenerated)
503
- await this.ensureUniqueId(table, dbm);
515
+ await this.ensureUniqueId(table, row);
504
516
  }
505
517
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
506
518
  opt = { ...opt, saveMethod: 'insert' };
507
519
  }
508
- const op = `saveAsDBM(${dbm.id})`;
509
- const started = this.logSaveStarted(op, dbm, table);
510
- await this.cfg.db.saveBatch(table, [dbm], {
511
- excludeFromIndexes: this.cfg.excludeFromIndexes,
520
+ const op = `saveAsDBM(${row.id})`;
521
+ const started = this.logSaveStarted(op, row, table);
522
+ const { excludeFromIndexes } = this.cfg;
523
+ const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
524
+ await this.cfg.db.saveBatch(table, [row], {
525
+ excludeFromIndexes,
526
+ assignGeneratedIds,
512
527
  ...opt,
513
528
  });
529
+ if (assignGeneratedIds) {
530
+ dbm.id = row.id;
531
+ }
514
532
  this.logSaveResult(started, op, table);
515
- return dbm;
533
+ return row;
516
534
  }
517
535
  async saveBatch(bms, opt = {}) {
518
536
  this.requireWriteAccess();
@@ -529,36 +547,49 @@ class CommonDao {
529
547
  .map(bm => bm.id)
530
548
  .join(', '), 50)})`;
531
549
  const started = this.logSaveStarted(op, bms, table);
550
+ const { excludeFromIndexes } = this.cfg;
551
+ const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
532
552
  await this.cfg.db.saveBatch(table, dbms, {
533
- excludeFromIndexes: this.cfg.excludeFromIndexes,
553
+ excludeFromIndexes,
554
+ assignGeneratedIds,
534
555
  ...opt,
535
556
  });
557
+ if (assignGeneratedIds) {
558
+ dbms.forEach((dbm, i) => (bms[i].id = dbm.id));
559
+ }
536
560
  this.logSaveResult(started, op, table);
537
561
  return bms;
538
562
  }
539
563
  async saveBatchAsDBM(dbms, opt = {}) {
540
564
  this.requireWriteAccess();
541
565
  const table = opt.table || this.cfg.table;
566
+ let rows = dbms;
542
567
  if (!opt.raw) {
543
568
  dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt)); // mutates
544
- dbms = this.anyToDBMs(dbms, opt);
569
+ rows = this.anyToDBMs(dbms, opt);
545
570
  if (opt.ensureUniqueId)
546
571
  throw new js_lib_1.AppError('ensureUniqueId is not supported in saveBatch');
547
572
  }
548
573
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
549
574
  opt = { ...opt, saveMethod: 'insert' };
550
575
  }
551
- const op = `saveBatchAsDBM ${dbms.length} row(s) (${(0, js_lib_1._truncate)(dbms
576
+ const op = `saveBatchAsDBM ${rows.length} row(s) (${(0, js_lib_1._truncate)(rows
552
577
  .slice(0, 10)
553
578
  .map(bm => bm.id)
554
579
  .join(', '), 50)})`;
555
- const started = this.logSaveStarted(op, dbms, table);
556
- await this.cfg.db.saveBatch(table, dbms, {
557
- excludeFromIndexes: this.cfg.excludeFromIndexes,
580
+ const started = this.logSaveStarted(op, rows, table);
581
+ const { excludeFromIndexes } = this.cfg;
582
+ const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds;
583
+ await this.cfg.db.saveBatch(table, rows, {
584
+ excludeFromIndexes,
585
+ assignGeneratedIds,
558
586
  ...opt,
559
587
  });
588
+ if (assignGeneratedIds) {
589
+ rows.forEach((row, i) => (dbms[i].id = row.id));
590
+ }
560
591
  this.logSaveResult(started, op, table);
561
- return dbms;
592
+ return rows;
562
593
  }
563
594
  async deleteById(id, opt = {}) {
564
595
  if (!id)
@@ -86,6 +86,11 @@ export interface CommonDaoCfg<BM extends Partial<ObjectWithId<ID>>, DBM extends
86
86
  * Useful e.g when your DB is generating ids by itself (e.g mysql auto_increment).
87
87
  */
88
88
  createId?: boolean;
89
+ /**
90
+ * See the same option in CommonDB.
91
+ * Defaults to false normally.
92
+ */
93
+ assignGeneratedIds?: boolean;
89
94
  /**
90
95
  * Defaults to true
91
96
  * Set to false to disable `created` field management.
@@ -19,6 +19,13 @@ export interface CommonDBSaveOptions<ROW extends Partial<ObjectWithId> = AnyObje
19
19
  * Default is `upsert`
20
20
  */
21
21
  saveMethod?: CommonDBSaveMethod;
22
+ /**
23
+ * Only applicable to tables where id is "auto-generated by DB", e.g `auto_increment` in MySQL.
24
+ * By default it's false, so, auto-generated id will NOT be assigned/returned.
25
+ * Setting it to true will assign and return auto-generated id (on all rows, one by one).
26
+ * It's not true by default, because getting auto-generated id incurs an overhead of doing extra call (e.g LAST_INSERT_ID() in MySQL).
27
+ */
28
+ assignGeneratedIds?: boolean;
22
29
  }
23
30
  export declare type CommonDBStreamOptions = CommonDBOptions;
24
31
  export interface CommonDBCreateOptions extends CommonDBOptions {
@@ -41,7 +41,7 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
41
41
  // CREATE TABLE, DROP
42
42
  if (createTable) {
43
43
  test('createTable, dropIfExists=true', async () => {
44
- await dao.createTable(test_model_1.testItemDBMJsonSchema.build(), { dropIfExists: true });
44
+ await dao.createTable(test_model_1.testItemDBMJsonSchema, { dropIfExists: true });
45
45
  });
46
46
  }
47
47
  if (querying) {
@@ -55,7 +55,7 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
55
55
  if (eventualConsistencyDelay)
56
56
  await (0, js_lib_1.pDelay)(eventualConsistencyDelay);
57
57
  expect(await dao.query().runQuery()).toEqual([]);
58
- expect(await dao.query().runQueryCount()).toEqual(0);
58
+ expect(await dao.query().runQueryCount()).toBe(0);
59
59
  });
60
60
  }
61
61
  // GET empty
@@ -81,7 +81,7 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
81
81
  await dao.save(item3);
82
82
  const item3Loaded = await dao.requireById(item3.id);
83
83
  (0, dbTest_1.expectMatch)([item3], [item3Loaded], quirks);
84
- expect(item3Loaded.k2).toBe(null);
84
+ expect(item3Loaded.k2).toBeNull();
85
85
  expect(Object.keys(item3)).toContain('k2');
86
86
  expect(item3.k2).toBeNull();
87
87
  });
@@ -98,7 +98,7 @@ function runCommonDaoTest(db, features = {}, quirks = {}) {
98
98
  expected.updated = item3.updated; // as it's mutated
99
99
  const item3Loaded = await dao.requireById(item3.id);
100
100
  (0, dbTest_1.expectMatch)([expected], [item3Loaded], quirks);
101
- expect(item3Loaded.k2).toBe(undefined);
101
+ expect(item3Loaded.k2).toBeUndefined();
102
102
  expect(Object.keys(item3Loaded)).not.toContain('k2');
103
103
  expect(Object.keys(item3)).toContain('k2');
104
104
  expect(item3.k2).toBeUndefined();
@@ -28,7 +28,7 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
28
28
  // CREATE TABLE, DROP
29
29
  if (createTable) {
30
30
  test('createTable, dropIfExists=true', async () => {
31
- await db.createTable(test_model_1.TEST_TABLE, test_model_1.testItemDBMJsonSchema.build(), { dropIfExists: true });
31
+ await db.createTable(test_model_1.TEST_TABLE, test_model_1.testItemDBMJsonSchema, { dropIfExists: true });
32
32
  });
33
33
  }
34
34
  if (querying) {
@@ -42,7 +42,7 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
42
42
  if (eventualConsistencyDelay)
43
43
  await (0, js_lib_1.pDelay)(eventualConsistencyDelay);
44
44
  expect((await db.runQuery(queryAll())).rows).toEqual([]);
45
- expect(await db.runQueryCount(queryAll())).toEqual(0);
45
+ expect(await db.runQueryCount(queryAll())).toBe(0);
46
46
  });
47
47
  }
48
48
  // GET empty
@@ -68,7 +68,7 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
68
68
  await db.saveBatch(test_model_1.TEST_TABLE, [item3]);
69
69
  const item3Loaded = (await db.getByIds(test_model_1.TEST_TABLE, [item3.id]))[0];
70
70
  expectMatch([item3], [item3Loaded], quirks);
71
- expect(item3Loaded.k2).toBe(null);
71
+ expect(item3Loaded.k2).toBeNull();
72
72
  });
73
73
  }
74
74
  if (documentDB) {
@@ -83,7 +83,7 @@ function runCommonDBTest(db, features = {}, quirks = {}) {
83
83
  await db.saveBatch(test_model_1.TEST_TABLE, [item3]);
84
84
  const item3Loaded = (await db.getByIds(test_model_1.TEST_TABLE, [item3.id]))[0];
85
85
  expectMatch([expected], [item3Loaded], quirks);
86
- expect(item3Loaded.k2).toBe(undefined);
86
+ expect(item3Loaded.k2).toBeUndefined();
87
87
  expect(Object.keys(item3Loaded)).not.toContain('k2');
88
88
  });
89
89
  }
@@ -17,8 +17,8 @@ export interface TestItemTM {
17
17
  export declare const testItemBMSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<TestItemBM, TestItemBM>;
18
18
  export declare const testItemDBMSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<TestItemDBM, TestItemDBM>;
19
19
  export declare const testItemTMSchema: import("@naturalcycles/nodejs-lib").ObjectSchemaTyped<TestItemTM, TestItemTM>;
20
- export declare const testItemBMJsonSchema: import("@naturalcycles/js-lib/dist/json-schema/jsonSchemaBuilder").JsonSchemaObjectBuilder<TestItemBM & Partial<import("@naturalcycles/js-lib").SavedDBEntity<string>>>;
21
- export declare const testItemDBMJsonSchema: import("@naturalcycles/js-lib/dist/json-schema/jsonSchemaBuilder").JsonSchemaObjectBuilder<TestItemDBM>;
20
+ export declare const testItemBMJsonSchema: import("@naturalcycles/js-lib").JsonSchemaObject<TestItemBM & Partial<import("@naturalcycles/js-lib").SavedDBEntity<string>>>;
21
+ export declare const testItemDBMJsonSchema: import("@naturalcycles/js-lib").JsonSchemaObject<TestItemDBM>;
22
22
  export declare function createTestItemDBM(num?: number): TestItemDBM;
23
23
  export declare function createTestItemBM(num?: number): Saved<TestItemBM>;
24
24
  export declare function createTestItemsDBM(count?: number): TestItemDBM[];
@@ -31,8 +31,10 @@ exports.testItemBMJsonSchema = js_lib_1.jsonSchema
31
31
  even: js_lib_1.jsonSchema.boolean().optional(),
32
32
  b1: js_lib_1.jsonSchema.buffer().optional(),
33
33
  })
34
- .baseDBEntity();
35
- exports.testItemDBMJsonSchema = js_lib_1.jsonSchema.rootObject({
34
+ .baseDBEntity()
35
+ .build();
36
+ exports.testItemDBMJsonSchema = js_lib_1.jsonSchema
37
+ .rootObject({
36
38
  // todo: figure out how to not copy-paste these 3 fields
37
39
  id: js_lib_1.jsonSchema.string(),
38
40
  created: js_lib_1.jsonSchema.unixTimestamp(),
@@ -42,7 +44,8 @@ exports.testItemDBMJsonSchema = js_lib_1.jsonSchema.rootObject({
42
44
  k3: js_lib_1.jsonSchema.number().optional(),
43
45
  even: js_lib_1.jsonSchema.boolean().optional(),
44
46
  b1: js_lib_1.jsonSchema.buffer().optional(),
45
- });
47
+ })
48
+ .build();
46
49
  function createTestItemDBM(num = 1) {
47
50
  return {
48
51
  id: `id${num}`,
package/package.json CHANGED
@@ -10,9 +10,9 @@
10
10
  },
11
11
  "devDependencies": {
12
12
  "@naturalcycles/bench-lib": "^1.0.0",
13
- "@naturalcycles/dev-lib": "^12.0.1",
14
- "@types/node": "^17.0.0",
15
- "jest": "^28.0.3",
13
+ "@naturalcycles/dev-lib": "^13.0.0",
14
+ "@types/node": "^18.0.3",
15
+ "jest": "^29.0.0",
16
16
  "weak-napi": "^2.0.2"
17
17
  },
18
18
  "files": [
@@ -42,7 +42,7 @@
42
42
  "engines": {
43
43
  "node": ">=14.15"
44
44
  },
45
- "version": "8.39.0",
45
+ "version": "8.41.0",
46
46
  "description": "Lowest Common Denominator API to supported Databases",
47
47
  "keywords": [
48
48
  "db",
@@ -256,11 +256,11 @@ export class FileDB extends BaseCommonDB implements CommonDB {
256
256
  this.logFinished(started, op)
257
257
  }
258
258
 
259
- sortRows<ROW>(rows: ROW[]): ROW[] {
259
+ private sortRows<ROW extends ObjectWithId>(rows: ROW[]): ROW[] {
260
260
  rows = rows.map(r => _filterUndefinedValues(r))
261
261
 
262
262
  if (this.cfg.sortOnSave) {
263
- _sortBy(rows, r => r[this.cfg.sortOnSave!.name], true)
263
+ _sortBy(rows, r => r[this.cfg.sortOnSave!.name as keyof ROW], true)
264
264
  if (this.cfg.sortOnSave.descending) rows.reverse() // mutates
265
265
  }
266
266
 
@@ -119,6 +119,12 @@ export interface CommonDaoCfg<
119
119
  */
120
120
  createId?: boolean
121
121
 
122
+ /**
123
+ * See the same option in CommonDB.
124
+ * Defaults to false normally.
125
+ */
126
+ assignGeneratedIds?: boolean
127
+
122
128
  /**
123
129
  * Defaults to true
124
130
  * Set to false to disable `created` field management.
@@ -73,6 +73,7 @@ export class CommonDao<
73
73
  logLevel: isGAE || isCI ? CommonDaoLogLevel.NONE : CommonDaoLogLevel.OPERATIONS,
74
74
  idType: 'string',
75
75
  createId: true,
76
+ assignGeneratedIds: false,
76
77
  created: true,
77
78
  updated: true,
78
79
  logger: console,
@@ -83,7 +84,7 @@ export class CommonDao<
83
84
  beforeDBMValidate: dbm => dbm,
84
85
  beforeDBMToBM: dbm => dbm as any,
85
86
  beforeBMToDBM: bm => bm as any,
86
- beforeTMToBM: tm => tm,
87
+ beforeTMToBM: tm => tm as any,
87
88
  beforeBMToTM: bm => bm as any,
88
89
  anonymize: dbm => dbm,
89
90
  onValidationError: err => err,
@@ -111,9 +112,9 @@ export class CommonDao<
111
112
  }
112
113
 
113
114
  // GET
114
- async getById(id: undefined, opt?: CommonDaoOptions): Promise<null>
115
- async getById(id?: ID, opt?: CommonDaoOptions): Promise<Saved<BM> | null>
116
- async getById(id?: ID, opt: CommonDaoOptions = {}): Promise<Saved<BM> | null> {
115
+ async getById(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
116
+ async getById(id?: ID | null, opt?: CommonDaoOptions): Promise<Saved<BM> | null>
117
+ async getById(id?: ID | null, opt: CommonDaoOptions = {}): Promise<Saved<BM> | null> {
117
118
  if (!id) return null
118
119
  const op = `getById(${id})`
119
120
  const table = opt.table || this.cfg.table
@@ -153,9 +154,9 @@ export class CommonDao<
153
154
  return await this.bmToDBM(bm, opt)
154
155
  }
155
156
 
156
- async getByIdAsDBM(id: undefined, opt?: CommonDaoOptions): Promise<null>
157
- async getByIdAsDBM(id?: ID, opt?: CommonDaoOptions): Promise<DBM | null>
158
- async getByIdAsDBM(id?: ID, opt: CommonDaoOptions = {}): Promise<DBM | null> {
157
+ async getByIdAsDBM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
158
+ async getByIdAsDBM(id?: ID | null, opt?: CommonDaoOptions): Promise<DBM | null>
159
+ async getByIdAsDBM(id?: ID | null, opt: CommonDaoOptions = {}): Promise<DBM | null> {
159
160
  if (!id) return null
160
161
  const op = `getByIdAsDBM(${id})`
161
162
  const table = opt.table || this.cfg.table
@@ -168,9 +169,9 @@ export class CommonDao<
168
169
  return dbm || null
169
170
  }
170
171
 
171
- async getByIdAsTM(id: undefined, opt?: CommonDaoOptions): Promise<null>
172
- async getByIdAsTM(id?: ID, opt?: CommonDaoOptions): Promise<TM | null>
173
- async getByIdAsTM(id?: ID, opt: CommonDaoOptions = {}): Promise<TM | null> {
172
+ async getByIdAsTM(id: undefined | null, opt?: CommonDaoOptions): Promise<null>
173
+ async getByIdAsTM(id?: ID | null, opt?: CommonDaoOptions): Promise<TM | null>
174
+ async getByIdAsTM(id?: ID | null, opt: CommonDaoOptions = {}): Promise<TM | null> {
174
175
  if (!id) return null
175
176
  const op = `getByIdAsTM(${id})`
176
177
  const table = opt.table || this.cfg.table
@@ -301,7 +302,7 @@ export class CommonDao<
301
302
  const col = q._selectedFieldNames[0]!
302
303
 
303
304
  const { rows } = await this.runQueryExtended(q, opt)
304
- return rows.map(r => r[col as any])
305
+ return rows.map((r: any) => r[col])
305
306
  }
306
307
 
307
308
  /**
@@ -593,11 +594,12 @@ export class CommonDao<
593
594
  obj.id ||= this.cfg.hooks!.createId?.(obj as BM)
594
595
 
595
596
  if (this.cfg.created) {
596
- obj['created'] ||= obj['updated'] || now
597
+ ;(obj as any)['created'] ||= (obj as any)['updated'] || now
597
598
  }
598
599
 
599
600
  if (this.cfg.updated) {
600
- obj['updated'] = opt.preserveUpdatedCreated && obj['updated'] ? obj['updated'] : now
601
+ ;(obj as any)['updated'] =
602
+ opt.preserveUpdatedCreated && (obj as any)['updated'] ? (obj as any)['updated'] : now
601
603
  }
602
604
 
603
605
  return obj as any
@@ -609,7 +611,7 @@ export class CommonDao<
609
611
  */
610
612
  async save(bm: Unsaved<BM>, opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>> {
611
613
  this.requireWriteAccess()
612
- const idWasGenerated = !bm.id
614
+ const idWasGenerated = !bm.id && this.cfg.createId
613
615
  this.assignIdCreatedUpdated(bm, opt) // mutates
614
616
  const dbm = await this.bmToDBM(bm as BM, opt)
615
617
  const table = opt.table || this.cfg.table
@@ -619,11 +621,18 @@ export class CommonDao<
619
621
  }
620
622
  const op = `save(${dbm.id})`
621
623
  const started = this.logSaveStarted(op, bm, table)
624
+ const { excludeFromIndexes } = this.cfg
625
+ const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
622
626
  await this.cfg.db.saveBatch(table, [dbm], {
623
- excludeFromIndexes: this.cfg.excludeFromIndexes,
627
+ excludeFromIndexes,
628
+ assignGeneratedIds,
624
629
  ...opt,
625
630
  })
626
631
 
632
+ if (assignGeneratedIds) {
633
+ bm.id = dbm.id as any
634
+ }
635
+
627
636
  this.logSaveResult(started, op, table)
628
637
  return bm as any
629
638
  }
@@ -666,23 +675,32 @@ export class CommonDao<
666
675
 
667
676
  // assigning id in case it misses the id
668
677
  // will override/set `updated` field, unless opts.preserveUpdated is set
678
+ let row = dbm
669
679
  if (!opt.raw) {
670
- const idWasGenerated = !dbm.id
680
+ const idWasGenerated = !dbm.id && this.cfg.createId
671
681
  this.assignIdCreatedUpdated(dbm, opt) // mutates
672
- dbm = this.anyToDBM(dbm, opt)
673
- if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, dbm)
682
+ row = this.anyToDBM(dbm, opt)
683
+ if (opt.ensureUniqueId && idWasGenerated) await this.ensureUniqueId(table, row)
674
684
  }
675
685
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
676
686
  opt = { ...opt, saveMethod: 'insert' }
677
687
  }
678
- const op = `saveAsDBM(${dbm.id})`
679
- const started = this.logSaveStarted(op, dbm, table)
680
- await this.cfg.db.saveBatch(table, [dbm], {
681
- excludeFromIndexes: this.cfg.excludeFromIndexes,
688
+ const op = `saveAsDBM(${row.id})`
689
+ const started = this.logSaveStarted(op, row, table)
690
+ const { excludeFromIndexes } = this.cfg
691
+ const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
692
+ await this.cfg.db.saveBatch(table, [row], {
693
+ excludeFromIndexes,
694
+ assignGeneratedIds,
682
695
  ...opt,
683
696
  })
697
+
698
+ if (assignGeneratedIds) {
699
+ dbm.id = row.id
700
+ }
701
+
684
702
  this.logSaveResult(started, op, table)
685
- return dbm
703
+ return row
686
704
  }
687
705
 
688
706
  async saveBatch(bms: Unsaved<BM>[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<Saved<BM>[]> {
@@ -703,12 +721,19 @@ export class CommonDao<
703
721
  50,
704
722
  )})`
705
723
  const started = this.logSaveStarted(op, bms, table)
724
+ const { excludeFromIndexes } = this.cfg
725
+ const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
706
726
 
707
727
  await this.cfg.db.saveBatch(table, dbms, {
708
- excludeFromIndexes: this.cfg.excludeFromIndexes,
728
+ excludeFromIndexes,
729
+ assignGeneratedIds,
709
730
  ...opt,
710
731
  })
711
732
 
733
+ if (assignGeneratedIds) {
734
+ dbms.forEach((dbm, i) => (bms[i]!.id = dbm.id as any))
735
+ }
736
+
712
737
  this.logSaveResult(started, op, table)
713
738
 
714
739
  return bms as any[]
@@ -717,39 +742,47 @@ export class CommonDao<
717
742
  async saveBatchAsDBM(dbms: DBM[], opt: CommonDaoSaveOptions<DBM> = {}): Promise<DBM[]> {
718
743
  this.requireWriteAccess()
719
744
  const table = opt.table || this.cfg.table
745
+ let rows = dbms
720
746
  if (!opt.raw) {
721
747
  dbms.forEach(dbm => this.assignIdCreatedUpdated(dbm, opt)) // mutates
722
- dbms = this.anyToDBMs(dbms, opt)
748
+ rows = this.anyToDBMs(dbms, opt)
723
749
  if (opt.ensureUniqueId) throw new AppError('ensureUniqueId is not supported in saveBatch')
724
750
  }
725
751
  if (this.cfg.immutable && !opt.allowMutability && !opt.saveMethod) {
726
752
  opt = { ...opt, saveMethod: 'insert' }
727
753
  }
728
- const op = `saveBatchAsDBM ${dbms.length} row(s) (${_truncate(
729
- dbms
754
+ const op = `saveBatchAsDBM ${rows.length} row(s) (${_truncate(
755
+ rows
730
756
  .slice(0, 10)
731
757
  .map(bm => bm.id)
732
758
  .join(', '),
733
759
  50,
734
760
  )})`
735
- const started = this.logSaveStarted(op, dbms, table)
761
+ const started = this.logSaveStarted(op, rows, table)
762
+ const { excludeFromIndexes } = this.cfg
763
+ const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
736
764
 
737
- await this.cfg.db.saveBatch(table, dbms, {
738
- excludeFromIndexes: this.cfg.excludeFromIndexes,
765
+ await this.cfg.db.saveBatch(table, rows, {
766
+ excludeFromIndexes,
767
+ assignGeneratedIds,
739
768
  ...opt,
740
769
  })
741
770
 
771
+ if (assignGeneratedIds) {
772
+ rows.forEach((row, i) => (dbms[i]!.id = row.id))
773
+ }
774
+
742
775
  this.logSaveResult(started, op, table)
743
- return dbms
776
+ return rows
744
777
  }
745
778
 
746
779
  // DELETE
747
780
  /**
748
781
  * @returns number of deleted items
749
782
  */
750
- async deleteById(id: undefined, opt?: CommonDaoOptions): Promise<0>
751
- async deleteById(id?: ID, opt?: CommonDaoOptions): Promise<number>
752
- async deleteById(id?: ID, opt: CommonDaoOptions = {}): Promise<number> {
783
+ async deleteById(id: undefined | null, opt?: CommonDaoOptions): Promise<0>
784
+ async deleteById(id?: ID | null, opt?: CommonDaoOptions): Promise<number>
785
+ async deleteById(id?: ID | null, opt: CommonDaoOptions = {}): Promise<number> {
753
786
  if (!id) return 0
754
787
  this.requireWriteAccess()
755
788
  this.requireObjectMutability(opt)
package/src/db.model.ts CHANGED
@@ -24,6 +24,14 @@ export interface CommonDBSaveOptions<ROW extends Partial<ObjectWithId> = AnyObje
24
24
  * Default is `upsert`
25
25
  */
26
26
  saveMethod?: CommonDBSaveMethod
27
+
28
+ /**
29
+ * Only applicable to tables where id is "auto-generated by DB", e.g `auto_increment` in MySQL.
30
+ * By default it's false, so, auto-generated id will NOT be assigned/returned.
31
+ * Setting it to true will assign and return auto-generated id (on all rows, one by one).
32
+ * It's not true by default, because getting auto-generated id incurs an overhead of doing extra call (e.g LAST_INSERT_ID() in MySQL).
33
+ */
34
+ assignGeneratedIds?: boolean
27
35
  }
28
36
 
29
37
  export type CommonDBStreamOptions = CommonDBOptions
@@ -186,8 +186,8 @@ export class DBQuery<ROW extends ObjectWithId = AnyObjectWithId> {
186
186
  }
187
187
 
188
188
  tokens.push(
189
- ...this._filters.map(f => `${f.name}${f.op}${f.val}`),
190
- ...this._orders.map(o => `order by ${o.name}${o.descending ? ' desc' : ''}`),
189
+ ...this._filters.map(f => `${f.name as string}${f.op}${f.val}`),
190
+ ...this._orders.map(o => `order by ${o.name as string}${o.descending ? ' desc' : ''}`),
191
191
  )
192
192
 
193
193
  if (this._groupByFieldNames) {
@@ -66,7 +66,7 @@ export function runCommonDaoTest(
66
66
  // CREATE TABLE, DROP
67
67
  if (createTable) {
68
68
  test('createTable, dropIfExists=true', async () => {
69
- await dao.createTable(testItemDBMJsonSchema.build(), { dropIfExists: true })
69
+ await dao.createTable(testItemDBMJsonSchema, { dropIfExists: true })
70
70
  })
71
71
  }
72
72
 
@@ -84,7 +84,7 @@ export function runCommonDaoTest(
84
84
  test('runQuery(all), runQueryCount should return empty', async () => {
85
85
  if (eventualConsistencyDelay) await pDelay(eventualConsistencyDelay)
86
86
  expect(await dao.query().runQuery()).toEqual([])
87
- expect(await dao.query().runQueryCount()).toEqual(0)
87
+ expect(await dao.query().runQueryCount()).toBe(0)
88
88
  })
89
89
  }
90
90
 
@@ -114,7 +114,7 @@ export function runCommonDaoTest(
114
114
  await dao.save(item3)
115
115
  const item3Loaded = await dao.requireById(item3.id)
116
116
  expectMatch([item3], [item3Loaded], quirks)
117
- expect(item3Loaded.k2).toBe(null)
117
+ expect(item3Loaded.k2).toBeNull()
118
118
  expect(Object.keys(item3)).toContain('k2')
119
119
  expect(item3.k2).toBeNull()
120
120
  })
@@ -135,7 +135,7 @@ export function runCommonDaoTest(
135
135
 
136
136
  const item3Loaded = await dao.requireById(item3.id)
137
137
  expectMatch([expected], [item3Loaded], quirks)
138
- expect(item3Loaded.k2).toBe(undefined)
138
+ expect(item3Loaded.k2).toBeUndefined()
139
139
  expect(Object.keys(item3Loaded)).not.toContain('k2')
140
140
  expect(Object.keys(item3)).toContain('k2')
141
141
  expect(item3.k2).toBeUndefined()
@@ -110,7 +110,7 @@ export function runCommonDBTest(
110
110
  // CREATE TABLE, DROP
111
111
  if (createTable) {
112
112
  test('createTable, dropIfExists=true', async () => {
113
- await db.createTable(TEST_TABLE, testItemDBMJsonSchema.build(), { dropIfExists: true })
113
+ await db.createTable(TEST_TABLE, testItemDBMJsonSchema, { dropIfExists: true })
114
114
  })
115
115
  }
116
116
 
@@ -128,7 +128,7 @@ export function runCommonDBTest(
128
128
  test('runQuery(all), runQueryCount should return empty', async () => {
129
129
  if (eventualConsistencyDelay) await pDelay(eventualConsistencyDelay)
130
130
  expect((await db.runQuery(queryAll())).rows).toEqual([])
131
- expect(await db.runQueryCount(queryAll())).toEqual(0)
131
+ expect(await db.runQueryCount(queryAll())).toBe(0)
132
132
  })
133
133
  }
134
134
 
@@ -158,7 +158,7 @@ export function runCommonDBTest(
158
158
  await db.saveBatch(TEST_TABLE, [item3])
159
159
  const item3Loaded = (await db.getByIds<TestItemDBM>(TEST_TABLE, [item3.id]))[0]!
160
160
  expectMatch([item3], [item3Loaded], quirks)
161
- expect(item3Loaded.k2).toBe(null)
161
+ expect(item3Loaded.k2).toBeNull()
162
162
  })
163
163
  }
164
164
 
@@ -175,7 +175,7 @@ export function runCommonDBTest(
175
175
  await db.saveBatch(TEST_TABLE, [item3])
176
176
  const item3Loaded = (await db.getByIds<TestItemDBM>(TEST_TABLE, [item3.id]))[0]!
177
177
  expectMatch([expected], [item3Loaded], quirks)
178
- expect(item3Loaded.k2).toBe(undefined)
178
+ expect(item3Loaded.k2).toBeUndefined()
179
179
  expect(Object.keys(item3Loaded)).not.toContain('k2')
180
180
  })
181
181
  }
@@ -58,18 +58,21 @@ export const testItemBMJsonSchema = jsonSchema
58
58
  b1: jsonSchema.buffer().optional(),
59
59
  })
60
60
  .baseDBEntity()
61
+ .build()
61
62
 
62
- export const testItemDBMJsonSchema = jsonSchema.rootObject<TestItemDBM>({
63
- // todo: figure out how to not copy-paste these 3 fields
64
- id: jsonSchema.string(),
65
- created: jsonSchema.unixTimestamp(),
66
- updated: jsonSchema.unixTimestamp(),
67
- k1: jsonSchema.string(),
68
- k2: jsonSchema.string().optional(),
69
- k3: jsonSchema.number().optional(),
70
- even: jsonSchema.boolean().optional(),
71
- b1: jsonSchema.buffer().optional(),
72
- })
63
+ export const testItemDBMJsonSchema = jsonSchema
64
+ .rootObject<TestItemDBM>({
65
+ // todo: figure out how to not copy-paste these 3 fields
66
+ id: jsonSchema.string(),
67
+ created: jsonSchema.unixTimestamp(),
68
+ updated: jsonSchema.unixTimestamp(),
69
+ k1: jsonSchema.string(),
70
+ k2: jsonSchema.string().optional(),
71
+ k3: jsonSchema.number().optional(),
72
+ even: jsonSchema.boolean().optional(),
73
+ b1: jsonSchema.buffer().optional(),
74
+ })
75
+ .build()
73
76
 
74
77
  export function createTestItemDBM(num = 1): TestItemDBM {
75
78
  return {