@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.
- package/dist/adapter/file/file.db.d.ts +1 -12
- package/dist/adapter/file/file.db.js +79 -69
- package/dist/adapter/inmemory/inMemory.db.d.ts +7 -4
- package/dist/adapter/inmemory/inMemory.db.js +33 -38
- package/dist/base.common.db.d.ts +2 -2
- package/dist/base.common.db.js +4 -2
- package/dist/common.db.d.ts +7 -3
- package/dist/commondao/common.dao.d.ts +19 -8
- package/dist/commondao/common.dao.js +46 -54
- package/dist/db.model.d.ts +18 -1
- package/dist/testing/daoTest.js +4 -4
- package/dist/testing/dbTest.js +10 -10
- package/dist/timeseries/commonTimeSeriesDao.js +10 -10
- package/dist/transaction/dbTransaction.util.d.ts +1 -4
- package/dist/transaction/dbTransaction.util.js +8 -5
- package/package.json +1 -1
- package/src/adapter/file/file.db.ts +8 -14
- package/src/adapter/inmemory/inMemory.db.ts +44 -41
- package/src/base.common.db.ts +5 -3
- package/src/common.db.ts +7 -3
- package/src/commondao/common.dao.ts +60 -57
- package/src/db.model.ts +20 -1
- package/src/testing/daoTest.ts +4 -4
- package/src/testing/dbTest.ts +10 -10
- package/src/timeseries/commonTimeSeriesDao.ts +11 -13
- package/src/transaction/dbTransaction.util.ts +9 -10
package/dist/testing/daoTest.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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();
|
package/dist/testing/dbTest.js
CHANGED
|
@@ -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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
103
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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[],
|
|
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
|
|
313
|
-
|
|
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
|
|
package/src/base.common.db.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
CommonDBOptions,
|
|
6
6
|
CommonDBSaveOptions,
|
|
7
7
|
DBPatch,
|
|
8
|
-
|
|
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
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1334
|
-
|
|
1335
|
-
|
|
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
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
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(
|
|
1416
|
+
constructor(
|
|
1417
|
+
private tx: DBTransaction,
|
|
1418
|
+
private logger: CommonLogger,
|
|
1419
|
+
) {}
|
|
1410
1420
|
|
|
1411
|
-
|
|
1412
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
)
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|