A simple in-memory key-value cache for function execution, allowing both sync and async operations using the same methods

Overview

What is this?

An in-memory key-value cache for function execution.
For every function, you can define a key or a set of keys and an additional array of dependencies to provide dependency-based invalidation.
The library provides many functionalities, such as:

  • Sync and async function executions: if you pass an async function, the library will return a Promise.
  • Multi-key association: you can associate multiple keys to the cache entry to have a fine-grained invalidation mechanism.
  • Dependency Key Association: you can associate a set of additional keys to the cache entry to have a dependency-based invalidation mechanism.
  • Invalidation based on both primary and dependency keys: you can invalidate the cache entry using both one of its keys or one of its dependency keys.
  • Invalidation based on TTL: you can set a TTL for each cache entry.
  • Invalidation based on threshold: if the cache entry exceeds a certain threshold, the library will invalidate the entry.
  • Event emitting mechanism: you can subscribe to events to be notified when a cache entry is added, removed, invalidated, hit, etc..

The library allows both ESM (import) and CommonJS (require) importing.
Be warned the ESM version currently uses directory import so, in order to correctly execute it, you should call your node app using the experimental specifier resolution flag:
node --experimental-specifier-resolution=node index.js

import { KeyValueCache } from '@cadienvan/key-value-cache';
const cache = new KeyValueCache();
const users = await cache.exec(fetchUsers, 'users');
cache.setDependencyKeys(
  'users',
  users.map((u) => `user-${u.id}`)
);
await updateUser(2);
cache.invalidateByKey('users:2');
const users = cache.get('users'); // This will be invalidated
import { KeyValueCache } from '@cadienvan/key-value-cache';
const cache = new KeyValueCache();
const users = await fetchUsers();
cache.set('users', users, 2); // Define a threshold of 2
cache.setDependencyKeys('users', ['users:1', 'users:2', 'users:3', 'users:4']);
await updateUser(2);
cache.invalidateByKey('users:2');
const users = cache.get('users'); // This will not be invalidated as the threshold is set to 2.
await updateUser(3);
cache.invalidateByKey('users:3');
const users = cache.get('users'); // This will be invalidated as the threshold is set to 2.

Look at the demo folder in the GitHub Repository in order to have some proofs of concept.

How do I install it?

You can install it by using the following command:

npm install @cadienvan/key-value-cache

How can I use it?

You can import and instance a new KeyValueCache object as follows:

import { KeyValueCache } from '@cadienvan/key-value-cache';
const cache = new KeyValueCache();

Look at the demo folder in the GitHub Repository in order to have some proofs of concept considering both synchronous and asynchronous functions.

Does it support both sync and async functions?

Yes, it does. You can use it with both synchronous and asynchronous functions.
Look at the demo folder in the GitHub Repository for an example.
If you pass an async function to the execmethod, it will return a Promise.
If you pass a synchronous function to the exec method, it will return the value.

Which parameters are available?

You can pass a parameter to the KeyValueCache, which defines the key separator.
As the cache is based on a Map, the key separator is used to split every given array key in a single string to allow key matching.

How can I add an element to the cache?

You can add an element to the cache by using the exec method passing a function and a key string or array.
It firstly searches for the key in the cache, if it is not found, it executes the function and stores the result in the cache.
In case the key is an array, it searches for a complete match in the cache.

const cache = new KeyValueCache();
cache.exec(() => {
  return longRunningOperation();
}, ['longRunningOperation']); // This will execute the function and store the result in the cache
cache.exec(() => {
  return longRunningOperation();
}, ['longRunningOperation']); // This will return the result directly from the cache

If you want to store the result of an async function, just await the result.

const cache = new KeyValueCache();
const result = await cache.exec(
  async () => asyncLongRunningOperation(),
  ['asyncLongRunningOperation']
); // This will execute the async function and store the result in the cache

Alternativaly, if you want to store the result of the function in the cache without executing it, you can use the set method and pass a straight value.

cache.set('key', 'value');

