@@ -130,21 +130,26 @@ class Stack {
130
130
/**
131
131
* Default export, the thing you're using this module to get.
132
132
*
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.
138
144
*/
139
145
class 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
143
147
#max;
144
148
#maxSize;
145
149
#dispose;
146
150
#disposeAfter;
147
151
#fetchMethod;
152
+ #memoMethod;
148
153
/**
149
154
* {@link LRUCache.OptionsBase.ttl }
150
155
*/
@@ -290,6 +295,9 @@ class LRUCache {
290
295
get fetchMethod ( ) {
291
296
return this . #fetchMethod;
292
297
}
298
+ get memoMethod ( ) {
299
+ return this . #memoMethod;
300
+ }
293
301
/**
294
302
* {@link LRUCache.OptionsBase.dispose } (read-only)
295
303
*/
@@ -303,7 +311,7 @@ class LRUCache {
303
311
return this . #disposeAfter;
304
312
}
305
313
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 ;
307
315
if ( max !== 0 && ! isPosInt ( max ) ) {
308
316
throw new TypeError ( 'max option must be a nonnegative integer' ) ;
309
317
}
@@ -323,6 +331,11 @@ class LRUCache {
323
331
throw new TypeError ( 'sizeCalculation set to non-function' ) ;
324
332
}
325
333
}
334
+ if ( memoMethod !== undefined &&
335
+ typeof memoMethod !== 'function' ) {
336
+ throw new TypeError ( 'memoMethod must be a function if defined' ) ;
337
+ }
338
+ this . #memoMethod = memoMethod ;
326
339
if ( fetchMethod !== undefined &&
327
340
typeof fetchMethod !== 'function' ) {
328
341
throw new TypeError ( 'fetchMethod must be a function if specified' ) ;
@@ -401,7 +414,8 @@ class LRUCache {
401
414
}
402
415
}
403
416
/**
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.
405
419
*/
406
420
getRemainingTTL ( key ) {
407
421
return this . #keyMap. has ( key ) ? Infinity : 0 ;
@@ -417,7 +431,7 @@ class LRUCache {
417
431
if ( ttl !== 0 && this . ttlAutopurge ) {
418
432
const t = setTimeout ( ( ) => {
419
433
if ( this . #isStale( index ) ) {
420
- this . delete ( this . #keyList[ index ] ) ;
434
+ this . # delete( this . #keyList[ index ] , 'expire' ) ;
421
435
}
422
436
} , ttl + 1 ) ;
423
437
// unref() not supported on all platforms
@@ -674,13 +688,14 @@ class LRUCache {
674
688
return this . entries ( ) ;
675
689
}
676
690
/**
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`.
679
694
*/
680
695
[ Symbol . toStringTag ] = 'LRUCache' ;
681
696
/**
682
697
* 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)` .
684
699
*/
685
700
find ( fn , getOptions = { } ) {
686
701
for ( const i of this . #indexes( ) ) {
@@ -696,10 +711,15 @@ class LRUCache {
696
711
}
697
712
}
698
713
/**
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.
703
723
*/
704
724
forEach ( fn , thisp = this ) {
705
725
for ( const i of this . #indexes( ) ) {
@@ -735,17 +755,23 @@ class LRUCache {
735
755
let deleted = false ;
736
756
for ( const i of this . #rindexes( { allowStale : true } ) ) {
737
757
if ( this . #isStale( i ) ) {
738
- this . delete ( this . #keyList[ i ] ) ;
758
+ this . # delete( this . #keyList[ i ] , 'expire' ) ;
739
759
deleted = true ;
740
760
}
741
761
}
742
762
return deleted ;
743
763
}
744
764
/**
745
765
* 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.
749
775
*/
750
776
info ( key ) {
751
777
const i = this . #keyMap. get ( key ) ;
@@ -774,7 +800,16 @@ class LRUCache {
774
800
}
775
801
/**
776
802
* 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.
778
813
*/
779
814
dump ( ) {
780
815
const arr = [ ] ;
@@ -803,8 +838,12 @@ class LRUCache {
803
838
}
804
839
/**
805
840
* 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.
808
847
*/
809
848
load ( arr ) {
810
849
this . clear ( ) ;
@@ -827,6 +866,30 @@ class LRUCache {
827
866
*
828
867
* Note: if `undefined` is specified as a value, this is an alias for
829
868
* {@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.
830
893
*/
831
894
set ( k , v , setOptions = { } ) {
832
895
if ( v === undefined ) {
@@ -844,7 +907,7 @@ class LRUCache {
844
907
status . maxEntrySizeExceeded = true ;
845
908
}
846
909
// have to delete, in case something is there already.
847
- this . delete ( k ) ;
910
+ this . # delete( k , 'set' ) ;
848
911
return this ;
849
912
}
850
913
let index = this . #size === 0 ? undefined : this . #keyMap. get ( k ) ;
@@ -996,6 +1059,14 @@ class LRUCache {
996
1059
* Will return false if the item is stale, even though it is technically
997
1060
* in the cache.
998
1061
*
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
+ *
999
1070
* Will not update item age unless
1000
1071
* {@link LRUCache.OptionsBase.updateAgeOnHas} is set.
1001
1072
*/
@@ -1087,7 +1158,7 @@ class LRUCache {
1087
1158
this . #valList[ index ] = bf . __staleWhileFetching ;
1088
1159
}
1089
1160
else {
1090
- this . delete ( k ) ;
1161
+ this . # delete( k , 'fetch' ) ;
1091
1162
}
1092
1163
}
1093
1164
else {
@@ -1116,7 +1187,7 @@ class LRUCache {
1116
1187
// the stale value is not removed from the cache when the fetch fails.
1117
1188
const del = ! noDelete || bf . __staleWhileFetching === undefined ;
1118
1189
if ( del ) {
1119
- this . delete ( k ) ;
1190
+ this . # delete( k , 'fetch' ) ;
1120
1191
}
1121
1192
else if ( ! allowStaleAborted ) {
1122
1193
// still replace the *promise* with the stale value,
@@ -1262,6 +1333,28 @@ class LRUCache {
1262
1333
return staleVal ? p . __staleWhileFetching : ( p . __returned = p ) ;
1263
1334
}
1264
1335
}
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
+ }
1265
1358
/**
1266
1359
* Return a value from the cache. Will update the recency of the cache
1267
1360
* entry found.
@@ -1282,7 +1375,7 @@ class LRUCache {
1282
1375
// delete only if not an in-flight background fetch
1283
1376
if ( ! fetching ) {
1284
1377
if ( ! noDeleteOnStaleGet ) {
1285
- this . delete ( k ) ;
1378
+ this . # delete( k , 'expire' ) ;
1286
1379
}
1287
1380
if ( status && allowStale )
1288
1381
status . returnedStale = true ;
@@ -1345,16 +1438,20 @@ class LRUCache {
1345
1438
}
1346
1439
/**
1347
1440
* Deletes a key out of the cache.
1441
+ *
1348
1442
* Returns true if the key was deleted, false otherwise.
1349
1443
*/
1350
1444
delete ( k ) {
1445
+ return this . #delete( k , 'delete' ) ;
1446
+ }
1447
+ #delete( k , reason ) {
1351
1448
let deleted = false ;
1352
1449
if ( this . #size !== 0 ) {
1353
1450
const index = this . #keyMap. get ( k ) ;
1354
1451
if ( index !== undefined ) {
1355
1452
deleted = true ;
1356
1453
if ( this . #size === 1 ) {
1357
- this . clear ( ) ;
1454
+ this . # clear( reason ) ;
1358
1455
}
1359
1456
else {
1360
1457
this . #removeItemSize( index ) ;
@@ -1364,10 +1461,10 @@ class LRUCache {
1364
1461
}
1365
1462
else if ( this . #hasDispose || this . #hasDisposeAfter) {
1366
1463
if ( this . #hasDispose) {
1367
- this . #dispose?. ( v , k , 'delete' ) ;
1464
+ this . #dispose?. ( v , k , reason ) ;
1368
1465
}
1369
1466
if ( this . #hasDisposeAfter) {
1370
- this . #disposed?. push ( [ v , k , 'delete' ] ) ;
1467
+ this . #disposed?. push ( [ v , k , reason ] ) ;
1371
1468
}
1372
1469
}
1373
1470
this . #keyMap. delete ( k ) ;
@@ -1403,6 +1500,9 @@ class LRUCache {
1403
1500
* Clear the cache entirely, throwing away all values.
1404
1501
*/
1405
1502
clear ( ) {
1503
+ return this . #clear( 'delete' ) ;
1504
+ }
1505
+ #clear( reason ) {
1406
1506
for ( const index of this . #rindexes( { allowStale : true } ) ) {
1407
1507
const v = this . #valList[ index ] ;
1408
1508
if ( this . #isBackgroundFetch( v ) ) {
@@ -1411,10 +1511,10 @@ class LRUCache {
1411
1511
else {
1412
1512
const k = this . #keyList[ index ] ;
1413
1513
if ( this . #hasDispose) {
1414
- this . #dispose?. ( v , k , 'delete' ) ;
1514
+ this . #dispose?. ( v , k , reason ) ;
1415
1515
}
1416
1516
if ( this . #hasDisposeAfter) {
1417
- this . #disposed?. push ( [ v , k , 'delete' ] ) ;
1517
+ this . #disposed?. push ( [ v , k , reason ] ) ;
1418
1518
}
1419
1519
}
1420
1520
}
0 commit comments