@naturalcycles/db-lib 9.0.0 → 9.1.1

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.
@@ -191,7 +191,7 @@ function runCommonDaoTest(db, quirks = {}) {
191
191
  await dao.query().deleteByQuery();
192
192
  // Test that id, created, updated are created
193
193
  const now = (0, js_lib_1.localTimeNow)().unix();
194
- await dao.useTransaction(async (tx) => {
194
+ await dao.runInTransaction(async (tx) => {
195
195
  const row = (0, js_lib_1._omit)(item1, ['id', 'created', 'updated']);
196
196
  await tx.save(dao, row);
197
197
  });
@@ -200,14 +200,14 @@ function runCommonDaoTest(db, quirks = {}) {
200
200
  expect(loaded[0].id).toBeDefined();
201
201
  expect(loaded[0].created).toBeGreaterThanOrEqual(now);
202
202
  expect(loaded[0].updated).toBe(loaded[0].created);
203
- await dao.useTransaction(async (tx) => {
203
+ await dao.runInTransaction(async (tx) => {
204
204
  await tx.deleteById(dao, loaded[0].id);
205
205
  });
206
206
  // saveBatch [item1, 2, 3]
207
207
  // save item3 with k1: k1_mod
208
208
  // delete item2
209
209
  // remaining: item1, item3_with_k1_mod
210
- await dao.useTransaction(async (tx) => {
210
+ await dao.runInTransaction(async (tx) => {
211
211
  await tx.saveBatch(dao, items);
212
212
  await tx.save(dao, { ...items[2], k1: 'k1_mod' });
213
213
  await tx.deleteById(dao, items[1].id);
@@ -217,7 +217,7 @@ function runCommonDaoTest(db, quirks = {}) {
217
217
  (0, dbTest_1.expectMatch)(expected, rows, quirks);
218
218
  });
219
219
  test('transaction rollback', async () => {
220
- await expect(dao.useTransaction(async (tx) => {
220
+ await expect(dao.runInTransaction(async (tx) => {
221
221
  await tx.deleteById(dao, items[2].id);
222
222
  await tx.save(dao, { ...items[0], k1: 5 }); // it should fail here
223
223
  })).rejects.toThrow();
@@ -210,23 +210,23 @@ function runCommonDBTest(db, quirks = {}) {
210
210
  // save item3 with k1: k1_mod
211
211
  // delete item2
212
212
  // remaining: item1, item3_with_k1_mod
213
- const tx = await db.createTransaction();
214
- await db.saveBatch(test_model_1.TEST_TABLE, items, { tx });
215
- await db.saveBatch(test_model_1.TEST_TABLE, [{ ...items[2], k1: 'k1_mod' }], { tx });
216
- await db.deleteByIds(test_model_1.TEST_TABLE, [items[1].id], { tx });
217
- await tx.commit();
213
+ await db.runInTransaction(async (tx) => {
214
+ await tx.saveBatch(test_model_1.TEST_TABLE, items);
215
+ await tx.saveBatch(test_model_1.TEST_TABLE, [{ ...items[2], k1: 'k1_mod' }]);
216
+ await tx.deleteByIds(test_model_1.TEST_TABLE, [items[1].id]);
217
+ });
218
218
  const { rows } = await db.runQuery(queryAll());
219
219
  const expected = [items[0], { ...items[2], k1: 'k1_mod' }];
220
220
  expectMatch(expected, rows, quirks);
221
221
  });
222
222
  test('transaction rollback', async () => {
223
- // It should fail on id == null
224
223
  let err;
225
224
  try {
226
- const tx = await db.createTransaction();
227
- await db.deleteByIds(test_model_1.TEST_TABLE, [items[2].id], { tx });
228
- await db.saveBatch(test_model_1.TEST_TABLE, [{ ...items[0], k1: 5, id: null }], { tx });
229
- await tx.commit();
225
+ await db.runInTransaction(async (tx) => {
226
+ await tx.deleteByIds(test_model_1.TEST_TABLE, [items[2].id]);
227
+ // It should fail on id == null
228
+ await tx.saveBatch(test_model_1.TEST_TABLE, [{ ...items[0], k1: 5, id: null }]);
229
+ });
230
230
  }
231
231
  catch (err_) {
232
232
  err = err_;
@@ -42,16 +42,16 @@ class CommonTimeSeriesDao {
42
42
  async commitTransaction(ops) {
43
43
  if (!ops.length)
44
44
  return;
45
- const tx = await this.cfg.db.createTransaction();
46
- for (const op of ops) {
47
- const rows = op.dataPoints.map(([ts, v]) => ({
48
- id: String(ts), // Convert Number id into String id, as per CommonDB
49
- ts, // to allow querying by ts, since querying by id is not always available (Datastore is one example)
50
- v,
51
- }));
52
- await this.cfg.db.saveBatch(`${op.series}${_TIMESERIES_RAW}`, rows, { tx });
53
- }
54
- await tx.commit();
45
+ await this.cfg.db.runInTransaction(async (tx) => {
46
+ for (const op of ops) {
47
+ const rows = op.dataPoints.map(([ts, v]) => ({
48
+ id: String(ts), // Convert Number id into String id, as per CommonDB
49
+ ts, // to allow querying by ts, since querying by id is not always available (Datastore is one example)
50
+ v,
51
+ }));
52
+ await tx.saveBatch(`${op.series}${_TIMESERIES_RAW}`, rows);
53
+ }
54
+ });
55
55
  }
56
56
  async deleteById(series, tsMillis) {
57
57
  await this.deleteByIds(series, [tsMillis]);
@@ -1,7 +1,6 @@
1
1
  import { ObjectWithId } from '@naturalcycles/js-lib';
2
2
  import type { CommonDB } from '../common.db';
3
- import { CommonDBOptions, CommonDBSaveOptions, DBTransaction, RunQueryResult } from '../db.model';
4
- import { DBQuery } from '../query/dbQuery';
3
+ import { CommonDBOptions, CommonDBSaveOptions, DBTransaction } from '../db.model';
5
4
  /**
6
5
  * Optimizes the Transaction (list of DBOperations) to do less operations.
7
6
  * E.g if you save id1 first and then delete it - this function will turn it into a no-op (self-eliminate).
@@ -22,10 +21,8 @@ import { DBQuery } from '../query/dbQuery';
22
21
  export declare class FakeDBTransaction implements DBTransaction {
23
22
  protected db: CommonDB;
24
23
  constructor(db: CommonDB);
25
- commit(): Promise<void>;
26
24
  rollback(): Promise<void>;
27
25
  getByIds<ROW extends ObjectWithId>(table: string, ids: string[], opt?: CommonDBOptions): Promise<ROW[]>;
28
- runQuery<ROW extends ObjectWithId>(q: DBQuery<ROW>, opt?: CommonDBOptions): Promise<RunQueryResult<ROW>>;
29
26
  saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rows: ROW[], opt?: CommonDBSaveOptions<ROW>): Promise<void>;
30
27
  deleteByIds(table: string, ids: string[], opt?: CommonDBOptions | undefined): Promise<number>;
31
28
  }
@@ -94,16 +94,19 @@ class FakeDBTransaction {
94
94
  constructor(db) {
95
95
  this.db = db;
96
96
  }
97
- async commit() { }
97
+ // no-op
98
98
  async rollback() { }
99
99
  async getByIds(table, ids, opt) {
100
100
  return await this.db.getByIds(table, ids, opt);
101
101
  }
102
- async runQuery(q, opt) {
103
- return await this.db.runQuery(q, opt);
104
- }
102
+ // async runQuery<ROW extends ObjectWithId>(
103
+ // q: DBQuery<ROW>,
104
+ // opt?: CommonDBOptions,
105
+ // ): Promise<RunQueryResult<ROW>> {
106
+ // return await this.db.runQuery(q, opt)
107
+ // }
105
108
  async saveBatch(table, rows, opt) {
106
- return await this.db.saveBatch(table, rows, opt);
109
+ await this.db.saveBatch(table, rows, opt);
107
110
  }
108
111
  async deleteByIds(table, ids, opt) {
109
112
  return await this.db.deleteByIds(table, ids, opt);
package/package.json CHANGED
@@ -40,7 +40,7 @@
40
40
  "engines": {
41
41
  "node": ">=18.12"
42
42
  },
43
- "version": "9.0.0",
43
+ "version": "9.1.1",
44
44
  "description": "Lowest Common Denominator API to supported Databases",
45
45
  "keywords": [
46
46
  "db",
@@ -1,20 +1,15 @@
1
1
  import {
2
2
  generateJsonSchemaFromData,
3
- pMap,
4
- StringMap,
5
3
  _by,
6
4
  _deepEquals,
7
5
  _since,
8
6
  _sortBy,
9
7
  _sortObjectDeep,
10
8
  _stringMapValues,
11
- _uniq,
12
9
  JsonSchemaRootObject,
13
10
  _filterUndefinedValues,
14
11
  ObjectWithId,
15
12
  _assert,
16
- _deepCopy,
17
- _stringMapEntries,
18
13
  Saved,
19
14
  } from '@naturalcycles/js-lib'
20
15
  import { readableCreate, ReadableTyped, dimGrey } from '@naturalcycles/nodejs-lib'
@@ -22,9 +17,7 @@ import {
22
17
  BaseCommonDB,
23
18
  commonDBFullSupport,
24
19
  CommonDBSupport,
25
- DBOperation,
26
20
  DBSaveBatchOperation,
27
- DBTransaction,
28
21
  queryInMemory,
29
22
  } from '../..'
30
23
  import { CommonDB } from '../../common.db'
@@ -55,7 +48,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
55
48
  updateSaveMethod: false,
56
49
  updateByQuery: false,
57
50
  createTable: false,
58
- transactions: false,
51
+ transactions: false, // todo
59
52
  }
60
53
 
61
54
  constructor(cfg: FileDBCfg) {
@@ -228,9 +221,9 @@ export class FileDB extends BaseCommonDB implements CommonDB {
228
221
  this.logFinished(started, op)
229
222
  }
230
223
 
231
- override async createTransaction(): Promise<FileDBTransaction> {
232
- return new FileDBTransaction(this)
233
- }
224
+ // override async createTransaction(): Promise<FileDBTransaction> {
225
+ // return new FileDBTransaction(this)
226
+ // }
234
227
 
235
228
  sortRows<ROW extends ObjectWithId>(rows: ROW[]): ROW[] {
236
229
  rows = rows.map(r => _filterUndefinedValues(r))
@@ -260,14 +253,14 @@ export class FileDB extends BaseCommonDB implements CommonDB {
260
253
  }
261
254
  }
262
255
 
256
+ // todo: get back and fix it
257
+ // Implementation is optimized for loading/saving _whole files_.
258
+ /*
263
259
  export class FileDBTransaction implements DBTransaction {
264
260
  constructor(private db: FileDB) {}
265
261
 
266
262
  ops: DBOperation[] = []
267
263
 
268
- /**
269
- * Implementation is optimized for loading/saving _whole files_.
270
- */
271
264
  async commit(): Promise<void> {
272
265
  // data[table][id] => row
273
266
  const data: StringMap<StringMap<ObjectWithId>> = {}
@@ -335,3 +328,4 @@ export class FileDBTransaction implements DBTransaction {
335
328
  this.ops = []
336
329
  }
337
330
  }
331
+ */
@@ -16,7 +16,6 @@ import {
16
16
  CommonLogger,
17
17
  _deepCopy,
18
18
  _assert,
19
- _omit,
20
19
  } from '@naturalcycles/js-lib'
21
20
  import {
22
21
  bufferReviver,
@@ -37,6 +36,7 @@ import {
37
36
  DBIncrement,
38
37
  DBOperation,
39
38
  DBPatch,
39
+ DBTransactionFn,
40
40
  queryInMemory,
41
41
  } from '../..'
42
42
  import {
@@ -177,17 +177,6 @@ export class InMemoryDB implements CommonDB {
177
177
  rows: ROW[],
178
178
  opt: CommonDBSaveOptions<ROW> = {},
179
179
  ): Promise<void> {
180
- const { tx } = opt
181
- if (tx) {
182
- ;(tx as InMemoryDBTransaction).ops.push({
183
- type: 'saveBatch',
184
- table: _table,
185
- rows,
186
- opt: _omit(opt, ['tx']),
187
- })
188
- return
189
- }
190
-
191
180
  const table = this.cfg.tablesPrefix + _table
192
181
  this.data[table] ||= {}
193
182
 
@@ -216,41 +205,18 @@ export class InMemoryDB implements CommonDB {
216
205
 
217
206
  async deleteByQuery<ROW extends ObjectWithId>(
218
207
  q: DBQuery<ROW>,
219
- opt: CommonDBOptions = {},
208
+ _opt?: CommonDBOptions,
220
209
  ): Promise<number> {
221
210
  const table = this.cfg.tablesPrefix + q.table
222
211
  if (!this.data[table]) return 0
223
212
  const ids = queryInMemory(q, Object.values(this.data[table]!) as ROW[]).map(r => r.id)
224
-
225
- const { tx } = opt
226
- if (tx) {
227
- ;(tx as InMemoryDBTransaction).ops.push({
228
- type: 'deleteByIds',
229
- table: q.table,
230
- ids,
231
- opt: _omit(opt, ['tx']),
232
- })
233
- return ids.length
234
- }
235
-
236
213
  return await this.deleteByIds(q.table, ids)
237
214
  }
238
215
 
239
- async deleteByIds(_table: string, ids: string[], opt: CommonDBOptions = {}): Promise<number> {
216
+ async deleteByIds(_table: string, ids: string[], _opt?: CommonDBOptions): Promise<number> {
240
217
  const table = this.cfg.tablesPrefix + _table
241
218
  if (!this.data[table]) return 0
242
219
 
243
- const { tx } = opt
244
- if (tx) {
245
- ;(tx as InMemoryDBTransaction).ops.push({
246
- type: 'deleteByIds',
247
- table: _table,
248
- ids,
249
- opt: _omit(opt, ['tx']),
250
- })
251
- return ids.length
252
- }
253
-
254
220
  let count = 0
255
221
  ids.forEach(id => {
256
222
  if (!this.data[table]![id]) return
@@ -268,8 +234,6 @@ export class InMemoryDB implements CommonDB {
268
234
  const patchEntries = Object.entries(patch)
269
235
  if (!patchEntries.length) return 0
270
236
 
271
- // todo: can we support tx here? :thinking:
272
-
273
237
  const table = this.cfg.tablesPrefix + q.table
274
238
  const rows = queryInMemory(q, Object.values(this.data[table] || {}) as ROW[])
275
239
  rows.forEach((row: any) => {
@@ -309,8 +273,15 @@ export class InMemoryDB implements CommonDB {
309
273
  return Readable.from(queryInMemory(q, Object.values(this.data[table] || {}) as ROW[]))
310
274
  }
311
275
 
312
- async createTransaction(): Promise<DBTransaction> {
313
- return new InMemoryDBTransaction(this)
276
+ async runInTransaction(fn: DBTransactionFn): Promise<void> {
277
+ const tx = new InMemoryDBTransaction(this)
278
+ try {
279
+ await fn(tx)
280
+ await tx.commit()
281
+ } catch (err) {
282
+ await tx.rollback()
283
+ throw err
284
+ }
314
285
  }
315
286
 
316
287
  /**
@@ -394,6 +365,37 @@ export class InMemoryDBTransaction implements DBTransaction {
394
365
 
395
366
  ops: DBOperation[] = []
396
367
 
368
+ async getByIds<ROW extends ObjectWithId>(
369
+ table: string,
370
+ ids: string[],
371
+ opt?: CommonDBOptions,
372
+ ): Promise<ROW[]> {
373
+ return await this.db.getByIds(table, ids, opt)
374
+ }
375
+
376
+ async saveBatch<ROW extends Partial<ObjectWithId>>(
377
+ table: string,
378
+ rows: ROW[],
379
+ opt?: CommonDBSaveOptions<ROW>,
380
+ ): Promise<void> {
381
+ this.ops.push({
382
+ type: 'saveBatch',
383
+ table,
384
+ rows,
385
+ opt,
386
+ })
387
+ }
388
+
389
+ async deleteByIds(table: string, ids: string[], opt?: CommonDBOptions): Promise<number> {
390
+ this.ops.push({
391
+ type: 'deleteByIds',
392
+ table,
393
+ ids,
394
+ opt,
395
+ })
396
+ return ids.length
397
+ }
398
+
397
399
  async commit(): Promise<void> {
398
400
  const backup = _deepCopy(this.db.data)
399
401
 
@@ -411,6 +413,7 @@ export class InMemoryDBTransaction implements DBTransaction {
411
413
  this.ops = []
412
414
  } catch (err) {
413
415
  // rollback
416
+ this.ops = []
414
417
  this.db.data = backup
415
418
  this.db.cfg.logger!.log('InMemoryDB transaction rolled back')
416
419
 
@@ -5,7 +5,7 @@ import {
5
5
  CommonDBOptions,
6
6
  CommonDBSaveOptions,
7
7
  DBPatch,
8
- DBTransaction,
8
+ DBTransactionFn,
9
9
  RunQueryResult,
10
10
  } from './db.model'
11
11
  import { DBQuery } from './query/dbQuery'
@@ -83,7 +83,9 @@ export class BaseCommonDB implements CommonDB {
83
83
  throw new Error('deleteByIds is not implemented')
84
84
  }
85
85
 
86
- async createTransaction(): Promise<DBTransaction> {
87
- return new FakeDBTransaction(this)
86
+ async runInTransaction(fn: DBTransactionFn): Promise<void> {
87
+ const tx = new FakeDBTransaction(this)
88
+ await fn(tx)
89
+ // there's no try/catch and rollback, as there's nothing to rollback
88
90
  }
89
91
  }
package/src/common.db.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib'
2
- import { ReadableTyped } from '@naturalcycles/nodejs-lib'
2
+ import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
3
3
  import {
4
4
  CommonDBCreateOptions,
5
5
  CommonDBOptions,
6
6
  CommonDBSaveOptions,
7
7
  CommonDBStreamOptions,
8
8
  DBPatch,
9
- DBTransaction,
9
+ DBTransactionFn,
10
10
  RunQueryResult,
11
11
  } from './db.model'
12
12
  import { DBQuery } from './query/dbQuery'
@@ -159,8 +159,12 @@ export interface CommonDB {
159
159
  /**
160
160
  * Should be implemented as a Transaction (best effort), which means that
161
161
  * either ALL or NONE of the operations should be applied.
162
+ *
163
+ * Transaction is automatically committed if fn resolves normally.
164
+ * Transaction is rolled back if fn throws, the error is re-thrown in that case.
165
+ * Graceful rollback is allowed on tx.rollback()
162
166
  */
163
- createTransaction: () => Promise<DBTransaction>
167
+ runInTransaction: (fn: DBTransactionFn) => Promise<void>
164
168
  }
165
169
 
166
170
  /**
@@ -13,6 +13,7 @@ import {
13
13
  AnyObject,
14
14
  AppError,
15
15
  AsyncMapper,
16
+ CommonLogger,
16
17
  ErrorMode,
17
18
  JsonSchemaObject,
18
19
  JsonSchemaRootObject,
@@ -201,7 +202,7 @@ export class CommonDao<
201
202
  const op = `getByIds ${ids.length} id(s) (${_truncate(ids.slice(0, 10).join(', '), 50)})`
202
203
  const table = opt.table || this.cfg.table
203
204
  const started = this.logStarted(op, table)
204
- let dbms = await this.cfg.db.getByIds<DBM>(table, ids)
205
+ let dbms = await (opt.tx || this.cfg.db).getByIds<DBM>(table, ids)
205
206
  if (!opt.raw && this.cfg.hooks!.afterLoad && dbms.length) {
206
207
  dbms = (await pMap(dbms, async dbm => await this.cfg.hooks!.afterLoad!(dbm))).filter(
207
208
  _isTruthy,
@@ -900,7 +901,7 @@ export class CommonDao<
900
901
  const { excludeFromIndexes } = this.cfg
901
902
  const assignGeneratedIds = opt.assignGeneratedIds || this.cfg.assignGeneratedIds
902
903
 
903
- await this.cfg.db.saveBatch(table, dbms, {
904
+ await (opt.tx || this.cfg.db).saveBatch(table, dbms, {
904
905
  excludeFromIndexes,
905
906
  assignGeneratedIds,
906
907
  ...opt,
@@ -1050,7 +1051,7 @@ export class CommonDao<
1050
1051
  const op = `deleteByIds(${ids.join(', ')})`
1051
1052
  const table = opt.table || this.cfg.table
1052
1053
  const started = this.logStarted(op, table)
1053
- const count = await this.cfg.db.deleteByIds(table, ids, opt)
1054
+ const count = await (opt.tx || this.cfg.db).deleteByIds(table, ids, opt)
1054
1055
  this.logSaveResult(started, op, table)
1055
1056
  return count
1056
1057
  }
@@ -1330,22 +1331,17 @@ export class CommonDao<
1330
1331
  await this.cfg.db.ping()
1331
1332
  }
1332
1333
 
1333
- async useTransaction(fn: (tx: CommonDaoTransaction) => Promise<void>): Promise<void> {
1334
- const tx = await this.cfg.db.createTransaction()
1335
- const daoTx = new CommonDaoTransaction(tx)
1336
-
1337
- try {
1338
- await fn(daoTx)
1339
- await daoTx.commit()
1340
- } catch (err) {
1341
- await daoTx.rollback()
1342
- throw err
1343
- }
1344
- }
1334
+ async runInTransaction(fn: CommonDaoTransactionFn): Promise<void> {
1335
+ await this.cfg.db.runInTransaction(async tx => {
1336
+ const daoTx = new CommonDaoTransaction(tx, this.cfg.logger!)
1345
1337
 
1346
- async createTransaction(): Promise<CommonDaoTransaction> {
1347
- const tx = await this.cfg.db.createTransaction()
1348
- return new CommonDaoTransaction(tx)
1338
+ try {
1339
+ await fn(daoTx)
1340
+ } catch (err) {
1341
+ await daoTx.rollback()
1342
+ throw err
1343
+ }
1344
+ })
1349
1345
  }
1350
1346
 
1351
1347
  protected logResult(started: number, op: string, res: any, table: string): void {
@@ -1405,25 +1401,41 @@ export class CommonDao<
1405
1401
  }
1406
1402
  }
1407
1403
 
1404
+ /**
1405
+ * Transaction is committed when the function returns resolved Promise (aka "returns normally").
1406
+ *
1407
+ * Transaction is rolled back when the function returns rejected Promise (aka "throws").
1408
+ */
1409
+ export type CommonDaoTransactionFn = (tx: CommonDaoTransaction) => Promise<void>
1410
+
1411
+ /**
1412
+ * Transaction context.
1413
+ * Has similar API than CommonDao, but all operations are performed in the context of the transaction.
1414
+ */
1408
1415
  export class CommonDaoTransaction {
1409
- constructor(private tx: DBTransaction) {}
1416
+ constructor(
1417
+ private tx: DBTransaction,
1418
+ private logger: CommonLogger,
1419
+ ) {}
1410
1420
 
1411
- async commit(): Promise<void> {
1412
- await this.tx.commit()
1413
- }
1421
+ /**
1422
+ * Perform a graceful rollback without throwing/re-throwing any error.
1423
+ */
1414
1424
  async rollback(): Promise<void> {
1415
1425
  try {
1416
1426
  await this.tx.rollback()
1417
1427
  } catch (err) {
1418
- console.log(err)
1428
+ // graceful rollback without re-throw
1429
+ this.logger.error(err)
1419
1430
  }
1420
1431
  }
1421
1432
 
1422
1433
  async getById<BM extends Partial<ObjectWithId>, DBM extends ObjectWithId>(
1423
1434
  dao: CommonDao<BM, DBM, any>,
1424
- id: string,
1435
+ id?: string | null,
1425
1436
  opt?: CommonDaoOptions,
1426
1437
  ): Promise<Saved<BM> | null> {
1438
+ if (!id) return null
1427
1439
  return (await this.getByIds(dao, [id], opt))[0] || null
1428
1440
  }
1429
1441
 
@@ -1432,26 +1444,22 @@ export class CommonDaoTransaction {
1432
1444
  ids: string[],
1433
1445
  opt?: CommonDaoOptions,
1434
1446
  ): Promise<Saved<BM>[]> {
1435
- try {
1436
- return await dao.getByIds(ids, { ...opt, tx: this.tx })
1437
- } catch (err) {
1438
- await this.rollback()
1439
- throw err
1440
- }
1441
- }
1442
-
1443
- async runQuery<BM extends Partial<ObjectWithId>, DBM extends ObjectWithId>(
1444
- dao: CommonDao<BM, DBM, any>,
1445
- q: DBQuery<DBM>,
1446
- opt?: CommonDaoOptions,
1447
- ): Promise<Saved<BM>[]> {
1448
- try {
1449
- return await dao.runQuery(q, { ...opt, tx: this.tx })
1450
- } catch (err) {
1451
- await this.rollback()
1452
- throw err
1453
- }
1454
- }
1447
+ return await dao.getByIds(ids, { ...opt, tx: this.tx })
1448
+ }
1449
+
1450
+ // todo: Queries inside Transaction are not supported yet
1451
+ // async runQuery<BM extends Partial<ObjectWithId>, DBM extends ObjectWithId>(
1452
+ // dao: CommonDao<BM, DBM, any>,
1453
+ // q: DBQuery<DBM>,
1454
+ // opt?: CommonDaoOptions,
1455
+ // ): Promise<Saved<BM>[]> {
1456
+ // try {
1457
+ // return await dao.runQuery(q, { ...opt, tx: this.tx })
1458
+ // } catch (err) {
1459
+ // await this.rollback()
1460
+ // throw err
1461
+ // }
1462
+ // }
1455
1463
 
1456
1464
  async save<BM extends Partial<ObjectWithId>, DBM extends ObjectWithId>(
1457
1465
  dao: CommonDao<BM, DBM, any>,
@@ -1466,24 +1474,19 @@ export class CommonDaoTransaction {
1466
1474
  bms: Unsaved<BM>[],
1467
1475
  opt?: CommonDaoSaveBatchOptions<DBM>,
1468
1476
  ): Promise<Saved<BM>[]> {
1469
- try {
1470
- return await dao.saveBatch(bms, { ...opt, tx: this.tx })
1471
- } catch (err) {
1472
- await this.rollback()
1473
- throw err
1474
- }
1477
+ return await dao.saveBatch(bms, { ...opt, tx: this.tx })
1475
1478
  }
1476
1479
 
1477
- async deleteById(dao: CommonDao<any>, id: string, opt?: CommonDaoOptions): Promise<number> {
1480
+ async deleteById(
1481
+ dao: CommonDao<any>,
1482
+ id?: string | null,
1483
+ opt?: CommonDaoOptions,
1484
+ ): Promise<number> {
1485
+ if (!id) return 0
1478
1486
  return await this.deleteByIds(dao, [id], opt)
1479
1487
  }
1480
1488
 
1481
1489
  async deleteByIds(dao: CommonDao<any>, ids: string[], opt?: CommonDaoOptions): Promise<number> {
1482
- try {
1483
- return await dao.deleteByIds(ids, { ...opt, tx: this.tx })
1484
- } catch (err) {
1485
- await this.rollback()
1486
- throw err
1487
- }
1490
+ return await dao.deleteByIds(ids, { ...opt, tx: this.tx })
1488
1491
  }
1489
1492
  }
package/src/db.model.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ObjectWithId } from '@naturalcycles/js-lib'
2
+ import { CommonDB } from './common.db'
2
3
 
3
4
  /**
4
5
  * Similar to SQL INSERT, UPDATE.
@@ -10,8 +11,26 @@ import type { ObjectWithId } from '@naturalcycles/js-lib'
10
11
  */
11
12
  export type CommonDBSaveMethod = 'upsert' | 'insert' | 'update'
12
13
 
14
+ /**
15
+ * Transaction is committed when the function returns resolved Promise (aka "returns normally").
16
+ *
17
+ * Transaction is rolled back when the function returns rejected Promise (aka "throws").
18
+ */
19
+ export type DBTransactionFn = (tx: DBTransaction) => Promise<void>
20
+
21
+ /**
22
+ * Transaction context.
23
+ * Has similar API than CommonDB, but all operations are performed in the context of the transaction.
24
+ */
13
25
  export interface DBTransaction {
14
- commit: () => Promise<void>
26
+ getByIds: CommonDB['getByIds']
27
+ saveBatch: CommonDB['saveBatch']
28
+ deleteByIds: CommonDB['deleteByIds']
29
+
30
+ /**
31
+ * Perform a graceful rollback.
32
+ * It'll rollback the transaction and won't throw/re-throw any errors.
33
+ */
15
34
  rollback: () => Promise<void>
16
35
  }
17
36