How can I retrieve an element from the cache?

As per the question above, the exec method will return the result directly from the cache if the key is found.
If you want to retrieve the result directly from the cache, you can use the get method.

const cache = new KeyValueCache();
cache.exec(() => {
  return longRunningOperation();
}, ['longRunningOperation']); // This will execute the function and store the result in the cache
cache.get(['longRunningOperation']); // This will return the result directly from the cache

How can I define a threshold for invalidation?

You can simply pass a threshold as the third parameter of the exec and set methods.
When the threshold is reached, the item is invalidated.

const cache = new KeyValueCache();
cache.set('key', 'value', 2); // This will store the value in the cache and set the threshold to 2
cache.get('key'); // This will return the value
cache.invalidateByKey('key'); // This won't invalidate the item, but will increase the invalidation counter.
cache.get('key'); // This will return the value
cache.invalidateByKey('key'); // This will invalidate the item as the defined threshold of two is reached.
cache.get('key'); // This will return null

Remember to use the invalidateByKey method to increase the invalidation counter, while the delete method will delete the item from the cache independently from the defined threshold.

How can I define a TTL for invalidation?

You can pass a TTL as the fifht parameter of the exec and set methods.
When the TTL is reached, the item is invalidated.
Remember the TTL is lazy, so it will be evaluated only when the item is retrieved from the cache.
As long as the item isn't requested, it will stay there.
Future updates will provide some sort of background job to invalidate the items.

const cache = new KeyValueCache();
cache.set('key', 'value', 1, [], 1000); // This will store the value in the cache and set the TTL to 1000ms
cache.get('key'); // This will return the value
await sleep(1000); // This will wait for 1000ms
cache.get('key'); // This will return null

What is the difference between the invalidate and invalidateByKey methods?

The first one will search for exact match between given key and the cache key.
The second one will search both in the primary keys and in the dependency keys.

const cache = new KeyValueCache();
cache.set('key', 'value', 1, ['depKey']); // This will store the value in the cache.
cache.get('key'); // This will return the value
cache.invalidate('key'); // This will invalidate the item
cache.get('key'); // This will return null
cache.set('key', 'value', 1, ['depKey']); // This will store the value in the cache.
cache.get('key'); // This will return the value
cache.invalidateByKey('depKey'); // This will invalidate the item
cache.get('key'); // This will return null

How can I define a dependency array for invalidation?

You can simply pass a dependency array as the fourth parameter of the exec and set methods.
When the dependency array is defined, the item is invalidated when one of the dependencies is invalidated.

const cache = new KeyValueCache();
cache.set('key', 'value', 1, ['dependency1', 'dependency2']); // This will store the value in the cache and set the threshold to 2
cache.get('key'); // This will return the value
cache.invalidateByKey('dependency1'); // This will invalidate the item as the dependency1 is in the dependency array.
cache.get('key'); // This will return null

You can also set the dependency array using the setDependencies method.

cache.setDependencies('key', ['dependency1', 'dependency2']); // This will set the dependency array for the key

This could be useful when you want to firstly execute the function and use the result to set the dependencies.

How can I remove an element from the cache?

You can remove an element from the cache by using the delete method.

cache.delete('key');

If you want to invalidate every element in the cache containing a particular key, you can use the invalidateByKey method.

cache.invalidateByKey('key');

You can also pass a regex to the invalidateByKey method in order to invalidate every element in the cache containing a particular key.

cache.invalidateByKey(/key/);

How can I invalidate multiple elements from the cache?

You can invalidate multiple elements from the cache by using the invalidateByKeys method.
This will call the invalidateByKey method for every key in the array.

cache.invalidateByKeys(['key1', 'key2']);

Because of the iteration, if you invalidate two keys which are part of the same item with a threshold of two, the item will be invalidated.

How can I clear the cache?

You can clear the cache by using the clear method.

cache.clear();

Is there an event emitting mechanism?

Yes, you can use the eventBus inside the cache to listen to events.

