Last active
May 11, 2017 07:13
-
-
Save devel-pa/e104d518b07dda068b55f9a549112cca to your computer and use it in GitHub Desktop.
Redux middleware for GunDB. Low perfomance. Supports arrays.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Created by Paul on 10/7/2016. | |
| * GunDB v0.3 | |
| * inspired by https://github.com/pgte/pouch-redux-middleware | |
| * TODO: issue: stores duplicates in an object path.docs for removing redundant operations | |
| * - can be implemented with redux history, but same lot of data | |
| * - checking with gun, don't know how, yet | |
| * - another method is eventually to store timestamps (updated_at) in redux and only that in path.docs | |
| * TODO: issue: not quite async as it should be in certain areas | |
| */ | |
| /*global Gun*/ | |
| /*eslint no-undef: "off"*/ | |
| import jPath from 'json-path'; | |
| import equal from 'deep-equal'; | |
| import extend from 'xtend'; | |
| export default function GunMiddleware(gun, _options) { | |
| // let count = 1; | |
| // let countMap = 1; | |
| // let countState = 1; | |
| let paths = _options || []; | |
| if (! paths.length) { | |
| throw new Error('GunMiddleware: no paths'); | |
| } | |
| const defaultSpec = { | |
| path: '.', | |
| remove, | |
| put, | |
| reduxRemove, | |
| reduxUpdate, | |
| reduxInsert, | |
| docs: {}, | |
| actions: { | |
| remove: defaultAction('remove'), | |
| update: defaultAction('update'), | |
| insert: defaultAction('insert') | |
| } | |
| }; | |
| let gunner = {}; | |
| paths = paths.map(function(path) { | |
| let spec = extend({}, defaultSpec, path); | |
| spec.actions = extend({}, defaultSpec.actions, spec.actions); | |
| spec.docs = {}; | |
| gunner[path.path] = gun.get(path.path.slice(1)); | |
| return spec; | |
| }); | |
| function toRedux(val, id, db, k) { | |
| let ret = id ? { id } : {}; | |
| //for nested objects I don't need an ID | |
| if(k) id = k; | |
| //reentering in Gun | |
| let base = Gun.is(val) ? val : db.path(id); | |
| //can't use `base` as I can't get values. Why? | |
| let keys = Object.keys(val); | |
| keys.shift(); | |
| if(keys[0] === "[") { | |
| keys.shift(); | |
| let arr = []; | |
| keys.forEach(function (key) { | |
| let current = val[key]; | |
| if (current['#']) { | |
| base.path(key).val(function (value) { | |
| arr.push(toRedux(value, void 0, base, key)); | |
| }); | |
| } | |
| }); | |
| return arr; | |
| } | |
| else { | |
| keys.forEach(function (key) { | |
| let current = val[key]; | |
| if (current && current['#']) { | |
| base.path(key).val(function (value) { | |
| ret[key] = toRedux(value, void 0, base, key); | |
| }); | |
| } | |
| else { | |
| ret[key] = current; | |
| } | |
| }); | |
| } | |
| return ret; | |
| } | |
| function array2object(arr) { | |
| var obj = { "[": true }; | |
| Gun.list.map(arr, function (v, f, t) { | |
| if (Gun.list.is(v)) { | |
| obj[Gun.text.random()] = array2object(v); | |
| return; | |
| } | |
| if (Gun.obj.is(v) && v !== null) { | |
| obj[Gun.text.random()] = toGun(v); | |
| return; | |
| } | |
| if(v === false) { | |
| ret[key] = v; | |
| return; | |
| } | |
| obj[Gun.text.random()] = v || null; | |
| }); | |
| return obj; | |
| } | |
| function toGun(val) { | |
| // let ret = extend({}, val); | |
| let ret = {}; | |
| let keys = Object.keys(val); | |
| keys.forEach(function (key) { | |
| let crt = val[key]; | |
| if(Gun.list.is(crt)) { | |
| ret[key] = array2object(crt); | |
| return; | |
| } | |
| if(Gun.obj.is(crt) && crt !== null) { | |
| ret[key] = toGun(crt); | |
| return; | |
| } | |
| if(crt === false) { | |
| ret[key] = crt; | |
| return; | |
| } | |
| ret[key] = crt || null; | |
| }); | |
| delete ret.id; | |
| return ret; | |
| } | |
| function listen(path, dispatch) { | |
| let db = gunner[path.path]; | |
| db.map(function (val, id) { | |
| // console.log('map: ', count++, countMap++); | |
| if(! val) { | |
| if(path.docs[id]) { | |
| // delete path.docs[id]; //useful? or in fact generates issues? | |
| return path.reduxRemove({ id }, dispatch); | |
| } | |
| else return void 0; | |
| } | |
| return onDbChange(path, toRedux(val, id, db), dispatch) | |
| }); | |
| } | |
| function processNewStateForPath(path, state) { | |
| let db = gunner[path.path]; | |
| let docs = jPath.resolve(state, path.path); | |
| if (docs && docs.length) { | |
| docs.forEach(function(docs) { | |
| // console.log('each: ', count++, countState++); | |
| let diffs = differences(path.docs, docs); | |
| diffs.put.forEach(doc => path.put(doc, db, path)); | |
| diffs.deleted.forEach(doc => path.remove(doc, db, path)); | |
| }); | |
| } | |
| } | |
| function put(doc, db, path) { | |
| path.docs[doc.id] = extend({}, doc ); | |
| db.path(doc.id).put(toGun(doc)); | |
| } | |
| function remove(doc, db, path) { | |
| delete path.docs[doc.id]; | |
| db.path(doc.id).put(null); | |
| } | |
| function reduxRemove(doc, dispatch) { | |
| dispatch(this.actions.remove(doc)); | |
| } | |
| function reduxInsert(doc, dispatch) { | |
| dispatch(this.actions.insert(doc)); | |
| } | |
| function reduxUpdate(doc, dispatch) { | |
| dispatch(this.actions.update(doc)); | |
| } | |
| const middleware = store => { | |
| paths.forEach((path) => listen(path, store.dispatch)); | |
| return next => action => { | |
| let returnValue = next(action); | |
| let newState = store.getState(); | |
| paths.forEach(path => processNewStateForPath(path, newState)); | |
| return returnValue; | |
| } | |
| }; | |
| return middleware; | |
| } | |
| function differences(oldDocs, newDocs) { | |
| let result = { | |
| put: [], | |
| deleted: Object.keys(oldDocs).map(oldDocId => oldDocs[oldDocId]), | |
| }; | |
| newDocs.forEach(function(newDoc) { | |
| let id = newDoc.id; | |
| if (! id) { | |
| warn('doc with no id'); | |
| } | |
| result.deleted = result.deleted.filter(doc => doc.id !== id); | |
| let oldDoc = oldDocs[id]; | |
| if (! oldDoc || ! equal(oldDoc, newDoc)) { | |
| result.put.push(newDoc); | |
| } | |
| }); | |
| return result; | |
| } | |
| function onDbChange(path, changed, dispatch) { | |
| let changeDoc = changed; | |
| if(path.changeFilter && (! path.changeFilter(changeDoc))) { | |
| return; | |
| } | |
| let oldDoc = path.docs[changeDoc.id]; | |
| path.docs[changeDoc.id] = extend({}, changeDoc ); | |
| if (oldDoc) { | |
| if(! equal(oldDoc, changeDoc)) { | |
| path.reduxUpdate(changeDoc, dispatch); | |
| } | |
| } else { | |
| path.reduxInsert(changeDoc, dispatch); | |
| } | |
| } | |
| function warn(what) { | |
| let fn = console.warn || console.log; | |
| if (fn) { | |
| fn.call(console, what); | |
| } | |
| } | |
| function defaultAction(action) { | |
| return function() { | |
| throw new Error('no action provided for ' + action); | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment