Socket
Socket
Sign inDemoInstall

subleveldown

Package Overview
Dependencies
Maintainers
3
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

subleveldown - npm Package Compare versions

Comparing version 4.1.4 to 5.0.0

13

CHANGELOG.md

@@ -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
}

11

package.json
{
"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) &lt; 2.0.0 (and thus [`levelup`](https://github.com/Level/levelup) &lt; 2.0.0)
- [`abstract-leveldown`](https://github.com/Level/abstract-leveldown) &lt; 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 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc