@catbee/utils 2.0.4 → 2.0.5

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/cache/index.cjs CHANGED
@@ -33,6 +33,7 @@ var TTLCache = class {
33
33
  __name(this, "TTLCache");
34
34
  }
35
35
  cache = /* @__PURE__ */ new Map();
36
+ pending = /* @__PURE__ */ new Map();
36
37
  ttlMs;
37
38
  maxSize;
38
39
  cleanupInterval;
@@ -90,7 +91,7 @@ var TTLCache = class {
90
91
  const entry = this.cache.get(key);
91
92
  if (!entry) return void 0;
92
93
  const now = Date.now();
93
- if (now > entry.expiresAt) {
94
+ if (now >= entry.expiresAt) {
94
95
  this.cache.delete(key);
95
96
  return void 0;
96
97
  }
@@ -102,17 +103,31 @@ var TTLCache = class {
102
103
  /**
103
104
  * Retrieves or computes a value if it's not in the cache or has expired.
104
105
  *
106
+ * Concurrent calls for the same key share the same producer promise,
107
+ * preventing duplicate computations while the value is being generated.
108
+ *
105
109
  * @param key - The key to retrieve
106
110
  * @param producer - Function to generate the value if not cached
107
111
  * @param ttlMs - Optional custom TTL for the computed value
108
112
  * @returns The cached or computed value
109
113
  */
110
114
  async getOrCompute(key, producer, ttlMs) {
111
- const value = this.get(key);
112
- if (value !== void 0) return value;
113
- const newValue = await producer();
114
- this.setWithTTL(key, newValue, ttlMs ?? this.ttlMs);
115
- return newValue;
115
+ const cached = this.get(key);
116
+ if (cached !== void 0) {
117
+ return cached;
118
+ }
119
+ const pending = this.pending.get(key);
120
+ if (pending) {
121
+ return pending;
122
+ }
123
+ const promise = Promise.resolve().then(producer).then((value) => {
124
+ this.setWithTTL(key, value, ttlMs ?? this.ttlMs);
125
+ return value;
126
+ }).finally(() => {
127
+ this.pending.delete(key);
128
+ });
129
+ this.pending.set(key, promise);
130
+ return promise;
116
131
  }
117
132
  /**
118
133
  * Checks if the key exists and hasn't expired.
@@ -174,7 +189,7 @@ var TTLCache = class {
174
189
  let removed = 0;
175
190
  const now = Date.now();
176
191
  for (const [key, entry] of this.cache.entries()) {
177
- if (now > entry.expiresAt) {
192
+ if (now >= entry.expiresAt) {
178
193
  this.cache.delete(key);
179
194
  removed++;
180
195
  }
@@ -188,7 +203,7 @@ var TTLCache = class {
188
203
  */
189
204
  *entries() {
190
205
  for (const [key, entry] of this.cache.entries()) {
191
- if (Date.now() <= entry.expiresAt) {
206
+ if (Date.now() < entry.expiresAt) {
192
207
  yield [
193
208
  key,
194
209
  entry.value
@@ -203,7 +218,7 @@ var TTLCache = class {
203
218
  */
204
219
  *keys() {
205
220
  for (const [key, entry] of this.cache.entries()) {
206
- if (Date.now() <= entry.expiresAt) {
221
+ if (Date.now() < entry.expiresAt) {
207
222
  yield key;
208
223
  }
209
224
  }
@@ -215,7 +230,7 @@ var TTLCache = class {
215
230
  */
216
231
  *values() {
217
232
  for (const entry of this.cache.values()) {
218
- if (Date.now() <= entry.expiresAt) {
233
+ if (Date.now() < entry.expiresAt) {
219
234
  yield entry.value;
220
235
  }
221
236
  }
@@ -231,12 +246,14 @@ var TTLCache = class {
231
246
  const entry = this.cache.get(key);
232
247
  if (!entry) return false;
233
248
  const now = Date.now();
234
- if (now > entry.expiresAt) {
249
+ if (now >= entry.expiresAt) {
235
250
  this.cache.delete(key);
236
251
  return false;
237
252
  }
238
253
  entry.expiresAt = now + (ttlMs ?? this.ttlMs);
239
254
  entry.lastAccessed = now;
255
+ this.cache.delete(key);
256
+ this.cache.set(key, entry);
240
257
  return true;
241
258
  }
242
259
  /**
@@ -249,7 +266,7 @@ var TTLCache = class {
249
266
  let expired = 0;
250
267
  let valid = 0;
251
268
  for (const entry of this.cache.values()) {
252
- if (now > entry.expiresAt) {
269
+ if (now >= entry.expiresAt) {
253
270
  expired++;
254
271
  } else {
255
272
  valid++;
@@ -277,14 +294,17 @@ var TTLCache = class {
277
294
  */
278
295
  evictLRU() {
279
296
  const now = Date.now();
280
- for (const [key, entry] of Array.from(this.cache.entries())) {
281
- if (now > entry.expiresAt) {
297
+ for (const [key, entry] of this.cache.entries()) {
298
+ if (now >= entry.expiresAt) {
282
299
  this.cache.delete(key);
283
300
  }
284
301
  }
285
302
  while (this.maxSize && this.cache.size > this.maxSize) {
286
- const oldestKey = this.cache.keys().next().value;
287
- this.cache.delete(oldestKey);
303
+ const next = this.cache.keys().next();
304
+ if (next.done) {
305
+ break;
306
+ }
307
+ this.cache.delete(next.value);
288
308
  }
289
309
  }
290
310
  };
package/cache/index.d.ts CHANGED
@@ -41,11 +41,12 @@ interface TTLCacheOptions {
41
41
  *
42
42
  * @example
43
43
  * const cache = new TTLCache<string, number>({ ttlMs: 1000 });
44
- * cache.set("x", 123);
45
- * const value = cache.get("x"); // 123
44
+ * cache.set('x', 123);
45
+ * const value = cache.get('x'); // 123
46
46
  */
47
47
  declare class TTLCache<K, V> {
48
48
  private readonly cache;
49
+ private readonly pending;
49
50
  private readonly ttlMs;
50
51
  private readonly maxSize?;
51
52
  private cleanupInterval?;
@@ -78,6 +79,9 @@ declare class TTLCache<K, V> {
78
79
  /**
79
80
  * Retrieves or computes a value if it's not in the cache or has expired.
80
81
  *
82
+ * Concurrent calls for the same key share the same producer promise,
83
+ * preventing duplicate computations while the value is being generated.
84
+ *
81
85
  * @param key - The key to retrieve
82
86
  * @param producer - Function to generate the value if not cached
83
87
  * @param ttlMs - Optional custom TTL for the computed value
package/cache/index.mjs CHANGED
@@ -31,6 +31,7 @@ var TTLCache = class {
31
31
  __name(this, "TTLCache");
32
32
  }
33
33
  cache = /* @__PURE__ */ new Map();
34
+ pending = /* @__PURE__ */ new Map();
34
35
  ttlMs;
35
36
  maxSize;
36
37
  cleanupInterval;
@@ -88,7 +89,7 @@ var TTLCache = class {
88
89
  const entry = this.cache.get(key);
89
90
  if (!entry) return void 0;
90
91
  const now = Date.now();
91
- if (now > entry.expiresAt) {
92
+ if (now >= entry.expiresAt) {
92
93
  this.cache.delete(key);
93
94
  return void 0;
94
95
  }
@@ -100,17 +101,31 @@ var TTLCache = class {
100
101
  /**
101
102
  * Retrieves or computes a value if it's not in the cache or has expired.
102
103
  *
104
+ * Concurrent calls for the same key share the same producer promise,
105
+ * preventing duplicate computations while the value is being generated.
106
+ *
103
107
  * @param key - The key to retrieve
104
108
  * @param producer - Function to generate the value if not cached
105
109
  * @param ttlMs - Optional custom TTL for the computed value
106
110
  * @returns The cached or computed value
107
111
  */
108
112
  async getOrCompute(key, producer, ttlMs) {
109
- const value = this.get(key);
110
- if (value !== void 0) return value;
111
- const newValue = await producer();
112
- this.setWithTTL(key, newValue, ttlMs ?? this.ttlMs);
113
- return newValue;
113
+ const cached = this.get(key);
114
+ if (cached !== void 0) {
115
+ return cached;
116
+ }
117
+ const pending = this.pending.get(key);
118
+ if (pending) {
119
+ return pending;
120
+ }
121
+ const promise = Promise.resolve().then(producer).then((value) => {
122
+ this.setWithTTL(key, value, ttlMs ?? this.ttlMs);
123
+ return value;
124
+ }).finally(() => {
125
+ this.pending.delete(key);
126
+ });
127
+ this.pending.set(key, promise);
128
+ return promise;
114
129
  }
115
130
  /**
116
131
  * Checks if the key exists and hasn't expired.
@@ -172,7 +187,7 @@ var TTLCache = class {
172
187
  let removed = 0;
173
188
  const now = Date.now();
174
189
  for (const [key, entry] of this.cache.entries()) {
175
- if (now > entry.expiresAt) {
190
+ if (now >= entry.expiresAt) {
176
191
  this.cache.delete(key);
177
192
  removed++;
178
193
  }
@@ -186,7 +201,7 @@ var TTLCache = class {
186
201
  */
187
202
  *entries() {
188
203
  for (const [key, entry] of this.cache.entries()) {
189
- if (Date.now() <= entry.expiresAt) {
204
+ if (Date.now() < entry.expiresAt) {
190
205
  yield [
191
206
  key,
192
207
  entry.value
@@ -201,7 +216,7 @@ var TTLCache = class {
201
216
  */
202
217
  *keys() {
203
218
  for (const [key, entry] of this.cache.entries()) {
204
- if (Date.now() <= entry.expiresAt) {
219
+ if (Date.now() < entry.expiresAt) {
205
220
  yield key;
206
221
  }
207
222
  }
@@ -213,7 +228,7 @@ var TTLCache = class {
213
228
  */
214
229
  *values() {
215
230
  for (const entry of this.cache.values()) {
216
- if (Date.now() <= entry.expiresAt) {
231
+ if (Date.now() < entry.expiresAt) {
217
232
  yield entry.value;
218
233
  }
219
234
  }
@@ -229,12 +244,14 @@ var TTLCache = class {
229
244
  const entry = this.cache.get(key);
230
245
  if (!entry) return false;
231
246
  const now = Date.now();
232
- if (now > entry.expiresAt) {
247
+ if (now >= entry.expiresAt) {
233
248
  this.cache.delete(key);
234
249
  return false;
235
250
  }
236
251
  entry.expiresAt = now + (ttlMs ?? this.ttlMs);
237
252
  entry.lastAccessed = now;
253
+ this.cache.delete(key);
254
+ this.cache.set(key, entry);
238
255
  return true;
239
256
  }
240
257
  /**
@@ -247,7 +264,7 @@ var TTLCache = class {
247
264
  let expired = 0;
248
265
  let valid = 0;
249
266
  for (const entry of this.cache.values()) {
250
- if (now > entry.expiresAt) {
267
+ if (now >= entry.expiresAt) {
251
268
  expired++;
252
269
  } else {
253
270
  valid++;
@@ -275,14 +292,17 @@ var TTLCache = class {
275
292
  */
276
293
  evictLRU() {
277
294
  const now = Date.now();
278
- for (const [key, entry] of Array.from(this.cache.entries())) {
279
- if (now > entry.expiresAt) {
295
+ for (const [key, entry] of this.cache.entries()) {
296
+ if (now >= entry.expiresAt) {
280
297
  this.cache.delete(key);
281
298
  }
282
299
  }
283
300
  while (this.maxSize && this.cache.size > this.maxSize) {
284
- const oldestKey = this.cache.keys().next().value;
285
- this.cache.delete(oldestKey);
301
+ const next = this.cache.keys().next();
302
+ if (next.done) {
303
+ break;
304
+ }
305
+ this.cache.delete(next.value);
286
306
  }
287
307
  }
288
308
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catbee/utils",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.",
5
5
  "publishConfig": {
6
6
  "access": "public"