subleveldown
Advanced tools
Comparing version 4.1.4 to 5.0.0
@@ -5,2 +5,13 @@ # Changelog | ||
## [5.0.0] - 2020-04-05 | ||
### Changed | ||
- **Breaking:** parent db must support deferredOpen ([#89](https://github.com/level/subleveldown/issues/89)) ([**@vweevers**](https://github.com/vweevers)) | ||
- Upgrade `dependency-check` devDependency from `^3.3.0` to `^4.1.0` ([`b47f991`](https://github.com/level/subleveldown/commit/b47f991)) ([**@vweevers**](https://github.com/vweevers)) | ||
### Fixed | ||
- **Breaking:** fix iterating buffer keys that contain bytes 196-255 ([#88](https://github.com/level/subleveldown/issues/88)) ([**@vweevers**](https://github.com/vweevers)) | ||
## [4.1.4] - 2019-10-08 | ||
@@ -232,2 +243,4 @@ | ||
[5.0.0]: https://github.com/level/subleveldown/compare/v4.1.4...v5.0.0 | ||
[4.1.4]: https://github.com/level/subleveldown/compare/v4.1.3...v4.1.4 | ||
@@ -234,0 +247,0 @@ |
111
leveldown.js
@@ -10,3 +10,2 @@ var inherits = require('inherits') | ||
var hasOwnProperty = Object.prototype.hasOwnProperty | ||
var END = Buffer.from([0xff]) | ||
@@ -31,2 +30,4 @@ function concat (prefix, key, force) { | ||
SubIterator.prototype._next = function (cb) { | ||
if (maybeError(this.db.leveldown, cb)) return | ||
var self = this | ||
@@ -45,2 +46,3 @@ this.iterator.next(function (err, key, value) { | ||
SubIterator.prototype._end = function (cb) { | ||
if (maybeError(this.db.leveldown, cb)) return | ||
this.iterator.end(cb) | ||
@@ -61,10 +63,46 @@ } | ||
var code = separator.charCodeAt(0) + 1 | ||
var ceiling = String.fromCharCode(code) | ||
Buffer.from(prefix).forEach(function (byte) { | ||
if (byte <= code) { | ||
throw new RangeError('Prefix must sort after ' + code) | ||
} | ||
}) | ||
this.db = db | ||
this.leveldown = null | ||
this.ownPrefix = separator + prefix + separator | ||
this.prefix = this.ownPrefix | ||
this.prefix = separator + prefix + separator | ||
this._beforeOpen = opts.open | ||
var self = this | ||
var manifest = db.supports || {} | ||
// The parent db must open itself or be (re)opened by the user because a | ||
// sublevel can't (shouldn't) initiate state changes on the rest of the db. | ||
if (!manifest.deferredOpen && !reachdown.is(db, 'levelup')) { | ||
throw new Error('Parent database must support deferredOpen') | ||
} | ||
var subdb = reachdown(db, 'subleveldown') | ||
if (subdb) { | ||
// Old subleveldown doesn't know its prefix and leveldown until opened | ||
if (!subdb.prefix || !subdb.leveldown) { | ||
throw new Error('Incompatible with subleveldown < 5.0.0') | ||
} | ||
this.prefix = subdb.prefix + this.prefix | ||
this.leveldown = subdb.leveldown | ||
} else { | ||
this.leveldown = reachdown(db, matchdown, false) | ||
} | ||
if (reachdown.is(this.leveldown, 'deferred-leveldown')) { | ||
// Old deferred-leveldown doesn't expose its underlying db until opened | ||
throw new Error('Incompatible with deferred-leveldown < 2.0.0') | ||
} else if (!this.leveldown.status) { | ||
// Old abstract-leveldown doesn't have a status property | ||
throw new Error('Incompatible with abstract-leveldown < 2.4.0') | ||
} | ||
this._wrap = { | ||
@@ -75,4 +113,7 @@ gt: function (x) { | ||
lt: function (x) { | ||
if (Buffer.isBuffer(x) && !x.length) x = END | ||
return concat(self.prefix, x || '\xff') | ||
if (!x || isEmptyBuffer(x)) { | ||
return self.prefix.slice(0, -1) + ceiling | ||
} else { | ||
return concat(self.prefix, x) | ||
} | ||
} | ||
@@ -88,24 +129,27 @@ } | ||
// TODO: remove _open() once abstract-leveldown supports deferredOpen, | ||
// because that means we can always do operations on this.leveldown. | ||
// Alternatively have the sublevel follow the open state of this.db. | ||
SubDown.prototype._open = function (opts, cb) { | ||
var self = this | ||
this.db.open(function (err) { | ||
if (err) return cb(err) | ||
// TODO: make _isOpening public in levelup or add a method like | ||
// ready(cb) which waits for - but does not initiate - a state change. | ||
var m = typeof this.db.isOpening === 'function' ? 'isOpening' : '_isOpening' | ||
var subdb = reachdown(self.db, 'subleveldown') | ||
if (this.db[m]()) { | ||
this.db.once('open', onopen) | ||
} else { | ||
this._nextTick(onopen) | ||
} | ||
if (subdb && subdb.prefix) { | ||
self.prefix = subdb.prefix + self.ownPrefix | ||
self.leveldown = subdb.leveldown | ||
} else { | ||
self.leveldown = reachdown(self.db, matchdown, false) | ||
} | ||
function onopen () { | ||
if (!self.db.isOpen()) return cb(new Error('Parent database is not open')) | ||
if (self.leveldown.status !== 'open') return cb(new Error('Inner database is not open')) | ||
if (self._beforeOpen) self._beforeOpen(cb) | ||
else cb() | ||
}) | ||
} | ||
// TODO: add hooks to abstract-leveldown | ||
if (self._beforeOpen) return self._beforeOpen(cb) | ||
SubDown.prototype._close = function (cb) { | ||
this.leveldown.close(cb) | ||
cb() | ||
} | ||
} | ||
@@ -118,2 +162,3 @@ | ||
SubDown.prototype._put = function (key, value, opts, cb) { | ||
if (maybeError(this.leveldown, cb)) return | ||
this.leveldown.put(concat(this.prefix, key), value, opts, cb) | ||
@@ -123,2 +168,3 @@ } | ||
SubDown.prototype._get = function (key, opts, cb) { | ||
if (maybeError(this.leveldown, cb)) return | ||
this.leveldown.get(concat(this.prefix, key), opts, cb) | ||
@@ -128,2 +174,3 @@ } | ||
SubDown.prototype._del = function (key, opts, cb) { | ||
if (maybeError(this.leveldown, cb)) return | ||
this.leveldown.del(concat(this.prefix, key), opts, cb) | ||
@@ -133,2 +180,4 @@ } | ||
SubDown.prototype._batch = function (operations, opts, cb) { | ||
if (maybeError(this.leveldown, cb)) return | ||
// No need to make a copy of the array, abstract-leveldown does that | ||
@@ -143,2 +192,4 @@ for (var i = 0; i < operations.length; i++) { | ||
SubDown.prototype._clear = function (opts, cb) { | ||
if (maybeError(this.leveldown, cb)) return | ||
if (typeof this.leveldown.clear === 'function') { | ||
@@ -168,2 +219,20 @@ // Prefer optimized implementation of clear() | ||
function isEmptyBuffer (key) { | ||
return Buffer.isBuffer(key) && key.length === 0 | ||
} | ||
// Before any operation, check if the inner db is open. Needed | ||
// because we don't follow open state of the parent db atm. | ||
// TODO: move to abstract-leveldown | ||
function maybeError (leveldown, callback) { | ||
if (leveldown.status !== 'open') { | ||
// Same error message as levelup | ||
// TODO: use require('level-errors').ReadError | ||
process.nextTick(callback, new Error('Database is not open')) | ||
return true | ||
} | ||
return false | ||
} | ||
// TODO (refactor): use addRestOptions instead | ||
@@ -170,0 +239,0 @@ function extend (xopts, opts) { |
@@ -6,4 +6,5 @@ module.exports = function matchdown (db, type) { | ||
if (type === 'deferred-leveldown') return false | ||
if (type === 'subleveldown') return false | ||
return true | ||
} |
{ | ||
"name": "subleveldown", | ||
"version": "4.1.4", | ||
"version": "5.0.0", | ||
"description": "Split a levelup database into sublevels with their own keyspace, encoding and events", | ||
@@ -12,3 +12,3 @@ "author": "Mathias Buus (@mafintosh)", | ||
"hallmark": "hallmark --fix", | ||
"dependency-check": "dependency-check . test/*.js", | ||
"dependency-check": "dependency-check --no-dev .", | ||
"prepublishOnly": "npm run dependency-check" | ||
@@ -20,3 +20,3 @@ }, | ||
"dependencies": { | ||
"abstract-leveldown": "^6.1.1", | ||
"abstract-leveldown": "^6.2.3", | ||
"encoding-down": "^6.2.0", | ||
@@ -26,3 +26,3 @@ "inherits": "^2.0.3", | ||
"levelup": "^4.3.1", | ||
"reachdown": "^1.0.0" | ||
"reachdown": "^1.1.0" | ||
}, | ||
@@ -32,3 +32,3 @@ "devDependencies": { | ||
"coveralls": "^3.0.2", | ||
"dependency-check": "^3.3.0", | ||
"dependency-check": "^4.1.0", | ||
"faucet": "^0.0.1", | ||
@@ -38,3 +38,2 @@ "hallmark": "^2.0.0", | ||
"level-concat-iterator": "^2.0.1", | ||
"memdb": "^1.3.1", | ||
"memdown": "^5.0.0", | ||
@@ -41,0 +40,0 @@ "nyc": "^14.0.0", |
@@ -90,3 +90,3 @@ # subleveldown | ||
- `separator` _(string, default: `'!'`)_ Character for separating sublevel prefixes from user keys and each other. Should be outside the character (or byte) range of user keys. | ||
- `separator` _(string, default: `'!'`)_ Character for separating sublevel prefixes from user keys and each other. Must sort before characters used in prefixes. An error will be thrown if that's not the case. | ||
- `open` _(function)_ Optional open hook called when the underlying `levelup` instance has been opened. The hook receives a callback which must be called to finish opening. | ||
@@ -93,0 +93,0 @@ |
@@ -11,36 +11,57 @@ var test = require('tape') | ||
var reachdown = require('reachdown') | ||
var memdb = require('memdb') | ||
var abstract = require('abstract-leveldown') | ||
var inherits = require('util').inherits | ||
var EventEmitter = require('events') | ||
// Test abstract-leveldown compliance | ||
suite({ | ||
test: test, | ||
factory: function () { | ||
return subdown(levelup(memdown()), 'test') | ||
}, | ||
function runSuite (factory) { | ||
suite({ | ||
test: test, | ||
factory: factory, | ||
// Unsupported features | ||
seek: false, | ||
createIfMissing: false, | ||
errorIfExists: false, | ||
// Unsupported features | ||
seek: false, | ||
createIfMissing: false, | ||
errorIfExists: false, | ||
// Opt-in to new clear() tests | ||
clear: true | ||
// Opt-in to new clear() tests | ||
clear: true | ||
}) | ||
} | ||
// Test basic prefix | ||
runSuite(function factory () { | ||
return subdown(levelup(memdown()), 'test') | ||
}) | ||
// Test empty prefix | ||
runSuite(function factory () { | ||
return subdown(levelup(memdown()), '') | ||
}) | ||
// Test custom separator | ||
runSuite(function factory () { | ||
return subdown(levelup(memdown()), 'test', { separator: '%' }) | ||
}) | ||
// Test without a user-provided levelup layer | ||
suite({ | ||
test: test, | ||
factory: function () { | ||
return subdown(memdown(), 'test') | ||
}, | ||
runSuite(function factory () { | ||
var down = memdown() | ||
var emitter = new EventEmitter() | ||
// Unsupported features | ||
seek: false, | ||
createIfMissing: false, | ||
errorIfExists: false, | ||
if (!down.supports.deferredOpen) { | ||
// Simulate a future abstract-leveldown that | ||
// supports deferredOpen just like levelup | ||
down.supports.deferredOpen = true | ||
down.isOpen = function () { return this.status === 'open' } | ||
down.isOpening = function () { return this.status === 'opening' } | ||
down.once = emitter.once.bind(emitter) | ||
down.open(function (err) { | ||
if (err) throw err | ||
emitter.emit('open') | ||
}) | ||
down.open = function () { throw new Error('Explicit open is not simulated') } | ||
} | ||
// Opt-in to new clear() tests | ||
clear: true | ||
return subdown(down, 'test') | ||
}) | ||
@@ -51,3 +72,3 @@ | ||
t.test('can be called without new', function (t) { | ||
var sub = subdown() | ||
var sub = subdown(levelup(memdown())) | ||
t.is(sub instanceof subdown, true, 'instanceof subdown') | ||
@@ -57,3 +78,3 @@ t.end() | ||
t.test('missing prefix and missing separator', function (t) { | ||
var sub = subdown() | ||
var sub = subdown(levelup(memdown())) | ||
t.is(sub.prefix, '!!') | ||
@@ -63,3 +84,3 @@ t.end() | ||
t.test('prefix and missing separator', function (t) { | ||
var sub = subdown({}, 'prefix') | ||
var sub = subdown(levelup(memdown()), 'prefix') | ||
t.is(sub.prefix, '!prefix!') | ||
@@ -69,3 +90,3 @@ t.end() | ||
t.test('prefix and separator (as string)', function (t) { | ||
var sub = subdown({}, 'prefix', '%') | ||
var sub = subdown(levelup(memdown()), 'prefix', '%') | ||
t.is(sub.prefix, '%prefix%') | ||
@@ -75,3 +96,3 @@ t.end() | ||
t.test('prefix and separator (as options)', function (t) { | ||
var sub = subdown({}, 'prefix', { separator: '%' }) | ||
var sub = subdown(levelup(memdown()), 'prefix', { separator: '%' }) | ||
t.is(sub.prefix, '%prefix%') | ||
@@ -81,3 +102,3 @@ t.end() | ||
t.test('prefix with same initial character as separator is sliced', function (t) { | ||
var sub = subdown({}, '!prefix') | ||
var sub = subdown(levelup(memdown()), '!prefix') | ||
t.is(sub.prefix, '!prefix!') | ||
@@ -87,3 +108,3 @@ t.end() | ||
t.test('prefix with same ending character as separator is sliced', function (t) { | ||
var sub = subdown({}, 'prefix!') | ||
var sub = subdown(levelup(memdown()), 'prefix!') | ||
t.is(sub.prefix, '!prefix!') | ||
@@ -94,3 +115,3 @@ t.end() | ||
// t.test('repeated separator is slices off from prefix parameter', function (t) { | ||
// var sub = subdown({}, '!!prefix!!') | ||
// var sub = subdown(levelup(memdown()), '!!prefix!!') | ||
// t.is(sub.prefix, '!prefix!') | ||
@@ -111,14 +132,21 @@ // t.end() | ||
t.test('error from open() bubbles up', function (t) { | ||
t.test('error from open() does not bubble up', function (t) { | ||
t.plan(1) | ||
var mockdb = { | ||
open: function (cb) { | ||
var mockdb = mock(abstract.AbstractLevelDOWN, { | ||
_open: function (opts, cb) { | ||
process.nextTick(cb, new Error('error from underlying store')) | ||
} | ||
} | ||
}) | ||
subdb(mockdb, 'test').on('error', (err) => { | ||
var db = levelup(mockdb) | ||
var sub = subdb(db, 'test') | ||
db.on('error', (err) => { | ||
t.is(err.message, 'error from underlying store') | ||
}) | ||
sub.on('error', (err) => { | ||
t.fail(err) | ||
}) | ||
}) | ||
@@ -164,4 +192,4 @@ | ||
t.test('wrap a closed levelup and re-open levelup', function (t) { | ||
t.plan(3) | ||
t.test('cannot create a sublevel on a closed db', function (t) { | ||
t.plan(4) | ||
var db = levelup(memdown()) | ||
@@ -171,8 +199,13 @@ db.once('open', function () { | ||
t.error(err, 'no error') | ||
var sub = subdb(db, 'test') | ||
sub.once('open', function () { | ||
t.pass('subdb openen') | ||
subdb(db, 'test').on('error', function (err) { | ||
t.is(err.message, 'Parent database is not open', 'sublevel not opened') | ||
}) | ||
db.open(function (err) { | ||
t.error(err, 'no error') | ||
subdb(db, 'test').on('open', function () { | ||
t.pass('sublevel opened') | ||
}) | ||
}) | ||
@@ -183,2 +216,124 @@ }) | ||
t.test('can close db and sublevel once opened', function (t) { | ||
t.plan(3) | ||
levelup(memdown(), function (err, db) { | ||
t.ifError(err, 'no open error') | ||
var sub = subdb(db, 'test') | ||
sub.once('open', function () { | ||
db.close(function (err) { | ||
t.ifError(err, 'no close error') | ||
}) | ||
sub.close(function (err) { | ||
t.ifError(err, 'no close error') | ||
}) | ||
}) | ||
}) | ||
}) | ||
t.test('rejects operations if parent db is closed', function (t) { | ||
t.plan(9) | ||
levelup(memdown(), function (err, db) { | ||
t.ifError(err, 'no open error') | ||
var sub = subdb(db, 'test') | ||
var it = sub.iterator() | ||
sub.once('open', function () { | ||
db.close(function (err) { | ||
t.ifError(err, 'no close error') | ||
sub.put('foo', 'bar', verify) | ||
sub.get('foo', verify) | ||
sub.del('foo', verify) | ||
sub.clear(verify) | ||
sub.batch([{ type: 'del', key: 'foo' }], verify) | ||
it.next(function (err) { | ||
verify(err) | ||
it.end(verify) | ||
}) | ||
function verify (err) { | ||
t.is(err.message, 'Database is not open') | ||
} | ||
}) | ||
}) | ||
}) | ||
}) | ||
t.test('cannot close db while sublevel is opening', function (t) { | ||
t.plan(5) | ||
levelup(memdown(), function (err, db) { | ||
t.ifError(err, 'no open error') | ||
var sub = subdb(db, 'test') | ||
sub.on('error', (err) => { | ||
t.is(err.message, 'Parent database is not open') | ||
}) | ||
db.close(function (err) { | ||
t.ifError(err, 'no close error') | ||
t.is(reachdown(sub, 'subleveldown').status, 'new') | ||
t.is(reachdown(sub).status, 'closed') | ||
}) | ||
sub.close(function () { | ||
t.fail('should not be called, because opening never finished') | ||
}) | ||
}) | ||
}) | ||
t.test('cannot create sublevel while db is closing', function (t) { | ||
t.plan(6) | ||
levelup(memdown(), function (err, db) { | ||
t.ifError(err, 'no open error') | ||
db.close(function (err) { | ||
t.ifError(err, 'no close error') | ||
t.is(reachdown(sub, 'subleveldown').status, 'opening') | ||
t.is(reachdown(sub).status, 'closed') | ||
sub.on('error', (err) => { | ||
t.is(err.message, 'Parent database is not open') | ||
t.is(reachdown(sub, 'subleveldown').status, 'new') | ||
}) | ||
}) | ||
var sub = subdb(db, 'test') | ||
sub.on('open', function () { | ||
t.fail('should not open') | ||
}) | ||
}) | ||
}) | ||
t.test('can reopen a sublevel without affecting encoding-down state of db', function (t) { | ||
t.plan(3) | ||
var db = levelup(encoding(memdown())) | ||
db.once('open', function () { | ||
var sub = subdb(db, 'test') | ||
sub.close(function (err) { | ||
t.ifError(err, 'no close error') | ||
// Previously, subleveldown would open a sublevel via levelup yet close | ||
// it via the innermost db (memdown). So at this point, the intermediate | ||
// encoding-down layer would still be open, leading levelup to believe | ||
// that encoding-down and its underlying memdown db need not be opened. | ||
// See https://github.com/Level/subleveldown/issues/60. | ||
sub.open(function (err) { | ||
t.error(err, 'no open error') | ||
t.is(reachdown(sub).status, 'open') | ||
}) | ||
}) | ||
}) | ||
}) | ||
t.test('can wrap a sublevel and reopen the wrapped sublevel', function (t) { | ||
@@ -289,7 +444,4 @@ var db = levelup(memdown()) | ||
var mockdb = { | ||
open: function (cb) { | ||
process.nextTick(cb) | ||
}, | ||
iterator: function () { | ||
var mockdb = mock(abstract.AbstractLevelDOWN, { | ||
_iterator: function () { | ||
return { | ||
@@ -304,5 +456,5 @@ next: function (cb) { | ||
} | ||
} | ||
}) | ||
var sub = subdb(mockdb, 'test') | ||
var sub = subdb(levelup(mockdb), 'test') | ||
var it = sub.iterator() | ||
@@ -421,2 +573,62 @@ | ||
// https://github.com/Level/subleveldown/issues/87 | ||
test('can store any key', function (t) { | ||
t.test('iterating buffer keys with bytes above 196', function (t) { | ||
t.plan(3) | ||
var db = levelup(memdown()) | ||
var sub = subdb(db, 'test', { keyEncoding: 'binary' }) | ||
sub.once('open', function () { | ||
const batch = sub.batch() | ||
for (let i = 0; i < 256; i++) { | ||
batch.put(Buffer.from([i]), 'test') | ||
} | ||
batch.write(function (err) { | ||
t.ifError(err, 'no write error') | ||
concat(sub.iterator(), function (err, entries) { | ||
t.ifError(err, 'no concat error') | ||
t.is(entries.length, 256, 'sub yields all entries') | ||
}) | ||
}) | ||
}) | ||
}) | ||
t.test('range logic', function (t) { | ||
const db = levelup(memdown()) | ||
const a = subdb(db, 'a', { separator: '#' }) | ||
const aA = subdb(a, 'a', { separator: '#' }) | ||
const b = subdb(db, 'b', { separator: '#' }) | ||
const next = after(3, verify) | ||
a.once('open', next) | ||
aA.once('open', next) | ||
b.once('open', next) | ||
function wrapper (sub) { | ||
return reachdown(sub, 'subleveldown')._wrap | ||
} | ||
function verify () { | ||
const ranges = [ | ||
wrapper(a).gt(), | ||
wrapper(aA).gt(), | ||
wrapper(aA).lt(), | ||
wrapper(a).lt(), | ||
wrapper(b).gt(), | ||
wrapper(b).lt() | ||
] | ||
t.same(ranges, ['#a#', '#a##a#', '#a##a$', '#a$', '#b#', '#b$']) | ||
t.same(ranges.slice().sort(), ranges) | ||
t.end() | ||
} | ||
}) | ||
t.end() | ||
}) | ||
// Test that we peel off the levelup, deferred-leveldown and encoding-down | ||
@@ -463,35 +675,23 @@ // layers from db, but stop at any other intermediate layer like encrypt-down, | ||
test('legacy memdb (old levelup)', function (t) { | ||
t.plan(7) | ||
function getKey (entry) { | ||
return entry.key | ||
} | ||
// Should not result in double json encoding | ||
var db = memdb({ valueEncoding: 'json' }) | ||
var sub = subdb(db, 'test', { valueEncoding: 'json' }) | ||
function implement (ctor, methods) { | ||
function Test () { | ||
ctor.apply(this, arguments) | ||
} | ||
// Integration with memdb still works because subleveldown waits to reachdown | ||
// until the (old levelup) db is open. Reaching down then correctly lands on | ||
// the memdown db. If subleveldown were to reachdown immediately it'd land on | ||
// the old deferred-leveldown (which when unopened doesn't have a reference to | ||
// the memdown db yet) so we'd be unable to persist anything. | ||
t.is(Object.getPrototypeOf(reachdown(db)).constructor.name, 'DeferredLevelDOWN') | ||
inherits(Test, ctor) | ||
sub.put('key', { a: 1 }, function (err) { | ||
t.ifError(err, 'no put error') | ||
for (var k in methods) { | ||
Test.prototype[k] = methods[k] | ||
} | ||
sub.get('key', function (err, value) { | ||
t.ifError(err, 'no get error') | ||
t.same(value, { a: 1 }) | ||
}) | ||
return Test | ||
} | ||
t.is(Object.getPrototypeOf(reachdown(db)).constructor.name, 'MemDOWN') | ||
reachdown(db).get('!test!key', { asBuffer: false }, function (err, value) { | ||
t.ifError(err, 'no get error') | ||
t.is(value, '{"a":1}') | ||
}) | ||
}) | ||
}) | ||
function getKey (entry) { | ||
return entry.key | ||
function mock (ctor, methods) { | ||
var Test = implement(ctor, methods) | ||
return new Test() | ||
} |
@@ -5,2 +5,57 @@ # Upgrade Guide | ||
## v5 | ||
### Fixes iterating buffer keys that contain bytes 196-255 ([#88](https://github.com/level/subleveldown/issues/88)) | ||
Previously (in any version) keys containing bytes 196-255 were wrongly excluded by the range logic of `subleveldown`. The fix is not breaking for most folks. | ||
It's breaking if you: | ||
- Use the default separator and prefixes that contain `"` (byte 34) | ||
- Use a custom separator that doesn't sort 2 positions before characters used in your prefixes. For example with separator `/` (byte 47) prefixes must use characters greater than `0` (byte 48). | ||
In either case, an error will be thrown from the constructor. | ||
### Parent database must support deferredOpen ([#89](https://github.com/level/subleveldown/issues/89)) | ||
By parent we mean: | ||
```js | ||
var parent = require('level')('db') | ||
var sublevel = require('subleveldown')(parent, 'a') | ||
``` | ||
By [deferredOpen](https://github.com/Level/supports#deferredopen-boolean) we mean that the db opens itself and defers operations until it's open. Currently that's only supported by [`levelup`](https://github.com/Level/levelup) (and [`levelup`](https://github.com/Level/levelup) factories like [`level`](https://github.com/Level/level)). Previously, `subleveldown` would also accept [`abstract-leveldown`](https://github.com/Level/abstract-leveldown) db's that were not wrapped in [`levelup`](https://github.com/Level/levelup). | ||
### Better isolation | ||
Opening and closing a sublevel no longer opens or closes the parent db. The sublevel does wait for the parent to open (which in the case of [`levelup`](https://github.com/Level/levelup) already happens automatically) but never initiates | ||
a state change on the parent. | ||
If one closes the parent but not the sublevel, subsequent operations on the sublevel (like `get` and `put`) will yield an error, to prevent segmentation faults from underlying stores. | ||
### Drops support of old modules | ||
- [`memdb`](https://github.com/juliangruber/memdb) (use [`level-mem`](https://github.com/Level/mem) instead) | ||
- [`deferred-leveldown`](https://github.com/Level/deferred-leveldown) < 2.0.0 (and thus [`levelup`](https://github.com/Level/levelup) < 2.0.0) | ||
- [`abstract-leveldown`](https://github.com/Level/abstract-leveldown) < 2.4.0 | ||
### Rejects new sublevels on a closing or closed database | ||
```js | ||
db.close(function (err) { | ||
subdb(db, 'example').on('error', function (err) { | ||
throw err // Error: Parent database is not open | ||
}) | ||
}) | ||
``` | ||
```js | ||
subdb(db, 'example').on('error', function (err) { | ||
throw err // Error: Parent database is not open | ||
}) | ||
db.close(function () {}) | ||
``` | ||
## v4 | ||
@@ -7,0 +62,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
54528
11
791
Updatedabstract-leveldown@^6.2.3
Updatedreachdown@^1.1.0