cachimbo 0.0.2 → 0.0.4

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.
package/README.md CHANGED
@@ -18,6 +18,7 @@ Cachimbo is an advanced caching library that allows you to layer different strat
18
18
  - Least Recently Used (LRU) eviction
19
19
  - Time-based (TTL) eviction
20
20
  - FIFO eviction
21
+ - Weak References (garbage collectable cached items)
21
22
  - Supports intermediary cache strategies
22
23
  - Request coalescing (deduplication)
23
24
  - Multi-layer caching (tiered cache)
package/dist/index.cjs CHANGED
@@ -40,6 +40,76 @@ var BaseCache = class {
40
40
  }
41
41
  };
42
42
 
43
+ //#endregion
44
+ //#region src/base/local.ts
45
+ var BaseLocalCache = class extends BaseCache {
46
+ disposeListeners = [];
47
+ /**
48
+ * Reads cached resources by their keys. (synchronous version)
49
+ */
50
+ _getMany(keys) {
51
+ return Object.fromEntries(keys.map((key) => [key, this._get(key)]));
52
+ }
53
+ /**
54
+ * Writes resources into cache. (synchronous version)
55
+ */
56
+ _setMany(data, options) {
57
+ for (const [key, value] of Object.entries(data)) this._set(key, value, options);
58
+ }
59
+ /**
60
+ * Deletes many cached resources by their keys. (synchronous version)
61
+ */
62
+ _deleteMany(keys) {
63
+ for (const key of keys) this._delete(key);
64
+ }
65
+ /**
66
+ * Adds a listener that will be called when a cached item is disposed.
67
+ *
68
+ * @param listener The listener function to add.
69
+ */
70
+ _addDisposeListener(listener) {
71
+ this.disposeListeners.push(listener);
72
+ }
73
+ /**
74
+ * Gets access to the internal synchronous methods.
75
+ * @experimental
76
+ */
77
+ get internal() {
78
+ return this;
79
+ }
80
+ /** @sealed **/
81
+ get(key) {
82
+ return Promise.resolve(this._get(key));
83
+ }
84
+ /** @sealed **/
85
+ set(key, value, options) {
86
+ this._set(key, value, options);
87
+ return Promise.resolve();
88
+ }
89
+ /** @sealed **/
90
+ delete(key) {
91
+ this._delete(key);
92
+ return Promise.resolve();
93
+ }
94
+ /** @sealed **/
95
+ getMany(keys) {
96
+ return Promise.resolve(this._getMany(keys));
97
+ }
98
+ /** @sealed **/
99
+ setMany(data, options) {
100
+ this._setMany(data, options);
101
+ return Promise.resolve();
102
+ }
103
+ /** @sealed **/
104
+ deleteMany(keys) {
105
+ this._deleteMany(keys);
106
+ return Promise.resolve();
107
+ }
108
+ onDispose(key, value, reason) {
109
+ for (const listener of this.disposeListeners) listener(key, value, reason);
110
+ }
111
+ };
112
+
43
113
  //#endregion
44
114
  //#region src/local/lru/index.ts
45
115
  /**
@@ -49,7 +119,7 @@ var BaseCache = class {
49
119
  *
50
120
  * Once the limit of items is reached, the least recently used items will be purged.
51
121
  */
52
- var LocalLRUCache = class extends BaseCache {
122
+ var LocalLRUCache = class extends BaseLocalCache {
53
123
  cache;
54
124
  shouldUseFetch;
55
125
  constructor(options = {}) {
@@ -62,16 +132,29 @@ var LocalLRUCache = class extends BaseCache {
62
132
  ttl: options.ttl ? options.ttl * 1e3 : void 0,
63
133
  max: options.max || 1e4,
64
134
  ttlAutopurge: false,
65
- fetchMethod: (_key, _staleValue, options$1) => options$1.context()
135
+ fetchMethod: (_key, _staleValue, options$1) => options$1.context(),
136
+ disposeAfter: (value, key, reason) => this.onDispose(key, value, reason)
66
137
  });
67
138
  this.shouldUseFetch = true;
68
139
  }
69
140
  }
