@twin.org/core 0.10.1-next.1 → 0.10.1-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -135,14 +135,10 @@ export class LfuCache {
135
135
  */
136
136
  get(key) {
137
137
  Guards.stringValue(LfuCache.CLASS_NAME, "key", key);
138
- const entry = this._keyMap.get(key);
138
+ const entry = this.lookupLive(key);
139
139
  if (Is.empty(entry)) {
140
140
  return undefined;
141
141
  }
142
- if (this.isExpired(entry, Date.now())) {
143
- this.removeEntry(key);
144
- return undefined;
145
- }
146
142
  this.promote(key, entry);
147
143
  return entry.value;
148
144
  }
@@ -215,8 +211,10 @@ export class LfuCache {
215
211
  throwOnTimeout: true
216
212
  });
217
213
  try {
218
- if (this.has(key)) {
219
- return this.get(key);
214
+ const entry = this.lookupLive(key);
215
+ if (Is.notEmpty(entry)) {
216
+ this.promote(key, entry);
217
+ return entry.value;
220
218
  }
221
219
  const value = await valueFactory();
222
220
  this.set(key, value, expires);
@@ -234,15 +232,7 @@ export class LfuCache {
234
232
  */
235
233
  has(key) {
236
234
  Guards.stringValue(LfuCache.CLASS_NAME, "key", key);
237
- const entry = this._keyMap.get(key);
238
- if (entry === undefined) {
239
- return false;
240
- }
241
- if (this.isExpired(entry, Date.now())) {
242
- this.removeEntry(key);
243
- return false;
244
- }
245
- return true;
235
+ return Is.notEmpty(this.lookupLive(key));
246
236
  }
247
237
  /**
248
238
  * Return all keys for entries that have not idled out.
@@ -302,6 +292,25 @@ export class LfuCache {
302
292
  this._minFreq = 0;
303
293
  this._nextExpires = undefined;
304
294
  }
295
+ /**
296
+ * Resolve an entry which is present and has not expired, evicting it if it has.
297
+ * The expiry check is evaluated once so a caller cannot observe an entry as live and
298
+ * then as expired across two separate lookups.
299
+ * @param key The key to resolve.
300
+ * @returns The live entry, or undefined if the key is absent or has been evicted.
301
+ * @internal
302
+ */
303
+ lookupLive(key) {
304
+ const entry = this._keyMap.get(key);
305
+ if (Is.empty(entry)) {
306
+ return undefined;
307
+ }
308
+ if (this.isExpired(entry, Date.now())) {
309
+ this.removeEntry(key);
310
+ return undefined;
311
+ }
312
+ return entry;
313
+ }
305
314
  /**
306
315
  * Increment the frequency of an entry and move it to the correct frequency bucket.
307
316
  * Updates lastAccessed to now.
@@ -1 +1 @@
1
- {"version":3,"file":"lfuCache.js","sourceRoot":"","sources":["../../../src/utils/lfuCache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG1D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,QAAQ;IACpB;;OAEG;IACI,MAAM,CAAU,UAAU,cAAuC;IAExE;;OAEG;IACI,MAAM,CAAU,gBAAgB,GAAG,IAAI,CAAC;IAE/C;;OAEG;IACI,MAAM,CAAU,cAAc,GAAG,KAAK,CAAC;IAE9C;;;OAGG;IACc,SAAS,CAAS;IAEnC;;;OAGG;IACc,MAAM,CAAS;IAEhC;;;OAGG;IACc,eAAe,CAAqB;IAErD;;;OAGG;IACc,WAAW,CAAS;IAErC;;;OAGG;IACc,OAAO,CAGtB;IAEF;;;OAGG;IACc,QAAQ,CAA2B;IAEpD;;;OAGG;IACK,QAAQ,CAAS;IAEzB;;;OAGG;IACK,YAAY,CAAqB;IAEzC;;;OAGG;IACK,eAAe,CAAS;IAEhC;;;OAGG;IACK,WAAW,CAA4C;IAE/D;;;;;;;OAOG;IACH,YAAY,OAAwE;QACnF,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,QAAQ,CAAC,gBAAgB,CAAC;QAChE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,QAAQ,CAAC,cAAc,CAAC;QACxD,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,CAAC;QAE/C,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,cAAoB,QAAQ,CAAC,CAAC;QAChE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,WAAiB,KAAK,CAAC,CAAC;QAC1D,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,oBAA0B,cAAc,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,QAAQ,GAAyB,EAAE,CAAC;QAC1C,UAAU,CAAC,OAAO,aAAmB,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QACrF,UAAU,CAAC,OAAO,UAAgB,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/E,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACjC,UAAU,CAAC,OAAO,mBAAyB,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE;gBAC/E,QAAQ,EAAE,CAAC;aACX,CAAC,CAAC;QACJ,CAAC;QACD,UAAU,CAAC,iBAAiB,CAAC,QAAQ,CAAC,UAAU,cAA+B,QAAQ,CAAC,CAAC;QAEzF,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC;QACtC,IAAI,CAAC,WAAW,GAAG,GAAG,QAAQ,CAAC,UAAU,IAAI,YAAY,CAAC,cAAc,EAAE,EAAE,CAAC;QAC7E,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC;QAC1B,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;QAC9B,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACI,KAAK;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC1B,CAAC;IAED;;;;;;OAMG;IACI,GAAG,CAAC,GAAW;QACrB,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,SAAe,GAAG,CAAC,CAAC;QAE1D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACtB,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAEzB,OAAO,KAAK,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;;;;;;;;OASG;IACI,GAAG,CAAC,GAAW,EAAE,KAAQ,EAAE,OAAgB;QACjD,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,SAAe,GAAG,CAAC,CAAC;QAC1D,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAC/D,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;gBAC1C,kEAAkE;gBAClE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACvB,CAAC;iBAAM,CAAC;gBACP,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;gBACvB,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC;gBAC3B,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;gBAC3B,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;gBAC5B,IAAI,CAAC,UAAU,EAAE,CAAC;gBAClB,OAAO;YACR,CAAC;QACF,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,SAAS,EAAE,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACjB,CAAC;QACD,MAAM,KAAK,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC;QACpE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC7B,IAAI,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;YAC3B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,UAAU,EAAE,CAAC;IACnB,CAAC;IAED;;;;;;;;OAQG;IACI,KAAK,CAAC,QAAQ,CAAC,GAAW,EAAE,YAA8B,EAAE,OAAgB;QAClF,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,SAAe,GAAG,CAAC,CAAC;QAC1D,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,kBAAwB,YAAY,CAAC,CAAC;QACzE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAC/D,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,WAAW,IAAI,GAAG,EAAE,CAAC;QAC9C,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;YAC1B,SAAS,EAAE,IAAI,CAAC,eAAe;YAC/B,cAAc,EAAE,IAAI;SACpB,CAAC,CAAC;QAEH,IAAI,CAAC;YACJ,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAM,CAAC;YAC3B,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,YAAY,EAAE,CAAC;YACnC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YAC9B,OAAO,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACV,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACxB,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,GAAG,CAAC,GAAW;QACrB,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,SAAe,GAAG,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC;QACd,CAAC;QACD,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACtB,OAAO,KAAK,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;;;OAKG;IACI,IAAI;QACV,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACvC,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;gBAChC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,CAAC;QACF,CAAC;QACD,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACpE,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC1B,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;oBACxB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAChB,CAAC;YACF,CAAC;QACF,CAAC;QACD,OAAO,MAAM,CAAC;IACf,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,GAAW;QACxB,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,SAAe,GAAG,CAAC,CAAC;QAC1D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,WAAW,EAAE,CAAC;QACpB,CAAC;IACF,CAAC;IAED;;OAEG;IACI,KAAK;QACX,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACI,OAAO;QACb,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;IAC/B,CAAC;IAED;;;;;;;;;;OAUG;IACK,OAAO,CACd,GAAW,EACX,KAAoF;QAEpF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC;QAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC7B,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAC9B,IAAI,OAAO,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;oBAC/B,IAAI,CAAC,QAAQ,GAAG,OAAO,GAAG,CAAC,CAAC;gBAC7B,CAAC;YACF,CAAC;QACF,CAAC;QACD,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;QAChB,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAChC,IAAI,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC7B,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;YAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC1C,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC;IAED;;;OAGG;IACK,QAAQ;QACf,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAe,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACxB,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACK,WAAW,CAAC,GAAW;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO;QACR,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACnB,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACjC,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;oBAClC,IAAI,MAAM,GAAG,MAAM,CAAC,gBAAgB,CAAC;oBACrC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;wBACtC,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;4BAChB,MAAM,GAAG,CAAC,CAAC;wBACZ,CAAC;oBACF,CAAC;oBACD,IAAI,CAAC,QAAQ,GAAG,MAAM,KAAK,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBACjE,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;OAGG;IACK,SAAS;QAChB,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,WAA+B,CAAC;QACpC,KAAK,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACvC,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;gBAChC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YACrB,CAAC;iBAAM,IACN,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC;gBAC1B,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,WAAW,CAAC,EACrD,CAAC;gBACF,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC;YAC7B,CAAC;QACF,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,UAAU,EAAE,CAAC;QACnB,CAAC;IACF,CAAC;IAED;;;;OAIG;IACK,YAAY,CAAC,OAA2B;QAC/C,IAAI,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YAC1F,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;QAC7B,CAAC;IACF,CAAC;IAED;;;;;;;;OAQG;IACK,SAAS,CAChB,KAA4D,EAC5D,GAAW;QAEX,OAAO,CACN,GAAG,GAAG,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,MAAM;YACvC,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CACpD,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,UAAU;QACjB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QACxB,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YACpC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC,eAAe,GAAG,GAAG,GAAG,KAAK,CAAC;YACnC,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,KAAK,CAAC,CAAC;QAC9D,CAAC;aAAM,IAAI,GAAG,GAAG,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YAC/C,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,UAAU,EAAE,CAAC;QACnB,CAAC;IACF,CAAC;IAED;;;OAGG;IACK,WAAW;QAClB,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YACnC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/B,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC9B,CAAC;IACF,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { nameof } from \"@twin.org/nameof\";\nimport { Guards } from \"./guards.js\";\nimport { Is } from \"./is.js\";\nimport { Mutex } from \"./mutex.js\";\nimport { Validation } from \"./validation.js\";\nimport { RandomHelper } from \"../helpers/randomHelper.js\";\nimport type { IValidationFailure } from \"../models/IValidationFailure.js\";\n\n/**\n * A fixed-capacity LFU cache with time-to-idle eviction.\n *\n * Entries are removed in two ways:\n * - Capacity eviction: when the cache is full the least-frequently-used entry is removed first.\n * Ties in frequency are broken by recency the least-recently-used entry among those with the\n * minimum frequency is evicted.\n * - TTI eviction: a background timer sweeps idle entries every ttiMs milliseconds.\n * The timer only runs while there are entries; it stops automatically when the cache empties.\n *\n * `get` and `set` increment an entry's access frequency and reset its idle timer.\n * `set` and `getOrSet` accept an optional hard expiry timestamp; the entry is removed once that\n * time is reached however recently it was used, and the TTI still applies alongside it.\n * `has` and `keys` are pure peeks they evict idle entries but do not affect frequency or TTI.\n * Call `destroy` when the cache is no longer needed to stop the background timer.\n */\nexport class LfuCache<T> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<LfuCache<unknown>>();\n\n\t/**\n\t * Default capacity.\n\t */\n\tpublic static readonly DEFAULT_CAPACITY = 1000;\n\n\t/**\n\t * Default time-to-idle in milliseconds.\n\t */\n\tpublic static readonly DEFAULT_TTI_MS = 10000;\n\n\t/**\n\t * The maximum number of entries the cache will hold.\n\t * @internal\n\t */\n\tprivate readonly _capacity: number;\n\n\t/**\n\t * The idle duration in milliseconds after which an untouched entry is evicted.\n\t * @internal\n\t */\n\tprivate readonly _ttiMs: number;\n\n\t/**\n\t * Optional timeout in milliseconds for mutex acquisition.\n\t * @internal\n\t */\n\tprivate readonly _mutexTimeoutMs: number | undefined;\n\n\t/**\n\t * Per-instance namespace prefix for mutex keys.\n\t * @internal\n\t */\n\tprivate readonly _mutexScope: string;\n\n\t/**\n\t * Maps each key to its cached value, access frequency, and last-accessed timestamp.\n\t * @internal\n\t */\n\tprivate readonly _keyMap: Map<\n\t\tstring,\n\t\t{ value: T; freq: number; lastAccessed: number; expires: number | undefined }\n\t>;\n\n\t/**\n\t * Maps each frequency to the ordered set of keys at that frequency (insertion order = LRU).\n\t * @internal\n\t */\n\tprivate readonly _freqMap: Map<number, Set<string>>;\n\n\t/**\n\t * The lowest frequency among all live entries. Maintained to make eviction O(1).\n\t * @internal\n\t */\n\tprivate _minFreq: number;\n\n\t/**\n\t * The earliest hard expiry timestamp among the live entries, used to pace the sweep timer.\n\t * @internal\n\t */\n\tprivate _nextExpires: number | undefined;\n\n\t/**\n\t * The timestamp the pending sweep is due to run at.\n\t * @internal\n\t */\n\tprivate _scheduledDueAt: number;\n\n\t/**\n\t * Handle for the pending idle-sweep timeout, or undefined if no timer is scheduled.\n\t * @internal\n\t */\n\tprivate _sweepTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t/**\n\t * Create a new instance of LfuCache.\n\t * @param options The cache options.\n\t * @param options.capacity Maximum number of entries. Defaults to 1000. Must be a positive integer.\n\t * @param options.ttiMs Time-to-idle in milliseconds. Defaults to 10000. Must be a positive integer.\n\t * @param options.mutexTimeoutMs Maximum time in milliseconds to wait for getOrSet mutex acquisition.\n\t * @throws ValidationError if capacity or ttiMs is not a positive integer.\n\t */\n\tconstructor(options?: { capacity?: number; ttiMs?: number; mutexTimeoutMs?: number }) {\n\t\tconst capacity = options?.capacity ?? LfuCache.DEFAULT_CAPACITY;\n\t\tconst ttiMs = options?.ttiMs ?? LfuCache.DEFAULT_TTI_MS;\n\t\tconst mutexTimeoutMs = options?.mutexTimeoutMs;\n\n\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(capacity), capacity);\n\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(ttiMs), ttiMs);\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(mutexTimeoutMs), mutexTimeoutMs);\n\t\t}\n\n\t\tconst failures: IValidationFailure[] = [];\n\t\tValidation.integer(nameof(capacity), capacity, failures, undefined, { minValue: 1 });\n\t\tValidation.integer(nameof(ttiMs), ttiMs, failures, undefined, { minValue: 1 });\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tValidation.integer(nameof(mutexTimeoutMs), mutexTimeoutMs, failures, undefined, {\n\t\t\t\tminValue: 0\n\t\t\t});\n\t\t}\n\t\tValidation.asValidationError(LfuCache.CLASS_NAME, nameof<LfuCache<unknown>>(), failures);\n\n\t\tthis._capacity = capacity;\n\t\tthis._ttiMs = ttiMs;\n\t\tthis._mutexTimeoutMs = mutexTimeoutMs;\n\t\tthis._mutexScope = `${LfuCache.CLASS_NAME}:${RandomHelper.generateUuidV7()}`;\n\t\tthis._keyMap = new Map();\n\t\tthis._freqMap = new Map();\n\t\tthis._minFreq = 0;\n\t\tthis._nextExpires = undefined;\n\t\tthis._scheduledDueAt = 0;\n\t\tthis._sweepTimer = undefined;\n\t}\n\n\t/**\n\t * The number of entries currently held in the cache.\n\t * @returns The number of entries in the cache.\n\t */\n\tpublic count(): number {\n\t\treturn this._keyMap.size;\n\t}\n\n\t/**\n\t * Get a value from the cache.\n\t * Returns undefined if the key is absent or the entry has idled out.\n\t * A successful hit increments the entry's frequency and resets its idle timer.\n\t * @param key The key to retrieve.\n\t * @returns The cached value, or undefined on a miss or idle eviction.\n\t */\n\tpublic get(key: string): T | undefined {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\n\t\tconst entry = this._keyMap.get(key);\n\t\tif (Is.empty(entry)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (this.isExpired(entry, Date.now())) {\n\t\t\tthis.removeEntry(key);\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.promote(key, entry);\n\n\t\treturn entry.value;\n\t}\n\n\t/**\n\t * Store a value in the cache.\n\t * If the key already exists its value and frequency are updated.\n\t * When the cache is at capacity, idle entries are swept first; if it is still full the\n\t * least-frequently-used entry is evicted (LRU among ties).\n\t * @param key The key to store.\n\t * @param value The value to cache.\n\t * @param expires Hard expiry timestamp in milliseconds since the epoch. The entry is removed\n\t * once this time is reached regardless of how recently it was used. Must be an integer.\n\t */\n\tpublic set(key: string, value: T, expires?: number): void {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\t\tif (!Is.empty(expires)) {\n\t\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(expires), expires);\n\t\t}\n\n\t\tconst existing = this._keyMap.get(key);\n\t\tif (existing !== undefined) {\n\t\t\tif (this.isExpired(existing, Date.now())) {\n\t\t\t\t// Idle or expired: evict and fall through to add as a fresh entry\n\t\t\t\tthis.removeEntry(key);\n\t\t\t} else {\n\t\t\t\texisting.value = value;\n\t\t\t\texisting.expires = expires;\n\t\t\t\tthis.trackExpires(expires);\n\t\t\t\tthis.promote(key, existing);\n\t\t\t\tthis.startTimer();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tif (this._keyMap.size >= this._capacity) {\n\t\t\tthis.sweepIdle();\n\t\t}\n\t\tif (this._keyMap.size >= this._capacity) {\n\t\t\tthis.evictLfu();\n\t\t}\n\t\tconst entry = { value, freq: 1, lastAccessed: Date.now(), expires };\n\t\tthis._keyMap.set(key, entry);\n\t\tlet bucket = this._freqMap.get(1);\n\t\tif (bucket === undefined) {\n\t\t\tbucket = new Set<string>();\n\t\t\tthis._freqMap.set(1, bucket);\n\t\t}\n\t\tbucket.add(key);\n\t\tthis._minFreq = 1;\n\t\tthis.trackExpires(expires);\n\t\tthis.startTimer();\n\t}\n\n\t/**\n\t * Atomically get an existing value or create and store it once using an async factory.\n\t * Concurrent calls for the same key are serialized via a mutex.\n\t * @param key The key to get or create.\n\t * @param valueFactory Async callback used to build a value when the key is absent.\n\t * @param expires Hard expiry timestamp in milliseconds since the epoch, applied to the entry\n\t * when one is created. Must be an integer.\n\t * @returns The existing or newly created value.\n\t */\n\tpublic async getOrSet(key: string, valueFactory: () => Promise<T>, expires?: number): Promise<T> {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\t\tGuards.function(LfuCache.CLASS_NAME, nameof(valueFactory), valueFactory);\n\t\tif (!Is.empty(expires)) {\n\t\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(expires), expires);\n\t\t}\n\n\t\tconst mutexKey = `${this._mutexScope}:${key}`;\n\t\tawait Mutex.lock(mutexKey, {\n\t\t\ttimeoutMs: this._mutexTimeoutMs,\n\t\t\tthrowOnTimeout: true\n\t\t});\n\n\t\ttry {\n\t\t\tif (this.has(key)) {\n\t\t\t\treturn this.get(key) as T;\n\t\t\t}\n\n\t\t\tconst value = await valueFactory();\n\t\t\tthis.set(key, value, expires);\n\t\t\treturn value;\n\t\t} finally {\n\t\t\tMutex.unlock(mutexKey);\n\t\t}\n\t}\n\n\t/**\n\t * Check whether a key exists in the cache and has not idled out.\n\t * Idle entries are evicted on peek, but a live entry's frequency and TTI are not updated.\n\t * @param key The key to test.\n\t * @returns True if the key is present and not idle.\n\t */\n\tpublic has(key: string): boolean {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\t\tconst entry = this._keyMap.get(key);\n\t\tif (entry === undefined) {\n\t\t\treturn false;\n\t\t}\n\t\tif (this.isExpired(entry, Date.now())) {\n\t\t\tthis.removeEntry(key);\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Return all keys for entries that have not idled out.\n\t * Idle entries encountered during iteration are evicted.\n\t * Keys are returned in ascending frequency order; within the same frequency, LRU first.\n\t * @returns An array of live keys ordered from least-frequently-used to most-frequently-used.\n\t */\n\tpublic keys(): string[] {\n\t\tconst now = Date.now();\n\t\tfor (const [k, entry] of this._keyMap) {\n\t\t\tif (this.isExpired(entry, now)) {\n\t\t\t\tthis.removeEntry(k);\n\t\t\t}\n\t\t}\n\t\tconst result: string[] = [];\n\t\tconst sortedFreqs = [...this._freqMap.keys()].sort((a, b) => a - b);\n\t\tfor (const freq of sortedFreqs) {\n\t\t\tconst bucket = this._freqMap.get(freq);\n\t\t\tif (bucket !== undefined) {\n\t\t\t\tfor (const k of bucket) {\n\t\t\t\t\tresult.push(k);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Remove an entry from the cache.\n\t * Cancels the background timer if the cache becomes empty.\n\t * @param key The key to remove.\n\t */\n\tpublic delete(key: string): void {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\t\tthis.removeEntry(key);\n\t\tif (this._keyMap.size === 0) {\n\t\t\tthis.cancelTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Remove all entries from the cache and cancel the background timer.\n\t */\n\tpublic clear(): void {\n\t\tthis.cancelTimer();\n\t\tthis._keyMap.clear();\n\t\tthis._freqMap.clear();\n\t\tthis._minFreq = 0;\n\t\tthis._nextExpires = undefined;\n\t}\n\n\t/**\n\t * Stop the background idle-sweep timer and release all entries.\n\t * The cache must not be used after this call.\n\t */\n\tpublic destroy(): void {\n\t\tthis.cancelTimer();\n\t\tthis._keyMap.clear();\n\t\tthis._freqMap.clear();\n\t\tthis._minFreq = 0;\n\t\tthis._nextExpires = undefined;\n\t}\n\n\t/**\n\t * Increment the frequency of an entry and move it to the correct frequency bucket.\n\t * Updates lastAccessed to now.\n\t * @param key The key to promote.\n\t * @param entry The entry object to update in place.\n\t * @param entry.value The cached value.\n\t * @param entry.freq The current access frequency.\n\t * @param entry.lastAccessed The last-accessed timestamp in milliseconds.\n\t * @param entry.expires The hard expiry timestamp in milliseconds, or undefined for none.\n\t * @internal\n\t */\n\tprivate promote(\n\t\tkey: string,\n\t\tentry: { value: T; freq: number; lastAccessed: number; expires: number | undefined }\n\t): void {\n\t\tconst oldFreq = entry.freq;\n\t\tconst oldBucket = this._freqMap.get(oldFreq);\n\t\tif (oldBucket !== undefined) {\n\t\t\toldBucket.delete(key);\n\t\t\tif (oldBucket.size === 0) {\n\t\t\t\tthis._freqMap.delete(oldFreq);\n\t\t\t\tif (oldFreq === this._minFreq) {\n\t\t\t\t\tthis._minFreq = oldFreq + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tentry.freq += 1;\n\t\tentry.lastAccessed = Date.now();\n\t\tlet newBucket = this._freqMap.get(entry.freq);\n\t\tif (newBucket === undefined) {\n\t\t\tnewBucket = new Set<string>();\n\t\t\tthis._freqMap.set(entry.freq, newBucket);\n\t\t}\n\t\tnewBucket.add(key);\n\t}\n\n\t/**\n\t * Evict the least-frequently-used entry, breaking ties by recency.\n\t * @internal\n\t */\n\tprivate evictLfu(): void {\n\t\tconst bucket = this._freqMap.get(this._minFreq);\n\t\tif (bucket === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tconst evictKey = bucket.values().next().value as string;\n\t\tbucket.delete(evictKey);\n\t\tif (bucket.size === 0) {\n\t\t\tthis._freqMap.delete(this._minFreq);\n\t\t}\n\t\tthis._keyMap.delete(evictKey);\n\t}\n\n\t/**\n\t * Remove an entry from both the key map and its frequency bucket.\n\t * Recalculates _minFreq if the removed entry was the last one at _minFreq.\n\t * @param key The key to remove.\n\t * @internal\n\t */\n\tprivate removeEntry(key: string): void {\n\t\tconst entry = this._keyMap.get(key);\n\t\tif (entry === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tthis._keyMap.delete(key);\n\t\tconst bucket = this._freqMap.get(entry.freq);\n\t\tif (bucket !== undefined) {\n\t\t\tbucket.delete(key);\n\t\t\tif (bucket.size === 0) {\n\t\t\t\tthis._freqMap.delete(entry.freq);\n\t\t\t\tif (entry.freq === this._minFreq) {\n\t\t\t\t\tlet newMin = Number.MAX_SAFE_INTEGER;\n\t\t\t\t\tfor (const f of this._freqMap.keys()) {\n\t\t\t\t\t\tif (f < newMin) {\n\t\t\t\t\t\t\tnewMin = f;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis._minFreq = newMin === Number.MAX_SAFE_INTEGER ? 1 : newMin;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Cancel the pending timer, sweep idle entries, then restart the timer if entries remain.\n\t * @internal\n\t */\n\tprivate sweepIdle(): void {\n\t\tthis.cancelTimer();\n\t\tconst now = Date.now();\n\t\tlet nextExpires: number | undefined;\n\t\tfor (const [k, entry] of this._keyMap) {\n\t\t\tif (this.isExpired(entry, now)) {\n\t\t\t\tthis.removeEntry(k);\n\t\t\t} else if (\n\t\t\t\tIs.notEmpty(entry.expires) &&\n\t\t\t\t(Is.empty(nextExpires) || entry.expires < nextExpires)\n\t\t\t) {\n\t\t\t\tnextExpires = entry.expires;\n\t\t\t}\n\t\t}\n\t\tthis._nextExpires = nextExpires;\n\t\tif (this._keyMap.size > 0) {\n\t\t\tthis.startTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Record an entry expiry timestamp if it is earlier than the currently tracked one.\n\t * @param expires The expiry timestamp in milliseconds, or undefined for none.\n\t * @internal\n\t */\n\tprivate trackExpires(expires: number | undefined): void {\n\t\tif (Is.notEmpty(expires) && (Is.empty(this._nextExpires) || expires < this._nextExpires)) {\n\t\t\tthis._nextExpires = expires;\n\t\t}\n\t}\n\n\t/**\n\t * Determine whether an entry has idled out or reached its hard expiry timestamp.\n\t * @param entry The entry to test.\n\t * @param entry.lastAccessed The last-accessed timestamp in milliseconds.\n\t * @param entry.expires The hard expiry timestamp in milliseconds, or undefined for none.\n\t * @param now The current time in milliseconds.\n\t * @returns True if the entry should be removed.\n\t * @internal\n\t */\n\tprivate isExpired(\n\t\tentry: { lastAccessed: number; expires: number | undefined },\n\t\tnow: number\n\t): boolean {\n\t\treturn (\n\t\t\tnow - entry.lastAccessed >= this._ttiMs ||\n\t\t\t(Is.notEmpty(entry.expires) && now >= entry.expires)\n\t\t);\n\t}\n\n\t/**\n\t * Schedule the next sweep if no timer is already pending, bringing a pending one forward\n\t * when an entry with an earlier hard expiry has since been added.\n\t * @internal\n\t */\n\tprivate startTimer(): void {\n\t\tconst now = Date.now();\n\t\tlet delay = this._ttiMs;\n\t\tif (Is.notEmpty(this._nextExpires)) {\n\t\t\tdelay = Math.min(delay, Math.max(0, this._nextExpires - now));\n\t\t}\n\t\tif (Is.empty(this._sweepTimer)) {\n\t\t\tthis._scheduledDueAt = now + delay;\n\t\t\tthis._sweepTimer = setTimeout(() => this.sweepIdle(), delay);\n\t\t} else if (now + delay < this._scheduledDueAt) {\n\t\t\tthis.cancelTimer();\n\t\t\tthis.startTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Cancel the pending idle-sweep timer.\n\t * @internal\n\t */\n\tprivate cancelTimer(): void {\n\t\tif (Is.notEmpty(this._sweepTimer)) {\n\t\t\tclearTimeout(this._sweepTimer);\n\t\t\tthis._sweepTimer = undefined;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"lfuCache.js","sourceRoot":"","sources":["../../../src/utils/lfuCache.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { nameof } from \"@twin.org/nameof\";\nimport { Guards } from \"./guards.js\";\nimport { Is } from \"./is.js\";\nimport { Mutex } from \"./mutex.js\";\nimport { Validation } from \"./validation.js\";\nimport { RandomHelper } from \"../helpers/randomHelper.js\";\nimport type { IValidationFailure } from \"../models/IValidationFailure.js\";\n\n/**\n * A fixed-capacity LFU cache with time-to-idle eviction.\n *\n * Entries are removed in two ways:\n * - Capacity eviction: when the cache is full the least-frequently-used entry is removed first.\n * Ties in frequency are broken by recency the least-recently-used entry among those with the\n * minimum frequency is evicted.\n * - TTI eviction: a background timer sweeps idle entries every ttiMs milliseconds.\n * The timer only runs while there are entries; it stops automatically when the cache empties.\n *\n * `get` and `set` increment an entry's access frequency and reset its idle timer.\n * `set` and `getOrSet` accept an optional hard expiry timestamp; the entry is removed once that\n * time is reached however recently it was used, and the TTI still applies alongside it.\n * `has` and `keys` are pure peeks they evict idle entries but do not affect frequency or TTI.\n * Call `destroy` when the cache is no longer needed to stop the background timer.\n */\nexport class LfuCache<T> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<LfuCache<unknown>>();\n\n\t/**\n\t * Default capacity.\n\t */\n\tpublic static readonly DEFAULT_CAPACITY = 1000;\n\n\t/**\n\t * Default time-to-idle in milliseconds.\n\t */\n\tpublic static readonly DEFAULT_TTI_MS = 10000;\n\n\t/**\n\t * The maximum number of entries the cache will hold.\n\t * @internal\n\t */\n\tprivate readonly _capacity: number;\n\n\t/**\n\t * The idle duration in milliseconds after which an untouched entry is evicted.\n\t * @internal\n\t */\n\tprivate readonly _ttiMs: number;\n\n\t/**\n\t * Optional timeout in milliseconds for mutex acquisition.\n\t * @internal\n\t */\n\tprivate readonly _mutexTimeoutMs: number | undefined;\n\n\t/**\n\t * Per-instance namespace prefix for mutex keys.\n\t * @internal\n\t */\n\tprivate readonly _mutexScope: string;\n\n\t/**\n\t * Maps each key to its cached value, access frequency, and last-accessed timestamp.\n\t * @internal\n\t */\n\tprivate readonly _keyMap: Map<\n\t\tstring,\n\t\t{ value: T; freq: number; lastAccessed: number; expires: number | undefined }\n\t>;\n\n\t/**\n\t * Maps each frequency to the ordered set of keys at that frequency (insertion order = LRU).\n\t * @internal\n\t */\n\tprivate readonly _freqMap: Map<number, Set<string>>;\n\n\t/**\n\t * The lowest frequency among all live entries. Maintained to make eviction O(1).\n\t * @internal\n\t */\n\tprivate _minFreq: number;\n\n\t/**\n\t * The earliest hard expiry timestamp among the live entries, used to pace the sweep timer.\n\t * @internal\n\t */\n\tprivate _nextExpires: number | undefined;\n\n\t/**\n\t * The timestamp the pending sweep is due to run at.\n\t * @internal\n\t */\n\tprivate _scheduledDueAt: number;\n\n\t/**\n\t * Handle for the pending idle-sweep timeout, or undefined if no timer is scheduled.\n\t * @internal\n\t */\n\tprivate _sweepTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t/**\n\t * Create a new instance of LfuCache.\n\t * @param options The cache options.\n\t * @param options.capacity Maximum number of entries. Defaults to 1000. Must be a positive integer.\n\t * @param options.ttiMs Time-to-idle in milliseconds. Defaults to 10000. Must be a positive integer.\n\t * @param options.mutexTimeoutMs Maximum time in milliseconds to wait for getOrSet mutex acquisition.\n\t * @throws ValidationError if capacity or ttiMs is not a positive integer.\n\t */\n\tconstructor(options?: { capacity?: number; ttiMs?: number; mutexTimeoutMs?: number }) {\n\t\tconst capacity = options?.capacity ?? LfuCache.DEFAULT_CAPACITY;\n\t\tconst ttiMs = options?.ttiMs ?? LfuCache.DEFAULT_TTI_MS;\n\t\tconst mutexTimeoutMs = options?.mutexTimeoutMs;\n\n\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(capacity), capacity);\n\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(ttiMs), ttiMs);\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(mutexTimeoutMs), mutexTimeoutMs);\n\t\t}\n\n\t\tconst failures: IValidationFailure[] = [];\n\t\tValidation.integer(nameof(capacity), capacity, failures, undefined, { minValue: 1 });\n\t\tValidation.integer(nameof(ttiMs), ttiMs, failures, undefined, { minValue: 1 });\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tValidation.integer(nameof(mutexTimeoutMs), mutexTimeoutMs, failures, undefined, {\n\t\t\t\tminValue: 0\n\t\t\t});\n\t\t}\n\t\tValidation.asValidationError(LfuCache.CLASS_NAME, nameof<LfuCache<unknown>>(), failures);\n\n\t\tthis._capacity = capacity;\n\t\tthis._ttiMs = ttiMs;\n\t\tthis._mutexTimeoutMs = mutexTimeoutMs;\n\t\tthis._mutexScope = `${LfuCache.CLASS_NAME}:${RandomHelper.generateUuidV7()}`;\n\t\tthis._keyMap = new Map();\n\t\tthis._freqMap = new Map();\n\t\tthis._minFreq = 0;\n\t\tthis._nextExpires = undefined;\n\t\tthis._scheduledDueAt = 0;\n\t\tthis._sweepTimer = undefined;\n\t}\n\n\t/**\n\t * The number of entries currently held in the cache.\n\t * @returns The number of entries in the cache.\n\t */\n\tpublic count(): number {\n\t\treturn this._keyMap.size;\n\t}\n\n\t/**\n\t * Get a value from the cache.\n\t * Returns undefined if the key is absent or the entry has idled out.\n\t * A successful hit increments the entry's frequency and resets its idle timer.\n\t * @param key The key to retrieve.\n\t * @returns The cached value, or undefined on a miss or idle eviction.\n\t */\n\tpublic get(key: string): T | undefined {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\n\t\tconst entry = this.lookupLive(key);\n\t\tif (Is.empty(entry)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.promote(key, entry);\n\n\t\treturn entry.value;\n\t}\n\n\t/**\n\t * Store a value in the cache.\n\t * If the key already exists its value and frequency are updated.\n\t * When the cache is at capacity, idle entries are swept first; if it is still full the\n\t * least-frequently-used entry is evicted (LRU among ties).\n\t * @param key The key to store.\n\t * @param value The value to cache.\n\t * @param expires Hard expiry timestamp in milliseconds since the epoch. The entry is removed\n\t * once this time is reached regardless of how recently it was used. Must be an integer.\n\t */\n\tpublic set(key: string, value: T, expires?: number): void {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\t\tif (!Is.empty(expires)) {\n\t\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(expires), expires);\n\t\t}\n\n\t\tconst existing = this._keyMap.get(key);\n\t\tif (existing !== undefined) {\n\t\t\tif (this.isExpired(existing, Date.now())) {\n\t\t\t\t// Idle or expired: evict and fall through to add as a fresh entry\n\t\t\t\tthis.removeEntry(key);\n\t\t\t} else {\n\t\t\t\texisting.value = value;\n\t\t\t\texisting.expires = expires;\n\t\t\t\tthis.trackExpires(expires);\n\t\t\t\tthis.promote(key, existing);\n\t\t\t\tthis.startTimer();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tif (this._keyMap.size >= this._capacity) {\n\t\t\tthis.sweepIdle();\n\t\t}\n\t\tif (this._keyMap.size >= this._capacity) {\n\t\t\tthis.evictLfu();\n\t\t}\n\t\tconst entry = { value, freq: 1, lastAccessed: Date.now(), expires };\n\t\tthis._keyMap.set(key, entry);\n\t\tlet bucket = this._freqMap.get(1);\n\t\tif (bucket === undefined) {\n\t\t\tbucket = new Set<string>();\n\t\t\tthis._freqMap.set(1, bucket);\n\t\t}\n\t\tbucket.add(key);\n\t\tthis._minFreq = 1;\n\t\tthis.trackExpires(expires);\n\t\tthis.startTimer();\n\t}\n\n\t/**\n\t * Atomically get an existing value or create and store it once using an async factory.\n\t * Concurrent calls for the same key are serialized via a mutex.\n\t * @param key The key to get or create.\n\t * @param valueFactory Async callback used to build a value when the key is absent.\n\t * @param expires Hard expiry timestamp in milliseconds since the epoch, applied to the entry\n\t * when one is created. Must be an integer.\n\t * @returns The existing or newly created value.\n\t */\n\tpublic async getOrSet(key: string, valueFactory: () => Promise<T>, expires?: number): Promise<T> {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\t\tGuards.function(LfuCache.CLASS_NAME, nameof(valueFactory), valueFactory);\n\t\tif (!Is.empty(expires)) {\n\t\t\tGuards.integer(LfuCache.CLASS_NAME, nameof(expires), expires);\n\t\t}\n\n\t\tconst mutexKey = `${this._mutexScope}:${key}`;\n\t\tawait Mutex.lock(mutexKey, {\n\t\t\ttimeoutMs: this._mutexTimeoutMs,\n\t\t\tthrowOnTimeout: true\n\t\t});\n\n\t\ttry {\n\t\t\tconst entry = this.lookupLive(key);\n\t\t\tif (Is.notEmpty(entry)) {\n\t\t\t\tthis.promote(key, entry);\n\t\t\t\treturn entry.value;\n\t\t\t}\n\n\t\t\tconst value = await valueFactory();\n\t\t\tthis.set(key, value, expires);\n\t\t\treturn value;\n\t\t} finally {\n\t\t\tMutex.unlock(mutexKey);\n\t\t}\n\t}\n\n\t/**\n\t * Check whether a key exists in the cache and has not idled out.\n\t * Idle entries are evicted on peek, but a live entry's frequency and TTI are not updated.\n\t * @param key The key to test.\n\t * @returns True if the key is present and not idle.\n\t */\n\tpublic has(key: string): boolean {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\t\treturn Is.notEmpty(this.lookupLive(key));\n\t}\n\n\t/**\n\t * Return all keys for entries that have not idled out.\n\t * Idle entries encountered during iteration are evicted.\n\t * Keys are returned in ascending frequency order; within the same frequency, LRU first.\n\t * @returns An array of live keys ordered from least-frequently-used to most-frequently-used.\n\t */\n\tpublic keys(): string[] {\n\t\tconst now = Date.now();\n\t\tfor (const [k, entry] of this._keyMap) {\n\t\t\tif (this.isExpired(entry, now)) {\n\t\t\t\tthis.removeEntry(k);\n\t\t\t}\n\t\t}\n\t\tconst result: string[] = [];\n\t\tconst sortedFreqs = [...this._freqMap.keys()].sort((a, b) => a - b);\n\t\tfor (const freq of sortedFreqs) {\n\t\t\tconst bucket = this._freqMap.get(freq);\n\t\t\tif (bucket !== undefined) {\n\t\t\t\tfor (const k of bucket) {\n\t\t\t\t\tresult.push(k);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Remove an entry from the cache.\n\t * Cancels the background timer if the cache becomes empty.\n\t * @param key The key to remove.\n\t */\n\tpublic delete(key: string): void {\n\t\tGuards.stringValue(LfuCache.CLASS_NAME, nameof(key), key);\n\t\tthis.removeEntry(key);\n\t\tif (this._keyMap.size === 0) {\n\t\t\tthis.cancelTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Remove all entries from the cache and cancel the background timer.\n\t */\n\tpublic clear(): void {\n\t\tthis.cancelTimer();\n\t\tthis._keyMap.clear();\n\t\tthis._freqMap.clear();\n\t\tthis._minFreq = 0;\n\t\tthis._nextExpires = undefined;\n\t}\n\n\t/**\n\t * Stop the background idle-sweep timer and release all entries.\n\t * The cache must not be used after this call.\n\t */\n\tpublic destroy(): void {\n\t\tthis.cancelTimer();\n\t\tthis._keyMap.clear();\n\t\tthis._freqMap.clear();\n\t\tthis._minFreq = 0;\n\t\tthis._nextExpires = undefined;\n\t}\n\n\t/**\n\t * Resolve an entry which is present and has not expired, evicting it if it has.\n\t * The expiry check is evaluated once so a caller cannot observe an entry as live and\n\t * then as expired across two separate lookups.\n\t * @param key The key to resolve.\n\t * @returns The live entry, or undefined if the key is absent or has been evicted.\n\t * @internal\n\t */\n\tprivate lookupLive(\n\t\tkey: string\n\t): { value: T; freq: number; lastAccessed: number; expires: number | undefined } | undefined {\n\t\tconst entry = this._keyMap.get(key);\n\t\tif (Is.empty(entry)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (this.isExpired(entry, Date.now())) {\n\t\t\tthis.removeEntry(key);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn entry;\n\t}\n\n\t/**\n\t * Increment the frequency of an entry and move it to the correct frequency bucket.\n\t * Updates lastAccessed to now.\n\t * @param key The key to promote.\n\t * @param entry The entry object to update in place.\n\t * @param entry.value The cached value.\n\t * @param entry.freq The current access frequency.\n\t * @param entry.lastAccessed The last-accessed timestamp in milliseconds.\n\t * @param entry.expires The hard expiry timestamp in milliseconds, or undefined for none.\n\t * @internal\n\t */\n\tprivate promote(\n\t\tkey: string,\n\t\tentry: { value: T; freq: number; lastAccessed: number; expires: number | undefined }\n\t): void {\n\t\tconst oldFreq = entry.freq;\n\t\tconst oldBucket = this._freqMap.get(oldFreq);\n\t\tif (oldBucket !== undefined) {\n\t\t\toldBucket.delete(key);\n\t\t\tif (oldBucket.size === 0) {\n\t\t\t\tthis._freqMap.delete(oldFreq);\n\t\t\t\tif (oldFreq === this._minFreq) {\n\t\t\t\t\tthis._minFreq = oldFreq + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tentry.freq += 1;\n\t\tentry.lastAccessed = Date.now();\n\t\tlet newBucket = this._freqMap.get(entry.freq);\n\t\tif (newBucket === undefined) {\n\t\t\tnewBucket = new Set<string>();\n\t\t\tthis._freqMap.set(entry.freq, newBucket);\n\t\t}\n\t\tnewBucket.add(key);\n\t}\n\n\t/**\n\t * Evict the least-frequently-used entry, breaking ties by recency.\n\t * @internal\n\t */\n\tprivate evictLfu(): void {\n\t\tconst bucket = this._freqMap.get(this._minFreq);\n\t\tif (bucket === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tconst evictKey = bucket.values().next().value as string;\n\t\tbucket.delete(evictKey);\n\t\tif (bucket.size === 0) {\n\t\t\tthis._freqMap.delete(this._minFreq);\n\t\t}\n\t\tthis._keyMap.delete(evictKey);\n\t}\n\n\t/**\n\t * Remove an entry from both the key map and its frequency bucket.\n\t * Recalculates _minFreq if the removed entry was the last one at _minFreq.\n\t * @param key The key to remove.\n\t * @internal\n\t */\n\tprivate removeEntry(key: string): void {\n\t\tconst entry = this._keyMap.get(key);\n\t\tif (entry === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tthis._keyMap.delete(key);\n\t\tconst bucket = this._freqMap.get(entry.freq);\n\t\tif (bucket !== undefined) {\n\t\t\tbucket.delete(key);\n\t\t\tif (bucket.size === 0) {\n\t\t\t\tthis._freqMap.delete(entry.freq);\n\t\t\t\tif (entry.freq === this._minFreq) {\n\t\t\t\t\tlet newMin = Number.MAX_SAFE_INTEGER;\n\t\t\t\t\tfor (const f of this._freqMap.keys()) {\n\t\t\t\t\t\tif (f < newMin) {\n\t\t\t\t\t\t\tnewMin = f;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis._minFreq = newMin === Number.MAX_SAFE_INTEGER ? 1 : newMin;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Cancel the pending timer, sweep idle entries, then restart the timer if entries remain.\n\t * @internal\n\t */\n\tprivate sweepIdle(): void {\n\t\tthis.cancelTimer();\n\t\tconst now = Date.now();\n\t\tlet nextExpires: number | undefined;\n\t\tfor (const [k, entry] of this._keyMap) {\n\t\t\tif (this.isExpired(entry, now)) {\n\t\t\t\tthis.removeEntry(k);\n\t\t\t} else if (\n\t\t\t\tIs.notEmpty(entry.expires) &&\n\t\t\t\t(Is.empty(nextExpires) || entry.expires < nextExpires)\n\t\t\t) {\n\t\t\t\tnextExpires = entry.expires;\n\t\t\t}\n\t\t}\n\t\tthis._nextExpires = nextExpires;\n\t\tif (this._keyMap.size > 0) {\n\t\t\tthis.startTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Record an entry expiry timestamp if it is earlier than the currently tracked one.\n\t * @param expires The expiry timestamp in milliseconds, or undefined for none.\n\t * @internal\n\t */\n\tprivate trackExpires(expires: number | undefined): void {\n\t\tif (Is.notEmpty(expires) && (Is.empty(this._nextExpires) || expires < this._nextExpires)) {\n\t\t\tthis._nextExpires = expires;\n\t\t}\n\t}\n\n\t/**\n\t * Determine whether an entry has idled out or reached its hard expiry timestamp.\n\t * @param entry The entry to test.\n\t * @param entry.lastAccessed The last-accessed timestamp in milliseconds.\n\t * @param entry.expires The hard expiry timestamp in milliseconds, or undefined for none.\n\t * @param now The current time in milliseconds.\n\t * @returns True if the entry should be removed.\n\t * @internal\n\t */\n\tprivate isExpired(\n\t\tentry: { lastAccessed: number; expires: number | undefined },\n\t\tnow: number\n\t): boolean {\n\t\treturn (\n\t\t\tnow - entry.lastAccessed >= this._ttiMs ||\n\t\t\t(Is.notEmpty(entry.expires) && now >= entry.expires)\n\t\t);\n\t}\n\n\t/**\n\t * Schedule the next sweep if no timer is already pending, bringing a pending one forward\n\t * when an entry with an earlier hard expiry has since been added.\n\t * @internal\n\t */\n\tprivate startTimer(): void {\n\t\tconst now = Date.now();\n\t\tlet delay = this._ttiMs;\n\t\tif (Is.notEmpty(this._nextExpires)) {\n\t\t\tdelay = Math.min(delay, Math.max(0, this._nextExpires - now));\n\t\t}\n\t\tif (Is.empty(this._sweepTimer)) {\n\t\t\tthis._scheduledDueAt = now + delay;\n\t\t\tthis._sweepTimer = setTimeout(() => this.sweepIdle(), delay);\n\t\t} else if (now + delay < this._scheduledDueAt) {\n\t\t\tthis.cancelTimer();\n\t\t\tthis.startTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Cancel the pending idle-sweep timer.\n\t * @internal\n\t */\n\tprivate cancelTimer(): void {\n\t\tif (Is.notEmpty(this._sweepTimer)) {\n\t\t\tclearTimeout(this._sweepTimer);\n\t\t\tthis._sweepTimer = undefined;\n\t\t}\n\t}\n}\n"]}
@@ -121,19 +121,11 @@ export class LruCache {
121
121
  */
122
122
  get(key) {
123
123
  Guards.stringValue(LruCache.CLASS_NAME, "key", key);
124
- const entry = this._cache.get(key);
125
- if (entry === undefined) {
126
- return undefined;
127
- }
128
- const now = Date.now();
129
- if (this.isExpired(entry, now)) {
130
- this._cache.delete(key);
124
+ const entry = this.lookupLive(key);
125
+ if (Is.empty(entry)) {
131
126
  return undefined;
132
127
  }
133
- // Move to end of Map (most-recently-used) via delete + re-insert
134
- this._cache.delete(key);
135
- entry.lastAccessed = now;
136
- this._cache.set(key, entry);
128
+ this.touch(key, entry);
137
129
  return entry.value;
138
130
  }
139
131
  /**
@@ -188,8 +180,10 @@ export class LruCache {
188
180
  throwOnTimeout: true
189
181
  });
190
182
  try {
191
- if (this.has(key)) {
192
- return this.get(key);
183
+ const entry = this.lookupLive(key);
184
+ if (Is.notEmpty(entry)) {
185
+ this.touch(key, entry);
186
+ return entry.value;
193
187
  }
194
188
  const value = await valueFactory();
195
189
  this.set(key, value, expires);
@@ -207,15 +201,7 @@ export class LruCache {
207
201
  */
208
202
  has(key) {
209
203
  Guards.stringValue(LruCache.CLASS_NAME, "key", key);
210
- const entry = this._cache.get(key);
211
- if (entry === undefined) {
212
- return false;
213
- }
214
- if (this.isExpired(entry, Date.now())) {
215
- this._cache.delete(key);
216
- return false;
217
- }
218
- return true;
204
+ return Is.notEmpty(this.lookupLive(key));
219
205
  }
220
206
  /**
221
207
  * Return all keys for entries that have not idled out.
@@ -264,6 +250,40 @@ export class LruCache {
264
250
  this._cache.clear();
265
251
  this._nextExpires = undefined;
266
252
  }
253
+ /**
254
+ * Resolve an entry which is present and has not expired, evicting it if it has.
255
+ * The expiry check is evaluated once so a caller cannot observe an entry as live and
256
+ * then as expired across two separate lookups.
257
+ * @param key The key to resolve.
258
+ * @returns The live entry, or undefined if the key is absent or has been evicted.
259
+ * @internal
260
+ */
261
+ lookupLive(key) {
262
+ const entry = this._cache.get(key);
263
+ if (Is.empty(entry)) {
264
+ return undefined;
265
+ }
266
+ if (this.isExpired(entry, Date.now())) {
267
+ this._cache.delete(key);
268
+ return undefined;
269
+ }
270
+ return entry;
271
+ }
272
+ /**
273
+ * Move an entry to the most-recently-used position and reset its idle timer.
274
+ * @param key The key to touch.
275
+ * @param entry The entry object to update in place.
276
+ * @param entry.value The cached value.
277
+ * @param entry.lastAccessed The last-accessed timestamp in milliseconds.
278
+ * @param entry.expires The hard expiry timestamp in milliseconds, or undefined for none.
279
+ * @internal
280
+ */
281
+ touch(key, entry) {
282
+ // Move to end of Map (most-recently-used) via delete + re-insert
283
+ this._cache.delete(key);
284
+ entry.lastAccessed = Date.now();
285
+ this._cache.set(key, entry);
286
+ }
267
287
  /**
268
288
  * Delete all entries whose idle time has been exceeded, then restart the timer
269
289
  * if any entries remain.
@@ -1 +1 @@
1
- {"version":3,"file":"lruCache.js","sourceRoot":"","sources":["../../../src/utils/lruCache.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { nameof } from \"@twin.org/nameof\";\nimport { Guards } from \"./guards.js\";\nimport { Is } from \"./is.js\";\nimport { Mutex } from \"./mutex.js\";\nimport { Validation } from \"./validation.js\";\nimport { RandomHelper } from \"../helpers/randomHelper.js\";\nimport type { IValidationFailure } from \"../models/IValidationFailure.js\";\n\n/**\n * A fixed-capacity LRU cache with time-to-idle eviction.\n *\n * Entries are removed in two ways:\n * - Capacity eviction: when the cache is full the least-recently-used entry is removed first.\n * - TTI eviction: a background timer sweeps idle entries every ttiMs milliseconds.\n * The timer only runs while there are entries; it stops automatically when the cache empties.\n *\n * `get` and `set` both update an entry's LRU position and reset its idle timer.\n * `set` and `getOrSet` accept an optional hard expiry timestamp; the entry is removed once that\n * time is reached however recently it was used, and the TTI still applies alongside it.\n * `has` is a pure peek it evicts idle entries but does not refresh a live entry's TTI.\n * Call `destroy` when the cache is no longer needed to stop the background timer.\n */\nexport class LruCache<T = unknown> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<LruCache>();\n\n\t/**\n\t * Default capacity.\n\t */\n\tpublic static readonly DEFAULT_CAPACITY = 1000;\n\n\t/**\n\t * Default time-to-idle in milliseconds.\n\t */\n\tpublic static readonly DEFAULT_TTI_MS = 10000;\n\n\t/**\n\t * The maximum number of entries the cache will hold.\n\t * @internal\n\t */\n\tprivate readonly _capacity: number;\n\n\t/**\n\t * The idle duration in milliseconds after which an untouched entry is evicted.\n\t * @internal\n\t */\n\tprivate readonly _ttiMs: number;\n\n\t/**\n\t * Optional timeout in milliseconds for mutex acquisition.\n\t * @internal\n\t */\n\tprivate readonly _mutexTimeoutMs: number | undefined;\n\n\t/**\n\t * Per-instance namespace prefix for mutex keys.\n\t * @internal\n\t */\n\tprivate readonly _mutexScope: string;\n\n\t/**\n\t * Underlying storage; Map iteration order tracks LRU position (first = oldest).\n\t * @internal\n\t */\n\tprivate readonly _cache: Map<\n\t\tstring,\n\t\t{ value: T; lastAccessed: number; expires: number | undefined }\n\t>;\n\n\t/**\n\t * The earliest hard expiry timestamp among the live entries, used to pace the sweep timer.\n\t * @internal\n\t */\n\tprivate _nextExpires: number | undefined;\n\n\t/**\n\t * The timestamp the pending sweep is due to run at.\n\t * @internal\n\t */\n\tprivate _scheduledDueAt: number;\n\n\t/**\n\t * Handle for the pending idle-sweep timeout, or undefined if no timer is scheduled.\n\t * @internal\n\t */\n\tprivate _sweepTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t/**\n\t * Create a new instance of LruCache.\n\t * @param options The cache options.\n\t * @param options.capacity Maximum number of entries. Defaults to 1000. Must be a positive integer.\n\t * @param options.ttiMs Time-to-idle in milliseconds. Defaults to 10000. Must be a positive integer.\n\t * @param options.mutexTimeoutMs Maximum time in milliseconds to wait for getOrSet mutex acquisition.\n\t * @throws ValidationError if capacity or ttiMs is not a positive integer.\n\t */\n\tconstructor(options?: { capacity?: number; ttiMs?: number; mutexTimeoutMs?: number }) {\n\t\tconst capacity = options?.capacity ?? LruCache.DEFAULT_CAPACITY;\n\t\tconst ttiMs = options?.ttiMs ?? LruCache.DEFAULT_TTI_MS;\n\t\tconst mutexTimeoutMs = options?.mutexTimeoutMs;\n\n\t\tGuards.integer(LruCache.CLASS_NAME, nameof(capacity), capacity);\n\t\tGuards.integer(LruCache.CLASS_NAME, nameof(ttiMs), ttiMs);\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tGuards.integer(LruCache.CLASS_NAME, nameof(mutexTimeoutMs), mutexTimeoutMs);\n\t\t}\n\n\t\tconst failures: IValidationFailure[] = [];\n\t\tValidation.integer(nameof(capacity), capacity, failures, undefined, { minValue: 1 });\n\t\tValidation.integer(nameof(ttiMs), ttiMs, failures, undefined, { minValue: 1 });\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tValidation.integer(nameof(mutexTimeoutMs), mutexTimeoutMs, failures, undefined, {\n\t\t\t\tminValue: 0\n\t\t\t});\n\t\t}\n\t\tValidation.asValidationError(LruCache.CLASS_NAME, nameof<LruCache>(), failures);\n\n\t\tthis._capacity = capacity;\n\t\tthis._ttiMs = ttiMs;\n\t\tthis._mutexTimeoutMs = mutexTimeoutMs;\n\t\tthis._mutexScope = `${LruCache.CLASS_NAME}:${RandomHelper.generateUuidV7()}`;\n\t\tthis._cache = new Map();\n\t\tthis._nextExpires = undefined;\n\t\tthis._scheduledDueAt = 0;\n\t\tthis._sweepTimer = undefined;\n\t}\n\n\t/**\n\t * The number of entries currently held in the cache.\n\t * @returns The number of entries in the cache.\n\t */\n\tpublic count(): number {\n\t\treturn this._cache.size;\n\t}\n\n\t/**\n\t * Get a value from the cache.\n\t * Returns undefined if the key is absent or the entry has idled out.\n\t * A successful hit resets the entry's idle timer and moves it to most-recently-used.\n\t * @param key The key to retrieve.\n\t * @returns The cached value, or undefined on a miss or idle eviction.\n\t */\n\tpublic get(key: string): T | undefined {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\n\t\tconst entry = this._cache.get(key);\n\t\tif (entry === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst now = Date.now();\n\t\tif (this.isExpired(entry, now)) {\n\t\t\tthis._cache.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\t\t// Move to end of Map (most-recently-used) via delete + re-insert\n\t\tthis._cache.delete(key);\n\t\tentry.lastAccessed = now;\n\t\tthis._cache.set(key, entry);\n\n\t\treturn entry.value;\n\t}\n\n\t/**\n\t * Store a value in the cache.\n\t * If the key already exists its value and idle timer are refreshed.\n\t * When the cache is at capacity, idle entries are swept first; if it is still full the\n\t * least-recently-used entry is evicted.\n\t * @param key The key to store.\n\t * @param value The value to cache.\n\t * @param expires Hard expiry timestamp in milliseconds since the epoch. The entry is removed\n\t * once this time is reached regardless of how recently it was used. Must be an integer.\n\t */\n\tpublic set(key: string, value: T, expires?: number): void {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\n\t\tif (!Is.empty(expires)) {\n\t\t\tGuards.integer(LruCache.CLASS_NAME, nameof(expires), expires);\n\t\t}\n\n\t\tconst now = Date.now();\n\t\t// Remove any existing entry so the refreshed version is inserted at the end\n\t\tthis._cache.delete(key);\n\t\tif (this._cache.size >= this._capacity) {\n\t\t\tthis.sweepIdle();\n\t\t}\n\t\tif (this._cache.size >= this._capacity) {\n\t\t\tconst lruKey = this._cache.keys().next().value;\n\t\t\tif (lruKey !== undefined) {\n\t\t\t\tthis._cache.delete(lruKey);\n\t\t\t}\n\t\t}\n\t\tthis._cache.set(key, { value, lastAccessed: now, expires });\n\t\tthis.trackExpires(expires);\n\t\tthis.startTimer();\n\t}\n\n\t/**\n\t * Atomically get an existing value or create and store it once using an async factory.\n\t * Concurrent calls for the same key are serialized via a mutex.\n\t * @param key The key to get or create.\n\t * @param valueFactory Async callback used to build a value when the key is absent.\n\t * @param expires Hard expiry timestamp in milliseconds since the epoch, applied to the entry\n\t * when one is created. Must be an integer.\n\t * @returns The existing or newly created value.\n\t */\n\tpublic async getOrSet(key: string, valueFactory: () => Promise<T>, expires?: number): Promise<T> {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\t\tGuards.function(LruCache.CLASS_NAME, nameof(valueFactory), valueFactory);\n\t\tif (!Is.empty(expires)) {\n\t\t\tGuards.integer(LruCache.CLASS_NAME, nameof(expires), expires);\n\t\t}\n\n\t\tconst mutexKey = `${this._mutexScope}:${key}`;\n\t\tawait Mutex.lock(mutexKey, {\n\t\t\ttimeoutMs: this._mutexTimeoutMs,\n\t\t\tthrowOnTimeout: true\n\t\t});\n\n\t\ttry {\n\t\t\tif (this.has(key)) {\n\t\t\t\treturn this.get(key) as T;\n\t\t\t}\n\n\t\t\tconst value = await valueFactory();\n\t\t\tthis.set(key, value, expires);\n\t\t\treturn value;\n\t\t} finally {\n\t\t\tMutex.unlock(mutexKey);\n\t\t}\n\t}\n\n\t/**\n\t * Check whether a key exists in the cache and has not idled out.\n\t * Idle entries are evicted on peek, but a live entry's TTI is not reset.\n\t * @param key The key to test.\n\t * @returns True if the key is present and not idle.\n\t */\n\tpublic has(key: string): boolean {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\t\tconst entry = this._cache.get(key);\n\t\tif (entry === undefined) {\n\t\t\treturn false;\n\t\t}\n\t\tif (this.isExpired(entry, Date.now())) {\n\t\t\tthis._cache.delete(key);\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Return all keys for entries that have not idled out.\n\t * Idle entries encountered during iteration are evicted.\n\t * @returns An array of live keys in least-recently-used to most-recently-used order.\n\t */\n\tpublic keys(): string[] {\n\t\tconst now = Date.now();\n\t\tconst result: string[] = [];\n\t\tfor (const [k, entry] of this._cache) {\n\t\t\tif (this.isExpired(entry, now)) {\n\t\t\t\tthis._cache.delete(k);\n\t\t\t} else {\n\t\t\t\tresult.push(k);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Remove an entry from the cache.\n\t * Cancels the background timer if the cache becomes empty.\n\t * @param key The key to remove.\n\t */\n\tpublic delete(key: string): void {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\t\tthis._cache.delete(key);\n\t\tif (this._cache.size === 0) {\n\t\t\tthis.cancelTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Remove all entries from the cache and cancel the background timer.\n\t */\n\tpublic clear(): void {\n\t\tthis.cancelTimer();\n\t\tthis._cache.clear();\n\t\tthis._nextExpires = undefined;\n\t}\n\n\t/**\n\t * Stop the background idle-sweep timer and release all entries.\n\t * The cache must not be used after this call.\n\t */\n\tpublic destroy(): void {\n\t\tthis.cancelTimer();\n\t\tthis._cache.clear();\n\t\tthis._nextExpires = undefined;\n\t}\n\n\t/**\n\t * Delete all entries whose idle time has been exceeded, then restart the timer\n\t * if any entries remain.\n\t * @internal\n\t */\n\tprivate sweepIdle(): void {\n\t\tthis.cancelTimer();\n\t\tconst now = Date.now();\n\t\tlet nextExpires: number | undefined;\n\t\tfor (const [k, entry] of this._cache) {\n\t\t\tif (this.isExpired(entry, now)) {\n\t\t\t\tthis._cache.delete(k);\n\t\t\t} else if (\n\t\t\t\tIs.notEmpty(entry.expires) &&\n\t\t\t\t(Is.empty(nextExpires) || entry.expires < nextExpires)\n\t\t\t) {\n\t\t\t\tnextExpires = entry.expires;\n\t\t\t}\n\t\t}\n\t\tthis._nextExpires = nextExpires;\n\t\tif (this._cache.size > 0) {\n\t\t\tthis.startTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Record an entry expiry timestamp if it is earlier than the currently tracked one.\n\t * @param expires The expiry timestamp in milliseconds, or undefined for none.\n\t * @internal\n\t */\n\tprivate trackExpires(expires: number | undefined): void {\n\t\tif (Is.notEmpty(expires) && (Is.empty(this._nextExpires) || expires < this._nextExpires)) {\n\t\t\tthis._nextExpires = expires;\n\t\t}\n\t}\n\n\t/**\n\t * Determine whether an entry has idled out or reached its hard expiry timestamp.\n\t * @param entry The entry to test.\n\t * @param entry.lastAccessed The last-accessed timestamp in milliseconds.\n\t * @param entry.expires The hard expiry timestamp in milliseconds, or undefined for none.\n\t * @param now The current time in milliseconds.\n\t * @returns True if the entry should be removed.\n\t * @internal\n\t */\n\tprivate isExpired(\n\t\tentry: { lastAccessed: number; expires: number | undefined },\n\t\tnow: number\n\t): boolean {\n\t\treturn (\n\t\t\tnow - entry.lastAccessed >= this._ttiMs ||\n\t\t\t(Is.notEmpty(entry.expires) && now >= entry.expires)\n\t\t);\n\t}\n\n\t/**\n\t * Schedule the next sweep if no timer is already pending, bringing a pending one forward\n\t * when an entry with an earlier hard expiry has since been added.\n\t * @internal\n\t */\n\tprivate startTimer(): void {\n\t\tconst now = Date.now();\n\t\tlet delay = this._ttiMs;\n\t\tif (Is.notEmpty(this._nextExpires)) {\n\t\t\tdelay = Math.min(delay, Math.max(0, this._nextExpires - now));\n\t\t}\n\t\tif (Is.empty(this._sweepTimer)) {\n\t\t\tthis._scheduledDueAt = now + delay;\n\t\t\tthis._sweepTimer = setTimeout(() => this.sweepIdle(), delay);\n\t\t} else if (now + delay < this._scheduledDueAt) {\n\t\t\tthis.cancelTimer();\n\t\t\tthis.startTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Cancel the pending idle-sweep timer.\n\t * @internal\n\t */\n\tprivate cancelTimer(): void {\n\t\tif (Is.notEmpty(this._sweepTimer)) {\n\t\t\tclearTimeout(this._sweepTimer);\n\t\t\tthis._sweepTimer = undefined;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"lruCache.js","sourceRoot":"","sources":["../../../src/utils/lruCache.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { nameof } from \"@twin.org/nameof\";\nimport { Guards } from \"./guards.js\";\nimport { Is } from \"./is.js\";\nimport { Mutex } from \"./mutex.js\";\nimport { Validation } from \"./validation.js\";\nimport { RandomHelper } from \"../helpers/randomHelper.js\";\nimport type { IValidationFailure } from \"../models/IValidationFailure.js\";\n\n/**\n * A fixed-capacity LRU cache with time-to-idle eviction.\n *\n * Entries are removed in two ways:\n * - Capacity eviction: when the cache is full the least-recently-used entry is removed first.\n * - TTI eviction: a background timer sweeps idle entries every ttiMs milliseconds.\n * The timer only runs while there are entries; it stops automatically when the cache empties.\n *\n * `get` and `set` both update an entry's LRU position and reset its idle timer.\n * `set` and `getOrSet` accept an optional hard expiry timestamp; the entry is removed once that\n * time is reached however recently it was used, and the TTI still applies alongside it.\n * `has` is a pure peek it evicts idle entries but does not refresh a live entry's TTI.\n * Call `destroy` when the cache is no longer needed to stop the background timer.\n */\nexport class LruCache<T = unknown> {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<LruCache>();\n\n\t/**\n\t * Default capacity.\n\t */\n\tpublic static readonly DEFAULT_CAPACITY = 1000;\n\n\t/**\n\t * Default time-to-idle in milliseconds.\n\t */\n\tpublic static readonly DEFAULT_TTI_MS = 10000;\n\n\t/**\n\t * The maximum number of entries the cache will hold.\n\t * @internal\n\t */\n\tprivate readonly _capacity: number;\n\n\t/**\n\t * The idle duration in milliseconds after which an untouched entry is evicted.\n\t * @internal\n\t */\n\tprivate readonly _ttiMs: number;\n\n\t/**\n\t * Optional timeout in milliseconds for mutex acquisition.\n\t * @internal\n\t */\n\tprivate readonly _mutexTimeoutMs: number | undefined;\n\n\t/**\n\t * Per-instance namespace prefix for mutex keys.\n\t * @internal\n\t */\n\tprivate readonly _mutexScope: string;\n\n\t/**\n\t * Underlying storage; Map iteration order tracks LRU position (first = oldest).\n\t * @internal\n\t */\n\tprivate readonly _cache: Map<\n\t\tstring,\n\t\t{ value: T; lastAccessed: number; expires: number | undefined }\n\t>;\n\n\t/**\n\t * The earliest hard expiry timestamp among the live entries, used to pace the sweep timer.\n\t * @internal\n\t */\n\tprivate _nextExpires: number | undefined;\n\n\t/**\n\t * The timestamp the pending sweep is due to run at.\n\t * @internal\n\t */\n\tprivate _scheduledDueAt: number;\n\n\t/**\n\t * Handle for the pending idle-sweep timeout, or undefined if no timer is scheduled.\n\t * @internal\n\t */\n\tprivate _sweepTimer: ReturnType<typeof setTimeout> | undefined;\n\n\t/**\n\t * Create a new instance of LruCache.\n\t * @param options The cache options.\n\t * @param options.capacity Maximum number of entries. Defaults to 1000. Must be a positive integer.\n\t * @param options.ttiMs Time-to-idle in milliseconds. Defaults to 10000. Must be a positive integer.\n\t * @param options.mutexTimeoutMs Maximum time in milliseconds to wait for getOrSet mutex acquisition.\n\t * @throws ValidationError if capacity or ttiMs is not a positive integer.\n\t */\n\tconstructor(options?: { capacity?: number; ttiMs?: number; mutexTimeoutMs?: number }) {\n\t\tconst capacity = options?.capacity ?? LruCache.DEFAULT_CAPACITY;\n\t\tconst ttiMs = options?.ttiMs ?? LruCache.DEFAULT_TTI_MS;\n\t\tconst mutexTimeoutMs = options?.mutexTimeoutMs;\n\n\t\tGuards.integer(LruCache.CLASS_NAME, nameof(capacity), capacity);\n\t\tGuards.integer(LruCache.CLASS_NAME, nameof(ttiMs), ttiMs);\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tGuards.integer(LruCache.CLASS_NAME, nameof(mutexTimeoutMs), mutexTimeoutMs);\n\t\t}\n\n\t\tconst failures: IValidationFailure[] = [];\n\t\tValidation.integer(nameof(capacity), capacity, failures, undefined, { minValue: 1 });\n\t\tValidation.integer(nameof(ttiMs), ttiMs, failures, undefined, { minValue: 1 });\n\t\tif (Is.notEmpty(mutexTimeoutMs)) {\n\t\t\tValidation.integer(nameof(mutexTimeoutMs), mutexTimeoutMs, failures, undefined, {\n\t\t\t\tminValue: 0\n\t\t\t});\n\t\t}\n\t\tValidation.asValidationError(LruCache.CLASS_NAME, nameof<LruCache>(), failures);\n\n\t\tthis._capacity = capacity;\n\t\tthis._ttiMs = ttiMs;\n\t\tthis._mutexTimeoutMs = mutexTimeoutMs;\n\t\tthis._mutexScope = `${LruCache.CLASS_NAME}:${RandomHelper.generateUuidV7()}`;\n\t\tthis._cache = new Map();\n\t\tthis._nextExpires = undefined;\n\t\tthis._scheduledDueAt = 0;\n\t\tthis._sweepTimer = undefined;\n\t}\n\n\t/**\n\t * The number of entries currently held in the cache.\n\t * @returns The number of entries in the cache.\n\t */\n\tpublic count(): number {\n\t\treturn this._cache.size;\n\t}\n\n\t/**\n\t * Get a value from the cache.\n\t * Returns undefined if the key is absent or the entry has idled out.\n\t * A successful hit resets the entry's idle timer and moves it to most-recently-used.\n\t * @param key The key to retrieve.\n\t * @returns The cached value, or undefined on a miss or idle eviction.\n\t */\n\tpublic get(key: string): T | undefined {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\n\t\tconst entry = this.lookupLive(key);\n\t\tif (Is.empty(entry)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.touch(key, entry);\n\n\t\treturn entry.value;\n\t}\n\n\t/**\n\t * Store a value in the cache.\n\t * If the key already exists its value and idle timer are refreshed.\n\t * When the cache is at capacity, idle entries are swept first; if it is still full the\n\t * least-recently-used entry is evicted.\n\t * @param key The key to store.\n\t * @param value The value to cache.\n\t * @param expires Hard expiry timestamp in milliseconds since the epoch. The entry is removed\n\t * once this time is reached regardless of how recently it was used. Must be an integer.\n\t */\n\tpublic set(key: string, value: T, expires?: number): void {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\n\t\tif (!Is.empty(expires)) {\n\t\t\tGuards.integer(LruCache.CLASS_NAME, nameof(expires), expires);\n\t\t}\n\n\t\tconst now = Date.now();\n\t\t// Remove any existing entry so the refreshed version is inserted at the end\n\t\tthis._cache.delete(key);\n\t\tif (this._cache.size >= this._capacity) {\n\t\t\tthis.sweepIdle();\n\t\t}\n\t\tif (this._cache.size >= this._capacity) {\n\t\t\tconst lruKey = this._cache.keys().next().value;\n\t\t\tif (lruKey !== undefined) {\n\t\t\t\tthis._cache.delete(lruKey);\n\t\t\t}\n\t\t}\n\t\tthis._cache.set(key, { value, lastAccessed: now, expires });\n\t\tthis.trackExpires(expires);\n\t\tthis.startTimer();\n\t}\n\n\t/**\n\t * Atomically get an existing value or create and store it once using an async factory.\n\t * Concurrent calls for the same key are serialized via a mutex.\n\t * @param key The key to get or create.\n\t * @param valueFactory Async callback used to build a value when the key is absent.\n\t * @param expires Hard expiry timestamp in milliseconds since the epoch, applied to the entry\n\t * when one is created. Must be an integer.\n\t * @returns The existing or newly created value.\n\t */\n\tpublic async getOrSet(key: string, valueFactory: () => Promise<T>, expires?: number): Promise<T> {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\t\tGuards.function(LruCache.CLASS_NAME, nameof(valueFactory), valueFactory);\n\t\tif (!Is.empty(expires)) {\n\t\t\tGuards.integer(LruCache.CLASS_NAME, nameof(expires), expires);\n\t\t}\n\n\t\tconst mutexKey = `${this._mutexScope}:${key}`;\n\t\tawait Mutex.lock(mutexKey, {\n\t\t\ttimeoutMs: this._mutexTimeoutMs,\n\t\t\tthrowOnTimeout: true\n\t\t});\n\n\t\ttry {\n\t\t\tconst entry = this.lookupLive(key);\n\t\t\tif (Is.notEmpty(entry)) {\n\t\t\t\tthis.touch(key, entry);\n\t\t\t\treturn entry.value;\n\t\t\t}\n\n\t\t\tconst value = await valueFactory();\n\t\t\tthis.set(key, value, expires);\n\t\t\treturn value;\n\t\t} finally {\n\t\t\tMutex.unlock(mutexKey);\n\t\t}\n\t}\n\n\t/**\n\t * Check whether a key exists in the cache and has not idled out.\n\t * Idle entries are evicted on peek, but a live entry's TTI is not reset.\n\t * @param key The key to test.\n\t * @returns True if the key is present and not idle.\n\t */\n\tpublic has(key: string): boolean {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\t\treturn Is.notEmpty(this.lookupLive(key));\n\t}\n\n\t/**\n\t * Return all keys for entries that have not idled out.\n\t * Idle entries encountered during iteration are evicted.\n\t * @returns An array of live keys in least-recently-used to most-recently-used order.\n\t */\n\tpublic keys(): string[] {\n\t\tconst now = Date.now();\n\t\tconst result: string[] = [];\n\t\tfor (const [k, entry] of this._cache) {\n\t\t\tif (this.isExpired(entry, now)) {\n\t\t\t\tthis._cache.delete(k);\n\t\t\t} else {\n\t\t\t\tresult.push(k);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Remove an entry from the cache.\n\t * Cancels the background timer if the cache becomes empty.\n\t * @param key The key to remove.\n\t */\n\tpublic delete(key: string): void {\n\t\tGuards.stringValue(LruCache.CLASS_NAME, nameof(key), key);\n\t\tthis._cache.delete(key);\n\t\tif (this._cache.size === 0) {\n\t\t\tthis.cancelTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Remove all entries from the cache and cancel the background timer.\n\t */\n\tpublic clear(): void {\n\t\tthis.cancelTimer();\n\t\tthis._cache.clear();\n\t\tthis._nextExpires = undefined;\n\t}\n\n\t/**\n\t * Stop the background idle-sweep timer and release all entries.\n\t * The cache must not be used after this call.\n\t */\n\tpublic destroy(): void {\n\t\tthis.cancelTimer();\n\t\tthis._cache.clear();\n\t\tthis._nextExpires = undefined;\n\t}\n\n\t/**\n\t * Resolve an entry which is present and has not expired, evicting it if it has.\n\t * The expiry check is evaluated once so a caller cannot observe an entry as live and\n\t * then as expired across two separate lookups.\n\t * @param key The key to resolve.\n\t * @returns The live entry, or undefined if the key is absent or has been evicted.\n\t * @internal\n\t */\n\tprivate lookupLive(\n\t\tkey: string\n\t): { value: T; lastAccessed: number; expires: number | undefined } | undefined {\n\t\tconst entry = this._cache.get(key);\n\t\tif (Is.empty(entry)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (this.isExpired(entry, Date.now())) {\n\t\t\tthis._cache.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn entry;\n\t}\n\n\t/**\n\t * Move an entry to the most-recently-used position and reset its idle timer.\n\t * @param key The key to touch.\n\t * @param entry The entry object to update in place.\n\t * @param entry.value The cached value.\n\t * @param entry.lastAccessed The last-accessed timestamp in milliseconds.\n\t * @param entry.expires The hard expiry timestamp in milliseconds, or undefined for none.\n\t * @internal\n\t */\n\tprivate touch(\n\t\tkey: string,\n\t\tentry: { value: T; lastAccessed: number; expires: number | undefined }\n\t): void {\n\t\t// Move to end of Map (most-recently-used) via delete + re-insert\n\t\tthis._cache.delete(key);\n\t\tentry.lastAccessed = Date.now();\n\t\tthis._cache.set(key, entry);\n\t}\n\n\t/**\n\t * Delete all entries whose idle time has been exceeded, then restart the timer\n\t * if any entries remain.\n\t * @internal\n\t */\n\tprivate sweepIdle(): void {\n\t\tthis.cancelTimer();\n\t\tconst now = Date.now();\n\t\tlet nextExpires: number | undefined;\n\t\tfor (const [k, entry] of this._cache) {\n\t\t\tif (this.isExpired(entry, now)) {\n\t\t\t\tthis._cache.delete(k);\n\t\t\t} else if (\n\t\t\t\tIs.notEmpty(entry.expires) &&\n\t\t\t\t(Is.empty(nextExpires) || entry.expires < nextExpires)\n\t\t\t) {\n\t\t\t\tnextExpires = entry.expires;\n\t\t\t}\n\t\t}\n\t\tthis._nextExpires = nextExpires;\n\t\tif (this._cache.size > 0) {\n\t\t\tthis.startTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Record an entry expiry timestamp if it is earlier than the currently tracked one.\n\t * @param expires The expiry timestamp in milliseconds, or undefined for none.\n\t * @internal\n\t */\n\tprivate trackExpires(expires: number | undefined): void {\n\t\tif (Is.notEmpty(expires) && (Is.empty(this._nextExpires) || expires < this._nextExpires)) {\n\t\t\tthis._nextExpires = expires;\n\t\t}\n\t}\n\n\t/**\n\t * Determine whether an entry has idled out or reached its hard expiry timestamp.\n\t * @param entry The entry to test.\n\t * @param entry.lastAccessed The last-accessed timestamp in milliseconds.\n\t * @param entry.expires The hard expiry timestamp in milliseconds, or undefined for none.\n\t * @param now The current time in milliseconds.\n\t * @returns True if the entry should be removed.\n\t * @internal\n\t */\n\tprivate isExpired(\n\t\tentry: { lastAccessed: number; expires: number | undefined },\n\t\tnow: number\n\t): boolean {\n\t\treturn (\n\t\t\tnow - entry.lastAccessed >= this._ttiMs ||\n\t\t\t(Is.notEmpty(entry.expires) && now >= entry.expires)\n\t\t);\n\t}\n\n\t/**\n\t * Schedule the next sweep if no timer is already pending, bringing a pending one forward\n\t * when an entry with an earlier hard expiry has since been added.\n\t * @internal\n\t */\n\tprivate startTimer(): void {\n\t\tconst now = Date.now();\n\t\tlet delay = this._ttiMs;\n\t\tif (Is.notEmpty(this._nextExpires)) {\n\t\t\tdelay = Math.min(delay, Math.max(0, this._nextExpires - now));\n\t\t}\n\t\tif (Is.empty(this._sweepTimer)) {\n\t\t\tthis._scheduledDueAt = now + delay;\n\t\t\tthis._sweepTimer = setTimeout(() => this.sweepIdle(), delay);\n\t\t} else if (now + delay < this._scheduledDueAt) {\n\t\t\tthis.cancelTimer();\n\t\t\tthis.startTimer();\n\t\t}\n\t}\n\n\t/**\n\t * Cancel the pending idle-sweep timer.\n\t * @internal\n\t */\n\tprivate cancelTimer(): void {\n\t\tif (Is.notEmpty(this._sweepTimer)) {\n\t\t\tclearTimeout(this._sweepTimer);\n\t\t\tthis._sweepTimer = undefined;\n\t\t}\n\t}\n}\n"]}
package/docs/changelog.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.1-next.2](https://github.com/iotaledger/twin-framework/compare/core-v0.10.1-next.1...core-v0.10.1-next.2) (2026-09-17)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * atomic has get ([#513](https://github.com/iotaledger/twin-framework/issues/513)) ([c8e080b](https://github.com/iotaledger/twin-framework/commit/c8e080b4b1731313ed2cecfff6915c824888996e))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/nameof bumped from 0.10.1-next.1 to 0.10.1-next.2
16
+ * devDependencies
17
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.1 to 0.10.1-next.2
18
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.1 to 0.10.1-next.2
19
+
3
20
  ## [0.10.1-next.1](https://github.com/iotaledger/twin-framework/compare/core-v0.10.1-next.0...core-v0.10.1-next.1) (2026-09-17)
4
21
 
5
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/core",
3
- "version": "0.10.1-next.1",
3
+ "version": "0.10.1-next.2",
4
4
  "description": "Helper methods/classes for data type checking/validation/guarding/error handling",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,7 @@
14
14
  "node": ">=24.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/nameof": "0.10.1-next.1",
17
+ "@twin.org/nameof": "0.10.1-next.2",
18
18
  "intl-messageformat": "11.2.13",
19
19
  "rfc6902": "5.3.0"
20
20
  },