@sebspark/promise-cache 3.3.3 → 3.4.0

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/dist/index.js CHANGED
@@ -20,8 +20,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ InMemoryPersistor: () => InMemoryPersistor,
23
24
  Persistor: () => Persistor,
24
- PromiseCache: () => PromiseCache
25
+ PromiseCache: () => PromiseCache,
26
+ createCache: () => createCache,
27
+ time: () => time_exports
25
28
  });
26
29
  module.exports = __toCommonJS(index_exports);
27
30
 
@@ -40,7 +43,7 @@ var LocalStorage = class {
40
43
  }
41
44
  set(key, value, options) {
42
45
  this.client.set(key, value);
43
- if (options == null ? void 0 : options.PX) {
46
+ if (options?.PX) {
44
47
  setTimeout(() => {
45
48
  this.client.delete(key);
46
49
  }, options.PX);
@@ -77,8 +80,8 @@ var fixESM = require("fix-esm");
77
80
  var superjson = fixESM.require("superjson");
78
81
  var CACHE_CLIENT = import_redis.createClient;
79
82
  var isTestRunning = process.env.NODE_ENV === "test";
80
- function toMillis(seconds) {
81
- return seconds * 1e3;
83
+ function toMillis(seconds2) {
84
+ return seconds2 * 1e3;
82
85
  }
83
86
  var Persistor = class {
84
87
  client = null;
@@ -110,17 +113,15 @@ var Persistor = class {
110
113
  }
111
114
  }
112
115
  async startConnection() {
113
- var _a;
114
116
  try {
115
117
  await new Promise((resolve, reject) => {
116
- var _a2, _b, _c, _d, _e;
117
118
  this.client = CACHE_CLIENT({
118
- url: (_a2 = this.redis) == null ? void 0 : _a2.url,
119
- username: (_b = this.redis) == null ? void 0 : _b.username,
120
- password: (_c = this.redis) == null ? void 0 : _c.password,
121
- pingInterval: ((_d = this.redis) == null ? void 0 : _d.pingInterval) || void 0,
119
+ url: this.redis?.url,
120
+ username: this.redis?.username,
121
+ password: this.redis?.password,
122
+ pingInterval: this.redis?.pingInterval || void 0,
122
123
  socket: {
123
- ...(_e = this.redis) == null ? void 0 : _e.socket,
124
+ ...this.redis?.socket,
124
125
  reconnectStrategy: (retries, cause) => {
125
126
  console.error(cause);
126
127
  return 1e3 * 2 ** retries;
@@ -133,17 +134,15 @@ var Persistor = class {
133
134
  this.onSuccess();
134
135
  resolve(true);
135
136
  }).on("reconnecting", () => {
136
- var _a3;
137
- (_a3 = this.logger) == null ? void 0 : _a3.info("reconnecting...", this.clientId);
137
+ this.logger?.info("reconnecting...", this.clientId);
138
138
  }).on("end", () => {
139
- var _a3;
140
- (_a3 = this.logger) == null ? void 0 : _a3.info("end...", this.clientId);
139
+ this.logger?.info("end...", this.clientId);
141
140
  });
142
141
  this.client.connect();
143
142
  });
144
143
  } catch (ex) {
145
144
  this.onError(`${ex}`);
146
- (_a = this.logger) == null ? void 0 : _a.error(ex);
145
+ this.logger?.error(ex);
147
146
  }
148
147
  }
149
148
  async size() {
@@ -156,8 +155,7 @@ var Persistor = class {
156
155
  return this.clientId;
157
156
  }
158
157
  getIsClientConnected() {
159
- var _a;
160
- return !!((_a = this.client) == null ? void 0 : _a.isReady);
158
+ return !!this.client?.isReady;
161
159
  }
162
160
  createOptions(ttl) {
163
161
  if (ttl !== null && ttl !== void 0) {
@@ -173,9 +171,8 @@ var Persistor = class {
173
171
  * @param object.timestamp Timestamp
174
172
  */
175
173
  async set(key, { value, timestamp = Date.now(), ttl }) {
176
- var _a, _b;
177
174
  if (!this.client || !this.client.isReady) {
178
- (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
175
+ this.logger?.error("Client not ready");
179
176
  return;
180
177
  }
181
178
  try {
@@ -187,7 +184,7 @@ var Persistor = class {
187
184
  const options = this.createOptions(ttl);
188
185
  await this.client.set(key, serializedData, options);
189
186
  } catch (error) {
190
- (_b = this.logger) == null ? void 0 : _b.error(`Error setting data in redis: ${error}`);
187
+ this.logger?.error(`Error setting data in redis: ${error}`);
191
188
  throw new Error(`Error setting data in redis: ${error}`);
192
189
  }
193
190
  }
@@ -197,9 +194,8 @@ var Persistor = class {
197
194
  * @returns GetType<T> value
198
195
  */
199
196
  async get(key) {
200
- var _a, _b;
201
197
  if (!this.client) {
202
- (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
198
+ this.logger?.error("Client not ready");
203
199
  return null;
204
200
  }
205
201
  try {
@@ -209,7 +205,7 @@ var Persistor = class {
209
205
  }
210
206
  return superjson.parse(data);
211
207
  } catch (error) {
212
- (_b = this.logger) == null ? void 0 : _b.error(`Error getting data in redis: ${error}`);
208
+ this.logger?.error(`Error getting data in redis: ${error}`);
213
209
  throw new Error(`Error getting data from redis: ${error}`);
214
210
  }
215
211
  }
@@ -218,15 +214,14 @@ var Persistor = class {
218
214
  * @param key Cache key
219
215
  */
220
216
  async delete(key) {
221
- var _a, _b;
222
217
  if (!this.client || !this.client.isReady) {
223
- (_a = this.logger) == null ? void 0 : _a.error("Client not ready");
218
+ this.logger?.error("Client not ready");
224
219
  return;
225
220
  }
226
221
  try {
227
222
  await this.client.del(key);
228
223
  } catch (error) {
229
- (_b = this.logger) == null ? void 0 : _b.error(`Error deleting data from redis: ${error}`);
224
+ this.logger?.error(`Error deleting data from redis: ${error}`);
230
225
  throw new Error(`Error deleting data from redis: ${error}`);
231
226
  }
232
227
  }
@@ -241,20 +236,20 @@ var getPersistor = ({
241
236
  onSuccess,
242
237
  clientId
243
238
  }) => {
244
- const connectionName = redis ? (redis == null ? void 0 : redis.name) || "default" : "local";
239
+ const connectionName = redis ? redis?.name || "default" : "local";
245
240
  if (!persistors[connectionName]) {
246
241
  persistors[connectionName] = new Persistor({
247
242
  redis,
248
243
  onError: (error) => {
249
- onError == null ? void 0 : onError(error);
250
- logger == null ? void 0 : logger.error(
251
- `\u274C REDIS | Client Error | ${connectionName} | ${redis == null ? void 0 : redis.url}: ${error}`
244
+ onError?.(error);
245
+ logger?.error(
246
+ `\u274C REDIS | Client Error | ${connectionName} | ${redis?.url}: ${error}`
252
247
  );
253
248
  },
254
249
  onSuccess: () => {
255
- onSuccess == null ? void 0 : onSuccess();
256
- logger == null ? void 0 : logger.info(
257
- `\u{1F4E6} REDIS | Connection Ready | ${connectionName} | ${redis == null ? void 0 : redis.url}`
250
+ onSuccess?.();
251
+ logger?.info(
252
+ `\u{1F4E6} REDIS | Connection Ready | ${connectionName} | ${redis?.url}`
258
253
  );
259
254
  },
260
255
  clientId,
@@ -322,7 +317,7 @@ var PromiseCache = class {
322
317
  */
323
318
  async find(key) {
324
319
  const result = await this.persistor.get(key);
325
- return (result == null ? void 0 : result.value) ?? null;
320
+ return result?.value ?? null;
326
321
  }
327
322
  /**
328
323
  * A simple promise cache wrapper.
@@ -359,8 +354,260 @@ var PromiseCache = class {
359
354
  return response;
360
355
  }
361
356
  };
357
+
358
+ // src/serializer.ts
359
+ var fixESM2 = require("fix-esm");
360
+ var superjson2 = fixESM2.require("superjson");
361
+ var serialize = (data) => {
362
+ return superjson2.stringify(data);
363
+ };
364
+ var deserialize = (serialized) => {
365
+ if (serialized === void 0 || serialized === null) return serialized;
366
+ return superjson2.parse(serialized);
367
+ };
368
+
369
+ // src/setOptions.ts
370
+ var toSetOptions = (expiry) => {
371
+ const options = {};
372
+ if (expiry !== void 0) {
373
+ if (typeof expiry === "number") {
374
+ options.PX = expiry;
375
+ } else if (expiry instanceof Date && !Number.isNaN(expiry.getTime())) {
376
+ const timestamp = expiry.getTime();
377
+ if (timestamp > Date.now()) {
378
+ options.PXAT = timestamp;
379
+ } else {
380
+ options.PX = 1;
381
+ }
382
+ }
383
+ }
384
+ if (!options.PX && !options.PXAT) {
385
+ options.EX = 1;
386
+ }
387
+ return options;
388
+ };
389
+
390
+ // src/cache.ts
391
+ var createCache = (persistor, prefix) => {
392
+ const pendingPromises = /* @__PURE__ */ new Map();
393
+ const cache = {
394
+ persistor,
395
+ wrap: (delegate, options) => {
396
+ return async (...args) => {
397
+ let key = typeof options.key === "string" ? options.key : options.key(...args);
398
+ if (prefix) {
399
+ key = `${prefix}:${key}`;
400
+ }
401
+ if (pendingPromises.has(key)) {
402
+ return pendingPromises.get(key);
403
+ }
404
+ const resultPromise = (async () => {
405
+ try {
406
+ const cached = await persistor.get(key);
407
+ if (cached !== null) {
408
+ return deserialize(cached);
409
+ }
410
+ const result = await delegate(...args);
411
+ const expiry = typeof options.expiry === "function" ? options.expiry(args, result) : options.expiry;
412
+ const serialized = serialize(result);
413
+ const setOptions = toSetOptions(expiry);
414
+ await persistor.set(key, serialized, setOptions);
415
+ return result;
416
+ } finally {
417
+ pendingPromises.delete(key);
418
+ }
419
+ })();
420
+ pendingPromises.set(key, resultPromise);
421
+ return resultPromise;
422
+ };
423
+ }
424
+ };
425
+ return cache;
426
+ };
427
+
428
+ // src/inMemoryPersistor.ts
429
+ var InMemoryPersistor = class {
430
+ /**
431
+ * Internal key-value store for caching string values.
432
+ * @private
433
+ */
434
+ store;
435
+ /**
436
+ * Tracks active timeouts for expiring keys.
437
+ * Each key maps to a `setTimeout` reference that deletes the key when triggered.
438
+ * @private
439
+ */
440
+ expirations;
441
+ /**
442
+ * Stores absolute expiration timestamps (in milliseconds since epoch) for each key.
443
+ * Used to compute remaining TTL.
444
+ * @private
445
+ */
446
+ expiryTimestamps;
447
+ /**
448
+ * Creates a new instance of `InMemoryPersistor`.
449
+ * Initializes an empty store, expiration map, and TTL tracker.
450
+ */
451
+ constructor() {
452
+ this.store = /* @__PURE__ */ new Map();
453
+ this.expirations = /* @__PURE__ */ new Map();
454
+ this.expiryTimestamps = /* @__PURE__ */ new Map();
455
+ }
456
+ /**
457
+ * Stores a key-value pair with optional expiration settings.
458
+ * If an expiration is provided (`EX`, `PX`, `EXAT`, `PXAT`), the key is automatically removed when TTL expires.
459
+ *
460
+ * @param {string} key - The key to store.
461
+ * @param {string} value - The string value to associate with the key.
462
+ * @param {SetOptions} [options] - Optional Redis-style expiration settings.
463
+ * @returns {Promise<'OK' | null>} Resolves to `'OK'` on success, or `null` if a conditional set (`NX`) fails.
464
+ */
465
+ async set(key, value, options) {
466
+ this.store.set(key, value);
467
+ if (options?.EX !== void 0) {
468
+ this.setExpiration(key, options.EX * 1e3);
469
+ } else if (options?.PX !== void 0) {
470
+ this.setExpiration(key, options.PX);
471
+ } else if (options?.EXAT !== void 0) {
472
+ const timeToExpire = options.EXAT * 1e3 - Date.now();
473
+ this.setExpiration(key, Math.max(0, timeToExpire));
474
+ } else if (options?.PXAT !== void 0) {
475
+ const timeToExpire = options.PXAT - Date.now();
476
+ this.setExpiration(key, Math.max(0, timeToExpire));
477
+ }
478
+ return "OK";
479
+ }
480
+ /**
481
+ * Retrieves the value associated with a key.
482
+ *
483
+ * @param {string} key - The key to retrieve.
484
+ * @returns {Promise<string | null>} Resolves to the string value, or `null` if the key does not exist.
485
+ */
486
+ async get(key) {
487
+ return this.store.get(key) ?? null;
488
+ }
489
+ /**
490
+ * Deletes a key from the store.
491
+ * If the key exists, it is removed along with any associated expiration.
492
+ *
493
+ * @param {string} key - The key to delete.
494
+ * @returns {Promise<number>} Resolves to `1` if the key was deleted, or `0` if the key did not exist.
495
+ */
496
+ async del(key) {
497
+ const existed = this.store.has(key);
498
+ if (existed) {
499
+ this.store.delete(key);
500
+ this.clearExpiration(key);
501
+ }
502
+ return existed ? 1 : 0;
503
+ }
504
+ /**
505
+ * Sets a time-to-live (TTL) in seconds for a key.
506
+ * If the key exists, it will be deleted after the specified duration.
507
+ *
508
+ * @param {string} key - The key to set an expiration on.
509
+ * @param {number} seconds - The TTL in seconds.
510
+ * @returns {Promise<number>} Resolves to `1` if the TTL was set, or `0` if the key does not exist.
511
+ */
512
+ async expire(key, seconds2) {
513
+ if (!this.store.has(key)) return false;
514
+ this.setExpiration(key, seconds2 * 1e3);
515
+ return true;
516
+ }
517
+ /**
518
+ * Retrieves the remaining time-to-live (TTL) of a key in seconds.
519
+ *
520
+ * @param {string} key - The key to check.
521
+ * @returns {Promise<number>} Resolves to:
522
+ * - Remaining TTL in **seconds** if the key exists and has an expiration.
523
+ * - `-1` if the key exists but has no expiration.
524
+ * - `-2` if the key does not exist.
525
+ */
526
+ async ttl(key) {
527
+ if (!this.store.has(key)) return -2;
528
+ if (!this.expiryTimestamps.has(key)) return -1;
529
+ const timeLeft = this.expiryTimestamps.get(key) - Date.now();
530
+ return timeLeft > 0 ? Math.ceil(timeLeft / 1e3) : -2;
531
+ }
532
+ /**
533
+ * Removes all keys from the store and clears all active expirations.
534
+ *
535
+ * @returns {Promise<'OK'>} Resolves to `'OK'` after all data is cleared.
536
+ */
537
+ async flushAll() {
538
+ this.store.clear();
539
+ for (const timeout of this.expirations.values()) {
540
+ clearTimeout(timeout);
541
+ }
542
+ this.expirations.clear();
543
+ this.expiryTimestamps.clear();
544
+ return "OK";
545
+ }
546
+ /**
547
+ * Sets an expiration timeout for a key.
548
+ * Cancels any existing expiration before setting a new one.
549
+ *
550
+ * @private
551
+ * @param {string} key - The key to expire.
552
+ * @param {number} ttlMs - Time-to-live in milliseconds.
553
+ */
554
+ setExpiration(key, ttlMs) {
555
+ this.clearExpiration(key);
556
+ const expiryTimestamp = Date.now() + ttlMs;
557
+ this.expiryTimestamps.set(key, expiryTimestamp);
558
+ const timeout = setTimeout(() => {
559
+ this.store.delete(key);
560
+ this.expirations.delete(key);
561
+ this.expiryTimestamps.delete(key);
562
+ }, ttlMs);
563
+ this.expirations.set(key, timeout);
564
+ }
565
+ /**
566
+ * Cancels an active expiration timeout for a key and removes its TTL record.
567
+ *
568
+ * @private
569
+ * @param {string} key - The key whose expiration should be cleared.
570
+ */
571
+ clearExpiration(key) {
572
+ if (this.expirations.has(key)) {
573
+ clearTimeout(this.expirations.get(key));
574
+ this.expirations.delete(key);
575
+ this.expiryTimestamps.delete(key);
576
+ }
577
+ }
578
+ };
579
+
580
+ // src/time.ts
581
+ var time_exports = {};
582
+ __export(time_exports, {
583
+ DAY: () => DAY,
584
+ HOUR: () => HOUR,
585
+ MINUTE: () => MINUTE,
586
+ SECOND: () => SECOND,
587
+ WEEK: () => WEEK,
588
+ add: () => import_date_fns.add,
589
+ days: () => days,
590
+ hours: () => hours,
591
+ minutes: () => minutes,
592
+ seconds: () => seconds,
593
+ weeks: () => weeks
594
+ });
595
+ var import_date_fns = require("date-fns");
596
+ var SECOND = 1e3;
597
+ var MINUTE = 60 * SECOND;
598
+ var HOUR = 60 * MINUTE;
599
+ var DAY = 24 * HOUR;
600
+ var WEEK = 7 * DAY;
601
+ var seconds = (num) => num * SECOND;
602
+ var minutes = (num) => num * MINUTE;
603
+ var hours = (num) => num * HOUR;
604
+ var days = (num) => num * DAY;
605
+ var weeks = (num) => num * WEEK;
362
606
  // Annotate the CommonJS export names for ESM import in node:
363
607
  0 && (module.exports = {
608
+ InMemoryPersistor,
364
609
  Persistor,
365
- PromiseCache
610
+ PromiseCache,
611
+ createCache,
612
+ time
366
613
  });