70
- async get(key) {
141
+ /** @internal */
142
+ _get(key) {
71
143
  this.logger?.debug(this.name, "[get]", "key =", key);
72
144
  const data = this.cache.get(key);
73
145
  return data === void 0 ? null : data;
74
146
  }
147
+ /** @internal */
148
+ _set(key, value, options) {
149
+ this.logger?.debug(this.name, "[set]", "key =", key);
150
+ const ttl = options?.ttl;
151
+ this.cache.set(key, value, { ttl: ttl ? ttl * 1e3 : void 0 });
152
+ }
153
+ /** @internal */
154
+ _delete(key) {
155
+ this.logger?.debug(this.name, "[delete]", "key =", key);
156
+ this.cache.delete(key);
157
+ }
75
158
  getOrLoad(key, load, options) {
76
159
  if (!this.shouldUseFetch) return super.getOrLoad(key, load, options);
77
160
  this.logger?.debug(this.name, "[getOrLoad] Running LRUCache's fetch...", "key =", key);
@@ -81,15 +164,6 @@ var LocalLRUCache = class extends BaseCache {
81
164
  ttl: ttl ? ttl * 1e3 : void 0
82
165
  });
83
166
  }
84
- async set(key, value, options) {
85
- this.logger?.debug(this.name, "[set]", "key =", key);
86
- const ttl = options?.ttl;
87
- this.cache.set(key, value, { ttl: ttl ? ttl * 1e3 : void 0 });
88
- }
89
- async delete(key) {
90
- this.logger?.debug(this.name, "[delete]", "key =", key);
91
- this.cache.delete(key);
92
- }
93
167
  };
94
168
 
95
169
  //#endregion
