Introduction
AppCache is a simple in memory key-value cache for global state in Node apps. AppCache takes away some of the pain of managing this data yourself by enforcing immutability on objects (AppCache clones object on get and set by default), read only setting and validators.
AppCache is no replacement for datastores like Redis or Memcached but can be used for things like:
- Storing a password entered in stdin
- Storing an average, total, min or max value of a database column for quick calculations on new inserts
- Storing bits of data that where other parts of the app needs to be notified when it updates
Getting Started
Installation
Installing AppCache can be done using npm.
npm install --save app-cache
After installation AppCache can be included in your project. Since AppCache is a singleton it does not need to be instantiated using the new
keyword.
const appCache = require('app-cache');
appCache.create('myValue', 'fooBar', {
readOnly: true
});
console.log(appCache.get('myValue')); // => fooBar
/* readOnly values cannot be set */
appCache.set('myValue', 'baz'); // => Throws a TypeError
Example
In the next example we use AppCache to keep track of the maximum value of the column myCol
in a database table. The model needs the max value to create a calculated value on update or create. We also want to notify the connected clients when this value changes.
const appCache = require('app-cache');
// First we create a maxValue key
// If a key is not created the set method will create the key first
appCache.create('maxValue', 0);
// Get max value from database with a fictional ORM
myModel.getAll(function (err, rows) {
appCache.set('maxValue', rows.reduce(function (max, value) {
if (max > value.myCol) {
return max;
}
return value.myCol
}, 0));
});
// When maxValue changes send the value to the connected clients
appCache.on('set', function (key, value) {
if (key === 'maxValue') {
// Send max value to all clients
sockets.send('maxValue', value);
}
});
// fictional ORM entry update method
myModal.onUpdate(function () {
if (this.myCol > appCache.get('maxValue')) {
appCache.set('maxValue', this.myCol);
}
this.computedValue = appCache.get('maxValue') / 4;
});
Testing
AppCache comes with a complete test suite. Just clone the repo from github the run all tests.
git clone https://github.com/Barry127/app-cache.git
cd app-cache
npm install
npm run test
AppCache
Create
appCache.create(key, [value], [options]) : <Object>appCache
Create a key for the store. Throws AppCacheKeyError
if key already exists.
Arguments
key
String
|
Key property name to create. |
value
*
|
Initial value. |
options
Object | Boolean
|
Options for this key, see options below. If value is a boolean the value will be set to readOnly . |
Options
mutable
Boolean
|
Are Object values mutable? If true the value is set by reference instead of a deep clone. Default value: false
|
readOnly
Boolean
|
Is the value read only? If true the value cannot be changed using the set method. Default value: false
|
validator
Function
|
Validate function which takes 1 argument the new value. If this function returns true the value will be set else the new value will be ignored. Default value: () => true
|
Delete
appCache.delete(key) : <Object>appCache
Delete a key from the store.
key
String
|
Key property name to delete. |
Get
appCache.get(key) : <Any>value
Get a value by key from the store.
key
String
|
Key property name to get value from. If key does not exist undefined will be returned. |
getOptions
appCache.getOptions(key) : <Object>options
Get a clone of the the options by key from the store.
key
String
|
Key property name to get options from. If key does not exist null will be returned. |
Set
appCache.set(key, value) : <Object>appCache
Set a value to key to the store. If the key does not exist create
will be called with the default options. Throws a TypeError
if the key has the options readOnly
set to true
.
key
String
|
Key property name to set value to. |
value
*
|
Value to set. |
Advanced Topics
Events
AppCache extends the NodeJS EventEmitter
. It emits events for all the methods.
Event Signatures
create
|
function (key, value)
|
delete
|
function (key)
|
get
|
function (key, value)
|
getOptions
|
function (key, options)
|
set
|
function (key, newValue)
|
Mutability
AppCache will deep clone all Object values by default. This prevents unwanted changes in the store:
const appCache = require('app-cache');
var testObject = {
foo: 'bar'
};
appCache.create('myObj');
appCache.set('myObj', testObject);
// appCache creates new deep cloned objects by default
console.log(appCache.get('myObj') === testObject) // => false
// This does not affect the stored value
testObject.bar = 'baz';
console.log(appCache.get('myObj').bar) // => undefined
Sometimes you might want to store objects by reference instead of a deep clone of the value. In that case set the mutable
option to true
.
const appCache = require('app-cache');
var user = {
name: 'John',
lastName: 'Doe'
};
appCache.create('myUser', user, { mutable: true });
// The value will be stored by reference
console.log(appCache.get('myUser') === user) // => true
user.name = 'Jane';
console.log(appCache.get('myUser').name) // => "Jane";
Warning!
Using mutable objects might lead to unexpected bugs or state.
- You can't be sure if a property is changed somewhere else in your App.
- AppCache will not emit events if a mutable Object is changed.
- Mutable objects can't be readOnly for sure because they can be changed without the
set
method.
Read Only
There might be cases when a value only needs to be set once and cannot be changed afterwards. The ReadOnly
setting can be used for this.
const appCache = require('app-cache');
// We want to keep track of when the API_KEY is used so we store it in our store.
appCache.create('myApiKey', process.env.API_KEY, { readOnly: true });
appCache.on('get', function (key) {
if (key === 'myApiKey') {
// API_KEY is used
}
});
// Try to set a new value to myApiKey
appCache.set('myApiKey', 'XXX'); // => Throws a TypeError
Deleting Read Only values
Although Read Only values cannot be changed after they are created, the can be deleted.
If you need to make sure a key is not deleted and recreated with a different value keep track of the delete event for the specific key and throw an Error if the key is deleted.
Validators
AppCache allows to validate a value before it is set by setting a function to the validate
option.
const appCache = require('app-cache');
AppCache.create('aNumber', 0, {
validate: function (newValue) {
if (typeof newValue !== 'number') {
return false;
}
if (Number.isNaN(newValue)) {
return false;
}
return true;
}
});
appCache.set('aNumber', 42);
console.log(appCache.get('aNumber')); // => 42
appCache.set('aNumber', Math.pow(-1)); // Math.pow(-1) === NaN
console.log(appCache.get('aNumber')) // => 42
appCache.set('aNumber', 2 + 5);
console.log(appCache.get('aNumber')) // => 7
appCache.set('aNumber', 'twelve');
console.log(appCache.get('aNumber')) // => 7
Keep the following things in mind when using validators:
- The initial value in the create method will not be validated.
- If a value is valid the function must explicitly return
true
otherwise the value will be flagged invalid.
- When a value is invalid there will be no
set
event emitted.
Lisence
MIT License
Copyright (c) 2017 Barry de Kleijn
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.