cache.eventBus.on('onSet', (key) => {
  console.log(`The key ${key} has been saved in the cache`);
});

Please, refer to the exported Events enum to see the available events.
Two commodity methods have been provided to listen to the two most common events: onHit and onMiss, providing a filter for the given key.

cache.onHit((key) => {
  console.log(`The key ${key} has been found in the cache`);
});
cache.onMiss((key) => {
  console.log(`The key ${key} has not been found in the cache`);
});

How can I get the size of the cache?

You can get the size of the cache by using the size property.

cache.size;

How can I get the keys of the cache?

You can get the keys of the cache by using the keys method.

cache.keys;

How can I get the values of the cache?

You can get the values of the cache by using the values method.

cache.values;

How can I get the entries of the cache?

You can get the entries ([key, value] pairs) of the cache by using the entries method.

cache.entries;

How can I iterate over the cache?

You can iterate over the cache by using the forEach method.

cache.forEach((value, key) => {
  console.log(key, value);
});

How can I check if an element is in the cache?

You can check if an element is in the cache by using the has method.

cache.has('key');

Can I make a snapshot of the cache and restore it in a later time?

You can create a snapshot of the cache by using the snapshot method.

const snapshot = cache.snapshot();

You can optionally pass a boolean to the snapshot method to reset the invalidation counter of the items in the snapshot.

const snapshot = cache.snapshot(true);

You can restore the snapshot by using the restore method.

cache.restore(snapshot);

Does the class support time-based expiration?

No, it doesn't. You can use the @cadienvan/timed-cache library in order to achieve this.

How does it work under the hood?

The cache is a simple object that stores the results of a function call in memory leveraging the Map constructor.
The cache is key-based, so the results can be invalidated just by calling the correct methods. If the cache is invalidated, the function is re-run and the results are cached again.

You might also like...

Async cache with dedupe support

async-cache-dedupe async-cache-dedupe is a cache for asynchronous fetching of resources with full deduplication, i.e. the same resource is only asked

Dec 8, 2022

Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

May 18, 2022

Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

May 3, 2022

Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

Jan 3, 2023

Generate deterministic fake values: The same input will always generate the same fake-output.

Generate deterministic fake values: The same input will always generate the same fake-output.

import { copycat } from '@snaplet/copycat' copycat.email('foo') // = '[email protected]' copycat.email('bar') // = 'Thurman.Schowalter668@

Dec 30, 2022

Tenzi is a dice game. The player needs to roll dice until they are all the same. Clicking on a dice, freezes it at its current value between rolls. Best scores are saved to local storage.

Tenzi is a dice game. The player needs to roll dice until they are all the same. Clicking on a dice, freezes it at its current value between rolls.  Best scores are saved to local storage.

