@warlock.js/cache 5.30.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 (77) hide show
  1. package/CHANGELOG.md +20 -8
  2. package/README.md +12 -21
  3. package/cjs/index.cjs +46 -27
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/cache-manager.d.mts +3 -1
  6. package/esm/cache-manager.d.mts.map +1 -1
  7. package/esm/cache-manager.mjs +2 -2
  8. package/esm/cache-manager.mjs.map +1 -1
  9. package/esm/drivers/base-cache-driver.d.mts +3 -1
  10. package/esm/drivers/base-cache-driver.d.mts.map +1 -1
  11. package/esm/drivers/base-cache-driver.mjs +4 -3
  12. package/esm/drivers/base-cache-driver.mjs.map +1 -1
  13. package/esm/drivers/file-cache-driver.d.mts.map +1 -1
  14. package/esm/drivers/file-cache-driver.mjs.map +1 -1
  15. package/esm/drivers/lru-memory-cache-driver.d.mts +3 -1
  16. package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
  17. package/esm/drivers/lru-memory-cache-driver.mjs +6 -4
  18. package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
  19. package/esm/drivers/memory-cache-driver.d.mts +3 -1
  20. package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
  21. package/esm/drivers/memory-cache-driver.mjs +9 -3
  22. package/esm/drivers/memory-cache-driver.mjs.map +1 -1
  23. package/esm/drivers/mock-cache-driver.d.mts +3 -1
  24. package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
  25. package/esm/drivers/mock-cache-driver.mjs +11 -4
  26. package/esm/drivers/mock-cache-driver.mjs.map +1 -1
  27. package/esm/drivers/pg-cache-driver.d.mts +3 -1
  28. package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
  29. package/esm/drivers/pg-cache-driver.mjs +6 -3
  30. package/esm/drivers/pg-cache-driver.mjs.map +1 -1
  31. package/esm/drivers/redis-cache-driver.d.mts +4 -2
  32. package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
  33. package/esm/drivers/redis-cache-driver.mjs +4 -3
  34. package/esm/drivers/redis-cache-driver.mjs.map +1 -1
  35. package/esm/list/memory-cache-list.mjs.map +1 -1
  36. package/esm/metrics.d.mts.map +1 -1
  37. package/esm/metrics.mjs.map +1 -1
  38. package/esm/scoped-cache.d.mts +3 -1
  39. package/esm/scoped-cache.d.mts.map +1 -1
  40. package/esm/scoped-cache.mjs +2 -2
  41. package/esm/scoped-cache.mjs.map +1 -1
  42. package/esm/tagged-cache.d.mts +3 -1
  43. package/esm/tagged-cache.d.mts.map +1 -1
  44. package/esm/tagged-cache.mjs +2 -2
  45. package/esm/tagged-cache.mjs.map +1 -1
  46. package/esm/tagged-scoped-cache.d.mts +3 -1
  47. package/esm/tagged-scoped-cache.d.mts.map +1 -1
  48. package/esm/tagged-scoped-cache.mjs +2 -2
  49. package/esm/tagged-scoped-cache.mjs.map +1 -1
  50. package/esm/types.d.mts +16 -5
  51. package/esm/types.d.mts.map +1 -1
  52. package/esm/types.mjs.map +1 -1
  53. package/esm/utils.d.mts.map +1 -1
  54. package/esm/utils.mjs.map +1 -1
  55. package/llms-full.txt +229 -224
  56. package/llms.txt +1 -1
  57. package/package.json +3 -3
  58. package/skills/apply-cache-patterns/SKILL.md +3 -2
  59. package/skills/cache-basics/SKILL.md +24 -24
  60. package/skills/configure-pg-cache/SKILL.md +2 -2
  61. package/skills/configure-set-options/SKILL.md +14 -14
  62. package/skills/handle-cache-errors/SKILL.md +8 -8
  63. package/skills/index.md +4 -1
  64. package/skills/observe-cache/SKILL.md +8 -8
  65. package/skills/pick-cache-driver/SKILL.md +24 -23
  66. package/skills/test-cache-code/SKILL.md +15 -10
  67. package/skills/use-cache-atomic/SKILL.md +17 -11
  68. package/skills/use-cache-bulk/SKILL.md +8 -5
  69. package/skills/use-cache-list/SKILL.md +13 -13
  70. package/skills/use-cache-lock/SKILL.md +11 -17
  71. package/skills/use-cache-namespace/SKILL.md +11 -13
  72. package/skills/use-cache-similarity/SKILL.md +9 -9
  73. package/skills/use-cache-tags/SKILL.md +13 -13
  74. package/skills/use-cache-update-merge/SKILL.md +10 -10
  75. package/skills/use-cache-utils/SKILL.md +17 -17
  76. package/skills/use-cached-hof/SKILL.md +8 -7
  77. package/skills/use-swr/SKILL.md +14 -18
@@ -113,7 +113,9 @@ declare class LRUMemoryCacheDriver extends BaseCacheDriver<LRUMemoryCacheDriver,
113
113
  * concurrent increments in this process never lose an update. An existing
114
114
  * node keeps its expiry; a missing key starts from 0 with the default TTL.
115
115
  */
116
- increment(key: CacheKey, value?: number): Promise<number>;
116
+ increment(key: CacheKey, value?: number, options?: {
117
+ ttl?: CacheTtl;
118
+ }): Promise<number>;
117
119
  /**
118
120
  * {@inheritdoc}
119
121
  *
@@ -1 +1 @@
1
- {"version":3,"file":"lru-memory-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/lru-memory-cache-driver.ts"],"mappings":";;;;;cAgBM,SAAA;EAeK,GAAA;EACA,KAAA;EAfF,IAAA,EAAM,SAAA;EACN,IAAA,EAAM,SAAS;EACf,SAAA;EAYE;;;;EAPF,OAAA;EANM;;;;EAWN,MAAA;cAEE,GAAA,UACA,KAAA,OACP,GAAA;EAAA,IAOS,SAAA;AAAA;;;AAAS;AAWtB;;;cAAa,oBAAA,SACH,eAAA,CAAgB,oBAAA,EAAsB,qBAAA,aACnC,WAAA,CAAY,oBAAA,EAAsB,qBAAA;EADC;;;EAMvC,IAAA;EAKU;;;EAAA,UAAP,KAAA,EAAO,GAAA,SAAY,SAAA;EAoBD;;;EAAA,UAflB,QAAA,EAAU,gBAAA;EAyHQ;;;EAAA,UApHlB,IAAA,EAAM,SAAA;EAmNQ;;;EAAA,UA9Md,IAAA,EAAM,SAAA;EAyPuB;;;EAAA,UApP7B,eAAA,GAAkB,MAAA,CAAO,OAAA;EAgTD;;;;EAqFD;;;EAtX1B,IAAA;EAoZoC;;;EA5YpC,YAAA;EA0ZmC;;;;;;;;EAlX7B,eAAA,CAAgB,SAAA,WAAiB,OAAA;EA7FxB;;;EAqIT,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EA1I2C;;;EAAA,UAwNpC,QAAA,CAAS,IAAA,EAAM,SAAA;EAlNlB;;;EAAA,UA0NG,UAAA,CAAW,IAAA,EAAM,SAAA;EAhNjB;;;EAAA,UAwNA,OAAA,CAAQ,IAAA,EAAM,SAAA;EA9Md;;;EAAA,UAwNA,UAAA;EAnNyB;;;;;;EAAA,UAiOnB,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EA1HpC;;;;EAAA,UA6IH,QAAA,CAAS,SAAA,WAAoB,SAAA;EA1IX;;;;;;;EAkKf,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EA3ErC;;;;;;EA+GR,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;EA/ET;;;EA0GZ,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAvFX;;;EAiJN,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EAzHV;;;;;EAgJV,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EA5GxB;;;EAmHrB,UAAA,CAAW,MAAA,EAAQ,QAAA,GAAW,OAAA;EAxFb;;;EA+FjB,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EArC5B;;;EA4CpB,SAAA,CAAU,MAAA,EAAQ,QAAA,GAAW,OAAA;EArBJ;;;;;;;EAgCzB,KAAA,IAAK,OAAA;EAlBK;;;;;;;;;EA2CV,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;EAHN;;;EAAA,IA2CV,QAAA;EAxCR;;;EA+CU,UAAA,IAAU,OAAA;AAAA"}
1
+ {"version":3,"file":"lru-memory-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/lru-memory-cache-driver.ts"],"mappings":";;;;;cAgBM,SAAA;EAeK,GAAA;EACA,KAAA;EAfF,IAAA,EAAM,SAAA;EACN,IAAA,EAAM,SAAS;EACf,SAAA;EAYE;;;;EAPF,OAAA;EANM;;;;EAWN,MAAA;cAEE,GAAA,UACA,KAAA,OACP,GAAA;EAAA,IAOS,SAAA;AAAA;;;AAAS;AAWtB;;;cAAa,oBAAA,SACH,eAAA,CAAgB,oBAAA,EAAsB,qBAAA,aACnC,WAAA,CAAY,oBAAA,EAAsB,qBAAA;EADC;;;EAMvC,IAAA;EAKU;;;EAAA,UAAP,KAAA,EAAO,GAAA,SAAY,SAAA;EAoBD;;;EAAA,UAflB,QAAA,EAAU,gBAAA;EAyHQ;;;EAAA,UApHlB,IAAA,EAAM,SAAA;EAmNQ;;;EAAA,UA9Md,IAAA,EAAM,SAAA;EAyPuB;;;EAAA,UApP7B,eAAA,GAAkB,MAAA,CAAO,OAAA;EA6TZ;;;;EAqFE;;;EAnYlB,IAAA;EAiayB;;;EAzZzB,YAAA;EAuawB;;;;;;;;EA/XlB,eAAA,CAAgB,SAAA,WAAiB,OAAA;EA7FnC;;;EAqIE,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EA1IqB;;;EAAA,UAwNd,QAAA,CAAS,IAAA,EAAM,SAAA;EAvNoB;;;EAAA,UA+NnC,UAAA,CAAW,IAAA,EAAM,SAAA;EArNE;;;EAAA,UA6NnB,OAAA,CAAQ,IAAA,EAAM,SAAA;EAnNR;;;EAAA,UA6NN,UAAA;EAnNkB;;;;;;EAAA,UAiOZ,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAlKH;;;;EAAA,UAqLpC,QAAA,CAAS,SAAA,WAAoB,SAAA;EA1ItB;;;;;;;EAkKJ,SAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,WACA,OAAA;IAAW,GAAA,GAAM,QAAA;EAAA,IAChB,OAAA;EAvEO;;;;;;EAoHG,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;EA5FO;;;EAuH5B,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EApGS;;;EA8J1B,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EApI/B;;;;;EA2JW,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EA5GnC;;;EAmHV,UAAA,CAAW,MAAA,EAAQ,QAAA,GAAW,OAAA;EAxFrB;;;EA+FT,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EArCpC;;;EA4CZ,SAAA,CAAU,MAAA,EAAQ,QAAA,GAAW,OAAA;EArBd;;;;;;;EAgCf,KAAA,IAAK,OAAA;EAlBL;;;;;;;;;EA2CA,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;EA5BT;;;EAAA,IAoEP,QAAA;EAzCA;;;EAgDE,UAAA,IAAU,OAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { cosineSimilarity } from "../utils.mjs";
1
+ import { cosineSimilarity, parseTtl } from "../utils.mjs";
2
2
  import { BaseCacheDriver } from "./base-cache-driver.mjs";
3
3
  import { InMemoryTagIndex } from "./in-memory-tag-index.mjs";
4
4
  import { maskEmailsInText } from "@warlock.js/logger";
@@ -211,7 +211,8 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
211
211
  * concurrent increments in this process never lose an update. An existing
212
212
  * node keeps its expiry; a missing key starts from 0 with the default TTL.
213
213
  */
