UNPKG

@jbagatta/johnny-cache

Version:

A robust distributed dictionary for coordinating and caching expensive operations in a distributed environment

115 lines (114 loc) 3.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JohnnyCache = void 0; const types_1 = require("./types"); class JohnnyCache { constructor(lock, cacheOptions, l1Cache) { this.lock = lock; this.cacheOptions = cacheOptions; this.l1Cache = l1Cache; this.keyString = (key) => `${key}`; } asyncBuildOrRetrieve(key, buildFunc, callback, timeoutMs) { this.buildOrRetrieve(key, buildFunc, timeoutMs) .then(async (result) => { await callback(result); }) .catch(async (err) => { await callback(undefined, err); }); } async buildOrRetrieve(key, buildFunc, timeoutMs) { const keyString = this.keyString(key); const localValue = this.tryGetFromL1Cache(keyString); if (localValue) { return localValue; } let existing = await this.lock.wait(keyString, timeoutMs); if (existing.value === null) { existing = await this.lock.withLock(keyString, timeoutMs, async (existingValue) => { if (existingValue !== null) { return existingValue; } return await buildFunc(); }, timeoutMs); } this.insertIntoL1Cache(keyString, existing.value); return existing.value; } async get(key, timeoutMs) { const keyString = this.keyString(key); const localValue = this.tryGetFromL1Cache(keyString); if (localValue) { return localValue; } const obj = await this.lock.wait(keyString, timeoutMs); if (obj.value === null) { throw new Error(`Key ${key} does not exist in cache ${this.cacheOptions.name}`); } this.updateExpiry(keyString); this.insertIntoL1Cache(keyString, obj.value); return obj.value; } async status(key) { const keyString = this.keyString(key); const localValue = this.tryGetFromL1Cache(keyString); if (localValue) { return types_1.KeyStatus.EXISTS; } const entry = await this.lock.tryAcquireLock(keyString); if (!entry.acquired) { return types_1.KeyStatus.PENDING; } await this.lock.releaseLock(keyString, entry.value); if (entry.value.value === null) { return types_1.KeyStatus.EMPTY; } this.insertIntoL1Cache(keyString, entry.value.value); return types_1.KeyStatus.EXISTS; } async delete(key) { const keyString = this.keyString(key); this.l1Cache?.delete(keyString); await this.lock.delete(keyString); } async close() { this.l1Cache?.close(); this.lock.close(); } insertIntoL1Cache(key, value) { if (this.l1Cache) { if (this.cacheOptions.expiry) { this.l1Cache.set(key, value, this.cacheOptions.expiry.timeMs * 0.001); } else { this.l1Cache.set(key, value); } } } tryGetFromL1Cache(key) { const localValue = this.l1Cache?.get(key); if (!localValue) { return null; } this.updateExpiry(key); return localValue; } updateExpiry(key) { if (this.cacheOptions.expiry?.type === types_1.ExpiryType.SLIDING) { const time = this.cacheOptions.expiry.timeMs; this.l1Cache?.ttl(key, time); const update = async () => { const lock = await this.lock.tryAcquireLock(key); if (lock.acquired) await this.lock.releaseLock(key, lock.value); }; update() .then(() => { }) .catch((err) => { console.error(`Could not update expiry for key ${key} due to: ${err}`); }); } } } exports.JohnnyCache = JohnnyCache;