diff --git a/README.md b/README.md index 7780451..a74818a 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Make a new Hyperbee instance. `core` should be a [Hypercore](https://github.com/ { keyEncoding: 'binary', // "binary" (default), "utf-8", "ascii", "json", or an abstract-encoding valueEncoding: 'binary' // Same options as keyEncoding like "json", etc + onlyIfChanged: true // put a value only if it means a change in the db } ``` diff --git a/index.js b/index.js index cf3d527..e3cbf68 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ +const sameData = require('same-data') const codecs = require('codecs') const { Readable } = require('streamx') const mutexify = require('mutexify/promise') @@ -287,6 +288,7 @@ class Hyperbee extends ReadyResource { this.sep = opts.sep || SEP this.readonly = !!opts.readonly this.prefix = opts.prefix || null + this.onlyIfChanged = !!opts.onlyIfChanged this._unprefixedKeyEncoding = this.keyEncoding this._sub = !!this.prefix @@ -669,7 +671,7 @@ class Batch { async put (key, value, opts) { const release = this.batchLock ? await this.batchLock() : null - const cas = (opts && opts.cas) || null + const cas = (opts && opts.cas) || (this.tree.onlyIfChanged ? sameData : null) const encoding = this._getEncoding(opts) if (!this.locked) await this.lock() diff --git a/package.json b/package.json index 25a497f..c1120b1 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "protocol-buffers-encodings": "^1.2.0", "ready-resource": "^1.0.0", "safety-catch": "^1.0.2", + "same-data": "^1.0.0", "streamx": "^2.12.4" }, "devDependencies": { diff --git a/test/basic.js b/test/basic.js index 4206cad..b4811ea 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,7 +1,6 @@ const test = require('brittle') const b4a = require('b4a') const { create, collect, createCore } = require('./helpers') - const Hyperbee = require('..') test('out of bounds iterator', async function (t) { @@ -486,3 +485,39 @@ test('supports encodings in snapshot', async function (t) { t.alike(await snap1.get('hi'), { seq: 1, key: b4a.from('hi'), value: 'there' }) t.alike(await snap2.get('hi'), { seq: 1, key: 'hi', value: b4a.from('there') }) }) + +test('onlyIfChanged put', async function (t) { + { + const db = create({ onlyIfChanged: true }) + await db.ready() + await db.put('key', 'value') + t.is(db.feed.length, 2) + await db.put('key', 'value') + t.is(db.feed.length, 2) + } + { + const db = create({ onlyIfChanged: true, valueEncoding: 'binary' }) + await db.ready() + await db.put('key', Buffer.from('buffer')) + t.is(db.feed.length, 2) + await db.put('key', Buffer.from('buffer')) + t.is(db.feed.length, 2) + } + { + const db = create({ onlyIfChanged: true, valueEncoding: 'json' }) + await db.ready() + await db.put('key', { a: 1 }) + t.is(db.feed.length, 2) + await db.put('key', { a: 1 }) + t.is(db.feed.length, 2) + } +}) + +test('onlyIfChanged put after del', async function (t) { + const db = create({ onlyIfChanged: true }) + await db.ready() + await db.put('key', 'value') + await db.del('key') + await db.put('key', 'value') + t.is(db.feed.length, 4) +})