Roll until all dice are the same Try me! Technologies Used Description Tenzi is a dice game used to demonstrate the use of React Hooks (useState, useE

Nov 23, 2022

Persistent key/value data storage for your Browser and/or PWA, promisified, including file support and service worker support, all with IndexedDB. Perfectly suitable for your next (PWA) app.

Persistent key/value data storage for your Browser and/or PWA, promisified, including file support and service worker support, all with IndexedDB. Perfectly suitable for your next (PWA) app.

BrowstorJS 🚀 💾 🔒 Persistent key/value data storage for your Browser and/or PWA, promisified, including file support and service worker support, all

Aug 5, 2022

This simple project, show how work with async Fetch, function component and class component

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Feb 17, 2022

Bookmate - Watch changes in Chrome bookmarks, and use bookmarks as an append-only key-value store via an fs-like API.

📗 Bookmate An append-only key-value store built on Chrome bookmarks, plus an asychronous stream of Bookmark changes. For NodeJS Actual production exa

Nov 8, 2022
Comments
  • Prevent directory resolution experimental flag for ESM

    Prevent directory resolution experimental flag for ESM

    As if right now, in order to properly use the ESM import statements we must call our node script by using an experimental flag because of the directory resolution mechanism.

    We should try to prevent it and allow a simple node script execution without flags

    enhancement help wanted 
    opened by Cadienvan 2
  • Implement a test suite in Jest

    Implement a test suite in Jest

    As of right now, the only plausible testing mechanism is provided by calling npm run demo-test on 0.3.0 and above and check for errors. I'd like to implement a more robust testing system, using Jest or similars.

    enhancement help wanted 
    opened by Cadienvan 1
  • refactor  ESbuild and other minor things

    refactor ESbuild and other minor things

    • Add format to esbuild build params (not sure if CommonJS is the default)
    • Add a dev script
    • Remove tsc package
    • Add Events enum export in types folder
    • Add dist folder to exclude option in tsconfig, allows overwrite of types
    opened by tonycaputome 0
  • Allow multiple caching strategies to be implemented

    Allow multiple caching strategies to be implemented

    I'd like to bring out of the basic cache class the in-memory implementation and consequently provide a mechanism for allowing multiple caching strategies to be implemented using the Strategy Pattern.

    Ex. redis, memcached, etc..

    enhancement help wanted 
    opened by Cadienvan 5
Owner
cadienvan
Full-Stack Developer
cadienvan
Tries to execute sync/async function, returns a specified default value if the function throws

good-try Tries to execute sync/async function, returns a specified default value if the function throws. Why Why not nice-try with it's 70+ million do

Antonio Stoilkov 14 Dec 8, 2022
A remote nodejs Cache Server, for you to have your perfect MAP Cache Saved and useable remotely. Easy Server and Client Creations, fast, stores the Cache before stopping and restores it again!

remote-map-cache A remote nodejs Cache Server, for you to have your perfect MAP Cache Saved and useable remotely. Easy Server and Client Creations, fa

Tomato6966 8 Oct 31, 2022
Provides simple and the most useful methods to string operations in JavaScript / Node.js

?? Strops (String Operations) Provides simple methods for the most useful operations with substrings: - remove, replace, get from A to B, get from A t

Max Shane 3 May 20, 2022
This branch is created to make receive and send data to api using async and await methods

Microverse-Leader-Board Project from module 2 week 4 This branch is created to make receive and send data to api using async and await methods Screens

Akshitha Reddy 6 Apr 22, 2022
Leader Board is a simple project based on JavaScript programing language. The purpose of this project is to work with APIs and ASYNC & AWAIT methods. I have used vanilla JavaScript with web pack to implement this project

Leader Board - JavaScript Project Table of contents Overview The challenge Screenshot Links Project Setup commands My process Built with What I learne

Mahdi Rezaei 7 Oct 21, 2022
A string of four operations of the library, can solve the js digital calculation accuracy of scientific notation and formatting problems, support for thousands of decimal point formatting output operations

A string of four operations of the library, can solve the js digital calculation accuracy of scientific notation and formatting problems, support for thousands of decimal point formatting output operations

null 10 Apr 6, 2022
Brain wallet using both language and visual memory.

*Check out the big brain on Brett! You're a smart motherfvcker, that's right.* -- Pulp fiction ?? SUPER BRAIN WALLET ?? Use your brain power to the ma

Code's All Right ™ 4 Jun 9, 2022
🧩 TypeScript utility type in order to ensure to return only properties (not methods) containing values in primitive types such as number or boolean (not Value Objects)

?? TypeScript Primitives type TypeScript utility type in order to ensure to return only properties (not methods) containing values in primitive types

CodelyTV 82 Dec 7, 2022
Argon - extension for VS Code and plugin for Roblox allowing easy two-way sync of code and instances

About Argon is a simple two-way sync plugin for Roblox and extension for Visual Studio Code allowing developers not only to sync code but every possib

DARK 16 Dec 29, 2022
A workshop about JavaScript iteration protocols: iterator, iterable, async iterator, async iterable

JavaScript Iteration protocol workshop A workshop about JavaScript iteration protocols: iterator, iterable, async iterator, async iterable by @loige.

Luciano Mammino 96 Dec 20, 2022