mastercache
Version:
Multi-tier cache module for Node.js. Redis, Upstash, CloudfareKV, File, in-memory and others drivers
242 lines (204 loc) • 5.99 kB
text/typescript
import { hexoid } from 'hexoid';
import { resolveTtl } from '../../helpers';
import type { Duration, RawCommonOptions } from '../../types/main';
const toId = hexoid(12);
export class CacheEntryOptions {
/**
* The options that were passed to the constructor
*/
#options: RawCommonOptions;
/**
* Unique identifier that will be used when logging
* debug information.
*/
id: string;
/**
* Logical TTL is when the value is considered expired
* but still can be in the cache ( Grace period )
*/
logicalTtl?: number;
/**
* Physical TTL is the time when value will be automatically
* removed from the cache. This is the Grace period
* duration
*/
physicalTtl?: number;
/**
* Early expiration TTL is when the value should be
* refreshed in the background.
*/
earlyExpireTtl?: number;
/**
* Timeouts for the cache operations
*/
timeouts?: {
soft?: number
hard?: number
};
/**
* Resolved grace period options
*/
gracePeriod: { enabled: false } | { enabled: true; duration?: number; fallbackDuration?: number };
/**
* Max time to wait for the lock to be acquired
*/
lockTimeout?: number;
constructor(options: RawCommonOptions = {}, defaults: Partial<RawCommonOptions> = {}) {
this.id = toId();
const timeouts = { ...defaults.timeouts, ...options.timeouts };
this.#options = {
...defaults,
...options,
gracePeriod: { ...defaults.gracePeriod, ...options.gracePeriod } as any,
timeouts: Object.keys(timeouts).length ? timeouts : undefined,
};
this.logicalTtl = this.#resolveLogicalTtl();
this.physicalTtl = this.#resolvePhysicalTtl();
this.earlyExpireTtl = this.#resolveEarlyExpireTtl();
this.timeouts = this.#resolveTimeouts();
this.gracePeriod = this.#resolveGracePeriod();
this.lockTimeout = resolveTtl(this.#options.lockTimeout, null);
}
/**
* Resolve the grace period options
*/
#resolveGracePeriod() {
if (!this.#options.gracePeriod || !this.#options.gracePeriod.enabled) {
return { enabled: false };
}
return {
enabled: true,
duration: resolveTtl(this.#options.gracePeriod.duration),
fallbackDuration: resolveTtl(this.#options.gracePeriod.fallbackDuration),
};
}
/**
* Resolve the timeouts to a duration in milliseconds
*/
#resolveTimeouts() {
const timeouts = this.#options.timeouts;
if (!timeouts) return undefined;
return {
soft: resolveTtl(timeouts.soft, null),
hard: resolveTtl(timeouts.hard, null),
};
}
/**
* Early expiration is received as a percentage of the
* logical TTL. We need to convert it to a duration
* in milliseconds.
*/
#resolveEarlyExpireTtl() {
const percentage = this.#options.earlyExpiration;
/**
* Ignore invalid values
*/
if (!percentage || percentage <= 0 || percentage >= 1) {
return undefined;
}
/**
* If no logical ttl, that means value will never expire
* So no early expiration
*/
if (!this.logicalTtl) return undefined;
return this.logicalTtl * percentage;
}
/**
* Returns a new instance of `CacheItemOptions` with the same
* options as the current instance, but with any provided
* options overriding the current
*
* For performance reasons, if no options are provided, the
* current instance is returned
*/
cloneWith(options?: Partial<RawCommonOptions>) {
return options ? new CacheEntryOptions(options, this.#options) : this;
}
/**
* Resolve the logical TTL to a duration in milliseconds
*/
#resolveLogicalTtl() {
return resolveTtl(this.#options.ttl);
}
/**
* Resolve the physical TTL to a duration in milliseconds
*
* If grace period is not enabled then the physical TTL
* is the same as the logical TTL
*/
#resolvePhysicalTtl() {
return this.isGracePeriodEnabled
? resolveTtl(this.#options.gracePeriod!.duration)
: this.logicalTtl;
}
get isGracePeriodEnabled() {
return this.#options.gracePeriod?.enabled;
}
get suppressL2Errors() {
return this.#options.suppressL2Errors;
}
/**
* Set a new logical TTL
*/
setLogicalTtl(ttl: Duration) {
this.#options.ttl = ttl;
this.logicalTtl = this.#resolveLogicalTtl();
this.physicalTtl = this.#resolvePhysicalTtl();
this.earlyExpireTtl = this.#resolveEarlyExpireTtl();
return this;
}
/**
* Compute the logical TTL timestamp from now
*/
logicalTtlFromNow() {
if (!this.logicalTtl) return undefined;
return Date.now() + this.logicalTtl;
}
/**
* Compute the physical TTL timestamp from now
*/
physicalTtlFromNow() {
if (!this.physicalTtl) return undefined;
return Date.now() + this.physicalTtl;
}
/**
* Compute the early expiration TTL timestamp from now
*/
earlyExpireTtlFromNow() {
if (!this.earlyExpireTtl) return undefined;
return Date.now() + this.earlyExpireTtl!;
}
/**
* Compute the lock timeout we should use for the
* factory
*/
factoryTimeout(hasFallbackValue: boolean) {
if (!this.timeouts) return undefined;
/**
* If grace period is enabled, we should use the soft timeout.
* Because if the soft timeout is reached, we will
* return the stale value.
*/
if (hasFallbackValue && this.isGracePeriodEnabled && this.timeouts.soft) {
return this.timeouts.soft;
}
return this.timeouts.hard;
}
/**
* Compute the maximum time we should wait for the
* lock to be acquired
*/
getApplicableLockTimeout(hasFallbackValue: boolean) {
if (this.lockTimeout) {
return this.lockTimeout;
}
/**
* If we have a fallback value and grace period is enabled,
* that means we should wait at most for the soft timeout
* duration.
*/
if (hasFallbackValue && this.isGracePeriodEnabled && this.timeouts?.soft) {
return this.timeouts.soft;
}
}
}