simple-in-memory-cache
v0.5.1
Published
A simple in-memory cache, for nodejs and the browser, with time based expiration policies.
Maintainers
Readme
simple-in-memory-cache
A simple, typed, in-memory cache for nodejs and the browser with time-based expiration policies.
install
npm install --save simple-in-memory-cacheusage
set and get
import { createCache } from 'simple-in-memory-cache';
const { set, get } = createCache();
set('purpose of life', 42);
const purpose = get('purpose of life'); // returns 42expiration
items in the cache expire after 5 minutes by default.
change the default expiration on cache creation:
const { set, get } = createCache({ expiration: { minutes: 10 } });override expiration per item:
set('ice cream state', 'solid', { expiration: { seconds: 30 } });set an item to never expire:
set('speed of light', 299792458, { expiration: null });invalidation
invalidate a cached item with set(key, undefined):
set('purpose of life', 42);
get('purpose of life'); // returns 42
set('purpose of life', undefined);
get('purpose of life'); // returns undefinedkeys
list all non-expired keys in the cache:
const { set, keys } = createCache();
set('a', 1);
set('b', 2);
keys(); // returns ['a', 'b']conditional writes (put-if-absent + compare-and-set)
gate a write on a version precondition, so it succeeds only when the key is in the state you
expect. on a precondition miss the write throws SimpleCacheConditionError (never a silent
clobber). this makes the cache usable as a coordination primitive (locks, leases, stampede
control, optimistic concurrency).
read the current opaque version token for a key with version(key):
import { createCache, SimpleCacheConditionError } from 'simple-in-memory-cache';
const { set, get, version } = createCache<string>();put-if-absent — write only if no live entry exists (condition: { version: null }):
set('lock', 'worker-a', { condition: { version: null } }); // ✓ wins — key was open
try {
set('lock', 'worker-b', { condition: { version: null } }); // ✋ throws — key held
} catch (error) {
if (!(error instanceof SimpleCacheConditionError)) throw error;
// worker-b lost the race, loudly — no silent overwrite
}compare-and-set — write only if the stored version matches a token from a prior
observation. version() returns string | undefined while a condition wants string | null, so
bridge an absent read with ?? null (absent → put-if-absent, present → compare-and-set):
set('counter', '1');
const v = version('counter'); // capture the token now
set('counter', '2', { condition: { version: v ?? null } }); // ✓ still at v
// ✋ if someone else wrote 'counter' since you read v, this throwsversion-checked get — read the value only if it is still the version you last saw:
const v = version('counter');
const current = get('counter', { condition: { version: v ?? null } });
// ✓ returns the value IF it is still at version v
// ✋ throws SimpleCacheConditionError if the version drifted (the value you were about to
// act on is stale)compare-and-delete — release a lock only if it is still yours (set(key, undefined, …)):
set('lock', 'worker-a', { condition: { version: null } });
const mine = version('lock');
set('lock', undefined, { condition: { version: mine ?? null } }); // release, only if still mine⚠️ the token must come from a prior observation (a
get/version/acquire taken before the write). a freshversion(key)read taken immediately before its own conditional write always matches the current token and so guards no state — the whole point of a condition is to compare against a value you saw earlier.
⚠️ a successful conditional write also mints a fresh token, so the token you used to authorize it is immediately dead. in a renew-loop (mutex renewal), re-observe
version(key)before each renewal — do not carry the token you acquired at lock time into the second renewal, or it will throw:set('lock', me, { condition: { version: null } }); // acquire let held = version('lock'); // token now set('lock', me, { condition: { version: held ?? null } }); // renew #1 → mints a new token held = version('lock'); // re-observe before the next renewal set('lock', me, { condition: { version: held ?? null } }); // renew #2 ✓ (the old token would throw)
⚠️ always bridge an absent read with
?? null.version()yieldsstring | undefined, but a condition wantsstring | null. typescript callers are safe — a bare{ version: v }wherev: string | undefinedfails to compile against the condition type. but plain-js/browser callers get no such guard: a{ condition: { version: someUndefinedVar } }meant as put-if-absent falls into the compare-and-set branch and always throws, since anundefinedtoken never equals the found version. use{ version: v ?? null }so an absent read means put-if-absent, not a guaranteed miss.
types
the cache is fully typed:
import { createCache, SimpleInMemoryCache } from 'simple-in-memory-cache';
const cache: SimpleInMemoryCache<number> = createCache<number>();
cache.set('answer', 42);
const answer: number | undefined = cache.get('answer');api
createCache<T>(options?)
creates a new cache instance.
options:
expiration?: IsoDuration | null— default expiration for items (default:{ minutes: 5 })
returns: SimpleInMemoryCache<T>
SimpleInMemoryCache<T>
get(key, options?): T | undefined— retrieve an item (returnsundefinedif absent or expired). withoptions.condition, verify the stored version before it yields the value; on a mismatch throwSimpleCacheConditionError(version-checked get).set(key, value, options?): void— store an item (or invalidate withvalue: undefined). withoptions.condition, gate the write:{ version: null }= put-if-absent,{ version: '<token>' }= compare-and-set; on a precondition miss throwSimpleCacheConditionError.version(key): string | undefined— read the current opaque version token for a live key (undefinedif absent or expired). treat the token as equality-only; never parse or order it.keys(): string[]— list all non-expired keys
options (both get and set):
condition?: { version: string | null }— the version precondition (see conditional writes above)
options (set only):
expiration?: IsoDuration | null— override the item's expiration (null= never expire)
SimpleCacheConditionError
thrown by get/set when a condition.version precondition is not met. extends ConstraintError
(from helpful-errors) — a caller-must-fix constraint (exit code 2). carries
{ key, condition, found } metadata for diagnosis.
