@warlock.js/cache 5.29.0 → 5.31.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +31 -7
  2. package/README.md +12 -21
  3. package/cjs/index.cjs +134 -71
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/cache-manager.d.mts +4 -2
  6. package/esm/cache-manager.d.mts.map +1 -1
  7. package/esm/cache-manager.mjs +3 -3
  8. package/esm/cache-manager.mjs.map +1 -1
  9. package/esm/cached/auto-key.d.mts +1 -1
  10. package/esm/cached/auto-key.d.mts.map +1 -1
  11. package/esm/cached/auto-key.mjs +1 -1
  12. package/esm/cached/auto-key.mjs.map +1 -1
  13. package/esm/cached/cached.d.mts +1 -1
  14. package/esm/cached/cached.d.mts.map +1 -1
  15. package/esm/cached/cached.mjs +1 -1
  16. package/esm/cached/cached.mjs.map +1 -1
  17. package/esm/cached/normalize-args.d.mts +1 -1
  18. package/esm/cached/normalize-args.d.mts.map +1 -1
  19. package/esm/cached/normalize-args.mjs +1 -1
  20. package/esm/cached/normalize-args.mjs.map +1 -1
  21. package/esm/drivers/base-cache-driver.d.mts +14 -6
  22. package/esm/drivers/base-cache-driver.d.mts.map +1 -1
  23. package/esm/drivers/base-cache-driver.mjs +32 -15
  24. package/esm/drivers/base-cache-driver.mjs.map +1 -1
  25. package/esm/drivers/file-cache-driver.d.mts +1 -1
  26. package/esm/drivers/file-cache-driver.d.mts.map +1 -1
  27. package/esm/drivers/file-cache-driver.mjs +2 -2
  28. package/esm/drivers/file-cache-driver.mjs.map +1 -1
  29. package/esm/drivers/in-memory-tag-index.d.mts +1 -1
  30. package/esm/drivers/in-memory-tag-index.d.mts.map +1 -1
  31. package/esm/drivers/in-memory-tag-index.mjs +1 -1
  32. package/esm/drivers/in-memory-tag-index.mjs.map +1 -1
  33. package/esm/drivers/index.d.mts +1 -1
  34. package/esm/drivers/index.mjs +1 -1
  35. package/esm/drivers/lru-memory-cache-driver.d.mts +4 -2
  36. package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
  37. package/esm/drivers/lru-memory-cache-driver.mjs +9 -6
  38. package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
  39. package/esm/drivers/memory-cache-driver.d.mts +4 -2
  40. package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
  41. package/esm/drivers/memory-cache-driver.mjs +12 -5
  42. package/esm/drivers/memory-cache-driver.mjs.map +1 -1
  43. package/esm/drivers/memory-extended-cache-driver.d.mts +1 -1
  44. package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
  45. package/esm/drivers/memory-extended-cache-driver.mjs +1 -1
  46. package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
  47. package/esm/drivers/mock-cache-driver.d.mts +4 -2
  48. package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
  49. package/esm/drivers/mock-cache-driver.mjs +14 -6
  50. package/esm/drivers/mock-cache-driver.mjs.map +1 -1
  51. package/esm/drivers/null-cache-driver.d.mts +5 -1
  52. package/esm/drivers/null-cache-driver.d.mts.map +1 -1
  53. package/esm/drivers/null-cache-driver.mjs +17 -10
  54. package/esm/drivers/null-cache-driver.mjs.map +1 -1
  55. package/esm/drivers/pg-cache-driver.d.mts +4 -2
  56. package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
  57. package/esm/drivers/pg-cache-driver.mjs +9 -5
  58. package/esm/drivers/pg-cache-driver.mjs.map +1 -1
  59. package/esm/drivers/redis-cache-driver.d.mts +17 -3
  60. package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
  61. package/esm/drivers/redis-cache-driver.mjs +26 -5
  62. package/esm/drivers/redis-cache-driver.mjs.map +1 -1
  63. package/esm/index.d.mts +2 -2
  64. package/esm/index.mjs +2 -2
  65. package/esm/list/memory-cache-list.d.mts +1 -1
  66. package/esm/list/memory-cache-list.d.mts.map +1 -1
  67. package/esm/list/memory-cache-list.mjs +1 -1
  68. package/esm/list/memory-cache-list.mjs.map +1 -1
  69. package/esm/metrics.d.mts +1 -1
  70. package/esm/metrics.d.mts.map +1 -1
  71. package/esm/metrics.mjs +1 -1
  72. package/esm/metrics.mjs.map +1 -1
  73. package/esm/scoped-cache.d.mts +4 -2
  74. package/esm/scoped-cache.d.mts.map +1 -1
  75. package/esm/scoped-cache.mjs +3 -3
  76. package/esm/scoped-cache.mjs.map +1 -1
  77. package/esm/tagged-cache.d.mts +4 -2
  78. package/esm/tagged-cache.d.mts.map +1 -1
  79. package/esm/tagged-cache.mjs +3 -3
  80. package/esm/tagged-cache.mjs.map +1 -1
  81. package/esm/tagged-scoped-cache.d.mts +4 -2
  82. package/esm/tagged-scoped-cache.d.mts.map +1 -1
  83. package/esm/tagged-scoped-cache.mjs +3 -3
  84. package/esm/tagged-scoped-cache.mjs.map +1 -1
  85. package/esm/types.d.mts +17 -6
  86. package/esm/types.d.mts.map +1 -1
  87. package/esm/types.mjs +1 -1
  88. package/esm/types.mjs.map +1 -1
  89. package/esm/utils.d.mts +1 -1
  90. package/esm/utils.d.mts.map +1 -1
  91. package/esm/utils.mjs +1 -1
  92. package/esm/utils.mjs.map +1 -1
  93. package/llms-full.txt +231 -224
  94. package/llms.txt +1 -1
  95. package/package.json +5 -4
  96. package/skills/apply-cache-patterns/SKILL.md +3 -2
  97. package/skills/cache-basics/SKILL.md +26 -24
  98. package/skills/configure-pg-cache/SKILL.md +2 -2
  99. package/skills/configure-set-options/SKILL.md +14 -14
  100. package/skills/handle-cache-errors/SKILL.md +8 -8
  101. package/skills/index.md +4 -1
  102. package/skills/observe-cache/SKILL.md +8 -8
  103. package/skills/pick-cache-driver/SKILL.md +24 -23
  104. package/skills/test-cache-code/SKILL.md +15 -10
  105. package/skills/use-cache-atomic/SKILL.md +17 -11
  106. package/skills/use-cache-bulk/SKILL.md +8 -5
  107. package/skills/use-cache-list/SKILL.md +13 -13
  108. package/skills/use-cache-lock/SKILL.md +11 -17
  109. package/skills/use-cache-namespace/SKILL.md +11 -13
  110. package/skills/use-cache-similarity/SKILL.md +9 -9
  111. package/skills/use-cache-tags/SKILL.md +13 -13
  112. package/skills/use-cache-update-merge/SKILL.md +10 -10
  113. package/skills/use-cache-utils/SKILL.md +17 -17
  114. package/skills/use-cached-hof/SKILL.md +8 -7
  115. package/skills/use-swr/SKILL.md +14 -18
