Skip to content
This repository was archived by the owner on May 25, 2025. It is now read-only.

Commit 32ac3ab

Browse files
feat: refactor and improve long transaction api
To match modern Firestore, Mongo, etc API.
1 parent cc87e57 commit 32ac3ab

File tree

11 files changed

+171
-155
lines changed

11 files changed

+171
-155
lines changed

src/adapter/file/file.db.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
11
import {
22
generateJsonSchemaFromData,
3-
pMap,
4-
StringMap,
53
_by,
64
_deepEquals,
75
_since,
86
_sortBy,
97
_sortObjectDeep,
108
_stringMapValues,
11-
_uniq,
129
JsonSchemaRootObject,
1310
_filterUndefinedValues,
1411
ObjectWithId,
1512
_assert,
16-
_deepCopy,
17-
_stringMapEntries,
1813
Saved,
1914
} from '@naturalcycles/js-lib'
2015
import { readableCreate, ReadableTyped, dimGrey } from '@naturalcycles/nodejs-lib'
2116
import {
2217
BaseCommonDB,
2318
commonDBFullSupport,
2419
CommonDBSupport,
25-
DBOperation,
2620
DBSaveBatchOperation,
27-
DBTransaction,
2821
queryInMemory,
2922
} from '../..'
3023
import { CommonDB } from '../../common.db'
@@ -55,7 +48,7 @@ export class FileDB extends BaseCommonDB implements CommonDB {
5548
updateSaveMethod: false,
5649
updateByQuery: false,
5750
createTable: false,
58-
transactions: false,
51+
transactions: false, // todo
5952
}
6053