@@ -99,27 +173,31 @@ var LocalLRUCache = class extends BaseCache {
99
173
  *
100
174
  * Once the limit of items is reached, the soonest expiring items will be purged.
101
175
  */
102
- var LocalTTLCache = class extends BaseCache {
176
+ var LocalTTLCache = class extends BaseLocalCache {
103
177
  cache;
104
178
  constructor(options = {}) {
105
179
  super(options);
106
180
  if ("cache" in options) this.cache = options.cache;
107
181
  else this.cache = new __isaacs_ttlcache.TTLCache({
108
182
  max: options.max,
109
- ttl: options.ttl ? options.ttl * 1e3 : void 0
183
+ ttl: options.ttl ? options.ttl * 1e3 : void 0,
184
+ dispose: (value, key, reason) => this.onDispose(key, value, reason)
110
185
  });
111
186
  }
112
- async get(key) {
187
+ /** @internal */
188
+ _get(key) {
113
189
  this.logger?.debug(this.name, "[get]", "key =", key);
114
190
  const data = this.cache.get(key);
115
191
  return data === void 0 ? null : data;
116
192
  }
117
- async set(key, value, options) {
193
+ /** @internal */
194
+ _set(key, value, options) {
118
195
  this.logger?.debug(this.name, "[set]", "key =", key);
119
196
  const ttl = options?.ttl;
120
197
  this.cache.set(key, value, { ttl: ttl ? ttl * 1e3 : void 0 });
121
198
  }
122
- async delete(key) {
199
+ /** @internal */
200
+ _delete(key) {
123
201
  this.logger?.debug(this.name, "[delete]", "key =", key);
124
202
  this.cache.delete(key);
125
203
  }
@@ -135,7 +213,7 @@ var LocalTTLCache = class extends BaseCache {
135
213
  * It implements a simple FIFO eviction policy:
136
214
  * Once the limit of items is reached, the first inserted keys will be purged.
137
215
  */
138
- var LocalMapCache = class extends BaseCache {
216
+ var LocalMapCache = class extends BaseLocalCache {
139
217
  cache;
140
218
  max;
141
219
  constructor(options = {}) {
@@ -143,47 +221,177 @@ var LocalMapCache = class extends BaseCache {
143
221
  this.cache = options.cache ?? /* @__PURE__ */ new Map();
144
222
  this.max = options.max ?? Infinity;
145
223
  }
146
- async get(key) {
224
+ /** @internal */
225
+ _get(key) {
147
226
  this.logger?.debug(this.name, "[get]", "key =", key);
148
227
  const data = this.cache.get(key);
149
228
  return data === void 0 ? null : data;
150
229
  }
151
- async set(key, value, options) {
230
+ /** @internal */
231
+ _set(key, value, options) {
152
232
  this.logger?.debug(this.name, "[set]", "key =", key);
153
- if (this.cache.size >= this.max && !this.cache.has(key)) this.evict(1);
233
+ const previousValue = this.cache.get(key);
234
+ if (this.cache.size >= this.max && previousValue === void 0) this.evict(1);
154
235
  this.cache.set(key, value);
236
+ this.onDispose(key, previousValue, "set");
155
237
  }
156
- async delete(key) {
238
+ /** @internal */
239
+ _delete(key) {
157
240
  this.logger?.debug(this.name, "[delete]", "key =", key);
241
+ const previousValue = this.cache.get(key);
158
242
  this.cache.delete(key);
243
+ this.onDispose(key, previousValue, "delete");
159
244
  }
160
245
  async setMany(data, options) {
161
246
  this.logger?.debug(this.name, "[setMany]", "data =", data);
162
247
  const entries = Object.entries(data);
163
248
  const newEntries = entries.filter(([key]) => !this.cache.has(key)).length;
164
249
  if (this.cache.size + newEntries > this.max) this.evict(this.cache.size + newEntries - this.max);
165
- for (const [key, value] of entries) this.cache.set(key, value);
250
+ for (const [key, value] of entries) {
251
+ const previousValue = this.cache.get(key);
252
+ this.cache.set(key, value);
253
+ this.onDispose(key, previousValue, "set");
254
+ }
166
255
  }
167
256
  clear() {
168
257
  this.logger?.debug(this.name, "[clear]");
258
+ for (const key of this.cache.keys()) this.onDispose(key, this.cache.get(key), "delete");
169
259
  this.cache.clear();
170
260
  }
261
+ onDispose(key, value, reason) {
262
+ if (value !== void 0) super.onDispose(key, value, reason);
263
+ }
171
264
  evict(length) {
172
265
  const keys = this.cache.keys();
173
266
  for (let i = 0; i < length; i++) {
174
267
  const key = keys.next();
175
268
  if (key.done) break;
176
269
  this.logger?.debug(this.name, "[evict]", "key = ", key);
270
+ const previousValue = this.cache.get(key.value);
177
271
  this.cache.delete(key.value);
272
+ this.onDispose(key.value, previousValue, "evict");
178
273
  }
179
274
  }
180
275
  };
181
276
 
277
+ //#endregion
278
+ //#region src/local/weak/index.ts
279
+ /**
280
+ * A cache layer that stores objects as weak references.
281
+ *
282
+ * When an object is garbage collected, its entry is automatically removed from the underlying cache.
283
+ *
284
+ * This implementation requires support for both `WeakRef` and `FinalizationRegistry`.
285
+ *
286
+ * @see https://caniuse.com/mdn-javascript_builtins_finalizationregistry
287
+ * @see https://caniuse.com/mdn-javascript_builtins_weakref
288
+ */
289
+ var WeakCache = class extends BaseLocalCache {
290
+ cache;
291
+ cacheInternal;
292
+ registry;
293
+ constructor(options) {
294
+ super(options);
295
+ this.cache = options.cache;
296
+ this.cacheInternal = options.cache.internal;
297
+ this.cacheInternal._addDisposeListener(this.onCacheDispose);
298
+ this.registry = new FinalizationRegistry(this.onGarbageCollect);
299
+ }
300
+ onGarbageCollect = (key) => this.cacheInternal._delete(key);
301
+ onCacheDispose = (key, value, reason) => {
302
+ this.unregister(value);
303
+ this.onDispose(key, this.unwrap(value), reason);
304
+ };
305
+ /** @internal */
306
+ _get(key) {
307
+ return this.unwrap(this.cacheInternal._get(key));
308
+ }
309
+ /** @internal */
310
+ _set(key, value, options) {
311
+ this.cacheInternal._set(key, this.wrapAndRegister(key, value), options);
312
+ }
313
+ /** @internal */
314
+ _delete(key) {
315
+ this.unregisterByKey(key);
316
+ this.cacheInternal._delete(key);
317
+ }
318
+ /** @internal */
319
+ _getMany(keys) {
320
+ const data = this.cacheInternal._getMany(keys);
321
+ for (const key of keys) data[key] = this.unwrap(data[key]);
322
+ return data;
323
+ }
324
+ /** @internal */
325
+ _setMany(data, options) {
326
+ Object.keys(data).forEach((key) => this.unregisterByKey(key));
327
+ const wrappedData = {};
328
+ for (const [key, value] of Object.entries(data)) wrappedData[key] = this.wrapAndRegister(key, value);
329
+ this.cacheInternal._setMany(wrappedData, options);
330
+ }
331
+ /** @internal */
332
+ _deleteMany(keys) {
333
+ keys.forEach((key) => this.unregisterByKey(key));
334
+ this.cacheInternal._deleteMany(keys);
335
+ }
336
+ async getOrLoad(key, load, options) {
337
+ const wrappedLoad = async () => this.wrapAndRegister(key, await load());
338
+ return this.unwrap(await this.cache.getOrLoad(key, wrappedLoad, options));
339
+ }
340
+ /**
341
+ * Wraps the value in a WeakRef and registers it in the FinalizationRegistry if it's an object.
342
+ *
343
+ * @param key The key to reference the value in the FinalizationRegistry
344
+ * @param value The value to wrap
345
+ * @returns The wrapped value
346
+ */
347
+ wrapAndRegister(key, value) {
348
+ this.unregisterByKey(key);
349
+ if (value !== null && typeof value === "object") {
350
+ this.registry.register(value, key);
351
+ return {
352
+ v: new WeakRef(value),
353
+ w: true
354
+ };
355
+ }
356
+ return {
357
+ v: value,
358
+ w: false
359
+ };
360
+ }
361
+ /**
362
+ * Unwraps the value from a WeakRef if it's an object.
363
+ *
364
+ * @param data The data to unwrap
365
+ * @returns The unwrapped value
366
+ */
367
+ unwrap(data) {
368
+ if (data === null) return null;
369
+ if (data.w) return data.v.deref() ?? null;
370
+ return data.v;
371
+ }
372
+ /**
373
+ * Unregisters the value from the FinalizationRegistry if it's an object.
374
+ *
375
+ * @param value The value to unregister
376
+ */
377
+ unregister(value) {
378
+ if (value && value.w) this.registry.unregister(value.v);
379
+ }
380
+ /**
381
+ * Unregisters the value associated with the given key from the FinalizationRegistry.
382
+ *
383
+ * @param key The key
384
+ */
385
+ unregisterByKey(key) {
386
+ this.unregister(this.cacheInternal._get(key));
387
+ }
388
+ };
389
+
182
390
  //#endregion
183
391
  //#region src/local/noop/index.ts
184
392
  /**
185
393
  * A cache implementation that does nothing.
186
- * It's useful for disabling cache.
394
+ * It's useful for disabling cache and unit testing.
187
395
  *
188
396
  * @example
189
397
  * ```ts
@@ -215,12 +423,14 @@ var NoOpCache = class {
215
423
  var IORedisCache = class extends BaseCache {
216
424
  client;
217
425
  defaultTTL;
426
+ isUNLINKSupported;
218
427
  isMSETEXSupported;
219
428
  constructor(options) {
220
429
  super(options);
221
430
  this.client = options.client;
222
431
  this.defaultTTL = options.defaultTTL;
223
- this.isMSETEXSupported = options.isMSETEXSupported;
432
+ this.isUNLINKSupported = options.isUNLINKSupported ?? true;
433
+ this.isMSETEXSupported = options.isMSETEXSupported ?? false;
224
434
  }
225
435
  async get(key) {
226
436
  this.logger?.debug(this.name, "[get] Running \"GET\" command...", "key =", key);
@@ -235,8 +445,13 @@ var IORedisCache = class extends BaseCache {
235
445
  else await this.client.set(key, raw);
236
446
  }
237
447
  async delete(key) {
238
- this.logger?.debug(this.name, "[delete] Running \"DEL\" command...", "key =", key);
239
- await this.client.del(key);
448
+ if (this.isUNLINKSupported) {
449
+ this.logger?.debug(this.name, "[delete] Running \"UNLINK\" command...", "key =", key);
450
+ await this.client.unlink(key);
451
+ } else {
452
+ this.logger?.debug(this.name, "[delete] Running \"DEL\" command...", "key =", key);
453
+ await this.client.del(key);
454
+ }
240
455
  }
241
456
  async getMany(keys) {
242
457
  this.logger?.debug(this.name, "[getMany] Running \"MGET\" command...", "keys =", keys);
@@ -257,8 +472,13 @@ var IORedisCache = class extends BaseCache {
257
472
  await this.client.call("MSETEX", entries.length, ...raw, ...ttl ? ["EX", ttl] : []);
258
473
  }
259
474
  async deleteMany(keys) {
260
- this.logger?.debug(this.name, "[deleteMany] Running \"DEL\" command...", "keys =", keys);
261
- await this.client.del(keys);
475
+ if (this.isUNLINKSupported) {
476
+ this.logger?.debug(this.name, "[deleteMany] Running \"UNLINK\" command...", "keys =", keys);
477
+ await this.client.unlink(keys);
478
+ } else {
479
+ this.logger?.debug(this.name, "[deleteMany] Running \"DEL\" command...", "keys =", keys);
480
+ await this.client.del(keys);
481
+ }
262
482
  }
263
483
  };
264
484
 
@@ -270,12 +490,14 @@ var IORedisCache = class extends BaseCache {
270
490
  var RedisCache = class extends BaseCache {
271
491
  client;
272
492
  defaultTTL;
493
+ isUNLINKSupported;
273
494
  isMSETEXSupported;
274
495
  constructor(options) {
275
496
  super(options);
276
497
  this.client = options.client;
277
498
  this.defaultTTL = options.defaultTTL;
278
- this.isMSETEXSupported = options.isMSETEXSupported;
499
+ this.isUNLINKSupported = options.isUNLINKSupported ?? true;
500
+ this.isMSETEXSupported = options.isMSETEXSupported ?? false;
279
501
  }
280
502
  async get(key) {
281
503
  this.logger?.debug(this.name, "[get] Running \"GET\" command...", "key =", key);
@@ -291,8 +513,13 @@ var RedisCache = class extends BaseCache {
291
513
  } : void 0 });
292
514
  }
293
515
  async delete(key) {
294
- this.logger?.debug(this.name, "[delete] Running \"DEL\" command...", "key =", key);
295
- await this.client.del(key);
516
+ if (this.isUNLINKSupported) {
517
+ this.logger?.debug(this.name, "[delete] Running \"UNLINK\" command...", "key =", key);
518
+ await this.client.unlink(key);
519
+ } else {
520
+ this.logger?.debug(this.name, "[delete] Running \"DEL\" command...", "key =", key);
521
+ await this.client.del(key);
522
+ }
296
523
  }
297
524
  async getMany(keys) {
298
525
  this.logger?.debug(this.name, "[getMany] Running \"MGET\" command...", "keys =", keys);
@@ -316,8 +543,13 @@ var RedisCache = class extends BaseCache {
316
543
  } : void 0 });
317
544
  }
318
545
  async deleteMany(keys) {
319
- this.logger?.debug(this.name, "[deleteMany] Running \"DEL\" command...", "keys =", keys);
320
- await this.client.del(keys);
546
+ if (this.isUNLINKSupported) {
547
+ this.logger?.debug(this.name, "[deleteMany] Running \"UNLINK\" command...", "keys =", keys);
548
+ await this.client.unlink(keys);
549
+ } else {
550
+ this.logger?.debug(this.name, "[deleteMany] Running \"DEL\" command...", "keys =", keys);
551
+ await this.client.del(keys);
552
+ }
321
553
  }
322
554
  };
323
555
 
@@ -348,8 +580,8 @@ var ValkeyGlideCache = class extends BaseCache {
348
580
  } : void 0 });
349
581
  }
350
582
  async delete(key) {
351
- this.logger?.debug(this.name, "[delete] Running \"DEL\" command...", "key =", key);
352
- await this.client.del([key]);
583
+ this.logger?.debug(this.name, "[delete] Running \"UNLINK\" command...", "key =", key);
584
+ await this.client.unlink([key]);
353
585
  }
354
586
  async getMany(keys) {
355
587
  this.logger?.debug(this.name, "[getMany] Running \"MGET\" command...", "keys =", keys);
@@ -362,8 +594,8 @@ var ValkeyGlideCache = class extends BaseCache {
362
594
  return data;
363
595
  }
364
596
  async deleteMany(keys) {
365
- this.logger?.debug(this.name, "[deleteMany] Running \"DEL\" command...", "keys =", keys);
366
- await this.client.del(keys);
597
+ this.logger?.debug(this.name, "[deleteMany] Running \"UNLINK\" command...", "keys =", keys);
598
+ await this.client.unlink(keys);
367
599
  }
368
600
  };
369
601
 
@@ -458,7 +690,10 @@ var WorkersKVCache = class extends BaseCache {
458
690
  return this.kv.delete(key);
459
691
  }
460
692
  async getMany(keys) {
461
- const data = await this.kv.get(keys, { type: "json" });
693
+ const data = await this.kv.get(keys, {
694
+ type: "json",
695
+ cacheTtl: this.edgeCacheTTL
696
+ });
462
697
  return Object.fromEntries(data);
463
698
  }
464
699
  };
@@ -1107,6 +1342,7 @@ var MetricsCollectingCache = class {
1107
1342
  //#endregion
1108
1343
  exports.AsyncLazyCache = AsyncLazyCache;
1109
1344
  exports.BaseCache = BaseCache;
1345
+ exports.BaseLocalCache = BaseLocalCache;
1110
1346
  exports.CoalescingCache = CoalescingCache;
1111
1347
  exports.IORedisCache = IORedisCache;
1112
1348
  exports.JitteringCache = JitteringCache;
@@ -1123,4 +1359,5 @@ exports.RedisCache = RedisCache;
1123
1359
  exports.SWRCache = SWRCache;
1124
1360
  exports.TieredCache = TieredCache;
1125
1361
  exports.ValkeyGlideCache = ValkeyGlideCache;
1362
+ exports.WeakCache = WeakCache;
1126
1363
  exports.WorkersKVCache = WorkersKVCache;