@warlock.js/cache 4.0.174 → 4.1.2

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 (213) hide show
  1. package/README.md +85 -0
  2. package/cjs/index.cjs +4088 -0
  3. package/cjs/index.cjs.map +1 -0
  4. package/esm/cache-manager.d.mts +314 -0
  5. package/esm/cache-manager.d.mts.map +1 -0
  6. package/esm/cache-manager.mjs +486 -0
  7. package/esm/cache-manager.mjs.map +1 -0
  8. package/esm/cached/auto-key.d.mts +25 -0
  9. package/esm/cached/auto-key.d.mts.map +1 -0
  10. package/esm/cached/auto-key.mjs +55 -0
  11. package/esm/cached/auto-key.mjs.map +1 -0
  12. package/esm/cached/cached.d.mts +54 -0
  13. package/esm/cached/cached.d.mts.map +1 -0
  14. package/esm/cached/cached.mjs +25 -0
  15. package/esm/cached/cached.mjs.map +1 -0
  16. package/esm/cached/index.d.mts +3 -0
  17. package/esm/cached/index.mjs +5 -0
  18. package/esm/cached/normalize-args.d.mts +51 -0
  19. package/esm/cached/normalize-args.d.mts.map +1 -0
  20. package/esm/cached/normalize-args.mjs +26 -0
  21. package/esm/cached/normalize-args.mjs.map +1 -0
  22. package/esm/drivers/base-cache-driver.d.mts +322 -0
  23. package/esm/drivers/base-cache-driver.d.mts.map +1 -0
  24. package/esm/drivers/base-cache-driver.mjs +522 -0
  25. package/esm/drivers/base-cache-driver.mjs.map +1 -0
  26. package/esm/drivers/file-cache-driver.d.mts +68 -0
  27. package/esm/drivers/file-cache-driver.d.mts.map +1 -0
  28. package/esm/drivers/file-cache-driver.mjs +174 -0
  29. package/esm/drivers/file-cache-driver.mjs.map +1 -0
  30. package/esm/drivers/index.d.mts +9 -0
  31. package/esm/drivers/index.mjs +11 -0
  32. package/esm/drivers/lru-memory-cache-driver.d.mts +136 -0
  33. package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -0
  34. package/esm/drivers/lru-memory-cache-driver.mjs +317 -0
  35. package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -0
  36. package/esm/drivers/memory-cache-driver.d.mts +112 -0
  37. package/esm/drivers/memory-cache-driver.d.mts.map +1 -0
  38. package/esm/drivers/memory-cache-driver.mjs +241 -0
  39. package/esm/drivers/memory-cache-driver.mjs.map +1 -0
  40. package/esm/drivers/memory-extended-cache-driver.d.mts +17 -0
  41. package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -0
  42. package/esm/drivers/memory-extended-cache-driver.mjs +34 -0
  43. package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -0
  44. package/esm/drivers/mock-cache-driver.d.mts +137 -0
  45. package/esm/drivers/mock-cache-driver.d.mts.map +1 -0
  46. package/esm/drivers/mock-cache-driver.mjs +226 -0
  47. package/esm/drivers/mock-cache-driver.mjs.map +1 -0
  48. package/esm/drivers/null-cache-driver.d.mts +69 -0
  49. package/esm/drivers/null-cache-driver.d.mts.map +1 -0
  50. package/esm/drivers/null-cache-driver.mjs +92 -0
  51. package/esm/drivers/null-cache-driver.mjs.map +1 -0
  52. package/esm/drivers/pg-cache-driver.d.mts +148 -0
  53. package/esm/drivers/pg-cache-driver.d.mts.map +1 -0
  54. package/esm/drivers/pg-cache-driver.mjs +437 -0
  55. package/esm/drivers/pg-cache-driver.mjs.map +1 -0
  56. package/esm/drivers/redis-cache-driver.d.mts +86 -0
  57. package/esm/drivers/redis-cache-driver.d.mts.map +1 -0
  58. package/esm/drivers/redis-cache-driver.mjs +312 -0
  59. package/esm/drivers/redis-cache-driver.mjs.map +1 -0
  60. package/esm/index.d.mts +21 -0
  61. package/esm/index.mjs +24 -0
  62. package/esm/list/index.d.mts +1 -0
  63. package/esm/list/memory-cache-list.d.mts +77 -0
  64. package/esm/list/memory-cache-list.d.mts.map +1 -0
  65. package/esm/list/memory-cache-list.mjs +119 -0
  66. package/esm/list/memory-cache-list.mjs.map +1 -0
  67. package/esm/metrics.d.mts +118 -0
  68. package/esm/metrics.d.mts.map +1 -0
  69. package/esm/metrics.mjs +197 -0
  70. package/esm/metrics.mjs.map +1 -0
  71. package/esm/scoped-cache.d.mts +205 -0
  72. package/esm/scoped-cache.d.mts.map +1 -0
  73. package/esm/scoped-cache.mjs +274 -0
  74. package/esm/scoped-cache.mjs.map +1 -0
  75. package/esm/tagged-cache.d.mts +89 -0
  76. package/esm/tagged-cache.d.mts.map +1 -0
  77. package/esm/tagged-cache.mjs +147 -0
  78. package/esm/tagged-cache.mjs.map +1 -0
  79. package/esm/tagged-scoped-cache.d.mts +111 -0
  80. package/esm/tagged-scoped-cache.d.mts.map +1 -0
  81. package/esm/tagged-scoped-cache.mjs +142 -0
  82. package/esm/tagged-scoped-cache.mjs.map +1 -0
  83. package/esm/types.d.mts +1067 -0
  84. package/esm/types.d.mts.map +1 -0
  85. package/esm/types.mjs +62 -0
  86. package/esm/types.mjs.map +1 -0
  87. package/esm/utils.d.mts +161 -0
  88. package/esm/utils.d.mts.map +1 -0
  89. package/esm/utils.mjs +222 -0
  90. package/esm/utils.mjs.map +1 -0
  91. package/llms-full.txt +2071 -0
  92. package/llms.txt +28 -0
  93. package/package.json +53 -39
  94. package/skills/apply-cache-patterns/SKILL.md +97 -0
  95. package/skills/cache-basics/SKILL.md +121 -0
  96. package/skills/configure-pg-cache/SKILL.md +115 -0
  97. package/skills/configure-set-options/SKILL.md +96 -0
  98. package/skills/handle-cache-errors/SKILL.md +91 -0
  99. package/skills/observe-cache/SKILL.md +103 -0
  100. package/skills/overview/SKILL.md +69 -0
  101. package/skills/pick-cache-driver/SKILL.md +115 -0
  102. package/skills/test-cache-code/SKILL.md +219 -0
  103. package/skills/use-cache-atomic/SKILL.md +67 -0
  104. package/skills/use-cache-bulk/SKILL.md +57 -0
  105. package/skills/use-cache-list/SKILL.md +85 -0
  106. package/skills/use-cache-lock/SKILL.md +104 -0
  107. package/skills/use-cache-namespace/SKILL.md +88 -0
  108. package/skills/use-cache-similarity/SKILL.md +94 -0
  109. package/skills/use-cache-tags/SKILL.md +85 -0
  110. package/skills/use-cache-update-merge/SKILL.md +84 -0
  111. package/skills/use-cache-utils/SKILL.md +89 -0
  112. package/skills/use-cached-hof/SKILL.md +102 -0
  113. package/skills/use-swr/SKILL.md +104 -0
  114. package/cjs/cache-manager.d.ts +0 -163
  115. package/cjs/cache-manager.d.ts.map +0 -1
  116. package/cjs/cache-manager.js +0 -322
  117. package/cjs/cache-manager.js.map +0 -1
  118. package/cjs/drivers/base-cache-driver.d.ts +0 -152
  119. package/cjs/drivers/base-cache-driver.d.ts.map +0 -1
  120. package/cjs/drivers/base-cache-driver.js +0 -321
  121. package/cjs/drivers/base-cache-driver.js.map +0 -1
  122. package/cjs/drivers/file-cache-driver.d.ts +0 -45
  123. package/cjs/drivers/file-cache-driver.d.ts.map +0 -1
  124. package/cjs/drivers/file-cache-driver.js +0 -133
  125. package/cjs/drivers/file-cache-driver.js.map +0 -1
  126. package/cjs/drivers/index.d.ts +0 -8
  127. package/cjs/drivers/index.d.ts.map +0 -1
  128. package/cjs/drivers/lru-memory-cache-driver.d.ts +0 -98
  129. package/cjs/drivers/lru-memory-cache-driver.d.ts.map +0 -1
  130. package/cjs/drivers/lru-memory-cache-driver.js +0 -252
  131. package/cjs/drivers/lru-memory-cache-driver.js.map +0 -1
  132. package/cjs/drivers/memory-cache-driver.d.ts +0 -82
  133. package/cjs/drivers/memory-cache-driver.d.ts.map +0 -1
  134. package/cjs/drivers/memory-cache-driver.js +0 -218
  135. package/cjs/drivers/memory-cache-driver.js.map +0 -1
  136. package/cjs/drivers/memory-extended-cache-driver.d.ts +0 -13
  137. package/cjs/drivers/memory-extended-cache-driver.d.ts.map +0 -1
  138. package/cjs/drivers/memory-extended-cache-driver.js +0 -25
  139. package/cjs/drivers/memory-extended-cache-driver.js.map +0 -1
  140. package/cjs/drivers/null-cache-driver.d.ts +0 -58
  141. package/cjs/drivers/null-cache-driver.d.ts.map +0 -1
  142. package/cjs/drivers/null-cache-driver.js +0 -84
  143. package/cjs/drivers/null-cache-driver.js.map +0 -1
  144. package/cjs/drivers/redis-cache-driver.d.ts +0 -57
  145. package/cjs/drivers/redis-cache-driver.d.ts.map +0 -1
  146. package/cjs/drivers/redis-cache-driver.js +0 -263
  147. package/cjs/drivers/redis-cache-driver.js.map +0 -1
  148. package/cjs/index.d.ts +0 -6
  149. package/cjs/index.d.ts.map +0 -1
  150. package/cjs/index.js +0 -1
  151. package/cjs/index.js.map +0 -1
  152. package/cjs/tagged-cache.d.ts +0 -77
  153. package/cjs/tagged-cache.d.ts.map +0 -1
  154. package/cjs/tagged-cache.js +0 -160
  155. package/cjs/tagged-cache.js.map +0 -1
  156. package/cjs/types.d.ts +0 -391
  157. package/cjs/types.d.ts.map +0 -1
  158. package/cjs/types.js +0 -36
  159. package/cjs/types.js.map +0 -1
  160. package/cjs/utils.d.ts +0 -50
  161. package/cjs/utils.d.ts.map +0 -1
  162. package/cjs/utils.js +0 -55
  163. package/cjs/utils.js.map +0 -1
  164. package/esm/cache-manager.d.ts +0 -163
  165. package/esm/cache-manager.d.ts.map +0 -1
  166. package/esm/cache-manager.js +0 -322
  167. package/esm/cache-manager.js.map +0 -1
  168. package/esm/drivers/base-cache-driver.d.ts +0 -152
  169. package/esm/drivers/base-cache-driver.d.ts.map +0 -1
  170. package/esm/drivers/base-cache-driver.js +0 -321
  171. package/esm/drivers/base-cache-driver.js.map +0 -1
  172. package/esm/drivers/file-cache-driver.d.ts +0 -45
  173. package/esm/drivers/file-cache-driver.d.ts.map +0 -1
  174. package/esm/drivers/file-cache-driver.js +0 -133
  175. package/esm/drivers/file-cache-driver.js.map +0 -1
  176. package/esm/drivers/index.d.ts +0 -8
  177. package/esm/drivers/index.d.ts.map +0 -1
  178. package/esm/drivers/lru-memory-cache-driver.d.ts +0 -98
  179. package/esm/drivers/lru-memory-cache-driver.d.ts.map +0 -1
  180. package/esm/drivers/lru-memory-cache-driver.js +0 -252
  181. package/esm/drivers/lru-memory-cache-driver.js.map +0 -1
  182. package/esm/drivers/memory-cache-driver.d.ts +0 -82
  183. package/esm/drivers/memory-cache-driver.d.ts.map +0 -1
  184. package/esm/drivers/memory-cache-driver.js +0 -218
  185. package/esm/drivers/memory-cache-driver.js.map +0 -1
  186. package/esm/drivers/memory-extended-cache-driver.d.ts +0 -13
  187. package/esm/drivers/memory-extended-cache-driver.d.ts.map +0 -1
  188. package/esm/drivers/memory-extended-cache-driver.js +0 -25
  189. package/esm/drivers/memory-extended-cache-driver.js.map +0 -1
  190. package/esm/drivers/null-cache-driver.d.ts +0 -58
  191. package/esm/drivers/null-cache-driver.d.ts.map +0 -1
  192. package/esm/drivers/null-cache-driver.js +0 -84
  193. package/esm/drivers/null-cache-driver.js.map +0 -1
  194. package/esm/drivers/redis-cache-driver.d.ts +0 -57
  195. package/esm/drivers/redis-cache-driver.d.ts.map +0 -1
  196. package/esm/drivers/redis-cache-driver.js +0 -263
  197. package/esm/drivers/redis-cache-driver.js.map +0 -1
  198. package/esm/index.d.ts +0 -6
  199. package/esm/index.d.ts.map +0 -1
  200. package/esm/index.js +0 -1
  201. package/esm/index.js.map +0 -1
  202. package/esm/tagged-cache.d.ts +0 -77
  203. package/esm/tagged-cache.d.ts.map +0 -1
  204. package/esm/tagged-cache.js +0 -160
  205. package/esm/tagged-cache.js.map +0 -1
  206. package/esm/types.d.ts +0 -391
  207. package/esm/types.d.ts.map +0 -1
  208. package/esm/types.js +0 -36
  209. package/esm/types.js.map +0 -1
  210. package/esm/utils.d.ts +0 -50
  211. package/esm/utils.d.ts.map +0 -1
  212. package/esm/utils.js +0 -55
  213. package/esm/utils.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["log"],"sources":["../../../../../@warlock.js/cache/src/metrics.ts","../../../../../@warlock.js/cache/src/types.ts","../../../../../@warlock.js/cache/src/utils.ts","../../../../../@warlock.js/cache/src/tagged-scoped-cache.ts","../../../../../@warlock.js/cache/src/scoped-cache.ts","../../../../../@warlock.js/cache/src/cache-manager.ts","../../../../../@warlock.js/cache/src/cached/auto-key.ts","../../../../../@warlock.js/cache/src/cached/normalize-args.ts","../../../../../@warlock.js/cache/src/cached/cached.ts","../../../../../@warlock.js/cache/src/list/memory-cache-list.ts","../../../../../@warlock.js/cache/src/tagged-cache.ts","../../../../../@warlock.js/cache/src/drivers/base-cache-driver.ts","../../../../../@warlock.js/cache/src/drivers/file-cache-driver.ts","../../../../../@warlock.js/cache/src/drivers/lru-memory-cache-driver.ts","../../../../../@warlock.js/cache/src/drivers/memory-cache-driver.ts","../../../../../@warlock.js/cache/src/drivers/memory-extended-cache-driver.ts","../../../../../@warlock.js/cache/src/drivers/mock-cache-driver.ts","../../../../../@warlock.js/cache/src/drivers/null-cache-driver.ts","../../../../../@warlock.js/cache/src/drivers/pg-cache-driver.ts","../../../../../@warlock.js/cache/src/drivers/redis-cache-driver.ts"],"sourcesContent":["import type {\n CacheEventData,\n CacheMetricsSnapshot,\n} from \"./types\";\n\n/**\n * Default size of the circular latency-sample buffer. 1000 samples covers\n * \"tell me the current p95\" for every realistic workload while keeping the\n * memory cost negligible (8KB at 8 bytes per number).\n */\nconst DEFAULT_LATENCY_BUFFER_SIZE = 1000;\n\n/**\n * Per-driver counter row tracked inside {@link CacheMetricsCollector}.\n *\n * Same shape as the public {@link CacheMetricsSnapshot} except for the lack\n * of `byDriver` (which is the breakdown itself) and the `latencySamples`\n * array — the buffer that p50/p95/p99 are computed from at snapshot time.\n */\ntype DriverCounters = {\n hits: number;\n misses: number;\n sets: number;\n removed: number;\n errors: number;\n latencySamples: number[];\n /** Pointer into `latencySamples` for the next write — circular buffer. */\n latencyCursor: number;\n};\n\n/**\n * Listens to `CacheManager` events and accumulates running counters + a\n * circular latency buffer per driver. Returned to consumers via\n * `cache.metrics()` as a {@link CacheMetricsSnapshot}.\n *\n * **Role.** Single-instance observability layer attached to the manager.\n * Subscribes once at construction; survives `cache.use()` driver switches\n * because the global event registry on the manager re-attaches handlers to\n * every loaded driver.\n *\n * **Responsibility.**\n * - Owns: per-driver and aggregate counters (`hits`, `misses`, `sets`,\n * `removed`, `errors`), the latency circular buffer, and snapshot\n * computation including hit-rate + percentile calculation.\n * - Does NOT own: event emission (driven by drivers via `BaseCacheDriver.emit`),\n * timing instrumentation (done at the manager level via `recordLatency`),\n * or persistence — every metric resets on `resetMetrics()` and on process\n * restart.\n *\n * @example\n * cache.metrics();\n * // {\n * // hits: 9821, misses: 173, hitRate: 0.983,\n * // latencyMs: { p50: 0.4, p95: 2.1, p99: 8.2, samples: 1000 },\n * // byDriver: { memory: { ... }, redis: { ... } },\n * // startedAt: 1714185600000,\n * // }\n */\nexport class CacheMetricsCollector {\n /**\n * Maximum number of latency samples retained per driver. Older samples\n * are overwritten in arrival order — quantile calc operates on whatever\n * window is currently in the buffer.\n */\n protected readonly bufferSize: number;\n\n /**\n * Aggregate counters across every driver. Mirrors what one driver bucket\n * holds — the snapshot reports both totals and per-driver breakdowns.\n */\n protected readonly aggregate: DriverCounters;\n\n /**\n * Per-driver buckets keyed by driver name (`\"memory\"`, `\"redis\"`, …).\n * Lazy-allocated on first event.\n */\n protected readonly byDriver: Map<string, DriverCounters> = new Map();\n\n /** Millisecond timestamp the collector last reset. */\n protected startedAt: number = Date.now();\n\n public constructor(bufferSize: number = DEFAULT_LATENCY_BUFFER_SIZE) {\n this.bufferSize = bufferSize;\n this.aggregate = this.createCounters();\n }\n\n /**\n * Increment the appropriate counters for a cache event. Called from the\n * manager's global listeners (one per event type).\n */\n public recordEvent(\n event: \"hit\" | \"miss\" | \"set\" | \"removed\" | \"error\",\n data: CacheEventData,\n ): void {\n const driverBucket = this.bucketFor(data.driver);\n const aggregate = this.aggregate;\n\n switch (event) {\n case \"hit\":\n aggregate.hits += 1;\n driverBucket.hits += 1;\n break;\n case \"miss\":\n aggregate.misses += 1;\n driverBucket.misses += 1;\n break;\n case \"set\":\n aggregate.sets += 1;\n driverBucket.sets += 1;\n break;\n case \"removed\":\n aggregate.removed += 1;\n driverBucket.removed += 1;\n break;\n case \"error\":\n aggregate.errors += 1;\n driverBucket.errors += 1;\n break;\n }\n }\n\n /**\n * Append a latency sample for `driver`. Called by the manager from its\n * timed wrappers around `get` / `set` / `remove`. Uses circular-buffer\n * semantics: oldest samples are overwritten once the buffer is full.\n */\n public recordLatency(driver: string, durationMs: number): void {\n this.appendLatency(this.aggregate, durationMs);\n this.appendLatency(this.bucketFor(driver), durationMs);\n }\n\n /**\n * Compute and return the current snapshot. Latency percentiles are\n * derived from a sorted copy of the buffer at call time — O(N log N)\n * on N=1000 is cheap enough that we don't bother caching.\n */\n public snapshot(): CacheMetricsSnapshot {\n const byDriver: Record<string, Omit<CacheMetricsSnapshot, \"byDriver\">> = {};\n\n for (const [driverName, bucket] of this.byDriver) {\n byDriver[driverName] = this.toRow(bucket);\n }\n\n return {\n ...this.toRow(this.aggregate),\n byDriver,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Wipe every counter, drop every latency sample, and reset `startedAt`.\n * The collector itself stays subscribed to events.\n */\n public reset(): void {\n this.resetCounters(this.aggregate);\n this.byDriver.clear();\n this.startedAt = Date.now();\n }\n\n /**\n * Locate the per-driver bucket, creating it on first reference. Driver\n * names are taken verbatim from `CacheEventData.driver`.\n */\n protected bucketFor(driverName: string): DriverCounters {\n let bucket = this.byDriver.get(driverName);\n\n if (!bucket) {\n bucket = this.createCounters();\n this.byDriver.set(driverName, bucket);\n }\n\n return bucket;\n }\n\n /**\n * Convert raw counters into the public snapshot row shape. Computes\n * `hitRate` and the latency percentiles on the fly.\n */\n protected toRow(bucket: DriverCounters): Omit<CacheMetricsSnapshot, \"byDriver\"> {\n const totalReads = bucket.hits + bucket.misses;\n const hitRate = totalReads === 0 ? 0 : bucket.hits / totalReads;\n const latency = this.computeLatency(bucket.latencySamples);\n\n return {\n hits: bucket.hits,\n misses: bucket.misses,\n sets: bucket.sets,\n removed: bucket.removed,\n errors: bucket.errors,\n hitRate,\n latencyMs: latency,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Sort a copy of the buffer and pick the percentile entries directly.\n * Empty buffers return zeroed percentiles so consumers can render\n * dashboards without null-checking.\n */\n protected computeLatency(samples: number[]): CacheMetricsSnapshot[\"latencyMs\"] {\n if (samples.length === 0) {\n return { p50: 0, p95: 0, p99: 0, samples: 0 };\n }\n\n const sorted = [...samples].sort((a, b) => a - b);\n const pick = (quantile: number): number => {\n const index = Math.min(sorted.length - 1, Math.floor(quantile * sorted.length));\n\n return sorted[index];\n };\n\n return {\n p50: pick(0.5),\n p95: pick(0.95),\n p99: pick(0.99),\n samples: sorted.length,\n };\n }\n\n /**\n * Append a latency sample using circular-buffer semantics — overwrite the\n * oldest entry once the buffer is full instead of growing unbounded.\n */\n protected appendLatency(bucket: DriverCounters, durationMs: number): void {\n if (bucket.latencySamples.length < this.bufferSize) {\n bucket.latencySamples.push(durationMs);\n\n return;\n }\n\n bucket.latencySamples[bucket.latencyCursor] = durationMs;\n bucket.latencyCursor = (bucket.latencyCursor + 1) % this.bufferSize;\n }\n\n /** Build a fresh counter row with zeroed totals and an empty buffer. */\n protected createCounters(): DriverCounters {\n return {\n hits: 0,\n misses: 0,\n sets: 0,\n removed: 0,\n errors: 0,\n latencySamples: [],\n latencyCursor: 0,\n };\n }\n\n /** Reset an existing counter row in place. Used by `reset()` for the aggregate. */\n protected resetCounters(bucket: DriverCounters): void {\n bucket.hits = 0;\n bucket.misses = 0;\n bucket.sets = 0;\n bucket.removed = 0;\n bucket.errors = 0;\n bucket.latencySamples.length = 0;\n bucket.latencyCursor = 0;\n }\n}\n","import type { GenericObject } from \"@mongez/reinforcements\";\r\nimport type { RedisClientOptions } from \"redis\";\r\nimport type {\r\n BaseCacheDriver,\r\n FileCacheDriver,\r\n LRUMemoryCacheDriver,\r\n MemoryCacheDriver,\r\n MockCacheDriver,\r\n MemoryExtendedCacheDriver,\r\n NullCacheDriver,\r\n PgCacheDriver,\r\n RedisCacheDriver,\r\n} from \"./drivers\";\r\n\r\n/**\r\n * Base error class for cache-related errors\r\n */\r\nexport class CacheError extends Error {\r\n public constructor(message: string) {\r\n super(message);\r\n this.name = \"CacheError\";\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when cache connection fails\r\n */\r\nexport class CacheConnectionError extends CacheError {\r\n public constructor(message: string) {\r\n super(message);\r\n this.name = \"CacheConnectionError\";\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when cache driver configuration is invalid\r\n */\r\nexport class CacheConfigurationError extends CacheError {\r\n public constructor(message: string) {\r\n super(message);\r\n this.name = \"CacheConfigurationError\";\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when cache driver is not initialized\r\n */\r\nexport class CacheDriverNotInitializedError extends CacheError {\r\n public constructor(\r\n message: string = \"No cache driver initialized. Call cache.init() or cache.use() first.\",\r\n ) {\r\n super(message);\r\n this.name = \"CacheDriverNotInitializedError\";\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when a driver does not implement a requested operation.\r\n *\r\n * Raised when a caller invokes a method the driver cannot fulfill —\r\n * e.g. `update()` on the file driver before the file-lock primitive lands.\r\n */\r\nexport class CacheUnsupportedError extends CacheError {\r\n public constructor(message: string) {\r\n super(message);\r\n this.name = \"CacheUnsupportedError\";\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when an optimistic-concurrency update exhausts its retry budget.\r\n */\r\nexport class CacheConcurrencyError extends CacheError {\r\n public constructor(message: string) {\r\n super(message);\r\n this.name = \"CacheConcurrencyError\";\r\n }\r\n}\r\n\r\n/**\r\n * TTL shape accepted by set/remember/config calls.\r\n *\r\n * - `number` — seconds\r\n * - `string` — human-readable duration parsed via `ms` (`\"1h\"`, `\"30m\"`, `\"7d\"`, `\"2 weeks\"`, …)\r\n * - `Infinity` — no expiration\r\n */\r\nexport type CacheTtl = number | string;\r\n\r\n/**\r\n * Conflict resolution policy for `set` operations.\r\n *\r\n * - `\"create\"` — set only if the key does not exist (Redis `NX`)\r\n * - `\"update\"` — set only if the key already exists (Redis `XX`)\r\n * - `\"upsert\"` — default; overwrite whether the key exists or not\r\n */\r\nexport type CacheConflictPolicy = \"create\" | \"update\" | \"upsert\";\r\n\r\n/**\r\n * Result of a conditional write (`onConflict: \"create\" | \"update\"`).\r\n */\r\nexport type CacheSetResult<T = any> = {\r\n /**\r\n * Whether the write actually took effect.\r\n */\r\n wasSet: boolean;\r\n /**\r\n * The existing value when the write was rejected. Undefined for successful writes\r\n * and for unconditional `upsert` writes.\r\n */\r\n existing: T | null;\r\n};\r\n\r\n/**\r\n * Options for `lock()` — TTL is required (forgotten locks would stay forever),\r\n * `owner` identifies who holds the lock (handy for debugging), `driver` routes\r\n * the call through a non-default driver.\r\n *\r\n * @example\r\n * await cache.lock(\"lock.import\", { ttl: \"5m\", driver: \"redis\" }, async () => {\r\n * await runImport();\r\n * });\r\n */\r\nexport type LockOptions = {\r\n /**\r\n * How long the lock lives before auto-release. Required — guards against\r\n * forgotten locks if the process crashes between acquire and release.\r\n */\r\n ttl: CacheTtl;\r\n /**\r\n * Identifying value stored under the lock key. Defaults to `pid.<process.pid>`.\r\n * Use a custom owner when you want a human-readable label (e.g. `\"worker.jobs-2\"`).\r\n */\r\n owner?: string;\r\n /**\r\n * Per-call driver override by registered name. Manager-level only.\r\n */\r\n driver?: string;\r\n};\r\n\r\n/**\r\n * Discriminated-union result of `lock()`. Unambiguous even when the wrapped\r\n * function legitimately returns `undefined` — the `acquired` flag tells you\r\n * whether `fn` ran at all.\r\n *\r\n * @example\r\n * const outcome = await cache.lock(\"lock.x\", \"1m\", async () => computeValue());\r\n * if (outcome.acquired) {\r\n * console.log(\"ran, got\", outcome.value);\r\n * } else {\r\n * console.log(\"skipped — someone else holds the lock\");\r\n * }\r\n */\r\nexport type LockOutcome<T> =\r\n | { acquired: true; value: T }\r\n | { acquired: false };\r\n\r\n/**\r\n * Options for `remember()` when you need more than just a TTL — e.g. attaching\r\n * tags to the cache-miss write, or routing the single call to a non-default driver.\r\n *\r\n * Passed in the TTL position: `cache.remember(key, { ttl: \"1h\", tags: [\"users\"] }, fn)`.\r\n *\r\n * @example\r\n * await cache.remember(\"user.1\", { ttl: \"1h\", tags: [\"users\"] }, () =>\r\n * db.users.find(1),\r\n * );\r\n */\r\nexport type RememberOptions = {\r\n /**\r\n * TTL applied on cache miss. Accepts seconds (number) or duration string.\r\n */\r\n ttl?: CacheTtl;\r\n /**\r\n * Tags attached to the entry created on miss. Invalidate via `cache.tags([...]).invalidate()`.\r\n */\r\n tags?: string[];\r\n /**\r\n * Per-call driver override. Routes this remember call (both read and write)\r\n * to the named driver without mutating `currentDriver`.\r\n */\r\n driver?: string;\r\n};\r\n\r\n/**\r\n * Rich options for the `set` call-site. Mutually exclusive with a positional TTL.\r\n *\r\n * @example\r\n * await cache.set(\"user:1\", user, {\r\n * ttl: \"1h\",\r\n * tags: [\"users\", \"active\"],\r\n * onConflict: \"create\",\r\n * });\r\n */\r\nexport type CacheSetOptions = {\r\n /**\r\n * Relative TTL (seconds or duration string). Mutually exclusive with `expiresAt`.\r\n */\r\n ttl?: CacheTtl;\r\n /**\r\n * Absolute expiration as a Unix epoch in milliseconds or a Date. Mutually exclusive with `ttl`.\r\n */\r\n expiresAt?: number | Date;\r\n /**\r\n * Tags attached to this entry for tag-based invalidation. Inline equivalent of\r\n * `cache.tags([...]).set(key, value)`.\r\n */\r\n tags?: string[];\r\n /**\r\n * Conflict resolution policy. Defaults to `\"upsert\"`.\r\n */\r\n onConflict?: CacheConflictPolicy;\r\n /**\r\n * Per-call namespace override. Applied ahead of `globalPrefix` for this write only.\r\n */\r\n namespace?: string;\r\n /**\r\n * Per-call driver override by registered name. Routes this write to a non-default driver\r\n * without mutating `currentDriver`.\r\n */\r\n driver?: string;\r\n /**\r\n * Optional embedding vector indexed alongside the entry for similarity retrieval\r\n * via `cache.similar(...)`. Drivers without similarity support throw\r\n * {@link CacheUnsupportedError} when this option is supplied.\r\n *\r\n * Cache is embedding-agnostic — callers compute the vector with whichever embedder\r\n * they like and pass the result here.\r\n *\r\n * @example\r\n * await cache.set(\"doc.123\", doc, { vector: await embed(doc.text), ttl: \"1d\" });\r\n */\r\n vector?: number[];\r\n /**\r\n * Freshness deadline as a Unix epoch in milliseconds. Primarily set by\r\n * {@link CacheSwrOptions} flow — entries with `staleAt` in the future are\r\n * considered \"fresh\" by `cache.swr()`; entries past `staleAt` but before\r\n * `expiresAt` are \"stale-but-revalidatable.\"\r\n *\r\n * Direct callers can pin this manually when bypassing `swr()`, but the\r\n * common path is `cache.swr(key, { freshTtl, staleTtl }, fn)`.\r\n */\r\n staleAt?: number;\r\n};\r\n\r\n/**\r\n * Options for `cache.namespace(prefix, options)` — defaults applied to every\r\n * write produced through the returned scope.\r\n *\r\n * Per-call options always win over scope defaults. Tags merge additively\r\n * (scope tags + call tags, deduped). Nested scopes inherit from the parent;\r\n * the child's own values override.\r\n */\r\nexport type CacheNamespaceOptions = {\r\n /**\r\n * Default TTL for writes inside this scope. Same shape as everywhere else\r\n * — number of seconds or a duration string (`\"1h\"`, `\"30d\"`, …).\r\n */\r\n ttl?: CacheTtl;\r\n /**\r\n * Tags auto-attached to every write inside this scope. Useful for cross-scope\r\n * invalidation hooks (e.g. tag every entry with `user.<id>` so a single\r\n * `cache.flushTag(\"user.42\")` wipes the user's footprint everywhere).\r\n *\r\n * Merged additively with per-call tags; scope tags are never replaced.\r\n */\r\n tags?: string[];\r\n};\r\n\r\n/**\r\n * Options for `cache.swr(key, options, callback)` — stale-while-revalidate.\r\n *\r\n * Two TTLs split the entry's lifecycle into three windows:\r\n * - `now < freshTtl` → fresh, return cached value, no upstream call.\r\n * - `freshTtl <= now < staleTtl` → stale-but-revalidatable, return cached\r\n * value immediately, kick off `callback` in the background to refresh.\r\n * - `now >= staleTtl` → expired, block on `callback` like a normal miss.\r\n *\r\n * Concurrent SWR callers in the stale window all return the cached value;\r\n * the background refresh runs exactly once per key (deduped via the\r\n * driver's per-key lock map).\r\n *\r\n * If the background refresh fails, the stale entry is preserved and the\r\n * error is logged + emitted on the `error` event. Callers that received\r\n * the stale value never see the failure — they got their data.\r\n */\r\nexport type CacheSwrOptions = {\r\n /**\r\n * How long the value is considered fresh. Within this window, SWR returns\r\n * the cached value with no upstream call. Accepts seconds or duration\r\n * string (`\"1m\"`, `\"30s\"`).\r\n */\r\n freshTtl: CacheTtl;\r\n /**\r\n * Total lifetime of the entry (must be greater than `freshTtl`). Between\r\n * `freshTtl` and `staleTtl`, SWR returns the stale value and triggers a\r\n * background refresh. Past `staleTtl`, SWR blocks and refetches.\r\n */\r\n staleTtl: CacheTtl;\r\n /**\r\n * Optional tags applied on the first miss-fetch and on every successful\r\n * background refresh. Same semantics as `CacheSetOptions.tags`.\r\n */\r\n tags?: string[];\r\n /**\r\n * Per-call driver override by registered name. Routes both the read and\r\n * any write/refresh through the named driver without mutating\r\n * `currentDriver`. Same semantics as `RememberOptions.driver`.\r\n */\r\n driver?: string;\r\n};\r\n\r\n/**\r\n * Options for `cache.similar(vector, options)` — similarity retrieval against\r\n * stored entries previously written with `set({ vector })`.\r\n */\r\nexport type CacheSimilarOptions = {\r\n /**\r\n * Maximum number of hits to return. Required — no implicit default keeps\r\n * callers from accidentally pulling the entire candidate set.\r\n */\r\n topK: number;\r\n /**\r\n * Cosine similarity floor in `[0, 1]`. Hits scoring strictly below this are\r\n * filtered out before `topK` truncation.\r\n */\r\n threshold?: number;\r\n /**\r\n * Optional tag filter. Only entries tagged with at least one of the given\r\n * tags are considered (matches the union semantics elsewhere in the package).\r\n */\r\n tags?: string[];\r\n};\r\n\r\n/**\r\n * One result from a `similar()` query — the original key, its stored value,\r\n * and the cosine similarity to the query vector.\r\n */\r\nexport type CacheSimilarHit<T = unknown> = {\r\n /**\r\n * The original cache key, post-`parseKey` normalization.\r\n */\r\n key: string;\r\n /**\r\n * Stored value, deep-cloned to protect the cache from accidental mutation\r\n * (same semantics as `get`).\r\n */\r\n value: T;\r\n /**\r\n * Cosine similarity in `[0, 1]`. Higher means more similar.\r\n */\r\n score: number;\r\n};\r\n\r\n/**\r\n * Cache key type - can be a string or an object\r\n */\r\nexport type CacheKey = string | GenericObject;\r\n\r\nexport type CacheOperationType =\r\n | \"fetching\"\r\n | \"fetched\"\r\n | \"caching\"\r\n | \"cached\"\r\n | \"flushing\"\r\n | \"flushed\"\r\n | \"removing\"\r\n | \"removed\"\r\n | \"clearing\"\r\n | \"cleared\"\r\n | \"expired\"\r\n | \"notFound\"\r\n | \"connecting\"\r\n | \"error\"\r\n | \"connected\"\r\n | \"disconnecting\"\r\n | \"disconnected\";\r\n\r\n/**\r\n * Cache event types for observability\r\n */\r\nexport type CacheEventType =\r\n | \"hit\"\r\n | \"miss\"\r\n | \"set\"\r\n | \"removed\"\r\n | \"flushed\"\r\n | \"expired\"\r\n | \"connected\"\r\n | \"disconnected\"\r\n | \"error\";\r\n\r\n/**\r\n * Cache event data structure\r\n */\r\nexport type CacheEventData = {\r\n /**\r\n * The cache key involved in the event\r\n */\r\n key?: string;\r\n /**\r\n * The value (for set/hit events)\r\n */\r\n value?: any;\r\n /**\r\n * TTL in seconds (for set events)\r\n */\r\n ttl?: number;\r\n /**\r\n * Driver name that emitted the event\r\n */\r\n driver: string;\r\n /**\r\n * Error object (for error events)\r\n */\r\n error?: any;\r\n /**\r\n * Namespace (for namespace operations)\r\n */\r\n namespace?: string;\r\n};\r\n\r\n/**\r\n * Event handler function type\r\n */\r\nexport type CacheEventHandler = (eventData: CacheEventData) => void | Promise<void>;\r\n\r\n/**\r\n * Per-operation latency + counter snapshot returned by `cache.metrics()`.\r\n *\r\n * The shape is a single recursive level — the top object covers all drivers\r\n * and each entry under `byDriver` repeats the same fields without nesting\r\n * further. Use this for ad-hoc dashboards, exporters to Prometheus / StatsD,\r\n * or just `console.log` during development.\r\n */\r\nexport type CacheMetricsSnapshot = {\r\n /** Cumulative cache hits across the lifetime of the collector. */\r\n hits: number;\r\n /** Cumulative cache misses (lookups that returned null). */\r\n misses: number;\r\n /** Cumulative successful writes. */\r\n sets: number;\r\n /** Cumulative key removals. */\r\n removed: number;\r\n /** Cumulative cache errors emitted via the `error` event. */\r\n errors: number;\r\n /** `hits / (hits + misses)` — `0` when no read ops have happened yet. */\r\n hitRate: number;\r\n /** Latency percentiles in milliseconds, computed from a circular buffer. */\r\n latencyMs: {\r\n p50: number;\r\n p95: number;\r\n p99: number;\r\n /** Size of the underlying buffer at snapshot time (capped at the configured max). */\r\n samples: number;\r\n };\r\n /** Per-driver breakdowns keyed by driver name (`\"memory\"`, `\"redis\"`, …). */\r\n byDriver: Record<string, Omit<CacheMetricsSnapshot, \"byDriver\">>;\r\n /** Millisecond timestamp the collector last reset (or was instantiated). */\r\n startedAt: number;\r\n};\r\n\r\n/**\r\n * Tagged cache interface for working with cache tags\r\n */\r\nexport interface TaggedCacheDriver {\r\n /**\r\n * Set a value in cache with tags\r\n */\r\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\r\n /**\r\n * Get a value from cache (checks tags)\r\n */\r\n get(key: CacheKey): Promise<any | null>;\r\n /**\r\n * Remove a specific key\r\n */\r\n remove(key: CacheKey): Promise<void>;\r\n /**\r\n * Invalidate (clear) all keys associated with the current tags\r\n */\r\n invalidate(): Promise<void>;\r\n /**\r\n * Flush all keys associated with the current tags\r\n * @deprecated Use invalidate() instead\r\n */\r\n flush(): Promise<void>;\r\n /**\r\n * Check if a key exists\r\n */\r\n has(key: CacheKey): Promise<boolean>;\r\n /**\r\n * Remember pattern with tags\r\n */\r\n remember(key: CacheKey, ttl: number, callback: () => Promise<any>): Promise<any>;\r\n /**\r\n * Pull value with tags\r\n */\r\n pull(key: CacheKey): Promise<any | null>;\r\n /**\r\n * Forever with tags\r\n */\r\n forever(key: CacheKey, value: any): Promise<any>;\r\n /**\r\n * Increment with tags\r\n */\r\n increment(key: CacheKey, value?: number): Promise<number>;\r\n /**\r\n * Decrement with tags\r\n */\r\n decrement(key: CacheKey, value?: number): Promise<number>;\r\n}\r\n\r\nexport type MemoryCacheOptions = {\r\n /**\r\n * The global prefix for the cache key\r\n */\r\n globalPrefix?: string | (() => string);\r\n /**\r\n * The default TTL for the cache. Accepts a number of seconds or a human-readable\r\n * duration string like `\"1h\"`, `\"30m\"`, `\"7d\"`.\r\n *\r\n * @default Infinity\r\n */\r\n ttl?: CacheTtl;\r\n /**\r\n * Maximum number of items in cache\r\n * When exceeded, least recently used items will be evicted\r\n *\r\n * @default undefined (no limit)\r\n */\r\n maxSize?: number;\r\n};\r\n\r\nexport type MemoryExtendedCacheOptions = MemoryCacheOptions;\r\n\r\nexport type LRUMemoryCacheOptions = {\r\n /**\r\n * The maximum number of items in the cache\r\n *\r\n * @default 1000\r\n */\r\n capacity?: number;\r\n /**\r\n * The global prefix for the cache key. Applied via `parseCacheKey`, same\r\n * semantics as the other drivers.\r\n */\r\n globalPrefix?: string | (() => string);\r\n /**\r\n * The default TTL for new entries. Accepts a number of seconds or a\r\n * human-readable duration string like `\"1h\"`, `\"30m\"`, `\"7d\"`.\r\n *\r\n * @default Infinity\r\n */\r\n ttl?: CacheTtl;\r\n};\r\n\r\nexport type FileCacheOptions = {\r\n /**\r\n * The global prefix for the cache key\r\n */\r\n globalPrefix?: string | (() => string);\r\n /**\r\n * The default TTL for the cache. Accepts a number of seconds or a human-readable\r\n * duration string like `\"1h\"`, `\"30m\"`, `\"7d\"`.\r\n *\r\n * @default 0\r\n */\r\n ttl?: CacheTtl;\r\n /**\r\n * Storage cache directory\r\n *\r\n * @default storagePath(\"cache\")\r\n */\r\n directory: string | (() => string);\r\n /**\r\n * File name\r\n *\r\n * @default cache.json\r\n */\r\n fileName?: string | (() => string);\r\n};\r\n\r\nexport type RedisOptions = {\r\n /**\r\n * Redis Port\r\n *\r\n * @default 6379\r\n */\r\n port?: number;\r\n /**\r\n * Redis Host\r\n */\r\n host?: string;\r\n /**\r\n * Redis Username\r\n */\r\n username?: string;\r\n /**\r\n * Redis Password\r\n */\r\n password?: string;\r\n /**\r\n * Redis URL\r\n *\r\n * If used, it will override the host and port options\r\n */\r\n url?: string;\r\n /**\r\n * Global prefix for the cache key\r\n */\r\n globalPrefix?: string | (() => string);\r\n /**\r\n * Default TTL. Accepts a number of seconds or a human-readable duration string\r\n * like `\"1h\"`, `\"30m\"`, `\"7d\"`.\r\n *\r\n * @default Infinity\r\n */\r\n ttl?: CacheTtl;\r\n /**\r\n * Redis client options\r\n */\r\n clientOptions?: RedisClientOptions;\r\n};\r\n\r\nexport type NullCacheDriverOptions = GenericObject;\r\n\r\n/**\r\n * Options accepted by {@link MockCacheDriver}. Same shape as the memory\r\n * driver — only `globalPrefix` and `ttl` apply, since the mock's storage is\r\n * a plain `Map` with no eviction policy.\r\n */\r\nexport type MockCacheOptions = {\r\n /**\r\n * Global key prefix, applied via `parseKey` (matches the other drivers).\r\n */\r\n globalPrefix?: string | (() => string);\r\n /**\r\n * Default TTL for new entries. Accepts seconds or a duration string.\r\n *\r\n * @default Infinity\r\n */\r\n ttl?: CacheTtl;\r\n};\r\n\r\n/**\r\n * One row in {@link MockCacheDriver.callLog} — captures every public op\r\n * the driver received in arrival order. Useful for behavioral assertions\r\n * in downstream tests (\"did my service actually invalidate the cache?\").\r\n */\r\nexport type CacheCall = {\r\n /**\r\n * Operation name as it appears on the driver contract — `\"set\"`, `\"get\"`,\r\n * `\"remove\"`, `\"flush\"`, `\"removeNamespace\"`, `\"has\"`, etc.\r\n */\r\n operation: string;\r\n /**\r\n * Post-`parseKey` cache key when the op is key-addressed; `undefined` for\r\n * keyless ops (`flush`, `connect`, `disconnect`).\r\n */\r\n key?: string;\r\n /**\r\n * Raw arguments passed to the call site, in declaration order. Lets\r\n * callers assert on TTLs, options objects, vector payloads, etc.\r\n */\r\n args: unknown[];\r\n /**\r\n * `Date.now()` when the call was recorded. Useful for timing-related\r\n * assertions (e.g. \"the second invalidation came within 100ms\").\r\n */\r\n timestamp: number;\r\n};\r\n\r\n/**\r\n * Minimal `pg`-compatible client surface the cache driver depends on.\r\n *\r\n * Both `pg.Pool` and `pg.Client` from the `pg` package satisfy this — the\r\n * driver only ever calls `query(text, values)`. Typing it loosely keeps\r\n * `pg` strictly optional: consumers without the `pg` package installed\r\n * never hit a missing import.\r\n */\r\nexport type PgClientLike = {\r\n query(text: string, values?: unknown[]): Promise<{ rows: any[]; rowCount?: number | null }>;\r\n};\r\n\r\nexport type PgCacheOptions = {\r\n /**\r\n * User-supplied `pg.Pool` or `pg.Client`. The driver does NOT own the\r\n * connection lifecycle — `cache.disconnect()` will not close this client.\r\n */\r\n client: PgClientLike;\r\n /**\r\n * Database table name. Default: `\"warlock_cache\"`.\r\n *\r\n * Sanitized: only `[A-Za-z0-9_]` are allowed; anything else throws\r\n * {@link CacheConfigurationError} at `setOptions` time.\r\n */\r\n table?: string;\r\n /**\r\n * Global key prefix, applied via `parseKey` (same semantics as the other drivers).\r\n */\r\n globalPrefix?: string | (() => string);\r\n /**\r\n * Default TTL for new entries. Accepts seconds or a duration string\r\n * (`\"1h\"`, `\"7d\"`, …).\r\n *\r\n * @default Infinity\r\n */\r\n ttl?: CacheTtl;\r\n /**\r\n * Optional pgvector configuration. When present, the driver provisions an\r\n * `embedding VECTOR(dimensions)` column + similarity index, and `similar()`\r\n * queries via the `<=>` cosine-distance operator.\r\n *\r\n * Requires the pgvector extension (`CREATE EXTENSION vector;`) — the driver\r\n * verifies its presence on the first vector operation and throws\r\n * {@link CacheConfigurationError} if missing.\r\n *\r\n * Omit this block (or remove it) to run the driver in KV-only mode —\r\n * `set({ vector })` and `similar()` then throw {@link CacheUnsupportedError}.\r\n */\r\n vector?: {\r\n /**\r\n * Vector dimension count. Must match the embedder you use throughout the\r\n * lifetime of the table. Mixing dimensions on the same table is unsupported.\r\n */\r\n dimensions: number;\r\n /**\r\n * pgvector index strategy. `hnsw` (default) is faster to query and slightly\r\n * slower to build; `ivfflat` is faster to build but typically slower to query.\r\n *\r\n * @default \"hnsw\"\r\n */\r\n index?: \"hnsw\" | \"ivfflat\";\r\n };\r\n};\r\n\r\nexport interface CacheDriver<ClientType, Options> {\r\n /**\r\n * The cache driver options\r\n */\r\n options: Options;\r\n /**\r\n * Cache driver name\r\n */\r\n name: string;\r\n /**\r\n * Set logging state\r\n */\r\n setLoggingState(shouldLog: boolean): any;\r\n /**\r\n * Remove all cached items by namespace\r\n */\r\n removeNamespace(namespace: string): Promise<any>;\r\n /**\r\n * Set the cache driver options\r\n */\r\n setOptions(options: Options): any;\r\n /**\r\n * Parse the key to be used in the cache\r\n */\r\n parseKey(key: CacheKey): string;\r\n /**\r\n * Set a value in the cache.\r\n *\r\n * @param key The cache key, could be an object or string\r\n * @param value The value to be stored in the cache\r\n * @param ttlOrOptions Either a TTL (seconds number, or duration string like `\"1h\"`),\r\n * or a full {@link CacheSetOptions} object.\r\n *\r\n * @example\r\n * // Positional TTL (back-compat)\r\n * await cache.set(\"user:1\", user, 3600);\r\n *\r\n * @example\r\n * // Human-readable duration\r\n * await cache.set(\"user:1\", user, \"1h\");\r\n *\r\n * @example\r\n * // Rich options\r\n * await cache.set(\"user:1\", user, { ttl: \"1h\", tags: [\"users\"], onConflict: \"create\" });\r\n */\r\n set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any>;\r\n /**\r\n * Get a value from the cache\r\n */\r\n get<T = any>(key: CacheKey): Promise<T | null>;\r\n /**\r\n * Remove a value from the cache\r\n */\r\n remove(key: CacheKey): Promise<void>;\r\n /**\r\n * Flush the entire cache\r\n */\r\n flush(): Promise<void>;\r\n /**\r\n * Connect to the cache driver\r\n */\r\n connect(): Promise<any>;\r\n /**\r\n * The cache client\r\n */\r\n client?: ClientType;\r\n /**\r\n * Disconnect the cache driver\r\n */\r\n disconnect(): Promise<void>;\r\n /**\r\n * Check if a key exists in the cache without fetching its value\r\n */\r\n has(key: CacheKey): Promise<boolean>;\r\n /**\r\n * Get value from cache or execute callback and cache the result.\r\n *\r\n * The second argument accepts a TTL (number of seconds or duration string like `\"1h\"`)\r\n * or a full {@link RememberOptions} object when you need to attach tags or route to\r\n * a non-default driver.\r\n *\r\n * @example\r\n * // Positional TTL — the common case\r\n * const user = await cache.remember(\"user.1\", \"1h\", () => db.users.find(1));\r\n *\r\n * @example\r\n * // Options form — tag the cache-miss write for bulk invalidation\r\n * const user = await cache.remember(\"user.1\", { ttl: \"1h\", tags: [\"users\"] }, () =>\r\n * db.users.find(1),\r\n * );\r\n */\r\n remember<T = any>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T>;\r\n /**\r\n * Stale-while-revalidate. Returns the cached value when fresh; returns\r\n * the stale value + kicks off a background refresh when within the\r\n * `freshTtl..staleTtl` window; blocks like a normal miss when past\r\n * `staleTtl`. Concurrent stale-window callers share one in-flight\r\n * refresh.\r\n *\r\n * Background refresh failures preserve the stale entry and emit an\r\n * `error` event — the stale-returning caller never sees the failure.\r\n *\r\n * @example\r\n * const product = await cache.swr(\r\n * \"product.42\",\r\n * { freshTtl: \"1m\", staleTtl: \"1h\" },\r\n * () => db.products.find(42),\r\n * );\r\n */\r\n swr<T = any>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T>;\r\n /**\r\n * Get value and remove it from cache (atomic operation)\r\n */\r\n pull<T = any>(key: CacheKey): Promise<T | null>;\r\n /**\r\n * Set a value in cache permanently (no expiration)\r\n */\r\n forever<T = any>(key: CacheKey, value: T): Promise<T>;\r\n /**\r\n * Atomically read, transform, and write a cached value.\r\n *\r\n * The callback receives the current value (or `null` on miss) and returns the\r\n * next value. Returning `null` removes the key. TTL is preserved unless\r\n * explicitly overridden via options.\r\n *\r\n * @example\r\n * await cache.update<User>(\"user:1\", (current) => {\r\n * if (!current) return null;\r\n * return { ...current, lastSeen: Date.now() };\r\n * });\r\n */\r\n update<T = any>(\r\n key: CacheKey,\r\n fn: (current: T | null) => T | null | Promise<T | null>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T | null>;\r\n /**\r\n * Shallow-merge a partial object into a cached value.\r\n *\r\n * If the key is missing, treats the current value as `{}`. Preserves TTL by default.\r\n *\r\n * @example\r\n * await cache.merge<User>(\"user:1\", { name: \"Jane\" });\r\n */\r\n merge<T extends Record<string, any> = Record<string, any>>(\r\n key: CacheKey,\r\n partial: Partial<T>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T>;\r\n /**\r\n * List sub-API factory — returns a {@link CacheListAccessor} bound to the given key.\r\n *\r\n * @example\r\n * const recent = cache.list<Event>(\"recent-events\");\r\n * await recent.push(event);\r\n * const last10 = await recent.slice(0, 10);\r\n */\r\n list<T = any>(key: CacheKey): CacheListAccessor<T>;\r\n /**\r\n * Acquire a distributed lock, run `fn`, and auto-release. Returns a\r\n * {@link LockOutcome} so callers can distinguish \"ran and produced this value\"\r\n * from \"skipped because someone else holds the lock.\"\r\n *\r\n * Built on top of `set({ onConflict: \"create\" })` — Redis-native, emulated\r\n * on other drivers (single-process semantics elsewhere).\r\n *\r\n * @example\r\n * const outcome = await cache.lock(\"lock.import\", \"5m\", async () => {\r\n * await runImport();\r\n * return \"done\";\r\n * });\r\n */\r\n lock<T>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\r\n fn: () => Promise<T>,\r\n ): Promise<LockOutcome<T>>;\r\n /**\r\n * Increment a numeric value in cache\r\n *\r\n * @param key The cache key\r\n * @param value The value to increment by (default 1)\r\n */\r\n increment(key: CacheKey, value?: number): Promise<number>;\r\n /**\r\n * Decrement a numeric value in cache\r\n *\r\n * @param key The cache key\r\n * @param value The value to decrement by (default 1)\r\n */\r\n decrement(key: CacheKey, value?: number): Promise<number>;\r\n /**\r\n * Get multiple values from cache at once\r\n */\r\n many(keys: CacheKey[]): Promise<any[]>;\r\n /**\r\n * Set multiple values in cache at once\r\n */\r\n setMany(items: Record<string, any>, ttl?: number): Promise<void>;\r\n /**\r\n * Register an event listener\r\n */\r\n on(event: CacheEventType, handler: CacheEventHandler): this;\r\n /**\r\n * Remove an event listener\r\n */\r\n off(event: CacheEventType, handler: CacheEventHandler): this;\r\n /**\r\n * Register a one-time event listener\r\n */\r\n once(event: CacheEventType, handler: CacheEventHandler): this;\r\n /**\r\n * Set if not exists (atomic operation)\r\n * Returns true if key was set, false if key already existed\r\n * Note: Not all drivers support this operation\r\n */\r\n setNX?(key: CacheKey, value: any, ttl?: number): Promise<boolean>;\r\n /**\r\n * Create a tagged cache instance for the given tags\r\n */\r\n tags(tags: string[]): TaggedCacheDriver;\r\n /**\r\n * Similarity retrieval. Returns the nearest stored entries to `vector` by\r\n * cosine similarity, ordered by descending score.\r\n *\r\n * Drivers without a similarity index throw {@link CacheUnsupportedError}.\r\n * Memory-family drivers brute-force scan in O(N) — suitable for development\r\n * but not for production knowledge bases beyond a few thousand entries; use\r\n * the `pg` driver (with pgvector) or `redis` driver (with RediSearch) instead.\r\n *\r\n * @example\r\n * const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });\r\n */\r\n similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]>;\r\n}\r\n\r\n/**\r\n * Accessor for list-shaped cached values.\r\n *\r\n * Defaults to read-mutate-write semantics on non-native drivers (memory, file, LRU).\r\n * The Redis driver overrides this with native `LPUSH` / `RPUSH` / `LRANGE` / `LTRIM` for O(1) ops.\r\n */\r\nexport interface CacheListAccessor<T = any> {\r\n /**\r\n * Append one or more items to the tail of the list. Returns the new length.\r\n */\r\n push(...items: T[]): Promise<number>;\r\n /**\r\n * Prepend one or more items to the head of the list. Returns the new length.\r\n */\r\n unshift(...items: T[]): Promise<number>;\r\n /**\r\n * Remove and return the tail item, or `null` if the list is empty.\r\n */\r\n pop(): Promise<T | null>;\r\n /**\r\n * Remove and return the head item, or `null` if the list is empty.\r\n */\r\n shift(): Promise<T | null>;\r\n /**\r\n * Return a slice of the list. End is exclusive, mirroring `Array.prototype.slice`.\r\n */\r\n slice(start?: number, end?: number): Promise<T[]>;\r\n /**\r\n * Return the full list.\r\n */\r\n all(): Promise<T[]>;\r\n /**\r\n * Return the length of the list.\r\n */\r\n length(): Promise<number>;\r\n /**\r\n * Trim the list to the inclusive range `[start, end]`. Outside elements are dropped.\r\n */\r\n trim(start: number, end: number): Promise<void>;\r\n /**\r\n * Remove the entire list.\r\n */\r\n clear(): Promise<void>;\r\n}\r\n\r\n/**\r\n * One-shot tagged handle returned by `ScopedCacheContract.tags(...)`.\r\n *\r\n * Identical write surface to {@link TaggedCacheDriver}, except the underlying\r\n * scope's prefix is applied to every key and the scope's default TTL/tags\r\n * still flow through. Tags supplied here merge additively with scope-level\r\n * tags.\r\n */\r\nexport interface TaggedScopedCacheContract {\r\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\r\n get<T = any>(key: CacheKey): Promise<T | null>;\r\n has(key: CacheKey): Promise<boolean>;\r\n remove(key: CacheKey): Promise<void>;\r\n pull<T = any>(key: CacheKey): Promise<T | null>;\r\n forever<T = any>(key: CacheKey, value: T): Promise<T>;\r\n setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean>;\r\n remember<T = any>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T>;\r\n increment(key: CacheKey, value?: number): Promise<number>;\r\n decrement(key: CacheKey, value?: number): Promise<number>;\r\n /**\r\n * Invalidate every entry tagged with the union of scope tags + handle tags.\r\n * Forwarded to the underlying tag index — tags are global, scope-agnostic.\r\n */\r\n invalidate(): Promise<void>;\r\n}\r\n\r\n/**\r\n * Scoped view over the cache. Returned by `cache.namespace(prefix, options?)`.\r\n *\r\n * A scope prepends its `prefix` to every key, applies optional default TTL/tags\r\n * to every write, and forwards everything else to the underlying source. It\r\n * stores nothing of its own — purely a convenience wrapper.\r\n *\r\n * @example\r\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\r\n * await chat.set(\"messages.1\", msg); // → \"chats.10.messages.1\", TTL 30d\r\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call ttl wins\r\n * await chat.clear(); // wipe the whole scope\r\n */\r\nexport interface ScopedCacheContract {\r\n /** The fully-qualified prefix this scope prepends to every key. */\r\n readonly prefix: string;\r\n\r\n /**\r\n * Nested scope. Inherits the parent's defaults; child's own options override.\r\n *\r\n * @example\r\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\r\n * const typing = chat.namespace(\"typing\", { ttl: \"5s\" });\r\n * // typing.prefix === \"chats.10.typing\"\r\n */\r\n namespace(prefix: string, options?: CacheNamespaceOptions): ScopedCacheContract;\r\n\r\n /**\r\n * One-shot tagged write handle. Tags merge additively with scope defaults.\r\n */\r\n tags(tags: string[]): TaggedScopedCacheContract;\r\n\r\n /** Wipe every entry under this scope's prefix. Sugar for `removeNamespace(prefix)`. */\r\n clear(): Promise<void>;\r\n\r\n // Reads\r\n get<T = any>(key: CacheKey): Promise<T | null>;\r\n has(key: CacheKey): Promise<boolean>;\r\n many(keys: CacheKey[]): Promise<any[]>;\r\n pull<T = any>(key: CacheKey): Promise<T | null>;\r\n\r\n // Writes\r\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\r\n setMany(items: Record<string, any>, ttl?: number): Promise<void>;\r\n setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean>;\r\n forever<T = any>(key: CacheKey, value: T): Promise<T>;\r\n remove(key: CacheKey): Promise<void>;\r\n\r\n // Read-or-compute\r\n remember<T = any>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T>;\r\n swr<T = any>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T>;\r\n\r\n // Mutations\r\n increment(key: CacheKey, value?: number): Promise<number>;\r\n decrement(key: CacheKey, value?: number): Promise<number>;\r\n update<T = any>(\r\n key: CacheKey,\r\n fn: (current: T | null) => T | null | Promise<T | null>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T | null>;\r\n merge<T extends Record<string, any> = Record<string, any>>(\r\n key: CacheKey,\r\n partial: Partial<T>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T>;\r\n\r\n // Structured accessors\r\n list<T = any>(key: CacheKey): CacheListAccessor<T>;\r\n\r\n // Coordination\r\n lock<T>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\r\n fn: () => Promise<T>,\r\n ): Promise<LockOutcome<T>>;\r\n\r\n // Similarity (delegated; throws CacheUnsupportedError if the driver lacks it)\r\n similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]>;\r\n}\r\n\r\nexport type CacheData = {\r\n /**\r\n * Value stored in the cache\r\n */\r\n data: any;\r\n /**\r\n * The expiration date in milliseconds\r\n */\r\n expiresAt?: number;\r\n /**\r\n * Time to live in seconds\r\n */\r\n ttl?: number;\r\n /**\r\n * Freshness deadline as a millisecond timestamp. Used by `swr()` — entries\r\n * with `staleAt` in the future are \"fresh\"; past `staleAt` but before\r\n * `expiresAt` are \"stale-but-revalidatable\" and trigger a background\r\n * refresh on the next read. Optional — entries written through plain\r\n * `set()` skip this field entirely and `swr()` treats them as always-fresh.\r\n */\r\n staleAt?: number;\r\n};\r\n\r\nexport type DriverClass = new () => CacheDriver<any, any>;\r\n\r\ntype DefaultDrivers =\r\n | \"redis\"\r\n | \"file\"\r\n | \"memory\"\r\n | \"memoryExtended\"\r\n | \"null\"\r\n | \"lru\"\r\n | \"pg\"\r\n | \"mock\";\r\n\r\ntype MergeWithDefaultDrivers<T> = T extends undefined ? DefaultDrivers : DefaultDrivers | T;\r\n\r\nexport type CacheConfigurations<\r\n T extends string | undefined = undefined,\r\n DriverName = MergeWithDefaultDrivers<T>,\r\n> = {\r\n /**\r\n * The default cache driver name\r\n */\r\n default?: DriverName;\r\n /**\r\n * Determine whether to log or not\r\n *\r\n * @default true\r\n */\r\n logging?: boolean;\r\n /**\r\n * The cache drivers list\r\n */\r\n drivers: {\r\n redis?: typeof RedisCacheDriver;\r\n file?: typeof FileCacheDriver;\r\n null?: typeof NullCacheDriver;\r\n memory?: typeof MemoryCacheDriver;\r\n memoryExtended?: typeof MemoryExtendedCacheDriver;\r\n lru?: typeof LRUMemoryCacheDriver;\r\n pg?: typeof PgCacheDriver;\r\n mock?: typeof MockCacheDriver;\r\n } & {\r\n [key in Extract<T, string>]?: typeof BaseCacheDriver<any, any> | undefined;\r\n };\r\n /**\r\n * The cache driver options\r\n */\r\n options: {\r\n redis?: RedisOptions;\r\n file?: FileCacheOptions;\r\n memory?: MemoryCacheOptions;\r\n memoryExtended?: MemoryExtendedCacheOptions;\r\n null?: NullCacheDriverOptions;\r\n lru?: LRUMemoryCacheOptions;\r\n pg?: PgCacheOptions;\r\n mock?: MockCacheOptions;\r\n } & {\r\n [key in Extract<T, string>]?: GenericObject;\r\n };\r\n};\r\n","import { rtrim } from \"@mongez/reinforcements\";\r\nimport ms, { StringValue } from \"ms\";\r\nimport type { CacheKey, CacheSetOptions, CacheTtl, RememberOptions } from \"./types\";\r\nimport { CacheConfigurationError } from \"./types\";\r\n\r\n/**\r\n * Make a proper key for the cache\r\n */\r\nexport function parseCacheKey(\r\n key: CacheKey,\r\n options: { globalPrefix?: string | (() => string) } = {},\r\n): string {\r\n if (typeof key === \"object\") {\r\n key = JSON.stringify(key);\r\n }\r\n\r\n // remove any curly braces and double quotes along with []\r\n key = key.replace(/[{}\"[\\]]/g, \"\").replaceAll(/[:,]/g, \".\");\r\n\r\n const cachePrefix =\r\n typeof options.globalPrefix === \"function\" ? options.globalPrefix() : options.globalPrefix;\r\n\r\n return rtrim(String(cachePrefix ? rtrim(cachePrefix, \".\") + \".\" + key : key), \".\");\r\n}\r\n\r\n/**\r\n * Parse a TTL value into seconds.\r\n *\r\n * Accepts:\r\n * - a number (already in seconds) — returned unchanged\r\n * - `Infinity` — no expiration, returned unchanged\r\n * - a human-readable duration string (e.g. `\"1h\"`, `\"30m\"`, `\"7d\"`) — parsed via `ms` then converted to seconds\r\n *\r\n * Throws `CacheConfigurationError` on unparseable strings or negative numbers.\r\n *\r\n * @example\r\n * parseTtl(3600); // 3600\r\n * parseTtl(\"1h\"); // 3600\r\n * parseTtl(\"7d\"); // 604800\r\n * parseTtl(Infinity); // Infinity\r\n */\r\nexport function parseTtl(input: CacheTtl): number {\r\n if (typeof input === \"number\") {\r\n if (input < 0) {\r\n throw new CacheConfigurationError(`Invalid TTL: negative number (${input}).`);\r\n }\r\n\r\n return input;\r\n }\r\n\r\n if (typeof input !== \"string\" || input.trim() === \"\") {\r\n throw new CacheConfigurationError(\r\n `Invalid TTL: expected number or duration string, got ${typeof input}.`,\r\n );\r\n }\r\n\r\n const milliseconds = ms(input as StringValue);\r\n\r\n if (milliseconds === undefined || Number.isNaN(milliseconds)) {\r\n throw new CacheConfigurationError(\r\n `Invalid TTL duration string: \"${input}\". Expected forms like \"1h\", \"30m\", \"7d\".`,\r\n );\r\n }\r\n\r\n return Math.floor(milliseconds / 1000);\r\n}\r\n\r\n/**\r\n * Convert an absolute `expiresAt` (Date or epoch milliseconds) into a\r\n * relative TTL in seconds.\r\n *\r\n * Throws {@link CacheConfigurationError} when the deadline is in the past —\r\n * the caller almost certainly has a bug (stale timestamp, wrong unit, etc.)\r\n * and silently storing an already-expired entry would hide it.\r\n *\r\n * @example\r\n * expiresAtToTtl(new Date(Date.now() + 60_000)); // ~60\r\n * expiresAtToTtl(Date.now() + 30 * 60 * 1000); // ~1800\r\n */\r\nexport function expiresAtToTtl(expiresAt: number | Date): number {\r\n const deadline = expiresAt instanceof Date ? expiresAt.getTime() : expiresAt;\r\n const relativeMs = deadline - Date.now();\r\n\r\n if (relativeMs <= 0) {\r\n throw new CacheConfigurationError(\r\n `\\`expiresAt\\` must be in the future; got ${new Date(deadline).toISOString()}.`,\r\n );\r\n }\r\n\r\n return Math.ceil(relativeMs / 1000);\r\n}\r\n\r\n/**\r\n * Coerce the polymorphic 3rd `set` argument into a uniform `CacheSetOptions`\r\n * shape. Lets callers (and `BaseCacheDriver.resolveSetOptions`) skip per-shape\r\n * branching.\r\n *\r\n * - `undefined` / `null` → `{}` (resolves to driver-level defaults later)\r\n * - `number` / `string` (positional TTL) → `{ ttl }`\r\n * - already an options object → returned as-is\r\n */\r\nexport function normalizeToOptions(\r\n input?: CacheTtl | CacheSetOptions,\r\n): CacheSetOptions {\r\n if (input === undefined || input === null) {\r\n return {};\r\n }\r\n\r\n if (typeof input === \"number\" || typeof input === \"string\") {\r\n return { ttl: input };\r\n }\r\n\r\n return input;\r\n}\r\n\r\n/**\r\n * Sibling of {@link normalizeToOptions} for the `remember()` call site, where\r\n * the polymorphic 2nd argument is `CacheTtl | RememberOptions` (no `expiresAt`,\r\n * no `onConflict`). Returns the same shape so callers can `{ ...opts, ... }`\r\n * without branching.\r\n *\r\n * @example\r\n * normalizeToRememberOptions(60); // { ttl: 60 }\r\n * normalizeToRememberOptions(\"1h\"); // { ttl: \"1h\" }\r\n * normalizeToRememberOptions({ ttl: \"1h\", tags: [\"x\"] }); // returned as-is\r\n */\r\nexport function normalizeToRememberOptions(\r\n input?: CacheTtl | RememberOptions,\r\n): RememberOptions {\r\n if (input === undefined || input === null) {\r\n return {};\r\n }\r\n\r\n if (typeof input === \"number\" || typeof input === \"string\") {\r\n return { ttl: input };\r\n }\r\n\r\n return input;\r\n}\r\n\r\n/**\r\n * Resolve the final TTL in seconds for a `set` call. Precedence:\r\n *\r\n * 1. Caller's `ttl` (number or duration string) wins.\r\n * 2. Otherwise, caller's `expiresAt` is converted to relative seconds.\r\n * 3. Otherwise, `fallback` is used (driver-level default — typically\r\n * `Infinity` when no default is configured, meaning \"never expires\").\r\n *\r\n * Throws {@link CacheConfigurationError} when `ttl` and `expiresAt` are\r\n * supplied together (mutually exclusive).\r\n */\r\nexport function resolveTtl(\r\n ttl: CacheTtl | undefined,\r\n expiresAt: number | Date | undefined,\r\n fallback: number,\r\n): number {\r\n if (ttl !== undefined && expiresAt !== undefined) {\r\n throw new CacheConfigurationError(\r\n \"Cache set options cannot specify both `ttl` and `expiresAt` — choose one.\",\r\n );\r\n }\r\n\r\n if (ttl !== undefined) {\r\n return parseTtl(ttl);\r\n }\r\n\r\n if (expiresAt !== undefined) {\r\n return expiresAtToTtl(expiresAt);\r\n }\r\n\r\n return fallback;\r\n}\r\n\r\n/**\r\n * Combine any number of tag lists into a single deduped array, dropping\r\n * `undefined`/empty entries. Returns `undefined` when no tags survive — lets\r\n * callers skip emitting empty `tags: []` into option payloads.\r\n *\r\n * Used by scoped-cache merging where scope tags + handle tags + per-call tags\r\n * must union additively without duplicates.\r\n *\r\n * @example\r\n * mergeTagSets([\"a\", \"b\"], [\"b\", \"c\"]); // [\"a\", \"b\", \"c\"]\r\n * mergeTagSets(undefined, [\"x\"]); // [\"x\"]\r\n * mergeTagSets(undefined, undefined); // undefined\r\n * mergeTagSets([], []); // undefined\r\n */\r\nexport function mergeTagSets(\r\n ...lists: (string[] | undefined)[]\r\n): string[] | undefined {\r\n const flat: string[] = [];\r\n\r\n for (const list of lists) {\r\n if (!list || list.length === 0) {\r\n continue;\r\n }\r\n\r\n flat.push(...list);\r\n }\r\n\r\n if (flat.length === 0) {\r\n return undefined;\r\n }\r\n\r\n return Array.from(new Set(flat));\r\n}\r\n\r\n/**\r\n * Add extra tags to any option-bag that already shapes `tags?: string[]`.\r\n * Pure — clones the input shape, never mutates. Tags are appended (caller\r\n * is responsible for de-duplication if needed; pair with {@link mergeTagSets}).\r\n *\r\n * @example\r\n * injectTags({ ttl: \"1h\" }, [\"unread\"]); // { ttl: \"1h\", tags: [\"unread\"] }\r\n * injectTags({ tags: [\"a\"] }, [\"b\"]); // { tags: [\"a\", \"b\"] }\r\n */\r\nexport function injectTags<T extends { tags?: string[] }>(\r\n options: T,\r\n extraTags: string[],\r\n): T {\r\n if (extraTags.length === 0) {\r\n return options;\r\n }\r\n\r\n return {\r\n ...options,\r\n tags: [...(options.tags ?? []), ...extraTags],\r\n };\r\n}\r\n\r\n/**\r\n * Cosine similarity between two equal-length numeric vectors.\r\n *\r\n * Returns a value in `[-1, 1]` where `1` means perfectly aligned, `0` means\r\n * orthogonal, and `-1` means opposing. For typical embedding spaces (where\r\n * vectors live in the positive cone) the practical range is `[0, 1]`.\r\n *\r\n * Throws {@link CacheConfigurationError} on dimension mismatch — fail loud at\r\n * the call site rather than silently returning a misleading score. A zero-norm\r\n * vector on either side returns `0` (no defined direction to compare).\r\n *\r\n * @example\r\n * cosineSimilarity([1, 0, 0], [1, 0, 0]); // 1\r\n * cosineSimilarity([1, 0, 0], [0, 1, 0]); // 0\r\n */\r\nexport function cosineSimilarity(a: number[], b: number[]): number {\r\n if (a.length !== b.length) {\r\n throw new CacheConfigurationError(\r\n `Vector dimension mismatch: got ${a.length} and ${b.length}.`,\r\n );\r\n }\r\n\r\n if (a.length === 0) {\r\n throw new CacheConfigurationError(\r\n \"Vector dimension mismatch: empty vector cannot be compared.\",\r\n );\r\n }\r\n\r\n let dot = 0;\r\n let normA = 0;\r\n let normB = 0;\r\n\r\n for (let i = 0; i < a.length; i++) {\r\n const x = a[i];\r\n const y = b[i];\r\n dot += x * y;\r\n normA += x * x;\r\n normB += y * y;\r\n }\r\n\r\n if (normA === 0 || normB === 0) {\r\n return 0;\r\n }\r\n\r\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\r\n}\r\n\r\nexport enum CACHE_FOR {\r\n /**\r\n * Cache for 30 Minutes (in seconds)\r\n */\r\n HALF_HOUR = 1800,\r\n /**\r\n * Cache for 1 Hour (in seconds)\r\n */\r\n ONE_HOUR = 3600,\r\n /**\r\n * Cache for 12 Hours (in seconds)\r\n */\r\n HALF_DAY = 43200,\r\n /**\r\n * Cache for 24 Hours (in seconds)\r\n */\r\n ONE_DAY = 86400,\r\n /**\r\n * Cache for 7 Days (in seconds)\r\n */\r\n ONE_WEEK = 604800,\r\n /**\r\n * Cache for 15 Days (in seconds)\r\n */\r\n HALF_MONTH = 1296000,\r\n /**\r\n * Cache for 30 Days (in seconds)\r\n */\r\n ONE_MONTH = 2592000,\r\n /**\r\n * Cache for 60 Days (in seconds)\r\n */\r\n TWO_MONTHS = 5184000,\r\n /**\r\n * Cache for 180 Days (in seconds)\r\n */\r\n SIX_MONTHS = 15768000,\r\n /**\r\n * Cache for 365 Days (in seconds)\r\n */\r\n ONE_YEAR = 31536000,\r\n}\r\n","import type { ScopedCache } from \"./scoped-cache\";\nimport type {\n CacheKey,\n CacheSetOptions,\n CacheTtl,\n RememberOptions,\n TaggedScopedCacheContract,\n} from \"./types\";\nimport {\n injectTags,\n mergeTagSets,\n normalizeToOptions,\n normalizeToRememberOptions,\n parseCacheKey,\n} from \"./utils\";\n\n/**\n * One-shot tagged write handle on top of a {@link ScopedCache}.\n *\n * **Role.** Returned by `scope.tags([...])`. Adds a fixed list of tags to\n * every write produced through this handle, on top of any tags the parent\n * scope already contributes. Stateless except for the captured tag list.\n *\n * **Responsibility.**\n * - Owns: appending the handle's tags to writes, delegating tag-index\n * bookkeeping for `setNX` (which lacks an inline `tags` knob on the driver\n * contract), and computing the union for `invalidate()` calls.\n * - Does NOT own: storage, prefix-prepending (delegated to the scope),\n * default `ttl` (delegated to the scope), or any kind of long-lived state.\n *\n * Tags compose additively: scope tags + handle tags + per-call tags, all\n * unioned and deduped. The handle never replaces scope tags — `invalidate()`\n * always sees the full union.\n *\n * @example\n * // Inside application code — scope provides the per-user tag automatically:\n * const feed = cache.namespace(`feed.${userId}`, { tags: [`user.${userId}`] });\n *\n * await feed.tags([\"unread\"]).set(\"messages.1\", message);\n * // → tagged with [user.<id>, unread]\n *\n * await feed.tags([\"unread\"]).invalidate();\n * // → wipes everything tagged with user.<id> OR unread.\n */\nexport class TaggedScopedCache implements TaggedScopedCacheContract {\n /**\n * The {@link ScopedCache} this handle delegates to. Held by reference so\n * scope-default changes (none today, but the option is preserved for the\n * future) are visible without rebuilding the handle.\n */\n protected readonly scope: ScopedCache;\n\n /**\n * Tags this handle contributes to every write. Cloned at the call site so\n * later mutation of the input array doesn't leak in.\n */\n protected readonly handleTags: string[];\n\n /**\n * Build a tagged handle. Constructed via `scope.tags([...])` — users never\n * call this directly.\n */\n public constructor(scope: ScopedCache, handleTags: string[]) {\n this.scope = scope;\n this.handleTags = [...handleTags];\n }\n\n /**\n * Write the scoped key with the handle's tags appended to whatever the\n * caller passed. Scope-level tags are added on top by the scope itself.\n */\n public set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const options = injectTags(normalizeToOptions(ttlOrOptions), this.handleTags);\n\n return this.scope.set(key, value, options);\n }\n\n /**\n * Read the scoped key. Tags don't affect reads — pass-through.\n */\n public get<T = any>(key: CacheKey): Promise<T | null> {\n return this.scope.get<T>(key);\n }\n\n /**\n * Check presence of the scoped key.\n */\n public has(key: CacheKey): Promise<boolean> {\n return this.scope.has(key);\n }\n\n /**\n * Remove the scoped key. The tag-index entry will eventually be cleaned up\n * by `invalidate()`; we don't proactively rewrite it here for cost reasons.\n */\n public remove(key: CacheKey): Promise<void> {\n return this.scope.remove(key);\n }\n\n /**\n * Read-and-remove the scoped key.\n */\n public pull<T = any>(key: CacheKey): Promise<T | null> {\n return this.scope.pull<T>(key);\n }\n\n /**\n * Permanent write with handle tags applied. Bypasses both the scope's and\n * the caller's TTL (forever means forever) — only tags get injected.\n */\n public forever<T = any>(key: CacheKey, value: T): Promise<T> {\n return this.scope.set(key, value, {\n ttl: Infinity,\n tags: this.handleTags,\n }) as Promise<T>;\n }\n\n /**\n * Atomic create-or-skip with the handle's tags applied on success. The\n * driver contract has no inline `tags` knob on `setNX`, so we register the\n * tag relationship manually after a successful write.\n */\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n const wasSet = await this.scope.setNX(key, value, ttl);\n\n if (!wasSet) {\n return false;\n }\n\n const allTags = mergeTagSets(this.scope.defaults.tags, this.handleTags);\n\n if (!allTags || allTags.length === 0) {\n return true;\n }\n\n const scopedKey = this.buildScopedKey(key);\n const parsedKey = this.scope.source.parseKey(scopedKey);\n const tagged = this.scope.source.tags(allTags) as unknown as {\n storeTagRelationship: (parsed: string) => Promise<void>;\n };\n\n await tagged.storeTagRelationship(parsedKey);\n\n return true;\n }\n\n /**\n * Read-or-compute with handle tags appended on the cache-miss write.\n */\n public remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n const options = injectTags(\n normalizeToRememberOptions(ttlOrOptions),\n this.handleTags,\n );\n\n return this.scope.remember<T>(key, options, callback);\n }\n\n /**\n * Atomic counter increment on the scoped key. Tags aren't applied to\n * subsequent increments — they're attached at first-write time.\n */\n public increment(key: CacheKey, value?: number): Promise<number> {\n return this.scope.increment(key, value);\n }\n\n /**\n * Atomic counter decrement on the scoped key. See {@link increment}.\n */\n public decrement(key: CacheKey, value?: number): Promise<number> {\n return this.scope.decrement(key, value);\n }\n\n /**\n * Wipe every entry tagged with the union of (scope tags + handle tags).\n * Tags are global across the package, so this reaches outside the scope's\n * prefix when scope tags are also used elsewhere.\n */\n public async invalidate(): Promise<void> {\n const allTags = mergeTagSets(this.scope.defaults.tags, this.handleTags);\n\n if (!allTags || allTags.length === 0) {\n return;\n }\n\n await this.scope.source.tags(allTags).invalidate();\n }\n\n /**\n * Compute the source-side key the same way `ScopedCache.scopedKey` does —\n * needed for `setNX`, where we have to register the tag relationship by\n * hand because the driver contract doesn't accept inline tags there.\n */\n protected buildScopedKey(key: CacheKey): string {\n const keyString = typeof key === \"string\" ? key : parseCacheKey(key);\n\n if (!keyString) {\n return this.scope.prefix;\n }\n\n return `${this.scope.prefix}.${keyString}`;\n }\n}\n","import { TaggedScopedCache } from \"./tagged-scoped-cache\";\nimport type {\n CacheDriver,\n CacheKey,\n CacheListAccessor,\n CacheNamespaceOptions,\n CacheSetOptions,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheSwrOptions,\n CacheTtl,\n LockOptions,\n LockOutcome,\n RememberOptions,\n ScopedCacheContract,\n TaggedScopedCacheContract,\n} from \"./types\";\nimport {\n mergeTagSets,\n normalizeToOptions,\n normalizeToRememberOptions,\n parseCacheKey,\n parseTtl,\n} from \"./utils\";\n\n/**\n * Scoped view over a cache source. Returned by `cache.namespace(prefix, options?)`.\n *\n * **Role.** A `ScopedCache` is a stateless wrapper that prepends a fixed\n * prefix to every key and applies optional default `ttl` / `tags` to every\n * write. Stores nothing itself — every call forwards to the underlying\n * `source` (typically the `CacheManager`, but any `CacheDriver` works).\n *\n * **Responsibility.**\n * - Owns: prefix-prepending of keys, normalization of nested-scope prefixes,\n * merging scope defaults into write options (`ttl`, `tags`), filtering\n * `similar()` hits to its own scope, and exposing `.clear()` as a sugar\n * for `removeNamespace(prefix)`.\n * - Does NOT own: actual storage, connection lifecycle, event listeners,\n * driver selection, or tag-index bookkeeping (delegated to the source's\n * tagged-cache machinery).\n *\n * Per-call options always win over scope defaults; tags merge additively\n * across (scope defaults + per-call) layers. Nested scopes inherit and may\n * override the parent's defaults — see {@link ScopedCache.namespace}.\n *\n * @example\n * const chat = cache.namespace(`chats.${id}`, { ttl: \"30d\" });\n *\n * await chat.set(\"messages.10\", msg); // 30d default\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call override\n * await chat.namespace(\"typing\", { ttl: \"5s\" }).set(\"user.42\", true);\n * await chat.clear();\n */\nexport class ScopedCache implements ScopedCacheContract {\n /**\n * Fully-qualified prefix prepended to every key handled by this scope.\n * Normalized through {@link parseCacheKey} on construction so colon-form\n * input (`\"chats:10\"`) and trailing dots compose cleanly with nested scopes.\n */\n public readonly prefix: string;\n\n /**\n * Underlying cache source. Public-readonly so co-located helpers\n * (`TaggedScopedCache`) can delegate without ceremony — not part of the\n * stable consumer API.\n *\n * @internal\n */\n public readonly source: CacheDriver<any, any>;\n\n /**\n * Defaults applied to every write through this scope. Per-call options\n * override `ttl`; `tags` merge additively across layers.\n *\n * @internal\n */\n public readonly defaults: CacheNamespaceOptions;\n\n /**\n * Build a scope. Constructed via `cache.namespace(prefix, options)` —\n * users never call this directly.\n */\n public constructor(\n source: CacheDriver<any, any>,\n prefix: string,\n defaults: CacheNamespaceOptions = {},\n ) {\n this.source = source;\n this.prefix = parseCacheKey(prefix);\n this.defaults = {\n ttl: defaults.ttl,\n tags: defaults.tags && defaults.tags.length > 0 ? [...defaults.tags] : undefined,\n };\n }\n\n /**\n * Build a nested scope. The child's prefix is `parent.child`; child options\n * override the parent's `ttl` and union into `tags`.\n *\n * @example\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\n * const typing = chat.namespace(\"typing\", { ttl: \"5s\" });\n * // typing.prefix === \"chats.10.typing\"\n */\n public namespace(prefix: string, options: CacheNamespaceOptions = {}): ScopedCacheContract {\n const childPrefix = `${this.prefix}.${parseCacheKey(prefix)}`;\n\n return new ScopedCache(this.source, childPrefix, {\n ttl: options.ttl ?? this.defaults.ttl,\n tags: mergeTagSets(this.defaults.tags, options.tags),\n });\n }\n\n /**\n * Return a one-shot tagged write handle. The handle's tags merge additively\n * with scope-level defaults — final tag list per write is the union of\n * (scope tags + handle tags + per-call tags), deduped.\n */\n public tags(tags: string[]): TaggedScopedCacheContract {\n return new TaggedScopedCache(this, tags);\n }\n\n /**\n * Wipe every entry under this scope's prefix. Sugar over\n * `source.removeNamespace(prefix)` — siblings outside the scope are\n * untouched.\n */\n public clear(): Promise<void> {\n return this.source.removeNamespace(this.prefix);\n }\n\n /**\n * Read the value at the scoped key. Forwards to the source after prefixing.\n */\n public get<T = any>(key: CacheKey): Promise<T | null> {\n return this.source.get<T>(this.scopedKey(key));\n }\n\n /**\n * Check presence of the scoped key without fetching the value.\n */\n public has(key: CacheKey): Promise<boolean> {\n return this.source.has(this.scopedKey(key));\n }\n\n /**\n * Batch-read scoped keys. Each input key is prefixed before forwarding.\n */\n public many(keys: CacheKey[]): Promise<any[]> {\n return this.source.many(keys.map((key) => this.scopedKey(key)));\n }\n\n /**\n * Read-and-remove. Returns the value or `null`; the entry is gone after.\n */\n public pull<T = any>(key: CacheKey): Promise<T | null> {\n return this.source.pull<T>(this.scopedKey(key));\n }\n\n /**\n * Write the scoped key. Per-call `ttl`/`tags` win over scope defaults;\n * `expiresAt` is preserved as-is (absolute deadlines are never overridden\n * by the scope's relative-ttl default).\n */\n public set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n return this.source.set(this.scopedKey(key), value, this.mergeSetOptions(ttlOrOptions));\n }\n\n /**\n * Batch-write under the scope. Caller's positional `ttl` wins; otherwise\n * the scope default is parsed to seconds (since `setMany` accepts only a\n * numeric ttl).\n */\n public setMany(items: Record<string, any>, ttl?: number): Promise<void> {\n const scoped: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(items)) {\n scoped[this.scopedKey(key)] = value;\n }\n\n return this.source.setMany(scoped, ttl ?? this.scopeTtlSeconds());\n }\n\n /**\n * Atomic create-or-skip on the scoped key. Throws when the underlying\n * source has no `setNX` (driver-specific — Redis-only today).\n */\n public setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n if (!this.source.setNX) {\n throw new Error(\n `setNX is not supported by the underlying cache source: ${this.source.name}`,\n );\n }\n\n return this.source.setNX(this.scopedKey(key), value, ttl ?? this.scopeTtlSeconds());\n }\n\n /**\n * Permanent write (no expiration). Bypasses the scope's `ttl` default —\n * `forever` always means forever, regardless of scope policy.\n */\n public forever<T = any>(key: CacheKey, value: T): Promise<T> {\n return this.source.forever<T>(this.scopedKey(key), value);\n }\n\n /**\n * Remove a single scoped key.\n */\n public remove(key: CacheKey): Promise<void> {\n return this.source.remove(this.scopedKey(key));\n }\n\n /**\n * Read-or-compute. Cache-miss writes pick up the scope's default `ttl`\n * and `tags` unless the caller passed an options object that overrides.\n */\n public remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n return this.source.remember<T>(\n this.scopedKey(key),\n this.mergeRememberOptions(ttlOrOptions),\n callback,\n );\n }\n\n /**\n * Stale-while-revalidate on the scoped key. Scope-level `tags` merge\n * additively with `options.tags`; `freshTtl`/`staleTtl` always come from\n * the caller (no scope-default precedence — the SWR shape is too\n * specific to the call site to inherit).\n */\n public swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n const merged: CacheSwrOptions = {\n ...options,\n tags: mergeTagSets(this.defaults.tags, options.tags),\n };\n\n return this.source.swr<T>(this.scopedKey(key), merged, callback);\n }\n\n /**\n * Atomic counter increment on the scoped key. TTL is preserved by the\n * underlying driver — scope ttl is only applied on first write via `set`.\n */\n public increment(key: CacheKey, value?: number): Promise<number> {\n return this.source.increment(this.scopedKey(key), value);\n }\n\n /**\n * Atomic counter decrement on the scoped key. See {@link increment} for\n * TTL semantics.\n */\n public decrement(key: CacheKey, value?: number): Promise<number> {\n return this.source.decrement(this.scopedKey(key), value);\n }\n\n /**\n * Atomic read-modify-write. Falls back to the scope's `ttl` when the caller\n * doesn't provide one; the source still keeps the existing entry's TTL on\n * an update unless `options.ttl` is explicitly set.\n */\n public update<T = any>(\n key: CacheKey,\n fn: (current: T | null) => T | null | Promise<T | null>,\n options?: { ttl?: CacheTtl },\n ): Promise<T | null> {\n return this.source.update<T>(this.scopedKey(key), fn, {\n ttl: options?.ttl ?? this.defaults.ttl,\n });\n }\n\n /**\n * Shallow-merge a partial object into the scoped entry. Same TTL semantics\n * as {@link update}.\n */\n public merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T> {\n return this.source.merge<T>(this.scopedKey(key), partial, {\n ttl: options?.ttl ?? this.defaults.ttl,\n });\n }\n\n /**\n * Return a list accessor bound to the scoped key. The accessor itself\n * does its own read-mutate-write under the prefixed entry.\n */\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\n return this.source.list<T>(this.scopedKey(key));\n }\n\n /**\n * Acquire a distributed lock on the scoped key. Caller's TTL wins; when\n * the options form omits `ttl`, the scope default fills in.\n */\n public lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>> {\n if (typeof ttlOrOptions === \"object\" && ttlOrOptions !== null) {\n const merged: Omit<LockOptions, \"driver\"> = {\n ...ttlOrOptions,\n ttl: ttlOrOptions.ttl ?? this.defaults.ttl ?? ttlOrOptions.ttl,\n };\n\n // The third coalesce is intentional — when both caller and scope omit\n // the TTL, we still pass the original (undefined) through so the source\n // raises its own validation error rather than us swallowing it silently.\n return this.source.lock<T>(this.scopedKey(key), merged, fn);\n }\n\n return this.source.lock<T>(this.scopedKey(key), ttlOrOptions, fn);\n }\n\n /**\n * Similarity retrieval, scope-isolated. Hits whose keys fall outside this\n * scope are filtered out before the result is returned. `topK` applies to\n * the underlying retrieval — when the scope contains fewer than `topK`\n * matches but other scopes do, the caller will see fewer hits than `topK`.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const hits = await this.source.similar<T>(vector, options);\n const parsedPrefix = this.source.parseKey(this.prefix);\n\n return hits.filter(\n (hit) => hit.key === parsedPrefix || hit.key.startsWith(parsedPrefix + \".\"),\n );\n }\n\n /**\n * Build the source-side key by prepending the scope prefix. Object keys\n * are normalized via {@link parseCacheKey} first so they compose with the\n * prefix as plain dot-strings.\n */\n protected scopedKey(key: CacheKey): string {\n const keyString = typeof key === \"string\" ? key : parseCacheKey(key);\n\n if (!keyString) {\n return this.prefix;\n }\n\n return `${this.prefix}.${keyString}`;\n }\n\n /**\n * Coerce the polymorphic 3rd `set` argument into a {@link CacheSetOptions}\n * with scope defaults filled in. Per-call values always win; tags merge\n * additively. `expiresAt` is preserved without injecting the scope's `ttl`\n * default (absolute deadlines override relative ones).\n */\n protected mergeSetOptions(\n input?: CacheTtl | CacheSetOptions,\n ): CacheSetOptions | undefined {\n const options = normalizeToOptions(input);\n const ttl =\n options.ttl ?? (options.expiresAt === undefined ? this.defaults.ttl : undefined);\n const tags = mergeTagSets(this.defaults.tags, options.tags);\n\n const merged: CacheSetOptions = { ...options };\n\n if (ttl !== undefined) {\n merged.ttl = ttl;\n }\n\n if (tags !== undefined) {\n merged.tags = tags;\n }\n\n return merged;\n }\n\n /**\n * Same merge as {@link mergeSetOptions} but for the `remember()` shape\n * ({@link RememberOptions} — no `expiresAt`).\n */\n protected mergeRememberOptions(\n input: CacheTtl | RememberOptions,\n ): CacheTtl | RememberOptions {\n const options = normalizeToRememberOptions(input);\n const ttl = options.ttl ?? this.defaults.ttl;\n const tags = mergeTagSets(this.defaults.tags, options.tags);\n\n const merged: RememberOptions = { ...options };\n\n if (ttl !== undefined) {\n merged.ttl = ttl;\n }\n\n if (tags !== undefined) {\n merged.tags = tags;\n }\n\n return merged;\n }\n\n /**\n * Convert the scope's default `ttl` (which may be a duration string) into\n * seconds, for the few methods (`setMany`, `setNX`) that accept only a\n * numeric ttl.\n */\n protected scopeTtlSeconds(): number | undefined {\n if (this.defaults.ttl === undefined) {\n return undefined;\n }\n\n return parseTtl(this.defaults.ttl);\n }\n}\n","import { CacheMetricsCollector } from \"./metrics\";\r\nimport { ScopedCache } from \"./scoped-cache\";\r\nimport type {\r\n CacheConfigurations,\r\n CacheDriver,\r\n CacheEventHandler,\r\n CacheEventType,\r\n CacheKey,\r\n CacheListAccessor,\r\n CacheMetricsSnapshot,\r\n CacheNamespaceOptions,\r\n CacheSetOptions,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheSwrOptions,\r\n CacheTtl,\r\n DriverClass,\r\n LockOptions,\r\n LockOutcome,\r\n RememberOptions,\r\n ScopedCacheContract,\r\n TaggedCacheDriver,\r\n} from \"./types\";\r\nimport { CacheConfigurationError, CacheDriverNotInitializedError } from \"./types\";\r\n\r\nexport class CacheManager implements CacheDriver<any, any> {\r\n /**\r\n * Cache Driver\r\n */\r\n public currentDriver?: CacheDriver<any, any>;\r\n\r\n /**\r\n * Loaded drivers\r\n */\r\n public loadedDrivers: Record<string, CacheDriver<any, any>> = {};\r\n\r\n /**\r\n * Configurations list\r\n */\r\n protected configurations: CacheConfigurations = {\r\n drivers: {},\r\n options: {},\r\n };\r\n\r\n /**\r\n * Global event listeners\r\n */\r\n protected globalEventListeners: Map<CacheEventType, Set<CacheEventHandler>> = new Map();\r\n\r\n /**\r\n * Metrics collector — lazy on first {@link metrics} call so apps that\r\n * never read metrics pay zero cost. Once instantiated, it stays\r\n * subscribed to events for the manager's lifetime.\r\n */\r\n protected metricsCollector?: CacheMetricsCollector;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"cacheManager\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get client() {\r\n return this.currentDriver?.client;\r\n }\r\n\r\n /**\r\n * Set the cache configurations\r\n */\r\n public setCacheConfigurations(configurations: CacheConfigurations) {\r\n this.configurations.default = configurations.default;\r\n this.configurations.drivers = configurations.drivers;\r\n this.configurations.options = configurations.options;\r\n this.configurations.logging = configurations.logging;\r\n }\r\n\r\n /**\r\n * Set logging state\r\n */\r\n public setLoggingState(loggingState: boolean) {\r\n this.ensureDriverInitialized();\r\n\r\n this.currentDriver!.setLoggingState(loggingState);\r\n }\r\n\r\n /**\r\n * Switch the manager to a registered driver, optionally injecting runtime\r\n * options that merge over the static config.\r\n *\r\n * The string form looks the driver up in `setCacheConfigurations({ drivers })`,\r\n * loads it (or returns the cached instance), and sets it as `currentDriver`.\r\n * The instance form takes a pre-built driver and bypasses the registry; the\r\n * `runtimeOptions` argument is silently ignored in that case because the\r\n * instance was constructed externally.\r\n *\r\n * Runtime options merge over `config.options[name]` per-key — runtime wins.\r\n * Use this for constructor-only knobs that can't live in static config\r\n * (e.g. `pg`'s `client: pg.Pool`).\r\n *\r\n * @example\r\n * const pool = new Pool({ connectionString });\r\n * await cache.use(\"pg\", { client: pool });\r\n */\r\n public async use(\r\n driver: string | CacheDriver<any, any>,\r\n runtimeOptions?: Record<string, any>,\r\n ) {\r\n if (typeof driver === \"string\") {\r\n const driverInstance = await this.load(driver, runtimeOptions);\r\n\r\n if (!driverInstance) {\r\n throw new CacheConfigurationError(\r\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\r\n );\r\n }\r\n\r\n driver = driverInstance;\r\n }\r\n\r\n this.attachGlobalListeners(driver);\r\n\r\n if (this.configurations.logging !== undefined) {\r\n driver.setLoggingState(this.configurations.logging);\r\n }\r\n\r\n this.currentDriver = driver;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Ensure driver is initialized before operations\r\n */\r\n protected ensureDriverInitialized(): void {\r\n if (!this.currentDriver) {\r\n throw new CacheDriverNotInitializedError();\r\n }\r\n }\r\n\r\n /**\r\n * Return the running metrics snapshot — counters, hit-rate, latency\r\n * percentiles, per-driver breakdowns. Lazy-attaches the collector on\r\n * first call so apps that never read metrics pay zero cost.\r\n *\r\n * @example\r\n * const m = cache.metrics();\r\n * console.log(`hit rate: ${(m.hitRate * 100).toFixed(1)}%`);\r\n * console.log(`p95: ${m.latencyMs.p95.toFixed(2)}ms`);\r\n */\r\n public metrics(): CacheMetricsSnapshot {\r\n return this.ensureMetricsCollector().snapshot();\r\n }\r\n\r\n /**\r\n * Wipe every counter + latency sample and reset `startedAt` to now.\r\n * The collector itself stays subscribed to events.\r\n */\r\n public resetMetrics(): void {\r\n this.ensureMetricsCollector().reset();\r\n }\r\n\r\n /**\r\n * Lazy-construct the metrics collector and wire it to the global event\r\n * bus. Subsequent calls return the same instance — survives `cache.use()`\r\n * driver switches because handlers attach via `on()` and re-bind to every\r\n * loaded driver.\r\n */\r\n protected ensureMetricsCollector(): CacheMetricsCollector {\r\n if (this.metricsCollector) {\r\n return this.metricsCollector;\r\n }\r\n\r\n const collector = new CacheMetricsCollector();\r\n\r\n this.on(\"hit\", (data) => collector.recordEvent(\"hit\", data));\r\n this.on(\"miss\", (data) => collector.recordEvent(\"miss\", data));\r\n this.on(\"set\", (data) => collector.recordEvent(\"set\", data));\r\n this.on(\"removed\", (data) => collector.recordEvent(\"removed\", data));\r\n this.on(\"error\", (data) => collector.recordEvent(\"error\", data));\r\n\r\n this.metricsCollector = collector;\r\n\r\n return collector;\r\n }\r\n\r\n /**\r\n * Time the body, record the elapsed milliseconds against the metrics\r\n * collector for the given driver (defaults to the current driver's name).\r\n * Pass-through if the collector hasn't been instantiated yet — apps that\r\n * don't read metrics never pay for sample collection.\r\n */\r\n protected async timed<T>(\r\n body: () => Promise<T>,\r\n driverName?: string,\r\n ): Promise<T> {\r\n if (!this.metricsCollector) {\r\n return body();\r\n }\r\n\r\n const start = performance.now();\r\n\r\n try {\r\n return await body();\r\n } finally {\r\n const elapsed = performance.now() - start;\r\n const name = driverName ?? this.currentDriver?.name ?? \"unknown\";\r\n this.metricsCollector.recordLatency(name, elapsed);\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get<T = any>(key: CacheKey): Promise<T | null> {\r\n this.ensureDriverInitialized();\r\n return this.timed(() => this.currentDriver!.get<T>(key));\r\n }\r\n\r\n /**\r\n * Set a value in the cache.\r\n *\r\n * Accepts a positional TTL (number of seconds or duration string like `\"1h\"`)\r\n * or a rich {@link CacheSetOptions} object supporting `ttl`, `expiresAt`,\r\n * `tags`, `onConflict`, `namespace`, and per-call `driver` overrides.\r\n */\r\n public async set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions) {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride =\r\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\r\n ? ttlOrOptions.driver\r\n : undefined;\r\n\r\n if (driverOverride) {\r\n const driver = await this.load(driverOverride);\r\n return this.timed(() => driver.set(key, value, ttlOrOptions), driver.name);\r\n }\r\n\r\n return this.timed(() => this.currentDriver!.set(key, value, ttlOrOptions));\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n this.ensureDriverInitialized();\r\n return this.timed(() => this.currentDriver!.remove(key));\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.removeNamespace(namespace);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.flush();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.connect();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public parseKey(key: CacheKey) {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.parseKey(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get options() {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.options;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: Record<string, any>) {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.setOptions(options || {});\r\n }\r\n\r\n /**\r\n * Return the loaded driver instance for `driverName`, loading it on first\r\n * call. Optional `runtimeOptions` follow the same merge-over-config rules\r\n * as {@link load}; passing options after the driver has already been\r\n * loaded throws to avoid silent swallowing.\r\n */\r\n public async driver(driverName: string, runtimeOptions?: Record<string, any>) {\r\n if (this.loadedDrivers[driverName]) {\r\n this.assertNoConflictingReload(driverName, runtimeOptions);\r\n\r\n return this.loadedDrivers[driverName];\r\n }\r\n\r\n return this.load(driverName, runtimeOptions);\r\n }\r\n\r\n /**\r\n * Initialize the cache manager and pick the default driver\r\n */\r\n public async init() {\r\n const defaultCacheDriverName = this.configurations.default;\r\n\r\n if (!defaultCacheDriverName) {\r\n return;\r\n }\r\n\r\n const driver = await this.driver(defaultCacheDriverName);\r\n\r\n await this.use(driver);\r\n }\r\n\r\n /**\r\n * Load and connect the registered driver named `driver`. First-call wins —\r\n * subsequent calls without `runtimeOptions` return the cached instance, and\r\n * subsequent calls *with* `runtimeOptions` throw {@link CacheConfigurationError}\r\n * to avoid silently dropping the new options.\r\n *\r\n * `runtimeOptions` merge over `config.options[driver]` per-key (runtime wins),\r\n * letting consumers split static knobs (table, ttl, globalPrefix) from\r\n * constructor-only ones (pg's `client`, custom adapters, etc.).\r\n *\r\n * @example\r\n * const pool = new Pool({ connectionString });\r\n * const pg = await cache.load(\"pg\", { client: pool });\r\n */\r\n public async load(driver: string, runtimeOptions?: Record<string, any>) {\r\n if (this.loadedDrivers[driver]) {\r\n this.assertNoConflictingReload(driver, runtimeOptions);\r\n\r\n return this.loadedDrivers[driver];\r\n }\r\n\r\n const Driver = this.configurations.drivers[\r\n driver as keyof typeof this.configurations.drivers\r\n ] as DriverClass | undefined;\r\n\r\n if (!Driver) {\r\n throw new CacheConfigurationError(\r\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\r\n );\r\n }\r\n\r\n const driverInstance = new Driver();\r\n const configOptions =\r\n this.configurations.options[driver as keyof typeof this.configurations.options] || {};\r\n\r\n driverInstance.setOptions({ ...configOptions, ...(runtimeOptions ?? {}) });\r\n\r\n await driverInstance.connect();\r\n\r\n this.attachGlobalListeners(driverInstance);\r\n\r\n this.loadedDrivers[driver] = driverInstance;\r\n\r\n return driverInstance as CacheDriver<any, any>;\r\n }\r\n\r\n /**\r\n * Guard against silently dropping runtime options on a re-load. Once a\r\n * driver has been instantiated, its options are frozen — calling `load` /\r\n * `driver` / `use` again with a non-empty `runtimeOptions` would otherwise\r\n * appear to work but actually use the original options. We throw instead\r\n * so the misuse surfaces at the call site.\r\n */\r\n protected assertNoConflictingReload(\r\n driverName: string,\r\n runtimeOptions: Record<string, any> | undefined,\r\n ): void {\r\n if (runtimeOptions === undefined) {\r\n return;\r\n }\r\n\r\n if (Object.keys(runtimeOptions).length === 0) {\r\n return;\r\n }\r\n\r\n throw new CacheConfigurationError(\r\n `Cache driver '${driverName}' is already loaded; runtime options on subsequent calls are ignored — register a second driver name if you need a different configuration.`,\r\n );\r\n }\r\n\r\n /**\r\n * Register and bind a driver\r\n */\r\n public registerDriver(driverName: string, driverClass: DriverClass) {\r\n (this.configurations.drivers as Record<string, DriverClass>)[driverName] = driverClass;\r\n }\r\n\r\n /**\r\n * Disconnect the cache manager\r\n */\r\n public async disconnect() {\r\n if (this.currentDriver) {\r\n await this.currentDriver.disconnect();\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.has(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember<T = any>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T> {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride =\r\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\r\n ? ttlOrOptions.driver\r\n : undefined;\r\n\r\n if (driverOverride) {\r\n const driver = await this.load(driverOverride);\r\n return driver.remember(key, ttlOrOptions, callback);\r\n }\r\n\r\n return this.currentDriver!.remember(key, ttlOrOptions, callback);\r\n }\r\n\r\n /**\r\n * Stale-while-revalidate. Returns cached when fresh, returns the stale\r\n * value plus a background refresh when within `freshTtl..staleTtl`,\r\n * blocks like a normal miss past `staleTtl`. Honors per-call `driver`\r\n * override the same way `remember()` does.\r\n *\r\n * @example\r\n * const product = await cache.swr(\r\n * \"product.42\",\r\n * { freshTtl: \"1m\", staleTtl: \"1h\" },\r\n * () => db.products.find(42),\r\n * );\r\n */\r\n public async swr<T = any>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T> {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride = options.driver;\r\n\r\n if (driverOverride) {\r\n const driver = await this.load(driverOverride);\r\n\r\n return driver.swr<T>(key, options, callback);\r\n }\r\n\r\n return this.currentDriver!.swr<T>(key, options, callback);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.pull(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.forever(key, value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value?: number): Promise<number> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.increment(key, value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value?: number): Promise<number> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.decrement(key, value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async many(keys: CacheKey[]): Promise<any[]> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.many(keys);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async setMany(items: Record<string, any>, ttl?: number): Promise<void> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.setMany(items, ttl);\r\n }\r\n\r\n /**\r\n * Register a global event listener (applies to all drivers)\r\n */\r\n public on(event: CacheEventType, handler: CacheEventHandler): this {\r\n if (!this.globalEventListeners.has(event)) {\r\n this.globalEventListeners.set(event, new Set());\r\n }\r\n this.globalEventListeners.get(event)!.add(handler);\r\n\r\n // Also attach to current driver if exists\r\n if (this.currentDriver) {\r\n this.currentDriver.on(event, handler);\r\n }\r\n\r\n // Attach to all loaded drivers\r\n for (const driver of Object.values(this.loadedDrivers)) {\r\n driver.on(event, handler);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove a global event listener\r\n */\r\n public off(event: CacheEventType, handler: CacheEventHandler): this {\r\n const handlers = this.globalEventListeners.get(event);\r\n if (handlers) {\r\n handlers.delete(handler);\r\n }\r\n\r\n // Also remove from current driver\r\n if (this.currentDriver) {\r\n this.currentDriver.off(event, handler);\r\n }\r\n\r\n // Remove from all loaded drivers\r\n for (const driver of Object.values(this.loadedDrivers)) {\r\n driver.off(event, handler);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Register a one-time global event listener\r\n */\r\n public once(event: CacheEventType, handler: CacheEventHandler): this {\r\n const onceHandler: CacheEventHandler = async (data) => {\r\n await handler(data);\r\n this.off(event, onceHandler);\r\n };\r\n return this.on(event, onceHandler);\r\n }\r\n\r\n /**\r\n * Attach global listeners to a driver\r\n */\r\n protected attachGlobalListeners(driver: CacheDriver<any, any>) {\r\n for (const [event, handlers] of this.globalEventListeners) {\r\n for (const handler of handlers) {\r\n driver.on(event, handler);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Set if not exists (atomic operation)\r\n * Returns true if key was set, false if key already existed\r\n * Note: Only supported by drivers that implement setNX (e.g., Redis)\r\n */\r\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\r\n this.ensureDriverInitialized();\r\n\r\n if (!this.currentDriver!.setNX) {\r\n throw new Error(\r\n `setNX is not supported by the current cache driver: ${this.currentDriver!.name}`,\r\n );\r\n }\r\n\r\n return this.currentDriver!.setNX(key, value, ttl);\r\n }\r\n\r\n /**\r\n * Create a tagged cache instance for the given tags\r\n */\r\n public tags(tags: string[]): TaggedCacheDriver {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.tags(tags);\r\n }\r\n\r\n /**\r\n * Atomically read, transform, and write a cached value. Delegates to the current driver.\r\n */\r\n public async update<T = any>(\r\n key: CacheKey,\r\n fn: (current: T | null) => T | null | Promise<T | null>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T | null> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.update<T>(key, fn, options);\r\n }\r\n\r\n /**\r\n * Shallow-merge a partial object into a cached value.\r\n */\r\n public async merge<T extends Record<string, any> = Record<string, any>>(\r\n key: CacheKey,\r\n partial: Partial<T>,\r\n options?: { ttl?: CacheTtl },\r\n ): Promise<T> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.merge<T>(key, partial, options);\r\n }\r\n\r\n /**\r\n * Obtain a list accessor bound to the current driver.\r\n */\r\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.list<T>(key);\r\n }\r\n\r\n /**\r\n * Acquire a distributed lock, run `fn`, and auto-release. Returns a\r\n * {@link LockOutcome} discriminated union so callers can distinguish\r\n * \"ran and got this value\" from \"skipped because someone else holds it\".\r\n *\r\n * Honors the `driver` option for per-call driver override, same as `set`\r\n * and `remember`.\r\n *\r\n * @example\r\n * const outcome = await cache.lock(\"lock.import\", \"5m\", async () => {\r\n * await runImport();\r\n * return \"done\";\r\n * });\r\n * if (!outcome.acquired) {\r\n * console.log(\"another worker is already importing\");\r\n * }\r\n */\r\n public async lock<T>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | LockOptions,\r\n fn: () => Promise<T>,\r\n ): Promise<LockOutcome<T>> {\r\n this.ensureDriverInitialized();\r\n\r\n const driverOverride =\r\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\r\n ? ttlOrOptions.driver\r\n : undefined;\r\n\r\n const driver = driverOverride\r\n ? await this.load(driverOverride)\r\n : this.currentDriver!;\r\n\r\n return driver.lock<T>(key, ttlOrOptions as CacheTtl | Omit<LockOptions, \"driver\">, fn);\r\n }\r\n\r\n /**\r\n * Similarity retrieval. Delegates to the current driver's `similar()` impl.\r\n *\r\n * Drivers that lack a similarity index throw {@link CacheUnsupportedError}.\r\n *\r\n * @example\r\n * const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });\r\n */\r\n /**\r\n * Create a scoped view over the cache. Every key written through the\r\n * returned scope is automatically prefixed with `prefix`; optional defaults\r\n * (`ttl`, `tags`) flow through every write inside the scope.\r\n *\r\n * Per-call options always win over scope defaults. Scope tags merge\r\n * additively with per-call tags. Nested scopes inherit from the parent.\r\n *\r\n * @example\r\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\r\n * await chat.set(\"messages.1\", msg); // → \"chats.10.messages.1\", 30d\r\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call ttl wins\r\n * await chat.namespace(\"typing\", { ttl: \"5s\" }).set(\"user.42\", true);\r\n * await chat.clear(); // wipe the whole scope\r\n */\r\n public namespace(prefix: string, options?: CacheNamespaceOptions): ScopedCacheContract {\r\n this.ensureDriverInitialized();\r\n return new ScopedCache(this, prefix, options);\r\n }\r\n\r\n public async similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n this.ensureDriverInitialized();\r\n return this.currentDriver!.similar<T>(vector, options);\r\n }\r\n}\r\n\r\nexport const cache = new CacheManager();\r\n","import { CacheConfigurationError } from \"../types\";\n\n/**\n * Derive a cache key from a prefix and a set of function arguments.\n *\n * Rules (in order of precedence):\n * 1. No args → prefix alone.\n * 2. All primitives (`string`, `number`, `boolean`) or `null` / `undefined` /\n * `bigint` → joined onto the prefix with dots.\n * 3. Any non-primitive arg present → the full args array is `JSON.stringify`-ed\n * and appended to the prefix.\n * 4. Serialization throws (circular refs, `BigInt` nested in an object) → we\n * re-throw as `CacheConfigurationError` so the caller sees a cache-scoped\n * error rather than a cryptic `TypeError`.\n *\n * @example\n * deriveAutoKey(\"user\", [42]); // \"user.42\"\n * deriveAutoKey(\"orders\", [42, \"abc\"]); // \"orders.42.abc\"\n * deriveAutoKey(\"featured\", []); // \"featured\"\n * deriveAutoKey(\"search\", [{ q: \"hello\" }]); // \"search.[{\\\"q\\\":\\\"hello\\\"}]\"\n * deriveAutoKey(\"user\", [null, undefined]); // \"user.null.undefined\"\n */\nexport function deriveAutoKey(prefix: string, args: readonly unknown[]): string {\n if (args.length === 0) {\n return prefix;\n }\n\n if (args.every(isPrimitiveOrNullish)) {\n return prefix + \".\" + args.map(serializePrimitive).join(\".\");\n }\n\n try {\n return prefix + \".\" + JSON.stringify(args);\n } catch (error) {\n throw new CacheConfigurationError(\n `cached(): could not derive an auto-key from args for prefix \"${prefix}\". ` +\n `The args include a value that is not JSON-serializable (circular reference, ` +\n `BigInt nested inside an object, or similar). Use the options form with a custom ` +\n `key function. Original error: ${(error as Error).message}`,\n );\n }\n}\n\n/**\n * Primitives and nullish values can be concatenated directly onto a key without\n * JSON serialization. Adding `bigint` here avoids the `JSON.stringify` throw on\n * top-level bigint args.\n */\nfunction isPrimitiveOrNullish(value: unknown): boolean {\n if (value === null || value === undefined) {\n return true;\n }\n\n const type = typeof value;\n return type === \"string\" || type === \"number\" || type === \"boolean\" || type === \"bigint\";\n}\n\n/**\n * Serialize a single primitive or nullish value to its string key-segment form.\n */\nfunction serializePrimitive(value: unknown): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"bigint\") return value.toString();\n return String(value);\n}\n","import type { CacheTtl } from \"../types\";\nimport { deriveAutoKey } from \"./auto-key\";\n\n/**\n * Options accepted by the `cached()` factory in its verbose form. `key` is the\n * decisive escape hatch when the shorthand's auto-key rules don't fit.\n *\n * @see cached\n */\nexport type CachedOptions<Args extends unknown[]> = {\n /**\n * Derive a cache key from the wrapped function's arguments. Must be pure and\n * deterministic — every call with equivalent inputs should produce the same\n * key.\n */\n key: (...args: Args) => string;\n /**\n * Optional TTL on cache miss. Falls back to the driver's default TTL when omitted.\n */\n ttl?: CacheTtl;\n /**\n * Optional tags attached to every cache-miss write this wrapper produces.\n * Invalidate them the usual way via `cache.tags([...]).invalidate()`.\n */\n tags?: string[];\n /**\n * Per-call driver override. Routes every call produced by this wrapper to\n * the named driver without mutating `currentDriver`.\n */\n driver?: string;\n};\n\n/**\n * Normalized form of the factory arguments — a single shape every call path\n * in `cached()` operates on. Users never see this type.\n *\n * @internal\n */\nexport type NormalizedCachedConfig<Args extends unknown[]> = {\n key: (...args: Args) => string;\n ttl?: CacheTtl;\n tags?: string[];\n driver?: string;\n};\n\n/**\n * Resolve the positional-or-options arguments of `cached()` into a single\n * normalized config. Keeps the wrapper body free of shape-checks.\n */\nexport function normalizeCachedArgs<Args extends unknown[]>(\n prefixOrOptions: string | CachedOptions<Args>,\n maybeTtl?: CacheTtl,\n): NormalizedCachedConfig<Args> {\n if (typeof prefixOrOptions === \"string\") {\n const prefix = prefixOrOptions;\n return {\n key: (...args: Args) => deriveAutoKey(prefix, args),\n ttl: maybeTtl,\n };\n }\n\n return {\n key: prefixOrOptions.key,\n ttl: prefixOrOptions.ttl,\n tags: prefixOrOptions.tags,\n driver: prefixOrOptions.driver,\n };\n}\n","import { cache } from \"../cache-manager\";\nimport type { CacheTtl, RememberOptions } from \"../types\";\nimport type { CachedOptions } from \"./normalize-args\";\nimport { normalizeCachedArgs } from \"./normalize-args\";\n\n/**\n * The shape returned by `cached()`. Callable like the wrapped function, plus\n * helpers for manual invalidation.\n */\nexport type CachedFn<Args extends unknown[], R> = ((...args: Args) => Promise<R>) & {\n /**\n * Drop the cache entry for a specific argument combination.\n *\n * @example\n * const getUser = cached((id: number) => db.users.find(id), \"user\", \"1h\");\n * await getUser.invalidate(42); // drops \"user.42\"\n */\n invalidate(...args: Args): Promise<void>;\n};\n\n/**\n * Wrap an async function so every invocation runs through the cache.\n *\n * Uses `cache.remember()` internally — inherits its stampede-protection\n * guarantees. Three calling shapes, `fn` always first.\n *\n * @example\n * // Shorthand — prefix auto-expands with the function's arguments.\n * // getUser(42) caches under \"user.42\".\n * const getUser = cached(\n * (id: number) => db.users.find(id),\n * \"user\",\n * \"1h\",\n * );\n *\n * @example\n * // Options form — custom key function, tags, per-call driver override.\n * const searchProducts = cached(\n * (filters: ProductFilters) => db.products.search(filters),\n * {\n * key: (filters) => `products.search.${filters.category}.${filters.sort}`,\n * ttl: \"15m\",\n * tags: [\"products\"],\n * },\n * );\n *\n * @example\n * // Manual invalidation — uses the same key scheme the wrapper uses internally.\n * await getUser.invalidate(42);\n */\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n prefix: string,\n): CachedFn<Args, R>;\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n prefix: string,\n ttl: CacheTtl,\n): CachedFn<Args, R>;\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n options: CachedOptions<Args>,\n): CachedFn<Args, R>;\nexport function cached<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>,\n prefixOrOptions: string | CachedOptions<Args>,\n maybeTtl?: CacheTtl,\n): CachedFn<Args, R> {\n const config = normalizeCachedArgs<Args>(prefixOrOptions, maybeTtl);\n\n // Build the RememberOptions payload once per call. `ttl` may still be undefined\n // here — that's fine, `remember` falls back to the driver's default TTL.\n const buildRememberOptions = (): RememberOptions => ({\n ttl: config.ttl,\n tags: config.tags,\n driver: config.driver,\n });\n\n const wrapper = (async (...args: Args): Promise<R> => {\n const key = config.key(...args);\n return cache.remember<R>(key, buildRememberOptions(), () => fn(...args));\n }) as CachedFn<Args, R>;\n\n wrapper.invalidate = async (...args: Args): Promise<void> => {\n const key = config.key(...args);\n await cache.remove(key);\n };\n\n return wrapper;\n}\n","import type { CacheDriver, CacheKey, CacheListAccessor } from \"../types\";\n\n/**\n * Generic array-backed {@link CacheListAccessor}.\n *\n * Stores the full list as a single cache entry and performs read-mutate-write\n * for every operation. Correct for any driver, but O(n) per op. The Redis\n * driver overrides `list()` to return a native-command accessor instead.\n *\n * **Role.** Fallback list accessor bound to a driver + key. Every mutation\n * fetches the array, transforms it in memory, and writes it back.\n *\n * **Responsibility.**\n * - Owns: translating list operations into array mutations + driver writes.\n * - Does NOT own: concurrency control (callers should wrap in a distributed\n * lock when multi-process writers are possible), TTL preservation across\n * ops (writes use driver defaults), or tagging of list entries.\n *\n * @example\n * // Never constructed directly — obtained via driver.list():\n * const list = cache.list<Event>(\"recent-events\");\n * await list.push(event);\n */\nexport class MemoryCacheList<T> implements CacheListAccessor<T> {\n public constructor(\n private readonly driver: CacheDriver<any, any>,\n private readonly key: CacheKey,\n ) {}\n\n /**\n * Read the backing array from the driver. Returns an empty array on miss.\n */\n private async read(): Promise<T[]> {\n const current = (await this.driver.get(this.key)) as T[] | null;\n return Array.isArray(current) ? [...current] : [];\n }\n\n /**\n * Persist the backing array. Removes the entry when empty to keep the\n * store clean.\n */\n private async write(items: T[]): Promise<void> {\n if (items.length === 0) {\n await this.driver.remove(this.key);\n return;\n }\n\n await this.driver.set(this.key, items);\n }\n\n /**\n * {@inheritdoc}\n */\n public async push(...items: T[]): Promise<number> {\n const current = await this.read();\n current.push(...items);\n await this.write(current);\n\n return current.length;\n }\n\n /**\n * {@inheritdoc}\n */\n public async unshift(...items: T[]): Promise<number> {\n const current = await this.read();\n current.unshift(...items);\n await this.write(current);\n\n return current.length;\n }\n\n /**\n * {@inheritdoc}\n */\n public async pop(): Promise<T | null> {\n const current = await this.read();\n\n if (current.length === 0) {\n return null;\n }\n\n const value = current.pop() as T;\n await this.write(current);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async shift(): Promise<T | null> {\n const current = await this.read();\n\n if (current.length === 0) {\n return null;\n }\n\n const value = current.shift() as T;\n await this.write(current);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async slice(start?: number, end?: number): Promise<T[]> {\n const current = await this.read();\n return current.slice(start, end);\n }\n\n /**\n * {@inheritdoc}\n */\n public async all(): Promise<T[]> {\n return this.read();\n }\n\n /**\n * {@inheritdoc}\n */\n public async length(): Promise<number> {\n const current = await this.read();\n return current.length;\n }\n\n /**\n * {@inheritdoc}\n */\n public async trim(start: number, end: number): Promise<void> {\n const current = await this.read();\n const trimmed = current.slice(start, end + 1);\n await this.write(trimmed);\n }\n\n /**\n * {@inheritdoc}\n */\n public async clear(): Promise<void> {\n await this.driver.remove(this.key);\n }\n}\n","import type {\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheTtl,\r\n TaggedCacheDriver,\r\n} from \"./types\";\r\n\r\n/**\r\n * Tagged Cache Wrapper\r\n * Wraps a cache driver to automatically manage tag relationships\r\n */\r\nexport class TaggedCache implements TaggedCacheDriver {\r\n /**\r\n * The tags associated with this tagged cache instance\r\n */\r\n protected cacheTags: string[];\r\n\r\n /**\r\n * The underlying cache driver\r\n */\r\n protected driver: CacheDriver<any, any>;\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor(tags: string[], driver: CacheDriver<any, any>) {\r\n this.cacheTags = tags;\r\n this.driver = driver;\r\n }\r\n\r\n /**\r\n * Get the tag key prefix for storing tag-key relationships\r\n */\r\n protected tagKey(tag: string): string {\r\n return `cache:tags:${tag}`;\r\n }\r\n\r\n /**\r\n * Store tag-key relationship\r\n */\r\n protected async storeTaggedKey(key: string): Promise<void> {\r\n await this.storeTagRelationship(key);\r\n }\r\n\r\n /**\r\n * Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`\r\n * when tags are passed inline through `CacheSetOptions.tags`.\r\n *\r\n * @internal — public for cross-class use within this package; not part of the\r\n * stable consumer API.\r\n */\r\n public async storeTagRelationship(parsedKey: string): Promise<void> {\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n\r\n if (!keys.includes(parsedKey)) {\r\n keys.push(parsedKey);\r\n await this.driver.set(tagKey, keys, Infinity);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Get all keys associated with tags\r\n */\r\n protected async getTaggedKeys(): Promise<Set<string>> {\r\n const allKeys = new Set<string>();\r\n\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n\r\n for (const key of keys) {\r\n allKeys.add(key);\r\n }\r\n }\r\n\r\n return allKeys;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.driver.parseKey(key);\r\n\r\n await this.driver.set(key, value, ttlOrOptions);\r\n\r\n await this.storeTaggedKey(parsedKey);\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey): Promise<any | null> {\r\n return this.driver.get(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey): Promise<void> {\r\n const parsedKey = this.driver.parseKey(key);\r\n\r\n // Remove the value\r\n await this.driver.remove(key);\r\n\r\n // Remove from all tag relationships\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n const updatedKeys = keys.filter((k: string) => k !== parsedKey);\r\n await this.driver.set(tagKey, updatedKeys, Infinity);\r\n }\r\n }\r\n\r\n /**\r\n * Invalidate (clear) all keys associated with the current tags\r\n */\r\n public async invalidate(): Promise<void> {\r\n const keysToRemove = await this.getTaggedKeys();\r\n\r\n // Remove all tagged keys\r\n for (const key of keysToRemove) {\r\n await this.driver.remove(key);\r\n }\r\n\r\n // Clear tag relationship keys\r\n for (const tag of this.cacheTags) {\r\n await this.driver.remove(this.tagKey(tag));\r\n }\r\n }\r\n\r\n /**\r\n * Flush all keys associated with the current tags\r\n * @deprecated Use invalidate() instead for better semantics\r\n */\r\n public async flush(): Promise<void> {\r\n return this.invalidate();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n return this.driver.has(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember(\r\n key: CacheKey,\r\n ttl: number,\r\n callback: () => Promise<any>,\r\n ): Promise<any> {\r\n const value = await this.get(key);\r\n\r\n if (value !== null) {\r\n return value;\r\n }\r\n\r\n const result = await callback();\r\n await this.set(key, result, ttl);\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n const value = await this.get(key);\r\n\r\n if (value !== null) {\r\n await this.remove(key);\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n return this.set(key, value, Infinity);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\r\n const current = (await this.get(key)) || 0;\r\n\r\n if (typeof current !== \"number\") {\r\n throw new Error(\r\n `Cannot increment non-numeric value for key: ${this.driver.parseKey(key)}`,\r\n );\r\n }\r\n\r\n const newValue = current + value;\r\n await this.set(key, newValue);\r\n\r\n return newValue;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\r\n return this.increment(key, -value);\r\n }\r\n}\r\n","import { log } from \"@warlock.js/logger\";\r\nimport { MemoryCacheList } from \"../list/memory-cache-list\";\r\nimport { TaggedCache } from \"../tagged-cache\";\r\nimport type {\r\n CacheConflictPolicy,\r\n CacheData,\r\n CacheDriver,\r\n CacheEventData,\r\n CacheEventHandler,\r\n CacheEventType,\r\n CacheKey,\r\n CacheListAccessor,\r\n CacheOperationType,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheSwrOptions,\r\n CacheTtl,\r\n LockOptions,\r\n LockOutcome,\r\n RememberOptions,\r\n} from \"../types\";\r\nimport { CacheUnsupportedError } from \"../types\";\r\nimport { normalizeToOptions, parseCacheKey, parseTtl, resolveTtl } from \"../utils\";\r\n\r\n/**\r\n * Normalized form of the 3rd `set` argument.\r\n *\r\n * All drivers operate on this shape internally regardless of whether the caller\r\n * passed a positional TTL, a duration string, or a rich options object.\r\n */\r\nexport type NormalizedSetOptions = {\r\n /**\r\n * Final TTL in seconds — already merged with the driver-level default\r\n * (`this.options.ttl`). `Infinity` means \"no expiration\".\r\n *\r\n * Always populated. Drivers do NOT need to fall back to `this.ttl`\r\n * themselves — `resolveSetOptions` does the merge centrally so that\r\n * every driver respects the configured default without ceremony.\r\n */\r\n ttl: number;\r\n /**\r\n * Inline tag list, or undefined when none were provided.\r\n */\r\n tags?: string[];\r\n /**\r\n * Conflict policy. Defaults to `\"upsert\"`.\r\n */\r\n onConflict: CacheConflictPolicy;\r\n /**\r\n * Optional embedding vector for similarity retrieval. Drivers that do not\r\n * support similarity must throw {@link CacheUnsupportedError} when this is\r\n * present (rather than silently dropping it).\r\n */\r\n vector?: number[];\r\n /**\r\n * Optional freshness deadline as a millisecond timestamp. Set by `swr()`\r\n * to mark when the entry stops being \"fresh\" and becomes\r\n * \"stale-but-revalidatable.\" Drivers route this through\r\n * `prepareDataForStorage` so it persists in the wrapper.\r\n */\r\n staleAt?: number;\r\n};\r\n\r\nconst messages = {\r\n clearing: \"Clearing namespace\",\r\n cleared: \"Namespace cleared\",\r\n fetching: \"Fetching key\",\r\n fetched: \"Key fetched\",\r\n caching: \"Caching key\",\r\n cached: \"Key cached\",\r\n flushing: \"Flushing cache\",\r\n flushed: \"Cache flushed\",\r\n removing: \"Removing key\",\r\n removed: \"Key removed\",\r\n expired: \"Key expired\",\r\n notFound: \"Key not found\",\r\n connecting: \"Connecting to the cache engine.\",\r\n connected: \"Connected to the cache engine.\",\r\n disconnecting: \"Disconnecting from the cache engine.\",\r\n disconnected: \"Disconnected from the cache engine.\",\r\n error: \"Error occurred\",\r\n};\r\n\r\nexport abstract class BaseCacheDriver<\r\n ClientType,\r\n Options extends Record<string, any>,\r\n> implements CacheDriver<ClientType, Options> {\r\n /**\r\n * CLient driver\r\n */\r\n protected clientDriver!: ClientType;\r\n\r\n /**\r\n * Determine whether to log or not\r\n */\r\n protected shouldLog: boolean = true;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get client() {\r\n return (this.clientDriver || this) as unknown as ClientType;\r\n }\r\n\r\n /**\r\n * Set logging state\r\n */\r\n public setLoggingState(shouldLog: boolean) {\r\n this.shouldLog = shouldLog;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set client driver\r\n */\r\n public set client(client: ClientType) {\r\n this.clientDriver = client;\r\n }\r\n\r\n /**\r\n * Get the cache driver name\r\n */\r\n public abstract name: string;\r\n\r\n /**\r\n * Options list\r\n */\r\n public options!: Options;\r\n\r\n /**\r\n * Event listeners storage\r\n */\r\n protected eventListeners: Map<CacheEventType, Set<CacheEventHandler>> = new Map();\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public parseKey(key: CacheKey) {\r\n return parseCacheKey(key, this.options);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: Options) {\r\n this.options = options || {};\r\n return this;\r\n }\r\n\r\n /**\r\n * Register an event listener\r\n */\r\n public on(event: CacheEventType, handler: CacheEventHandler): this {\r\n if (!this.eventListeners.has(event)) {\r\n this.eventListeners.set(event, new Set());\r\n }\r\n this.eventListeners.get(event)!.add(handler);\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove an event listener\r\n */\r\n public off(event: CacheEventType, handler: CacheEventHandler): this {\r\n const handlers = this.eventListeners.get(event);\r\n if (handlers) {\r\n handlers.delete(handler);\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Register a one-time event listener\r\n */\r\n public once(event: CacheEventType, handler: CacheEventHandler): this {\r\n const onceHandler: CacheEventHandler = async (data) => {\r\n await handler(data);\r\n this.off(event, onceHandler);\r\n };\r\n return this.on(event, onceHandler);\r\n }\r\n\r\n /**\r\n * Emit an event to all registered listeners\r\n */\r\n protected async emit(event: CacheEventType, data: Partial<CacheEventData> = {}): Promise<void> {\r\n const handlers = this.eventListeners.get(event);\r\n if (!handlers || handlers.size === 0) return;\r\n\r\n const eventData: CacheEventData = {\r\n driver: this.name,\r\n ...data,\r\n };\r\n\r\n // Execute all handlers\r\n const promises: Promise<void>[] = [];\r\n for (const handler of handlers) {\r\n try {\r\n const result = handler(eventData);\r\n if (result instanceof Promise) {\r\n promises.push(result);\r\n }\r\n } catch (error) {\r\n this.logError(`Error in event handler for '${event}'`, error);\r\n }\r\n }\r\n\r\n // Wait for all async handlers\r\n if (promises.length > 0) {\r\n await Promise.allSettled(promises);\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract removeNamespace(namespace: string): Promise<any>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any>;\r\n\r\n /**\r\n * Normalize the 3rd argument of a `set` call into a single shape every driver\r\n * can act on. Handles TTL parsing (number | string | Infinity), `expiresAt` →\r\n * relative TTL conversion, and mutual-exclusion validation.\r\n *\r\n * @throws {CacheConfigurationError} when `ttl` and `expiresAt` are passed together\r\n * or an unparseable duration string is supplied.\r\n */\r\n protected resolveSetOptions(\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): NormalizedSetOptions {\r\n const options = normalizeToOptions(ttlOrOptions);\r\n\r\n return {\r\n ttl: resolveTtl(options.ttl, options.expiresAt, this.ttl),\r\n tags: options.tags,\r\n onConflict: options.onConflict ?? \"upsert\",\r\n vector: options.vector,\r\n staleAt: options.staleAt,\r\n };\r\n }\r\n\r\n /**\r\n * Resolve the union of cache keys associated with any of the given tags.\r\n * Used by `similar()` to narrow the candidate pool before similarity ranking.\r\n *\r\n * Returns `null` when no tags are passed (callers should treat that as \"no filter\").\r\n */\r\n protected async getKeysForTags(tags: string[] | undefined): Promise<Set<string> | null> {\r\n if (!tags || tags.length === 0) {\r\n return null;\r\n }\r\n\r\n const allKeys = new Set<string>();\r\n for (const tag of tags) {\r\n const tagKey = `cache:tags:${tag}`;\r\n const keys = ((await this.get(tagKey)) as string[] | null) || [];\r\n for (const k of keys) {\r\n allKeys.add(k);\r\n }\r\n }\r\n\r\n return allKeys;\r\n }\r\n\r\n /**\r\n * Apply tag relationships after a successful write. Called by drivers once\r\n * the value is in storage.\r\n */\r\n protected async applyTags(parsedKey: string, tags: string[]): Promise<void> {\r\n if (tags.length === 0) {\r\n return;\r\n }\r\n\r\n const tagged = this.tags(tags);\r\n await (tagged as TaggedCache).storeTagRelationship(parsedKey);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract get(key: CacheKey): Promise<any>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract remove(key: CacheKey): Promise<void>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract flush(): Promise<void>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n const value = await this.get(key);\r\n // Event is emitted by get() method\r\n return value !== null;\r\n }\r\n\r\n /**\r\n * Lock storage for preventing cache stampede\r\n */\r\n protected locks: Map<string, Promise<any>> = new Map();\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<any>,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n // The options-form lets callers forward tags / driver-override through to\r\n // the cache-miss write. Normalize both shapes into a single CacheSetOptions\r\n // blob so there's one path from here on.\r\n const setOptions = this.normalizeRememberOptions(ttlOrOptions);\r\n\r\n const cachedValue = await this.get(key);\r\n if (cachedValue) {\r\n return cachedValue;\r\n }\r\n\r\n const existingLock = this.locks.get(parsedKey);\r\n if (existingLock) {\r\n return existingLock;\r\n }\r\n\r\n const promise = callback()\r\n .then(async (result) => {\r\n await this.set(key, result, setOptions);\r\n this.locks.delete(parsedKey);\r\n return result;\r\n })\r\n .catch((err) => {\r\n this.locks.delete(parsedKey);\r\n throw err;\r\n });\r\n\r\n this.locks.set(parsedKey, promise);\r\n return promise;\r\n }\r\n\r\n /**\r\n * Resolve the TTL-or-options arg of `remember` into a `CacheSetOptions` object\r\n * that can be passed straight to `set()`. Keeps the implementation unbranched.\r\n */\r\n protected normalizeRememberOptions(\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n ): CacheSetOptions {\r\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\r\n return { ttl: ttlOrOptions };\r\n }\r\n\r\n return {\r\n ttl: ttlOrOptions.ttl,\r\n tags: ttlOrOptions.tags,\r\n };\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation: read raw entry, branch on freshness/staleness,\r\n * trigger background refresh in the stale window, fall through to\r\n * `callback` on miss/expiry. Concurrent stale-window callers share a\r\n * single in-flight refresh via {@link locks}.\r\n *\r\n * Drivers without a real {@link getEntry} override degrade gracefully —\r\n * the synthetic entry has no `staleAt`, which the freshness check treats\r\n * as \"always fresh,\" so SWR behaves like a TTL-only cached read on those\r\n * drivers (no background refresh, but no double-fetch either).\r\n */\r\n public async swr<T = any>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T> {\r\n const parsedKey = this.parseKey(key);\r\n const freshSeconds = parseTtl(options.freshTtl);\r\n const staleSeconds = parseTtl(options.staleTtl);\r\n\r\n if (staleSeconds <= freshSeconds) {\r\n throw new Error(\r\n `cache.swr: 'staleTtl' (${staleSeconds}s) must be greater than 'freshTtl' (${freshSeconds}s).`,\r\n );\r\n }\r\n\r\n const entry = await this.getEntry(key);\r\n const now = Date.now();\r\n\r\n const isExpired = entry?.expiresAt !== undefined && entry.expiresAt <= now;\r\n\r\n if (!entry || isExpired) {\r\n return this.swrFetchAndStore<T>(key, options, callback, freshSeconds, staleSeconds);\r\n }\r\n\r\n const isFresh = entry.staleAt === undefined || entry.staleAt > now;\r\n\r\n if (isFresh) {\r\n return entry.data as T;\r\n }\r\n\r\n this.scheduleSwrRefresh<T>(parsedKey, key, options, callback, freshSeconds, staleSeconds);\r\n\r\n return entry.data as T;\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper for a key, including any\r\n * `expiresAt` / `staleAt` metadata. Default implementation falls back to\r\n * `get()` and synthesizes a metadata-less wrapper — drivers that store\r\n * the wrapper directly (memory, lru, file, redis, pg, mock) override\r\n * this to return real metadata so SWR can branch on freshness.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const value = await this.get(key);\r\n\r\n if (value === null) {\r\n return null;\r\n }\r\n\r\n return { data: value };\r\n }\r\n\r\n /**\r\n * Remaining lifetime of an existing entry, in seconds — used by TTL-preserving\r\n * writes such as `update()` / `merge()` when the caller passes no explicit\r\n * `ttl`.\r\n *\r\n * - `Infinity` — the entry exists with no expiry (preserve \"never expires\").\r\n * - positive number — seconds left before the entry expires.\r\n * - `undefined` — the key is missing or already past its deadline; the caller\r\n * should fall back to the driver default TTL.\r\n *\r\n * Default reads `expiresAt` from {@link getEntry}, which the metadata-aware\r\n * drivers (memory, lru, mock, pg) populate. Drivers that track TTL natively\r\n * and don't carry `expiresAt` in their payload (Redis) override this.\r\n */\r\n protected async getRemainingTtl(key: CacheKey): Promise<number | undefined> {\r\n const entry = await this.getEntry(key);\r\n\r\n if (!entry) {\r\n return undefined;\r\n }\r\n\r\n if (!entry.expiresAt || entry.expiresAt === Infinity) {\r\n return Infinity;\r\n }\r\n\r\n const remainingSeconds = Math.ceil((entry.expiresAt - Date.now()) / 1000);\r\n\r\n return remainingSeconds > 0 ? remainingSeconds : undefined;\r\n }\r\n\r\n /**\r\n * Block-and-fetch path of `swr()`: invoked on miss or past-`staleTtl`\r\n * expiry. Writes through `set()` with the SWR options translated into\r\n * standard `CacheSetOptions` (ttl = staleTtl, staleAt = now + freshTtl).\r\n */\r\n protected async swrFetchAndStore<T>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n freshSeconds: number,\r\n staleSeconds: number,\r\n ): Promise<T> {\r\n const result = await callback();\r\n\r\n await this.set(key, result, {\r\n ttl: staleSeconds,\r\n staleAt: Date.now() + freshSeconds * 1000,\r\n tags: options.tags,\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Stale-window background refresh. Registers a single in-flight promise\r\n * per parsed key so concurrent SWR callers share one refresh. Failed\r\n * refreshes preserve the stale entry, log via `logError`, and emit on\r\n * `error` — the stale-returning caller never sees the failure.\r\n */\r\n protected scheduleSwrRefresh<T>(\r\n parsedKey: string,\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n freshSeconds: number,\r\n staleSeconds: number,\r\n ): void {\r\n if (this.locks.has(parsedKey)) {\r\n return;\r\n }\r\n\r\n let refresh!: Promise<void>;\r\n refresh = (async () => {\r\n try {\r\n const result = await callback();\r\n\r\n await this.set(key, result, {\r\n ttl: staleSeconds,\r\n staleAt: Date.now() + freshSeconds * 1000,\r\n tags: options.tags,\r\n });\r\n } catch (error) {\r\n this.logError(`SWR background refresh failed for ${parsedKey}`, error);\r\n await this.emit(\"error\", { key: parsedKey, error });\r\n } finally {\r\n if (this.locks.get(parsedKey) === refresh) {\r\n this.locks.delete(parsedKey);\r\n }\r\n }\r\n })();\r\n\r\n this.locks.set(parsedKey, refresh);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n const value = await this.get(key);\r\n if (value !== null) {\r\n await this.remove(key);\r\n }\r\n // Events are emitted by get() and remove() methods\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n // Event is emitted by set() method\r\n return this.set(key, value, Infinity);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\r\n const current = (await this.get(key)) || 0;\r\n\r\n if (typeof current !== \"number\") {\r\n throw new Error(`Cannot increment non-numeric value for key: ${this.parseKey(key)}`);\r\n }\r\n\r\n const newValue = current + value;\r\n await this.set(key, newValue);\r\n return newValue;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\r\n return this.increment(key, -value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async many(keys: CacheKey[]): Promise<any[]> {\r\n return Promise.all(keys.map((key) => this.get(key)));\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async setMany(items: Record<string, any>, ttl?: number): Promise<void> {\r\n await Promise.all(Object.entries(items).map(([key, value]) => this.set(key, value, ttl)));\r\n }\r\n\r\n /**\r\n * Log the operation\r\n */\r\n protected log(operation: CacheOperationType, key?: string) {\r\n if (!this.shouldLog) return;\r\n\r\n if (key) {\r\n // this will be likely used with file cache driver as it will convert the dot to slash\r\n // to make it consistent and not to confuse developers we will output the key by making sure it's a dot\r\n key = key.replaceAll(\"/\", \".\");\r\n }\r\n\r\n if (operation == \"notFound\" || operation == \"expired\") {\r\n return log.warn(\r\n \"cache.\" + this.name,\r\n operation,\r\n (key ? key + \" \" : \"\") + messages[operation],\r\n );\r\n }\r\n\r\n if (operation.endsWith(\"ed\")) {\r\n return log.success(\r\n \"cache.\" + this.name,\r\n operation,\r\n (key ? key + \" \" : \"\") + messages[operation],\r\n );\r\n }\r\n\r\n log.info(\"cache.\" + this.name, operation, (key ? key + \" \" : \"\") + messages[operation]);\r\n }\r\n\r\n /**\r\n * Log error message\r\n */\r\n protected logError(message: string, error?: any) {\r\n log.error(\"cache.\" + this.name, \"error\", message);\r\n if (error) {\r\n console.log(error);\r\n }\r\n }\r\n\r\n /**\r\n * Get the default TTL in seconds. Parses human-readable strings (`\"1h\"`, `\"30m\"`)\r\n * from driver options if present; falls back to `Infinity` when no default is set.\r\n */\r\n public get ttl() {\r\n if (this.options.ttl === undefined) {\r\n return Infinity;\r\n }\r\n\r\n return parseTtl(this.options.ttl);\r\n }\r\n\r\n /**\r\n * Get time to live value in milliseconds\r\n */\r\n public getExpiresAt(ttl: number = this.ttl) {\r\n if (ttl) {\r\n return new Date().getTime() + ttl * 1000;\r\n }\r\n }\r\n\r\n /**\r\n * Wrap a value with TTL and optional freshness metadata for backend\r\n * storage. `staleAt` persists alongside `expiresAt` when supplied — used\r\n * by the SWR flow to mark when the entry stops being fresh.\r\n */\r\n protected prepareDataForStorage(data: any, ttl?: number, staleAt?: number) {\r\n const preparedData: CacheData = {\r\n data,\r\n };\r\n\r\n if (ttl) {\r\n preparedData.ttl = ttl;\r\n preparedData.expiresAt = this.getExpiresAt(ttl);\r\n }\r\n\r\n if (staleAt !== undefined) {\r\n preparedData.staleAt = staleAt;\r\n }\r\n\r\n return preparedData;\r\n }\r\n\r\n /**\r\n * Parse fetched data from cache\r\n */\r\n protected async parseCachedData(key: string, data: CacheData) {\r\n this.log(\"fetched\", key);\r\n\r\n if (data.expiresAt && data.expiresAt < Date.now()) {\r\n this.remove(key);\r\n return null;\r\n }\r\n\r\n const value = data.data;\r\n\r\n // Skip cloning for primitives (immutable types)\r\n if (value === null || value === undefined) {\r\n return value;\r\n }\r\n\r\n const type = typeof value;\r\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\r\n return value;\r\n }\r\n\r\n // Deep clone objects/arrays to prevent cache mutation\r\n try {\r\n return structuredClone(value);\r\n } catch (error) {\r\n console.log(value);\r\n\r\n this.logError(\r\n `Failed to clone cached value for ${key}, typeof value: ${typeof value}`,\r\n error,\r\n );\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n this.log(\"connecting\");\r\n this.log(\"connected\");\r\n await this.emit(\"connected\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async disconnect() {\r\n this.log(\"disconnected\");\r\n await this.emit(\"disconnected\");\r\n }\r\n\r\n /**\r\n * Create a tagged cache instance for the given tags\r\n */\r\n public tags(tags: string[]): any {\r\n return new TaggedCache(tags, this);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation: read → transform → write under a per-key in-process\r\n * lock. Drivers that can offer stronger semantics (Redis via `WATCH`/`MULTI`)\r\n * should override.\r\n */\r\n public async update<T = any>(\r\n key: CacheKey,\r\n fn: (current: T | null) => T | null | Promise<T | null>,\r\n options: { ttl?: CacheTtl } = {},\r\n ): Promise<T | null> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n // Chain each update onto the previous one for the same key so concurrent\r\n // callers are serialized end-to-end, not merely awakened together when an\r\n // earlier lock resolves.\r\n const previous = this.locks.get(parsedKey) ?? Promise.resolve();\r\n\r\n const next = previous.catch(() => undefined).then(async () => {\r\n const current = (await this.get(key)) as T | null;\r\n const result = await fn(current);\r\n\r\n if (result === null) {\r\n await this.remove(key);\r\n return null;\r\n }\r\n\r\n if (options.ttl !== undefined) {\r\n await this.set(key, result, { ttl: options.ttl });\r\n\r\n return result;\r\n }\r\n\r\n // No explicit TTL → preserve the existing entry's remaining lifetime\r\n // rather than resetting it to the driver default.\r\n const remainingTtl = await this.getRemainingTtl(key);\r\n\r\n if (remainingTtl !== undefined) {\r\n await this.set(key, result, { ttl: remainingTtl });\r\n } else {\r\n await this.set(key, result);\r\n }\r\n\r\n return result;\r\n });\r\n\r\n this.locks.set(parsedKey, next);\r\n\r\n // Clean up the slot once this link finishes — but only if nobody chained\r\n // a follow-up onto it in the meantime.\r\n next.finally(() => {\r\n if (this.locks.get(parsedKey) === next) {\r\n this.locks.delete(parsedKey);\r\n }\r\n });\r\n\r\n return next;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async merge<T extends Record<string, any> = Record<string, any>>(\r\n key: CacheKey,\r\n partial: Partial<T>,\r\n options: { ttl?: CacheTtl } = {},\r\n ): Promise<T> {\r\n const result = await this.update<T>(\r\n key,\r\n (current) => {\r\n const base = (current ?? {}) as T;\r\n return { ...base, ...partial } as T;\r\n },\r\n options,\r\n );\r\n\r\n return result as T;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation: read-mutate-write array backed by the underlying\r\n * cache entry. Concrete drivers (e.g. Redis) override with native commands.\r\n */\r\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\r\n return new MemoryCacheList<T>(this, key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Built on top of `set({ onConflict: \"create\" })` — Redis-native `SET … NX EX`\r\n * under the hood on Redis, emulated via key-existence check on other drivers.\r\n * The lock value is the resolved `owner` (defaults to `pid.<process.pid>`).\r\n *\r\n * Always releases in `finally`, even if `fn` throws — the thrown error\r\n * propagates to the caller unchanged.\r\n */\r\n public async lock<T>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\r\n fn: () => Promise<T>,\r\n ): Promise<LockOutcome<T>> {\r\n const { ttl, owner } = this.normalizeLockOptions(ttlOrOptions);\r\n const lockOwner = owner ?? `pid.${process.pid}`;\r\n\r\n const setResult = (await this.set(key, lockOwner, {\r\n onConflict: \"create\",\r\n ttl,\r\n })) as CacheSetResult | unknown;\r\n\r\n // `onConflict` drivers return CacheSetResult. Drivers that no-op on set\r\n // (e.g. the null driver) may return anything else — treat that as\r\n // \"acquired\" since there's nothing to collide with.\r\n const wasSet =\r\n typeof setResult === \"object\" && setResult !== null && \"wasSet\" in setResult\r\n ? (setResult as CacheSetResult).wasSet\r\n : true;\r\n\r\n if (!wasSet) {\r\n return { acquired: false };\r\n }\r\n\r\n try {\r\n const value = await fn();\r\n return { acquired: true, value };\r\n } finally {\r\n await this.remove(key);\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation throws {@link CacheUnsupportedError}. Drivers that\r\n * support similarity retrieval (memory family, `pg`, `redis` w/ RediSearch)\r\n * override this with a real impl.\r\n */\r\n public async similar<T = any>(\r\n _vector: number[],\r\n _options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n throw new CacheUnsupportedError(\r\n `'${this.name}' driver does not support similarity retrieval. Use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).`,\r\n );\r\n }\r\n\r\n /**\r\n * Resolve the TTL-or-options arg of `lock` into a uniform shape.\r\n */\r\n protected normalizeLockOptions(\r\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\r\n ): { ttl: CacheTtl; owner?: string } {\r\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\r\n return { ttl: ttlOrOptions };\r\n }\r\n\r\n return { ttl: ttlOrOptions.ttl, owner: ttlOrOptions.owner };\r\n }\r\n}\r\n","import {\r\n ensureDirectoryAsync,\r\n getJsonFileAsync,\r\n putJsonFileAsync,\r\n removeDirectoryAsync,\r\n} from \"@warlock.js/fs\";\r\nimport path from \"path\";\r\nimport type {\r\n CacheData,\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheTtl,\r\n FileCacheOptions,\r\n} from \"../types\";\r\nimport { CacheConfigurationError, CacheUnsupportedError } from \"../types\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nexport class FileCacheDriver\r\n extends BaseCacheDriver<FileCacheDriver, FileCacheOptions>\r\n implements CacheDriver<FileCacheDriver, FileCacheOptions>\r\n{\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"file\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: FileCacheOptions) {\r\n if (!options.directory) {\r\n throw new CacheConfigurationError(\r\n \"File driver requires 'directory' option to be configured.\",\r\n );\r\n }\r\n\r\n return super.setOptions(options);\r\n }\r\n\r\n /**\r\n * Get the cache directory\r\n */\r\n public get directory() {\r\n const directory = this.options.directory;\r\n\r\n if (typeof directory === \"function\") {\r\n return directory();\r\n }\r\n\r\n throw new CacheConfigurationError(\r\n \"Cache directory is not defined, please define it in the file driver options\",\r\n );\r\n }\r\n\r\n /**\r\n * Get file name\r\n */\r\n public get fileName() {\r\n const fileName = this.options.fileName;\r\n\r\n if (typeof fileName === \"function\") {\r\n return fileName();\r\n }\r\n\r\n return \"cache.json\";\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n this.log(\"clearing\", namespace);\r\n\r\n try {\r\n await removeDirectoryAsync(path.resolve(this.directory, namespace));\r\n\r\n this.log(\"cleared\", namespace);\r\n } catch (error) {\r\n //\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\r\n\r\n if (vector) {\r\n throw new CacheUnsupportedError(\r\n \"'file' driver does not support similarity retrieval — use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).\",\r\n );\r\n }\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n const existing = onConflict === \"upsert\" ? null : await this.get(key);\r\n const exists = existing !== null;\r\n\r\n if (onConflict === \"create\" && exists) {\r\n const result: CacheSetResult = { wasSet: false, existing };\r\n return result;\r\n }\r\n\r\n if (onConflict === \"update\" && !exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: null };\r\n return result;\r\n }\r\n\r\n const data = this.prepareDataForStorage(value, ttl, staleAt);\r\n\r\n const fileDirectory = path.resolve(this.directory, parsedKey);\r\n\r\n await ensureDirectoryAsync(fileDirectory);\r\n\r\n await putJsonFileAsync(path.resolve(fileDirectory, this.fileName), data);\r\n\r\n if (tags && tags.length > 0) {\r\n await this.applyTags(parsedKey, tags);\r\n }\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n await this.emit(\"set\", { key: parsedKey, value, ttl });\r\n\r\n if (onConflict === \"create\" || onConflict === \"update\") {\r\n const result: CacheSetResult = { wasSet: true, existing: null };\r\n return result;\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * File driver does not yet ship with a file-lock primitive, so concurrent\r\n * writers could clobber each other. Rather than ship an unsafe default, we\r\n * throw — consumers can fall back to memory/redis for `update` until a\r\n * proper file lock lands (tracked in `domains/cache/backlog.md`).\r\n */\r\n public async update(): Promise<never> {\r\n throw new CacheUnsupportedError(\r\n \"`update()` is not supported on the file driver. Use the memory or redis driver, or wait for the file-lock primitive (see domains/cache/backlog.md).\",\r\n );\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async merge(): Promise<never> {\r\n throw new CacheUnsupportedError(\r\n \"`merge()` is not supported on the file driver. Use the memory or redis driver.\",\r\n );\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper from disk, including `staleAt`\r\n * metadata. Returns `null` for missing or expired files — `swr()`\r\n * consumes this to branch on freshness.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const parsedKey = this.parseKey(key);\r\n const fileDirectory = path.resolve(this.directory, parsedKey);\r\n\r\n try {\r\n const entry = (await getJsonFileAsync(path.resolve(fileDirectory, this.fileName))) as\r\n | CacheData\r\n | undefined;\r\n\r\n if (!entry) {\r\n return null;\r\n }\r\n\r\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\r\n return null;\r\n }\r\n\r\n return entry;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"fetching\", parsedKey);\r\n\r\n const fileDirectory = path.resolve(this.directory, parsedKey);\r\n\r\n try {\r\n const value = await getJsonFileAsync(path.resolve(fileDirectory, this.fileName));\r\n\r\n const result = await this.parseCachedData(parsedKey, value as CacheData);\r\n\r\n if (result === null) {\r\n // Expired\r\n await this.emit(\"miss\", { key: parsedKey });\r\n } else {\r\n // Emit hit event\r\n await this.emit(\"hit\", { key: parsedKey, value: result });\r\n }\r\n\r\n return result;\r\n } catch (error) {\r\n this.log(\"notFound\", parsedKey);\r\n // Emit miss event\r\n await this.emit(\"miss\", { key: parsedKey });\r\n // Await the cleanup so it fully settles before returning. Leaving this\r\n // fire-and-forget let the async directory removal race a follow-up write\r\n // to the same key (e.g. the existence probe inside a `set({ onConflict })`\r\n // call), surfacing as an ENOENT mkdir/rm collision on Windows.\r\n await this.remove(key);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n this.log(\"removing\", parsedKey);\r\n\r\n const fileDirectory = path.resolve(this.directory, parsedKey);\r\n\r\n try {\r\n await removeDirectoryAsync(fileDirectory);\r\n\r\n this.log(\"removed\", parsedKey);\r\n // Emit removed event\r\n await this.emit(\"removed\", { key: parsedKey });\r\n } catch (error) {\r\n //\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n this.log(\"flushing\");\r\n\r\n if (this.options.globalPrefix) {\r\n await this.removeNamespace(\"\");\r\n } else {\r\n await removeDirectoryAsync(this.directory);\r\n }\r\n\r\n this.log(\"flushed\");\r\n\r\n // Emit flushed event\r\n await this.emit(\"flushed\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n this.log(\"connecting\");\r\n await ensureDirectoryAsync(this.directory);\r\n this.log(\"connected\");\r\n await this.emit(\"connected\");\r\n }\r\n}\r\n","import type {\r\n CacheData,\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheTtl,\r\n LRUMemoryCacheOptions,\r\n} from \"../types\";\r\nimport { cosineSimilarity } from \"../utils\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nclass CacheNode {\r\n public next: CacheNode | null = null;\r\n public prev: CacheNode | null = null;\r\n public expiresAt?: number;\r\n /**\r\n * Freshness deadline (ms timestamp) — populated by `swr()`. Within\r\n * `expiresAt > now > staleAt` the entry is \"stale-but-revalidatable.\"\r\n */\r\n public staleAt?: number;\r\n /**\r\n * Optional embedding vector — populated when the entry was written with\r\n * `set({ vector })`. Scanned by `similar()`.\r\n */\r\n public vector?: number[];\r\n public constructor(\r\n public key: string,\r\n public value: any,\r\n ttl?: number,\r\n ) {\r\n if (ttl && ttl !== Infinity) {\r\n this.expiresAt = Date.now() + ttl * 1000;\r\n }\r\n }\r\n\r\n public get isExpired(): boolean {\r\n return this.expiresAt !== undefined && this.expiresAt < Date.now();\r\n }\r\n}\r\n\r\n/**\r\n * LRU Memory Cache Driver\r\n * The concept of LRU is to remove the least recently used data\r\n * whenever the cache is full\r\n * The question that resides here is how to tell the cache is full?\r\n */\r\nexport class LRUMemoryCacheDriver\r\n extends BaseCacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\r\n implements CacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\r\n{\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"lru\";\r\n\r\n /**\r\n * Cache map\r\n */\r\n protected cache: Map<string, CacheNode> = new Map();\r\n\r\n /**\r\n * Head of the cache\r\n */\r\n protected head: CacheNode = new CacheNode(\"\", null);\r\n\r\n /**\r\n * Tail of the cache\r\n */\r\n protected tail: CacheNode = new CacheNode(\"\", null);\r\n\r\n /**\r\n * Cleanup interval reference\r\n */\r\n protected cleanupInterval?: NodeJS.Timeout;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public constructor() {\r\n super();\r\n\r\n this.init();\r\n this.startCleanup();\r\n }\r\n\r\n /**\r\n * Initialize the cache\r\n */\r\n public init() {\r\n this.head.next = this.tail;\r\n this.tail.prev = this.head;\r\n }\r\n\r\n /**\r\n * Start the cleanup process for expired items\r\n */\r\n public startCleanup() {\r\n // Clear existing interval if any\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n }\r\n\r\n this.cleanupInterval = setInterval(async () => {\r\n const now = Date.now();\r\n const expiredKeys: string[] = [];\r\n\r\n for (const [key, node] of this.cache) {\r\n if (node.expiresAt && node.expiresAt <= now) {\r\n expiredKeys.push(key);\r\n }\r\n }\r\n\r\n for (const key of expiredKeys) {\r\n const node = this.cache.get(key);\r\n if (node) {\r\n this.removeNode(node);\r\n this.cache.delete(key);\r\n this.log(\"expired\", key);\r\n // Emit expired event\r\n await this.emit(\"expired\", { key });\r\n }\r\n }\r\n }, 1000);\r\n\r\n // do not block the process from exiting\r\n this.cleanupInterval.unref();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Clears every entry whose key starts with the parsed namespace (followed\r\n * by a dot) or equals it exactly. Called with an empty namespace while a\r\n * `globalPrefix` is configured, clears everything under the prefix — which\r\n * is how `flush()` scopes cleanup per tenant.\r\n */\r\n public async removeNamespace(namespace: string) {\r\n const parsedNamespace = this.parseKey(namespace);\r\n\r\n this.log(\"clearing\", parsedNamespace || \"(all)\");\r\n\r\n const removed: string[] = [];\r\n\r\n if (parsedNamespace === \"\") {\r\n for (const key of this.cache.keys()) {\r\n removed.push(key);\r\n }\r\n } else {\r\n const prefix = parsedNamespace + \".\";\r\n\r\n for (const key of this.cache.keys()) {\r\n if (key === parsedNamespace || key.startsWith(prefix)) {\r\n removed.push(key);\r\n }\r\n }\r\n }\r\n\r\n for (const key of removed) {\r\n const node = this.cache.get(key);\r\n if (node) {\r\n this.removeNode(node);\r\n this.cache.delete(key);\r\n }\r\n await this.emit(\"removed\", { key });\r\n }\r\n\r\n this.log(\"cleared\", parsedNamespace || \"(all)\");\r\n\r\n return removed;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n // For conditional writes, check existence via get() so expired entries\r\n // are treated as missing (get() handles expiry cleanup). The raw `cache.get`\r\n // node lookup would let stale entries block onConflict: \"create\".\r\n let existingNode = this.cache.get(parsedKey);\r\n if (existingNode && existingNode.isExpired) {\r\n this.removeNode(existingNode);\r\n this.cache.delete(parsedKey);\r\n existingNode = undefined;\r\n }\r\n\r\n const exists = Boolean(existingNode);\r\n\r\n if (onConflict === \"create\" && exists) {\r\n const result: CacheSetResult = {\r\n wasSet: false,\r\n existing: existingNode!.value,\r\n };\r\n return result;\r\n }\r\n\r\n if (onConflict === \"update\" && !exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: null };\r\n return result;\r\n }\r\n\r\n if (existingNode) {\r\n existingNode.value = value;\r\n if (ttl && ttl !== Infinity) {\r\n existingNode.expiresAt = Date.now() + ttl * 1000;\r\n } else {\r\n existingNode.expiresAt = undefined;\r\n }\r\n existingNode.staleAt = staleAt;\r\n if (vector) {\r\n existingNode.vector = vector.slice();\r\n }\r\n\r\n this.moveHead(existingNode);\r\n } else {\r\n const newNode = new CacheNode(parsedKey, value, ttl);\r\n newNode.staleAt = staleAt;\r\n if (vector) {\r\n newNode.vector = vector.slice();\r\n }\r\n\r\n this.cache.set(parsedKey, newNode);\r\n\r\n this.addNode(newNode);\r\n if (this.cache.size > this.capacity) {\r\n this.removeTail();\r\n }\r\n }\r\n\r\n if (tags && tags.length > 0) {\r\n await this.applyTags(parsedKey, tags);\r\n }\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n await this.emit(\"set\", { key: parsedKey, value, ttl });\r\n\r\n if (onConflict === \"create\" || onConflict === \"update\") {\r\n const result: CacheSetResult = { wasSet: true, existing: null };\r\n return result;\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Move the node to the head\r\n */\r\n protected moveHead(node: CacheNode) {\r\n this.removeNode(node);\r\n this.addNode(node);\r\n }\r\n\r\n /**\r\n * Remove the node from the cache\r\n */\r\n protected removeNode(node: CacheNode) {\r\n node.prev!.next = node.next;\r\n node.next!.prev = node.prev;\r\n }\r\n\r\n /**\r\n * Add the node to the head\r\n */\r\n protected addNode(node: CacheNode) {\r\n node.next = this.head.next;\r\n node.prev = this.head;\r\n this.head.next!.prev = node;\r\n this.head.next = node;\r\n }\r\n\r\n /**\r\n * Remove the tail node\r\n */\r\n protected removeTail() {\r\n const node = this.tail.prev!;\r\n\r\n this.removeNode(node);\r\n\r\n this.cache.delete(node.key);\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\r\n * Returns `null` for missing or expired nodes — `swr()` consumes this\r\n * to branch on freshness without going through `get()`'s clone-and-emit\r\n * path.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const parsedKey = this.parseKey(key);\r\n const node = this.cache.get(parsedKey);\r\n\r\n if (!node || node.isExpired) {\r\n return null;\r\n }\r\n\r\n return {\r\n data: node.value,\r\n expiresAt: node.expiresAt,\r\n staleAt: node.staleAt,\r\n };\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"fetching\", parsedKey);\r\n\r\n const node = this.cache.get(parsedKey);\r\n\r\n if (!node) {\r\n this.log(\"notFound\", parsedKey);\r\n // Emit miss event\r\n await this.emit(\"miss\", { key: parsedKey });\r\n return null;\r\n }\r\n\r\n // Check if expired\r\n if (node.isExpired) {\r\n this.removeNode(node);\r\n this.cache.delete(parsedKey);\r\n this.log(\"expired\", parsedKey);\r\n // Emit expired event\r\n await this.emit(\"expired\", { key: parsedKey });\r\n // Also emit miss since we're returning null\r\n await this.emit(\"miss\", { key: parsedKey });\r\n return null;\r\n }\r\n\r\n this.moveHead(node);\r\n\r\n this.log(\"fetched\", parsedKey);\r\n\r\n const value = node.value;\r\n\r\n // Apply cloning for immutability protection\r\n if (value === null || value === undefined) {\r\n return value;\r\n }\r\n\r\n const type = typeof value;\r\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\r\n // Emit hit event\r\n await this.emit(\"hit\", { key: parsedKey, value });\r\n return value;\r\n }\r\n\r\n try {\r\n const clonedValue = structuredClone(value);\r\n // Emit hit event\r\n await this.emit(\"hit\", { key: parsedKey, value: clonedValue });\r\n return clonedValue;\r\n } catch (error) {\r\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"removing\", parsedKey);\r\n\r\n const node = this.cache.get(parsedKey);\r\n\r\n if (node) {\r\n this.removeNode(node);\r\n this.cache.delete(parsedKey);\r\n }\r\n\r\n this.log(\"removed\", parsedKey);\r\n\r\n // Emit removed event\r\n await this.emit(\"removed\", { key: parsedKey });\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * When a `globalPrefix` is configured, `flush` scopes itself to that prefix\r\n * so multi-tenant caches don't accidentally wipe sibling tenants. Without\r\n * a prefix, clears everything.\r\n */\r\n public async flush() {\r\n this.log(\"flushing\");\r\n\r\n if (this.options.globalPrefix) {\r\n await this.removeNamespace(\"\");\r\n } else {\r\n this.cache.clear();\r\n this.init();\r\n }\r\n\r\n this.log(\"flushed\");\r\n\r\n await this.emit(\"flushed\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Brute-force O(N) cosine similarity over every cached node that carries a\r\n * vector. Suitable for development and small in-memory knowledge bases —\r\n * not for production beyond ~10k entries.\r\n *\r\n * @warning Dev-only — O(N) per query.\r\n */\r\n public async similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n const tagFilter = await this.getKeysForTags(options.tags);\r\n\r\n const hits: CacheSimilarHit<T>[] = [];\r\n\r\n for (const [parsedKey, node] of this.cache) {\r\n if (!node.vector) continue;\r\n if (node.isExpired) continue;\r\n if (tagFilter && !tagFilter.has(parsedKey)) continue;\r\n\r\n const score = cosineSimilarity(vector, node.vector);\r\n\r\n if (options.threshold !== undefined && score < options.threshold) {\r\n continue;\r\n }\r\n\r\n // Clone object values to match get() semantics.\r\n let value: any = node.value;\r\n if (value !== null && value !== undefined) {\r\n const t = typeof value;\r\n if (t !== \"string\" && t !== \"number\" && t !== \"boolean\") {\r\n value = structuredClone(value);\r\n }\r\n }\r\n\r\n hits.push({ key: parsedKey, value, score });\r\n }\r\n\r\n hits.sort((a, b) => b.score - a.score);\r\n\r\n if (options.topK >= 0 && hits.length > options.topK) {\r\n hits.length = options.topK;\r\n }\r\n\r\n return hits;\r\n }\r\n\r\n /**\r\n * Get lru capacity\r\n */\r\n public get capacity() {\r\n return this.options.capacity || 1000;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async disconnect() {\r\n // Clear the cleanup interval to prevent memory leaks\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n this.cleanupInterval = undefined;\r\n }\r\n\r\n await super.disconnect();\r\n }\r\n}\r\n","import type { GenericObject } from \"@mongez/reinforcements\";\r\nimport { get, set, unset } from \"@mongez/reinforcements\";\r\nimport type {\r\n CacheData,\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheTtl,\r\n MemoryCacheOptions,\r\n} from \"../types\";\r\nimport { cosineSimilarity } from \"../utils\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nexport class MemoryCacheDriver\r\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\r\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\r\n{\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"memory\";\r\n\r\n /**\r\n * Cached data\r\n */\r\n public data: GenericObject = {};\r\n\r\n /**\r\n * List of data that will be cleared from cache\r\n */\r\n protected temporaryData: Record<\r\n string,\r\n {\r\n key: string;\r\n expiresAt: number;\r\n }\r\n > = {};\r\n\r\n /**\r\n * Cleanup interval reference\r\n */\r\n protected cleanupInterval?: NodeJS.Timeout;\r\n\r\n /**\r\n * Access order tracking for LRU eviction (when maxSize is set)\r\n */\r\n protected accessOrder: string[] = [];\r\n\r\n /**\r\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\r\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\r\n * `remove`, `flush`, expiry, namespace clear, and LRU eviction.\r\n */\r\n protected vectorIndex: Map<string, number[]> = new Map();\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public constructor() {\r\n super();\r\n\r\n this.startCleanup();\r\n }\r\n\r\n /**\r\n * Start the cleanup process whenever a data that has a cache key is set\r\n */\r\n public startCleanup() {\r\n // Clear existing interval if any\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n }\r\n\r\n this.cleanupInterval = setInterval(async () => {\r\n const now = Date.now();\r\n\r\n for (const key in this.temporaryData) {\r\n if (this.temporaryData[key].expiresAt <= now) {\r\n await this.remove(this.temporaryData[key].key);\r\n delete this.temporaryData[key];\r\n\r\n this.log(\"expired\", key);\r\n // Emit expired event\r\n await this.emit(\"expired\", { key });\r\n }\r\n }\r\n }, 1000);\r\n\r\n // do not block the process from exiting\r\n this.cleanupInterval.unref();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n this.log(\"clearing\", namespace);\r\n\r\n namespace = this.parseKey(namespace);\r\n\r\n unset(this.data, [namespace]);\r\n\r\n // Drop vector entries that fall under this namespace.\r\n if (namespace === \"\") {\r\n this.vectorIndex.clear();\r\n } else {\r\n const prefix = namespace + \".\";\r\n for (const k of [...this.vectorIndex.keys()]) {\r\n if (k === namespace || k.startsWith(prefix)) {\r\n this.vectorIndex.delete(k);\r\n }\r\n }\r\n }\r\n\r\n this.log(\"cleared\", namespace);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n // Use get() for the existence check so expired entries are treated as\r\n // missing (and cleaned up as a side effect). A raw map lookup would let\r\n // stale entries block onConflict: \"create\" even after their TTL elapsed.\r\n const existingValue = onConflict === \"upsert\" ? null : await this.get(key);\r\n const exists = existingValue !== null;\r\n\r\n if (onConflict === \"create\" && exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: existingValue };\r\n return result;\r\n }\r\n\r\n if (onConflict === \"update\" && !exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: null };\r\n return result;\r\n }\r\n\r\n const data = this.prepareDataForStorage(value, ttl, staleAt);\r\n\r\n if (ttl) {\r\n this.setTemporaryData(key, parsedKey, ttl);\r\n }\r\n\r\n set(this.data, parsedKey, data);\r\n\r\n this.trackAccess(parsedKey);\r\n\r\n if (!exists && this.options.maxSize) {\r\n await this.enforceMaxSize();\r\n }\r\n\r\n if (tags && tags.length > 0) {\r\n await this.applyTags(parsedKey, tags);\r\n }\r\n\r\n if (vector) {\r\n this.vectorIndex.set(parsedKey, vector.slice());\r\n }\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n await this.emit(\"set\", { key: parsedKey, value, ttl });\r\n\r\n if (onConflict === \"create\" || onConflict === \"update\") {\r\n const result: CacheSetResult = { wasSet: true, existing: null };\r\n return result;\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"fetching\", parsedKey);\r\n\r\n const value: CacheData = get(this.data, parsedKey);\r\n\r\n if (!value) {\r\n this.log(\"notFound\", parsedKey);\r\n // Emit miss event\r\n await this.emit(\"miss\", { key: parsedKey });\r\n return null;\r\n }\r\n\r\n const result = await this.parseCachedData(parsedKey, value);\r\n\r\n if (result === null) {\r\n // Expired\r\n await this.emit(\"miss\", { key: parsedKey });\r\n } else {\r\n // Track access for LRU\r\n this.trackAccess(parsedKey);\r\n // Emit hit event\r\n await this.emit(\"hit\", { key: parsedKey, value: result });\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\r\n * Returns `null` for missing or expired entries so the SWR flow can branch\r\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const parsedKey = this.parseKey(key);\r\n const entry: CacheData | undefined = get(this.data, parsedKey);\r\n\r\n if (!entry) {\r\n return null;\r\n }\r\n\r\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\r\n return null;\r\n }\r\n\r\n return entry;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"removing\", parsedKey);\r\n\r\n unset(this.data, [parsedKey]);\r\n\r\n // Clean up from temporaryData as well\r\n delete this.temporaryData[parsedKey];\r\n\r\n // Remove from access order\r\n this.removeFromAccessOrder(parsedKey);\r\n\r\n // Drop the vector index entry if any\r\n this.vectorIndex.delete(parsedKey);\r\n\r\n this.log(\"removed\", parsedKey);\r\n\r\n // Emit removed event\r\n await this.emit(\"removed\", { key: parsedKey });\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n this.log(\"flushing\");\r\n if (this.options.globalPrefix) {\r\n this.removeNamespace(\"\");\r\n } else {\r\n this.data = {};\r\n this.accessOrder = [];\r\n this.vectorIndex.clear();\r\n }\r\n\r\n this.log(\"flushed\");\r\n\r\n // Emit flushed event\r\n await this.emit(\"flushed\");\r\n }\r\n\r\n /**\r\n * Set the temporary data\r\n */\r\n protected setTemporaryData(key: CacheKey, parsedKey: string, ttl: number) {\r\n this.temporaryData[parsedKey] = {\r\n key: JSON.stringify(key),\r\n expiresAt: Date.now() + ttl * 1000,\r\n };\r\n }\r\n\r\n /**\r\n * Track access for LRU eviction\r\n */\r\n protected trackAccess(key: string) {\r\n if (!this.options.maxSize) return;\r\n\r\n // Remove key from current position\r\n const index = this.accessOrder.indexOf(key);\r\n if (index > -1) {\r\n this.accessOrder.splice(index, 1);\r\n }\r\n\r\n // Add to end (most recently used)\r\n this.accessOrder.push(key);\r\n }\r\n\r\n /**\r\n * Remove key from access order tracking\r\n */\r\n protected removeFromAccessOrder(key: string) {\r\n const index = this.accessOrder.indexOf(key);\r\n if (index > -1) {\r\n this.accessOrder.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Enforce max size by evicting least recently used items.\r\n *\r\n * Recomputes the live cache size on every iteration — a single snapshot at\r\n * the top of the loop would go stale and cause this routine to evict every\r\n * entry in `accessOrder` (including the just-inserted key).\r\n */\r\n protected async enforceMaxSize() {\r\n if (!this.options.maxSize) {\r\n return;\r\n }\r\n\r\n while (\r\n this.getCacheSize() > this.options.maxSize &&\r\n this.accessOrder.length > 0\r\n ) {\r\n const lruKey = this.accessOrder.shift();\r\n if (!lruKey) {\r\n break;\r\n }\r\n\r\n this.log(\"removing\", lruKey);\r\n unset(this.data, [lruKey]);\r\n delete this.temporaryData[lruKey];\r\n this.vectorIndex.delete(lruKey);\r\n this.log(\"removed\", lruKey);\r\n }\r\n }\r\n\r\n /**\r\n * Get current cache size (number of cached items)\r\n */\r\n protected getCacheSize(): number {\r\n // Count top-level keys in data object\r\n return Object.keys(this.data).length;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Brute-force O(N) cosine similarity over every entry that was written with\r\n * `set({ vector })`. Suitable for development and small in-memory knowledge\r\n * bases — not for production beyond ~10k entries. Use the `pg` driver\r\n * (with pgvector) or `redis` (with RediSearch) at scale.\r\n *\r\n * @warning Dev-only — O(N) per query.\r\n */\r\n public async similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n const tagFilter = await this.getKeysForTags(options.tags);\r\n\r\n const hits: CacheSimilarHit<T>[] = [];\r\n\r\n for (const [parsedKey, stored] of this.vectorIndex) {\r\n if (tagFilter && !tagFilter.has(parsedKey)) {\r\n continue;\r\n }\r\n\r\n const value = (await this.get(parsedKey)) as T | null;\r\n // get() returns null for expired entries — and remove() drops the vector\r\n // index, so the next pass won't see it. Skip in case of timing.\r\n if (value === null) {\r\n continue;\r\n }\r\n\r\n const score = cosineSimilarity(vector, stored);\r\n\r\n if (options.threshold !== undefined && score < options.threshold) {\r\n continue;\r\n }\r\n\r\n hits.push({ key: parsedKey, value, score });\r\n }\r\n\r\n hits.sort((a, b) => b.score - a.score);\r\n\r\n if (options.topK >= 0 && hits.length > options.topK) {\r\n hits.length = options.topK;\r\n }\r\n\r\n return hits;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async disconnect() {\r\n // Clear the cleanup interval to prevent memory leaks\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n this.cleanupInterval = undefined;\r\n }\r\n\r\n await super.disconnect();\r\n }\r\n}\r\n","import { get } from \"@mongez/reinforcements\";\r\nimport type { CacheData, CacheDriver, CacheKey, MemoryExtendedCacheOptions } from \"../types\";\r\nimport { parseTtl } from \"../utils\";\r\nimport { MemoryCacheDriver } from \"./memory-cache-driver\";\r\n\r\nexport class MemoryExtendedCacheDriver\r\n extends MemoryCacheDriver\r\n implements CacheDriver<MemoryExtendedCacheDriver, MemoryExtendedCacheOptions>\r\n{\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"memoryExtended\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"fetching\", parsedKey);\r\n\r\n const value: CacheData = get(this.data, parsedKey);\r\n\r\n if (!value) {\r\n this.log(\"notFound\", parsedKey);\r\n return null;\r\n }\r\n\r\n const rawTtl = value.ttl ?? this.options.ttl;\r\n const ttl = rawTtl !== undefined ? parseTtl(rawTtl) : undefined;\r\n\r\n if (ttl) {\r\n // reset the expiration time\r\n this.setTemporaryData(key, parsedKey, ttl);\r\n value.expiresAt = this.getExpiresAt(ttl);\r\n }\r\n\r\n return this.parseCachedData(parsedKey, value);\r\n }\r\n}\r\n","import 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\";\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 * 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 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 const existing = onConflict === \"upsert\" ? null : await this.get(key);\n const exists = existing !== null;\n\n if (onConflict === \"create\" && exists) {\n const result: CacheSetResult = { wasSet: false, existing };\n\n return result;\n }\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n\n return result;\n }\n\n const data = this.prepareDataForStorage(value, ttl, staleAt);\n this.storage.set(parsedKey, data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, 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 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 * 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;\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 * 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\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.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","import type { GenericObject } from \"@mongez/reinforcements\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type {\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheTtl,\r\n NullCacheDriverOptions,\r\n} from \"../types\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nexport class NullCacheDriver\r\n extends BaseCacheDriver<NullCacheDriver, NullCacheDriverOptions>\r\n implements CacheDriver<NullCacheDriver, NullCacheDriverOptions>\r\n{\r\n /**\r\n * Options list\r\n */\r\n public options: GenericObject = {};\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"null\";\r\n\r\n /**\r\n * Cached data\r\n */\r\n public data: GenericObject = {};\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get client() {\r\n return this;\r\n }\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor(options: GenericObject = {}) {\r\n super();\r\n this.setOptions(options);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: GenericObject) {\r\n this.options = options;\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public parseKey(_key: CacheKey) {\r\n return \"\";\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n log.info(\"cache\", \"clearing namespace\", namespace);\r\n\r\n log.success(\"cache\", \"namespace cleared\", namespace);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n _value: any,\r\n _ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n log.info(\"cache\", \"setting key\", key);\r\n\r\n log.success(\"cache\", \"key set\", key);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n log.info(\"cache\", \"fetching\", key);\r\n\r\n log.success(\"cache\", \"fetched\", key);\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n log.info(\"cache\", \"removing\", key);\r\n\r\n log.success(\"cache\", \"removed\", key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n log.info(\"cache\", \"flushing\", \"all\");\r\n\r\n log.success(\"cache\", \"flushed\", \"all\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * The null driver is a black-hole — `similar()` mirrors `get()` and returns\r\n * an empty result set rather than throwing.\r\n */\r\n public async similar(): Promise<any[]> {\r\n return [];\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n log.success(\"cache\", \"connected\", \"Connected to null cache driver\");\r\n }\r\n}\r\n","import type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n PgCacheOptions,\n PgClientLike,\n} from \"../types\";\nimport { CacheConfigurationError, CacheUnsupportedError } from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\n/**\n * Allowed characters in a Postgres identifier (table name). We accept the\n * conservative ASCII subset and reject anything else — interpolating an\n * arbitrary string into DDL would be a SQL-injection footgun.\n */\nconst SAFE_IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Postgres cache driver with optional pgvector similarity support.\n *\n * Connection lifecycle is the caller's responsibility — pass an already-built\n * `pg.Pool` or `pg.Client` via the `client` option. The driver never closes it\n * on `cache.disconnect()`, so the same pool can serve queries elsewhere in\n * the app.\n *\n * Schema is not auto-migrated. Call `driver.schema()` to get the DDL string\n * and run it through whichever migration tool you use.\n *\n * @example\n * import { Pool } from \"pg\";\n * import { cache, PgCacheDriver } from \"@warlock.js/cache\";\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n *\n * cache.setCacheConfigurations({\n * default: \"pg\",\n * drivers: { pg: PgCacheDriver },\n * options: { pg: { client: pool, table: \"warlock_cache\", ttl: \"1h\" } },\n * });\n *\n * await cache.init();\n *\n * // Run once, via your own migration tooling:\n * // await pool.query(driver.schema());\n */\nexport class PgCacheDriver\n extends BaseCacheDriver<PgClientLike, PgCacheOptions>\n implements CacheDriver<PgClientLike, PgCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"pg\";\n\n /**\n * Cached result of the pgvector extension check. Populated lazily on first\n * vector op so the driver doesn't probe the database at construction time.\n */\n protected vectorReady?: boolean;\n\n /**\n * {@inheritdoc}\n *\n * Validates `client` presence and the table name before storing options.\n */\n public setOptions(options: PgCacheOptions) {\n if (!options || !options.client || typeof options.client.query !== \"function\") {\n throw new CacheConfigurationError(\n \"Pg cache driver requires a 'client' option implementing { query(text, values) } — pass a pg.Pool or pg.Client.\",\n );\n }\n\n const table = options.table ?? \"warlock_cache\";\n if (!SAFE_IDENT.test(table)) {\n throw new CacheConfigurationError(\n `Pg cache driver: invalid table name '${table}'. Allowed: [A-Za-z_][A-Za-z0-9_]*.`,\n );\n }\n\n if (options.vector) {\n const dim = options.vector.dimensions;\n if (!Number.isInteger(dim) || dim <= 0) {\n throw new CacheConfigurationError(\n `Pg cache driver: vector.dimensions must be a positive integer; got ${dim}.`,\n );\n }\n const idx = options.vector.index ?? \"hnsw\";\n if (idx !== \"hnsw\" && idx !== \"ivfflat\") {\n throw new CacheConfigurationError(\n `Pg cache driver: vector.index must be 'hnsw' or 'ivfflat'; got '${idx}'.`,\n );\n }\n }\n\n return super.setOptions({ ...options, table });\n }\n\n /**\n * Lazy pgvector availability check. Runs once on the first vector op and\n * caches the result — subsequent ops are zero-overhead. Throws\n * {@link CacheConfigurationError} if the extension isn't installed; throws\n * {@link CacheUnsupportedError} if `vector` config wasn't provided at all.\n */\n protected async ensureVectorReady(): Promise<void> {\n if (!this.options.vector) {\n throw new CacheUnsupportedError(\n \"'pg' driver: similarity retrieval requires the 'vector' config block. Set options.vector.dimensions and reconnect.\",\n );\n }\n\n if (this.vectorReady === true) {\n return;\n }\n\n const { rows } = await this.pgClient.query(\n `SELECT 1 FROM pg_extension WHERE extname = 'vector'`,\n );\n\n if (rows.length === 0) {\n throw new CacheConfigurationError(\n \"'pg' driver: pgvector extension not installed. Run 'CREATE EXTENSION vector;' or remove the 'vector' config option.\",\n );\n }\n\n this.vectorReady = true;\n }\n\n /**\n * Format a numeric vector for pgvector ingestion. The `vector` type accepts\n * a string literal `'[1,2,3]'` cast via `::vector` — this avoids depending\n * on the binary protocol and works against any pg client.\n */\n protected formatVector(vector: number[]): string {\n return `[${vector.join(\",\")}]`;\n }\n\n /**\n * Resolved table name. Always defined post-`setOptions` — the validator\n * fills in the default.\n */\n protected get table(): string {\n return this.options.table ?? \"warlock_cache\";\n }\n\n /**\n * The user-supplied `pg.Pool` / `pg.Client`. Use this rather than `this.client`\n * (which has a generic fallback to `this`) for actual queries.\n */\n protected get pgClient(): PgClientLike {\n return this.options.client;\n }\n\n /**\n * Compute an absolute `expires_at` Date for the given relative TTL in seconds,\n * or `null` when the entry should not expire (`Infinity` / 0 / undefined).\n */\n protected ttlToExpiresAt(ttl?: number): Date | null {\n if (!ttl || ttl === Infinity) {\n return null;\n }\n\n return new Date(Date.now() + ttl * 1000);\n }\n\n /**\n * Return the SQL needed to provision the cache table + index. Run once via\n * the caller's migration tooling — the driver never auto-migrates.\n *\n * @example\n * await pool.query(driver.schema());\n */\n public schema(): string {\n const t = this.table;\n const vec = this.options.vector;\n\n const columns = [\n ` key TEXT PRIMARY KEY,`,\n ` value JSONB NOT NULL,`,\n ` expires_at TIMESTAMPTZ,`,\n ` stale_at TIMESTAMPTZ,`,\n ` tags TEXT[] NOT NULL DEFAULT '{}'::TEXT[]`,\n ];\n\n if (vec) {\n // Trailing comma on the previous line to make this a valid column list.\n columns[columns.length - 1] = columns[columns.length - 1] + \",\";\n columns.push(` embedding VECTOR(${vec.dimensions})`);\n }\n\n const lines = [\n `CREATE TABLE IF NOT EXISTS ${t} (`,\n ...columns,\n `);`,\n `CREATE INDEX IF NOT EXISTS idx_${t}_expires_at ON ${t} (expires_at);`,\n `CREATE INDEX IF NOT EXISTS idx_${t}_tags ON ${t} USING GIN (tags);`,\n ];\n\n if (vec) {\n const idx = vec.index ?? \"hnsw\";\n lines.push(\n `CREATE INDEX IF NOT EXISTS idx_${t}_embedding ON ${t} USING ${idx} (embedding vector_cosine_ops);`,\n );\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n // No-op — caller owns the connection. We emit `connected` for symmetry\n // with the other drivers' lifecycle events.\n this.log(\"connecting\");\n this.log(\"connected\");\n await this.emit(\"connected\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Does NOT close the user-supplied client — lifecycle stays with the caller.\n */\n public async disconnect() {\n this.log(\"disconnected\");\n await this.emit(\"disconnected\");\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 if (vector) {\n if (!this.options.vector) {\n throw new CacheUnsupportedError(\n \"'pg' driver: cannot index a vector without options.vector configuration — set { dimensions } and recreate the table via driver.schema().\",\n );\n }\n\n const expected = this.options.vector.dimensions;\n if (vector.length !== expected) {\n throw new CacheConfigurationError(\n `Pg cache driver: vector dimension mismatch — expected ${expected}, got ${vector.length}.`,\n );\n }\n\n await this.ensureVectorReady();\n }\n\n this.log(\"caching\", parsedKey);\n\n const expiresAt = this.ttlToExpiresAt(ttl);\n const staleAtDate = staleAt !== undefined ? new Date(staleAt) : null;\n const tagsArr = tags ?? [];\n const serialized = JSON.stringify(value);\n const vecLiteral = vector ? this.formatVector(vector) : null;\n\n const t = this.table;\n\n // Build column / placeholder / param triplets dynamically so the same code\n // path serves both KV-only and vector-aware writes. Param order is fixed:\n // $1 = key, $2 = value (jsonb), $3 = expires_at, $4 = stale_at,\n // $5 = tags, $6 = embedding::vector (only present when vecLiteral !== null).\n const cols = [\"key\", \"value\", \"expires_at\", \"stale_at\", \"tags\"];\n const placeholders = [\"$1\", \"$2::jsonb\", \"$3\", \"$4\", \"$5\"];\n const params: unknown[] = [parsedKey, serialized, expiresAt, staleAtDate, tagsArr];\n if (vecLiteral !== null) {\n cols.push(\"embedding\");\n placeholders.push(`$${params.length + 1}::vector`);\n params.push(vecLiteral);\n }\n const colList = cols.join(\", \");\n const valList = placeholders.join(\", \");\n const setClause = cols\n .slice(1)\n .map((c) => `${c} = EXCLUDED.${c}`)\n .join(\", \");\n const updateSetClause = cols\n .slice(1)\n .map((c, i) => `${c} = ${placeholders[i + 1]}`)\n .join(\", \");\n\n if (onConflict === \"create\") {\n // Race-safe insert: if another worker already holds the key (and the row\n // hasn't expired), DO NOTHING; we then SELECT to surface the existing value.\n const { rows } = await this.pgClient.query(\n `INSERT INTO ${t}(${colList})\n VALUES (${valList})\n ON CONFLICT (key) DO UPDATE\n SET ${setClause}\n WHERE ${t}.expires_at IS NOT NULL AND ${t}.expires_at < now()\n RETURNING value`,\n params,\n );\n\n if (rows.length === 0) {\n // Conflict + existing row not expired → fetch existing for the result.\n const existing = await this.get(key);\n return { wasSet: false, existing } satisfies CacheSetResult;\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n return { wasSet: true, existing: null } satisfies CacheSetResult;\n }\n\n if (onConflict === \"update\") {\n // Update only when the key exists AND hasn't expired.\n const { rows } = await this.pgClient.query(\n `UPDATE ${t}\n SET ${updateSetClause}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())\n RETURNING value`,\n params,\n );\n\n if (rows.length === 0) {\n return { wasSet: false, existing: null } satisfies CacheSetResult;\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n return { wasSet: true, existing: null } satisfies CacheSetResult;\n }\n\n // upsert (default)\n await this.pgClient.query(\n `INSERT INTO ${t}(${colList})\n VALUES (${valList})\n ON CONFLICT (key) DO UPDATE\n SET ${setClause}`,\n params,\n );\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n this.log(\"fetching\", parsedKey);\n\n const t = this.table;\n const { rows } = await this.pgClient.query(\n `SELECT value FROM ${t}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())`,\n [parsedKey],\n );\n\n if (rows.length === 0) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.log(\"fetched\", parsedKey);\n\n // pg's JSONB type round-trips through node-postgres as a parsed JS value\n // already — but some pool implementations may hand back a string. Be defensive.\n let value = rows[0].value;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n // Leave it as-is; cloning below will fail loud if it's truly broken.\n }\n }\n\n if (value === null || value === undefined) {\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n try {\n const cloned = structuredClone(value);\n await this.emit(\"hit\", { key: parsedKey, value: cloned });\n return cloned;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\n throw error;\n }\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\n * Returns `null` for missing or expired rows — `swr()` consumes this to\n * branch on freshness without going through `get()`'s clone-and-emit path.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const t = this.table;\n\n const { rows } = await this.pgClient.query(\n `SELECT value, expires_at, stale_at FROM ${t}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())`,\n [parsedKey],\n );\n\n if (rows.length === 0) {\n return null;\n }\n\n let data = rows[0].value;\n if (typeof data === \"string\") {\n try {\n data = JSON.parse(data);\n } catch {\n // Leave as-is — getEntry is metadata-shaped, the caller will hit the\n // same parse path on the public `get()` if they care.\n }\n }\n\n const entry: CacheData = { data };\n\n const expiresAtRaw = rows[0].expires_at as Date | string | null;\n if (expiresAtRaw) {\n const expiresAt = expiresAtRaw instanceof Date ? expiresAtRaw : new Date(expiresAtRaw);\n entry.expiresAt = expiresAt.getTime();\n }\n\n const staleAtRaw = rows[0].stale_at as Date | string | null;\n if (staleAtRaw) {\n const staleAt = staleAtRaw instanceof Date ? staleAtRaw : new Date(staleAtRaw);\n entry.staleAt = staleAt.getTime();\n }\n\n return entry;\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n this.log(\"removing\", parsedKey);\n\n const t = this.table;\n await this.pgClient.query(`DELETE FROM ${t} WHERE key = $1`, [parsedKey]);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Deletes all rows whose key equals the namespace exactly or starts with\n * `<namespace>.` — same boundary semantics as the other drivers.\n */\n public async removeNamespace(namespace: string) {\n const parsed = this.parseKey(namespace);\n this.log(\"clearing\", parsed || \"(all)\");\n\n const t = this.table;\n\n if (parsed === \"\") {\n await this.pgClient.query(`DELETE FROM ${t}`);\n } else {\n // Escape `_` and `%` in the prefix so they aren't treated as LIKE wildcards.\n const escaped = parsed.replace(/\\\\/g, \"\\\\\\\\\").replace(/_/g, \"\\\\_\").replace(/%/g, \"\\\\%\");\n await this.pgClient.query(`DELETE FROM ${t} WHERE key = $1 OR key LIKE $2 ESCAPE '\\\\'`, [\n parsed,\n `${escaped}.%`,\n ]);\n }\n\n this.log(\"cleared\", parsed || \"(all)\");\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * Honors `globalPrefix` — when configured, scopes the flush to entries\n * under the prefix rather than truncating the entire table (which could\n * wipe sibling tenants sharing the same Postgres database).\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n await this.pgClient.query(`DELETE FROM ${this.table}`);\n }\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n *\n * pgvector-backed similarity. Uses the `<=>` cosine-distance operator\n * (lower distance = higher similarity) and converts to cosine similarity\n * as `1 - distance` so the returned `score` matches the rest of the\n * package (`[0, 1]`, higher is more similar).\n *\n * Honors `topK`, `threshold`, and an optional `tags` filter (native\n * `tags && $tags` overlap query — much faster than the meta-key path).\n *\n * Throws {@link CacheUnsupportedError} when `options.vector` was not\n * configured at driver setup; throws {@link CacheConfigurationError} when\n * the pgvector extension is missing or the query vector's dimension count\n * doesn't match the configured one.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n if (!this.options.vector) {\n throw new CacheUnsupportedError(\n \"'pg' driver: similarity retrieval requires the 'vector' config block. Set options.vector.dimensions and reconnect.\",\n );\n }\n\n const expected = this.options.vector.dimensions;\n if (vector.length !== expected) {\n throw new CacheConfigurationError(\n `Pg cache driver: vector dimension mismatch — expected ${expected}, got ${vector.length}.`,\n );\n }\n\n if (!Number.isInteger(options.topK) || options.topK <= 0) {\n throw new CacheConfigurationError(\n `Pg cache driver: similar.topK must be a positive integer; got ${options.topK}.`,\n );\n }\n\n await this.ensureVectorReady();\n\n const t = this.table;\n const vecLiteral = this.formatVector(vector);\n const params: unknown[] = [vecLiteral];\n let tagFilter = \"\";\n\n if (options.tags && options.tags.length > 0) {\n params.push(options.tags);\n tagFilter = `AND tags && $${params.length}`;\n }\n\n params.push(options.topK);\n const topKParam = `$${params.length}`;\n\n const { rows } = await this.pgClient.query(\n `SELECT key, value, 1 - (embedding <=> $1::vector) AS score\n FROM ${t}\n WHERE embedding IS NOT NULL\n AND (expires_at IS NULL OR expires_at > now())\n ${tagFilter}\n ORDER BY embedding <=> $1::vector\n LIMIT ${topKParam}`,\n params,\n );\n\n const hits: CacheSimilarHit<T>[] = [];\n for (const row of rows) {\n const score = Number(row.score);\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n let value = row.value;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n // Surface as-is; if non-JSON crept in, the consumer will notice.\n }\n }\n\n // Match get() cloning semantics so consumers can't mutate cached state.\n if (value !== null && value !== undefined) {\n const ty = typeof value;\n if (ty !== \"string\" && ty !== \"number\" && ty !== \"boolean\") {\n value = structuredClone(value);\n }\n }\n\n hits.push({ key: row.key, value, score });\n }\n\n return hits;\n }\n}\n","import { log } from \"@warlock.js/logger\";\r\nimport type { createClient } from \"redis\";\r\nimport type {\r\n CacheData,\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheTtl,\r\n RedisOptions,\r\n} from \"../types\";\r\nimport { CacheConfigurationError, CacheUnsupportedError } from \"../types\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\n// ============================================================\r\n// Lazy-loaded Redis SDK Types\r\n// ============================================================\r\n\r\n/**\r\n * Cached Redis module (loaded once, reused)\r\n */\r\nlet RedisClient: typeof import(\"redis\");\r\n\r\nlet isModuleExists: boolean | null = null;\r\n\r\n/**\r\n * Installation instructions for Redis package\r\n */\r\nconst REDIS_INSTALL_INSTRUCTIONS = `\r\nRedis cache driver requires the redis package.\r\nInstall it with:\r\n\r\n npm install redis\r\n\r\nOr with your preferred package manager:\r\n\r\n pnpm add redis\r\n yarn add redis\r\n`.trim();\r\n\r\n/**\r\n * Load Redis module\r\n */\r\nasync function loadRedis() {\r\n try {\r\n RedisClient = await import(\"redis\");\r\n isModuleExists = true;\r\n } catch {\r\n isModuleExists = false;\r\n }\r\n}\r\n\r\nloadRedis();\r\n\r\n// ============================================================\r\n// RedisCacheDriver Class\r\n// ============================================================\r\n\r\nexport class RedisCacheDriver\r\n extends BaseCacheDriver<ReturnType<typeof createClient>, RedisOptions>\r\n implements CacheDriver<ReturnType<typeof createClient>, RedisOptions>\r\n{\r\n /**\r\n * Cache driver name\r\n */\r\n public name = \"redis\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: RedisOptions) {\r\n if (!options.url && !options.host) {\r\n throw new CacheConfigurationError(\r\n \"Redis driver requires either 'url' or 'host' option to be configured.\",\r\n );\r\n }\r\n\r\n return super.setOptions(options);\r\n }\r\n\r\n /**\r\n * {@inheritDoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n namespace = this.parseKey(namespace);\r\n\r\n this.log(\"clearing\", namespace);\r\n\r\n const keys = await this.client?.keys(`${namespace}*`);\r\n\r\n if (!keys || keys.length === 0) {\r\n this.log(\"notFound\", namespace);\r\n return;\r\n }\r\n\r\n await this.client?.del(keys);\r\n\r\n this.log(\"cleared\", namespace);\r\n\r\n return keys;\r\n }\r\n\r\n /**\r\n * {@inheritDoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\r\n\r\n if (vector) {\r\n throw new CacheUnsupportedError(\r\n \"'redis' driver does not yet support similarity retrieval. Phase 2 (RediSearch) is on the backlog — use a memory driver or the 'pg' driver (with pgvector) for now.\",\r\n );\r\n }\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n const serialized = JSON.stringify(value);\r\n const hasExpiry = Boolean(ttl) && ttl !== Infinity;\r\n\r\n let reply: string | null | undefined;\r\n\r\n if (onConflict === \"create\") {\r\n const options: { NX: true; EX?: number } = { NX: true };\r\n if (hasExpiry) {\r\n options.EX = ttl as number;\r\n }\r\n reply = await this.client?.set(parsedKey, serialized, options);\r\n } else if (onConflict === \"update\") {\r\n const options: { XX: true; EX?: number } = { XX: true };\r\n if (hasExpiry) {\r\n options.EX = ttl as number;\r\n }\r\n reply = await this.client?.set(parsedKey, serialized, options);\r\n } else if (hasExpiry) {\r\n reply = await this.client?.set(parsedKey, serialized, { EX: ttl as number });\r\n } else {\r\n reply = await this.client?.set(parsedKey, serialized);\r\n }\r\n\r\n const wasSet = reply === \"OK\";\r\n\r\n if ((onConflict === \"create\" || onConflict === \"update\") && !wasSet) {\r\n const existing = onConflict === \"create\" ? ((await this.get(key)) as any) : null;\r\n return { wasSet: false, existing } satisfies CacheSetResult;\r\n }\r\n\r\n if (tags && tags.length > 0) {\r\n await this.applyTags(parsedKey, tags);\r\n }\r\n\r\n if (staleAt !== undefined) {\r\n // Sidecar key for SWR freshness — keeps the main value JSON\r\n // backwards-compatible with entries written before SWR landed.\r\n const sidecarOptions: { EX?: number } = {};\r\n\r\n if (hasExpiry) {\r\n sidecarOptions.EX = ttl as number;\r\n }\r\n\r\n await this.client?.set(this.swrMetaKey(parsedKey), String(staleAt), sidecarOptions);\r\n }\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n await this.emit(\"set\", { key: parsedKey, value, ttl });\r\n\r\n if (onConflict === \"create\" || onConflict === \"update\") {\r\n return { wasSet: true, existing: null } satisfies CacheSetResult;\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * Build the sidecar key Redis uses to track SWR freshness without\r\n * wrapping the main value JSON.\r\n */\r\n protected swrMetaKey(parsedKey: string): string {\r\n return `__swrmeta:${parsedKey}`;\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper, fetching the value and the\r\n * SWR sidecar in parallel. Returns `null` when the main key is missing\r\n * or expired (Redis handles expiry natively, so the absence of the\r\n * value alone tells us).\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n const [valueRaw, staleAtRaw] = await Promise.all([\r\n this.client?.get(parsedKey),\r\n this.client?.get(this.swrMetaKey(parsedKey)),\r\n ]);\r\n\r\n if (!valueRaw) {\r\n return null;\r\n }\r\n\r\n const data = JSON.parse(valueRaw);\r\n const staleAt = staleAtRaw ? Number(staleAtRaw) : undefined;\r\n\r\n return staleAt !== undefined ? { data, staleAt } : { data };\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Redis tracks expiry natively (the payload carries no `expiresAt`), so read\r\n * the remaining lifetime with the `TTL` command. Redis returns `-2` for a\r\n * missing key and `-1` for a key with no expiry.\r\n */\r\n protected async getRemainingTtl(key: CacheKey): Promise<number | undefined> {\r\n const parsedKey = this.parseKey(key);\r\n const ttl = await this.client?.ttl(parsedKey);\r\n\r\n if (ttl === undefined || ttl === -2) {\r\n return undefined;\r\n }\r\n\r\n if (ttl === -1) {\r\n return Infinity;\r\n }\r\n\r\n return ttl;\r\n }\r\n\r\n /**\r\n * {@inheritDoc}\r\n */\r\n public async get(key: CacheKey) {\r\n key = this.parseKey(key);\r\n\r\n this.log(\"fetching\", key);\r\n\r\n const value = await this.client?.get(key);\r\n\r\n if (!value) {\r\n this.log(\"notFound\", key);\r\n // Emit miss event\r\n await this.emit(\"miss\", { key });\r\n return null;\r\n }\r\n\r\n this.log(\"fetched\", key);\r\n\r\n // Parse and return the value directly (Redis handles expiration natively)\r\n const parsedValue = JSON.parse(value);\r\n\r\n // Apply cloning for immutability protection\r\n if (parsedValue === null || parsedValue === undefined) {\r\n // Emit hit event\r\n await this.emit(\"hit\", { key, value: parsedValue });\r\n return parsedValue;\r\n }\r\n\r\n const type = typeof parsedValue;\r\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\r\n // Emit hit event\r\n await this.emit(\"hit\", { key, value: parsedValue });\r\n return parsedValue;\r\n }\r\n\r\n try {\r\n const clonedValue = structuredClone(parsedValue);\r\n // Emit hit event\r\n await this.emit(\"hit\", { key, value: clonedValue });\r\n return clonedValue;\r\n } catch (error) {\r\n this.logError(`Failed to clone cached value for ${key}`, error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritDoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n key = this.parseKey(key);\r\n\r\n this.log(\"removing\", key);\r\n\r\n // Drop the SWR sidecar alongside the main key — keeps metadata from\r\n // surviving a `remove` and confusing a later `swr` read.\r\n await this.client?.del([key, this.swrMetaKey(key)]);\r\n\r\n this.log(\"removed\", key);\r\n\r\n await this.emit(\"removed\", { key });\r\n }\r\n\r\n /**\r\n * {@inheritDoc}\r\n */\r\n public async flush() {\r\n this.log(\"flushing\");\r\n\r\n if (this.options.globalPrefix) {\r\n await this.removeNamespace(\"\");\r\n } else {\r\n await this.client?.flushAll();\r\n }\r\n\r\n this.log(\"flushed\");\r\n\r\n // Emit flushed event\r\n await this.emit(\"flushed\");\r\n }\r\n\r\n /**\r\n * {@inheritDoc}\r\n */\r\n public async connect() {\r\n if (this.clientDriver) return;\r\n\r\n if (!isModuleExists) {\r\n throw new Error(REDIS_INSTALL_INSTRUCTIONS);\r\n }\r\n\r\n const options = this.options;\r\n\r\n if (options && !options.url && options.host) {\r\n const auth =\r\n options.password || options.username ? `${options.username}:${options.password}@` : \"\";\r\n\r\n if (!options.url) {\r\n const host = options.host || \"localhost\";\r\n const port = options.port || 6379;\r\n options.url = `redis://${auth}${host}:${port}`;\r\n }\r\n }\r\n\r\n const clientOptions = {\r\n ...options,\r\n ...(this.options.clientOptions || {}),\r\n };\r\n\r\n this.log(\"connecting\");\r\n const { createClient } = RedisClient;\r\n\r\n this.client = createClient(clientOptions);\r\n\r\n this.client.on(\"error\", (error: Error) => {\r\n this.log(\"error\", error.message);\r\n });\r\n try {\r\n await this.client.connect();\r\n\r\n this.log(\"connected\");\r\n await this.emit(\"connected\");\r\n } catch (error) {\r\n log.error(\"cache\", \"redis\", error);\r\n await this.emit(\"error\", { error });\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritDoc}\r\n *\r\n * Guards against disconnecting when the client was never created. The base\r\n * `client` getter falls back to `this` when no client is set, so we check\r\n * the backing `clientDriver` directly — using `this.client` for this guard\r\n * would always be truthy and crash with \"this.quit is not a function\".\r\n */\r\n public async disconnect() {\r\n if (!this.clientDriver) {\r\n return;\r\n }\r\n\r\n this.log(\"disconnecting\");\r\n\r\n await this.clientDriver.quit();\r\n\r\n this.log(\"disconnected\");\r\n await this.emit(\"disconnected\");\r\n }\r\n\r\n /**\r\n * Atomic increment using Redis native INCRBY command\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n const result = await this.client?.incrBy(parsedKey, value);\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n // Emit set event\r\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: undefined });\r\n\r\n return result || 0;\r\n }\r\n\r\n /**\r\n * Atomic decrement using Redis native DECRBY command\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n const result = await this.client?.decrBy(parsedKey, value);\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n // Emit set event\r\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: undefined });\r\n\r\n return result || 0;\r\n }\r\n\r\n /**\r\n * Set if not exists (atomic operation)\r\n * Returns true if key was set, false if key already existed\r\n */\r\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n if (ttl === undefined) {\r\n ttl = this.ttl;\r\n }\r\n\r\n let result: string | null;\r\n\r\n // Use Redis native SET with NX option\r\n if (ttl && ttl !== Infinity) {\r\n result = await this.client?.set(parsedKey, JSON.stringify(value), {\r\n NX: true,\r\n EX: ttl,\r\n });\r\n } else {\r\n result = await this.client?.set(parsedKey, JSON.stringify(value), {\r\n NX: true,\r\n });\r\n }\r\n\r\n const wasSet = result === \"OK\";\r\n\r\n if (wasSet) {\r\n this.log(\"cached\", parsedKey);\r\n // Emit set event\r\n await this.emit(\"set\", { key: parsedKey, value, ttl });\r\n } else {\r\n this.log(\"notFound\", parsedKey);\r\n }\r\n\r\n return wasSet;\r\n }\r\n}\r\n"],"mappings":"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"}