UNPKG

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
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