UNPKG

pathdb

Version:

Database built on levelup/leveldb that stores javascript objects as a series of paths and values.

201 lines (177 loc) 4.41 kB
var pathos = require('pathos'), clone = require('clone'), EventEmitter = require('events').EventEmitter, createError = require('errno').create LevelUPError = createError('LevelUPError') NotFoundError = createError('NotFoundError', LevelUPError), Deferred = require('deferential'); NotFoundError.prototype.notFound = true; NotFoundError.prototype.status = 404; module.exports = pathdb; function pathdb(db) { if (db && !db.pathdb) { db.pathdb = { put: put.bind(null, db), get: get.bind(null, db), del: del.bind(null, db), watch: watch.bind(null, db), batch: batch.bind(null, db) }; } return db; } function put(db, key, value, cb) { if (typeof cb === 'undefined' && typeof value === 'function' || typeof value === 'undefined') { cb = value; value = key; key = []; } var d = Deferred(); var resolver = d.resolver(); children(db, key, function (err, data) { if (err) return resolver(err); var batch = data.map(function (k) { return { type: 'del', key: k, }; }); var paths = pathos(value); paths.forEach(function (e) { batch.push({ type: 'put', key: key.concat(e.key), value: e.value }); }); db.batch(batch, resolver); }); return d.nodeify(cb); } function get(db, key, cb) { if (typeof cb === 'undefined' && typeof key === 'function' || typeof key === 'undefined') { cb = key; key = []; } var d = Deferred(); var resolver = d.resolver(); var result = [] db.createReadStream({ start: key.concat(null), end: key.concat(undefined) }) .on('data', function (data) { result.push(data); }) .once('error', function (err) { resolver(err); }) .once('end', function () { if (result.length) { var leaves = result.map(function (item) { item.key = item.key.slice(key.length); return item; }); var obj = pathos.build(leaves); resolver(null, obj); } else { resolver(new NotFoundError('Path not found in database ' + JSON.stringify(key))); } }); return d.nodeify(cb); } function del(db, key, cb) { if (typeof cb === 'undefined' && typeof key === 'function' || typeof key === 'undefined') { cb = key; key = []; } var d = Deferred(); var resolver = d.resolver(); children(db, key, function (err, data) { if (err) return resolver(err); var batch = data.map(function (k) { return { type: 'del', key: k }; }); db.batch(batch, resolver); }); return d.nodeify(cb); } function children(db, key, cb) { if (typeof cb === 'undefined') { cb = key; key = []; } var batch = [] db.createReadStream({ keys: true, values: false, start: key.concat(null), end: key.concat(undefined) }) .on('data', function (key) { batch.push(key); }) .once('error', function (err) { cb(err); }) .once('end', function () { cb(null, batch); }); } function watch(db, key, def) { if (typeof def === 'undefined') { def = {}; } var ee = new EventEmitter(); db.pathdb.get(key, function (err, value) { if (typeof value === 'undefined') { value = def; } ee.emit('value', value); db.on('batch', function (batch) { var relevant = batch.filter(function (item) { return startsWith(item.key, key); }) .map(function (item) { // don't change the batch data which is shared on 'batch' events var _item = clone(item); _item.key = _item.key.slice(key.length); return _item; }); ee.emit('change', relevant); }); }); return ee; } function startsWith(haystack, prefix) { var i = 0; while (i < haystack.length && i < prefix.length) { if (prefix[i] !== haystack[i]) return false; i++; } return (i === prefix.length) } function batch(db, key, data, cb) { if (typeof cb === 'undefined' && typeof data === 'function' || typeof data === 'undefined') { cb = data; data = key; key = []; } var d = Deferred(); var resolver = d.resolver(); var _data = data.map(function (item) { item.key = key.concat(item.key); return item; }); db.batch(_data, resolver); return d.nodeify(cb); }