Last active
October 21, 2016 08:17
-
-
Save peeke/da2f81a635bfdc12623ea01fb432824d to your computer and use it in GitHub Desktop.
Revisions
-
peeke revised this gist
Oct 20, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -37,6 +37,6 @@ setTimeout(() => store2.set('baz', { bar: 'Just waiting around for a bit' }), 40 // after 4s // => multiple --> Object { bar: "Just waiting around for a bit" } // => single --> Object { bar: "Just waiting around for a bit" } // => wait untill --> Just waiting around for a bit -
peeke revised this gist
Oct 20, 2016 . 1 changed file with 4 additions and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -80,10 +80,13 @@ class DataStore { */ delete(path) { // Get data at the location const data = this._get(this._location(path)); // Delete the key delete data[this._key(path)]; // Update data this.set(this._location(path), data); } @@ -179,7 +182,7 @@ class DataStore { } /** * Get the location of the path: the whole path excluding the last part (last part is the key) * E.g.: for 'a.b.c.d', would return 'a.b.c' * @param {String} path - The path * @returns {string} -
peeke revised this gist
Oct 4, 2016 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -13,7 +13,7 @@ debug('tail of path')(value1); // => tail of path --> Hi there debug('head of path')(value2); // => head of path --> Object { foo: { bar: { baz: { hello: { world: { message: "Hi there" } } } } } } const store2 = new DataStore(); @@ -37,6 +37,6 @@ setTimeout(() => store2.set('baz', { bar: 'Just waiting around for a bit' }), 40 // after 4s // => multiple bar baz --> Object { bar: "Just waiting around for a bit" } // => single --> Object { bar: "Just waiting around for a bit" } // => wait untill --> Just waiting around for a bit -
peeke renamed this gist
Oct 4, 2016 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
peeke revised this gist
Oct 4, 2016 . 2 changed files with 44 additions and 2 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -65,8 +65,8 @@ class DataStore { // Publish to all relevant paths const lowerPaths = this._lowerPaths(value).map(lowerPath => path + '.' + lowerPath); const higherPaths = this._higherPaths(path); const relevantPaths = [...higherPaths, ...lowerPaths]; relevantPaths.forEach(relevantPath => { observer.publish(this.publisher(relevantPath), 'change', this.get(relevantPath)); 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,42 @@ import DataStore from 'concepts/DataStore'; import observer from 'util/observer'; const debug = key => (...args) => console.log(key, '-->', ...args); const store1 = DataStore.getSingleton('store-label'); store1.set('foo.bar.baz', { hello: { world: { message: 'Hi there' } } }); const value1 = store1.get('foo.bar.baz.hello.world.message'); const value2 = store1.get('foo'); debug('tail of path')(value1); // => tail of path --> Hi there debug('head of path')(value2); // => head of path --> Object { foo: { bar: { baz: { hello: { world: { message: 'Hi there' } } } } } } const store2 = new DataStore(); const single = store2.publisher('baz'); observer.subscribe(single, 'change', debug('single')); const multiple = store2.publisher(['foo', 'bar', 'baz']); observer.subscribe(multiple, 'change', debug('multiple')); const waitUntill = store2.publisher('baz.bar'); observer.subscribe(waitUntill, 'change', debug('wait untill')); store2.set('baz', { foo: 'helloworld' }); // => single --> Object {foo: "helloworld"} store2.set('foo', 'bar'); store2.delete('baz'); store2.set('bar', 'baz'); setTimeout(() => store2.set('baz', { bar: 'Just waiting around for a bit' }), 4000); // after 4s // => multiple bar baz --> Object {bar: "Just waiting around for a bit"} // => single --> Object {bar: "Just waiting around for a bit"} // => wait untill --> Just waiting around for a bit -
peeke revised this gist
Oct 1, 2016 . 1 changed file with 75 additions and 7 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -24,6 +24,11 @@ class DataStore { } /** * Get (a copy) of the data stored at the path * @param {String} path - The path * @returns {*} - The data stored at the path */ get(path) { // In some cases we need to clone the result, because we don't want to pass result by reference. @@ -41,12 +46,18 @@ class DataStore { } /** * Update the path with a new value and publish the changes, if not silenced * @param {String} path - The path * @param {*} value - The value to set * @param {Boolean} silent - Whether to silence change publications */ set(path, value, silent = false) { // Bailout if the value doesn't change with this set if (this._get(path) === value) return; // Update path with the new value this._set(path, value); // Bailout if this is a silent set @@ -63,7 +74,10 @@ class DataStore { } /** * Clear the data from the path * @param {String} path - The path */ delete(path) { const data = this._get(this._location(path)); @@ -74,7 +88,11 @@ class DataStore { } /** * Get publischer object for a path, to manually listen for changes using the observer * @param {String} path - The path * @returns {Object} - Publisher */ publisher(path) { if (Array.isArray(path)) { @@ -89,7 +107,12 @@ class DataStore { } /** * Returns an array of publishers, on which change publications get published all given paths contain valid values * @param {Array} paths - An array of paths to watch * @returns {Array} - An array of publishers * @private */ _watcher(paths) { // Call the callback function, once all requested paths contain a defined value @@ -117,11 +140,26 @@ class DataStore { // Private /** * Return higher paths for a given path. * E.g.: 'foo.bar.baz' returns ['foo', 'foo.bar', 'foo.bar.baz'] * @param {String} path - The path * @returns {Array} - An array of paths * @private */ _higherPaths(path) { const parts = path.split('.'); return parts.filter(v => v).map((part, i) => parts.slice(0, i + 1).join('.')); } /** * Converts an object to paths. * E.g.: { foo: { bar: 'baz', baz: { msg: 'helloworld' } } } becomes: * ['foo', 'foo.bar', 'foo.baz', 'foo.baz.msg'] * @param {Object} object - The object to traverse * @returns {Array} - An array of paths * @private */ _lowerPaths(object) { if (typeof object !== 'object' || Array.isArray(object)) return []; @@ -140,16 +178,36 @@ class DataStore { } /** * Get the location of the path: the whole path excluding the last part * E.g.: for 'a.b.c.d', would return 'a.b.c' * @param {String} path - The path * @returns {string} * @private */ _location(path) { const parts = path.split('.'); return parts.slice(0, parts.length - 1).join('.'); } /** * Get the key of the path: the tail of the path * E.g.: for 'a.b.c.d', would return 'd' * @param {String} path - The path * @returns {String} - The key * @private */ _key(path) { const parts = path.split('.'); return parts[parts.length - 1]; } /** * Updates this._data with the new value at path merged in * @param {String} path - The path * @param {*} value - The value to set * @private */ _set(path, value) { // Update data! @@ -172,7 +230,14 @@ class DataStore { } /** * Get the value at the given path * When forceDefined is true, undefined values on the path are defined with {} * @param {String} path - The path * @param {Boolean} forceDefined - Whether to define undefined values * @returns {*} * @private */ _get(path, forceDefined = true) { if (!path) { @@ -186,8 +251,11 @@ class DataStore { } /** * Returns a singleton instance of this class * @param {String} store - Store identifier * @returns {V} */ static getSingleton(store) { if (!dataStores.has(store)) { -
peeke renamed this gist
Sep 30, 2016 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
peeke created this gist
Sep 30, 2016 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,203 @@ /** * A single data store for modules to communicate with. Keeping 'the truth' in a single place reduces bugs and allows * for greater seperation of concerns. * * The module can be used as a singleton through DataStore.getSingleton('key'), or on instance basis through the new keyword. * * Uses the Observer module found here: https://gist.github.com/peeke/42a3f30c2a3856c65c224cc8a47b95f9 * * @name DataStore * @author Peeke Kuepers */ import observer from 'util/observer'; // Keep track of singleton instances const dataStores = new Map(); class DataStore { constructor() { this._data = {}; this._publishers = new Map(); } get(path) { // In some cases we need to clone the result, because we don't want to pass result by reference. // This would lead to cases where changing an object outside of the DataStore actually changes the data stored within. const result = this._get(path, false); // Clone array if result is an array if (Array.isArray(result)) return result.slice(0); // Clone object if result is an object if (typeof result === 'object') return Object.assign({}, result); // Any other result is safe to pass directly return result; } set(path, value, silent = false) { // Bailout if the value doesn't change with this set if (this._get(path) === value) return; // Update data with the edited data this._set(path, value); // Bailout if this is a silent set if (silent) return; // Publish to all relevant paths const lowerPaths = this._lowerPaths(value).map(lowerPath => path + '.' + lowerPath); const relevantPaths = [...this._higherPaths(path), ...lowerPaths]; relevantPaths.forEach(relevantPath => { observer.publish(this.publisher(relevantPath), 'change', this.get(relevantPath)); }); } // Clear the path from the data delete(path) { const data = this._get(this._location(path)); delete data[this._key(path)]; this.set(this._location(path), data); } // Get publischer object for a path, to manually listen for changes using the observer publisher(path) { if (Array.isArray(path)) { return this._watcher(path); } if (!this._publishers.has(path)) { this._publishers.set(path, {}); } return this._publishers.get(path); } // Returns a publisher that publishes events when multiple paths contain valid values _watcher(paths) { // Call the callback function, once all requested paths contain a defined value const fire = () => { const args = paths.map(path => this.get(path)); const undefinedValues = args.filter(arg => typeof arg === 'undefined'); if (undefinedValues.length) return; observer.publish(publishers, 'change', ...args); }; // Listen to paths const publishers = paths.map(path => this.publisher(path)); publishers.forEach(publisher => observer.subscribe(publisher, 'change', fire)); // Update initially, to check if all paths already have a value fire(); return publishers; } // Private _higherPaths(path) { const parts = path.split('.'); return parts.filter(v => v).map((part, i) => parts.slice(0, i + 1).join('.')); } _lowerPaths(object) { if (typeof object !== 'object' || Array.isArray(object)) return []; let paths = Object.keys(object); paths.forEach(key => { const lowerPaths = this._lowerPaths(object[key]); paths = [...paths, ...lowerPaths.map(lowerPath => key + '.' + lowerPath)]; }); return paths; } _location(path) { const parts = path.split('.'); return parts.slice(0, parts.length - 1).join('.'); } _key(path) { const parts = path.split('.'); return parts[parts.length - 1]; } _set(path, value) { // Update data! const foldByPath = (update, higherPath) => { const object = {}; const deadEnd = typeof update !== 'object' || Array.isArray(update); object[this._key(higherPath)] = deadEnd ? update : Object.assign(this._get(higherPath), update); return object; }; const update = this._higherPaths(path) .reverse() .reduce(foldByPath, value); Object.assign(this._data, update); } // When forceDefined is true, undefined values on the path are defined with {} _get(path, forceDefined = true) { if (!path) { return this._data; } return path.split('.').reduce((result, part) => { if (result && typeof result[part] !== 'undefined') return result[part]; if (forceDefined) return {}; }, this._data); } // Instantiator static getSingleton(store) { if (!dataStores.has(store)) { dataStores.set(store, new DataStore(true)); } return dataStores.get(store); } } export default DataStore;