@@ -130,21 +130,26 @@ class Stack {
130130/**
131131 * Default export, the thing you're using this module to get.
132132 *
133- * All properties from the options object (with the exception of
134- * {@link OptionsBase.max} and {@link OptionsBase.maxSize}) are added as
135- * normal public members. (`max` and `maxBase` are read-only getters.)
136- * Changing any of these will alter the defaults for subsequent method calls,
137- * but is otherwise safe.
133+ * The `K` and `V` types define the key and value types, respectively. The
134+ * optional `FC` type defines the type of the `context` object passed to
135+ * `cache.fetch()` and `cache.memo()`.
136+ *
137+ * Keys and values **must not** be `null` or `undefined`.
138+ *
139+ * All properties from the options object (with the exception of `max`,
140+ * `maxSize`, `fetchMethod`, `memoMethod`, `dispose` and `disposeAfter`) are
141+ * added as normal public members. (The listed options are read-only getters.)
142+ *
143+ * Changing any of these will alter the defaults for subsequent method calls.
138144 */
139145class LRUCache {
140- // properties coming in from the options of these, only max and maxSize
141- // really *need* to be protected. The rest can be modified, as they just
142- // set defaults for various methods.
146+ // options that cannot be changed without disaster
143147 #max;
144148 #maxSize;
145149 #dispose;
146150 #disposeAfter;
147151 #fetchMethod;
152+ #memoMethod;
148153 /**
149154 * {@link LRUCache.OptionsBase.ttl }
150155 */
@@ -290,6 +295,9 @@ class LRUCache {
290295 get fetchMethod ( ) {
291296 return this . #fetchMethod;
292297 }
298+ get memoMethod ( ) {
299+ return this . #memoMethod;
300+ }
293301 /**
294302 * {@link LRUCache.OptionsBase.dispose } (read-only)
295303 */
@@ -303,7 +311,7 @@ class LRUCache {
303311 return this . #disposeAfter;
304312 }
305313 constructor ( options ) {
306- const { max = 0 , ttl, ttlResolution = 1 , ttlAutopurge, updateAgeOnGet, updateAgeOnHas, allowStale, dispose, disposeAfter, noDisposeOnSet, noUpdateTTL, maxSize = 0 , maxEntrySize = 0 , sizeCalculation, fetchMethod, noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort, } = options ;
314+ const { max = 0 , ttl, ttlResolution = 1 , ttlAutopurge, updateAgeOnGet, updateAgeOnHas, allowStale, dispose, disposeAfter, noDisposeOnSet, noUpdateTTL, maxSize = 0 , maxEntrySize = 0 , sizeCalculation, fetchMethod, memoMethod , noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort, } = options ;
307315 if ( max !== 0 && ! isPosInt ( max ) ) {
308316 throw new TypeError ( 'max option must be a nonnegative integer' ) ;
309317 }
@@ -323,6 +331,11 @@ class LRUCache {
323331 throw new TypeError ( 'sizeCalculation set to non-function' ) ;
324332 }
325333 }
334+ if ( memoMethod !== undefined &&
335+ typeof memoMethod !== 'function' ) {
336+ throw new TypeError ( 'memoMethod must be a function if defined' ) ;
337+ }
338+ this . #memoMethod = memoMethod ;
326339 if ( fetchMethod !== undefined &&
327340 typeof fetchMethod !== 'function' ) {
328341 throw new TypeError ( 'fetchMethod must be a function if specified' ) ;
@@ -401,7 +414,8 @@ class LRUCache {
401414 }
402415 }
403416 /**
404- * Return the remaining TTL time for a given entry key
417+ * Return the number of ms left in the item's TTL. If item is not in cache,
418+ * returns `0`. Returns `Infinity` if item is in cache without a defined TTL.
405419 */
406420 getRemainingTTL ( key ) {
407421 return this . #keyMap. has ( key ) ? Infinity : 0 ;
@@ -417,7 +431,7 @@ class LRUCache {
417431 if ( ttl !== 0 && this . ttlAutopurge ) {
418432 const t = setTimeout ( ( ) => {
419433 if ( this . #isStale( index ) ) {
420- this . delete ( this . #keyList[ index ] ) ;
434+ this . # delete( this . #keyList[ index ] , 'expire' ) ;
421435 }
422436 } , ttl + 1 ) ;
423437 // unref() not supported on all platforms
@@ -674,13 +688,14 @@ class LRUCache {
674688 return this . entries ( ) ;
675689 }
676690 /**
677- * A String value that is used in the creation of the default string description of an object.
678- * Called by the built-in method Object.prototype.toString.
691+ * A String value that is used in the creation of the default string
692+ * description of an object. Called by the built-in method
693+ * `Object.prototype.toString`.
679694 */
680695 [ Symbol . toStringTag ] = 'LRUCache' ;
681696 /**
682697 * Find a value for which the supplied fn method returns a truthy value,
683- * similar to Array.find(). fn is called as fn(value, key, cache).
698+ * similar to ` Array.find()`. fn is called as ` fn(value, key, cache)` .
684699 */
685700 find ( fn , getOptions = { } ) {
686701 for ( const i of this . #indexes( ) ) {
@@ -696,10 +711,15 @@ class LRUCache {
696711 }
697712 }
698713 /**
699- * Call the supplied function on each item in the cache, in order from
700- * most recently used to least recently used. fn is called as
701- * fn(value, key, cache). Does not update age or recenty of use.
702- * Does not iterate over stale values.
714+ * Call the supplied function on each item in the cache, in order from most
715+ * recently used to least recently used.
716+ *
717+ * `fn` is called as `fn(value, key, cache)`.
718+ *
719+ * If `thisp` is provided, function will be called in the `this`-context of
720+ * the provided object, or the cache if no `thisp` object is provided.
721+ *
722+ * Does not update age or recenty of use, or iterate over stale values.
703723 */
704724 forEach ( fn , thisp = this ) {
705725 for ( const i of this . #indexes( ) ) {
@@ -735,17 +755,23 @@ class LRUCache {
735755 let deleted = false ;
736756 for ( const i of this . #rindexes( { allowStale : true } ) ) {
737757 if ( this . #isStale( i ) ) {
738- this . delete ( this . #keyList[ i ] ) ;
758+ this . # delete( this . #keyList[ i ] , 'expire' ) ;
739759 deleted = true ;
740760 }
741761 }
742762 return deleted ;
743763 }
744764 /**
745765 * Get the extended info about a given entry, to get its value, size, and
746- * TTL info simultaneously. Like {@link LRUCache#dump}, but just for a
747- * single key. Always returns stale values, if their info is found in the
748- * cache, so be sure to check for expired TTLs if relevant.
766+ * TTL info simultaneously. Returns `undefined` if the key is not present.
767+ *
768+ * Unlike {@link LRUCache#dump}, which is designed to be portable and survive
769+ * serialization, the `start` value is always the current timestamp, and the
770+ * `ttl` is a calculated remaining time to live (negative if expired).
771+ *
772+ * Always returns stale values, if their info is found in the cache, so be
773+ * sure to check for expirations (ie, a negative {@link LRUCache.Entry#ttl})
774+ * if relevant.
749775 */
750776 info ( key ) {
751777 const i = this . #keyMap. get ( key ) ;
@@ -774,7 +800,16 @@ class LRUCache {
774800 }
775801 /**
776802 * Return an array of [key, {@link LRUCache.Entry}] tuples which can be
777- * passed to cache.load()
803+ * passed to {@link LRLUCache#load}.
804+ *
805+ * The `start` fields are calculated relative to a portable `Date.now()`
806+ * timestamp, even if `performance.now()` is available.
807+ *
808+ * Stale entries are always included in the `dump`, even if
809+ * {@link LRUCache.OptionsBase.allowStale} is false.
810+ *
811+ * Note: this returns an actual array, not a generator, so it can be more
812+ * easily passed around.
778813 */
779814 dump ( ) {
780815 const arr = [ ] ;
@@ -803,8 +838,12 @@ class LRUCache {
803838 }
804839 /**
805840 * Reset the cache and load in the items in entries in the order listed.
806- * Note that the shape of the resulting cache may be different if the
807- * same options are not used in both caches.
841+ *
842+ * The shape of the resulting cache may be different if the same options are
843+ * not used in both caches.
844+ *
845+ * The `start` fields are assumed to be calculated relative to a portable
846+ * `Date.now()` timestamp, even if `performance.now()` is available.
808847 */
809848 load ( arr ) {
810849 this . clear ( ) ;
@@ -827,6 +866,30 @@ class LRUCache {
827866 *
828867 * Note: if `undefined` is specified as a value, this is an alias for
829868 * {@link LRUCache#delete}
869+ *
870+ * Fields on the {@link LRUCache.SetOptions} options param will override
871+ * their corresponding values in the constructor options for the scope
872+ * of this single `set()` operation.
873+ *
874+ * If `start` is provided, then that will set the effective start
875+ * time for the TTL calculation. Note that this must be a previous
876+ * value of `performance.now()` if supported, or a previous value of
877+ * `Date.now()` if not.
878+ *
879+ * Options object may also include `size`, which will prevent
880+ * calling the `sizeCalculation` function and just use the specified
881+ * number if it is a positive integer, and `noDisposeOnSet` which
882+ * will prevent calling a `dispose` function in the case of
883+ * overwrites.
884+ *
885+ * If the `size` (or return value of `sizeCalculation`) for a given
886+ * entry is greater than `maxEntrySize`, then the item will not be
887+ * added to the cache.
888+ *
889+ * Will update the recency of the entry.
890+ *
891+ * If the value is `undefined`, then this is an alias for
892+ * `cache.delete(key)`. `undefined` is never stored in the cache.
830893 */
831894 set ( k , v , setOptions = { } ) {
832895 if ( v === undefined ) {
@@ -844,7 +907,7 @@ class LRUCache {
844907 status . maxEntrySizeExceeded = true ;
845908 }
846909 // have to delete, in case something is there already.
847- this . delete ( k ) ;
910+ this . # delete( k , 'set' ) ;
848911 return this ;
849912 }
850913 let index = this . #size === 0 ? undefined : this . #keyMap. get ( k ) ;
@@ -996,6 +1059,14 @@ class LRUCache {
9961059 * Will return false if the item is stale, even though it is technically
9971060 * in the cache.
9981061 *
1062+ * Check if a key is in the cache, without updating the recency of
1063+ * use. Age is updated if {@link LRUCache.OptionsBase.updateAgeOnHas} is set
1064+ * to `true` in either the options or the constructor.
1065+ *
1066+ * Will return `false` if the item is stale, even though it is technically in
1067+ * the cache. The difference can be determined (if it matters) by using a
1068+ * `status` argument, and inspecting the `has` field.
1069+ *
9991070 * Will not update item age unless
10001071 * {@link LRUCache.OptionsBase.updateAgeOnHas} is set.
10011072 */
@@ -1087,7 +1158,7 @@ class LRUCache {
10871158 this . #valList[ index ] = bf . __staleWhileFetching ;
10881159 }
10891160 else {
1090- this . delete ( k ) ;
1161+ this . # delete( k , 'fetch' ) ;
10911162 }
10921163 }
10931164 else {
@@ -1116,7 +1187,7 @@ class LRUCache {
11161187 // the stale value is not removed from the cache when the fetch fails.
11171188 const del = ! noDelete || bf . __staleWhileFetching === undefined ;
11181189 if ( del ) {
1119- this . delete ( k ) ;
1190+ this . # delete( k , 'fetch' ) ;
11201191 }
11211192 else if ( ! allowStaleAborted ) {
11221193 // still replace the *promise* with the stale value,
@@ -1262,6 +1333,28 @@ class LRUCache {
12621333 return staleVal ? p . __staleWhileFetching : ( p . __returned = p ) ;
12631334 }
12641335 }
1336+ async forceFetch ( k , fetchOptions = { } ) {
1337+ const v = await this . fetch ( k , fetchOptions ) ;
1338+ if ( v === undefined )
1339+ throw new Error ( 'fetch() returned undefined' ) ;
1340+ return v ;
1341+ }
1342+ memo ( k , memoOptions = { } ) {
1343+ const memoMethod = this . #memoMethod;
1344+ if ( ! memoMethod ) {
1345+ throw new Error ( 'no memoMethod provided to constructor' ) ;
1346+ }
1347+ const { context, forceRefresh, ...options } = memoOptions ;
1348+ const v = this . get ( k , options ) ;
1349+ if ( ! forceRefresh && v !== undefined )
1350+ return v ;
1351+ const vv = memoMethod ( k , v , {
1352+ options,
1353+ context,
1354+ } ) ;
1355+ this . set ( k , vv , options ) ;
1356+ return vv ;
1357+ }
12651358 /**
12661359 * Return a value from the cache. Will update the recency of the cache
12671360 * entry found.
@@ -1282,7 +1375,7 @@ class LRUCache {
12821375 // delete only if not an in-flight background fetch
12831376 if ( ! fetching ) {
12841377 if ( ! noDeleteOnStaleGet ) {
1285- this . delete ( k ) ;
1378+ this . # delete( k , 'expire' ) ;
12861379 }
12871380 if ( status && allowStale )
12881381 status . returnedStale = true ;
@@ -1345,16 +1438,20 @@ class LRUCache {
13451438 }
13461439 /**
13471440 * Deletes a key out of the cache.
1441+ *
13481442 * Returns true if the key was deleted, false otherwise.
13491443 */
13501444 delete ( k ) {
1445+ return this . #delete( k , 'delete' ) ;
1446+ }
1447+ #delete( k , reason ) {
13511448 let deleted = false ;
13521449 if ( this . #size !== 0 ) {
13531450 const index = this . #keyMap. get ( k ) ;
13541451 if ( index !== undefined ) {
13551452 deleted = true ;
13561453 if ( this . #size === 1 ) {
1357- this . clear ( ) ;
1454+ this . # clear( reason ) ;
13581455 }
13591456 else {
13601457 this . #removeItemSize( index ) ;
@@ -1364,10 +1461,10 @@ class LRUCache {
13641461 }
13651462 else if ( this . #hasDispose || this . #hasDisposeAfter) {
13661463 if ( this . #hasDispose) {
1367- this . #dispose?. ( v , k , 'delete' ) ;
1464+ this . #dispose?. ( v , k , reason ) ;
13681465 }
13691466 if ( this . #hasDisposeAfter) {
1370- this . #disposed?. push ( [ v , k , 'delete' ] ) ;
1467+ this . #disposed?. push ( [ v , k , reason ] ) ;
13711468 }
13721469 }
13731470 this . #keyMap. delete ( k ) ;
@@ -1403,6 +1500,9 @@ class LRUCache {
14031500 * Clear the cache entirely, throwing away all values.
14041501 */
14051502 clear ( ) {
1503+ return this . #clear( 'delete' ) ;
1504+ }
1505+ #clear( reason ) {
14061506 for ( const index of this . #rindexes( { allowStale : true } ) ) {
14071507 const v = this . #valList[ index ] ;
14081508 if ( this . #isBackgroundFetch( v ) ) {
@@ -1411,10 +1511,10 @@ class LRUCache {
14111511 else {
14121512 const k = this . #keyList[ index ] ;
14131513 if ( this . #hasDispose) {
1414- this . #dispose?. ( v , k , 'delete' ) ;
1514+ this . #dispose?. ( v , k , reason ) ;
14151515 }
14161516 if ( this . #hasDisposeAfter) {
1417- this . #disposed?. push ( [ v , k , 'delete' ] ) ;
1517+ this . #disposed?. push ( [ v , k , reason ] ) ;
14181518 }
14191519 }
14201520 }
0 commit comments