Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <gerwitzm@lovullo.com>2016-12-21 11:20:45 -0500
committerMike Gerwitz <gerwitzm@lovullo.com>2017-01-03 09:10:53 -0500
commitd8d44130e888d43b2b8c47a526f9dc074726d024 (patch)
treef06a46999f72d179736bc0bca4a622ff7ca17320
parent6dd5b83a271dea1917d2e59c2c49d58e785aa9a8 (diff)
downloadliza-d8d44130e888d43b2b8c47a526f9dc074726d024.tar.gz
liza-d8d44130e888d43b2b8c47a526f9dc074726d024.tar.bz2
liza-d8d44130e888d43b2b8c47a526f9dc074726d024.zip
Add {,Memory}Store
* src/store/Store.js: Add interface. * src/store/MemoryStore.js: Add class. * test/store/MemoryStoreTest.js: Add test case.
-rw-r--r--src/store/MemoryStore.js134
-rw-r--r--src/store/Store.js91
-rw-r--r--test/store/MemoryStoreTest.js185
3 files changed, 410 insertions, 0 deletions
diff --git a/src/store/MemoryStore.js b/src/store/MemoryStore.js
new file mode 100644
index 0000000..74bfbdf
--- /dev/null
+++ b/src/store/MemoryStore.js
@@ -0,0 +1,134 @@
+/**
+ * Generic key/value store in local memory
+ *
+ * Copyright (C) 2016 LoVullo Associates, Inc.
+ *
+ * This file is part of the Liza Data Collection Framework
+ *
+ * Liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var Class = require( 'easejs' ).Class,
+ Store = require( './Store' );
+
+
+/**
+ * Generic key/value store with bulk clear
+ *
+ * @todo There's a lot of overlap between this concept and that of the
+ * Bucket. Maybe have the Bucket layer atop of simple Store
+ * interface as a step toward a new, simpler Bucket
+ * implementation. This was not implemented atop of the Bucket
+ * interface because its haphazard implementation would
+ * overcomplicate this.
+ *
+ * @example
+ * let s = MemoryStore();
+ *
+ * s.add( 'foo', 'bar' );
+ * s.add( 'baz', 'quux' );
+ * s.get( 'foo' ); // bar
+ * s.get( 'baz' ); // quux
+ *
+ * s.clear();
+ * s.get( 'foo' ); // undefined
+ * s.get( 'baz' ); // undefined
+ */
+module.exports = Class( 'MemoryStore' )
+ .implement( Store )
+ .extend(
+{
+ /**
+ * Key/value store
+ *
+ * @type {Object}
+ */
+ 'private _store': {},
+
+
+ /**
+ * Add item to cache under `key` with value `value`
+ *
+ * @param {string} key cache key
+ * @param {*} value value for key
+ *
+ * @return {Store} self
+ */
+ 'virtual public add': function( key, value )
+ {
+ this._store[ key ] = value;
+
+ return this;
+ },
+
+
+ /**
+ * Retrieve item from cache under `key`
+ *
+ * @param {string} key cache key
+ *
+ * @return {*} `key` value
+ */
+ 'virtual public get': function( key )
+ {
+ return this._store[ key ];
+ },
+
+
+ /**
+ * Clear all items in cache
+ *
+ * @return {Store} self
+ */
+ 'virtual public clear': function()
+ {
+ this._store = {};
+
+ return this;
+ },
+
+
+ /**
+ * Fold (reduce) all cached values
+ *
+ * This provides a way to iterate through all cached values and
+ * their keys while providing a useful functional result (folding).
+ *
+ * The order of folding is undefined.
+ *
+ * The ternary function `callback` is of the same form as
+ * {@link Array#fold}: the first argument is the value of the
+ * accumulator (initialized to the value of `initial`; the second
+ * is the cached item; and the third is the key of that item.
+ *
+ * @param {function(*,*,string=)} callback folding function
+ * @param {*} initial initial value for accumulator
+ *
+ * @return {*} folded value (final accumulator value)
+ */
+ 'public reduce': function( callback, initial )
+ {
+ var store = this._store;
+
+ return Object.keys( store )
+ .map( function( key )
+ {
+ return [ key, store[ key ] ];
+ } )
+ .reduce( function( accum, values )
+ {
+ return callback( accum, values[ 1 ], values[ 0 ] );
+ }, initial );
+ }
+} );
diff --git a/src/store/Store.js b/src/store/Store.js
new file mode 100644
index 0000000..3daf69b
--- /dev/null
+++ b/src/store/Store.js
@@ -0,0 +1,91 @@
+/**
+ * Generic key/value store
+ *
+ * Copyright (C) 2016 LoVullo Associates, Inc.
+ *
+ * This file is part of the Liza Data Collection Framework
+ *
+ * Liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var Interface = require( 'easejs' ).Interface;
+
+
+/**
+ * Generic key/value store with bulk clear
+ *
+ * @todo There's a lot of overlap between this concept and that of the
+ * Bucket. Maybe have the Bucket layer atop of simple Store
+ * interface as a step toward a new, simpler Bucket
+ * implementation. This was not implemented atop of the Bucket
+ * interface because its haphazard implementation would
+ * overcomplicate this.
+ */
+module.exports = Interface( 'Store',
+{
+ /**
+ * Add item to store under `key` with value `value`
+ *
+ * The promise will be fulfilled with an object containing the
+ * `key` and `value` added to the store; this is convenient for
+ * promises.
+ *
+ * @param {string} key store key
+ * @param {*} value value for key
+ *
+ * @return {Promise} promise to add item to store
+ */
+ 'public add': [ 'key', 'value' ],
+
+
+ /**
+ * Retrieve item from store under `key`
+ *
+ * The promise will be rejected if the key is unavailable.
+ *
+ * @param {string} key store key
+ *
+ * @return {Promise} promise for the key value
+ */
+ 'public get': [ 'key' ],
+
+
+ /**
+ * Clear all items in store
+ *
+ * @return {Promise} promise to clear store
+ */
+ 'public clear': [],
+
+
+ /**
+ * Fold (reduce) all stored values
+ *
+ * This provides a way to iterate through all stored values and
+ * their keys while providing a useful functional result (folding).
+ *
+ * The order of folding is undefined.
+ *
+ * The ternary function `callback` is of the same form as
+ * {@link Array#fold}: the first argument is the value of the
+ * accumulator (initialized to the value of `initial`; the second
+ * is the stored item; and the third is the key of that item.
+ *
+ * @param {function(*,*,string=)} callback folding function
+ * @param {*} initial initial value for accumulator
+ *
+ * @return {Promise} promise of a folded value (final accumulator value)
+ */
+ 'public reduce': [ 'callback', 'initial' ],
+} );
diff --git a/test/store/MemoryStoreTest.js b/test/store/MemoryStoreTest.js
new file mode 100644
index 0000000..a152911
--- /dev/null
+++ b/test/store/MemoryStoreTest.js
@@ -0,0 +1,185 @@
+/**
+ * Test case for MemoryStore
+ *
+ * Copyright (C) 2016 LoVullo Associates, Inc.
+ *
+ * This file is part of the Liza Data Collection Framework
+ *
+ * Liza is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+var store = require( '../../' ).store,
+ expect = require( 'chai' ).expect,
+ Class = require( 'easejs' ).Class,
+ Trait = require( 'easejs' ).Trait,
+ Sut = store.MemoryStore;
+
+
+describe( 'store.MemoryStore', () =>
+{
+ describe( '#add', () =>
+ {
+ it( 'adds item to store when missing', () =>
+ {
+ const sut = Sut();
+ const item = {};
+
+ expect(
+ sut.add( 'foo', item )
+ .get( 'foo' )
+ ).to.equal( item );
+ } );
+
+
+ it( 'replaces item in store if existing', () =>
+ {
+ const sut = Sut();
+ const item = {};
+
+ expect(
+ sut.add( 'foo', [] )
+ .add( 'foo', item )
+ .get( 'foo' )
+ ).to.equal( item );
+ } );
+ } );
+
+
+ // most things implicitly tested above
+ describe( '#get', () =>
+ {
+ it( 'returns undefined if store item does not exist', () =>
+ {
+ expect( Sut().get( 'unknown' ) ).to.be.undefined;
+ } );
+ } );
+
+
+ describe( '#clear', () =>
+ {
+ it( 'removes all items from store', () =>
+ {
+ const sut = Sut();
+ const keys = [ 'foo', 'bar', 'baz' ];
+
+ keys.forEach( key => sut.add( key ) );
+
+ // should remove all items
+ sut.clear();
+
+ keys.forEach( key => expect( sut.get( key ) ).to.be.undefined );
+ } );
+
+
+ it( 'returns self', () =>
+ {
+ const sut = Sut();
+
+ expect( sut.clear() ).to.equal( sut );
+ } );
+ } );
+
+
+ describe( 'with mixin', () =>
+ {
+ it( 'allows overriding add', done =>
+ {
+ const expected_key = 'foo';
+ const expected_value = {};
+
+ Sut.use(
+ Trait.extend( Sut,
+ {
+ 'override add'( key, value )
+ {
+ expect( key ).to.equal( expected_key );
+ expect( value ).to.equal( expected_value );
+ done();
+ }
+ } )
+ )().add( expected_key, expected_value );
+ } );
+
+
+ it( 'allows overriding get', done =>
+ {
+ const expected_key = 'bar';
+
+ Sut.use(
+ Trait.extend( Sut,
+ {
+ 'override get'( key )
+ {
+ expect( key ).to.equal( expected_key );
+ done();
+ }
+ } )
+ )().get( expected_key );
+ } );
+
+
+ it( 'allows overriding clear', done =>
+ {
+ Sut.use(
+ Trait.extend( Sut,
+ {
+ 'override clear'( key )
+ {
+ done();
+ }
+ } )
+ )().clear();
+ } );
+ } );
+
+
+ describe( '#reduce', () =>
+ {
+ it( 'folds each stored item', () =>
+ {
+ const StubSut = Sut.extend(
+ {
+ sum()
+ {
+ return this.reduce(
+ ( accum, item, key ) =>
+ {
+ // correct key for item?
+ expect( item ).to.equal( vals[ key ] );
+
+ return accum + item;
+ },
+ 5
+ );
+ }
+ } );
+
+ const sut = StubSut();
+ const vals = {
+ one: 1,
+ two: 2,
+ three: 3,
+ };
+
+ Object.keys( vals ).forEach(
+ ( key, i ) => sut.add( key, vals[ key ] )
+ );
+
+ // implicitly tests initial
+ expect( sut.sum() ).to.equal( 11 );
+ } );
+ } );
+} );