AppCache

Introduction

Known Vulnerabilities

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:

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:

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.