package/cjs/index.cjs CHANGED
@@ -37,7 +37,7 @@ let _warlock_js_fs = require("@warlock.js/fs");
37
37
  let path = require("path");
38
38
  path = __toESM(path, 1);
39
39
 
40
- //#region ../cache/src/metrics.ts
40
+ //#region ../../packages/cache/src/metrics.ts
41
41
  /**
42
42
  * Default size of the circular latency-sample buffer. 1000 samples covers
43
43
  * "tell me the current p95" for every realistic workload while keeping the
@@ -232,7 +232,7 @@ var CacheMetricsCollector = class {
232
232
  };
233
233
 
234
234
  //#endregion
235
- //#region ../cache/src/types.ts
235
+ //#region ../../packages/cache/src/types.ts
236
236
  /**
237
237
  * Base error class for cache-related errors
238
238
  */
@@ -292,7 +292,7 @@ var CacheConcurrencyError = class extends CacheError {
292
292
  };
293
293
 
294
294
  //#endregion
295
- //#region ../cache/src/utils.ts
295
+ //#region ../../packages/cache/src/utils.ts
296
296
  /**
297
297
  * Make a proper key for the cache
298
298
  */
@@ -549,7 +549,7 @@ let CACHE_FOR = /* @__PURE__ */ function(CACHE_FOR) {
549
549
  }({});
550
550
 
551
551
  //#endregion
552
- //#region ../cache/src/tagged-scoped-cache.ts
552
+ //#region ../../packages/cache/src/tagged-scoped-cache.ts
553
553
  /**
554
554
  * One-shot tagged write handle on top of a {@link ScopedCache}.
555
555
  *
@@ -666,8 +666,8 @@ var TaggedScopedCache = class {
666
666
  * Atomic counter increment on the scoped key. Tags aren't applied to
667
667
  * subsequent increments — they're attached at first-write time.
668
668
  */
