level-hooks
Advanced tools
Comparing version 4.3.0 to 4.3.1
162
index.js
var ranges = require('string-range') | ||
var locker = require('lock') | ||
module.exports = function (db) { | ||
var lock = locker() | ||
if(db.hooks) { | ||
@@ -10,5 +9,4 @@ return | ||
var posthooks = [] | ||
var prehooks = [] | ||
var asynchooks = [] | ||
var posthooks = [] | ||
var prehooks = [] | ||
@@ -46,11 +44,4 @@ function getPrefix (p) { | ||
}, | ||
async: function (prefix, hook) { | ||
if(!hook) hook = prefix, prefix = '' | ||
var h = {test: ranges.checker(prefix), hook: hook, async: true} | ||
asynchooks.push(h) | ||
return remover(asynchooks, h) | ||
}, | ||
posthooks : posthooks, | ||
prehooks : prehooks, | ||
asynchooks: asynchooks | ||
posthooks: posthooks, | ||
prehooks: prehooks | ||
} | ||
@@ -85,103 +76,60 @@ | ||
function callHooks (isBatch, b, opts, cb) { | ||
if(!cb) | ||
cb = opts, opts = {} | ||
//ASYNC HOOKS | ||
var toHook = [], toLock = [], n = 0, locked = false | ||
//skip this if there are no async hooks. | ||
if(!asynchooks.length) | ||
return n=1, sync() | ||
b.forEach(function (e) { | ||
asynchooks.forEach(function (h) { | ||
try { | ||
b.forEach(function hook(e, i) { | ||
prehooks.forEach(function (h) { | ||
if(h.test(String(e.key))) { | ||
locked = true | ||
// should I lock the whole batch? | ||
// or just the keys that have asynchooks? | ||
// just locking the hooked keys for now... | ||
toLock.push(e.key) | ||
toHook.push(function (cb) { | ||
h.hook(e, cb) | ||
}) | ||
n++ | ||
} | ||
}) | ||
}) | ||
if(toLock.length) | ||
lock(toLock, function (release) { | ||
//release the lock when the callback is called | ||
//after the batch is processed! | ||
cb = release(cb) | ||
toHook.forEach(function (f) { f(sync) }) | ||
}) | ||
else | ||
n=1, sync() | ||
//SYNC HOOKS | ||
function sync () { | ||
if(--n) return | ||
try { | ||
b.forEach(function hook(e, i) { | ||
prehooks.forEach(function (h) { | ||
if(h.test(String(e.key))) { | ||
//optimize this? | ||
//maybe faster to not create a new object each time? | ||
//have one object and expose scope to it? | ||
var context = { | ||
add: function (ch, db) { | ||
if(typeof ch === 'undefined') { | ||
return this | ||
} | ||
if(ch === false) | ||
return delete b[i] | ||
var prefix = ( | ||
getPrefix(ch.prefix) || | ||
getPrefix(db) || | ||
h.prefix || '' | ||
) | ||
ch.key = prefix + ch.key | ||
if(h.test(String(ch.key))) { | ||
//this usually means a stack overflow. | ||
throw new Error('prehook cannot insert into own range') | ||
} | ||
b.push(ch) | ||
hook(ch, b.length - 1) | ||
//optimize this? | ||
//maybe faster to not create a new object each time? | ||
//have one object and expose scope to it? | ||
var context = { | ||
add: function (ch, db) { | ||
if(typeof ch === 'undefined') { | ||
return this | ||
}, | ||
put: function (ch, db) { | ||
if('object' === typeof ch) ch.type = 'put' | ||
return this.add(ch, db) | ||
}, | ||
del: function (ch, db) { | ||
if('object' === typeof ch) ch.type = 'del' | ||
return this.add(ch, db) | ||
}, | ||
veto: function () { | ||
return this.add(false) | ||
} | ||
if(ch === false) | ||
return delete b[i] | ||
var prefix = ( | ||
getPrefix(ch.prefix) || | ||
getPrefix(db) || | ||
h.prefix || '' | ||
) | ||
ch.key = prefix + ch.key | ||
if(h.test(String(ch.key))) { | ||
//this usually means a stack overflow. | ||
throw new Error('prehook cannot insert into own range') | ||
} | ||
b.push(ch) | ||
hook(ch, b.length - 1) | ||
return this | ||
}, | ||
put: function (ch, db) { | ||
if('object' === typeof ch) ch.type = 'put' | ||
return this.add(ch, db) | ||
}, | ||
del: function (ch, db) { | ||
if('object' === typeof ch) ch.type = 'del' | ||
return this.add(ch, db) | ||
}, | ||
veto: function () { | ||
return this.add(false) | ||
} | ||
h.hook.call(context, e, context.add, b) | ||
} | ||
}) | ||
h.hook.call(context, e, context.add, b) | ||
} | ||
}) | ||
} catch (err) { | ||
db.emit('error', err) | ||
return (cb || opts)(err) | ||
} | ||
b = b.filter(function (e) { | ||
return e && e.type //filter out empty items | ||
}) | ||
}) | ||
} catch (err) { | ||
return (cb || opts)(err) | ||
} | ||
b = b.filter(function (e) { | ||
return e && e.type //filter out empty items | ||
}) | ||
if(b.length == 1 && !isBatch) { | ||
var change = b[0] | ||
return change.type == 'put' | ||
? put.call(db, change.key, change.value, opts, cb) | ||
: del.call(db, change.key, opts, cb) | ||
} | ||
return batch.call(db, b, opts, cb) | ||
if(b.length == 1 && !isBatch) { | ||
var change = b[0] | ||
return change.type == 'put' | ||
? put.call(db, change.key, change.value, opts, cb) | ||
: del.call(db, change.key, opts, cb) | ||
} | ||
return batch.call(db, b, opts, cb) | ||
} | ||
@@ -188,0 +136,0 @@ |
{ | ||
"name": "level-hooks", | ||
"description": "pre/post hooks for leveldb", | ||
"version": "4.3.0", | ||
"version": "4.3.1", | ||
"homepage": "https://github.com/dominictarr/level-hooks", | ||
@@ -12,10 +12,9 @@ "repository": { | ||
"dependencies": { | ||
"string-range": "~1.2", | ||
"lock": "0.0.3" | ||
"string-range": "~1.2" | ||
}, | ||
"devDependencies": { | ||
"levelup": "~0.8", | ||
"rimraf": "~2.0.2", | ||
"macgyver": "~1.9", | ||
"range-bucket": "0.0.0", | ||
"level-test": "~1.1.1" | ||
"range-bucket": "0.0.0" | ||
}, | ||
@@ -22,0 +21,0 @@ "scripts": { |
@@ -5,5 +5,2 @@ # Pre/Post hooks for leveldb | ||
[ | ||
](https://travis-ci.org/dominictarr/level-hooks) | ||
## Warning - Breaking Changes | ||
@@ -29,16 +26,18 @@ | ||
var db = levelup(file) | ||
levelup(file, {createIfMissing: true}, function (err, db) { | ||
//install hooks onto db. | ||
hooks(db) | ||
//install hooks onto db. | ||
hooks(db) | ||
db.hooks.pre({start: '', end: '~'}, function (change, add) { | ||
//change is same pattern as the an element in the batch array. | ||
//add a log to record every put operation. | ||
add({type: 'put', key: '~log-'+timestamp()+'-'+change.type, value: change.key}) | ||
}) | ||
db.hooks.pre({start: '', end: '~'}, function (change, add) { | ||
//change is same pattern as the an element in the batch array. | ||
//add a log to record every put operation. | ||
add({type: 'put', key: '~log-'+timestamp()+'-'+change.type, value: change.key}) | ||
}) | ||
//add a hook that responds after an operation has completed. | ||
db.hooks.post(function (ch) { | ||
//{type: 'put'|'del', key: ..., value: ...} | ||
//add a hook that responds after an operation has completed. | ||
db.hooks.post(function (ch) { | ||
//{type: 'put'|'del', key: ..., value: ...} | ||
}) | ||
}) | ||
@@ -50,29 +49,2 @@ ``` | ||
## Async Example | ||
``` js | ||
var levelup = require('levelup') | ||
var timestamp = require('monotonic-timestamp') | ||
var hooks = require('level-hooks') | ||
var db = levelup(file) | ||
//install hooks onto db. | ||
hooks(db) | ||
db.hooks.pre('counter!', function (op, done) { | ||
db.get(op.key, function (err, val) { | ||
op.value = Number(op.value || 0) + Number(val || 0) | ||
cb() | ||
}) | ||
}) | ||
db.put('counter!foo', 1, function (err) { | ||
db.put('counter!foo', 2, function (err) { | ||
db.get('counter!foo', console.log) //3! | ||
}) | ||
}) | ||
``` | ||
## API | ||
@@ -105,14 +77,4 @@ | ||
### rm = db.hooks.async(range?, hook) | ||
Async hooks are another kind of prehook that allow IO to happen | ||
before the batch/put/del is processed. | ||
Also, async hooked keys are processed strictly in series, | ||
subsequent calls being queued until the previous call has returned. | ||
This may be an issue for write heavy applications! | ||
## License | ||
MIT |
var rimraf = require('rimraf') | ||
var levelup = require('level-test')() | ||
var levelup = require('levelup') | ||
@@ -9,20 +9,21 @@ var Hooks = require('../') | ||
var db = levelup('throw') | ||
var dir ='/tmp/map-reduce-prehook-test' | ||
Hooks(db) | ||
rimraf(dir, function () { | ||
levelup(dir, function (err, db) { | ||
if(err) throw err | ||
db.hooks.pre({min: 'a', max:'z'}, function (ch, add) { | ||
add(ch) //this should cause an error | ||
}) | ||
Hooks(db) | ||
db.on('error', mac(function (err) { | ||
assert.ok(err) | ||
console.log('expect error:', err) | ||
}).once()) | ||
db.hooks.pre({min: 'a', max:'z'}, function (ch, add) { | ||
add(ch) //this should cause an error | ||
}) | ||
db.put('c', 'whatever', mac(function (err) { | ||
assert.ok(err) | ||
console.log('expect error:', err) | ||
}).once()) | ||
db.put('c', 'whatever', mac(function (err) { | ||
assert.ok(err) | ||
console.log('expect error:', err) | ||
}).once()) | ||
}) | ||
}) |
var rimraf = require('rimraf') | ||
var levelup = require('level-test')() | ||
var levelup = require('levelup') | ||
@@ -9,28 +9,34 @@ var hooks = require('..') | ||
var db = levelup('map-reduce-intercept-test') | ||
var dir ='/tmp/map-reduce-intercept-test' | ||
rimraf(dir, function () { | ||
levelup(dir, {createIfMissing: true}, function (err, db) { | ||
hooks(db) | ||
var _batch = [] | ||
//hook keys that start with a word character | ||
db.hooks.pre(/^\w/, mac(function (ch, add) { | ||
_batch.push(ch) | ||
var a | ||
add(a = {key: '~h', value: 'hello', type: 'put'}) | ||
_batch.push(a) | ||
}).atLeast(1)) | ||
hooks(db) | ||
var _batch = [] | ||
//hook keys that start with a word character | ||
db.hooks.pre(/^\w/, mac(function (ch, add) { | ||
_batch.push(ch) | ||
var a | ||
add(a = {key: '~h', value: 'hello', type: 'put'}) | ||
_batch.push(a) | ||
}).atLeast(1)) | ||
//assert that it really became a batch | ||
db.on('batch', mac(function (batch) { | ||
console.log('batch', _batch) | ||
assert.deepEqual(_batch, batch.map(function (e) { | ||
return {key: ''+e.key, value: ''+ e.value, type: e.type} | ||
})) | ||
}).once()) | ||
//assert that it really became a batch | ||
db.on('batch', mac(function (batch) { | ||
console.log('batch', _batch) | ||
assert.deepEqual(_batch, batch.map(function (e) { | ||
return {key: ''+e.key, value: ''+ e.value, type: e.type} | ||
})) | ||
}).once()) | ||
db.put('hello' , 'whatever' , mac(function (){ | ||
db.put('hello' , 'whatever' , mac(function (){ | ||
}).once()) | ||
}).once()) | ||
}) | ||
}) | ||
var rimraf = require('rimraf') | ||
var levelup = require('level-test')() | ||
var levelup = require('levelup') | ||
@@ -13,48 +13,52 @@ var Hooks = require('../') | ||
var db = levelup('map-reduce-prehook-test') | ||
rimraf(dir, function () { | ||
levelup(dir, function (err, db) { | ||
if(err) throw err | ||
var SEQ = 0 | ||
var bucket = Bucket('prehook') | ||
var SEQ = 0 | ||
var bucket = Bucket('prehook') | ||
Hooks(db) | ||
Hooks(db) | ||
db.hooks.pre(/^\w/, mac(function (ch, add, batch) { | ||
//iterate backwards so you can push without breaking stuff. | ||
db.hooks.pre(/^\w/, mac(function (ch, add, batch) { | ||
//iterate backwards so you can push without breaking stuff. | ||
assert.ok(Array.isArray(batch)) | ||
assert.notEqual(batch.indexOf(ch), -1) | ||
var key = ch.key | ||
add({ | ||
type: 'put', | ||
key: new Buffer('~log~'+ ++SEQ), | ||
value: new Buffer(JSON.stringify({ | ||
type: ch.type, | ||
key: key.toString(), | ||
time: Date.now() | ||
})) | ||
}) | ||
add({type: 'put', key: new Buffer('~seq'), value: new Buffer(SEQ.toString())}) | ||
assert.ok(Array.isArray(batch)) | ||
assert.notEqual(batch.indexOf(ch), -1) | ||
var key = ch.key | ||
add({ | ||
type: 'put', | ||
key: new Buffer('~log~'+ ++SEQ), | ||
value: new Buffer(JSON.stringify({ | ||
type: ch.type, | ||
key: key.toString(), | ||
time: Date.now() | ||
})) | ||
}) | ||
add({type: 'put', key: new Buffer('~seq'), value: new Buffer(SEQ.toString())}) | ||
}).atLeast(1)) | ||
}).atLeast(1)) | ||
var n = 3 | ||
var n = 3 | ||
var next = mac(function () { | ||
console.log('test', n) | ||
if(--n) return | ||
var next = mac(function () { | ||
console.log('test', n) | ||
if(--n) return | ||
db.get('~seq', mac(function (err, val) { | ||
console.log('seq=', ''+val) | ||
assert.equal(Number(''+val), 3) | ||
db.createReadStream({start: '~log~', end: '~log~~'}) | ||
.on('data', function (data) { | ||
console.log(data.key.toString(), data.value.toString()) | ||
}) | ||
}).once()) | ||
}).times(3) | ||
db.get('~seq', mac(function (err, val) { | ||
console.log('seq=', ''+val) | ||
assert.equal(Number(''+val), 3) | ||
db.createReadStream({start: '~log~', end: '~log~~'}) | ||
.on('data', function (data) { | ||
console.log(data.key.toString(), data.value.toString()) | ||
}) | ||
}).once()) | ||
}).times(3) | ||
db.put('hello' , 'whatever' , next) | ||
db.put('hi' , 'message' , next) | ||
db.put('yoohoo', 'test 1, 2', next) | ||
db.put('hello' , 'whatever' , next) | ||
db.put('hi' , 'message' , next) | ||
db.put('yoohoo', 'test 1, 2', next) | ||
}) | ||
}) | ||
var rimraf = require('rimraf') | ||
var levelup = require('level-test')() | ||
var levelup = require('levelup') | ||
@@ -10,94 +10,99 @@ var Hooks = require('../') | ||
var dir ='/tmp/map-reduce-prehook-test' | ||
var db = levelup('test-prehook2') | ||
rimraf(dir, function () { | ||
levelup(dir, function (err, db) { | ||
if(err) throw err | ||
var SEQ = 0, LOGSEQ = 0 | ||
var bucket = Bucket('prehook') | ||
var SEQ = 0, LOGSEQ = 0 | ||
var bucket = Bucket('prehook') | ||
Hooks(db) | ||
Hooks(db) | ||
db.hooks.pre(/^\w/, mac(function (ch, add) { | ||
//iterate backwards so you can push without breaking stuff. | ||
var key = ch.key | ||
add({ | ||
type: 'put', | ||
key: ++SEQ, | ||
value: key.toString() | ||
}, '~log~') | ||
db.hooks.pre(/^\w/, mac(function (ch, add) { | ||
//iterate backwards so you can push without breaking stuff. | ||
var key = ch.key | ||
add({ | ||
type: 'put', | ||
key: ++SEQ, | ||
value: key.toString() | ||
}, '~log~') | ||
add({ | ||
type: 'put', key: new Buffer('~seq'), | ||
value: new Buffer(SEQ.toString()) | ||
}) | ||
add({ | ||
type: 'put', key: new Buffer('~seq'), | ||
value: new Buffer(SEQ.toString()) | ||
}) | ||
}).atLeast(1)) | ||
}).atLeast(1)) | ||
var removeLogHook = db.hooks.pre('~log', mac(function (ch, add) { | ||
//iterate backwards so you can push without breaking stuff. | ||
console.log('LOG2', ch) | ||
var key = ch.key | ||
add({ | ||
type: 'put', | ||
key: ++LOGSEQ, | ||
value: Date.now(), | ||
prefix: '~LOGSEQ~' | ||
}) | ||
var removeLogHook = db.hooks.pre('~log', mac(function (ch, add) { | ||
//iterate backwards so you can push without breaking stuff. | ||
console.log('LOG2', ch) | ||
var key = ch.key | ||
add({ | ||
type: 'put', | ||
key: ++LOGSEQ, | ||
value: Date.now(), | ||
prefix: '~LOGSEQ~' | ||
}) | ||
}).atLeast(1)) | ||
}).atLeast(1)) | ||
var n = 4 | ||
var n = 4 | ||
var next = mac(function () { | ||
console.log('test', n) | ||
if(--n) return | ||
var next = mac(function () { | ||
console.log('test', n) | ||
if(--n) return | ||
db.get('~seq', mac(function (err, val) { | ||
console.log('seq=', ''+val) | ||
assert.equal(Number(''+val), 4) | ||
db.readStream({start: '~log~', end: '~log~~'}) | ||
.on('data', function (data) { | ||
console.log(data.key.toString(), data.value.toString()) | ||
}) | ||
}).once()) | ||
db.get('~seq', mac(function (err, val) { | ||
console.log('seq=', ''+val) | ||
assert.equal(Number(''+val), 4) | ||
db.readStream({start: '~log~', end: '~log~~'}) | ||
.on('data', function (data) { | ||
console.log(data.key.toString(), data.value.toString()) | ||
}) | ||
}).once()) | ||
var all = {} | ||
var all = {} | ||
db.readStream() | ||
.on('data', function (data) { | ||
all[data.key.toString()] = data.value.toString() | ||
}) | ||
.on('end', function () { | ||
console.log(all) | ||
db.readStream() | ||
.on('data', function (data) { | ||
all[data.key.toString()] = data.value.toString() | ||
}) | ||
.on('end', function () { | ||
console.log(all) | ||
//these will be times, and will have changed. | ||
delete all['~LOGSEQ~1'] | ||
delete all['~LOGSEQ~2'] | ||
delete all['~LOGSEQ~3'] | ||
//these will be times, and will have changed. | ||
delete all['~LOGSEQ~1'] | ||
delete all['~LOGSEQ~2'] | ||
delete all['~LOGSEQ~3'] | ||
assert.deepEqual(all, { | ||
hello: 'whatever', | ||
hi: 'message', | ||
thing: 'WHATEVER', | ||
yoohoo: 'test 1, 2', | ||
'~log~1': 'hello', | ||
'~log~2': 'hi', | ||
'~log~3': 'yoohoo', | ||
'~log~4': 'thing', | ||
'~seq': '4' | ||
}) | ||
assert.deepEqual(all, { | ||
hello: 'whatever', | ||
hi: 'message', | ||
thing: 'WHATEVER', | ||
yoohoo: 'test 1, 2', | ||
'~log~1': 'hello', | ||
'~log~2': 'hi', | ||
'~log~3': 'yoohoo', | ||
'~log~4': 'thing', | ||
'~seq': '4' | ||
}) | ||
}) | ||
}) | ||
}).times(4) | ||
}).times(4) | ||
db.put('hello' , 'whatever' , next) | ||
db.put('hi' , 'message' , next) | ||
db.put('yoohoo', 'test 1, 2', next) | ||
db.put('hello' , 'whatever' , next) | ||
db.put('hi' , 'message' , next) | ||
db.put('yoohoo', 'test 1, 2', next) | ||
removeLogHook() | ||
removeLogHook() | ||
db.put('thing' , 'WHATEVER' , next) | ||
db.put('thing' , 'WHATEVER' , next) | ||
}) | ||
}) | ||
1
15647
13
392
77
- Removedlock@0.0.3
- Removeddeep-equal@0.0.0(transitive)
- Removeddefined@0.0.0(transitive)
- Removedjsonify@0.0.1(transitive)
- Removedlock@0.0.3(transitive)
- Removedtape@0.2.2(transitive)