UNPKG

hyperjsondb

Version:

A proxymised Json database using hyperdb, to store key-value flat objects

216 lines (190 loc) 5.62 kB
const jsonpatch = require('fast-json-patch') const applyReducer = require('fast-json-patch').applyReducer const hyperdb = require('hyperdb') const flat = require('flat') const unflatten = require('flat').unflatten const proxymise = require('proxymise') const hypercore = require('hypercore') const EventEmitter = require('events') const toJsonSchema = require('to-json-schema') module.exports = class Hyperjsondb extends EventEmitter { constructor (...args) { super() args[1] = Object.assign({ valueEncoding: 'json' }, args[1]) this._log = hypercore(`${args[0]}/logs`, { valueEncoding: 'json' }) this._log.on('ready', () => { this._hdb = hyperdb(...args) this._hdb.ready(() => { this.emit('ready') }) }) } get hdb () { return this._hdb } get log () { return this._log } schema (path) { return this._promise(async cb => { let dataObject = await this.get(`/${path}` || '/') cb(null, toJsonSchema(dataObject)) }) } get version () { return this._promise(async cb => { this.hdb.version((error, v) => { if (error) throw new Error(error) cb(null, Buffer.from(v).toString('hex')) }) }) } put (path, data) { if (path === '/') path = '' return this._promise(async cb => { let diffs = await this.compare(path, data) for await (let diff of diffs) { console.log('DIFF', diff) if (diff) this.emit('change', path, diff) this._log.append(diff) if (diff.op === 'remove') { // console.log('removing', path) await this.remove(path) } } if (Array.isArray(data)) { for (let [index, item] of data.entries()) { await this.set(`${path}/${index}`, item) // console.log('set', `${path}/${index}`, item) } } let flattened = flat({ [path]: data }, { delimiter: '/', safe: false }) for (let kv of Object.entries(flattened)) { await this.set(kv[0], kv[1]) // console.log('setkv', kv[0], kv[1]) } cb(null, true) }) } patch (path, patches, data) { return this._promise(async cb => { try { if (!data) data = await this.get(path) let updatedData = {} var errors = jsonpatch.validate(patches, data) // console.log('errors', errors) if (errors) { cb(errors, null) throw new Error(errors) } // console.log('PATCHES', patches, '\ndata:', data, '\npath', path) updatedData = patches.reduce(applyReducer, data) // console.log('UPDATA', JSON.stringify(updatedData)) await this.put(path, updatedData) cb(null, updatedData) } catch (e) { cb(e, null) } }) } add (path, value) { return this._promise(async cb => { try { let data = await this.get(path) if (Object.entries(data).length === 0 && !Array.isArray(data)) data = [] if (Object.entries(data).length > 0 && !Array.isArray(data)) { data = [data] } let patch = { op: 'add', path: '/', value } let result = await this.patch(path, [patch], data) cb(null, result) } catch (error) { cb(error, null) } }) } replace (path, value) { return this._promise(async cb => { try { let data = await this.get(path) let patch = {} patch = { op: 'replace', path: '', value } let result = await this.patch(path, [patch], data) cb(null, result) } catch (error) { cb(error, null) } }) } compare (path, newData) { return this._promise(async cb => { let currentData = await this.get(path) let diffs = jsonpatch.compare(currentData, newData) cb(null, diffs) }) } get (path) { return this._promise(cb => { if (!path) path = '/' const stream = this._hdb.createReadStream(path, { recursive: true, gt: false }) let dataObject = {} stream.on('data', nodes => { // console.log('dataobj', nodes[0].key, nodes[0].value) dataObject[nodes[0].key] = nodes[0].value }) stream.on('end', () => { let result = unflatten(dataObject, { delimiter: '/' }) if (path === '/') cb(null, result) let getResult = this.mb(path.substring(1, path.length).split('/')) cb(null, getResult(result)) }) }) } get mb () { return p => o => p.map(c => (o = (o || {})[c])) && o } set (key, value) { return this._promise(cb => this._hdb.put(key, value, (err, val) => cb(err, val)) ) } del (key) { return this._promise(async cb => { this._hdb.del(key, (err, val) => cb(err, val)) }) } remove (path, recursive = true) { // console.log('tremove', path) return this._promise(async cb => { const deleted = [] const stream = this._hdb.createReadStream(path, { recursive, gt: false }) stream.on('data', async data => { // console.log('deleting', data[0].key) deleted.push(data[0].key) }) stream.on('end', async () => { for (let key of deleted) { await this.del(key) } cb(null, deleted) }) }) } _promise (funcwithcb) { try { return proxymise( new Promise((resolve, reject) => { funcwithcb((error, response) => { error ? reject(error) : response ? resolve(response) : resolve({}) }) }) ) } catch (error) { throw new Error(error) } } }