diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..49bac18 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,37 @@ +module.exports = { + "ecmaFeatures": { + "modules": true, + "spread" : true, + "restParams" : true + }, + // "plugins": [ + // "unicorn" + // ], + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" + }, + "extends": "eslint:recommended", + "rules": { + "indent": [ + "error", + 2 + ], + // "unicorn/no-array-instanceof": "error", + "no-console": 0, + "semi": ["error", "never"], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e61051f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/coverage/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..2f680a8 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +tests/ +test/ +*.test.js +testing/ +examples/ diff --git a/examples/example.js b/examples/example.js new file mode 100644 index 0000000..bf2a542 --- /dev/null +++ b/examples/example.js @@ -0,0 +1,39 @@ +import DataStore from '../src/datastore' + +// let circuits = await this.db.getAll('circuits') +// console.log(circuits) +// let obs = this.db.getStore('circuits').addObserver(undefined,'bogus') +// let obs = this.db.getStore('circuits').addObserver('uy0cADaONfPv38th.prop1',{prop1:'test1'}) +// // console.log(this.db.getStore('circuits')._observers) +// obs.subscribe(console.log) +// obs.next({prop1:'test1'}) +// obs.next({prop1:'test1', prop2:{prop21:'test21',prop22:'test22'}}) +// obs.next({prop1:'test1', prop2:{prop21:'test21',prop22:'test22'}}) +// obs.next({prop1:'test1', prop2:{prop21:'test21',prop22:'test22x'}}) +// obs.next({prop1:'test2'}) + + +// let obs2 = this.db.getStore('circuits').addobs2erver('.prop1',{prop1:'test1'}) +// let obs2 = this.db.getStore('circuits').get() +// console.log(this.db.getStore('circuits').subscribe(console.log)) +// // obs2.subscribe(console.log) +// obs2.next({prop1:'test1'}) +// obs2.next({prop1:'test1', prop2:{prop21:'test21',prop22:'test22'}}) +// obs2.next({prop1:'test1', prop2:{prop21:'test21',prop22:'test22'}}) +// obs2.next({prop1:'test1', prop2:{prop21:'test21',prop22:'test22x'}}) +// obs2.next({prop1:'test2'}) + + +// let obs2 = this.db.getStore('circuits').addobs2erver('.prop1',{prop1:'test1'}) +// let obs2 = this.db.getStore('circuits').get() +// console.log(this.db.getStore('circuits').subscribe(console.log)) +// // obs2.subscribe(console.log) +// obs2.next([{prop1:'test1'},{prop1:'test1'}]) +// obs2.next([{prop1:'test1'},{prop1:'test1'}]) +// obs2.next([{prop1:'test1'},{prop1:'test1'},{prop1:'test1'}]) +// obs2.next([{prop1:'test1'},{prop1:'test1'},{prop1:'test1'}]) +// obs2.next([{prop1:'test1'},{prop1:'test1'},{prop1:'test2'}]) +// obs2.next([]) +// obs2.next() + +// console.log(this.db.getStore('circuits')._observers) diff --git a/package.json b/package.json new file mode 100644 index 0000000..c0e065d --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "@uci-utils/nedb-rx", + "version": "0.1.2", + "description": "A Class for promise Nedb with reactivity", + "main": "src/datastore.js", + "scripts": { + "example": "node -r esm examples/example", + "example:dev": "./node_modules/.bin/nodemon -r esm examples/example", + "test": "./node_modules/.bin/mocha -r esm --timeout 30000" + }, + "author": "David Kebler", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/uCOMmandIt/.git" + }, + "keywords": [ + "node.js" + ], + "bugs": { + "url": "https://github.com/uCOMmandIt/uci-utils/issues" + }, + "homepage": "https://github.com/uCOMmandIt/uci-utils#readme", + "dependencies": { + "deep-equal": "^2.0.1", + "get-value": "^3.0.1", + "is-plain-object": "^3.0.0", + "nedb-promises": "^4.0.1", + "rxjs": "^6.5.4", + "set-value": "^3.0.1" + }, + "devDependencies": { + "chai": "^4.2.0", + "esm": "^3.2.25", + "mocha": "^6.2.2", + "nodemon": "^1.19.4" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..5f38a03 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +### uCOMmandIt Ready Class + +Watches a list of events and/or promises that resolve to a boolean. When all a met it emits true otherwise at each event it emits false diff --git a/src/datastore.js b/src/datastore.js new file mode 100644 index 0000000..ea1f167 --- /dev/null +++ b/src/datastore.js @@ -0,0 +1,153 @@ +import NEDBP from 'nedb-promises' +import { BehaviorSubject as Subject, from } from 'rxjs' +import { distinctUntilChanged } from 'rxjs/operators' +import _getObj from 'get-value' +import _setObj from 'set-value' +import equal from 'deep-equal' + +// ammend/react datastore class +export default class DataStore extends NEDBP { + constructor(opts){ + super(opts) + this.name = opts.name + this._observers = {} + // console.log('collection observer', this.name) + // this.addObserver() // add observer for collection + // console.log('collection observer') + } + + get indexes () { + return this.__original.indexes + } + + get nedb() { + return this.__original + } + + compact () { + this.__original.persistence.compactDatafile() + } + + autoCompact(interval) { + this.__original.persistence.setAutocompactionInterval(interval) + } + + addObserver (path,value) { + let [doc,prop] = (path||'').split('.') + let subj = new Subject(value||{}) + let obs = from(subj).pipe( + distinctUntilChanged((p,c)=>{ + // console.log(p,c) + // TODO allow deep prop looking and setting + if (prop) return equal(_getObj(p,prop),_getObj(c,prop)) + return (equal(p,c)) + }) + ) + this._set(path,obs) + return obs + } + + _updateObserver (id,doc) { + // let obs = this.has(id) + // if (!obs) return false + // console.log('observer next',obs) + // obs.next(value) + // return true + } + + _set(path,obs) { + let type + ({type,path} = getObserverType.call(this,path)) + _setObj(type,path,obs) + } + + get (path) { + let obs = this.has(path) + if (!obs) obs = this.addObserver(path) + return obs + } + + has (path) { + let type + ({type,path} = getObserverType.call(this,path)) + return _getObj(type,path) + } + + subscribe (path,handler) { + if (typeof path === 'function') handler=path;path=null + let obs = this.get(path) + if (!obs) return false + if (typeof handler==='function') return obs.subscribe(handler) + else return obs + } + + // async remove(arg){ + async remove(obj,opts){ + const id = obj._id + // console.log(this.name,'remove',id,opts) + this._updateObserver(id,null) + this._updateObserver() + await super.remove(...arguments) + } + + async update(obj,doc){ + const id = obj._id + // console.log(this.name, 'updating',doc._id||doc.id,doc.name||doc.hostname) + this._updateObserver(id,doc) + await super.update(...arguments) + } + + async insert() { + // if (!Array.isArray(docs)) docs = [docs] + // console.log(this.name, 'inserting',docs.map(doc=>[doc._id||doc.id,doc.name||doc.hostname])) + let docs = await super.insert(...arguments) + // console.log(this.name,docs) + if (!Array.isArray(docs)) docs = [docs] + docs.forEach(doc =>{ + // console.log('inserted', this.name, doc._id,doc.name) + // if !_getthis.addObserver(doc._id,doc) + }) + } + + async getID(id,prop) { // returns null for no results + let doc = {} + if (id) { + doc = await this.findOne({$or:[{_id:id},{hid:id}]}) + if (!doc) { + if (arguments.length ===2) return null + // id might be missing so id is prop, use collection name as id + let doc = await this.findOne({_id:this.name}) + return (doc ||{})[id]!=null ? doc[id] : doc + } + if (!prop) return doc + return (doc ||{})[prop]!=null? doc[prop] : null + } + // if no id passed look for id and prop same as store name + doc = await this.findOne({_id:this.name}) + return (doc ||{})[this.name]!=null ? doc[this.name] : doc + } + + async getAll() { + return await this.find({$not:{_id:'schema'}}) // return all but the schema + } + + async patch (id, patch) { + let curDoc = await this.getID(id) + return await this.update({_id:id}, Object.assign(curDoc,patch)) + } + +} // end datastore class + + +function getObserverType(path) { + let doc;let prop; let type + if (typeof path!=='string') ({doc,prop}=path||{}) + else [doc,prop] = (path||'').split(/[./:]/) + path = [doc,prop].filter(Boolean).join('.') + if (!prop) + if (!doc) type = this._observers + else type = this._observers.doc + else type = this._observers.prop + path = path==='' ? 'col' : (path || 'col') + return {path:path, type:type} +}