diff --git a/README.md b/README.md index 54628434..f70c37d3 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,10 @@ Destroy a batch, and releases any locks it has acquired on the db. Call this if you want to abort a batch without flushing it. +#### `const output = await db.transaction((batch) => {})` + +Makes a new locked batch, gives a callback for operations, and flushes or closes if any error. + #### `const stream = db.createReadStream([range], [options])` Make a read stream. Sort order is based on the binary value of the keys. diff --git a/index.js b/index.js index fd216feb..b71f56c6 100644 --- a/index.js +++ b/index.js @@ -476,6 +476,23 @@ class Hyperbee extends ReadyResource { return new Batch(this, this.core, mutexify(), true, opts) } + async transaction (cb) { + const batch = this.batch() + let output = null + + try { + await batch.lock() + output = await cb(batch) + } catch (err) { + await batch.close() + throw err + } + + await batch.flush() + + return output + } + del (key, opts) { const b = new Batch(this, this.core, null, true, opts) return b.del(key, opts) diff --git a/test/batches.js b/test/batches.js index 0b96437e..ae789bbe 100644 --- a/test/batches.js +++ b/test/batches.js @@ -284,3 +284,31 @@ test('batches close when instance closes', async function (t) { await d.close() }) + +test('transaction', async function (t) { + const db = create() + + const id = await db.transaction(async function (b) { + await b.put('/users/1') + return 123 + }) + + t.is(id, 123) + t.ok(await db.get('/users/1')) +}) + +test('transaction does not commit if any error', async function (t) { + const db = create() + + try { + await db.transaction(async function (b) { + await b.put('/users/1') + throw new Error('Failed') + }) + t.fail('Should have failed') + } catch (err) { + t.is(err.message, 'Failed') + } + + t.absent(await db.get('/users/1')) +})