Add {,Memory}Store
* src/store/Store.js: Add interface. * src/store/MemoryStore.js: Add class. * test/store/MemoryStoreTest.js: Add test case.master
parent
6dd5b83a27
commit
d8d44130e8
|
@ -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 );
|
||||
}
|
||||
} );
|
|
@ -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' ],
|
||||
} );
|
|
@ -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 );
|
||||
} );
|
||||
} );
|
||||
} );
|
Loading…
Reference in New Issue