transitory
Version:
In-memory cache with high hit rates via LFU eviction. Supports time-based expiration, automatic loading and metrics.
234 lines • 6.86 kB
JavaScript
import { BoundedCache } from '../cache/bounded/BoundedCache';
import { BoundlessCache } from '../cache/boundless/BoundlessCache';
import { ExpirationCache } from '../cache/expiration/ExpirationCache';
import { DefaultLoadingCache } from '../cache/loading/DefaultLoadingCache';
import { MetricsCache } from '../cache/metrics/MetricsCache';
/**
* Builder for cache instances.
*/
export class CacheBuilderImpl {
constructor() {
this.optMetrics = false;
}
/**
* Set a listener that will be called every time something is removed
* from the cache.
*
* @param listener -
* removal listener to use
* @returns self
*/
withRemovalListener(listener) {
this.optRemovalListener = listener;
return this;
}
/**
* Set the maximum number of items to keep in the cache before evicting
* something.
*
* @param size -
* number of items to keep
* @returns self
*/
maxSize(size) {
this.optMaxSize = size;
return this;
}
/**
* Set a function to use to determine the size of a cached object.
*
* @param weigher -
* function used to weight objects
* @returns self
*/
withWeigher(weigher) {
if (typeof weigher !== 'function') {
throw new Error('Weigher should be a function that takes a key and value and returns a number');
}
this.optWeigher = weigher;
return this;
}
/**
* Change to a cache where get can also resolve values if provided with
* a function as the second argument.
*
* @returns self
*/
loading() {
return new LoadingCacheBuilderImpl(this, null);
}
/**
* Change to a loading cache, where the get-method will return instances
* of Promise and automatically load unknown values.
*
* @param loader -
* function used to load objects
* @returns self
*/
withLoader(loader) {
if (typeof loader !== 'function') {
throw new Error('Loader should be a function that takes a key and returns a value or a promise that resolves to a value');
}
return new LoadingCacheBuilderImpl(this, loader);
}
/**
* Set that the cache should expire items after some time.
*
* @param time -
* max time in milliseconds, or function that will be asked per key/value
* for expiration time
* @returns self
*/
expireAfterWrite(time) {
let evaluator;
if (typeof time === 'function') {
evaluator = time;
}
else if (typeof time === 'number') {
evaluator = () => time;
}
else {
throw new Error('expireAfterWrite needs either a maximum age as a number or a function that returns a number');
}
this.optMaxWriteAge = evaluator;
return this;
}
/**
* Set that the cache should expire items some time after they have been read.
*
* @param time -
* max time in milliseconds, or function will be asked per key/value
* for expiration time
* @returns self
*/
expireAfterRead(time) {
let evaluator;
if (typeof time === 'function') {
evaluator = time;
}
else if (typeof time === 'number') {
evaluator = () => time;
}
else {
throw new Error('expireAfterRead needs either a maximum age as a number or a function that returns a number');
}
this.optMaxNoReadAge = evaluator;
return this;
}
/**
* Activate tracking of metrics for this cache.
*
* @returns self
*/
metrics() {
this.optMetrics = true;
return this;
}
/**
* Build and return the cache.
*
* @returns cache
*/
build() {
let cache;
if (typeof this.optMaxWriteAge !== 'undefined' || typeof this.optMaxNoReadAge !== 'undefined') {
/*
* Requested expiration - wrap the base cache a bit as it needs
* custom types, a custom weigher if used and removal listeners
* are added on the expiration cache instead.
*/
let parentCache;
if (this.optMaxSize) {
parentCache = new BoundedCache({
maxSize: this.optMaxSize,
weigher: createExpirableWeigher(this.optWeigher)
});
}
else {
parentCache = new BoundlessCache({});
}
cache = new ExpirationCache({
maxNoReadAge: this.optMaxNoReadAge,
maxWriteAge: this.optMaxWriteAge,
removalListener: this.optRemovalListener,
parent: parentCache
});
}
else {
if (this.optMaxSize) {
cache = new BoundedCache({
maxSize: this.optMaxSize,
weigher: this.optWeigher,
removalListener: this.optRemovalListener
});
}
else {
cache = new BoundlessCache({
removalListener: this.optRemovalListener
});
}
}
if (this.optMetrics) {
// Collect metrics if requested
cache = new MetricsCache({
parent: cache
});
}
return cache;
}
}
class LoadingCacheBuilderImpl {
constructor(parent, loader) {
this.parent = parent;
this.loader = loader;
}
withRemovalListener(listener) {
this.parent.withRemovalListener(listener);
return this;
}
maxSize(size) {
this.parent.maxSize(size);
return this;
}
withWeigher(weigher) {
this.parent.withWeigher(weigher);
return this;
}
loading() {
throw new Error('Already building a loading cache');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
withLoader(loader) {
throw new Error('Already building a loading cache');
}
expireAfterWrite(time) {
this.parent.expireAfterWrite(time);
return this;
}
expireAfterRead(time) {
this.parent.expireAfterRead(time);
return this;
}
metrics() {
this.parent.metrics();
return this;
}
build() {
return new DefaultLoadingCache({
loader: this.loader,
parent: this.parent.build()
});
}
}
/**
* Helper function to create a weigher that uses an Expirable object.
*
* @param w -
* @returns weigher
*/
function createExpirableWeigher(w) {
if (!w)
return null;
return (key, node) => w(key, node.value);
}
//# sourceMappingURL=CacheBuilder.js.map