6154
constructor(cfg: FileDBCfg) {
@@ -228,9 +221,9 @@ export class FileDB extends BaseCommonDB implements CommonDB {
228221
this.logFinished(started, op)
229222
}
230223

231-
override async createTransaction(): Promise<FileDBTransaction> {
232-
return new FileDBTransaction(this)
233-
}
224+
// override async createTransaction(): Promise<FileDBTransaction> {
225+
// return new FileDBTransaction(this)
226+
// }
234227

235228
sortRows<ROW extends ObjectWithId>(rows: ROW[]): ROW[] {
236229
rows = rows.map(r => _filterUndefinedValues(r))
@@ -260,14 +253,14 @@ export class FileDB extends BaseCommonDB implements CommonDB {
260253
}
261254
}
262255

256+
// todo: get back and fix it
257+
// Implementation is optimized for loading/saving _whole files_.
258+
/*
263259
export class FileDBTransaction implements DBTransaction {
264260
constructor(private db: FileDB) {}
265261
266262
ops: DBOperation[] = []
267263
268-
/**
269-
* Implementation is optimized for loading/saving _whole files_.
270-
*/
271264
async commit(): Promise<void> {
272265
// data[table][id] => row
273266
const data: StringMap<StringMap<ObjectWithId>> = {}
@@ -335,3 +328,4 @@ export class FileDBTransaction implements DBTransaction {
335328
this.ops = []
336329
}
337330
}
331+
*/

src/adapter/inmemory/inMemory.db.ts

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
CommonLogger,
1717
_deepCopy,
1818
_assert,
19-
_omit,
2019
} from '@naturalcycles/js-lib'
2120
import {
2221
bufferReviver,
@@ -37,6 +36,7 @@ import {
3736
DBIncrement,
3837
DBOperation,
3938
DBPatch,
39+
DBTransactionFn,
4040
queryInMemory,
4141
} from '../..'
4242
import {
@@ -177,17 +177,6 @@ export class InMemoryDB implements CommonDB {
177177
rows: ROW[],
178178
opt: CommonDBSaveOptions<ROW> = {},
179179
): 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-
191180
const table = this.cfg.tablesPrefix + _table
192181
this.data[table] ||= {}
193182

@@ -216,41 +205,18 @@ export class InMemoryDB implements CommonDB {
216205

217206
async deleteByQuery<ROW extends ObjectWithId>(
218207
q: DBQuery<ROW>,
219-
opt: CommonDBOptions = {},
208+
_opt?: CommonDBOptions,
220209
): Promise<number> {
221210
const table = this.cfg.tablesPrefix + q.table
222211
if (!this.data[table]) return 0
223212
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-
236213
return await this.deleteByIds(q.table, ids)
237214
}
238215

239-
async deleteByIds(_table: string, ids: string[], opt: CommonDBOptions = {}): Promise<number> {
216+
async deleteByIds(_table: string, ids: string[], _opt?: CommonDBOptions): Promise<number> {
240217
const table = this.cfg.tablesPrefix + _table
241218
if (!this.data[table]) return 0
242219

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-
254220
let count = 0
255221
ids.forEach(id => {
256222
if (!this.data[table]![id]) return
@@ -268,8 +234,6 @@ export class InMemoryDB implements CommonDB {
268234
const patchEntries = Object.entries(patch)
269235
if (!patchEntries.length) return 0
270236

271-
// todo: can we support tx here? :thinking:
272-
273237
const table = this.cfg.tablesPrefix + q.table
274238
const rows = queryInMemory(q, Object.values(this.data[table] || {}) as ROW[])
275239
rows.forEach((row: any) => {
@@ -309,8 +273,15 @@ export class InMemoryDB implements CommonDB {
309273
return Readable.from(queryInMemory(q, Object.values(this.data[table] || {}) as ROW[]))
310274
}
311275

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+
}
314285
}
315286

316287
/**
@@ -394,6 +365,37 @@ export class InMemoryDBTransaction implements DBTransaction {
394365

395366
ops: DBOperation[] = []
396367

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+
397399
async commit(): Promise<void> {
398400
const backup = _deepCopy(this.db.data)
399401

@@ -411,6 +413,7 @@ export class InMemoryDBTransaction implements DBTransaction {
411413
this.ops = []
412414
} catch (err) {
413415
// rollback
416+
this.ops = []
414417
this.db.data = backup
415418
this.db.cfg.logger!.log('InMemoryDB transaction rolled back')
416419

src/base.common.db.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
CommonDBOptions,
66
CommonDBSaveOptions,
77
DBPatch,
8-
DBTransaction,
8+
DBTransactionFn,
99
RunQueryResult,
1010
} from './db.model'
1111
import { DBQuery } from './query/dbQuery'
@@ -83,7 +83,9 @@ export class BaseCommonDB implements CommonDB {
8383
throw new Error('deleteByIds is not implemented')
8484
}
8585

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
8890
}
8991
}

src/common.db.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib'
2-
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
2+
import type { ReadableTyped } from '@naturalcycles/nodejs-lib'
33
import {
44
CommonDBCreateOptions,
55
CommonDBOptions,
66
CommonDBSaveOptions,
77
CommonDBStreamOptions,
88
DBPatch,
9-
DBTransaction,
9+
DBTransactionFn,
1010
RunQueryResult,
1111
} from './db.model'
1212
import { DBQuery } from './query/dbQuery'
@@ -159,8 +159,12 @@ export interface CommonDB {
159159
/**
160160
* Should be implemented as a Transaction (best effort), which means that
161161
* 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()
162166
*/
163-
createTransaction: () => Promise<DBTransaction>
167+
runInTransaction: (fn: DBTransactionFn) => Promise<void>
164168
}
165169

166170
/**

src/commondao/common.dao.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ async function getEven(): Promise<TestItemBM[]> {
467467
test('runInTransaction', async () => {
468468
const items = createTestItemsBM(4)
469469

470-
await dao.useTransaction(async tx => {
470+
await dao.runInTransaction(async tx => {
471471
await tx.save(dao, items[0]!)
472472
await tx.save(dao, items[1]!)
473473
await tx.save(dao, items[3]!)

0 commit comments

Comments
 (0)