mastercache
Version:
Multi-tier cache module for Node.js. Redis, Upstash, CloudfareKV, File, in-memory and others drivers
84 lines (71 loc) • 2.6 kB
text/typescript
import pTimeout from 'p-timeout';
import type { MutexInterface } from 'async-mutex';
import type { Locks } from './locks';
import * as exceptions from '../errors';
import { events } from '../events/index';
import type { CacheStack } from './stack/cache-stack';
import type { GetSetFactory } from '../types/helpers';
import type { CacheStackWriter } from './stack/cache-stack-writer';
import type { CacheEntryOptions } from './cache-entry/cache-entry-options';
/**
* Factory Runner is responsible for executing factories
*/
export class FactoryRunner {
#stack: CacheStack;
#stackWriter: CacheStackWriter;
#locks: Locks;
constructor(stack: CacheStack, stackWriter: CacheStackWriter, locks: Locks) {
this.#stack = stack;
this.#stackWriter = stackWriter;
this.#locks = locks;
}
async saveBackgroundFactoryResult(
key: string,
factoryResult: unknown,
options: CacheEntryOptions,
lockReleaser: MutexInterface.Releaser,
) {
await this.#stackWriter.set(key, factoryResult, options);
this.#locks.release(key, lockReleaser);
}
async writeFactoryResult(
key: string,
item: unknown,
options: CacheEntryOptions,
lockReleaser: MutexInterface.Releaser,
) {
await this.#stackWriter.set(key, item, options);
this.#stack.emit(new events.CacheMiss(key, this.#stack.name));
this.#stack.logger.trace({ key, cache: this.#stack.name, opId: options.id }, 'cache miss');
this.#locks.release(key, lockReleaser);
}
async run(
key: string,
factory: GetSetFactory,
hasFallback: boolean,
options: CacheEntryOptions,
lockReleaser: MutexInterface.Releaser,
) {
const timeoutDuration = options.factoryTimeout(hasFallback);
const timeoutException =
timeoutDuration === options.timeouts?.hard
? exceptions.E_FACTORY_HARD_TIMEOUT
: exceptions.E_FACTORY_SOFT_TIMEOUT;
const promisifiedFactory = async () => {
return await factory({ setTtl: (ttl) => options.setLogicalTtl(ttl) });
};
const factoryPromise = promisifiedFactory();
const factoryResult = await pTimeout(factoryPromise, {
milliseconds: timeoutDuration ?? Number.POSITIVE_INFINITY,
fallback: async () => {
factoryPromise
.then((result) => this.saveBackgroundFactoryResult(key, result, options, lockReleaser))
.catch(() => {})
.finally(() => this.#locks.release(key, lockReleaser));
throw new timeoutException();
},
});
await this.writeFactoryResult(key, factoryResult, options, lockReleaser);
return factoryResult;
}
}