214
- async increment(key, value = 1) {
214
+ async increment(key, value = 1, options = {}) {
215
+ const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
215
216
  const parsedKey = this.parseKey(key);
216
217
  const node = this.liveNode(parsedKey);
217
218
  const current = node ? node.value : 0;
@@ -219,9 +220,10 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
219
220
  const newValue = current + value;
220
221
  if (node) {
221
222
  node.value = newValue;
223
+ if (options.ttl !== void 0 && !Number.isFinite(node.expiresAt) && Number.isFinite(ttl) && ttl > 0) node.expiresAt = Date.now() + ttl * 1e3;
222
224
  this.moveHead(node);
223
225
  } else {
224
- const newNode = new CacheNode(parsedKey, newValue, this.ttl);
226
+ const newNode = new CacheNode(parsedKey, newValue, ttl);
225
227
  this.cache.set(parsedKey, newNode);
226
228
  this.addNode(newNode);
227
229
  if (this.cache.size > this.capacity) this.removeTail();
@@ -229,7 +231,7 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
229
231
  await this.emit("set", {
230
232
  key: parsedKey,
231
233
  value: newValue,
232
- ttl: this.ttl
234
+ ttl
233
235
  });
234
236
  return newValue;
235
237
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lru-memory-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/lru-memory-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n LRUMemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\nclass CacheNode {\n public next: CacheNode | null = null;\n public prev: CacheNode | null = null;\n public expiresAt?: number;\n /**\n * Freshness deadline (ms timestamp) — populated by `swr()`. Within\n * `expiresAt > now > staleAt` the entry is \"stale-but-revalidatable.\"\n */\n public staleAt?: number;\n /**\n * Optional embedding vector — populated when the entry was written with\n * `set({ vector })`. Scanned by `similar()`.\n */\n public vector?: number[];\n public constructor(\n public key: string,\n public value: any,\n ttl?: number,\n ) {\n if (ttl && ttl !== Infinity) {\n this.expiresAt = Date.now() + ttl * 1000;\n }\n }\n\n public get isExpired(): boolean {\n return this.expiresAt !== undefined && this.expiresAt < Date.now();\n }\n}\n\n/**\n * LRU Memory Cache Driver\n * The concept of LRU is to remove the least recently used data\n * whenever the cache is full\n * The question that resides here is how to tell the cache is full?\n */\nexport class LRUMemoryCacheDriver\n extends BaseCacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n implements CacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"lru\";\n\n /**\n * Cache map\n */\n protected cache: Map<string, CacheNode> = new Map();\n\n /**\n * Tag index, kept out of the LRU list so capacity pressure can never evict it.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Head of the cache\n */\n protected head: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Tail of the cache\n */\n protected tail: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.init();\n this.startCleanup();\n }\n\n /**\n * Initialize the cache\n */\n public init() {\n this.head.next = this.tail;\n this.tail.prev = this.head;\n }\n\n /**\n * Start the cleanup process for expired items\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, node] of this.cache) {\n if (node.expiresAt && node.expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * {@inheritdoc}\n *\n * Clears every entry whose key starts with the parsed namespace (followed\n * by a dot) or equals it exactly. Called with an empty namespace while a\n * `globalPrefix` is configured, clears everything under the prefix — which\n * is how `flush()` scopes cleanup per tenant.\n */\n public async removeNamespace(namespace: string) {\n const parsedNamespace = this.parseKey(namespace);\n\n this.log(\"clearing\", parsedNamespace || \"(all)\");\n\n const removed: string[] = [];\n\n this.tagIndex.removeNamespace(parsedNamespace);\n\n if (parsedNamespace === \"\") {\n for (const key of this.cache.keys()) {\n removed.push(key);\n }\n } else {\n const prefix = parsedNamespace + \".\";\n\n for (const key of this.cache.keys()) {\n if (key === parsedNamespace || key.startsWith(prefix)) {\n removed.push(key);\n }\n }\n }\n\n for (const key of removed) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n }\n await this.emit(\"removed\", { key });\n }\n\n this.log(\"cleared\", parsedNamespace || \"(all)\");\n\n return removed;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // For conditional writes, check existence via get() so expired entries\n // are treated as missing (get() handles expiry cleanup). The raw `cache.get`\n // node lookup would let stale entries block onConflict: \"create\".\n let existingNode = this.cache.get(parsedKey);\n if (existingNode && existingNode.isExpired) {\n this.removeNode(existingNode);\n this.cache.delete(parsedKey);\n existingNode = undefined;\n }\n\n const exists = Boolean(existingNode);\n\n if (onConflict === \"create\" && exists) {\n const result: CacheSetResult = {\n wasSet: false,\n existing: existingNode!.value,\n };\n return result;\n }\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n if (existingNode) {\n existingNode.value = value;\n if (ttl && ttl !== Infinity) {\n existingNode.expiresAt = Date.now() + ttl * 1000;\n } else {\n existingNode.expiresAt = undefined;\n }\n existingNode.staleAt = staleAt;\n if (vector) {\n existingNode.vector = vector.slice();\n }\n\n this.moveHead(existingNode);\n } else {\n const newNode = new CacheNode(parsedKey, value, ttl);\n newNode.staleAt = staleAt;\n if (vector) {\n newNode.vector = vector.slice();\n }\n\n this.cache.set(parsedKey, newNode);\n\n this.addNode(newNode);\n if (this.cache.size > this.capacity) {\n this.removeTail();\n }\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * Move the node to the head\n */\n protected moveHead(node: CacheNode) {\n this.removeNode(node);\n this.addNode(node);\n }\n\n /**\n * Remove the node from the cache\n */\n protected removeNode(node: CacheNode) {\n node.prev!.next = node.next;\n node.next!.prev = node.prev;\n }\n\n /**\n * Add the node to the head\n */\n protected addNode(node: CacheNode) {\n node.next = this.head.next;\n node.prev = this.head;\n this.head.next!.prev = node;\n this.head.next = node;\n }\n\n /**\n * Remove the tail node\n */\n protected removeTail() {\n const node = this.tail.prev!;\n\n this.removeNode(node);\n\n this.cache.delete(node.key);\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\n * Returns `null` for missing or expired nodes — `swr()` consumes this\n * to branch on freshness without going through `get()`'s clone-and-emit\n * path.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const node = this.cache.get(parsedKey);\n\n if (!node || node.isExpired) {\n return null;\n }\n\n return {\n data: node.value,\n expiresAt: node.expiresAt,\n staleAt: node.staleAt,\n };\n }\n\n /**\n * Live node for `parsedKey`, or `undefined` when missing / expired (an\n * expired node is unlinked). Synchronous.\n */\n protected liveNode(parsedKey: string): CacheNode | undefined {\n const node = this.cache.get(parsedKey);\n\n if (!node) {\n return undefined;\n }\n\n if (node.isExpired) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n\n return undefined;\n }\n\n return node;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * node keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n const node = this.liveNode(parsedKey);\n const current = node ? node.value : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (node) {\n node.value = newValue;\n this.moveHead(node);\n } else {\n const newNode = new CacheNode(parsedKey, newValue, this.ttl);\n\n this.cache.set(parsedKey, newNode);\n this.addNode(newNode);\n\n if (this.cache.size > this.capacity) {\n this.removeTail();\n }\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: this.ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and unlink run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const node = this.liveNode(parsedKey);\n\n if (!node) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.removeNode(node);\n this.cache.delete(parsedKey);\n\n const value =\n node.value !== null && typeof node.value === \"object\"\n ? structuredClone(node.value)\n : node.value;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (!node) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n // Check if expired\n if (node.isExpired) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n this.log(\"expired\", parsedKey);\n // Emit expired event\n await this.emit(\"expired\", { key: parsedKey });\n // Also emit miss since we're returning null\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.moveHead(node);\n\n this.log(\"fetched\", parsedKey);\n\n const value = node.value;\n\n // Apply cloning for immutability protection\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n try {\n const clonedValue = structuredClone(value);\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: clonedValue });\n return clonedValue;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\n throw error;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (node) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n }\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n *\n * When a `globalPrefix` is configured, `flush` scopes itself to that prefix\n * so multi-tenant caches don't accidentally wipe sibling tenants. Without\n * a prefix, clears everything.\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.cache.clear();\n this.tagIndex.clear();\n this.init();\n }\n\n this.log(\"flushed\");\n\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every cached node that carries a\n * vector. Suitable for development and small in-memory knowledge bases —\n * not for production beyond ~10k entries.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, node] of this.cache) {\n if (!node.vector) continue;\n if (node.isExpired) continue;\n if (tagFilter && !tagFilter.has(parsedKey)) continue;\n\n const score = cosineSimilarity(vector, node.vector);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n // Clone object values to match get() semantics.\n let value: any = node.value;\n if (value !== null && value !== undefined) {\n const t = typeof value;\n if (t !== \"string\" && t !== \"number\" && t !== \"boolean\") {\n value = structuredClone(value);\n }\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * Get lru capacity\n */\n public get capacity() {\n return this.options.capacity || 1000;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lru-memory-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/lru-memory-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n LRUMemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity, parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\nclass CacheNode {\n public next: CacheNode | null = null;\n public prev: CacheNode | null = null;\n public expiresAt?: number;\n /**\n * Freshness deadline (ms timestamp) — populated by `swr()`. Within\n * `expiresAt > now > staleAt` the entry is \"stale-but-revalidatable.\"\n */\n public staleAt?: number;\n /**\n * Optional embedding vector — populated when the entry was written with\n * `set({ vector })`. Scanned by `similar()`.\n */\n public vector?: number[];\n public constructor(\n public key: string,\n public value: any,\n ttl?: number,\n ) {\n if (ttl && ttl !== Infinity) {\n this.expiresAt = Date.now() + ttl * 1000;\n }\n }\n\n public get isExpired(): boolean {\n return this.expiresAt !== undefined && this.expiresAt < Date.now();\n }\n}\n\n/**\n * LRU Memory Cache Driver\n * The concept of LRU is to remove the least recently used data\n * whenever the cache is full\n * The question that resides here is how to tell the cache is full?\n */\nexport class LRUMemoryCacheDriver\n extends BaseCacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n implements CacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"lru\";\n\n /**\n * Cache map\n */\n protected cache: Map<string, CacheNode> = new Map();\n\n /**\n * Tag index, kept out of the LRU list so capacity pressure can never evict it.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Head of the cache\n */\n protected head: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Tail of the cache\n */\n protected tail: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.init();\n this.startCleanup();\n }\n\n /**\n * Initialize the cache\n */\n public init() {\n this.head.next = this.tail;\n this.tail.prev = this.head;\n }\n\n /**\n * Start the cleanup process for expired items\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, node] of this.cache) {\n if (node.expiresAt && node.expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * {@inheritdoc}\n *\n * Clears every entry whose key starts with the parsed namespace (followed\n * by a dot) or equals it exactly. Called with an empty namespace while a\n * `globalPrefix` is configured, clears everything under the prefix — which\n * is how `flush()` scopes cleanup per tenant.\n */\n public async removeNamespace(namespace: string) {\n const parsedNamespace = this.parseKey(namespace);\n\n this.log(\"clearing\", parsedNamespace || \"(all)\");\n\n const removed: string[] = [];\n\n this.tagIndex.removeNamespace(parsedNamespace);\n\n if (parsedNamespace === \"\") {\n for (const key of this.cache.keys()) {\n removed.push(key);\n }\n } else {\n const prefix = parsedNamespace + \".\";\n\n for (const key of this.cache.keys()) {\n if (key === parsedNamespace || key.startsWith(prefix)) {\n removed.push(key);\n }\n }\n }\n\n for (const key of removed) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n }\n await this.emit(\"removed\", { key });\n }\n\n this.log(\"cleared\", parsedNamespace || \"(all)\");\n\n return removed;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // For conditional writes, check existence via get() so expired entries\n // are treated as missing (get() handles expiry cleanup). The raw `cache.get`\n // node lookup would let stale entries block onConflict: \"create\".\n let existingNode = this.cache.get(parsedKey);\n if (existingNode && existingNode.isExpired) {\n this.removeNode(existingNode);\n this.cache.delete(parsedKey);\n existingNode = undefined;\n }\n\n const exists = Boolean(existingNode);\n\n if (onConflict === \"create\" && exists) {\n const result: CacheSetResult = {\n wasSet: false,\n existing: existingNode!.value,\n };\n return result;\n }\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n if (existingNode) {\n existingNode.value = value;\n if (ttl && ttl !== Infinity) {\n existingNode.expiresAt = Date.now() + ttl * 1000;\n } else {\n existingNode.expiresAt = undefined;\n }\n existingNode.staleAt = staleAt;\n if (vector) {\n existingNode.vector = vector.slice();\n }\n\n this.moveHead(existingNode);\n } else {\n const newNode = new CacheNode(parsedKey, value, ttl);\n newNode.staleAt = staleAt;\n if (vector) {\n newNode.vector = vector.slice();\n }\n\n this.cache.set(parsedKey, newNode);\n\n this.addNode(newNode);\n if (this.cache.size > this.capacity) {\n this.removeTail();\n }\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * Move the node to the head\n */\n protected moveHead(node: CacheNode) {\n this.removeNode(node);\n this.addNode(node);\n }\n\n /**\n * Remove the node from the cache\n */\n protected removeNode(node: CacheNode) {\n node.prev!.next = node.next;\n node.next!.prev = node.prev;\n }\n\n /**\n * Add the node to the head\n */\n protected addNode(node: CacheNode) {\n node.next = this.head.next;\n node.prev = this.head;\n this.head.next!.prev = node;\n this.head.next = node;\n }\n\n /**\n * Remove the tail node\n */\n protected removeTail() {\n const node = this.tail.prev!;\n\n this.removeNode(node);\n\n this.cache.delete(node.key);\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\n * Returns `null` for missing or expired nodes — `swr()` consumes this\n * to branch on freshness without going through `get()`'s clone-and-emit\n * path.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const node = this.cache.get(parsedKey);\n\n if (!node || node.isExpired) {\n return null;\n }\n\n return {\n data: node.value,\n expiresAt: node.expiresAt,\n staleAt: node.staleAt,\n };\n }\n\n /**\n * Live node for `parsedKey`, or `undefined` when missing / expired (an\n * expired node is unlinked). Synchronous.\n */\n protected liveNode(parsedKey: string): CacheNode | undefined {\n const node = this.cache.get(parsedKey);\n\n if (!node) {\n return undefined;\n }\n\n if (node.isExpired) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n\n return undefined;\n }\n\n return node;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * node keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const parsedKey = this.parseKey(key);\n const node = this.liveNode(parsedKey);\n const current = node ? node.value : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (node) {\n node.value = newValue;\n if (\n options.ttl !== undefined &&\n !Number.isFinite(node.expiresAt) &&\n Number.isFinite(ttl) &&\n ttl > 0\n ) {\n node.expiresAt = Date.now() + ttl * 1000;\n }\n this.moveHead(node);\n } else {\n const newNode = new CacheNode(parsedKey, newValue, ttl);\n\n this.cache.set(parsedKey, newNode);\n this.addNode(newNode);\n\n if (this.cache.size > this.capacity) {\n this.removeTail();\n }\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and unlink run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const node = this.liveNode(parsedKey);\n\n if (!node) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.removeNode(node);\n this.cache.delete(parsedKey);\n\n const value =\n node.value !== null && typeof node.value === \"object\"\n ? structuredClone(node.value)\n : node.value;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (!node) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n // Check if expired\n if (node.isExpired) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n this.log(\"expired\", parsedKey);\n // Emit expired event\n await this.emit(\"expired\", { key: parsedKey });\n // Also emit miss since we're returning null\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.moveHead(node);\n\n this.log(\"fetched\", parsedKey);\n\n const value = node.value;\n\n // Apply cloning for immutability protection\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n try {\n const clonedValue = structuredClone(value);\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: clonedValue });\n return clonedValue;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\n throw error;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (node) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n }\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n *\n * When a `globalPrefix` is configured, `flush` scopes itself to that prefix\n * so multi-tenant caches don't accidentally wipe sibling tenants. Without\n * a prefix, clears everything.\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.cache.clear();\n this.tagIndex.clear();\n this.init();\n }\n\n this.log(\"flushed\");\n\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every cached node that carries a\n * vector. Suitable for development and small in-memory knowledge bases —\n * not for production beyond ~10k entries.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, node] of this.cache) {\n if (!node.vector) continue;\n if (node.isExpired) continue;\n if (tagFilter && !tagFilter.has(parsedKey)) continue;\n\n const score = cosineSimilarity(vector, node.vector);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n // Clone object values to match get() semantics.\n let value: any = node.value;\n if (value !== null && value !== undefined) {\n const t = typeof value;\n if (t !== \"string\" && t !== \"number\" && t !== \"boolean\") {\n value = structuredClone(value);\n }\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * Get lru capacity\n */\n public get capacity() {\n return this.options.capacity || 1000;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n"],"mappings":"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"}
@@ -99,7 +99,9 @@ declare class MemoryCacheDriver extends BaseCacheDriver<MemoryCacheDriver, Memor
99
99
  * concurrent increments in this process never lose an update. An existing
100
100
  * entry keeps its expiry; a missing key starts from 0 with the default TTL.
101
101
  */
102
- increment(key: CacheKey, value?: number): Promise<number>;
102
+ increment(key: CacheKey, value?: number, options?: {
103
+ ttl?: CacheTtl;
104
+ }): Promise<number>;
103
105
  /**
104
106
  * {@inheritdoc}
105
107
  *
@@ -1 +1 @@
1
- {"version":3,"file":"memory-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts"],"mappings":";;;;;cAkCa,iBAAA,SACH,eAAA,CAAgB,iBAAA,EAAmB,kBAAA,aAChC,WAAA,CAAY,iBAAA,EAAmB,kBAAA;;AAF5C;;EAOS,IAAA;EANiB;;;;;EAAA,UAad,OAAA,EAAS,GAAA,SAAY,SAAA;EAMG;;;;EAAA,IAAvB,IAAA,IAAQ,MAAA,SAAe,SAAA;EA2Bd;;;;EAAA,UAnBV,MAAA,EAAQ,GAAA;EA0KX;;;EAAA,UArKG,eAAA,GAAkB,MAAA,CAAO,OAAA;EAqOC;;;;;;EAAA,UA7N1B,WAAA,EAAa,GAAA;EA6SO;;;;EAAA,UAvSpB,QAAA,EAAU,gBAAA;EAkVa;;;;EAyBU;;;EA7VpC,YAAA;EA2WmC;;;EAAA,UApUhC,SAAA,CAAU,SAAA;EAmYT;;;;EAAA,UAzXD,QAAA,CAAS,SAAA,WAAoB,SAAA;EA5GjB;;;EAAA,UA8HZ,KAAA,CAAM,SAAA,UAAmB,KAAA,EAAO,SAAA;EA/HC;;;;;;EAAA,UA4IjC,iBAAA;EA/HqB;;;;;EAAA,UAsIrB,MAAA,CAAO,SAAA,UAAmB,KAAA,EAAO,SAAA;EAnHjC;;;EAoIG,eAAA,CAAgB,SAAA,WAAiB,OAAA;EA5HvB;;;;;;EA4JV,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EA3FO;;;;;;EAAA,UAwJM,cAAA,CAAe,GAAA,EAAK,QAAA,EAAU,QAAA,YAAoB,OAAA;EAzHxD;;;;;;;EA6IG,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EArF7C;;;;;;EA0HA,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;EAtH/B;;;EA6IU,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAhFgB;;;;;EAAA,UA8G9B,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EA1FS;;;EAuG7C,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EAlEC;;;;;EAoFrB,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EA/B5B;;;EAsCjB,UAAA,CAAW,MAAA,EAAQ,QAAA,GAAW,OAAA;EAzB9B;;;EAgCA,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAdhD;;;EAqBA,SAAA,CAAU,MAAA,EAAQ,QAAA,GAAW,OAAA;EArBgB;;;EA4B7C,KAAA,IAAK,OAAA;EArByB;;;;EAAA,UA0CjC,cAAA,CAAe,YAAA;EAnCoC;;;;;;;;;;EAmEhD,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;EAFzB;;;EA2CW,UAAA,IAAU,OAAA;AAAA"}
1
+ {"version":3,"file":"memory-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts"],"mappings":";;;;;cAkCa,iBAAA,SACH,eAAA,CAAgB,iBAAA,EAAmB,kBAAA,aAChC,WAAA,CAAY,iBAAA,EAAmB,kBAAA;;AAF5C;;EAOS,IAAA;EANiB;;;;;EAAA,UAad,OAAA,EAAS,GAAA,SAAY,SAAA;EAMG;;;;EAAA,IAAvB,IAAA,IAAQ,MAAA,SAAe,SAAA;EA2Bd;;;;EAAA,UAnBV,MAAA,EAAQ,GAAA;EA0KX;;;EAAA,UArKG,eAAA,GAAkB,MAAA,CAAO,OAAA;EAsOC;;;;;;EAAA,UA9N1B,WAAA,EAAa,GAAA;EA6TD;;;;EAAA,UAvTZ,QAAA,EAAU,gBAAA;EAkWK;;;;EAyBO;;;EA7WzB,YAAA;EA2XwB;;;EAAA,UApVrB,SAAA,CAAU,SAAA;EAmZO;;;;EAAA,UAzYjB,QAAA,CAAS,SAAA,WAAoB,SAAA;EA5G5B;;;EAAA,UA8HD,KAAA,CAAM,SAAA,UAAmB,KAAA,EAAO,SAAA;EA/HlB;;;;;;EAAA,UA4Id,iBAAA;EA/HS;;;;;EAAA,UAsIT,MAAA,CAAO,SAAA,UAAmB,KAAA,EAAO,SAAA;EAxHzB;;;EAyIL,eAAA,CAAgB,SAAA,WAAiB,OAAA;EA5HpC;;;;;;EA4JG,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EArGiB;;;;;;EAAA,UAmKJ,cAAA,CAAe,GAAA,EAAK,QAAA,EAAU,QAAA,YAAoB,OAAA;EAvI/B;;;;;;;EA2JtB,SAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,WACA,OAAA;IAAW,GAAA,GAAM,QAAA;EAAA,IAChB,OAAA;EAzFI;;;;;;EAyIM,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;EAxElB;;;EA+FH,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EA/FoC;;;;;EAAA,UA6HlD,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAtG9B;;;EAmHN,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EAlEV;;;;;EAoFV,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EA7D5B;;;EAoEjB,UAAA,CAAW,MAAA,EAAQ,QAAA,GAAW,OAAA;EAtCF;;;EA6C5B,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAhCzC;;;EAuCP,SAAA,CAAU,MAAA,EAAQ,QAAA,GAAW,OAAA;EArBtB;;;EA4BP,KAAA,IAAK,OAAA;EArBc;;;;EAAA,UA0CtB,cAAA,CAAe,YAAA;EAnCF;;;;;;;;;;EAmEV,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;EAHd;;;EA4CA,UAAA,IAAU,OAAA;AAAA"}
@@ -175,7 +175,8 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
175
175
  * concurrent increments in this process never lose an update. An existing
176
176
  * entry keeps its expiry; a missing key starts from 0 with the default TTL.
177
177
  */
178
- async increment(key, value = 1) {
178
+ async increment(key, value = 1, options = {}) {
179
+ const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
179
180
  const parsedKey = this.parseKey(key);
180
181
  const entry = this.readLive(parsedKey);
181
182
  const current = entry ? entry.data : 0;
@@ -183,9 +184,14 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
183
184
  const newValue = current + value;
184
185
  if (entry) {
185
186
  entry.data = newValue;
187
+ if (options.ttl !== void 0 && !Number.isFinite(entry.expiresAt) && Number.isFinite(ttl) && ttl > 0) {
188
+ entry.ttl = ttl;
189
+ entry.expiresAt = Date.now() + ttl * 1e3;
190
+ this.expiry.set(parsedKey, entry.expiresAt);
191
+ }
186
192
  this.touch(parsedKey, entry);
187
193
  } else {
188
- const data = this.prepareDataForStorage(newValue, this.ttl);
194
+ const data = this.prepareDataForStorage(newValue, ttl);
189
195
  this.entries.set(parsedKey, data);
190
196
  if (data.expiresAt !== void 0 && Number.isFinite(data.expiresAt)) this.expiry.set(parsedKey, data.expiresAt);
191
197
  this.enforceMaxSize(parsedKey);
@@ -193,7 +199,7 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
193
199
  await this.emit("set", {
194
200
  key: parsedKey,
195
201
  value: newValue,
196
- ttl: entry?.ttl ?? this.ttl
202
+ ttl: entry?.ttl ?? ttl
197
203
  });
198
204
  return newValue;
199
205
  }
@@ -1 +1 @@
1
- {"version":3,"file":"memory-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n MemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity, parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * Clone non-primitive values so cached state can't be mutated through\n * references held by callers. Primitives are immutable and returned as-is.\n */\nfunction cloneValue<T>(value: T): T {\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n return value;\n }\n\n return structuredClone(value);\n}\n\nexport class MemoryCacheDriver\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memory\";\n\n /**\n * Flat storage keyed by the parsed key. Insertion order doubles as LRU\n * order (least recently used first) — accessed entries are deleted and\n * re-inserted.\n */\n protected entries: Map<string, CacheData> = new Map();\n\n /**\n * Read-only object view of the stored entries, keyed by parsed key.\n * Kept for introspection; the values are the live stored wrappers.\n */\n public get data(): Record<string, CacheData> {\n return Object.fromEntries(this.entries);\n }\n\n /**\n * Expiry records for entries with a FINITE ttl only (parsed key => expiresAt ms).\n * The sweep iterates just these.\n */\n protected expiry: Map<string, number> = new Map();\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\n * `remove`, `flush`, expiry, namespace clear, overwrite without a vector,\n * and LRU eviction.\n */\n protected vectorIndex: Map<string, number[]> = new Map();\n\n /**\n * Tag index, kept apart from `entries`: never evicted by `maxSize`, never\n * swept by TTL, and updated synchronously.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.startCleanup();\n }\n\n /**\n * Start the cleanup process for entries that carry a finite ttl\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, expiresAt] of this.expiry) {\n if (expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n // the entry may have been rewritten/slid since the snapshot\n const expiresAt = this.expiry.get(key);\n\n if (expiresAt === undefined || expiresAt > now) {\n continue;\n }\n\n this.dropEntry(key);\n\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * Delete an entry and every side record (expiry, vector). Synchronous.\n */\n protected dropEntry(parsedKey: string) {\n this.entries.delete(parsedKey);\n this.expiry.delete(parsedKey);\n this.vectorIndex.delete(parsedKey);\n }\n\n /**\n * Raw, synchronous read of a live entry. An expired entry is deleted and\n * reported as missing. Never slides TTLs, never emits, never touches LRU order.\n */\n protected readLive(parsedKey: string): CacheData | undefined {\n const entry = this.entries.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.dropEntry(parsedKey);\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Mark an entry as most recently used (only tracked when `maxSize` is set).\n */\n protected touch(parsedKey: string, entry: CacheData) {\n if (!this.options.maxSize) return;\n\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, entry);\n }\n\n /**\n * When true, every live read slides the entry's expiration forward by its\n * ttl (the `memoryExtended` driver). Set in a subclass constructor rather\n * than redeclared, so subclasses stay structurally compatible with this\n * class (the `client` type relies on that).\n */\n protected slidingExpiration = false;\n\n /**\n * Invoked on a live read hit, before the value is returned. Runs only from\n * `get()` after the expiry check, so an expired entry is never resurrected,\n * and never from the internal existence check used by `onConflict`.\n */\n protected onRead(parsedKey: string, entry: CacheData) {\n if (!this.slidingExpiration) {\n return;\n }\n\n const rawTtl = entry.ttl ?? this.options.ttl;\n const ttl = rawTtl !== undefined ? parseTtl(rawTtl) : undefined;\n\n if (ttl && Number.isFinite(ttl)) {\n entry.expiresAt = this.getExpiresAt(ttl);\n this.expiry.set(parsedKey, entry.expiresAt as number);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.log(\"clearing\", namespace);\n\n namespace = this.parseKey(namespace);\n\n this.tagIndex.removeNamespace(namespace);\n\n if (namespace === \"\") {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n } else {\n const prefix = namespace + \".\";\n\n for (const key of [...this.entries.keys()]) {\n if (key === namespace || key.startsWith(prefix)) {\n this.dropEntry(key);\n }\n }\n }\n\n this.log(\"cleared\", namespace);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The conditional check and the write run with no `await` in between, so\n * `onConflict` decisions are atomic within the process.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // expired entries count as missing (readLive deletes them)\n const existing = onConflict === \"create\" || onConflict === \"update\" ? this.readLive(parsedKey) : undefined;\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: cloneValue(existing.data) };\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(cloneValue(value), ttl, staleAt);\n\n // delete + set moves the key to the most-recent position\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n } else {\n this.expiry.delete(parsedKey);\n }\n\n if (vector) {\n this.vectorIndex.set(parsedKey, vector.slice());\n } else {\n this.vectorIndex.delete(parsedKey);\n }\n\n this.enforceMaxSize(parsedKey);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The check and the delete run synchronously (no `await` between them), so\n * the compare-and-delete is atomic within the process.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry || entry.data !== expected) {\n return false;\n }\n\n await this.remove(key);\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * entry keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n this.touch(parsedKey, entry);\n } else {\n const data = this.prepareDataForStorage(newValue, this.ttl);\n\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n }\n\n this.enforceMaxSize(parsedKey);\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: entry?.ttl ?? this.ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and delete run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.dropEntry(parsedKey);\n\n const value = cloneValue(entry.data);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.onRead(parsedKey, entry);\n this.touch(parsedKey, entry);\n\n const result = await this.parseCachedData(parsedKey, entry);\n\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: result });\n\n return result;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper (cloned), including `staleAt` metadata.\n * Returns `null` for missing or expired entries so the SWR flow can branch\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const entry = this.readLive(this.parseKey(key));\n\n if (!entry) {\n return null;\n }\n\n return { ...entry, data: cloneValue(entry.data) };\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n this.dropEntry(parsedKey);\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set — concurrent tagged writes never drop members.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n this.tagIndex.clear();\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * Evict least recently used entries (Map insertion order) until the size is\n * within `maxSize`. Never evicts `protectedKey` (the key just written).\n */\n protected enforceMaxSize(protectedKey: string) {\n const maxSize = this.options.maxSize;\n\n if (!maxSize) {\n return;\n }\n\n for (const lruKey of [...this.entries.keys()]) {\n if (this.entries.size <= maxSize) {\n break;\n }\n\n if (lruKey === protectedKey) {\n continue;\n }\n\n this.log(\"removing\", lruKey);\n this.dropEntry(lruKey);\n this.log(\"removed\", lruKey);\n }\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every entry that was written with\n * `set({ vector })`. Suitable for development and small in-memory knowledge\n * bases — not for production beyond ~10k entries. Use the `pg` driver\n * (with pgvector) or `redis` (with RediSearch) at scale.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, stored] of [...this.vectorIndex]) {\n if (tagFilter && !tagFilter.has(parsedKey)) {\n continue;\n }\n\n // Read the entry directly by its already-parsed key — this.get() would\n // parse it again and apply globalPrefix twice, missing every entry.\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n continue;\n }\n\n const value: any = cloneValue(entry.data);\n\n const score = cosineSimilarity(vector, stored);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"memory-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n MemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity, parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * Clone non-primitive values so cached state can't be mutated through\n * references held by callers. Primitives are immutable and returned as-is.\n */\nfunction cloneValue<T>(value: T): T {\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n return value;\n }\n\n return structuredClone(value);\n}\n\nexport class MemoryCacheDriver\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memory\";\n\n /**\n * Flat storage keyed by the parsed key. Insertion order doubles as LRU\n * order (least recently used first) — accessed entries are deleted and\n * re-inserted.\n */\n protected entries: Map<string, CacheData> = new Map();\n\n /**\n * Read-only object view of the stored entries, keyed by parsed key.\n * Kept for introspection; the values are the live stored wrappers.\n */\n public get data(): Record<string, CacheData> {\n return Object.fromEntries(this.entries);\n }\n\n /**\n * Expiry records for entries with a FINITE ttl only (parsed key => expiresAt ms).\n * The sweep iterates just these.\n */\n protected expiry: Map<string, number> = new Map();\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\n * `remove`, `flush`, expiry, namespace clear, overwrite without a vector,\n * and LRU eviction.\n */\n protected vectorIndex: Map<string, number[]> = new Map();\n\n /**\n * Tag index, kept apart from `entries`: never evicted by `maxSize`, never\n * swept by TTL, and updated synchronously.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.startCleanup();\n }\n\n /**\n * Start the cleanup process for entries that carry a finite ttl\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, expiresAt] of this.expiry) {\n if (expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n // the entry may have been rewritten/slid since the snapshot\n const expiresAt = this.expiry.get(key);\n\n if (expiresAt === undefined || expiresAt > now) {\n continue;\n }\n\n this.dropEntry(key);\n\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * Delete an entry and every side record (expiry, vector). Synchronous.\n */\n protected dropEntry(parsedKey: string) {\n this.entries.delete(parsedKey);\n this.expiry.delete(parsedKey);\n this.vectorIndex.delete(parsedKey);\n }\n\n /**\n * Raw, synchronous read of a live entry. An expired entry is deleted and\n * reported as missing. Never slides TTLs, never emits, never touches LRU order.\n */\n protected readLive(parsedKey: string): CacheData | undefined {\n const entry = this.entries.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.dropEntry(parsedKey);\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Mark an entry as most recently used (only tracked when `maxSize` is set).\n */\n protected touch(parsedKey: string, entry: CacheData) {\n if (!this.options.maxSize) return;\n\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, entry);\n }\n\n /**\n * When true, every live read slides the entry's expiration forward by its\n * ttl (the `memoryExtended` driver). Set in a subclass constructor rather\n * than redeclared, so subclasses stay structurally compatible with this\n * class (the `client` type relies on that).\n */\n protected slidingExpiration = false;\n\n /**\n * Invoked on a live read hit, before the value is returned. Runs only from\n * `get()` after the expiry check, so an expired entry is never resurrected,\n * and never from the internal existence check used by `onConflict`.\n */\n protected onRead(parsedKey: string, entry: CacheData) {\n if (!this.slidingExpiration) {\n return;\n }\n\n const rawTtl = entry.ttl ?? this.options.ttl;\n const ttl = rawTtl !== undefined ? parseTtl(rawTtl) : undefined;\n\n if (ttl && Number.isFinite(ttl)) {\n entry.expiresAt = this.getExpiresAt(ttl);\n this.expiry.set(parsedKey, entry.expiresAt as number);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.log(\"clearing\", namespace);\n\n namespace = this.parseKey(namespace);\n\n this.tagIndex.removeNamespace(namespace);\n\n if (namespace === \"\") {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n } else {\n const prefix = namespace + \".\";\n\n for (const key of [...this.entries.keys()]) {\n if (key === namespace || key.startsWith(prefix)) {\n this.dropEntry(key);\n }\n }\n }\n\n this.log(\"cleared\", namespace);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The conditional check and the write run with no `await` in between, so\n * `onConflict` decisions are atomic within the process.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // expired entries count as missing (readLive deletes them)\n const existing =\n onConflict === \"create\" || onConflict === \"update\" ? this.readLive(parsedKey) : undefined;\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: cloneValue(existing.data) };\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(cloneValue(value), ttl, staleAt);\n\n // delete + set moves the key to the most-recent position\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n } else {\n this.expiry.delete(parsedKey);\n }\n\n if (vector) {\n this.vectorIndex.set(parsedKey, vector.slice());\n } else {\n this.vectorIndex.delete(parsedKey);\n }\n\n this.enforceMaxSize(parsedKey);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The check and the delete run synchronously (no `await` between them), so\n * the compare-and-delete is atomic within the process.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry || entry.data !== expected) {\n return false;\n }\n\n await this.remove(key);\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * entry keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n if (\n options.ttl !== undefined &&\n !Number.isFinite(entry.expiresAt) &&\n Number.isFinite(ttl) &&\n ttl > 0\n ) {\n entry.ttl = ttl;\n entry.expiresAt = Date.now() + ttl * 1000;\n this.expiry.set(parsedKey, entry.expiresAt);\n }\n this.touch(parsedKey, entry);\n } else {\n const data = this.prepareDataForStorage(newValue, ttl);\n\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n }\n\n this.enforceMaxSize(parsedKey);\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: entry?.ttl ?? ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and delete run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.dropEntry(parsedKey);\n\n const value = cloneValue(entry.data);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.onRead(parsedKey, entry);\n this.touch(parsedKey, entry);\n\n const result = await this.parseCachedData(parsedKey, entry);\n\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: result });\n\n return result;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper (cloned), including `staleAt` metadata.\n * Returns `null` for missing or expired entries so the SWR flow can branch\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const entry = this.readLive(this.parseKey(key));\n\n if (!entry) {\n return null;\n }\n\n return { ...entry, data: cloneValue(entry.data) };\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n this.dropEntry(parsedKey);\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set — concurrent tagged writes never drop members.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n this.tagIndex.clear();\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * Evict least recently used entries (Map insertion order) until the size is\n * within `maxSize`. Never evicts `protectedKey` (the key just written).\n */\n protected enforceMaxSize(protectedKey: string) {\n const maxSize = this.options.maxSize;\n\n if (!maxSize) {\n return;\n }\n\n for (const lruKey of [...this.entries.keys()]) {\n if (this.entries.size <= maxSize) {\n break;\n }\n\n if (lruKey === protectedKey) {\n continue;\n }\n\n this.log(\"removing\", lruKey);\n this.dropEntry(lruKey);\n this.log(\"removed\", lruKey);\n }\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every entry that was written with\n * `set({ vector })`. Suitable for development and small in-memory knowledge\n * bases — not for production beyond ~10k entries. Use the `pg` driver\n * (with pgvector) or `redis` (with RediSearch) at scale.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, stored] of [...this.vectorIndex]) {\n if (tagFilter && !tagFilter.has(parsedKey)) {\n continue;\n }\n\n // Read the entry directly by its already-parsed key — this.get() would\n // parse it again and apply globalPrefix twice, missing every entry.\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n continue;\n }\n\n const value: any = cloneValue(entry.data);\n\n const score = cosineSimilarity(vector, stored);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n"],"mappings":"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"}
@@ -105,7 +105,9 @@ declare class MockCacheDriver extends BaseCacheDriver<MockCacheDriver, MockCache
105
105
  * Synchronous read-add-write (atomic within the process). Keeps an existing
106
106
  * entry's expiry; a missing key starts from 0 with the default TTL.
107
107
  */
108
- increment(key: CacheKey, value?: number): Promise<number>;
108
+ increment(key: CacheKey, value?: number, options?: {
109
+ ttl?: CacheTtl;
110
+ }): Promise<number>;
109
111
  /**
110
112
  * Synchronous read-and-delete: a value is handed out exactly once.
111
113
  */
@@ -1 +1 @@
1
- {"version":3,"file":"mock-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts"],"mappings":";;;;;;;AAuDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,gBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,gBAAA;EA6SqB;;;EAxStD,IAAA;EA4UmC;;;EAvUnC,OAAA,EAAS,gBAAA;EAVL;;;;EAAA,SAgBK,OAAA,EAAS,GAAA,SAAY,SAAA;EAjBI;;;EAAA,UAsB/B,QAAA,EAAU,gBAAA;EAhBb;;;;;EAAA,SAuBS,OAAA,EAAS,SAAA;EAPf;;;;EAaG,OAAA,IAAW,OAAA;EAAA;;;EAQX,UAAA,IAAc,OAAA;EASE;;;;EAAhB,eAAA,CAAgB,SAAA,WAAoB,OAAA;EAyB/C;;;;EAFW,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAoDO;;;EAAA,UAAA,UAAA,IAAc,KAAA,EAAO,CAAA,GAAI,CAAA;EAAA;;;;EAYtB,GAAA,UAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAAR;;;;EAAA,UAkChC,SAAA,CAAU,SAAA,WAAoB,SAAA;EAoB3B;;;;EAAA,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EA4B7C;;;EAAA,IAAA,UAAc,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAAR;;;;;EAAA,UA4B3B,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAAA;;;EAkBpC,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAAA;;;;;EAiBvB,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAO7C;;;EAAA,UAAA,CAAW,MAAA,EAAQ,QAAA,GAAW,OAAA;EAO9B;;;EAAA,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAAA;;;EAOhD,SAAA,CAAU,MAAA,EAAQ,QAAA,GAAW,OAAA;EAAA;;;;EAQ7B,KAAA,IAAS,OAAA;EAqBoB;;;;;;;;;;EAAnC,SAAA,CAAU,SAAA,UAAmB,GAAA,GAAM,QAAA;EA+CxC;;AAAoB;;;;;EA5Bf,SAAA,UAAmB,GAAA,EAAK,QAAA,GAAW,CAAA;;;;;EAenC,KAAA;;;;;YAUG,UAAA,CACR,SAAA,UACA,GAAA,sBACA,IAAA;AAAA"}
1
+ {"version":3,"file":"mock-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts"],"mappings":";;;;;;;AAwDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,gBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,gBAAA;EA0TT;;;EArTxB,IAAA;EAoUe;;;EA/Tf,OAAA,EAAS,gBAAA;EAXR;;;;EAAA,SAiBQ,OAAA,EAAS,GAAA,SAAY,SAAA;EAjBb;;;EAAA,UAsBd,QAAA,EAAU,gBAAA;EArBoB;;;;;EAAA,SA4BxB,OAAA,EAAS,SAAA;EAZY;;;;EAkBxB,OAAA,IAAW,OAAA;EAAX;;;EAQA,UAAA,IAAc,OAAA;EASd;;;;EAAA,eAAA,CAAgB,SAAA,WAAoB,OAAA;EAwB/C;;;;EADW,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAAA;;;EAAA,UAmDO,UAAA,IAAc,KAAA,EAAO,CAAA,GAAI,CAAA;EAAX;;;;EAYX,GAAA,UAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAAxB;;;;EAAA,UAkChB,SAAA,CAAU,SAAA,WAAoB,SAAA;EAAA;;;;EAoB3B,SAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,WACA,OAAA;IAAW,GAAA,GAAM,QAAA;EAAA,IAChB,OAAA;EADD;;;EAuCW,IAAA,UAAc,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAAnB;;;;;EAAA,UA4BhB,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAAxB;;;EAkBZ,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAAX;;;;;EAiBZ,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAApB;;;EAOzB,UAAA,CAAW,MAAA,EAAQ,QAAA,GAAW,OAAA;EAAnB;;;EAOX,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAAtC;;;EAOV,SAAA,CAAU,MAAA,EAAQ,QAAA,GAAW,OAAA;EAAX;;;;EAQlB,KAAA,IAAS,OAAA;EAqBf;;;;;;;;;;EAAA,SAAA,CAAU,SAAA,UAAmB,GAAA,GAAM,QAAA;EA0CrB;;;;AAAgE;;;EAzB9E,SAAA,UAAmB,GAAA,EAAK,QAAA,GAAW,CAAA;;;;;EAenC,KAAA;;;;;YAUG,UAAA,CAAW,SAAA,UAAmB,GAAA,sBAAyB,IAAA;AAAA"}
@@ -1,3 +1,4 @@
1
+ import { parseTtl } from "../utils.mjs";
1
2
  import { BaseCacheDriver } from "./base-cache-driver.mjs";
2
3
  import { InMemoryTagIndex } from "./in-memory-tag-index.mjs";
3
4
  import { maskEmailsInText } from "@warlock.js/logger";
@@ -168,19 +169,25 @@ var MockCacheDriver = class extends BaseCacheDriver {
168
169
  * Synchronous read-add-write (atomic within the process). Keeps an existing
169
170
  * entry's expiry; a missing key starts from 0 with the default TTL.
170
171
  */
171
- async increment(key, value = 1) {
172
+ async increment(key, value = 1, options = {}) {
173
+ const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
172
174
  const parsedKey = this.parseKey(key);
173
175
  this.recordCall("increment", parsedKey, [value]);
174
176
  const entry = this.liveEntry(parsedKey);
175
177
  const current = entry ? entry.data : 0;
176
178
  if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);
177
179
  const newValue = current + value;
178
- if (entry) entry.data = newValue;
179
- else this.storage.set(parsedKey, this.prepareDataForStorage(newValue, this.ttl));
180
+ if (entry) {
181
+ entry.data = newValue;
182
+ if (options.ttl !== void 0 && !Number.isFinite(entry.expiresAt) && Number.isFinite(ttl) && ttl > 0) {
183
+ entry.ttl = ttl;
184
+ entry.expiresAt = Date.now() + ttl * 1e3;
185
+ }
186
+ } else this.storage.set(parsedKey, this.prepareDataForStorage(newValue, ttl));
180
187
  await this.emit("set", {
181
188
  key: parsedKey,
182
189
  value: newValue,
183
- ttl: this.ttl
190
+ ttl
184
191
  });
185
192
  return newValue;
186
193
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mock-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheCall,\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n MockCacheOptions,\n} from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * In-memory cache driver with introspection helpers, intended for use as a\n * test double in downstream packages.\n *\n * **Role.** Drop-in replacement for any real driver in test setups, with\n * extra surface that makes behavioral assertions easy: every public op gets\n * recorded into {@link MockCacheDriver.callLog}, and {@link wasCalled} /\n * {@link getStored} / {@link reset} let tests verify side effects without\n * pulling in a real Redis / Postgres / file system.\n *\n * **Responsibility.**\n * - Owns: in-memory storage backed by a `Map`, the `callLog`, TTL handling\n * via `parseCachedData`, `onConflict` policies, and tag-index storage.\n * - Does NOT own: similarity retrieval (vectors are recorded into the\n * `callLog` but `similar()` throws — use `MemoryCacheDriver` for tests\n * that need real nearest-neighbor scoring), connection lifecycle\n * (no-op `connect`/`disconnect`), or eviction (no `maxSize`).\n *\n * Register it like any other driver — sub-paths are not part of the\n * package's export convention; the same single barrel ships it next to\n * the production drivers.\n *\n * @example\n * import { cache, MockCacheDriver } from \"@warlock.js/cache\";\n *\n * beforeEach(async () => {\n * cache.setCacheConfigurations({\n * default: \"mock\",\n * drivers: { mock: MockCacheDriver },\n * options: { mock: {} },\n * });\n * await cache.init();\n * });\n *\n * it(\"invalidates the user cache after update\", async () => {\n * await userService.update(42, { name: \"Jane\" });\n *\n * const driver = cache.currentDriver as MockCacheDriver;\n * expect(driver.wasCalled(\"remove\", \"users.42\")).toBe(true);\n * });\n */\nexport class MockCacheDriver\n extends BaseCacheDriver<MockCacheDriver, MockCacheOptions>\n implements CacheDriver<MockCacheDriver, MockCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"mock\";\n\n /**\n * {@inheritdoc}\n */\n public options: MockCacheOptions = {};\n\n /**\n * Storage backing the mock — keyed by post-`parseKey` string. Public-readonly\n * so tests can introspect raw entries when {@link getStored} isn't enough.\n */\n public readonly storage: Map<string, CacheData> = new Map();\n\n /**\n * Tag index, kept apart from {@link storage} (same model as the memory driver).\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Ordered record of every public operation routed through this driver.\n * Pushed to before each op runs; tests assert via {@link wasCalled} or by\n * inspecting the array directly.\n */\n public readonly callLog: CacheCall[] = [];\n\n /**\n * Standard driver setup. Mirrors the null driver — no connection, no\n * resources to release.\n */\n public async connect(): Promise<void> {\n this.recordCall(\"connect\", undefined);\n await super.connect();\n }\n\n /**\n * Standard driver teardown.\n */\n public async disconnect(): Promise<void> {\n this.recordCall(\"disconnect\", undefined);\n await super.disconnect();\n }\n\n /**\n * Wipe everything under `namespace`. Matches `MemoryCacheDriver` semantics\n * — the namespace itself and any key with the namespace prefix is removed.\n */\n public async removeNamespace(namespace: string): Promise<void> {\n const parsed = this.parseKey(namespace);\n\n this.recordCall(\"removeNamespace\", parsed, [namespace]);\n this.log(\"clearing\", parsed);\n\n const prefix = parsed + \".\";\n\n this.tagIndex.removeNamespace(parsed);\n\n for (const key of [...this.storage.keys()]) {\n if (key === parsed || key.startsWith(prefix)) {\n this.storage.delete(key);\n }\n }\n\n this.log(\"cleared\", parsed);\n }\n\n /**\n * Standard `set` with full `onConflict` support. Honors scope-default TTL\n * via {@link BaseCacheDriver.resolveSetOptions}.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.recordCall(\"set\", parsedKey, [value, ttlOrOptions]);\n this.log(\"caching\", parsedKey);\n\n // Synchronous check + write (no await between) so onConflict is atomic.\n // Expired entries count as missing and are deleted.\n let existing = onConflict === \"create\" || onConflict === \"update\"\n ? this.storage.get(parsedKey)\n : undefined;\n\n if (existing && existing.expiresAt !== undefined && existing.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n existing = undefined;\n }\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: this.cloneValue(existing.data) };\n\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n\n return result;\n }\n\n const data = this.prepareDataForStorage(this.cloneValue(value), ttl, staleAt);\n this.storage.set(parsedKey, data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n\n return result;\n }\n\n return this;\n }\n\n /**\n * Clone non-primitive values so stored state can't be mutated by callers.\n */\n protected cloneValue<T>(value: T): T {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return value;\n }\n\n return structuredClone(value);\n }\n\n /**\n * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep\n * downstream metrics tests realistic.\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"get\", parsedKey);\n this.log(\"fetching\", parsedKey);\n\n const data = this.storage.get(parsedKey);\n\n if (!data) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = await this.parseCachedData(parsedKey, data);\n\n if (value === null) {\n // expired — the parse helper already logged + queued the cleanup\n this.storage.delete(parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.emit(\"hit\", { key: parsedKey, value });\n\n return value as T;\n }\n\n /**\n * Live entry for `parsedKey`, or `undefined` when missing / expired (an\n * expired entry is deleted). Synchronous.\n */\n protected liveEntry(parsedKey: string): CacheData | undefined {\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Synchronous read-add-write (atomic within the process). Keeps an existing\n * entry's expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"increment\", parsedKey, [value]);\n\n const entry = this.liveEntry(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n } else {\n this.storage.set(parsedKey, this.prepareDataForStorage(newValue, this.ttl));\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: this.ttl });\n\n return newValue;\n }\n\n /**\n * Synchronous read-and-delete: a value is handed out exactly once.\n */\n public async pull<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"pull\", parsedKey);\n\n const entry = this.liveEntry(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.storage.delete(parsedKey);\n\n const value = this.cloneValue(entry.data) as T;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from the in-memory `Map`,\n * including `staleAt` metadata. Returns `null` for missing or expired\n * entries — `swr()` consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return { ...entry, data: this.cloneValue(entry.data) };\n }\n\n /**\n * Standard `remove` — drops the entry and emits the `removed` event.\n */\n public async remove(key: CacheKey): Promise<void> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"remove\", parsedKey);\n this.log(\"removing\", parsedKey);\n\n this.storage.delete(parsedKey);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * Standard `flush` — wipes the entire mock store + tag index. Does NOT\n * touch the call log; use {@link reset} to clear that as well.\n */\n public async flush(): Promise<void> {\n this.recordCall(\"flush\", undefined);\n this.log(\"flushing\");\n\n this.storage.clear();\n this.tagIndex.clear();\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * Was a given operation invoked? When `key` is provided, the match is\n * post-`parseKey` so callers pass the same key shape they used at the\n * call site — strings or objects, both resolve to the same parsed key.\n *\n * @example\n * driver.wasCalled(\"set\"); // any set\n * driver.wasCalled(\"set\", \"users.42\"); // set on this specific key\n * driver.wasCalled(\"set\", { id: 42 }); // same — object key normalized\n */\n public wasCalled(operation: string, key?: CacheKey): boolean {\n if (key === undefined) {\n return this.callLog.some((call) => call.operation === operation);\n }\n\n const parsedKey = this.parseKey(key);\n\n return this.callLog.some(\n (call) => call.operation === operation && call.key === parsedKey,\n );\n }\n\n /**\n * Return the raw stored value for `key`, bypassing TTL handling and clone\n * protection. Useful when a test wants to assert on the persisted shape\n * (or assert that an entry expired without going through `get`).\n *\n * Returns `undefined` when the key isn't present.\n */\n public getStored<T = any>(key: CacheKey): T | undefined {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n return entry.data as T;\n }\n\n /**\n * Wipe everything — storage, tag index, and the call log. Pair with\n * Vitest's `beforeEach` to get clean isolation between tests.\n */\n public reset(): void {\n this.storage.clear();\n this.tagIndex.clear();\n this.callLog.length = 0;\n }\n\n /**\n * Append a row to {@link callLog}. Internal helper called by every\n * recorded op before the actual work runs.\n */\n protected recordCall(\n operation: string,\n key: string | undefined,\n args: unknown[] = [],\n ): void {\n this.callLog.push({\n operation,\n key,\n args,\n timestamp: Date.now(),\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mock-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheCall,\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n MockCacheOptions,\n} from \"../types\";\nimport { parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * In-memory cache driver with introspection helpers, intended for use as a\n * test double in downstream packages.\n *\n * **Role.** Drop-in replacement for any real driver in test setups, with\n * extra surface that makes behavioral assertions easy: every public op gets\n * recorded into {@link MockCacheDriver.callLog}, and {@link wasCalled} /\n * {@link getStored} / {@link reset} let tests verify side effects without\n * pulling in a real Redis / Postgres / file system.\n *\n * **Responsibility.**\n * - Owns: in-memory storage backed by a `Map`, the `callLog`, TTL handling\n * via `parseCachedData`, `onConflict` policies, and tag-index storage.\n * - Does NOT own: similarity retrieval (vectors are recorded into the\n * `callLog` but `similar()` throws — use `MemoryCacheDriver` for tests\n * that need real nearest-neighbor scoring), connection lifecycle\n * (no-op `connect`/`disconnect`), or eviction (no `maxSize`).\n *\n * Register it like any other driver — sub-paths are not part of the\n * package's export convention; the same single barrel ships it next to\n * the production drivers.\n *\n * @example\n * import { cache, MockCacheDriver } from \"@warlock.js/cache\";\n *\n * beforeEach(async () => {\n * cache.setCacheConfigurations({\n * default: \"mock\",\n * drivers: { mock: MockCacheDriver },\n * options: { mock: {} },\n * });\n * await cache.init();\n * });\n *\n * it(\"invalidates the user cache after update\", async () => {\n * await userService.update(42, { name: \"Jane\" });\n *\n * const driver = cache.currentDriver as MockCacheDriver;\n * expect(driver.wasCalled(\"remove\", \"users.42\")).toBe(true);\n * });\n */\nexport class MockCacheDriver\n extends BaseCacheDriver<MockCacheDriver, MockCacheOptions>\n implements CacheDriver<MockCacheDriver, MockCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"mock\";\n\n /**\n * {@inheritdoc}\n */\n public options: MockCacheOptions = {};\n\n /**\n * Storage backing the mock — keyed by post-`parseKey` string. Public-readonly\n * so tests can introspect raw entries when {@link getStored} isn't enough.\n */\n public readonly storage: Map<string, CacheData> = new Map();\n\n /**\n * Tag index, kept apart from {@link storage} (same model as the memory driver).\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Ordered record of every public operation routed through this driver.\n * Pushed to before each op runs; tests assert via {@link wasCalled} or by\n * inspecting the array directly.\n */\n public readonly callLog: CacheCall[] = [];\n\n /**\n * Standard driver setup. Mirrors the null driver — no connection, no\n * resources to release.\n */\n public async connect(): Promise<void> {\n this.recordCall(\"connect\", undefined);\n await super.connect();\n }\n\n /**\n * Standard driver teardown.\n */\n public async disconnect(): Promise<void> {\n this.recordCall(\"disconnect\", undefined);\n await super.disconnect();\n }\n\n /**\n * Wipe everything under `namespace`. Matches `MemoryCacheDriver` semantics\n * — the namespace itself and any key with the namespace prefix is removed.\n */\n public async removeNamespace(namespace: string): Promise<void> {\n const parsed = this.parseKey(namespace);\n\n this.recordCall(\"removeNamespace\", parsed, [namespace]);\n this.log(\"clearing\", parsed);\n\n const prefix = parsed + \".\";\n\n this.tagIndex.removeNamespace(parsed);\n\n for (const key of [...this.storage.keys()]) {\n if (key === parsed || key.startsWith(prefix)) {\n this.storage.delete(key);\n }\n }\n\n this.log(\"cleared\", parsed);\n }\n\n /**\n * Standard `set` with full `onConflict` support. Honors scope-default TTL\n * via {@link BaseCacheDriver.resolveSetOptions}.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.recordCall(\"set\", parsedKey, [value, ttlOrOptions]);\n this.log(\"caching\", parsedKey);\n\n // Synchronous check + write (no await between) so onConflict is atomic.\n // Expired entries count as missing and are deleted.\n let existing =\n onConflict === \"create\" || onConflict === \"update\" ? this.storage.get(parsedKey) : undefined;\n\n if (existing && existing.expiresAt !== undefined && existing.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n existing = undefined;\n }\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: this.cloneValue(existing.data) };\n\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n\n return result;\n }\n\n const data = this.prepareDataForStorage(this.cloneValue(value), ttl, staleAt);\n this.storage.set(parsedKey, data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n\n return result;\n }\n\n return this;\n }\n\n /**\n * Clone non-primitive values so stored state can't be mutated by callers.\n */\n protected cloneValue<T>(value: T): T {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return value;\n }\n\n return structuredClone(value);\n }\n\n /**\n * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep\n * downstream metrics tests realistic.\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"get\", parsedKey);\n this.log(\"fetching\", parsedKey);\n\n const data = this.storage.get(parsedKey);\n\n if (!data) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = await this.parseCachedData(parsedKey, data);\n\n if (value === null) {\n // expired — the parse helper already logged + queued the cleanup\n this.storage.delete(parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.emit(\"hit\", { key: parsedKey, value });\n\n return value as T;\n }\n\n /**\n * Live entry for `parsedKey`, or `undefined` when missing / expired (an\n * expired entry is deleted). Synchronous.\n */\n protected liveEntry(parsedKey: string): CacheData | undefined {\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Synchronous read-add-write (atomic within the process). Keeps an existing\n * entry's expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"increment\", parsedKey, [value]);\n\n const entry = this.liveEntry(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n if (\n options.ttl !== undefined &&\n !Number.isFinite(entry.expiresAt) &&\n Number.isFinite(ttl) &&\n ttl > 0\n ) {\n entry.ttl = ttl;\n entry.expiresAt = Date.now() + ttl * 1000;\n }\n } else {\n this.storage.set(parsedKey, this.prepareDataForStorage(newValue, ttl));\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl });\n\n return newValue;\n }\n\n /**\n * Synchronous read-and-delete: a value is handed out exactly once.\n */\n public async pull<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"pull\", parsedKey);\n\n const entry = this.liveEntry(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.storage.delete(parsedKey);\n\n const value = this.cloneValue(entry.data) as T;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from the in-memory `Map`,\n * including `staleAt` metadata. Returns `null` for missing or expired\n * entries — `swr()` consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return { ...entry, data: this.cloneValue(entry.data) };\n }\n\n /**\n * Standard `remove` — drops the entry and emits the `removed` event.\n */\n public async remove(key: CacheKey): Promise<void> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"remove\", parsedKey);\n this.log(\"removing\", parsedKey);\n\n this.storage.delete(parsedKey);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * Standard `flush` — wipes the entire mock store + tag index. Does NOT\n * touch the call log; use {@link reset} to clear that as well.\n */\n public async flush(): Promise<void> {\n this.recordCall(\"flush\", undefined);\n this.log(\"flushing\");\n\n this.storage.clear();\n this.tagIndex.clear();\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * Was a given operation invoked? When `key` is provided, the match is\n * post-`parseKey` so callers pass the same key shape they used at the\n * call site — strings or objects, both resolve to the same parsed key.\n *\n * @example\n * driver.wasCalled(\"set\"); // any set\n * driver.wasCalled(\"set\", \"users.42\"); // set on this specific key\n * driver.wasCalled(\"set\", { id: 42 }); // same — object key normalized\n */\n public wasCalled(operation: string, key?: CacheKey): boolean {\n if (key === undefined) {\n return this.callLog.some((call) => call.operation === operation);\n }\n\n const parsedKey = this.parseKey(key);\n\n return this.callLog.some((call) => call.operation === operation && call.key === parsedKey);\n }\n\n /**\n * Return the raw stored value for `key`, bypassing TTL handling and clone\n * protection. Useful when a test wants to assert on the persisted shape\n * (or assert that an entry expired without going through `get`).\n *\n * Returns `undefined` when the key isn't present.\n */\n public getStored<T = any>(key: CacheKey): T | undefined {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n return entry.data as T;\n }\n\n /**\n * Wipe everything — storage, tag index, and the call log. Pair with\n * Vitest's `beforeEach` to get clean isolation between tests.\n */\n public reset(): void {\n this.storage.clear();\n this.tagIndex.clear();\n this.callLog.length = 0;\n }\n\n /**\n * Append a row to {@link callLog}. Internal helper called by every\n * recorded op before the actual work runs.\n */\n protected recordCall(operation: string, key: string | undefined, args: unknown[] = []): void {\n this.callLog.push({\n operation,\n key,\n args,\n timestamp: Date.now(),\n });\n }\n}\n"],"mappings":"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"}
@@ -157,7 +157,9 @@ declare class PgCacheDriver extends BaseCacheDriver<PgClientLike, PgCacheOptions
157
157
  * keeps its `expires_at`; a missing or expired row restarts from `value`
158
158
  * with the driver default TTL. A non-numeric live value is rejected.
159
159
  */
160
- increment(key: CacheKey, value?: number): Promise<number>;
160
+ increment(key: CacheKey, value?: number, options?: {
161
+ ttl?: CacheTtl;
162
+ }): Promise<number>;
161
163
  /**
162
164
  * {@inheritdoc}
163
165
  *
@@ -1 +1 @@
1
- {"version":3,"file":"pg-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/pg-cache-driver.ts"],"mappings":";;;;;;AAuDA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,aAAA,SACH,eAAA,CAAgB,YAAA,EAAc,cAAA,aAC3B,WAAA,CAAY,YAAA,EAAc,cAAA;EAsfI;;;EAjflC,IAAA;EAqlB2B;;;;EAAA,UA/kBxB,WAAA;EAqnB8B;;;;;EA9mBjC,UAAA,CAAW,OAAA,EAAS,cAAA;EAotBmB;;;;;;EAAA,UA9qB9B,iBAAA,IAAqB,OAAA;EAxD1B;;;;;EAAA,UAqFD,YAAA,CAAa,MAAA;EArFZ;;;;EAAA,cA6FG,KAAA;EA3EP;;;;EAAA,cAmFO,QAAA,IAAY,YAAA;EAhBhB;;;;;EAAA,UAyBM,cAAA,CAAe,GAAA,EAAK,QAAA,EAAU,QAAA,YAAoB,OAAA;EAA9B;;;;;;;;;;;;;EAgCvB,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAiEhD;;;;;;;EAhCG,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EA+GzC;;;;;EAAA,UA/EV,YAAA,CAAa,GAAA;EAsGrB;;;;EA1FW,KAAA,CAAM,KAAA,YAAe,OAAA;EAmOrB;;;EAAA,UAtNH,UAAA;EA8QM;;;;;;;EA/PT,MAAA;EAgUqB;;;EAzRf,OAAA,IAAO,OAAA;EA4TP;;;;;EA/SA,UAAA,IAAU,OAAA;EAoVhB;;;EA5UM,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAyU0B;;;EAlMhB,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAmMjB;;;;;EAAA,UA3IG,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EA+NxB;;;;EAAA,UAlLf,UAAA,CAAW,KAAA;EAmMyB;;;;;;;;EA/KjC,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EA2O/C;;;AAAiB;;;EAxMf,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;;;;;;;;;;;;;EAoCrB,MAAA,UACX,GAAA,EAAK,QAAA,EACL,EAAA,GAAK,OAAA,EAAS,CAAA,YAAa,CAAA,UAAW,OAAA,CAAQ,CAAA,UAC9C,OAAA;IAAW,GAAA,GAAM,QAAA;EAAA,IAChB,OAAA,CAAQ,CAAA;;;;EAmFE,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;;;;EAiBpB,eAAA,CAAgB,SAAA,WAAiB,OAAA;;;;;;;;EA4BjC,KAAA,IAAK,OAAA;;;;;;;;;;;;;;;;;EA6BL,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;AAAA"}
1
+ {"version":3,"file":"pg-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/pg-cache-driver.ts"],"mappings":";;;;;;AAuDA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,aAAA,SACH,eAAA,CAAgB,YAAA,EAAc,cAAA,aAC3B,WAAA,CAAY,YAAA,EAAc,cAAA;EAsfI;;;EAjflC,IAAA;EAgmBgB;;;;EAAA,UA1lBb,WAAA;EAgoBsC;;;;;EAznBzC,UAAA,CAAW,OAAA,EAAS,cAAA;EA8sBM;;;;;;EAAA,UAxqBjB,iBAAA,IAAqB,OAAA;EAzD7B;;;;;EAAA,UAsFE,YAAA,CAAa,MAAA;EAtFe;;;;EAAA,cA8FxB,KAAA;EAlFJ;;;;EAAA,cA0FI,QAAA,IAAY,YAAA;EA7CW;;;;;EAAA,UAsDrB,cAAA,CAAe,GAAA,EAAK,QAAA,EAAU,QAAA,YAAoB,OAAA;EAAlD;;;;;;;;;;;;;EAgCH,MAAA,CAAO,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EAiCG;;;;;;;EAAhD,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,OAAA,aAAoB,OAAA;EA+GhD;;;;;EAAA,UA/EH,YAAA,CAAa,GAAA;EAqGrB;;;;EAzFW,KAAA,CAAM,KAAA,YAAe,OAAA;EA4F/B;;;EAAA,UA/EO,UAAA;EAsNoB;;;;;;;EAvMvB,MAAA;EAgUM;;;EAzRA,OAAA,IAAO,OAAA;EA4RP;;;;;EA/QA,UAAA,IAAU,OAAA;EA0TL;;;EAlTL,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAmVI;;;EA5MM,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EA6MD;;;;;EAAA,UArJb,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAsJ/C;;;;EAAA,UAzGQ,UAAA,CAAW,KAAA;EA6LD;;;;;;;;EAzKP,SAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,WACA,OAAA;IAAW,GAAA,GAAM,QAAA;EAAA,IAChB,OAAA;EAiPD;;;;;AAC0B;EAxMf,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;;;;;;;;;;;;;EAoCrB,MAAA,UACX,GAAA,EAAK,QAAA,EACL,EAAA,GAAK,OAAA,EAAS,CAAA,YAAa,CAAA,UAAW,OAAA,CAAQ,CAAA,UAC9C,OAAA;IAAW,GAAA,GAAM,QAAA;EAAA,IAChB,OAAA,CAAQ,CAAA;;;;EAmFE,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;;;;EAiBpB,eAAA,CAAgB,SAAA,WAAiB,OAAA;;;;;;;;EA4BjC,KAAA,IAAK,OAAA;;;;;;;;;;;;;;;;;EA6BL,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;AAAA"}
@@ -438,7 +438,7 @@ var PgCacheDriver = class extends BaseCacheDriver {
438
438
  * keeps its `expires_at`; a missing or expired row restarts from `value`
439
439
  * with the driver default TTL. A non-numeric live value is rejected.
440
440
  */
441
- async increment(key, value = 1) {
441
+ async increment(key, value = 1, options = {}) {
442
442
  const parsedKey = this.parseKey(key);
443
443
  const t = this.table;
444
444
  const expired = `${t}.expires_at IS NOT NULL AND ${t}.expires_at <= now()`;
@@ -447,13 +447,16 @@ var PgCacheDriver = class extends BaseCacheDriver {
447
447
  ON CONFLICT (key) DO UPDATE
448
448
  SET value = CASE WHEN ${expired} THEN EXCLUDED.value
449
449
  ELSE to_jsonb((${t}.value #>> '{}')::numeric + $2::numeric) END,
450
- expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at ELSE ${t}.expires_at END,
450
+ expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at
451
+ WHEN $4::boolean AND ${t}.expires_at IS NULL THEN EXCLUDED.expires_at
452
+ ELSE ${t}.expires_at END,
451
453
  stale_at = CASE WHEN ${expired} THEN NULL ELSE ${t}.stale_at END
452
454
  WHERE (${expired}) OR jsonb_typeof(${t}.value) = 'number'
453
455
  RETURNING value`, [
454
456
  parsedKey,
455
457
  value,
456
- this.ttlToSeconds(this.ttl)
458
+ this.ttlToSeconds(options.ttl === void 0 ? this.ttl : parseTtl(options.ttl)),
459
+ options.ttl !== void 0
457
460
  ]);
458
461
  if (rows.length === 0) throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);
459
462
  const newValue = Number(this.decodeJson(rows[0].value));