@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
JavaScript
"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;