669
- increment(key, value) {
670
- return this.scope.increment(key, value);
669
+ increment(key, value, options) {
670
+ return this.scope.increment(key, value, options);
671
671
  }
672
672
  /**
673
673
  * Atomic counter decrement on the scoped key. See {@link increment}.
@@ -698,7 +698,7 @@ var TaggedScopedCache = class {
698
698
  };
699
699
 
700
700
  //#endregion
701
- //#region ../cache/src/scoped-cache.ts
701
+ //#region ../../packages/cache/src/scoped-cache.ts
702
702
  /**
703
703
  * Scoped view over a cache source. Returned by `cache.namespace(prefix, options?)`.
704
704
  *
@@ -860,8 +860,8 @@ var ScopedCache = class ScopedCache {
860
860
  * Atomic counter increment on the scoped key. TTL is preserved by the
861
861
  * underlying driver — scope ttl is only applied on first write via `set`.
862
862
  */
863
- increment(key, value) {
864
- return this.source.increment(this.scopedKey(key), value);
863
+ increment(key, value, options) {
864
+ return this.source.increment(this.scopedKey(key), value, { ttl: options?.ttl ?? this.defaults.ttl });
865
865
  }
866
866
  /**
867
867
  * Atomic counter decrement on the scoped key. See {@link increment} for
@@ -979,7 +979,7 @@ var ScopedCache = class ScopedCache {
979
979
  };
980
980
 
981
981
  //#endregion
982
- //#region ../cache/src/cache-manager.ts
982
+ //#region ../../packages/cache/src/cache-manager.ts
983
983
  var CacheManager = class {
984
984
  constructor() {
985
985
  this.loadedDrivers = {};
@@ -1321,9 +1321,9 @@ var CacheManager = class {
1321
1321
  /**
1322
1322
  * {@inheritdoc}
1323
1323
  */
1324
- async increment(key, value) {
1324
+ async increment(key, value, options) {
1325
1325
  this.ensureDriverInitialized();
1326
- return this.currentDriver.increment(key, value);
1326
+ return this.currentDriver.increment(key, value, options);
1327
1327
  }
1328
1328
  /**
1329
1329
  * {@inheritdoc}
@@ -1477,7 +1477,7 @@ var CacheManager = class {
1477
1477
  const cache = new CacheManager();
1478
1478
 
1479
1479
  //#endregion
1480
- //#region ../cache/src/cached/auto-key.ts
1480
+ //#region ../../packages/cache/src/cached/auto-key.ts
1481
1481
  /**
1482
1482
  * Derive a cache key from a prefix and a set of function arguments.
1483
1483
  *
@@ -1543,7 +1543,7 @@ function normalize(value, prefix, seen) {
1543
1543
  }
1544
1544
 
1545
1545
  //#endregion
1546
- //#region ../cache/src/cached/normalize-args.ts
1546
+ //#region ../../packages/cache/src/cached/normalize-args.ts
1547
1547
  /**
1548
1548
  * Resolve the positional-or-options arguments of `cached()` into a single
1549
1549
  * normalized config. Keeps the wrapper body free of shape-checks.
@@ -1565,7 +1565,7 @@ function normalizeCachedArgs(prefixOrOptions, maybeTtl) {
1565
1565
  }
1566
1566
 
1567
1567
  //#endregion
1568
- //#region ../cache/src/cached/cached.ts
1568
+ //#region ../../packages/cache/src/cached/cached.ts
1569
1569
  function cached(fn, prefixOrOptions, maybeTtl) {
1570
1570
  const config = normalizeCachedArgs(prefixOrOptions, maybeTtl);
1571
1571
  const buildRememberOptions = () => ({
@@ -1589,7 +1589,7 @@ function cached(fn, prefixOrOptions, maybeTtl) {
1589
1589
  }
1590
1590
 
1591
1591
  //#endregion
1592
- //#region ../cache/src/list/memory-cache-list.ts
1592
+ //#region ../../packages/cache/src/list/memory-cache-list.ts
1593
1593
  /**
1594
1594
  * Generic array-backed {@link CacheListAccessor}.
1595
1595
  *
@@ -1708,7 +1708,7 @@ var MemoryCacheList = class {
1708
1708
  };
1709
1709
 
1710
1710
  //#endregion
1711
- //#region ../cache/src/tagged-cache.ts
1711
+ //#region ../../packages/cache/src/tagged-cache.ts
1712
1712
  /**
1713
1713
  * Tagged Cache Wrapper
1714
1714
  * Wraps a cache driver to automatically manage tag relationships
@@ -1902,8 +1902,8 @@ var TaggedCache = class TaggedCache {
1902
1902
  /**
1903
1903
  * {@inheritdoc}
1904
1904
  */
1905
- async increment(key, value = 1) {
1906
- const newValue = await this.driver.increment(key, value);
1905
+ async increment(key, value = 1, options) {
1906
+ const newValue = await this.driver.increment(key, value, options);
1907
1907
  await this.storeTaggedKey(key);
1908
1908
  return newValue;
1909
1909
  }
@@ -1916,7 +1916,7 @@ var TaggedCache = class TaggedCache {
1916
1916
  };
1917
1917
 
1918
1918
  //#endregion
1919
- //#region ../cache/src/drivers/base-cache-driver.ts
1919
+ //#region ../../packages/cache/src/drivers/base-cache-driver.ts
1920
1920
  const messages = {
1921
1921
  clearing: "Clearing namespace",
1922
1922
  cleared: "Namespace cleared",
@@ -2309,20 +2309,31 @@ var BaseCacheDriver = class {
2309
2309
  * {@inheritdoc}
2310
2310
  *
2311
2311
  * Default implementation: read-add-write serialized per key in THIS process,
2312
- * keeping the entry's remaining TTL (a missing key starts from 0 with the
2313
- * driver default TTL). Not atomic across servers: drivers with a native
2314
- * primitive override it (redis `INCRBY`, pg single-statement upsert).
2315
- */
2316
- async increment(key, value = 1) {
2312
+ * keeping the entry's deadline (a missing key starts from 0 with the driver
2313
+ * default TTL). Not atomic across servers: drivers with a native primitive
2314
+ * override it (redis `INCRBY`, pg single-statement upsert).
2315
+ *
2316
+ * The value and its deadline come from ONE read. Reading them separately
2317
+ * let a counter that expired in between be rewritten from its stale value
2318
+ * with no expiry at all (a login-throttle counter that then never resets).
2319
+ * The remaining TTL is rounded down, so repeated increments can never push
2320
+ * a window's deadline later.
2321
+ */
2322
+ async increment(key, value = 1, options = {}) {
2323
+ const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
2317
2324
  const parsedKey = this.parseKey(key);
2318
2325
  return this.runSerialized(parsedKey, async () => {
2319
- const current = await this.get(key);
2326
+ const entry = await this.getEntry(key);
2327
+ const now = Date.now();
2328
+ const expiresAt = entry?.expiresAt;
2329
+ const live = entry ? expiresAt == null || expiresAt > now : false;
2330
+ const current = entry && live ? entry.data : null;
2320
2331
  const base = current === null || current === void 0 ? 0 : current;
2321
- if (typeof base !== "number") throw new Error(`Cannot increment non-numeric value for key: ${parsedKey}`);
2332
+ if (typeof base !== "number") throw new Error(`Cannot increment non-numeric value for key: ${(0, _warlock_js_logger.maskEmailsInText)(parsedKey)}`);
2322
2333
  const newValue = base + value;
2323
- const remainingTtl = current === null ? void 0 : await this.getRemainingTtl(key);
2324
- if (remainingTtl !== void 0) await this.set(key, newValue, { ttl: remainingTtl });
2325
- else await this.set(key, newValue);
2334
+ if (!live) await this.set(key, newValue, { ttl });
2335
+ else if (expiresAt == null || expiresAt === Infinity) await this.set(key, newValue, { ttl: options.ttl === void 0 ? Infinity : ttl });
2336
+ else await this.set(key, newValue, { ttl: Math.max(1, Math.floor((expiresAt - now) / 1e3)) });
2326
2337
  return newValue;
2327
2338
  });
2328
2339
  }
@@ -2349,7 +2360,7 @@ var BaseCacheDriver = class {
2349
2360
  */
2350
2361
  log(operation, key) {
2351
2362
  if (!this.shouldLog) return;
2352
- if (typeof key === "string") key = key.replace(/\//g, ".");
2363
+ if (typeof key === "string") key = (0, _warlock_js_logger.maskEmailsInText)(key.replace(/\//g, "."));
2353
2364
  if (operation === "connectionFailed") _warlock_js_logger.log.fatal(`cache.${this.name}`, operation, key);
2354
2365
  if (operation == "notFound" || operation == "expired") return _warlock_js_logger.log.info("cache." + this.name, operation, (key ? key + " " : "") + messages[operation]);
2355
2366
  if (operation.endsWith("ed")) return _warlock_js_logger.log.success("cache." + this.name, operation, (key ? key + " " : "") + messages[operation]);
@@ -2364,8 +2375,14 @@ var BaseCacheDriver = class {
2364
2375
  * passed to the structured logger.
2365
2376
  */
2366
2377
  logError(message, error) {
2367
- if (error) _warlock_js_logger.log.error("cache." + this.name, "error", message, safeErrorInfo(error));
2368
- else _warlock_js_logger.log.error("cache." + this.name, "error", message);
2378
+ const maskedMessage = (0, _warlock_js_logger.maskEmailsInText)(message);
2379
+ if (error) {
2380
+ const info = safeErrorInfo(error);
2381
+ _warlock_js_logger.log.error("cache." + this.name, "error", maskedMessage, {
2382
+ ...info,
2383
+ message: (0, _warlock_js_logger.maskEmailsInText)(info.message)
2384
+ });
2385
+ } else _warlock_js_logger.log.error("cache." + this.name, "error", maskedMessage);
2369
2386
  }
2370
2387
  /**
2371
2388
  * Get the default TTL in seconds. Parses human-readable strings (`"1h"`, `"30m"`)
@@ -2560,7 +2577,7 @@ var BaseCacheDriver = class {
2560
2577
  };
2561
2578
 
2562
2579
  //#endregion
2563
- //#region ../cache/src/drivers/file-cache-driver.ts
2580
+ //#region ../../packages/cache/src/drivers/file-cache-driver.ts
2564
2581
  var FileCacheDriver = class extends BaseCacheDriver {
2565
2582
  constructor(..._args) {
2566
2583
  super(..._args);
@@ -2770,7 +2787,7 @@ var FileCacheDriver = class extends BaseCacheDriver {
2770
2787
  try {
2771
2788
  const entry = await (0, _warlock_js_fs.getJsonFileAsync)(path.default.resolve(fileDirectory, this.fileName));
2772
2789
  if (!entry) return null;
2773
- if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) return null;
2790
+ if (typeof entry.expiresAt === "number" && entry.expiresAt <= Date.now()) return null;
2774
2791
  return entry;
2775
2792
  } catch {
2776
2793
  return null;
@@ -2837,7 +2854,7 @@ var FileCacheDriver = class extends BaseCacheDriver {
2837
2854
  };
2838
2855
 
2839
2856
  //#endregion
2840
- //#region ../cache/src/drivers/in-memory-tag-index.ts
2857
+ //#region ../../packages/cache/src/drivers/in-memory-tag-index.ts
2841
2858
  /**
2842
2859
  * Tag index for the in-process drivers (memory, memoryExtended, lru, mock).
2843
2860
  *
@@ -2908,7 +2925,7 @@ var InMemoryTagIndex = class {
2908
2925
  };
2909
2926
 
2910
2927
  //#endregion
2911
- //#region ../cache/src/drivers/lru-memory-cache-driver.ts
2928
+ //#region ../../packages/cache/src/drivers/lru-memory-cache-driver.ts
2912
2929
  var CacheNode = class {
2913
2930
  constructor(key, value, ttl) {
2914
2931
  this.key = key;
@@ -3116,17 +3133,19 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
3116
3133
  * concurrent increments in this process never lose an update. An existing
3117
3134
  * node keeps its expiry; a missing key starts from 0 with the default TTL.
3118
3135
  */
3119
- async increment(key, value = 1) {
3136
+ async increment(key, value = 1, options = {}) {
3137
+ const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
3120
3138
  const parsedKey = this.parseKey(key);
3121
3139
  const node = this.liveNode(parsedKey);
3122
3140
  const current = node ? node.value : 0;
3123
- if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${parsedKey}`);
3141
+ if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${(0, _warlock_js_logger.maskEmailsInText)(parsedKey)}`);
3124
3142
  const newValue = current + value;
3125
3143
  if (node) {
3126
3144
  node.value = newValue;
3145
+ if (options.ttl !== void 0 && !Number.isFinite(node.expiresAt) && Number.isFinite(ttl) && ttl > 0) node.expiresAt = Date.now() + ttl * 1e3;
3127
3146
  this.moveHead(node);
3128
3147
  } else {
3129
- const newNode = new CacheNode(parsedKey, newValue, this.ttl);
3148
+ const newNode = new CacheNode(parsedKey, newValue, ttl);
3130
3149
  this.cache.set(parsedKey, newNode);
3131
3150
  this.addNode(newNode);
3132
3151
  if (this.cache.size > this.capacity) this.removeTail();
@@ -3134,7 +3153,7 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
3134
3153
  await this.emit("set", {
3135
3154
  key: parsedKey,
3136
3155
  value: newValue,
3137
- ttl: this.ttl
3156
+ ttl
3138
3157
  });
3139
3158
  return newValue;
3140
3159
  }
@@ -3315,7 +3334,7 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
3315
3334
  };
3316
3335
 
3317
3336
  //#endregion
3318
- //#region ../cache/src/drivers/memory-cache-driver.ts
3337
+ //#region ../../packages/cache/src/drivers/memory-cache-driver.ts
3319
3338
  /**
3320
3339
  * Clone non-primitive values so cached state can't be mutated through
3321
3340
  * references held by callers. Primitives are immutable and returned as-is.
@@ -3487,17 +3506,23 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
3487
3506
  * concurrent increments in this process never lose an update. An existing
3488
3507
  * entry keeps its expiry; a missing key starts from 0 with the default TTL.
3489
3508
  */
3490
- async increment(key, value = 1) {
3509
+ async increment(key, value = 1, options = {}) {
3510
+ const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
3491
3511
  const parsedKey = this.parseKey(key);
3492
3512
  const entry = this.readLive(parsedKey);
3493
3513
  const current = entry ? entry.data : 0;
3494
- if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${parsedKey}`);
3514
+ if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${(0, _warlock_js_logger.maskEmailsInText)(parsedKey)}`);
3495
3515
  const newValue = current + value;
3496
3516
  if (entry) {
3497
3517
  entry.data = newValue;
3518
+ if (options.ttl !== void 0 && !Number.isFinite(entry.expiresAt) && Number.isFinite(ttl) && ttl > 0) {
3519
+ entry.ttl = ttl;
3520
+ entry.expiresAt = Date.now() + ttl * 1e3;
3521
+ this.expiry.set(parsedKey, entry.expiresAt);
3522
+ }
3498
3523
  this.touch(parsedKey, entry);
3499
3524
  } else {
3500
- const data = this.prepareDataForStorage(newValue, this.ttl);
3525
+ const data = this.prepareDataForStorage(newValue, ttl);
3501
3526
  this.entries.set(parsedKey, data);
3502
3527
  if (data.expiresAt !== void 0 && Number.isFinite(data.expiresAt)) this.expiry.set(parsedKey, data.expiresAt);
3503
3528
  this.enforceMaxSize(parsedKey);
@@ -3505,7 +3530,7 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
3505
3530
  await this.emit("set", {
3506
3531
  key: parsedKey,
3507
3532
  value: newValue,
3508
- ttl: entry?.ttl ?? this.ttl
3533
+ ttl: entry?.ttl ?? ttl
3509
3534
  });
3510
3535
  return newValue;
3511
3536
  }
@@ -3674,7 +3699,7 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
3674
3699
  };
3675
3700
 
3676
3701
  //#endregion
3677
- //#region ../cache/src/drivers/memory-extended-cache-driver.ts
3702
+ //#region ../../packages/cache/src/drivers/memory-extended-cache-driver.ts
3678
3703
  /**
3679
3704
  * Memory driver with a sliding ttl: every live read pushes the entry's
3680
3705
  * expiration forward by its ttl. The sliding itself lives in
@@ -3691,7 +3716,7 @@ var MemoryExtendedCacheDriver = class extends MemoryCacheDriver {
3691
3716
  };
3692
3717
 
3693
3718
  //#endregion
3694
- //#region ../cache/src/drivers/mock-cache-driver.ts
3719
+ //#region ../../packages/cache/src/drivers/mock-cache-driver.ts
3695
3720
  /**
3696
3721
  * In-memory cache driver with introspection helpers, intended for use as a
3697
3722
  * test double in downstream packages.
@@ -3857,19 +3882,25 @@ var MockCacheDriver = class extends BaseCacheDriver {
3857
3882
  * Synchronous read-add-write (atomic within the process). Keeps an existing
3858
3883
  * entry's expiry; a missing key starts from 0 with the default TTL.
3859
3884
  */
3860
- async increment(key, value = 1) {
3885
+ async increment(key, value = 1, options = {}) {
3886
+ const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
3861
3887
  const parsedKey = this.parseKey(key);
3862
3888
  this.recordCall("increment", parsedKey, [value]);
3863
3889
  const entry = this.liveEntry(parsedKey);
3864
3890
  const current = entry ? entry.data : 0;
3865
- if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${parsedKey}`);
3891
+ if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${(0, _warlock_js_logger.maskEmailsInText)(parsedKey)}`);
3866
3892
  const newValue = current + value;
3867
- if (entry) entry.data = newValue;
3868
- else this.storage.set(parsedKey, this.prepareDataForStorage(newValue, this.ttl));
3893
+ if (entry) {
3894
+ entry.data = newValue;
3895
+ if (options.ttl !== void 0 && !Number.isFinite(entry.expiresAt) && Number.isFinite(ttl) && ttl > 0) {
3896
+ entry.ttl = ttl;
3897
+ entry.expiresAt = Date.now() + ttl * 1e3;
3898
+ }
3899
+ } else this.storage.set(parsedKey, this.prepareDataForStorage(newValue, ttl));
3869
3900
  await this.emit("set", {
3870
3901
  key: parsedKey,
3871
3902
  value: newValue,
3872
- ttl: this.ttl
3903
+ ttl
3873
3904
  });
3874
3905
  return newValue;
3875
3906
  }
@@ -4009,7 +4040,7 @@ var MockCacheDriver = class extends BaseCacheDriver {
4009
4040
  };
4010
4041
 
4011
4042
  //#endregion
4012
- //#region ../cache/src/drivers/null-cache-driver.ts
4043
+ //#region ../../packages/cache/src/drivers/null-cache-driver.ts
4013
4044
  var NullCacheDriver = class extends BaseCacheDriver {
4014
4045
  /**
4015
4046
  * {@inheritdoc}
@@ -4052,32 +4083,39 @@ var NullCacheDriver = class extends BaseCacheDriver {
4052
4083
  * {@inheritdoc}
4053
4084
  */
4054
4085
  async removeNamespace(namespace) {
4055
- _warlock_js_logger.log.info("cache", "clearing namespace", namespace);
4056
- _warlock_js_logger.log.success("cache", "namespace cleared", namespace);
4086
+ const loggableNamespace = (0, _warlock_js_logger.maskEmailsInText)(namespace);
4087
+ _warlock_js_logger.log.info("cache", "clearing namespace", loggableNamespace);
4088
+ _warlock_js_logger.log.success("cache", "namespace cleared", loggableNamespace);
4057
4089
  return this;
4058
4090
  }
4059
4091
  /**
4092
+ * The key as it may appear in a log line: parsed, with email addresses masked
4093
+ */
4094
+ loggableKey(key) {
4095
+ return (0, _warlock_js_logger.maskEmailsInText)(this.parseKey(key));
4096
+ }
4097
+ /**
4060
4098
  * {@inheritdoc}
4061
4099
  */
4062
4100
  async set(key, _value, _ttlOrOptions) {
4063
- _warlock_js_logger.log.info("cache", "setting key", key);
4064
- _warlock_js_logger.log.success("cache", "key set", key);
4101
+ _warlock_js_logger.log.info("cache", "setting key", this.loggableKey(key));
4102
+ _warlock_js_logger.log.success("cache", "key set", this.loggableKey(key));
4065
4103
  return this;
4066
4104
  }
4067
4105
  /**
4068
4106
  * {@inheritdoc}
4069
4107
  */
4070
4108
  async get(key) {
4071
- _warlock_js_logger.log.info("cache", "fetching", key);
4072
- _warlock_js_logger.log.success("cache", "fetched", key);
4109
+ _warlock_js_logger.log.info("cache", "fetching", this.loggableKey(key));
4110
+ _warlock_js_logger.log.success("cache", "fetched", this.loggableKey(key));
4073
4111
  return null;
4074
4112
  }
4075
4113
  /**
4076
4114
  * {@inheritdoc}
4077
4115
  */
4078
4116
  async remove(key) {
4079
- _warlock_js_logger.log.info("cache", "removing", key);
4080
- _warlock_js_logger.log.success("cache", "removed", key);
4117
+ _warlock_js_logger.log.info("cache", "removing", this.loggableKey(key));
4118
+ _warlock_js_logger.log.success("cache", "removed", this.loggableKey(key));
4081
4119
  }
4082
4120
  /**
4083
4121
  * {@inheritdoc}
@@ -4104,7 +4142,7 @@ var NullCacheDriver = class extends BaseCacheDriver {
4104
4142
  };
4105
4143
 
4106
4144
  //#endregion
4107
- //#region ../cache/src/drivers/pg-cache-driver.ts
4145
+ //#region ../../packages/cache/src/drivers/pg-cache-driver.ts
4108
4146
  /** Compare-and-set attempts before `update()` gives up under contention. */
4109
4147
  const PG_UPDATE_MAX_ATTEMPTS = 10;
4110
4148
  /**
@@ -4539,7 +4577,7 @@ var PgCacheDriver = class extends BaseCacheDriver {
4539
4577
  * keeps its `expires_at`; a missing or expired row restarts from `value`
4540
4578
  * with the driver default TTL. A non-numeric live value is rejected.
4541
4579
  */
4542
- async increment(key, value = 1) {
4580
+ async increment(key, value = 1, options = {}) {
4543
4581
  const parsedKey = this.parseKey(key);
4544
4582
  const t = this.table;
4545
4583
  const expired = `${t}.expires_at IS NOT NULL AND ${t}.expires_at <= now()`;
@@ -4548,15 +4586,18 @@ var PgCacheDriver = class extends BaseCacheDriver {
4548
4586
  ON CONFLICT (key) DO UPDATE
4549
4587
  SET value = CASE WHEN ${expired} THEN EXCLUDED.value
4550
4588
  ELSE to_jsonb((${t}.value #>> '{}')::numeric + $2::numeric) END,
4551
- expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at ELSE ${t}.expires_at END,
4589
+ expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at
4590
+ WHEN $4::boolean AND ${t}.expires_at IS NULL THEN EXCLUDED.expires_at
4591
+ ELSE ${t}.expires_at END,
4552
4592
  stale_at = CASE WHEN ${expired} THEN NULL ELSE ${t}.stale_at END
4553
4593
  WHERE (${expired}) OR jsonb_typeof(${t}.value) = 'number'
4554
4594
  RETURNING value`, [
4555
4595
  parsedKey,
4556
4596
  value,
4557
- this.ttlToSeconds(this.ttl)
4597
+ this.ttlToSeconds(options.ttl === void 0 ? this.ttl : parseTtl(options.ttl)),
4598
+ options.ttl !== void 0
4558
4599
  ]);
4559
- if (rows.length === 0) throw new Error(`Cannot increment non-numeric value for key: ${parsedKey}`);
4600
+ if (rows.length === 0) throw new Error(`Cannot increment non-numeric value for key: ${(0, _warlock_js_logger.maskEmailsInText)(parsedKey)}`);
4560
4601
  const newValue = Number(this.decodeJson(rows[0].value));
4561
4602
  await this.emit("set", {
4562
4603
  key: parsedKey,
@@ -4757,7 +4798,7 @@ var PgCacheDriver = class extends BaseCacheDriver {
4757
4798
  };
4758
4799
 
4759
4800
  //#endregion
4760
- //#region ../cache/src/drivers/redis-cache-driver.ts
4801
+ //#region ../../packages/cache/src/drivers/redis-cache-driver.ts
4761
4802
  /**
4762
4803
  * Cached Redis module (loaded once, reused)
4763
4804
  */
@@ -4821,6 +4862,23 @@ return 1`;
4821
4862
  const REDIS_GET_DEL_SCRIPT = `local v = redis.call('GET', KEYS[1])
4822
4863
  if v then redis.call('DEL', KEYS[1]) end
4823
4864
  return v`;
4865
+ /**
4866
+ * Increment that gives a counter without an expiry its per-call or default TTL,
4867
+ * in one round trip.
4868
+ *
4869
+ * KEYS[1]=key; ARGV[1]=amount; ARGV[2]=ttl seconds ('0' = none).
4870
+ *
4871
+ * Replaces `INCRBY` + `EXPIRE … NX`: the `NX` flag needs Redis >= 7.0, and on
4872
+ * Redis 6 the second command failed after the count had already gone up.
4873
+ * `TTL == -1` means "exists without an expiry", so a TTL set earlier (by
4874
+ * `set(..., { ttl })`) is never shortened or reset.
4875
+ */
4876
+ const REDIS_INCREMENT_SCRIPT = `local v = redis.call('INCRBY', KEYS[1], ARGV[1])
4877
+ local ttl = tonumber(ARGV[2])
4878
+ if ttl > 0 and redis.call('TTL', KEYS[1]) == -1 then
4879
+ redis.call('EXPIRE', KEYS[1], ttl)
4880
+ end
4881
+ return v`;
4824
4882
  /** Compare-and-set attempts before `update()` gives up under contention. */
4825
4883
  const UPDATE_MAX_ATTEMPTS = 10;
4826
4884
  /** Keys deleted per UNLINK/DEL while draining a namespace. */
@@ -5249,11 +5307,15 @@ var RedisCacheDriver = class extends BaseCacheDriver {
5249
5307
  * Atomic increment using Redis native INCRBY command
5250
5308
  * {@inheritdoc}
5251
5309
  */
5252
- async increment(key, value = 1) {
5310
+ async increment(key, value = 1, options = {}) {
5253
5311
  const parsedKey = this.parseKey(key);
5254
5312
  this.log("caching", parsedKey);
5255
- const result = await this.client?.incrBy(parsedKey, value);
5256
- if (this.ttl !== Infinity && this.ttl > 0) await this.client?.expire(parsedKey, this.ttl, "NX");
5313
+ const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
5314
+ const defaultTtl = Number.isFinite(ttl) && ttl > 0 ? Math.ceil(ttl) : 0;
5315
+ const result = defaultTtl > 0 ? Number(await this.client?.eval(REDIS_INCREMENT_SCRIPT, {
5316
+ keys: [parsedKey],
5317
+ arguments: [String(value), String(defaultTtl)]
5318
+ })) : await this.client?.incrBy(parsedKey, value);
5257
5319
  this.log("cached", parsedKey);
5258
5320
  await this.emit("set", {
5259
5321
  key: parsedKey,
@@ -5327,6 +5389,7 @@ exports.PgCacheDriver = PgCacheDriver;
5327
5389
  exports.REDIS_CAS_SET_SCRIPT = REDIS_CAS_SET_SCRIPT;
5328
5390
  exports.REDIS_COMPARE_DELETE_SCRIPT = REDIS_COMPARE_DELETE_SCRIPT;
5329
5391
  exports.REDIS_GET_DEL_SCRIPT = REDIS_GET_DEL_SCRIPT;
5392
+ exports.REDIS_INCREMENT_SCRIPT = REDIS_INCREMENT_SCRIPT;
5330
5393
  exports.RedisCacheDriver = RedisCacheDriver;
5331
5394
  exports.ScopedCache = ScopedCache;
5332
5395
  exports.TaggedCache = TaggedCache;