hyperjsondb
Version:
A proxymised Json database using hyperdb, to store key-value flat objects
216 lines (190 loc) • 5.62 kB
JavaScript
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)
}
}
}