diff --git a/src/builder/CacheBuilder.ts b/src/builder/CacheBuilder.ts index 1005c83..61e700c 100644 --- a/src/builder/CacheBuilder.ts +++ b/src/builder/CacheBuilder.ts @@ -86,6 +86,10 @@ export class CacheBuilderImpl implements CacheBuilder) { this.optRemovalListener = listener; @@ -95,6 +99,10 @@ export class CacheBuilderImpl implements CacheBuilder implements CacheBuilder) { if(typeof weigher !== 'function') { @@ -115,6 +127,8 @@ export class CacheBuilderImpl implements CacheBuilder { return new LoadingCacheBuilderImpl(this, null); @@ -123,6 +137,10 @@ export class CacheBuilderImpl implements CacheBuilder): LoadingCacheBuilder { if(typeof loader !== 'function') { @@ -133,6 +151,11 @@ export class CacheBuilderImpl implements CacheBuilder) { let evaluator; @@ -149,6 +172,11 @@ export class CacheBuilderImpl implements CacheBuilder): this { let evaluator; @@ -165,6 +193,8 @@ export class CacheBuilderImpl implements CacheBuilder implements CacheBuilder; @@ -282,6 +314,12 @@ class LoadingCacheBuilderImpl implements LoadingCacheBuild } } +/** + * Helper function to create a weigher that uses an Expirable object. + * + * @param w - + * @returns weigher + */ function createExpirableWeigher(w: Weigher | undefined): Weigher> | null { if(! w) return null; diff --git a/src/builder/index.ts b/src/builder/index.ts index b28b4c7..99db6ef 100644 --- a/src/builder/index.ts +++ b/src/builder/index.ts @@ -6,6 +6,9 @@ export { CacheBuilder, LoadingCacheBuilder } from './CacheBuilder'; /** * Create a new cache via a builder. + * + * @returns + * builder of a cache */ export function newCache(): CacheBuilder { return new CacheBuilderImpl(); diff --git a/src/cache/Cache.ts b/src/cache/Cache.ts index cda91de..16ff72c 100644 --- a/src/cache/Cache.ts +++ b/src/cache/Cache.ts @@ -24,6 +24,13 @@ export interface Cache { /** * Store a value tied to the specified key. Returns the previous value or * `null` if no value currently exists for the given key. + * + * @param key - + * key to store value under + * @param value - + * value to store + * @returns + * current value or `null` */ set(key: K, value: V): V | null; @@ -31,6 +38,11 @@ export interface Cache { * Get the cached value for the specified key if it exists. Will return * the value or `null` if no cached value exist. Updates the usage of the * key. + * + * @param key - + * key to get + * @returns + * current value or `null` */ getIfPresent(key: K): V | null; @@ -41,24 +53,31 @@ export interface Cache { * * In many cases `has(key)` is a better option to see if a key is present. * - * @param key + * @param key - * the key to check + * @returns + * value associated with key or `null` */ peek(key: K): V | null; /** * Check if the given key exists in the cache. * - * @param key + * @param key - + * key to check + * @returns + * `true` if value currently exists, `false` otherwise */ has(key: K): boolean; /** - * Delete a value in the cache. Returns the removed value or `null` if + * Delete a value in the cache. Returns the deleted value or `null` if * there was no value associated with the key in the cache. * - * @param key + * @param key - * the key to delete + * @returns + * deleted value or `null` */ delete(key: K): V | null; @@ -68,9 +87,13 @@ export interface Cache { clear(): void; /** - * Get all of the keys in the cache as an `Array`. Can be used to iterate - * over all of the values in the cache, but be sure to protect against values - * being removed during iteration due to time-based expiration if used. + * Get all of the keys in the cache as an array. Can be used to iterate + * over all of the values in the cache, but be sure to protect against + * values being removed during iteration due to time-based expiration if + * used. + * + * @returns + * snapshot of keys */ keys(): K[]; diff --git a/src/cache/CacheSPI.ts b/src/cache/CacheSPI.ts index 94d9254..98e53dc 100644 --- a/src/cache/CacheSPI.ts +++ b/src/cache/CacheSPI.ts @@ -8,7 +8,7 @@ import { ON_REMOVE, ON_MAINTENANCE } from './symbols'; */ export interface CacheSPI { /** - * Called when a key is removed from the cache. Intended to be overriden + * Called when a key is removed from the cache. Intended to be overridden * so that the value and removal reason can be modified. */ [ON_REMOVE]?: RemovalListener; diff --git a/src/cache/WrappedCache.ts b/src/cache/WrappedCache.ts index de9e6e4..5b90121 100644 --- a/src/cache/WrappedCache.ts +++ b/src/cache/WrappedCache.ts @@ -30,50 +30,147 @@ export abstract class WrappedCache extends AbstractCache extends AbstractCache impl /** * Get the maximum size this cache can be. + * + * @returns + * maximum size of the cache */ public get maxSize() { return this[DATA].maxSize; @@ -296,6 +299,9 @@ export class BoundedCache extends AbstractCache impl /** * Get the current size of the cache. + * + * @returns + * items currently in the cache */ public get size() { return this[DATA].values.size; @@ -303,13 +309,24 @@ export class BoundedCache extends AbstractCache impl /** * Get the weighted size of all items in the cache. + * + * @returns + * the weighted size of all items in the cache */ public get weightedSize() { return this[DATA].weightedSize; } /** - * Cache and associate a value with the given key. + * Store a value tied to the specified key. Returns the previous value or + * `null` if no value currently exists for the given key. + * + * @param key - + * key to store value under + * @param value - + * value to store + * @returns + * current value or `null` */ public set(key: K, value: V): V | null { const data = this[DATA]; @@ -377,7 +394,14 @@ export class BoundedCache extends AbstractCache impl } /** - * Get a previously cached value. + * Get the cached value for the specified key if it exists. Will return + * the value or `null` if no cached value exist. Updates the usage of the + * key. + * + * @param key - + * key to get + * @returns + * current value or `null` */ public getIfPresent(key: K) { const data = this[DATA]; @@ -429,6 +453,18 @@ export class BoundedCache extends AbstractCache impl return node.value; } + /** + * Peek to see if a key is present without updating the usage of the + * key. Returns the value associated with the key or `null` if the key + * is not present. + * + * In many cases `has(key)` is a better option to see if a key is present. + * + * @param key - + * the key to check + * @returns + * value associated with key or `null` + */ public peek(key: K) { const data = this[DATA]; const node = data.values.get(key); @@ -436,7 +472,13 @@ export class BoundedCache extends AbstractCache impl } /** - * Delete any value associated with the given key from the cache. + * Delete a value in the cache. Returns the deleted value or `null` if + * there was no value associated with the key in the cache. + * + * @param key - + * the key to delete + * @returns + * deleted value or `null` */ public delete(key: K) { const data = this[DATA]; @@ -476,13 +518,21 @@ export class BoundedCache extends AbstractCache impl } /** - * Check if a certain value exists in the cache. + * Check if the given key exists in the cache. + * + * @param key - + * key to check + * @returns + * `true` if value currently exists, `false` otherwise */ public has(key: K) { const data = this[DATA]; return data.values.has(key); } + /** + * Clear the cache removing all of the entries cached. + */ public clear() { const data = this[DATA]; @@ -507,15 +557,38 @@ export class BoundedCache extends AbstractCache impl } } + /** + * Get all of the keys in the cache as an array. Can be used to iterate + * over all of the values in the cache, but be sure to protect against + * values being removed during iteration due to time-based expiration if + * used. + * + * @returns + * snapshot of keys + */ public keys(): K[] { this[MAINTENANCE](); return Array.from(this[DATA].values.keys()); } + /** + * Request clean up of the cache by removing expired entries and + * old data. Clean up is done automatically a short time after sets and + * deletes, but if your cache uses time-based expiration and has very + * sporadic updates it might be a good idea to call `cleanUp()` at times. + * + * A good starting point would be to call `cleanUp()` in a `setInterval` + * with a delay of at least a few minutes. + */ public cleanUp() { this[MAINTENANCE](); } + /** + * Get metrics for this cache. Returns an object with the keys `hits`, + * `misses` and `hitRate`. For caches that do not have metrics enabled + * trying to access metrics will throw an error. + */ public get metrics(): Metrics { throw new Error('Metrics are not supported by this cache'); } @@ -665,7 +738,7 @@ export class BoundedCache extends AbstractCache impl * chunks. At every invocation it currently moves a maximum of 1000 nodes * around. * - * @param data + * @param data - */ function adaptiveAdjustment(data: BoundedCacheData) { /* @@ -688,7 +761,7 @@ function adaptiveAdjustment(data: BoundedCacheData) * Evict nodes from the protected segment to the probation segment if there * are too many nodes in the protected segment. * - * @param data + * @param data - */ function evictProtectedToProbation(data: BoundedCacheData) { /* @@ -711,6 +784,7 @@ function evictProtectedToProbation(data: BoundedCacheData< * enough samples to do a step and if so perform a simple hill climbing to * find the new adjustment. * + * @param data - * @returns * `true` if an adjustment occurred, `false` otherwise */ @@ -754,7 +828,7 @@ function calculateAdaptiveAdjustment(data: BoundedCacheDat * segment. The method will then move nodes from the probation and protected * segment the window segment. * - * @param data + * @param data - */ function increaseWindowSegmentSize(data: BoundedCacheData) { if(data.protected.maxSize === 0) { @@ -825,7 +899,7 @@ function increaseWindowSegmentSize(data: BoundedCacheData< * will be moved from the window segment into the probation segment, where * they are later moved to the protected segment when they are accessed. * - * @param data + * @param data - */ function decreaseWindowSegmentSize(data: BoundedCacheData) { if(data.window.maxSize <= 1) { diff --git a/src/cache/bounded/CountMinSketch.ts b/src/cache/bounded/CountMinSketch.ts index 1e8e6ad..9ed28e9 100644 --- a/src/cache/bounded/CountMinSketch.ts +++ b/src/cache/bounded/CountMinSketch.ts @@ -2,10 +2,25 @@ import { KeyType } from '../KeyType'; import { hashcode } from './hashcode'; +/** + * Helper function to calculate the closest power of two to N. + * + * @param n - + * input + * @returns + * closest power of two to `n` + */ function toPowerOfN(n: number) { return Math.pow(2, Math.ceil(Math.log(n) / Math.LN2)); } +/** + * Calculates a component of the hash. + * + * @param a0 - + * @returns + * hash + */ function hash2(a0: number) { let a = (a0 ^ 61) ^ (a0 >>> 16); a = a + (a << 3); @@ -15,6 +30,13 @@ function hash2(a0: number) { return a; } +/** + * Tiny helper to perform a multiply that's slightly safer to use for hashing. + * + * @param a - + * @param b - + * @returns a * b + */ function safeishMultiply(a: number, b: number) { return ((a & 0xffff) * b) + ((((a >>> 16) * b) & 0xffff) << 16); } diff --git a/src/cache/bounded/hashcode.ts b/src/cache/bounded/hashcode.ts index 458749d..baf81ac 100644 --- a/src/cache/bounded/hashcode.ts +++ b/src/cache/bounded/hashcode.ts @@ -1,12 +1,26 @@ const C1 = 0xcc9e2d51; const C2 = 0x1b873593; +/** + * Tiny helper to perform a multiply that's slightly safer to use for hashing. + * + * @param a - + * @param b - + * @returns a * b + */ function safeishMultiply(a: number, b: number) { return ((a & 0xffff) * b) + ((((a >>> 16) * b) & 0xffff) << 16); } /** * Utility for calculating stable hashcodes for keys used in a cache. + * + * @param obj - + * object to hash + * @param seed - + * seed to hash with + * @returns + * hash code */ export function hashcode(obj: string | number | boolean | null, seed = 0) { switch(typeof obj) { diff --git a/src/cache/boundless/BoundlessCache.ts b/src/cache/boundless/BoundlessCache.ts index 2b5bdc0..f2c427e 100644 --- a/src/cache/boundless/BoundlessCache.ts +++ b/src/cache/boundless/BoundlessCache.ts @@ -54,25 +54,45 @@ export class BoundlessCache extends AbstractCache im } /** - * Get the maximum size this cache can be. + * The maximum size the cache can be. Will be -1 if the cache is unbounded. + * + * @returns + * maximum size, always `-1` */ public get maxSize() { return -1; } /** - * Get the current size of the cache. + * The current size of the cache. + * + * @returns + * entries in the cache */ public get size() { return this[DATA].values.size; } + /** + * The size of the cache weighted via the activate estimator. + * + * @returns + * entries in the cache + */ public get weightedSize() { return this.size; } /** - * Cache and associate a value with the given key. + * Store a value tied to the specified key. Returns the previous value or + * `null` if no value currently exists for the given key. + * + * @param key - + * key to store value under + * @param value - + * value to store + * @returns + * current value or `null` */ public set(key: K, value: V): V | null { const data = this[DATA]; @@ -97,7 +117,14 @@ export class BoundlessCache extends AbstractCache im } /** - * Get a value from this cache if it has been previously cached. + * Get the cached value for the specified key if it exists. Will return + * the value or `null` if no cached value exist. Updates the usage of the + * key. + * + * @param key - + * key to get + * @returns + * current value or `null` */ public getIfPresent(key: K): V | null { const data = this[DATA]; @@ -105,6 +132,18 @@ export class BoundlessCache extends AbstractCache im return value === undefined ? null : value; } + /** + * Peek to see if a key is present without updating the usage of the + * key. Returns the value associated with the key or `null` if the key + * is not present. + * + * In many cases `has(key)` is a better option to see if a key is present. + * + * @param key - + * the key to check + * @returns + * value associated with key or `null` + */ public peek(key: K): V | null { const data = this[DATA]; const value = data.values.get(key); @@ -112,7 +151,13 @@ export class BoundlessCache extends AbstractCache im } /** - * Delete any value associated with the given key from the cache. + * Delete a value in the cache. Returns the deleted value or `null` if + * there was no value associated with the key in the cache. + * + * @param key - + * the key to delete + * @returns + * deleted value or `null` */ public delete(key: K): V | null { const data = this[DATA]; @@ -136,7 +181,12 @@ export class BoundlessCache extends AbstractCache im } /** - * Check if a certain value exists in the cache. + * Check if the given key exists in the cache. + * + * @param key - + * key to check + * @returns + * `true` if value currently exists, `false` otherwise */ public has(key: K) { const data = this[DATA]; @@ -160,7 +210,13 @@ export class BoundlessCache extends AbstractCache im } /** - * Get all of the keys currently in the cache. + * Get all of the keys in the cache as an array. Can be used to iterate + * over all of the values in the cache, but be sure to protect against + * values being removed during iteration due to time-based expiration if + * used. + * + * @returns + * snapshot of keys */ public keys() { this[MAINTENANCE](); @@ -168,13 +224,24 @@ export class BoundlessCache extends AbstractCache im } /** - * Clean up. + * Request clean up of the cache by removing expired entries and + * old data. Clean up is done automatically a short time after sets and + * deletes, but if your cache uses time-based expiration and has very + * sporadic updates it might be a good idea to call `cleanUp()` at times. + * + * A good starting point would be to call `cleanUp()` in a `setInterval` + * with a delay of at least a few minutes. */ public cleanUp() { // Simply request eviction so extra layers can handle this this[MAINTENANCE](); } + /** + * Get metrics for this cache. Returns an object with the keys `hits`, + * `misses` and `hitRate`. For caches that do not have metrics enabled + * trying to access metrics will throw an error. + */ public get metrics(): Metrics { throw new Error('Metrics are not supported by this cache'); } diff --git a/src/cache/expiration/ExpirationCache.ts b/src/cache/expiration/ExpirationCache.ts index 3f55956..2fe30cb 100644 --- a/src/cache/expiration/ExpirationCache.ts +++ b/src/cache/expiration/ExpirationCache.ts @@ -76,18 +76,47 @@ export class ExpirationCache extends AbstractCache i this[PARENT][ON_MAINTENANCE] = this[MAINTENANCE].bind(this); } + /** + * The maximum size the cache can be. Will be -1 if the cache is unbounded. + * + * @returns + * maximum size + */ public get maxSize(): number { return this[PARENT].maxSize; } + /** + * The current size of the cache. + * + * @returns + * current size + */ public get size(): number { return this[PARENT].size; } + /** + * The size of the cache weighted via the activate estimator. + * + * @returns + * weighted size + */ public get weightedSize(): number { return this[PARENT].weightedSize; } + /** + * Store a value tied to the specified key. Returns the previous value or + * `null` if no value currently exists for the given key. + * + * @param key - + * key to store value under + * @param value - + * value to store + * @returns + * current value or `null` + */ public set(key: K, value: V) { const data = this[DATA]; const timerWheel = data.timerWheel; @@ -114,6 +143,16 @@ export class ExpirationCache extends AbstractCache i } } + /** + * Get the cached value for the specified key if it exists. Will return + * the value or `null` if no cached value exist. Updates the usage of the + * key. + * + * @param key - + * key to get + * @returns + * current value or `null` + */ public getIfPresent(key: K): V | null { const node = this[PARENT].getIfPresent(key); if(node) { @@ -138,33 +177,91 @@ export class ExpirationCache extends AbstractCache i return null; } + /** + * Peek to see if a key is present without updating the usage of the + * key. Returns the value associated with the key or `null` if the key + * is not present. + * + * In many cases `has(key)` is a better option to see if a key is present. + * + * @param key - + * the key to check + * @returns + * value associated with key or `null` + */ public peek(key: K): V | null { const node = this[PARENT].peek(key); return node && ! node.isExpired() ? node.value : null; } + /** + * Check if the given key exists in the cache. + * + * @param key - + * key to check + * @returns + * `true` if value currently exists, `false` otherwise + */ public has(key: K): boolean { const node = this[PARENT].peek(key); return (node && ! node.isExpired()) || false; } + /** + * Delete a value in the cache. Returns the deleted value or `null` if + * there was no value associated with the key in the cache. + * + * @param key - + * the key to delete + * @returns + * deleted value or `null` + */ public delete(key: K): V | null { const node = this[PARENT].delete(key); return node ? node.value : null; } + /** + * Clear the cache removing all of the entries cached. + */ public clear(): void { this[PARENT].clear(); } + /** + * Get all of the keys in the cache as an array. Can be used to iterate + * over all of the values in the cache, but be sure to protect against + * values being removed during iteration due to time-based expiration if + * used. + * + * @returns + * snapshot of keys + */ public keys(): K[] { return this[PARENT].keys(); } + /** + * Request clean up of the cache by removing expired entries and + * old data. Clean up is done automatically a short time after sets and + * deletes, but if your cache uses time-based expiration and has very + * sporadic updates it might be a good idea to call `cleanUp()` at times. + * + * A good starting point would be to call `cleanUp()` in a `setInterval` + * with a delay of at least a few minutes. + */ public cleanUp(): void { this[PARENT].cleanUp(); } + /** + * Get metrics for this cache. Returns an object with the keys `hits`, + * `misses` and `hitRate`. For caches that do not have metrics enabled + * trying to access metrics will throw an error. + * + * @returns + * metrics if available via the parent cache + */ public get metrics(): Metrics { return this[PARENT].metrics; } diff --git a/src/cache/expiration/TimerWheel.ts b/src/cache/expiration/TimerWheel.ts index c5b3612..525e1f5 100644 --- a/src/cache/expiration/TimerWheel.ts +++ b/src/cache/expiration/TimerWheel.ts @@ -3,6 +3,14 @@ import { KeyType } from '../KeyType'; import { Expirable } from './Expirable'; +/** + * Helper function to calculate the closest power of two to N. + * + * @param n - + * input + * @returns + * closest power of two to `n` + */ function toPowerOfN(n: number) { return Math.pow(2, Math.ceil(Math.log(n) / Math.LN2)); } @@ -138,6 +146,13 @@ export class TimerWheel { /** * Create a node that that helps with tracking when a key and value * should be evicted. + * + * @param key - + * key to set + * @param value - + * value to set + * @returns + * node */ public node(key: K, value: V): TimerNode { return new TimerNode(this, key, value); @@ -145,6 +160,13 @@ export class TimerWheel { /** * Schedule eviction of the given node at the given timestamp. + * + * @param node - + * node to reschedule + * @param time - + * new expiration time + * @returns + * if the node was rescheduled */ public schedule(node: TimerNode, time: number) { node.remove(); diff --git a/src/cache/loading/DefaultLoadingCache.ts b/src/cache/loading/DefaultLoadingCache.ts index 8e4eb1c..359ab35 100644 --- a/src/cache/loading/DefaultLoadingCache.ts +++ b/src/cache/loading/DefaultLoadingCache.ts @@ -39,6 +39,17 @@ export class DefaultLoadingCache extends WrappedCache): Promise { const currentValue = this.getIfPresent(key); if(currentValue !== null) { diff --git a/src/cache/loading/LoadingCache.ts b/src/cache/loading/LoadingCache.ts index 7c013ba..19c7611 100644 --- a/src/cache/loading/LoadingCache.ts +++ b/src/cache/loading/LoadingCache.ts @@ -7,5 +7,16 @@ import { Loader } from './Loader'; * Cache that also supports loading of data if it's not in the cache. */ export interface LoadingCache extends Cache { + /** + * Get cached value or load it if not currently cached. Updates the usage + * of the key. + * + * @param key - + * key to get + * @param loader - + * optional loader to use for loading the object + * @returns + * promise that resolves to the loaded value + */ get(key: K, loader?: Loader): Promise; } diff --git a/src/cache/metrics/MetricsCache.ts b/src/cache/metrics/MetricsCache.ts index f418ce0..45060d7 100644 --- a/src/cache/metrics/MetricsCache.ts +++ b/src/cache/metrics/MetricsCache.ts @@ -38,10 +38,28 @@ export class MetricsCache extends WrappedCache { }; } + /** + * Get metrics for this cache. Returns an object with the keys `hits`, + * `misses` and `hitRate`. For caches that do not have metrics enabled + * trying to access metrics will throw an error. + * + * @returns + * metrics of cache + */ public get metrics(): Metrics { return this[METRICS]; } + /** + * Get the cached value for the specified key if it exists. Will return + * the value or `null` if no cached value exist. Updates the usage of the + * key. + * + * @param key - + * key to get + * @returns + * current value or `null` + */ public getIfPresent(key: K): V | null { const result = super.getIfPresent(key); diff --git a/src/utils/memoryEstimator.ts b/src/utils/memoryEstimator.ts index b543e13..df2f8c8 100644 --- a/src/utils/memoryEstimator.ts +++ b/src/utils/memoryEstimator.ts @@ -2,6 +2,14 @@ import { CacheNode } from '../cache/CacheNode'; const OBJ_OVERHEAD = 4; +/** + * Estimate the memory usage of the given object. + * + * @param value - + * value to estimates + * @returns + * estimated memory usage in bytes + */ export function memoryEstimator(value: any): number { switch(typeof value) { case 'string':