@warlock.js/cache 5.30.0 → 5.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -8
- package/README.md +12 -21
- package/cjs/index.cjs +46 -27
- package/cjs/index.cjs.map +1 -1
- package/esm/cache-manager.d.mts +3 -1
- package/esm/cache-manager.d.mts.map +1 -1
- package/esm/cache-manager.mjs +2 -2
- package/esm/cache-manager.mjs.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +3 -1
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +4 -3
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.d.mts.map +1 -1
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts +3 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +6 -4
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts +3 -1
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-cache-driver.mjs +9 -3
- package/esm/drivers/memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/mock-cache-driver.d.mts +3 -1
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +11 -4
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/pg-cache-driver.d.mts +3 -1
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +6 -3
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.d.mts +4 -2
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +4 -3
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/list/memory-cache-list.mjs.map +1 -1
- package/esm/metrics.d.mts.map +1 -1
- package/esm/metrics.mjs.map +1 -1
- package/esm/scoped-cache.d.mts +3 -1
- package/esm/scoped-cache.d.mts.map +1 -1
- package/esm/scoped-cache.mjs +2 -2
- package/esm/scoped-cache.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +3 -1
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +2 -2
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts +3 -1
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +2 -2
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/esm/types.d.mts +16 -5
- package/esm/types.d.mts.map +1 -1
- package/esm/types.mjs.map +1 -1
- package/esm/utils.d.mts.map +1 -1
- package/esm/utils.mjs.map +1 -1
- package/llms-full.txt +229 -224
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/apply-cache-patterns/SKILL.md +3 -2
- package/skills/cache-basics/SKILL.md +24 -24
- package/skills/configure-pg-cache/SKILL.md +2 -2
- package/skills/configure-set-options/SKILL.md +14 -14
- package/skills/handle-cache-errors/SKILL.md +8 -8
- package/skills/index.md +4 -1
- package/skills/observe-cache/SKILL.md +8 -8
- package/skills/pick-cache-driver/SKILL.md +24 -23
- package/skills/test-cache-code/SKILL.md +15 -10
- package/skills/use-cache-atomic/SKILL.md +17 -11
- package/skills/use-cache-bulk/SKILL.md +8 -5
- package/skills/use-cache-list/SKILL.md +13 -13
- package/skills/use-cache-lock/SKILL.md +11 -17
- package/skills/use-cache-namespace/SKILL.md +11 -13
- package/skills/use-cache-similarity/SKILL.md +9 -9
- package/skills/use-cache-tags/SKILL.md +13 -13
- package/skills/use-cache-update-merge/SKILL.md +10 -10
- package/skills/use-cache-utils/SKILL.md +17 -17
- package/skills/use-cached-hof/SKILL.md +8 -7
- package/skills/use-swr/SKILL.md +14 -18
package/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["log","fs"],"sources":["../../../../../../../packages/cache/src/metrics.ts","../../../../../../../packages/cache/src/types.ts","../../../../../../../packages/cache/src/utils.ts","../../../../../../../packages/cache/src/tagged-scoped-cache.ts","../../../../../../../packages/cache/src/scoped-cache.ts","../../../../../../../packages/cache/src/cache-manager.ts","../../../../../../../packages/cache/src/cached/auto-key.ts","../../../../../../../packages/cache/src/cached/normalize-args.ts","../../../../../../../packages/cache/src/cached/cached.ts","../../../../../../../packages/cache/src/list/memory-cache-list.ts","../../../../../../../packages/cache/src/tagged-cache.ts","../../../../../../../packages/cache/src/drivers/base-cache-driver.ts","../../../../../../../packages/cache/src/drivers/file-cache-driver.ts","../../../../../../../packages/cache/src/drivers/in-memory-tag-index.ts","../../../../../../../packages/cache/src/drivers/lru-memory-cache-driver.ts","../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts","../../../../../../../packages/cache/src/drivers/memory-extended-cache-driver.ts","../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts","../../../../../../../packages/cache/src/drivers/null-cache-driver.ts","../../../../../../../packages/cache/src/drivers/pg-cache-driver.ts","../../../../../../../packages/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 // `sorted` is non-empty (the `samples.length === 0` early return above)\n // and `index` is clamped to its last element, so this always hits — but\n // the clamp is arithmetic the compiler does not evaluate. `?? 0` states\n // the floor without asserting: a percentile of an empty set is 0, which\n // is exactly what the early return above already answers.\n return sorted[index] ?? 0;\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\";\nimport type { RedisClientOptions } from \"redis\";\nimport type {\n BaseCacheDriver,\n FileCacheDriver,\n LRUMemoryCacheDriver,\n MemoryCacheDriver,\n MockCacheDriver,\n MemoryExtendedCacheDriver,\n NullCacheDriver,\n PgCacheDriver,\n RedisCacheDriver,\n} from \"./drivers\";\n\n/**\n * Base error class for cache-related errors\n */\nexport class CacheError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheError\";\n }\n}\n\n/**\n * Error thrown when cache connection fails\n */\nexport class CacheConnectionError extends CacheError {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheConnectionError\";\n }\n}\n\n/**\n * Error thrown when cache driver configuration is invalid\n */\nexport class CacheConfigurationError extends CacheError {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheConfigurationError\";\n }\n}\n\n/**\n * Error thrown when cache driver is not initialized\n */\nexport class CacheDriverNotInitializedError extends CacheError {\n public constructor(\n message: string = \"No cache driver initialized. Call cache.init() or cache.use() first.\",\n ) {\n super(message);\n this.name = \"CacheDriverNotInitializedError\";\n }\n}\n\n/**\n * Error thrown when a driver does not implement a requested operation.\n *\n * Raised when a caller invokes a method the driver cannot fulfill —\n * e.g. `update()` on the file driver before the file-lock primitive lands.\n */\nexport class CacheUnsupportedError extends CacheError {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheUnsupportedError\";\n }\n}\n\n/**\n * Error thrown when an optimistic-concurrency update exhausts its retry budget.\n */\nexport class CacheConcurrencyError extends CacheError {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheConcurrencyError\";\n }\n}\n\n/**\n * TTL shape accepted by set/remember/config calls.\n *\n * - `number` — seconds\n * - `string` — human-readable duration parsed via `ms` (`\"1h\"`, `\"30m\"`, `\"7d\"`, `\"2 weeks\"`, …)\n * - `Infinity` — no expiration\n */\nexport type CacheTtl = number | string;\n\n/**\n * Conflict resolution policy for `set` operations.\n *\n * - `\"create\"` — set only if the key does not exist (Redis `NX`)\n * - `\"update\"` — set only if the key already exists (Redis `XX`)\n * - `\"upsert\"` — default; overwrite whether the key exists or not\n */\nexport type CacheConflictPolicy = \"create\" | \"update\" | \"upsert\";\n\n/**\n * Result of a conditional write (`onConflict: \"create\" | \"update\"`).\n */\nexport type CacheSetResult<T = any> = {\n /**\n * Whether the write actually took effect.\n */\n wasSet: boolean;\n /**\n * The existing value when the write was rejected. Undefined for successful writes\n * and for unconditional `upsert` writes.\n */\n existing: T | null;\n};\n\n/**\n * Options for `lock()` — TTL is required (forgotten locks would stay forever),\n * `owner` identifies who holds the lock (handy for debugging), `driver` routes\n * the call through a non-default driver.\n *\n * @example\n * await cache.lock(\"lock.import\", { ttl: \"5m\", driver: \"redis\" }, async () => {\n * await runImport();\n * });\n */\nexport type LockOptions = {\n /**\n * How long the lock lives before auto-release. Required — guards against\n * forgotten locks if the process crashes between acquire and release.\n */\n ttl: CacheTtl;\n /**\n * Identifying value stored under the lock key. Defaults to `pid.<process.pid>`.\n * Use a custom owner when you want a human-readable label (e.g. `\"worker.jobs-2\"`).\n */\n owner?: string;\n /**\n * Per-call driver override by registered name. Manager-level only.\n */\n driver?: string;\n};\n\n/**\n * Discriminated-union result of `lock()`. Unambiguous even when the wrapped\n * function legitimately returns `undefined` — the `acquired` flag tells you\n * whether `fn` ran at all.\n *\n * @example\n * const outcome = await cache.lock(\"lock.x\", \"1m\", async () => computeValue());\n * if (outcome.acquired) {\n * console.log(\"ran, got\", outcome.value);\n * } else {\n * console.log(\"skipped — someone else holds the lock\");\n * }\n */\nexport type LockOutcome<T> =\n | { acquired: true; value: T }\n | { acquired: false };\n\n/**\n * Options for `remember()` when you need more than just a TTL — e.g. attaching\n * tags to the cache-miss write, or routing the single call to a non-default driver.\n *\n * Passed in the TTL position: `cache.remember(key, { ttl: \"1h\", tags: [\"users\"] }, fn)`.\n *\n * @example\n * await cache.remember(\"user.1\", { ttl: \"1h\", tags: [\"users\"] }, () =>\n * db.users.find(1),\n * );\n */\nexport type RememberOptions = {\n /**\n * TTL applied on cache miss. Accepts seconds (number) or duration string.\n */\n ttl?: CacheTtl;\n /**\n * Tags attached to the entry created on miss. Invalidate via `cache.tags([...]).invalidate()`.\n */\n tags?: string[];\n /**\n * Per-call driver override. Routes this remember call (both read and write)\n * to the named driver without mutating `currentDriver`.\n */\n driver?: string;\n};\n\n/**\n * Rich options for the `set` call-site. Mutually exclusive with a positional TTL.\n *\n * @example\n * await cache.set(\"user:1\", user, {\n * ttl: \"1h\",\n * tags: [\"users\", \"active\"],\n * onConflict: \"create\",\n * });\n */\nexport type CacheSetOptions = {\n /**\n * Relative TTL (seconds or duration string). Mutually exclusive with `expiresAt`.\n */\n ttl?: CacheTtl;\n /**\n * Absolute expiration as a Unix epoch in milliseconds or a Date. Mutually exclusive with `ttl`.\n */\n expiresAt?: number | Date;\n /**\n * Tags attached to this entry for tag-based invalidation. Inline equivalent of\n * `cache.tags([...]).set(key, value)`.\n */\n tags?: string[];\n /**\n * Conflict resolution policy. Defaults to `\"upsert\"`.\n */\n onConflict?: CacheConflictPolicy;\n /**\n * Per-call namespace override. Applied ahead of `globalPrefix` for this write only.\n */\n namespace?: string;\n /**\n * Per-call driver override by registered name. Routes this write to a non-default driver\n * without mutating `currentDriver`.\n */\n driver?: string;\n /**\n * Optional embedding vector indexed alongside the entry for similarity retrieval\n * via `cache.similar(...)`. Drivers without similarity support throw\n * {@link CacheUnsupportedError} when this option is supplied.\n *\n * Cache is embedding-agnostic — callers compute the vector with whichever embedder\n * they like and pass the result here.\n *\n * @example\n * await cache.set(\"doc.123\", doc, { vector: await embed(doc.text), ttl: \"1d\" });\n */\n vector?: number[];\n /**\n * Freshness deadline as a Unix epoch in milliseconds. Primarily set by\n * {@link CacheSwrOptions} flow — entries with `staleAt` in the future are\n * considered \"fresh\" by `cache.swr()`; entries past `staleAt` but before\n * `expiresAt` are \"stale-but-revalidatable.\"\n *\n * Direct callers can pin this manually when bypassing `swr()`, but the\n * common path is `cache.swr(key, { freshTtl, staleTtl }, fn)`.\n */\n staleAt?: number;\n};\n\n/**\n * Options for `cache.namespace(prefix, options)` — defaults applied to every\n * write produced through the returned scope.\n *\n * Per-call options always win over scope defaults. Tags merge additively\n * (scope tags + call tags, deduped). Nested scopes inherit from the parent;\n * the child's own values override.\n */\nexport type CacheNamespaceOptions = {\n /**\n * Default TTL for writes inside this scope. Same shape as everywhere else\n * — number of seconds or a duration string (`\"1h\"`, `\"30d\"`, …).\n */\n ttl?: CacheTtl;\n /**\n * Tags auto-attached to every write inside this scope. Useful for cross-scope\n * invalidation hooks (e.g. tag every entry with `user.<id>` so a single\n * `cache.flushTag(\"user.42\")` wipes the user's footprint everywhere).\n *\n * Merged additively with per-call tags; scope tags are never replaced.\n */\n tags?: string[];\n};\n\n/**\n * Options for `cache.swr(key, options, callback)` — stale-while-revalidate.\n *\n * Two TTLs split the entry's lifecycle into three windows:\n * - `now < freshTtl` → fresh, return cached value, no upstream call.\n * - `freshTtl <= now < staleTtl` → stale-but-revalidatable, return cached\n * value immediately, kick off `callback` in the background to refresh.\n * - `now >= staleTtl` → expired, block on `callback` like a normal miss.\n *\n * Concurrent SWR callers in the stale window all return the cached value;\n * the background refresh runs exactly once per key (deduped via the\n * driver's per-key lock map).\n *\n * If the background refresh fails, the stale entry is preserved and the\n * error is logged + emitted on the `error` event. Callers that received\n * the stale value never see the failure — they got their data.\n */\nexport type CacheSwrOptions = {\n /**\n * How long the value is considered fresh. Within this window, SWR returns\n * the cached value with no upstream call. Accepts seconds or duration\n * string (`\"1m\"`, `\"30s\"`).\n */\n freshTtl: CacheTtl;\n /**\n * Total lifetime of the entry (must be greater than `freshTtl`). Between\n * `freshTtl` and `staleTtl`, SWR returns the stale value and triggers a\n * background refresh. Past `staleTtl`, SWR blocks and refetches.\n */\n staleTtl: CacheTtl;\n /**\n * Optional tags applied on the first miss-fetch and on every successful\n * background refresh. Same semantics as `CacheSetOptions.tags`.\n */\n tags?: string[];\n /**\n * Per-call driver override by registered name. Routes both the read and\n * any write/refresh through the named driver without mutating\n * `currentDriver`. Same semantics as `RememberOptions.driver`.\n */\n driver?: string;\n};\n\n/**\n * Options for `cache.similar(vector, options)` — similarity retrieval against\n * stored entries previously written with `set({ vector })`.\n */\nexport type CacheSimilarOptions = {\n /**\n * Maximum number of hits to return. Required — no implicit default keeps\n * callers from accidentally pulling the entire candidate set.\n */\n topK: number;\n /**\n * Cosine similarity floor in `[0, 1]`. Hits scoring strictly below this are\n * filtered out before `topK` truncation.\n */\n threshold?: number;\n /**\n * Optional tag filter. Only entries tagged with at least one of the given\n * tags are considered (matches the union semantics elsewhere in the package).\n */\n tags?: string[];\n};\n\n/**\n * One result from a `similar()` query — the original key, its stored value,\n * and the cosine similarity to the query vector.\n */\nexport type CacheSimilarHit<T = unknown> = {\n /**\n * The original cache key, post-`parseKey` normalization.\n */\n key: string;\n /**\n * Stored value, deep-cloned to protect the cache from accidental mutation\n * (same semantics as `get`).\n */\n value: T;\n /**\n * Cosine similarity in `[0, 1]`. Higher means more similar.\n */\n score: number;\n};\n\n/**\n * Cache key type - can be a string or an object\n */\nexport type CacheKey = string | GenericObject;\n\nexport type CacheOperationType =\n | \"fetching\"\n | \"fetched\"\n | \"caching\"\n | \"cached\"\n | \"flushing\"\n | \"flushed\"\n | \"removing\"\n | \"removed\"\n | \"clearing\"\n | \"cleared\"\n | \"expired\"\n | \"notFound\"\n | \"connecting\"\n | \"error\"\n | \"connectionFailed\"\n | \"connected\"\n | \"disconnecting\"\n | \"disconnected\";\n\n/**\n * Cache event types for observability\n */\nexport type CacheEventType =\n | \"hit\"\n | \"miss\"\n | \"set\"\n | \"removed\"\n | \"invalidated\"\n | \"flushed\"\n | \"expired\"\n | \"connected\"\n | \"disconnected\"\n | \"error\";\n\n/**\n * Cache event data structure\n */\nexport type CacheEventData = {\n /**\n * The cache key involved in the event\n */\n key?: string;\n /**\n * The value (for set/hit events)\n */\n value?: any;\n /**\n * TTL in seconds (for set events)\n */\n ttl?: number;\n /**\n * Driver name that emitted the event\n */\n driver: string;\n /**\n * Error object (for error events)\n */\n error?: any;\n /**\n * Namespace (for namespace operations)\n */\n namespace?: string;\n /** Tags passed to `tags([...]).invalidate()` (`invalidated` events only). */\n tags?: string[];\n /** Keys a tag invalidation removed (`invalidated` events only). */\n keys?: string[];\n /** How long the operation took, in milliseconds (`invalidated` events only). */\n durationMs?: number;\n};\n\n/**\n * Event handler function type\n */\nexport type CacheEventHandler = (eventData: CacheEventData) => void | Promise<void>;\n\n/**\n * Per-operation latency + counter snapshot returned by `cache.metrics()`.\n *\n * The shape is a single recursive level — the top object covers all drivers\n * and each entry under `byDriver` repeats the same fields without nesting\n * further. Use this for ad-hoc dashboards, exporters to Prometheus / StatsD,\n * or just `console.log` during development.\n */\nexport type CacheMetricsSnapshot = {\n /** Cumulative cache hits across the lifetime of the collector. */\n hits: number;\n /** Cumulative cache misses (lookups that returned null). */\n misses: number;\n /** Cumulative successful writes. */\n sets: number;\n /** Cumulative key removals. */\n removed: number;\n /** Cumulative cache errors emitted via the `error` event. */\n errors: number;\n /** `hits / (hits + misses)` — `0` when no read ops have happened yet. */\n hitRate: number;\n /** Latency percentiles in milliseconds, computed from a circular buffer. */\n latencyMs: {\n p50: number;\n p95: number;\n p99: number;\n /** Size of the underlying buffer at snapshot time (capped at the configured max). */\n samples: number;\n };\n /** Per-driver breakdowns keyed by driver name (`\"memory\"`, `\"redis\"`, …). */\n byDriver: Record<string, Omit<CacheMetricsSnapshot, \"byDriver\">>;\n /** Millisecond timestamp the collector last reset (or was instantiated). */\n startedAt: number;\n};\n\n/**\n * Tagged cache interface for working with cache tags\n */\nexport interface TaggedCacheDriver {\n /**\n * Set a value in cache with tags\n */\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\n /**\n * Get a value from cache (checks tags)\n */\n get(key: CacheKey): Promise<any | null>;\n /**\n * Remove a specific key\n */\n remove(key: CacheKey): Promise<void>;\n /**\n * Invalidate (clear) all keys associated with the current tags\n */\n invalidate(): Promise<void>;\n /**\n * Flush all keys associated with the current tags\n * @deprecated Use invalidate() instead\n */\n flush(): Promise<void>;\n /**\n * Check if a key exists\n */\n has(key: CacheKey): Promise<boolean>;\n /**\n * Remember pattern with tags\n */\n remember(key: CacheKey, ttl: number, callback: () => Promise<any>): Promise<any>;\n /**\n * Pull value with tags\n */\n pull(key: CacheKey): Promise<any | null>;\n /**\n * Forever with tags\n */\n forever(key: CacheKey, value: any): Promise<any>;\n /**\n * Increment with tags\n */\n increment(key: CacheKey, value?: number): Promise<number>;\n /**\n * Decrement with tags\n */\n decrement(key: CacheKey, value?: number): Promise<number>;\n}\n\nexport type MemoryCacheOptions = {\n /**\n * The global prefix for the cache key\n */\n globalPrefix?: string | (() => string);\n /**\n * The default TTL for the cache. Accepts a number of seconds or a human-readable\n * duration string like `\"1h\"`, `\"30m\"`, `\"7d\"`.\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n /**\n * Maximum number of items in cache\n * When exceeded, least recently used items will be evicted\n *\n * @default undefined (no limit)\n */\n maxSize?: number;\n};\n\nexport type MemoryExtendedCacheOptions = MemoryCacheOptions;\n\nexport type LRUMemoryCacheOptions = {\n /**\n * The maximum number of items in the cache\n *\n * @default 1000\n */\n capacity?: number;\n /**\n * The global prefix for the cache key. Applied via `parseCacheKey`, same\n * semantics as the other drivers.\n */\n globalPrefix?: string | (() => string);\n /**\n * The default TTL for new entries. Accepts a number of seconds or a\n * human-readable duration string like `\"1h\"`, `\"30m\"`, `\"7d\"`.\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n};\n\nexport type FileCacheOptions = {\n /**\n * The global prefix for the cache key\n */\n globalPrefix?: string | (() => string);\n /**\n * The default TTL for the cache. Accepts a number of seconds or a human-readable\n * duration string like `\"1h\"`, `\"30m\"`, `\"7d\"`.\n *\n * @default 0\n */\n ttl?: CacheTtl;\n /**\n * Storage cache directory\n *\n * @default storagePath(\"cache\")\n */\n directory: string | (() => string);\n /**\n * File name\n *\n * @default cache.json\n */\n fileName?: string | (() => string);\n};\n\nexport type RedisOptions = {\n /**\n * Redis Port\n *\n * @default 6379\n */\n port?: number;\n /**\n * Redis Host\n */\n host?: string;\n /**\n * Redis Username\n */\n username?: string;\n /**\n * Redis Password\n */\n password?: string;\n /**\n * Redis URL\n *\n * If used, it will override the host and port options\n */\n url?: string;\n /**\n * Global prefix for the cache key\n */\n globalPrefix?: string | (() => string);\n /**\n * Default TTL. Accepts a number of seconds or a human-readable duration string\n * like `\"1h\"`, `\"30m\"`, `\"7d\"`.\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n /**\n * Redis client options\n */\n clientOptions?: RedisClientOptions;\n};\n\nexport type NullCacheDriverOptions = GenericObject;\n\n/**\n * Options accepted by {@link MockCacheDriver}. Same shape as the memory\n * driver — only `globalPrefix` and `ttl` apply, since the mock's storage is\n * a plain `Map` with no eviction policy.\n */\nexport type MockCacheOptions = {\n /**\n * Global key prefix, applied via `parseKey` (matches the other drivers).\n */\n globalPrefix?: string | (() => string);\n /**\n * Default TTL for new entries. Accepts seconds or a duration string.\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n};\n\n/**\n * One row in {@link MockCacheDriver.callLog} — captures every public op\n * the driver received in arrival order. Useful for behavioral assertions\n * in downstream tests (\"did my service actually invalidate the cache?\").\n */\nexport type CacheCall = {\n /**\n * Operation name as it appears on the driver contract — `\"set\"`, `\"get\"`,\n * `\"remove\"`, `\"flush\"`, `\"removeNamespace\"`, `\"has\"`, etc.\n */\n operation: string;\n /**\n * Post-`parseKey` cache key when the op is key-addressed; `undefined` for\n * keyless ops (`flush`, `connect`, `disconnect`).\n */\n key?: string;\n /**\n * Raw arguments passed to the call site, in declaration order. Lets\n * callers assert on TTLs, options objects, vector payloads, etc.\n */\n args: unknown[];\n /**\n * `Date.now()` when the call was recorded. Useful for timing-related\n * assertions (e.g. \"the second invalidation came within 100ms\").\n */\n timestamp: number;\n};\n\n/**\n * Minimal `pg`-compatible client surface the cache driver depends on.\n *\n * Both `pg.Pool` and `pg.Client` from the `pg` package satisfy this — the\n * driver only ever calls `query(text, values)`. Typing it loosely keeps\n * `pg` strictly optional: consumers without the `pg` package installed\n * never hit a missing import.\n */\nexport type PgClientLike = {\n query(text: string, values?: unknown[]): Promise<{ rows: any[]; rowCount?: number | null }>;\n};\n\nexport type PgCacheOptions = {\n /**\n * User-supplied `pg.Pool` or `pg.Client`. The driver does NOT own the\n * connection lifecycle — `cache.disconnect()` will not close this client.\n */\n client: PgClientLike;\n /**\n * Database table name. Default: `\"warlock_cache\"`.\n *\n * Sanitized: only `[A-Za-z0-9_]` are allowed; anything else throws\n * {@link CacheConfigurationError} at `setOptions` time.\n */\n table?: string;\n /**\n * Global key prefix, applied via `parseKey` (same semantics as the other drivers).\n */\n globalPrefix?: string | (() => string);\n /**\n * Default TTL for new entries. Accepts seconds or a duration string\n * (`\"1h\"`, `\"7d\"`, …).\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n /**\n * Optional pgvector configuration. When present, the driver provisions an\n * `embedding VECTOR(dimensions)` column + similarity index, and `similar()`\n * queries via the `<=>` cosine-distance operator.\n *\n * Requires the pgvector extension (`CREATE EXTENSION vector;`) — the driver\n * verifies its presence on the first vector operation and throws\n * {@link CacheConfigurationError} if missing.\n *\n * Omit this block (or remove it) to run the driver in KV-only mode —\n * `set({ vector })` and `similar()` then throw {@link CacheUnsupportedError}.\n */\n vector?: {\n /**\n * Vector dimension count. Must match the embedder you use throughout the\n * lifetime of the table. Mixing dimensions on the same table is unsupported.\n */\n dimensions: number;\n /**\n * pgvector index strategy. `hnsw` (default) is faster to query and slightly\n * slower to build; `ivfflat` is faster to build but typically slower to query.\n *\n * @default \"hnsw\"\n */\n index?: \"hnsw\" | \"ivfflat\";\n };\n};\n\nexport interface CacheDriver<ClientType, Options> {\n /**\n * The cache driver options\n */\n options: Options;\n /**\n * Cache driver name\n */\n name: string;\n /**\n * Set logging state\n */\n setLoggingState(shouldLog: boolean): any;\n /**\n * Remove all cached items by namespace\n */\n removeNamespace(namespace: string): Promise<any>;\n /**\n * Set the cache driver options\n */\n setOptions(options: Options): any;\n /**\n * Parse the key to be used in the cache\n */\n parseKey(key: CacheKey): string;\n /**\n * Set a value in the cache.\n *\n * @param key The cache key, could be an object or string\n * @param value The value to be stored in the cache\n * @param ttlOrOptions Either a TTL (seconds number, or duration string like `\"1h\"`),\n * or a full {@link CacheSetOptions} object.\n *\n * @example\n * // Positional TTL (back-compat)\n * await cache.set(\"user:1\", user, 3600);\n *\n * @example\n * // Human-readable duration\n * await cache.set(\"user:1\", user, \"1h\");\n *\n * @example\n * // Rich options\n * await cache.set(\"user:1\", user, { ttl: \"1h\", tags: [\"users\"], onConflict: \"create\" });\n */\n set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any>;\n /**\n * Get a value from the cache\n */\n get<T = any>(key: CacheKey): Promise<T | null>;\n /**\n * Remove a value from the cache\n */\n remove(key: CacheKey): Promise<void>;\n /**\n * Flush the entire cache\n */\n flush(): Promise<void>;\n /**\n * Connect to the cache driver\n */\n connect(): Promise<any>;\n /**\n * The cache client\n */\n client?: ClientType;\n /**\n * Disconnect the cache driver\n */\n disconnect(): Promise<void>;\n /**\n * Check if a key exists in the cache without fetching its value\n */\n has(key: CacheKey): Promise<boolean>;\n /**\n * Get value from cache or execute callback and cache the result.\n *\n * The second argument accepts a TTL (number of seconds or duration string like `\"1h\"`)\n * or a full {@link RememberOptions} object when you need to attach tags or route to\n * a non-default driver.\n *\n * @example\n * // Positional TTL — the common case\n * const user = await cache.remember(\"user.1\", \"1h\", () => db.users.find(1));\n *\n * @example\n * // Options form — tag the cache-miss write for bulk invalidation\n * const user = await cache.remember(\"user.1\", { ttl: \"1h\", tags: [\"users\"] }, () =>\n * db.users.find(1),\n * );\n */\n remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T>;\n /**\n * Stale-while-revalidate. Returns the cached value when fresh; returns\n * the stale value + kicks off a background refresh when within the\n * `freshTtl..staleTtl` window; blocks like a normal miss when past\n * `staleTtl`. Concurrent stale-window callers share one in-flight\n * refresh.\n *\n * Background refresh failures preserve the stale entry and emit an\n * `error` event — the stale-returning caller never sees the failure.\n *\n * @example\n * const product = await cache.swr(\n * \"product.42\",\n * { freshTtl: \"1m\", staleTtl: \"1h\" },\n * () => db.products.find(42),\n * );\n */\n swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T>;\n /**\n * Get a value and remove it, handing it out exactly once.\n *\n * Atomicity: across servers on `redis` (`GETDEL`) and `pg`\n * (`DELETE … RETURNING`); within the process on `memory`, `memoryExtended`,\n * `lru` and `mock`; serialized per key within the process only on `file`.\n */\n pull<T = any>(key: CacheKey): Promise<T | null>;\n /**\n * Set a value in cache permanently (no expiration)\n */\n forever<T = any>(key: CacheKey, value: T): Promise<T>;\n /**\n * Read, transform, and write a cached value without losing concurrent updates.\n *\n * The callback receives the current value (or `null` on miss) and returns the\n * next value. Returning `null` removes the key. TTL is preserved unless\n * explicitly overridden via options.\n *\n * Atomicity: across servers on `redis` and `pg` (optimistic\n * compare-and-set with retries — `fn` may run more than once under\n * contention, so keep it side-effect free). On every other driver updates\n * are serialized per key within ONE process only. `file` does not support it.\n *\n * @example\n * await cache.update<User>(\"user:1\", (current) => {\n * if (!current) return null;\n * return { ...current, lastSeen: Date.now() };\n * });\n */\n 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 /**\n * Shallow-merge a partial object into a cached value.\n *\n * If the key is missing, treats the current value as `{}`. Preserves TTL by default.\n *\n * @example\n * await cache.merge<User>(\"user:1\", { name: \"Jane\" });\n */\n merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T>;\n /**\n * List sub-API factory — returns a {@link CacheListAccessor} bound to the given key.\n *\n * @example\n * const recent = cache.list<Event>(\"recent-events\");\n * await recent.push(event);\n * const last10 = await recent.slice(0, 10);\n */\n list<T = any>(key: CacheKey): CacheListAccessor<T>;\n /**\n * Acquire a distributed lock, run `fn`, and auto-release. Returns a\n * {@link LockOutcome} so callers can distinguish \"ran and produced this value\"\n * from \"skipped because someone else holds the lock.\"\n *\n * Built on top of `set({ onConflict: \"create\" })` — Redis-native, emulated\n * on other drivers (single-process semantics elsewhere).\n *\n * @example\n * const outcome = await cache.lock(\"lock.import\", \"5m\", async () => {\n * await runImport();\n * return \"done\";\n * });\n */\n lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>>;\n /**\n * Increment a numeric value in cache. A missing key starts from 0 with the\n * driver default TTL; an existing key keeps its remaining TTL.\n *\n * Atomicity: across servers on `redis` (`INCRBY`) and `pg` (single\n * upsert statement); within the process on `memory`, `memoryExtended`,\n * `lru` and `mock`; serialized per key within the process only on `file`.\n *\n * @param key The cache key\n * @param value The value to increment by (default 1)\n */\n increment(key: CacheKey, value?: number): Promise<number>;\n /**\n * Decrement a numeric value in cache\n *\n * @param key The cache key\n * @param value The value to decrement by (default 1)\n */\n decrement(key: CacheKey, value?: number): Promise<number>;\n /**\n * Get multiple values from cache at once\n */\n many(keys: CacheKey[]): Promise<any[]>;\n /**\n * Set multiple values in cache at once\n */\n setMany(items: Record<string, any>, ttl?: number): Promise<void>;\n /**\n * Register an event listener\n */\n on(event: CacheEventType, handler: CacheEventHandler): this;\n /**\n * Remove an event listener\n */\n off(event: CacheEventType, handler: CacheEventHandler): this;\n /**\n * Register a one-time event listener\n */\n once(event: CacheEventType, handler: CacheEventHandler): this;\n /**\n * Set if not exists (atomic operation)\n * Returns true if key was set, false if key already existed\n * Note: Not all drivers support this operation\n */\n setNX?(key: CacheKey, value: any, ttl?: number): Promise<boolean>;\n /**\n * Create a tagged cache instance for the given tags\n */\n tags(tags: string[]): TaggedCacheDriver;\n /**\n * Tag-index primitive: add `members` (normalized, un-prefixed cache keys)\n * to the index stored under `tagKey`. Optional — `TaggedCache` falls back to\n * a get/set array when a driver has none.\n *\n * Built-in drivers: redis → a native SET (`SADD`, atomic across servers);\n * pg → one atomic upsert of a JSON array row; memory / lru / mock → an\n * in-process set that is never evicted or expired; file (base default) →\n * a get/set array serialized per tag key in this process only.\n */\n tagAdd?(tagKey: CacheKey, members: string[]): Promise<void>;\n /**\n * Tag-index primitive: the members stored under `tagKey` (empty when none).\n */\n tagMembers?(tagKey: CacheKey): Promise<string[]>;\n /**\n * Tag-index primitive: remove `members` from `tagKey`. Members added\n * concurrently and not listed are kept.\n */\n tagRemove?(tagKey: CacheKey, members: string[]): Promise<void>;\n /**\n * Tag-index primitive: drop the whole index stored under `tagKey`.\n */\n tagDelete?(tagKey: CacheKey): Promise<void>;\n /**\n * Similarity retrieval. Returns the nearest stored entries to `vector` by\n * cosine similarity, ordered by descending score.\n *\n * Drivers without a similarity index throw {@link CacheUnsupportedError}.\n * Memory-family drivers brute-force scan in O(N) — suitable for development\n * but not for production knowledge bases beyond a few thousand entries; use\n * the `pg` driver (with pgvector) or `redis` driver (with RediSearch) instead.\n *\n * @example\n * const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });\n */\n similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]>;\n}\n\n/**\n * Accessor for list-shaped cached values.\n *\n * Defaults to read-mutate-write semantics on non-native drivers (memory, file, LRU).\n * The Redis driver overrides this with native `LPUSH` / `RPUSH` / `LRANGE` / `LTRIM` for O(1) ops.\n */\nexport interface CacheListAccessor<T = any> {\n /**\n * Append one or more items to the tail of the list. Returns the new length.\n */\n push(...items: T[]): Promise<number>;\n /**\n * Prepend one or more items to the head of the list. Returns the new length.\n */\n unshift(...items: T[]): Promise<number>;\n /**\n * Remove and return the tail item, or `null` if the list is empty.\n */\n pop(): Promise<T | null>;\n /**\n * Remove and return the head item, or `null` if the list is empty.\n */\n shift(): Promise<T | null>;\n /**\n * Return a slice of the list. End is exclusive, mirroring `Array.prototype.slice`.\n */\n slice(start?: number, end?: number): Promise<T[]>;\n /**\n * Return the full list.\n */\n all(): Promise<T[]>;\n /**\n * Return the length of the list.\n */\n length(): Promise<number>;\n /**\n * Trim the list to the inclusive range `[start, end]`. Outside elements are dropped.\n */\n trim(start: number, end: number): Promise<void>;\n /**\n * Remove the entire list.\n */\n clear(): Promise<void>;\n}\n\n/**\n * One-shot tagged handle returned by `ScopedCacheContract.tags(...)`.\n *\n * Identical write surface to {@link TaggedCacheDriver}, except the underlying\n * scope's prefix is applied to every key and the scope's default TTL/tags\n * still flow through. Tags supplied here merge additively with scope-level\n * tags.\n */\nexport interface TaggedScopedCacheContract {\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\n get<T = any>(key: CacheKey): Promise<T | null>;\n has(key: CacheKey): Promise<boolean>;\n remove(key: CacheKey): Promise<void>;\n pull<T = any>(key: CacheKey): Promise<T | null>;\n forever<T = any>(key: CacheKey, value: T): Promise<T>;\n setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean>;\n remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T>;\n increment(key: CacheKey, value?: number): Promise<number>;\n decrement(key: CacheKey, value?: number): Promise<number>;\n /**\n * Invalidate every entry tagged with the union of scope tags + handle tags.\n * Forwarded to the underlying tag index — tags are global, scope-agnostic.\n */\n invalidate(): Promise<void>;\n}\n\n/**\n * Scoped view over the cache. Returned by `cache.namespace(prefix, options?)`.\n *\n * A scope prepends its `prefix` to every key, applies optional default TTL/tags\n * to every write, and forwards everything else to the underlying source. It\n * stores nothing of its own — purely a convenience wrapper.\n *\n * @example\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\n * await chat.set(\"messages.1\", msg); // → \"chats.10.messages.1\", TTL 30d\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call ttl wins\n * await chat.clear(); // wipe the whole scope\n */\nexport interface ScopedCacheContract {\n /** The fully-qualified prefix this scope prepends to every key. */\n readonly prefix: string;\n\n /**\n * Nested scope. Inherits the parent's defaults; child's own options override.\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 namespace(prefix: string, options?: CacheNamespaceOptions): ScopedCacheContract;\n\n /**\n * One-shot tagged write handle. Tags merge additively with scope defaults.\n */\n tags(tags: string[]): TaggedScopedCacheContract;\n\n /** Wipe every entry under this scope's prefix. Sugar for `removeNamespace(prefix)`. */\n clear(): Promise<void>;\n\n // Reads\n get<T = any>(key: CacheKey): Promise<T | null>;\n has(key: CacheKey): Promise<boolean>;\n many(keys: CacheKey[]): Promise<any[]>;\n pull<T = any>(key: CacheKey): Promise<T | null>;\n\n // Writes\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\n setMany(items: Record<string, any>, ttl?: number): Promise<void>;\n setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean>;\n forever<T = any>(key: CacheKey, value: T): Promise<T>;\n remove(key: CacheKey): Promise<void>;\n\n // Read-or-compute\n remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T>;\n swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T>;\n\n // Mutations\n increment(key: CacheKey, value?: number): Promise<number>;\n decrement(key: CacheKey, value?: number): Promise<number>;\n 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 merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T>;\n\n // Structured accessors\n list<T = any>(key: CacheKey): CacheListAccessor<T>;\n\n // Coordination\n lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>>;\n\n // Similarity (delegated; throws CacheUnsupportedError if the driver lacks it)\n similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]>;\n}\n\nexport type CacheData = {\n /**\n * Value stored in the cache\n */\n data: any;\n /**\n * The expiration date in milliseconds\n */\n expiresAt?: number;\n /**\n * Time to live in seconds\n */\n ttl?: number;\n /**\n * Freshness deadline as a millisecond timestamp. Used by `swr()` — entries\n * with `staleAt` in the future are \"fresh\"; past `staleAt` but before\n * `expiresAt` are \"stale-but-revalidatable\" and trigger a background\n * refresh on the next read. Optional — entries written through plain\n * `set()` skip this field entirely and `swr()` treats them as always-fresh.\n */\n staleAt?: number;\n};\n\nexport type DriverClass = new () => CacheDriver<any, any>;\n\ntype DefaultDrivers =\n | \"redis\"\n | \"file\"\n | \"memory\"\n | \"memoryExtended\"\n | \"null\"\n | \"lru\"\n | \"pg\"\n | \"mock\";\n\ntype MergeWithDefaultDrivers<T> = T extends undefined ? DefaultDrivers : DefaultDrivers | T;\n\nexport type CacheConfigurations<\n T extends string | undefined = undefined,\n DriverName = MergeWithDefaultDrivers<T>,\n> = {\n /**\n * The default cache driver name\n */\n default?: DriverName;\n /**\n * Determine whether to log or not\n *\n * @default true\n */\n logging?: boolean;\n /**\n * The cache drivers list\n */\n drivers: {\n redis?: typeof RedisCacheDriver;\n file?: typeof FileCacheDriver;\n null?: typeof NullCacheDriver;\n memory?: typeof MemoryCacheDriver;\n memoryExtended?: typeof MemoryExtendedCacheDriver;\n lru?: typeof LRUMemoryCacheDriver;\n pg?: typeof PgCacheDriver;\n mock?: typeof MockCacheDriver;\n } & {\n [key in Extract<T, string>]?: typeof BaseCacheDriver<any, any> | undefined;\n };\n /**\n * The cache driver options\n */\n options: {\n redis?: RedisOptions;\n file?: FileCacheOptions;\n memory?: MemoryCacheOptions;\n memoryExtended?: MemoryExtendedCacheOptions;\n null?: NullCacheDriverOptions;\n lru?: LRUMemoryCacheOptions;\n pg?: PgCacheOptions;\n mock?: MockCacheOptions;\n } & {\n [key in Extract<T, string>]?: GenericObject;\n };\n};\n","import { rtrim } from \"@mongez/reinforcements\";\nimport ms, { type StringValue } from \"ms\";\nimport type { CacheKey, CacheSetOptions, CacheTtl, RememberOptions } from \"./types\";\nimport { CacheConfigurationError } from \"./types\";\n\n/**\n * Make a proper key for the cache\n */\nexport function parseCacheKey(\n key: CacheKey,\n options: { globalPrefix?: string | (() => string) } = {},\n): string {\n if (typeof key === \"object\") {\n key = JSON.stringify(key);\n }\n\n // remove any curly braces and double quotes along with []\n key = key.replace(/[{}\"[\\]]/g, \"\").replaceAll(/[:,]/g, \".\");\n\n const cachePrefix =\n typeof options.globalPrefix === \"function\" ? options.globalPrefix() : options.globalPrefix;\n\n return rtrim(String(cachePrefix ? rtrim(cachePrefix, \".\") + \".\" + key : key), \".\");\n}\n\n/**\n * Parse a TTL value into seconds.\n *\n * Accepts:\n * - a number (seconds) — fractions are rounded UP to whole seconds\n * (so Redis `EX` never receives a fraction); `0` means \"no expiry\"\n * - `Infinity` — no expiration, returned unchanged\n * - a bare numeric string (e.g. `\"3600\"`, from env vars) — treated as seconds\n * - a human-readable duration string (e.g. `\"1h\"`, `\"30m\"`, `\"7d\"`) — parsed via `ms`,\n * then converted to seconds, rounding up\n *\n * Throws `CacheConfigurationError` on NaN, negative numbers, or unparseable strings.\n *\n * @example\n * parseTtl(3600); // 3600\n * parseTtl(0.5); // 1\n * parseTtl(\"3600\"); // 3600\n * parseTtl(\"500ms\"); // 1\n * parseTtl(\"1h\"); // 3600\n * parseTtl(\"7d\"); // 604800\n * parseTtl(Infinity); // Infinity\n */\nexport function parseTtl(input: CacheTtl): number {\n if (typeof input === \"number\") {\n if (Number.isNaN(input)) {\n throw new CacheConfigurationError(\"Invalid TTL: NaN.\");\n }\n\n if (input < 0) {\n throw new CacheConfigurationError(`Invalid TTL: negative number (${input}).`);\n }\n\n return Math.ceil(input);\n }\n\n if (typeof input !== \"string\" || input.trim() === \"\") {\n throw new CacheConfigurationError(\n `Invalid TTL: expected number or duration string, got ${typeof input}.`,\n );\n }\n\n const trimmed = input.trim();\n\n if (/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n return Math.ceil(Number(trimmed));\n }\n\n const milliseconds = ms(trimmed as StringValue);\n\n if (milliseconds === undefined || Number.isNaN(milliseconds)) {\n throw new CacheConfigurationError(\n `Invalid TTL duration string: \"${input}\". Expected forms like \"1h\", \"30m\", \"7d\".`,\n );\n }\n\n if (milliseconds < 0) {\n throw new CacheConfigurationError(`Invalid TTL: negative duration (\"${input}\").`);\n }\n\n return Math.ceil(milliseconds / 1000);\n}\n\n/**\n * Convert an absolute `expiresAt` (Date or epoch milliseconds) into a\n * relative TTL in seconds.\n *\n * Throws {@link CacheConfigurationError} when the deadline is in the past —\n * the caller almost certainly has a bug (stale timestamp, wrong unit, etc.)\n * and silently storing an already-expired entry would hide it.\n *\n * @example\n * expiresAtToTtl(new Date(Date.now() + 60_000)); // ~60\n * expiresAtToTtl(Date.now() + 30 * 60 * 1000); // ~1800\n */\nexport function expiresAtToTtl(expiresAt: number | Date): number {\n const deadline = expiresAt instanceof Date ? expiresAt.getTime() : expiresAt;\n const relativeMs = deadline - Date.now();\n\n if (relativeMs <= 0) {\n throw new CacheConfigurationError(\n `\\`expiresAt\\` must be in the future; got ${new Date(deadline).toISOString()}.`,\n );\n }\n\n return Math.ceil(relativeMs / 1000);\n}\n\n/**\n * Coerce the polymorphic 3rd `set` argument into a uniform `CacheSetOptions`\n * shape. Lets callers (and `BaseCacheDriver.resolveSetOptions`) skip per-shape\n * branching.\n *\n * - `undefined` / `null` → `{}` (resolves to driver-level defaults later)\n * - `number` / `string` (positional TTL) → `{ ttl }`\n * - already an options object → returned as-is\n */\nexport function normalizeToOptions(\n input?: CacheTtl | CacheSetOptions,\n): CacheSetOptions {\n if (input === undefined || input === null) {\n return {};\n }\n\n if (typeof input === \"number\" || typeof input === \"string\") {\n return { ttl: input };\n }\n\n return input;\n}\n\n/**\n * Sibling of {@link normalizeToOptions} for the `remember()` call site, where\n * the polymorphic 2nd argument is `CacheTtl | RememberOptions` (no `expiresAt`,\n * no `onConflict`). Returns the same shape so callers can `{ ...opts, ... }`\n * without branching.\n *\n * @example\n * normalizeToRememberOptions(60); // { ttl: 60 }\n * normalizeToRememberOptions(\"1h\"); // { ttl: \"1h\" }\n * normalizeToRememberOptions({ ttl: \"1h\", tags: [\"x\"] }); // returned as-is\n */\nexport function normalizeToRememberOptions(\n input?: CacheTtl | RememberOptions,\n): RememberOptions {\n if (input === undefined || input === null) {\n return {};\n }\n\n if (typeof input === \"number\" || typeof input === \"string\") {\n return { ttl: input };\n }\n\n return input;\n}\n\n/**\n * Resolve the final TTL in seconds for a `set` call. Precedence:\n *\n * 1. Caller's `ttl` (number or duration string) wins.\n * 2. Otherwise, caller's `expiresAt` is converted to relative seconds.\n * 3. Otherwise, `fallback` is used (driver-level default — typically\n * `Infinity` when no default is configured, meaning \"never expires\").\n *\n * Throws {@link CacheConfigurationError} when `ttl` and `expiresAt` are\n * supplied together (mutually exclusive).\n */\nexport function resolveTtl(\n ttl: CacheTtl | undefined,\n expiresAt: number | Date | undefined,\n fallback: number,\n): number {\n if (ttl !== undefined && expiresAt !== undefined) {\n throw new CacheConfigurationError(\n \"Cache set options cannot specify both `ttl` and `expiresAt` — choose one.\",\n );\n }\n\n if (ttl !== undefined) {\n return parseTtl(ttl);\n }\n\n if (expiresAt !== undefined) {\n return expiresAtToTtl(expiresAt);\n }\n\n return fallback;\n}\n\n/**\n * Combine any number of tag lists into a single deduped array, dropping\n * `undefined`/empty entries. Returns `undefined` when no tags survive — lets\n * callers skip emitting empty `tags: []` into option payloads.\n *\n * Used by scoped-cache merging where scope tags + handle tags + per-call tags\n * must union additively without duplicates.\n *\n * @example\n * mergeTagSets([\"a\", \"b\"], [\"b\", \"c\"]); // [\"a\", \"b\", \"c\"]\n * mergeTagSets(undefined, [\"x\"]); // [\"x\"]\n * mergeTagSets(undefined, undefined); // undefined\n * mergeTagSets([], []); // undefined\n */\nexport function mergeTagSets(\n ...lists: (string[] | undefined)[]\n): string[] | undefined {\n const flat: string[] = [];\n\n for (const list of lists) {\n if (!list || list.length === 0) {\n continue;\n }\n\n flat.push(...list);\n }\n\n if (flat.length === 0) {\n return undefined;\n }\n\n return Array.from(new Set(flat));\n}\n\n/**\n * Add extra tags to any option-bag that already shapes `tags?: string[]`.\n * Pure — clones the input shape, never mutates. Tags are appended (caller\n * is responsible for de-duplication if needed; pair with {@link mergeTagSets}).\n *\n * @example\n * injectTags({ ttl: \"1h\" }, [\"unread\"]); // { ttl: \"1h\", tags: [\"unread\"] }\n * injectTags({ tags: [\"a\"] }, [\"b\"]); // { tags: [\"a\", \"b\"] }\n */\nexport function injectTags<T extends { tags?: string[] }>(\n options: T,\n extraTags: string[],\n): T {\n if (extraTags.length === 0) {\n return options;\n }\n\n return {\n ...options,\n tags: [...(options.tags ?? []), ...extraTags],\n };\n}\n\n/**\n * Cosine similarity between two equal-length numeric vectors.\n *\n * Returns a value in `[-1, 1]` where `1` means perfectly aligned, `0` means\n * orthogonal, and `-1` means opposing. For typical embedding spaces (where\n * vectors live in the positive cone) the practical range is `[0, 1]`.\n *\n * Throws {@link CacheConfigurationError} on dimension mismatch — fail loud at\n * the call site rather than silently returning a misleading score. A zero-norm\n * vector on either side returns `0` (no defined direction to compare).\n *\n * @example\n * cosineSimilarity([1, 0, 0], [1, 0, 0]); // 1\n * cosineSimilarity([1, 0, 0], [0, 1, 0]); // 0\n */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n if (a.length !== b.length) {\n throw new CacheConfigurationError(\n `Vector dimension mismatch: got ${a.length} and ${b.length}.`,\n );\n }\n\n if (a.length === 0) {\n throw new CacheConfigurationError(\n \"Vector dimension mismatch: empty vector cannot be compared.\",\n );\n }\n\n let dot = 0;\n let normA = 0;\n let normB = 0;\n\n for (let i = 0; i < a.length; i++) {\n const x = a[i];\n const y = b[i];\n\n if (x === undefined || y === undefined) {\n // Unreachable: both guards above establish `a.length === b.length` and\n // a non-zero length, so every index in range exists on both sides. Said\n // out loud rather than asserted with `as number`, because the two guards\n // that make it true are the kind of thing a later edit removes — and if\n // one ever did, an assertion would let `undefined` into the arithmetic\n // and return NaN as a similarity SCORE. A silently wrong ranking is\n // worse than a stopped one.\n break;\n }\n\n dot += x * y;\n normA += x * x;\n normB += y * y;\n }\n\n if (normA === 0 || normB === 0) {\n return 0;\n }\n\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n\n/**\n * Matches userinfo credentials embedded in a connection URL\n * (e.g. `redis://user:pass@host`). Node/Redis/Postgres client errors\n * frequently echo the target connection string — including the password —\n * back in `error.message` on connection failure.\n */\nconst CREDENTIALS_IN_URL = /(:\\/\\/)[^\\s/@]+:[^\\s/@]+@/g;\n\n/**\n * Strip embedded `user:pass@` credentials from a string before it reaches\n * any log sink.\n */\nexport function redactCredentials(message: string): string {\n return message.replace(CREDENTIALS_IN_URL, \"$1[REDACTED]@\");\n}\n\n/**\n * Reduce an unknown thrown value to a log-safe `{ message, code? }` shape.\n * Callers must never log the raw error object itself — it may carry the\n * connection URL (with password) in the top-level message, `cause`, or\n * driver-specific fields, all of which `console.log`/`util.inspect` would\n * still print in full.\n */\nexport function safeErrorInfo(error: unknown): { message: string; code?: string } {\n if (error instanceof Error) {\n const info: { message: string; code?: string } = {\n message: redactCredentials(error.message),\n };\n\n const code = (error as any).code;\n if (code) info.code = String(code);\n\n return info;\n }\n\n return { message: redactCredentials(String(error)) };\n}\n\nexport enum CACHE_FOR {\n /**\n * Cache for 30 Minutes (in seconds)\n */\n HALF_HOUR = 1800,\n /**\n * Cache for 1 Hour (in seconds)\n */\n ONE_HOUR = 3600,\n /**\n * Cache for 12 Hours (in seconds)\n */\n HALF_DAY = 43200,\n /**\n * Cache for 24 Hours (in seconds)\n */\n ONE_DAY = 86400,\n /**\n * Cache for 7 Days (in seconds)\n */\n ONE_WEEK = 604800,\n /**\n * Cache for 15 Days (in seconds)\n */\n HALF_MONTH = 1296000,\n /**\n * Cache for 30 Days (in seconds)\n */\n ONE_MONTH = 2592000,\n /**\n * Cache for 60 Days (in seconds)\n */\n TWO_MONTHS = 5184000,\n /**\n * Cache for 180 Days (in seconds)\n */\n SIX_MONTHS = 15768000,\n /**\n * Cache for 365 Days (in seconds)\n */\n ONE_YEAR = 31536000,\n}\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 and take it out of every tag index this handle\n * writes to (scope tags + handle tags).\n */\n public async remove(key: CacheKey): Promise<void> {\n await this.scope.remove(key);\n await this.detachFromTags(key);\n }\n\n /**\n * Read-and-remove the scoped key, then take it out of this handle's tags.\n */\n public async pull<T = any>(key: CacheKey): Promise<T | null> {\n const value = await this.scope.pull<T>(key);\n\n if (value !== null) {\n await this.detachFromTags(key);\n }\n\n return value;\n }\n\n /**\n * Remove the un-prefixed scoped key from every tag index of this handle.\n */\n protected async detachFromTags(key: CacheKey): Promise<void> {\n const allTags = mergeTagSets(this.scope.defaults.tags, this.handleTags);\n\n if (!allTags || allTags.length === 0) {\n return;\n }\n\n const scopedKey = this.buildScopedKey(key);\n const tagged = this.scope.source.tags(allTags) as unknown as {\n detachFromTags: (indexedKey: string) => Promise<void>;\n };\n\n await tagged.detachFromTags(parseCacheKey(scopedKey));\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 // Register the un-prefixed scoped key — the tag index never holds the\n // driver-parsed form, so `invalidate()` can remove it via `remove(key)`.\n const scopedKey = this.buildScopedKey(key);\n const tagged = this.scope.source.tags(allTags) as unknown as {\n storeTagRelationship: (key: CacheKey) => Promise<void>;\n };\n\n await tagged.storeTagRelationship(scopedKey);\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. An explicit `options.ttl` always wins. Otherwise\n * the scope's default `ttl` is applied only when the key does not exist yet;\n * for an existing key no ttl is passed so the source preserves the entry's\n * remaining TTL.\n */\n public async 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 const scopedKey = this.scopedKey(key);\n\n return this.source.update<T>(scopedKey, fn, await this.writeOptions(scopedKey, options));\n }\n\n /**\n * Shallow-merge a partial object into the scoped entry. Same TTL semantics\n * as {@link update}.\n */\n public async merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T> {\n const scopedKey = this.scopedKey(key);\n\n return this.source.merge<T>(scopedKey, partial, await this.writeOptions(scopedKey, options));\n }\n\n /**\n * Resolve the ttl options for update/merge: explicit ttl, else the scope\n * default only for keys that don't exist yet, else nothing (keep remaining).\n */\n private async writeOptions(\n scopedKey: CacheKey,\n options?: { ttl?: CacheTtl },\n ): Promise<{ ttl?: CacheTtl }> {\n if (options?.ttl !== undefined) {\n return { ttl: options.ttl };\n }\n\n if (this.defaults.ttl === undefined) {\n return {};\n }\n\n return (await this.source.has(scopedKey)) ? {} : { ttl: this.defaults.ttl };\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\";\nimport { ScopedCache } from \"./scoped-cache\";\nimport type {\n CacheConfigurations,\n CacheDriver,\n CacheEventHandler,\n CacheEventType,\n CacheKey,\n CacheListAccessor,\n CacheMetricsSnapshot,\n CacheNamespaceOptions,\n CacheSetOptions,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheSwrOptions,\n CacheTtl,\n DriverClass,\n LockOptions,\n LockOutcome,\n RememberOptions,\n ScopedCacheContract,\n TaggedCacheDriver,\n} from \"./types\";\nimport { CacheConfigurationError, CacheDriverNotInitializedError } from \"./types\";\n\nexport class CacheManager implements CacheDriver<any, any> {\n /**\n * Cache Driver\n */\n public currentDriver?: CacheDriver<any, any>;\n\n /**\n * Loaded drivers\n */\n public loadedDrivers: Record<string, CacheDriver<any, any>> = {};\n\n /**\n * In-flight `load()` promises, so concurrent callers share one `connect()`.\n */\n protected loadingDrivers = new Map<string, Promise<CacheDriver<any, any>>>();\n\n /**\n * Configurations list\n */\n protected configurations: CacheConfigurations = {\n drivers: {},\n options: {},\n };\n\n /**\n * Global event listeners\n */\n protected globalEventListeners: Map<CacheEventType, Set<CacheEventHandler>> = new Map();\n\n /**\n * Metrics collector — lazy on first {@link metrics} call so apps that\n * never read metrics pay zero cost. Once instantiated, it stays\n * subscribed to events for the manager's lifetime.\n */\n protected metricsCollector?: CacheMetricsCollector;\n\n /**\n * {@inheritdoc}\n */\n public name = \"cacheManager\";\n\n /**\n * {@inheritdoc}\n */\n public get client() {\n return this.currentDriver?.client;\n }\n\n /**\n * Set the cache configurations\n */\n public setCacheConfigurations(configurations: CacheConfigurations) {\n this.configurations.default = configurations.default;\n this.configurations.drivers = configurations.drivers;\n this.configurations.options = configurations.options;\n this.configurations.logging = configurations.logging;\n }\n\n /**\n * Set logging state\n */\n public setLoggingState(loggingState: boolean) {\n this.ensureDriverInitialized();\n\n this.currentDriver!.setLoggingState(loggingState);\n }\n\n /**\n * Switch the manager to a registered driver, optionally injecting runtime\n * options that merge over the static config.\n *\n * The string form looks the driver up in `setCacheConfigurations({ drivers })`,\n * loads it (or returns the cached instance), and sets it as `currentDriver`.\n * The instance form takes a pre-built driver and bypasses the registry; the\n * `runtimeOptions` argument is silently ignored in that case because the\n * instance was constructed externally.\n *\n * Runtime options merge over `config.options[name]` per-key — runtime wins.\n * Use this for constructor-only knobs that can't live in static config\n * (e.g. `pg`'s `client: pg.Pool`).\n *\n * @example\n * const pool = new Pool({ connectionString });\n * await cache.use(\"pg\", { client: pool });\n */\n public async use(\n driver: string | CacheDriver<any, any>,\n runtimeOptions?: Record<string, any>,\n ) {\n if (typeof driver === \"string\") {\n const driverInstance = await this.load(driver, runtimeOptions);\n\n if (!driverInstance) {\n throw new CacheConfigurationError(\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\n );\n }\n\n driver = driverInstance;\n }\n\n this.attachGlobalListeners(driver);\n\n if (this.configurations.logging !== undefined) {\n driver.setLoggingState(this.configurations.logging);\n }\n\n this.currentDriver = driver;\n\n return this;\n }\n\n /**\n * Ensure driver is initialized before operations\n */\n protected ensureDriverInitialized(): void {\n if (!this.currentDriver) {\n throw new CacheDriverNotInitializedError();\n }\n }\n\n /**\n * Return the running metrics snapshot — counters, hit-rate, latency\n * percentiles, per-driver breakdowns. Lazy-attaches the collector on\n * first call so apps that never read metrics pay zero cost.\n *\n * @example\n * const m = cache.metrics();\n * console.log(`hit rate: ${(m.hitRate * 100).toFixed(1)}%`);\n * console.log(`p95: ${m.latencyMs.p95.toFixed(2)}ms`);\n */\n public metrics(): CacheMetricsSnapshot {\n return this.ensureMetricsCollector().snapshot();\n }\n\n /**\n * Wipe every counter + latency sample and reset `startedAt` to now.\n * The collector itself stays subscribed to events.\n */\n public resetMetrics(): void {\n this.ensureMetricsCollector().reset();\n }\n\n /**\n * Lazy-construct the metrics collector and wire it to the global event\n * bus. Subsequent calls return the same instance — survives `cache.use()`\n * driver switches because handlers attach via `on()` and re-bind to every\n * loaded driver.\n */\n protected ensureMetricsCollector(): CacheMetricsCollector {\n if (this.metricsCollector) {\n return this.metricsCollector;\n }\n\n const collector = new CacheMetricsCollector();\n\n this.on(\"hit\", (data) => collector.recordEvent(\"hit\", data));\n this.on(\"miss\", (data) => collector.recordEvent(\"miss\", data));\n this.on(\"set\", (data) => collector.recordEvent(\"set\", data));\n this.on(\"removed\", (data) => collector.recordEvent(\"removed\", data));\n this.on(\"error\", (data) => collector.recordEvent(\"error\", data));\n\n this.metricsCollector = collector;\n\n return collector;\n }\n\n /**\n * Time the body, record the elapsed milliseconds against the metrics\n * collector for the given driver (defaults to the current driver's name).\n * Pass-through if the collector hasn't been instantiated yet — apps that\n * don't read metrics never pay for sample collection.\n */\n protected async timed<T>(\n body: () => Promise<T>,\n driverName?: string,\n ): Promise<T> {\n if (!this.metricsCollector) {\n return body();\n }\n\n const start = performance.now();\n\n try {\n return await body();\n } finally {\n const elapsed = performance.now() - start;\n const name = driverName ?? this.currentDriver?.name ?? \"unknown\";\n this.metricsCollector.recordLatency(name, elapsed);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n this.ensureDriverInitialized();\n return this.timed(() => this.currentDriver!.get<T>(key));\n }\n\n /**\n * Set a value in the cache.\n *\n * Accepts a positional TTL (number of seconds or duration string like `\"1h\"`)\n * or a rich {@link CacheSetOptions} object supporting `ttl`, `expiresAt`,\n * `tags`, `onConflict`, `namespace`, and per-call `driver` overrides.\n */\n public async set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions) {\n this.ensureDriverInitialized();\n\n const driverOverride =\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\n ? ttlOrOptions.driver\n : undefined;\n\n if (driverOverride) {\n const driver = await this.load(driverOverride);\n return this.timed(() => driver.set(key, value, ttlOrOptions), driver.name);\n }\n\n return this.timed(() => this.currentDriver!.set(key, value, ttlOrOptions));\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n this.ensureDriverInitialized();\n return this.timed(() => this.currentDriver!.remove(key));\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.ensureDriverInitialized();\n return this.currentDriver!.removeNamespace(namespace);\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.ensureDriverInitialized();\n return this.currentDriver!.flush();\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n this.ensureDriverInitialized();\n return this.currentDriver!.connect();\n }\n\n /**\n * {@inheritdoc}\n */\n public parseKey(key: CacheKey) {\n this.ensureDriverInitialized();\n return this.currentDriver!.parseKey(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public get options() {\n this.ensureDriverInitialized();\n return this.currentDriver!.options;\n }\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: Record<string, any>) {\n this.ensureDriverInitialized();\n return this.currentDriver!.setOptions(options || {});\n }\n\n /**\n * Return the loaded driver instance for `driverName`, loading it on first\n * call. Optional `runtimeOptions` follow the same merge-over-config rules\n * as {@link load}; passing options after the driver has already been\n * loaded throws to avoid silent swallowing.\n */\n public async driver(driverName: string, runtimeOptions?: Record<string, any>) {\n if (this.loadedDrivers[driverName]) {\n this.assertNoConflictingReload(driverName, runtimeOptions);\n\n return this.loadedDrivers[driverName];\n }\n\n return this.load(driverName, runtimeOptions);\n }\n\n /**\n * Initialize the cache manager and pick the default driver\n */\n public async init() {\n const defaultCacheDriverName = this.configurations.default;\n\n if (!defaultCacheDriverName) {\n return;\n }\n\n const driver = await this.driver(defaultCacheDriverName);\n\n await this.use(driver);\n }\n\n /**\n * Load and connect the registered driver named `driver`. First-call wins —\n * subsequent calls without `runtimeOptions` return the cached instance, and\n * subsequent calls *with* `runtimeOptions` throw {@link CacheConfigurationError}\n * to avoid silently dropping the new options.\n *\n * `runtimeOptions` merge over `config.options[driver]` per-key (runtime wins),\n * letting consumers split static knobs (table, ttl, globalPrefix) from\n * constructor-only ones (pg's `client`, custom adapters, etc.).\n *\n * @example\n * const pool = new Pool({ connectionString });\n * const pg = await cache.load(\"pg\", { client: pool });\n */\n public async load(driver: string, runtimeOptions?: Record<string, any>) {\n if (this.loadedDrivers[driver]) {\n this.assertNoConflictingReload(driver, runtimeOptions);\n\n return this.loadedDrivers[driver];\n }\n\n const inFlight = this.loadingDrivers.get(driver);\n\n if (inFlight) {\n this.assertNoConflictingReload(driver, runtimeOptions);\n\n return inFlight;\n }\n\n const Driver = this.configurations.drivers[\n driver as keyof typeof this.configurations.drivers\n ] as DriverClass | undefined;\n\n if (!Driver) {\n throw new CacheConfigurationError(\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\n );\n }\n\n const driverInstance = new Driver();\n const configOptions =\n this.configurations.options[driver as keyof typeof this.configurations.options] || {};\n\n driverInstance.setOptions({ ...configOptions, ...(runtimeOptions ?? {}) });\n\n const promise = (async () => {\n try {\n await driverInstance.connect();\n\n this.attachGlobalListeners(driverInstance);\n\n this.loadedDrivers[driver] = driverInstance;\n\n return driverInstance as CacheDriver<any, any>;\n } finally {\n this.loadingDrivers.delete(driver);\n }\n })();\n\n this.loadingDrivers.set(driver, promise);\n\n return promise;\n }\n\n /**\n * Guard against silently dropping runtime options on a re-load. Once a\n * driver has been instantiated, its options are frozen — calling `load` /\n * `driver` / `use` again with a non-empty `runtimeOptions` would otherwise\n * appear to work but actually use the original options. We throw instead\n * so the misuse surfaces at the call site.\n */\n protected assertNoConflictingReload(\n driverName: string,\n runtimeOptions: Record<string, any> | undefined,\n ): void {\n if (runtimeOptions === undefined) {\n return;\n }\n\n if (Object.keys(runtimeOptions).length === 0) {\n return;\n }\n\n throw new CacheConfigurationError(\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.`,\n );\n }\n\n /**\n * Register and bind a driver\n */\n public registerDriver(driverName: string, driverClass: DriverClass) {\n (this.configurations.drivers as Record<string, DriverClass>)[driverName] = driverClass;\n }\n\n /**\n * Disconnect the cache manager\n */\n public async disconnect() {\n const drivers = new Set<any>(Object.values(this.loadedDrivers));\n\n if (this.currentDriver) {\n drivers.add(this.currentDriver);\n }\n\n await Promise.allSettled([...drivers].map(driver => driver.disconnect()));\n\n for (const name of Object.keys(this.loadedDrivers)) {\n delete this.loadedDrivers[name];\n }\n\n this.loadingDrivers.clear();\n }\n\n /**\n * {@inheritdoc}\n */\n public async has(key: CacheKey): Promise<boolean> {\n this.ensureDriverInitialized();\n return this.currentDriver!.has(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n this.ensureDriverInitialized();\n\n const driverOverride =\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\n ? ttlOrOptions.driver\n : undefined;\n\n if (driverOverride) {\n const driver = await this.load(driverOverride);\n return driver.remember(key, ttlOrOptions, callback);\n }\n\n return this.currentDriver!.remember(key, ttlOrOptions, callback);\n }\n\n /**\n * Stale-while-revalidate. Returns cached when fresh, returns the stale\n * value plus a background refresh when within `freshTtl..staleTtl`,\n * blocks like a normal miss past `staleTtl`. Honors per-call `driver`\n * override the same way `remember()` does.\n *\n * @example\n * const product = await cache.swr(\n * \"product.42\",\n * { freshTtl: \"1m\", staleTtl: \"1h\" },\n * () => db.products.find(42),\n * );\n */\n public async swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n this.ensureDriverInitialized();\n\n const driverOverride = options.driver;\n\n if (driverOverride) {\n const driver = await this.load(driverOverride);\n\n return driver.swr<T>(key, options, callback);\n }\n\n return this.currentDriver!.swr<T>(key, options, callback);\n }\n\n /**\n * {@inheritdoc}\n */\n public async pull(key: CacheKey): Promise<any | null> {\n this.ensureDriverInitialized();\n return this.currentDriver!.pull(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async forever(key: CacheKey, value: any): Promise<any> {\n this.ensureDriverInitialized();\n return this.currentDriver!.forever(key, value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async increment(key: CacheKey, value?: number): Promise<number> {\n this.ensureDriverInitialized();\n return this.currentDriver!.increment(key, value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value?: number): Promise<number> {\n this.ensureDriverInitialized();\n return this.currentDriver!.decrement(key, value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async many(keys: CacheKey[]): Promise<any[]> {\n this.ensureDriverInitialized();\n return this.currentDriver!.many(keys);\n }\n\n /**\n * {@inheritdoc}\n */\n public async setMany(items: Record<string, any>, ttl?: number): Promise<void> {\n this.ensureDriverInitialized();\n return this.currentDriver!.setMany(items, ttl);\n }\n\n /**\n * Register a global event listener (applies to all drivers)\n */\n public on(event: CacheEventType, handler: CacheEventHandler): this {\n if (!this.globalEventListeners.has(event)) {\n this.globalEventListeners.set(event, new Set());\n }\n this.globalEventListeners.get(event)!.add(handler);\n\n // Also attach to current driver if exists\n if (this.currentDriver) {\n this.currentDriver.on(event, handler);\n }\n\n // Attach to all loaded drivers\n for (const driver of Object.values(this.loadedDrivers)) {\n driver.on(event, handler);\n }\n\n return this;\n }\n\n /**\n * Remove a global event listener\n */\n public off(event: CacheEventType, handler: CacheEventHandler): this {\n const handlers = this.globalEventListeners.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n\n // Also remove from current driver\n if (this.currentDriver) {\n this.currentDriver.off(event, handler);\n }\n\n // Remove from all loaded drivers\n for (const driver of Object.values(this.loadedDrivers)) {\n driver.off(event, handler);\n }\n\n return this;\n }\n\n /**\n * Register a one-time global event listener\n */\n public once(event: CacheEventType, handler: CacheEventHandler): this {\n const onceHandler: CacheEventHandler = async (data) => {\n await handler(data);\n this.off(event, onceHandler);\n };\n return this.on(event, onceHandler);\n }\n\n /**\n * Attach global listeners to a driver\n */\n protected attachGlobalListeners(driver: CacheDriver<any, any>) {\n for (const [event, handlers] of this.globalEventListeners) {\n for (const handler of handlers) {\n driver.on(event, handler);\n }\n }\n }\n\n /**\n * Set if not exists (atomic operation)\n * Returns true if key was set, false if key already existed\n * Note: Only supported by drivers that implement setNX (e.g., Redis)\n */\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n this.ensureDriverInitialized();\n\n if (!this.currentDriver!.setNX) {\n throw new Error(\n `setNX is not supported by the current cache driver: ${this.currentDriver!.name}`,\n );\n }\n\n return this.currentDriver!.setNX(key, value, ttl);\n }\n\n /**\n * Create a tagged cache instance for the given tags\n */\n public tags(tags: string[]): TaggedCacheDriver {\n this.ensureDriverInitialized();\n return this.currentDriver!.tags(tags);\n }\n\n /**\n * Atomically read, transform, and write a cached value. Delegates to the current driver.\n */\n public async 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 this.ensureDriverInitialized();\n return this.currentDriver!.update<T>(key, fn, options);\n }\n\n /**\n * Shallow-merge a partial object into a cached value.\n */\n public async merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T> {\n this.ensureDriverInitialized();\n return this.currentDriver!.merge<T>(key, partial, options);\n }\n\n /**\n * Obtain a list accessor bound to the current driver.\n */\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\n this.ensureDriverInitialized();\n return this.currentDriver!.list<T>(key);\n }\n\n /**\n * Acquire a distributed lock, run `fn`, and auto-release. Returns a\n * {@link LockOutcome} discriminated union so callers can distinguish\n * \"ran and got this value\" from \"skipped because someone else holds it\".\n *\n * Honors the `driver` option for per-call driver override, same as `set`\n * and `remember`.\n *\n * @example\n * const outcome = await cache.lock(\"lock.import\", \"5m\", async () => {\n * await runImport();\n * return \"done\";\n * });\n * if (!outcome.acquired) {\n * console.log(\"another worker is already importing\");\n * }\n */\n public async lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | LockOptions,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>> {\n this.ensureDriverInitialized();\n\n const driverOverride =\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\n ? ttlOrOptions.driver\n : undefined;\n\n const driver = driverOverride\n ? await this.load(driverOverride)\n : this.currentDriver!;\n\n return driver.lock<T>(key, ttlOrOptions as CacheTtl | Omit<LockOptions, \"driver\">, fn);\n }\n\n /**\n * Similarity retrieval. Delegates to the current driver's `similar()` impl.\n *\n * Drivers that lack a similarity index throw {@link CacheUnsupportedError}.\n *\n * @example\n * const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });\n */\n /**\n * Create a scoped view over the cache. Every key written through the\n * returned scope is automatically prefixed with `prefix`; optional defaults\n * (`ttl`, `tags`) flow through every write inside the scope.\n *\n * Per-call options always win over scope defaults. Scope tags merge\n * additively with per-call tags. Nested scopes inherit from the parent.\n *\n * @example\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\n * await chat.set(\"messages.1\", msg); // → \"chats.10.messages.1\", 30d\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call ttl wins\n * await chat.namespace(\"typing\", { ttl: \"5s\" }).set(\"user.42\", true);\n * await chat.clear(); // wipe the whole scope\n */\n public namespace(prefix: string, options?: CacheNamespaceOptions): ScopedCacheContract {\n this.ensureDriverInitialized();\n return new ScopedCache(this, prefix, options);\n }\n\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n this.ensureDriverInitialized();\n return this.currentDriver!.similar<T>(vector, options);\n }\n}\n\nexport const cache = new CacheManager();\n","import { createHash } from \"node:crypto\";\nimport { CacheConfigurationError } from \"../types\";\n\n/**\n * Derive a cache key from a prefix and a set of function arguments.\n *\n * The key is `<prefix>.<hash>` where `hash` is the first 16 hex chars of the\n * sha1 of a stable JSON encoding of the args. Hashing keeps the mapping\n * injective — raw punctuation-joined segments can be folded together by\n * `parseCacheKey` (e.g. `(\"1.private\", \"\")` vs `(\"1\", \"private\")`).\n *\n * Encoding rules: object keys are sorted, `Date` → ISO string, `undefined` →\n * `{\"$u\":1}`, `bigint` → decimal string with an `n` suffix.\n *\n * No args → the prefix alone.\n *\n * Map, Set, functions, symbols and class instances cannot be encoded stably\n * and throw `CacheConfigurationError`; supply a custom `key` function instead.\n *\n * @example\n * deriveAutoKey(\"featured\", []); // \"featured\"\n * deriveAutoKey(\"user\", [42]); // \"user.<16 hex chars>\"\n */\nexport function deriveAutoKey(prefix: string, args: readonly unknown[]): string {\n if (args.length === 0) {\n return prefix;\n }\n\n let encoded: string;\n\n try {\n encoded = JSON.stringify(args.map((arg) => normalize(arg, prefix, new Set())));\n } catch (error) {\n if (error instanceof CacheConfigurationError) {\n throw error;\n }\n\n throw new CacheConfigurationError(\n `cached(): could not derive an auto-key from args for prefix \"${prefix}\". ` +\n `The args are not serializable. Use the options form with a custom key function. ` +\n `Original error: ${(error as Error).message}`,\n );\n }\n\n const hash = createHash(\"sha1\").update(encoded).digest(\"hex\").slice(0, 16);\n\n return `${prefix}.${hash}`;\n}\n\nfunction unsupported(prefix: string, what: string): never {\n throw new CacheConfigurationError(\n `cached(): cannot derive an auto-key for prefix \"${prefix}\" from ${what}. ` +\n `Use the options form with a custom key function.`,\n );\n}\n\nfunction normalize(value: unknown, prefix: string, seen: Set<object>): unknown {\n if (value === undefined) return { $u: 1 };\n if (value === null) return null;\n\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return value;\n case \"number\":\n // NaN / Infinity would collapse to null in JSON\n return Number.isFinite(value) ? value : { $n: String(value) };\n case \"bigint\":\n return `${value.toString()}n`;\n case \"function\":\n return unsupported(prefix, \"a function argument\");\n case \"symbol\":\n return unsupported(prefix, \"a symbol argument\");\n }\n\n const obj = value as object;\n\n if (obj instanceof Date) {\n return Number.isNaN(obj.getTime()) ? { $d: \"invalid\" } : obj.toISOString();\n }\n\n if (obj instanceof Map) return unsupported(prefix, \"a Map argument\");\n if (obj instanceof Set) return unsupported(prefix, \"a Set argument\");\n\n if (seen.has(obj)) {\n throw new CacheConfigurationError(\n `cached(): could not derive an auto-key for prefix \"${prefix}\": circular reference in args. ` +\n `Use the options form with a custom key function.`,\n );\n }\n\n seen.add(obj);\n\n let result: unknown;\n\n if (Array.isArray(obj)) {\n result = obj.map((item) => normalize(item, prefix, seen));\n } else {\n const proto = Object.getPrototypeOf(obj);\n\n if (proto !== Object.prototype && proto !== null) {\n unsupported(prefix, \"a class instance argument\");\n }\n\n const out: Record<string, unknown> = {};\n\n for (const key of Object.keys(obj).sort()) {\n out[key] = normalize((obj as Record<string, unknown>)[key], prefix, seen);\n }\n\n result = out;\n }\n\n seen.delete(obj);\n\n return result;\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 if (config.driver) {\n await (await cache.driver(config.driver)).remove(key);\n return;\n }\n\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 * runs through `driver.update()`, so it is serialized in-process and keeps the\n * entry's remaining TTL.\n *\n * **Responsibility.**\n * - Owns: translating list operations into array mutations + driver writes.\n * - Does NOT own: cross-process concurrency control (callers should wrap in a\n * distributed lock when multi-process writers are possible), or tagging of\n * 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 * Serialized read-modify-write through `driver.update()`. No ttl is passed,\n * so the driver keeps the entry's remaining TTL. An empty list removes the\n * entry to keep the store clean.\n */\n private async mutate<R>(mutation: (items: T[]) => R): Promise<R> {\n let result!: R;\n\n await this.driver.update<T[]>(this.key, current => {\n const items = Array.isArray(current) ? [...current] : [];\n\n result = mutation(items);\n\n return items.length === 0 ? null : items;\n });\n\n return result;\n }\n\n /**\n * {@inheritdoc}\n */\n public push(...items: T[]): Promise<number> {\n return this.mutate(current => {\n current.push(...items);\n\n return current.length;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public unshift(...items: T[]): Promise<number> {\n return this.mutate(current => {\n current.unshift(...items);\n\n return current.length;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public pop(): Promise<T | null> {\n return this.mutate(current => (current.length === 0 ? null : (current.pop() as T)));\n }\n\n /**\n * {@inheritdoc}\n */\n public shift(): Promise<T | null> {\n return this.mutate(current => (current.length === 0 ? null : (current.shift() as T)));\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 * Keep the inclusive range `[start, end]` like Redis LTRIM; negative\n * indexes count from the end.\n */\n public async trim(start: number, end: number): Promise<void> {\n await this.mutate(current => {\n const length = current.length;\n const from = start < 0 ? Math.max(length + start, 0) : start;\n const to = end < 0 ? length + end : end;\n const kept = current.slice(from, to + 1);\n\n current.splice(0, current.length, ...kept);\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async clear(): Promise<void> {\n await this.driver.remove(this.key);\n }\n}\n","import type {\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheTtl,\n TaggedCacheDriver,\n} from \"./types\";\nimport { parseCacheKey } from \"./utils\";\n\n/** Implemented by `BaseCacheDriver`; a custom driver may not have it. */\ntype TagsInvalidatedEmitter = {\n emitTagsInvalidated(data: { tags: string[]; keys: string[]; durationMs: number }): Promise<void>;\n};\n\n/**\n * Tagged Cache Wrapper\n * Wraps a cache driver to automatically manage tag relationships\n */\nexport class TaggedCache implements TaggedCacheDriver {\n /**\n * The tags associated with this tagged cache instance\n */\n protected cacheTags: string[];\n\n /**\n * The underlying cache driver\n */\n protected driver: CacheDriver<any, any>;\n\n /**\n * Constructor\n */\n public constructor(tags: string[], driver: CacheDriver<any, any>) {\n this.cacheTags = tags;\n this.driver = driver;\n }\n\n /**\n * Get the tag key prefix for storing tag-key relationships\n */\n protected tagKey(tag: string): string {\n return `cache:tags:${tag}`;\n }\n\n /**\n * Store tag-key relationship\n */\n protected async storeTaggedKey(key: CacheKey): Promise<void> {\n await this.storeTagRelationship(key);\n }\n\n /**\n * Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`\n * when tags are passed inline through `CacheSetOptions.tags`.\n *\n * Takes the caller's key, never the driver-parsed one — see {@link indexedKey}.\n *\n * @internal — public for cross-class use within this package; not part of the\n * stable consumer API.\n */\n public async storeTagRelationship(key: CacheKey): Promise<void> {\n const indexedKey = this.indexedKey(key);\n\n for (const tag of this.cacheTags) {\n const tagKey = this.tagKey(tag);\n\n await this.addToTag(tagKey, [indexedKey]);\n await this.pruneTag(tagKey, indexedKey);\n }\n }\n\n /**\n * Upper bound on member-existence checks per tagged write, so pruning stays\n * O(1) per write no matter how large an index grows.\n */\n protected static readonly PRUNE_CHECKS_PER_WRITE = 20;\n\n /**\n * Drop index members whose entry no longer exists (expired, evicted, or\n * removed outside a tagged handle). Checks a random window of at most\n * {@link PRUNE_CHECKS_PER_WRITE} members, so a long-lived index converges\n * to its live members without any write paying for a full scan.\n */\n protected async pruneTag(tagKey: string, justWritten: string): Promise<void> {\n const members = (await this.membersOf(tagKey)).filter((member) => member !== justWritten);\n\n if (members.length === 0) {\n return;\n }\n\n const limit = TaggedCache.PRUNE_CHECKS_PER_WRITE;\n const start = members.length > limit ? Math.floor(Math.random() * members.length) : 0;\n const window = Array.from(\n { length: Math.min(limit, members.length) },\n (_, index) => members[(start + index) % members.length]!,\n );\n\n const missing: string[] = [];\n\n for (const member of window) {\n if (!(await this.driver.has(member))) {\n missing.push(member);\n }\n }\n\n if (missing.length > 0) {\n await this.removeFromTag(tagKey, missing);\n }\n }\n\n // ------------------------------------------------------------\n // Tag-index access: the driver's primitives when it has them, else\n // the legacy get/set array (third-party drivers without primitives).\n // ------------------------------------------------------------\n\n protected async addToTag(tagKey: string, members: string[]): Promise<void> {\n if (typeof this.driver.tagAdd === \"function\") {\n return this.driver.tagAdd(tagKey, members);\n }\n\n const current = await this.legacyMembers(tagKey);\n const merged = [...new Set([...current, ...members])];\n\n if (merged.length !== current.length) {\n await this.driver.set(tagKey, merged, Infinity);\n }\n }\n\n protected async membersOf(tagKey: string): Promise<string[]> {\n if (typeof this.driver.tagMembers === \"function\") {\n return this.driver.tagMembers(tagKey);\n }\n\n return this.legacyMembers(tagKey);\n }\n\n protected async removeFromTag(tagKey: string, members: string[]): Promise<void> {\n if (typeof this.driver.tagRemove === \"function\") {\n return this.driver.tagRemove(tagKey, members);\n }\n\n const dropped = new Set(members);\n const current = await this.legacyMembers(tagKey);\n const kept = current.filter((member) => !dropped.has(member));\n\n if (kept.length === current.length) {\n return;\n }\n\n if (kept.length === 0) {\n await this.driver.remove(tagKey);\n } else {\n await this.driver.set(tagKey, kept, Infinity);\n }\n }\n\n protected async legacyMembers(tagKey: string): Promise<string[]> {\n const value = await this.driver.get(tagKey);\n\n return Array.isArray(value)\n ? value.filter((member): member is string => typeof member === \"string\")\n : [];\n }\n\n /**\n * The form a key takes inside a tag index: normalized but un-prefixed.\n *\n * The index is read back and deleted through `driver.remove(key)`, which\n * applies `globalPrefix` itself — so the index must hold the key *before*\n * the prefix, or invalidation would prefix it twice and delete nothing.\n * With a function prefix, the current prefix is re-applied at invalidation\n * time; the tag index key is resolved under that same prefix, so the two\n * agree as long as the prefix is stable for a given request/tenant.\n */\n protected indexedKey(key: CacheKey): string {\n return parseCacheKey(key);\n }\n\n /**\n * Get all keys associated with tags\n */\n protected async getTaggedKeys(): Promise<Set<string>> {\n const allKeys = new Set<string>();\n\n for (const tag of this.cacheTags) {\n for (const key of await this.membersOf(this.tagKey(tag))) {\n allKeys.add(key);\n }\n }\n\n return allKeys;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n await this.driver.set(key, value, ttlOrOptions);\n\n await this.storeTaggedKey(key);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey): Promise<any | null> {\n return this.driver.get(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey): Promise<void> {\n const indexedKey = this.indexedKey(key);\n\n // Remove the value\n await this.driver.remove(key);\n\n // Remove from all tag relationships\n await this.detachFromTags(indexedKey);\n }\n\n /**\n * Invalidate (clear) all keys associated with the current tags\n */\n public async invalidate(): Promise<void> {\n const startedAt = performance.now();\n const removed = new Set<string>();\n\n for (const tag of this.cacheTags) {\n const tagKey = this.tagKey(tag);\n const members = await this.membersOf(tagKey);\n\n // Remove the tagged entries — indexed un-prefixed, so `remove()` prefixes once\n for (const member of members) {\n await this.driver.remove(member);\n removed.add(member);\n }\n\n // Remove exactly the members read above rather than dropping the whole\n // index: a key tagged concurrently (after the read) keeps its entry in\n // the index, so a later invalidate still reaches it.\n await this.removeFromTag(tagKey, members);\n }\n\n // Drivers built on `BaseCacheDriver` emit `invalidated`; a custom driver\n // without the method simply emits nothing.\n const driver = this.driver as Partial<TagsInvalidatedEmitter>;\n\n await driver.emitTagsInvalidated?.({\n tags: [...this.cacheTags],\n keys: [...removed],\n durationMs: performance.now() - startedAt,\n });\n }\n\n /**\n * Remove `indexedKey` from every tag of this handle.\n */\n protected async detachFromTags(indexedKey: string): Promise<void> {\n for (const tag of this.cacheTags) {\n await this.removeFromTag(this.tagKey(tag), [indexedKey]);\n }\n }\n\n /**\n * Flush all keys associated with the current tags\n * @deprecated Use invalidate() instead for better semantics\n */\n public async flush(): Promise<void> {\n return this.invalidate();\n }\n\n /**\n * {@inheritdoc}\n */\n public async has(key: CacheKey): Promise<boolean> {\n return this.driver.has(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remember(\n key: CacheKey,\n ttl: number,\n callback: () => Promise<any>,\n ): Promise<any> {\n const value = await this.get(key);\n\n if (value !== null) {\n return value;\n }\n\n const result = await callback();\n await this.set(key, result, ttl);\n\n return result;\n }\n\n /**\n * {@inheritdoc}\n */\n public async pull(key: CacheKey): Promise<any | null> {\n // The driver's own `pull` (atomic on redis / pg / in-process drivers)\n // hands the value out once; then the key leaves this handle's tags.\n const value = await this.driver.pull(key);\n\n if (value !== null) {\n await this.detachFromTags(this.indexedKey(key));\n }\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async forever(key: CacheKey, value: any): Promise<any> {\n return this.set(key, value, Infinity);\n }\n\n /**\n * {@inheritdoc}\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n // The driver's atomic increment (INCRBY / pg upsert / synchronous\n // in-process), then record the tag relationship.\n const newValue = await this.driver.increment(key, value);\n\n await this.storeTaggedKey(key);\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\n return this.increment(key, -value);\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { log, maskEmailsInText } from \"@warlock.js/logger\";\nimport { MemoryCacheList } from \"../list/memory-cache-list\";\nimport { TaggedCache } from \"../tagged-cache\";\nimport type {\n CacheConflictPolicy,\n CacheData,\n CacheDriver,\n CacheEventData,\n CacheEventHandler,\n CacheEventType,\n CacheKey,\n CacheListAccessor,\n CacheOperationType,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheSwrOptions,\n CacheTtl,\n LockOptions,\n LockOutcome,\n RememberOptions,\n} from \"../types\";\nimport { CacheConfigurationError, CacheUnsupportedError } from \"../types\";\nimport {\n normalizeToOptions,\n parseCacheKey,\n parseTtl,\n resolveTtl,\n safeErrorInfo,\n} from \"../utils\";\n\n/**\n * Normalized form of the 3rd `set` argument.\n *\n * All drivers operate on this shape internally regardless of whether the caller\n * passed a positional TTL, a duration string, or a rich options object.\n */\nexport type NormalizedSetOptions = {\n /**\n * Final TTL in seconds — already merged with the driver-level default\n * (`this.options.ttl`). `Infinity` means \"no expiration\".\n *\n * Always populated. Drivers do NOT need to fall back to `this.ttl`\n * themselves — `resolveSetOptions` does the merge centrally so that\n * every driver respects the configured default without ceremony.\n */\n ttl: number;\n /**\n * Inline tag list, or undefined when none were provided.\n */\n tags?: string[];\n /**\n * Conflict policy. Defaults to `\"upsert\"`.\n */\n onConflict: CacheConflictPolicy;\n /**\n * Optional embedding vector for similarity retrieval. Drivers that do not\n * support similarity must throw {@link CacheUnsupportedError} when this is\n * present (rather than silently dropping it).\n */\n vector?: number[];\n /**\n * Optional freshness deadline as a millisecond timestamp. Set by `swr()`\n * to mark when the entry stops being \"fresh\" and becomes\n * \"stale-but-revalidatable.\" Drivers route this through\n * `prepareDataForStorage` so it persists in the wrapper.\n */\n staleAt?: number;\n};\n\nconst messages = {\n clearing: \"Clearing namespace\",\n cleared: \"Namespace cleared\",\n fetching: \"Fetching key\",\n fetched: \"Key fetched\",\n caching: \"Caching key\",\n cached: \"Key cached\",\n flushing: \"Flushing cache\",\n flushed: \"Cache flushed\",\n removing: \"Removing key\",\n removed: \"Key removed\",\n expired: \"Key expired\",\n notFound: \"Key not found\",\n connecting: \"Connecting to the cache engine.\",\n connected: \"Connected to the cache engine.\",\n disconnecting: \"Disconnecting from the cache engine.\",\n disconnected: \"Disconnected from the cache engine.\",\n connectionFailed: \"Failed to connect to the cache engine.\",\n error: \"Error occurred\",\n};\n\nexport abstract class BaseCacheDriver<\n ClientType,\n Options extends Record<string, any>,\n> implements CacheDriver<ClientType, Options> {\n /**\n * CLient driver\n */\n protected clientDriver!: ClientType;\n\n /**\n * Determine whether to log or not\n */\n protected shouldLog: boolean = true;\n\n /**\n * {@inheritdoc}\n */\n public get client() {\n return (this.clientDriver || this) as unknown as ClientType;\n }\n\n /**\n * Set logging state\n */\n public setLoggingState(shouldLog: boolean) {\n this.shouldLog = shouldLog;\n\n return this;\n }\n\n /**\n * Set client driver\n */\n public set client(client: ClientType) {\n this.clientDriver = client;\n }\n\n /**\n * Get the cache driver name\n */\n public abstract name: string;\n\n /**\n * Options list\n */\n public options!: Options;\n\n /**\n * Event listeners storage\n */\n protected eventListeners: Map<CacheEventType, Set<CacheEventHandler>> =\n new Map();\n\n /**\n * {@inheritdoc}\n */\n public parseKey(key: CacheKey) {\n return parseCacheKey(key, this.options);\n }\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: Options) {\n this.options = options || {};\n return this;\n }\n\n /**\n * Register an event listener\n */\n public on(event: CacheEventType, handler: CacheEventHandler): this {\n if (!this.eventListeners.has(event)) {\n this.eventListeners.set(event, new Set());\n }\n this.eventListeners.get(event)!.add(handler);\n return this;\n }\n\n /**\n * Remove an event listener\n */\n public off(event: CacheEventType, handler: CacheEventHandler): this {\n const handlers = this.eventListeners.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n return this;\n }\n\n /**\n * Register a one-time event listener\n */\n public once(event: CacheEventType, handler: CacheEventHandler): this {\n const onceHandler: CacheEventHandler = async (data) => {\n await handler(data);\n this.off(event, onceHandler);\n };\n return this.on(event, onceHandler);\n }\n\n /**\n * Emit an event to all registered listeners\n */\n protected async emit(\n event: CacheEventType,\n data: Partial<CacheEventData> = {},\n ): Promise<void> {\n const handlers = this.eventListeners.get(event);\n if (!handlers || handlers.size === 0) return;\n\n const eventData: CacheEventData = {\n driver: this.name,\n ...data,\n };\n\n // Execute all handlers\n const promises: Promise<void>[] = [];\n for (const handler of handlers) {\n try {\n const result = handler(eventData);\n if (result instanceof Promise) {\n promises.push(result);\n }\n } catch (error) {\n this.logError(`Error in event handler for '${event}'`, error);\n }\n }\n\n // Wait for all async handlers\n if (promises.length > 0) {\n await Promise.allSettled(promises);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public abstract removeNamespace(namespace: string): Promise<any>;\n\n /**\n * {@inheritdoc}\n */\n public abstract set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any>;\n\n /**\n * Normalize the 3rd argument of a `set` call into a single shape every driver\n * can act on. Handles TTL parsing (number | string | Infinity), `expiresAt` →\n * relative TTL conversion, and mutual-exclusion validation.\n *\n * @throws {CacheConfigurationError} when `ttl` and `expiresAt` are passed together\n * or an unparseable duration string is supplied.\n */\n protected resolveSetOptions(\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): NormalizedSetOptions {\n const options = normalizeToOptions(ttlOrOptions);\n\n return {\n ttl: resolveTtl(options.ttl, options.expiresAt, this.ttl),\n tags: options.tags,\n onConflict: options.onConflict ?? \"upsert\",\n vector: options.vector,\n staleAt: options.staleAt,\n };\n }\n\n /**\n * Resolve the union of cache keys associated with any of the given tags.\n * Used by `similar()` to narrow the candidate pool before similarity ranking.\n *\n * Returns `null` when no tags are passed (callers should treat that as \"no filter\").\n * Keys come back parsed (prefixed) — the index stores them un-prefixed, but\n * callers match them against their own parsed storage keys.\n */\n protected async getKeysForTags(\n tags: string[] | undefined,\n ): Promise<Set<string> | null> {\n if (!tags || tags.length === 0) {\n return null;\n }\n\n const allKeys = new Set<string>();\n for (const tag of tags) {\n const keys = await this.tagMembers(`cache:tags:${tag}`);\n for (const k of keys) {\n allKeys.add(this.parseKey(k));\n }\n }\n\n return allKeys;\n }\n\n // ============================================================\n // Tag index primitives\n // ============================================================\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: the index is an ordinary entry holding a string\n * array, read-modified-written under {@link runSerialized} — correct within\n * THIS process only. Drivers with a native primitive override it (redis SET,\n * pg atomic upsert, the in-process drivers' {@link InMemoryTagIndex}).\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n await this.runSerialized(this.parseKey(tagKey), async () => {\n const current = await this.readTagArray(tagKey);\n const merged = new Set(current);\n\n for (const member of members) {\n merged.add(member);\n }\n\n if (merged.size !== current.length) {\n await this.set(tagKey, [...merged], Infinity);\n }\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.readTagArray(tagKey);\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: serialized read-filter-write (process-local); an\n * emptied index is removed.\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n await this.runSerialized(this.parseKey(tagKey), async () => {\n const current = await this.readTagArray(tagKey);\n const dropped = new Set(members);\n const kept = current.filter((member) => !dropped.has(member));\n\n if (kept.length === current.length) {\n return;\n }\n\n if (kept.length === 0) {\n await this.remove(tagKey);\n } else {\n await this.set(tagKey, kept, Infinity);\n }\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n await this.runSerialized(this.parseKey(tagKey), () => this.remove(tagKey));\n }\n\n /**\n * The index stored as a plain entry, or `[]`. Non-string members (a corrupted\n * or foreign value) are ignored rather than trusted.\n */\n protected async readTagArray(tagKey: CacheKey): Promise<string[]> {\n const value = await this.get(tagKey);\n\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.filter((member): member is string => typeof member === \"string\");\n }\n\n /**\n * Apply tag relationships after a successful write. Called by drivers once\n * the value is in storage.\n *\n * Pass the caller's key, not the parsed one: the tag index stores keys\n * un-prefixed so `invalidate()` can delete them through `remove(key)`.\n */\n protected async applyTags(key: CacheKey, tags: string[]): Promise<void> {\n if (tags.length === 0) {\n return;\n }\n\n const tagged = this.tags(tags);\n await (tagged as TaggedCache).storeTagRelationship(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public abstract get(key: CacheKey): Promise<any>;\n\n /**\n * {@inheritdoc}\n */\n public abstract remove(key: CacheKey): Promise<void>;\n\n /**\n * {@inheritdoc}\n */\n public abstract flush(): Promise<void>;\n\n /**\n * {@inheritdoc}\n */\n public async has(key: CacheKey): Promise<boolean> {\n const value = await this.get(key);\n // Event is emitted by get() method\n return value !== null;\n }\n\n /**\n * In-flight `remember()` computations, one per parsed key (stampede guard).\n */\n protected rememberInFlight: Map<string, Promise<any>> = new Map();\n\n /**\n * In-flight `swr()` cold-miss fetches, one per parsed key.\n */\n protected swrFetchInFlight: Map<string, Promise<any>> = new Map();\n\n /**\n * In-flight `swr()` stale-window background refreshes, one per parsed key.\n */\n protected swrRefreshes: Map<string, Promise<void>> = new Map();\n\n /**\n * Per-key serialization chains for the in-process read-modify-write\n * operations (`update`, and the default `increment` / `pull`). Kept apart\n * from the stampede maps so a `remember()` can never break an update chain,\n * and never receives an unrelated promise.\n */\n protected updateChains: Map<string, Promise<any>> = new Map();\n\n /**\n * Run `task` once per key at a time: concurrent callers for the same\n * `parsedKey` share the in-flight promise registered in `inFlight`.\n */\n protected singleFlight<T>(\n inFlight: Map<string, Promise<any>>,\n parsedKey: string,\n task: () => Promise<T>,\n ): Promise<T> {\n const existing = inFlight.get(parsedKey);\n\n if (existing) {\n return existing as Promise<T>;\n }\n\n const promise: Promise<T> = task().finally(() => {\n if (inFlight.get(parsedKey) === promise) {\n inFlight.delete(parsedKey);\n }\n });\n\n inFlight.set(parsedKey, promise);\n\n return promise;\n }\n\n /**\n * Chain `task` after every earlier serialized task for the same key, so\n * read-modify-write callers in THIS process run one at a time. This is\n * process-local: it does not coordinate with other servers.\n */\n protected runSerialized<T>(parsedKey: string, task: () => Promise<T>): Promise<T> {\n const previous = this.updateChains.get(parsedKey) ?? Promise.resolve();\n\n const next = previous.catch(() => undefined).then(task);\n\n this.updateChains.set(parsedKey, next);\n\n // Clean up the slot once this link finishes, but only if nobody chained a\n // follow-up onto it meanwhile. The trailing catch keeps a rejected link\n // from surfacing as an unhandled rejection here (the caller still sees it).\n next\n .finally(() => {\n if (this.updateChains.get(parsedKey) === next) {\n this.updateChains.delete(parsedKey);\n }\n })\n .catch(() => undefined);\n\n return next;\n }\n\n /**\n * {@inheritdoc}\n */\n public async remember(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<any>,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n\n // The options-form lets callers forward tags / driver-override through to\n // the cache-miss write. Normalize both shapes into a single CacheSetOptions\n // blob so there's one path from here on.\n const setOptions = this.normalizeRememberOptions(ttlOrOptions);\n\n // Only a real miss (`null`) recomputes: cached falsy values such as\n // `0`, `false` and `\"\"` are returned as-is.\n const cachedValue = await this.get(key);\n\n if (cachedValue !== null && cachedValue !== undefined) {\n return cachedValue;\n }\n\n return this.singleFlight(this.rememberInFlight, parsedKey, async () => {\n const result = await callback();\n\n await this.set(key, result, setOptions);\n\n return result;\n });\n }\n\n /**\n * Resolve the TTL-or-options arg of `remember` into a `CacheSetOptions` object\n * that can be passed straight to `set()`. Keeps the implementation unbranched.\n */\n protected normalizeRememberOptions(\n ttlOrOptions: CacheTtl | RememberOptions,\n ): CacheSetOptions {\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\n return { ttl: ttlOrOptions };\n }\n\n return {\n ttl: ttlOrOptions.ttl,\n tags: ttlOrOptions.tags,\n };\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: read raw entry, branch on freshness/staleness,\n * trigger background refresh in the stale window, fall through to\n * `callback` on miss/expiry. Concurrent cold-miss callers share one fetch\n * ({@link swrFetchInFlight}); concurrent stale-window callers share one\n * background refresh ({@link swrRefreshes}).\n *\n * Drivers without a real {@link getEntry} override degrade gracefully —\n * the synthetic entry has no `staleAt`, which the freshness check treats\n * as \"always fresh,\" so SWR behaves like a TTL-only cached read on those\n * drivers (no background refresh, but no double-fetch either).\n */\n public async swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n const parsedKey = this.parseKey(key);\n const freshSeconds = parseTtl(options.freshTtl);\n const staleSeconds = parseTtl(options.staleTtl);\n\n if (staleSeconds <= freshSeconds) {\n throw new Error(\n `cache.swr: 'staleTtl' (${staleSeconds}s) must be greater than 'freshTtl' (${freshSeconds}s).`,\n );\n }\n\n const entry = await this.getEntry(key);\n const now = Date.now();\n\n const isExpired = entry?.expiresAt !== undefined && entry.expiresAt <= now;\n\n if (!entry || isExpired) {\n return this.singleFlight(this.swrFetchInFlight, parsedKey, () =>\n this.swrFetchAndStore<T>(key, options, callback, freshSeconds, staleSeconds),\n );\n }\n\n const isFresh = entry.staleAt === undefined || entry.staleAt > now;\n\n if (isFresh) {\n return entry.data as T;\n }\n\n this.scheduleSwrRefresh<T>(\n parsedKey,\n key,\n options,\n callback,\n freshSeconds,\n staleSeconds,\n );\n\n return entry.data as T;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper for a key, including any\n * `expiresAt` / `staleAt` metadata. Default implementation falls back to\n * `get()` and synthesizes a metadata-less wrapper — drivers that store\n * the wrapper directly (memory, lru, file, redis, pg, mock) override\n * this to return real metadata so SWR can branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const value = await this.get(key);\n\n if (value === null) {\n return null;\n }\n\n return { data: value };\n }\n\n /**\n * Remaining lifetime of an existing entry, in seconds — used by TTL-preserving\n * writes such as `update()` / `merge()` when the caller passes no explicit\n * `ttl`.\n *\n * - `Infinity` — the entry exists with no expiry (preserve \"never expires\").\n * - positive number — seconds left before the entry expires.\n * - `undefined` — the key is missing or already past its deadline; the caller\n * should fall back to the driver default TTL.\n *\n * Default reads `expiresAt` from {@link getEntry}, which the metadata-aware\n * drivers (memory, lru, mock, pg) populate. Drivers that track TTL natively\n * and don't carry `expiresAt` in their payload (Redis) override this.\n */\n protected async getRemainingTtl(key: CacheKey): Promise<number | undefined> {\n const entry = await this.getEntry(key);\n\n if (!entry) {\n return undefined;\n }\n\n if (!entry.expiresAt || entry.expiresAt === Infinity) {\n return Infinity;\n }\n\n const remainingSeconds = Math.ceil((entry.expiresAt - Date.now()) / 1000);\n\n return remainingSeconds > 0 ? remainingSeconds : undefined;\n }\n\n /**\n * Block-and-fetch path of `swr()`: invoked on miss or past-`staleTtl`\n * expiry. Writes through `set()` with the SWR options translated into\n * standard `CacheSetOptions` (ttl = staleTtl, staleAt = now + freshTtl).\n */\n protected async swrFetchAndStore<T>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n freshSeconds: number,\n staleSeconds: number,\n ): Promise<T> {\n const result = await callback();\n\n await this.set(key, result, {\n ttl: staleSeconds,\n staleAt: Date.now() + freshSeconds * 1000,\n tags: options.tags,\n });\n\n return result;\n }\n\n /**\n * Stale-window background refresh. Registers a single in-flight promise\n * per parsed key so concurrent SWR callers share one refresh. Failed\n * refreshes preserve the stale entry, log via `logError`, and emit on\n * `error` — the stale-returning caller never sees the failure.\n */\n protected scheduleSwrRefresh<T>(\n parsedKey: string,\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n freshSeconds: number,\n staleSeconds: number,\n ): void {\n if (this.swrRefreshes.has(parsedKey)) {\n return;\n }\n\n let refresh!: Promise<void>;\n refresh = (async () => {\n try {\n const result = await callback();\n\n await this.set(key, result, {\n ttl: staleSeconds,\n staleAt: Date.now() + freshSeconds * 1000,\n tags: options.tags,\n });\n } catch (error) {\n this.logError(`SWR background refresh failed for ${parsedKey}`, error);\n await this.emit(\"error\", { key: parsedKey, error });\n } finally {\n if (this.swrRefreshes.get(parsedKey) === refresh) {\n this.swrRefreshes.delete(parsedKey);\n }\n }\n })();\n\n this.swrRefreshes.set(parsedKey, refresh);\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: get-then-remove, serialized per key in THIS\n * process. Drivers with a native primitive override it to be atomic across\n * servers (redis `GETDEL`, pg `DELETE … RETURNING`) or process-atomic\n * (memory, lru, mock).\n */\n public async pull(key: CacheKey): Promise<any | null> {\n return this.runSerialized(this.parseKey(key), async () => {\n // Events are emitted by get() and remove()\n const value = await this.get(key);\n\n if (value !== null) {\n await this.remove(key);\n }\n\n return value;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async forever(key: CacheKey, value: any): Promise<any> {\n // Event is emitted by set() method\n return this.set(key, value, Infinity);\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: read-add-write serialized per key in THIS process,\n * keeping the entry's deadline (a missing key starts from 0 with the driver\n * default TTL). Not atomic across servers: drivers with a native primitive\n * override it (redis `INCRBY`, pg single-statement upsert).\n *\n * The value and its deadline come from ONE read. Reading them separately\n * let a counter that expired in between be rewritten from its stale value\n * with no expiry at all (a login-throttle counter that then never resets).\n * The remaining TTL is rounded down, so repeated increments can never push\n * a window's deadline later.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n\n return this.runSerialized(parsedKey, async () => {\n const entry = await this.getEntry(key);\n const now = Date.now();\n const expiresAt = entry?.expiresAt;\n const live = entry ? expiresAt == null || expiresAt > now : false;\n const current = entry && live ? entry.data : null;\n const base = current === null || current === undefined ? 0 : current;\n\n if (typeof base !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = base + value;\n\n if (!live) {\n // missing, or expired since it was read: a fresh counter\n await this.set(key, newValue);\n } else if (expiresAt == null || expiresAt === Infinity) {\n await this.set(key, newValue, { ttl: Infinity });\n } else {\n await this.set(key, newValue, {\n ttl: Math.max(1, Math.floor((expiresAt - now) / 1000)),\n });\n }\n\n return newValue;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\n return this.increment(key, -value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async many(keys: CacheKey[]): Promise<any[]> {\n return Promise.all(keys.map((key) => this.get(key)));\n }\n\n /**\n * {@inheritdoc}\n */\n public async setMany(\n items: Record<string, any>,\n ttl?: number,\n ): Promise<void> {\n await Promise.all(\n Object.entries(items).map(([key, value]) => this.set(key, value, ttl)),\n );\n }\n\n /**\n * Log the operation\n */\n protected log(operation: CacheOperationType, key?: string | any) {\n if (!this.shouldLog) return;\n\n if (typeof key === \"string\") {\n // this will be likely used with file cache driver as it will convert the dot to slash\n // to make it consistent and not to confuse developers we will output the key by making sure it's a dot\n // An email inside a key (an app's `user.<email>` key, say) is personal\n // data in a log line; key-based redaction cannot reach it, so mask it.\n key = maskEmailsInText(key.replace(/\\//g, \".\"));\n }\n\n if (operation === \"connectionFailed\") {\n log.fatal(`cache.${this.name}`, operation, key);\n }\n\n if (operation == \"notFound\" || operation == \"expired\") {\n return log.info(\n \"cache.\" + this.name,\n operation,\n (key ? key + \" \" : \"\") + messages[operation],\n );\n }\n\n if (operation.endsWith(\"ed\")) {\n return log.success(\n \"cache.\" + this.name,\n operation,\n (key ? key + \" \" : \"\") + messages[operation],\n );\n }\n\n log.info(\n \"cache.\" + this.name,\n operation,\n (key ? key + \" \" : \"\") + messages[operation],\n );\n }\n\n /**\n * Log error message\n *\n * Never logs the raw `error` object — driver connection errors (Redis,\n * Postgres, ...) can carry the connection string, including the password,\n * in the message/cause. Only a redacted `{ message, code }` shape is\n * passed to the structured logger.\n */\n protected logError(message: string, error?: any) {\n // Error lines name the key (\"SWR background refresh failed for\n // user.alice@example.com\"), and they are written even with logging off,\n // so addresses are masked here as in `log()`.\n const maskedMessage = maskEmailsInText(message);\n\n if (error) {\n const info = safeErrorInfo(error);\n\n log.error(\"cache.\" + this.name, \"error\", maskedMessage, {\n ...info,\n message: maskEmailsInText(info.message),\n });\n } else {\n log.error(\"cache.\" + this.name, \"error\", maskedMessage);\n }\n }\n\n /**\n * Get the default TTL in seconds. Parses human-readable strings (`\"1h\"`, `\"30m\"`)\n * from driver options if present; falls back to `Infinity` when no default is set.\n */\n public get ttl() {\n if (this.options.ttl === undefined) {\n return Infinity;\n }\n\n return parseTtl(this.options.ttl);\n }\n\n /**\n * Get time to live value in milliseconds\n */\n public getExpiresAt(ttl: number = this.ttl) {\n if (ttl) {\n return new Date().getTime() + ttl * 1000;\n }\n }\n\n /**\n * Wrap a value with TTL and optional freshness metadata for backend\n * storage. `staleAt` persists alongside `expiresAt` when supplied — used\n * by the SWR flow to mark when the entry stops being fresh.\n */\n protected prepareDataForStorage(data: any, ttl?: number, staleAt?: number) {\n const preparedData: CacheData = {\n data,\n };\n\n if (ttl) {\n preparedData.ttl = ttl;\n preparedData.expiresAt = this.getExpiresAt(ttl);\n }\n\n if (staleAt !== undefined) {\n preparedData.staleAt = staleAt;\n }\n\n return preparedData;\n }\n\n /**\n * Parse fetched data from cache\n */\n protected async parseCachedData(key: string, data: CacheData) {\n this.log(\"fetched\", key);\n\n if (data.expiresAt && data.expiresAt < Date.now()) {\n this.remove(key);\n return null;\n }\n\n const value = data.data;\n\n // Skip cloning for primitives (immutable types)\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n return value;\n }\n\n // Deep clone objects/arrays to prevent cache mutation\n try {\n return structuredClone(value);\n } catch (error) {\n this.logError(\n `Failed to clone cached value for ${key}, typeof value: ${typeof value}`,\n error,\n );\n throw error;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n this.log(\"connecting\");\n this.log(\"connected\");\n await this.emit(\"connected\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n this.log(\"disconnected\");\n await this.emit(\"disconnected\");\n }\n\n /**\n * Create a tagged cache instance for the given tags\n */\n public tags(tags: string[]): any {\n return new TaggedCache(tags, this);\n }\n\n /**\n * Emit `invalidated` on behalf of a {@link TaggedCache} created by this\n * driver, once per `invalidate()` call. Listeners cannot fail the\n * invalidation: `emit` isolates them.\n *\n * @internal\n */\n public async emitTagsInvalidated(data: {\n tags: string[];\n keys: string[];\n durationMs: number;\n }): Promise<void> {\n await this.emit(\"invalidated\", data);\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: read → transform → write, serialized per key in\n * THIS process only. Drivers that can offer cross-server atomicity (redis,\n * pg: compare-and-set with retries) override it.\n */\n public async 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 // Chain each update onto the previous one for the same key so concurrent\n // callers are serialized end-to-end, not merely awakened together.\n return this.runSerialized(this.parseKey(key), async () => {\n const current = (await this.get(key)) as T | null;\n const result = await fn(current);\n\n if (result === null) {\n await this.remove(key);\n return null;\n }\n\n if (options.ttl !== undefined) {\n await this.set(key, result, { ttl: options.ttl });\n\n return result;\n }\n\n // No explicit TTL → preserve the existing entry's remaining lifetime\n // rather than resetting it to the driver default.\n const remainingTtl = await this.getRemainingTtl(key);\n\n if (remainingTtl !== undefined) {\n await this.set(key, result, { ttl: remainingTtl });\n } else {\n await this.set(key, result);\n }\n\n return result;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options: { ttl?: CacheTtl } = {},\n ): Promise<T> {\n const result = await this.update<T>(\n key,\n (current) => {\n const base = (current ?? {}) as T;\n return { ...base, ...partial } as T;\n },\n options,\n );\n\n return result as T;\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: read-mutate-write array backed by the underlying\n * cache entry. Concrete drivers (e.g. Redis) override with native commands.\n */\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\n return new MemoryCacheList<T>(this, key);\n }\n\n /**\n * {@inheritdoc}\n *\n * Built on top of `set({ onConflict: \"create\" })` — Redis-native `SET … NX EX`\n * under the hood on Redis, emulated via key-existence check on other drivers.\n * The lock value is the resolved `owner` (defaults to `pid.<process.pid>`).\n *\n * Always releases in `finally`, even if `fn` throws — the thrown error\n * propagates to the caller unchanged, and a failed release is logged rather\n * than replacing `fn`'s result or error. The ttl must be finite and\n * positive; there is no renewal, so it must exceed the worst-case duration.\n */\n public async lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>> {\n const { ttl, owner } = this.normalizeLockOptions(ttlOrOptions);\n\n // A lock with no expiry stays held forever if its holder crashes, on every\n // server. Refuse it up front (parseTtl also rejects undefined / NaN).\n const ttlSeconds = parseTtl(ttl);\n\n if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {\n throw new CacheConfigurationError(\n `cache.lock() requires a finite, positive ttl (got ${String(ttl)}). The ttl must exceed the worst-case duration of the locked work.`,\n );\n }\n // The stored value is `<owner>#<token>`: the token makes it unique per\n // acquisition so release can compare-and-delete instead of deleting blindly.\n const lockValue = `${owner ?? `pid.${process.pid}`}#${randomUUID()}`;\n\n const setResult = (await this.set(key, lockValue, {\n onConflict: \"create\",\n ttl,\n })) as CacheSetResult | unknown;\n\n // `onConflict` drivers return CacheSetResult. Drivers that no-op on set\n // (e.g. the null driver) may return anything else — treat that as\n // \"acquired\" since there's nothing to collide with.\n const wasSet =\n typeof setResult === \"object\" &&\n setResult !== null &&\n \"wasSet\" in setResult\n ? (setResult as CacheSetResult).wasSet\n : true;\n\n if (!wasSet) {\n return { acquired: false };\n }\n\n try {\n const value = await fn();\n return { acquired: true, value };\n } finally {\n // Only delete our own lock — if the TTL expired and a successor\n // acquired the key, its value differs and it is left alone. A release\n // failure (e.g. a Redis blip) is logged, never allowed to replace fn's\n // outcome: the lock still expires by its TTL.\n try {\n await this.deleteIfEquals(key, lockValue);\n } catch (error) {\n this.logError(`lock release failed for ${this.parseKey(key)}`, error);\n }\n }\n }\n\n /**\n * Compare-and-delete: remove `key` only when its current value equals\n * `expected`. Returns whether a delete happened.\n *\n * This default is get-then-remove and is NOT atomic across processes; drivers\n * with a real primitive (memory, redis, pg) override it.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n if ((await this.get(key)) !== expected) {\n return false;\n }\n\n await this.remove(key);\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation throws {@link CacheUnsupportedError}. Drivers that\n * support similarity retrieval (memory family, `pg`, `redis` w/ RediSearch)\n * override this with a real impl.\n */\n public async similar<T = any>(\n _vector: number[],\n _options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n throw new CacheUnsupportedError(\n `'${this.name}' driver does not support similarity retrieval. Use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).`,\n );\n }\n\n /**\n * Resolve the TTL-or-options arg of `lock` into a uniform shape.\n */\n protected normalizeLockOptions(\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n ): {\n ttl: CacheTtl;\n owner?: string;\n } {\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\n return { ttl: ttlOrOptions };\n }\n\n return { ttl: ttlOrOptions.ttl, owner: ttlOrOptions.owner };\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport {\n atomicWriteJsonAsync,\n ensureDirectoryAsync,\n getJsonFileAsync,\n listDirectoriesAsync,\n removeDirectoryAsync,\n} from \"@warlock.js/fs\";\nimport path from \"path\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n FileCacheOptions,\n} from \"../types\";\nimport { CacheConfigurationError, CacheError, CacheUnsupportedError } from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\nexport class FileCacheDriver\n extends BaseCacheDriver<FileCacheDriver, FileCacheOptions>\n implements CacheDriver<FileCacheDriver, FileCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"file\";\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: FileCacheOptions) {\n if (!options.directory) {\n throw new CacheConfigurationError(\n \"File driver requires 'directory' option to be configured.\",\n );\n }\n\n return super.setOptions(options);\n }\n\n /**\n * Get the cache directory\n */\n public get directory() {\n const directory = this.options.directory;\n\n if (typeof directory === \"function\") {\n return directory();\n }\n\n throw new CacheConfigurationError(\n \"Cache directory is not defined, please define it in the file driver options\",\n );\n }\n\n /**\n * Get file name\n */\n public get fileName() {\n const fileName = this.options.fileName;\n\n if (typeof fileName === \"function\") {\n return fileName();\n }\n\n return \"cache.json\";\n }\n\n /**\n * Map a parsed cache key (or namespace) to its on-disk directory.\n *\n * The key must become exactly one directory component: `%` and the path\n * separators are percent-encoded so a hostile key (`../../etc`,\n * `..\\\\..\\\\evil`) turns into an inert directory name instead of a path\n * traversal, while distinct keys can never collide after encoding. Dots are\n * left untouched — the `.`-delimited namespace scheme is purely logical and\n * only ever produces a single filesystem component here.\n */\n protected keyDirectory(parsedKey: string): string {\n // An empty key would resolve to the cache root — `remove(\"{}\")` must never\n // be able to wipe it.\n if (parsedKey === \"\") {\n throw new CacheConfigurationError(\"Cache key must not be empty.\");\n }\n\n return this.containedPath(this.encodeKeySegment(parsedKey));\n }\n\n /**\n * Percent-encode a parsed key into an inert, single-component directory\n * name. Dots are left untouched — see {@link keyDirectory}.\n */\n protected encodeKeySegment(parsedKey: string): string {\n return parsedKey\n .replace(/%/g, \"%25\")\n .replace(/\\//g, \"%2F\")\n .replace(/\\\\/g, \"%5C\");\n }\n\n /**\n * Reverse of {@link encodeKeySegment} — recovers the logical, dot-delimited\n * parsed key from an on-disk directory name. Used by {@link removeNamespace}\n * to test which sibling directories logically belong to a namespace, since\n * dotted keys (`ns.a`) are stored as sibling directories rather than nested\n * ones (see {@link keyDirectory}'s doc comment).\n */\n protected decodeKeySegment(encoded: string): string {\n return encoded\n .replace(/%2F/g, \"/\")\n .replace(/%5C/g, \"\\\\\")\n .replace(/%25/g, \"%\");\n }\n\n /**\n * Resolve `segment` against the cache root and throw when the result lands\n * outside it — the last line of defense against path traversal, independent\n * of how the key was encoded.\n */\n protected containedPath(segment: string): string {\n const base = path.resolve(this.directory);\n const resolved = path.resolve(base, segment);\n const relative = path.relative(base, resolved);\n\n if (\n relative === \"..\" ||\n relative.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relative)\n ) {\n throw new CacheError(\n `Cache key resolves outside the cache directory: \"${segment}\"`,\n );\n }\n\n return resolved;\n }\n\n /**\n * {@inheritdoc}\n *\n * Dotted keys (`ns.a`) are stored as sibling directories, not nested ones\n * (see {@link keyDirectory}) — a directory literally named `<namespace>`\n * rarely exists on its own. Namespace removal therefore has to scan the\n * cache root's immediate children and remove every directory whose\n * *logical* (decoded) name equals the namespace or starts with\n * `<namespace>.`, mirroring the `key = $1 OR key LIKE $2` boundary\n * semantics the `pg` driver already uses for the same contract.\n */\n public async removeNamespace(namespace: string) {\n const parsedNamespace = this.parseKey(namespace);\n\n this.log(\"clearing\", namespace);\n\n try {\n const root = this.containedPath(\"\");\n\n if (parsedNamespace === \"\") {\n await removeDirectoryAsync(root);\n this.log(\"cleared\", namespace);\n return this;\n }\n\n const prefix = `${parsedNamespace}.`;\n const entries = await listDirectoriesAsync(root).catch(() => [] as string[]);\n\n await Promise.all(\n entries.map(async (entryPath) => {\n const decoded = this.decodeKeySegment(path.basename(entryPath));\n\n if (decoded === parsedNamespace || decoded.startsWith(prefix)) {\n await removeDirectoryAsync(entryPath);\n }\n }),\n );\n\n this.log(\"cleared\", namespace);\n } catch (error) {\n //\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * `onConflict: \"create\"` is exclusive (temp file + hard link, so `lock()`\n * is safe across concurrent writers). `onConflict: \"update\"` is NOT atomic:\n * the existence probe and the write are separate steps.\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 throw new CacheUnsupportedError(\n \"'file' driver does not support similarity retrieval — use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).\",\n );\n }\n\n this.log(\"caching\", parsedKey);\n\n const existing = onConflict === \"upsert\" || onConflict === \"create\" ? null : await this.get(key);\n const exists = existing !== null;\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(value, ttl, staleAt);\n\n const fileDirectory = this.keyDirectory(parsedKey);\n\n await ensureDirectoryAsync(fileDirectory);\n\n const filePath = path.resolve(fileDirectory, this.fileName);\n\n if (onConflict === \"create\") {\n const conflict = await this.writeExclusive(filePath, data);\n\n if (conflict) {\n const result: CacheSetResult = { wasSet: false, existing: conflict.existing };\n return result;\n }\n } else {\n await atomicWriteJsonAsync(filePath, data);\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * Create `filePath` only if it does not exist. Writes a temp file, then\n * hard-links it to the target (EEXIST = already present). An existing but\n * expired entry is removed (the file only, never the directory) and the\n * link retried once.\n *\n * Returns `null` when written, or `{ existing }` on conflict.\n */\n protected async writeExclusive(\n filePath: string,\n data: CacheData,\n ): Promise<{ existing: any } | null> {\n const tempPath = `${filePath}.${randomBytes(6).toString(\"hex\")}.tmp`;\n\n try {\n await fs.promises.writeFile(tempPath, JSON.stringify(data, null, 2));\n\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n await fs.promises.link(tempPath, filePath);\n return null;\n } catch (error: any) {\n if (error?.code !== \"EEXIST\") {\n throw error;\n }\n }\n\n let current: CacheData | undefined;\n\n try {\n current = (await getJsonFileAsync(filePath)) as CacheData | undefined;\n } catch {\n current = undefined;\n }\n\n const expired =\n current?.expiresAt !== undefined && current.expiresAt !== null && current.expiresAt <= Date.now();\n\n if (current && !expired) {\n return { existing: current.data ?? null };\n }\n\n if (attempt === 0 && expired) {\n await fs.promises.rm(filePath, { force: true });\n continue;\n }\n\n // Unreadable (corrupt) file: treat as held, never delete it.\n return { existing: null };\n }\n\n return { existing: null };\n } finally {\n await fs.promises.unlink(tempPath).catch(() => undefined);\n }\n }\n\n /**\n * {@inheritdoc}\n *\n * File driver does not yet ship with a file-lock primitive, so concurrent\n * writers could clobber each other. Rather than ship an unsafe default, we\n * throw — consumers can fall back to memory/redis for `update` until a\n * proper file lock lands (tracked in `domains/cache/backlog.md`).\n */\n public async update(): Promise<never> {\n throw new CacheUnsupportedError(\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).\",\n );\n }\n\n /**\n * {@inheritdoc}\n */\n public async merge(): Promise<never> {\n throw new CacheUnsupportedError(\n \"`merge()` is not supported on the file driver. Use the memory or redis driver.\",\n );\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from disk, including `staleAt`\n * metadata. Returns `null` for missing or expired files — `swr()`\n * consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const fileDirectory = this.keyDirectory(parsedKey);\n\n try {\n const entry = (await getJsonFileAsync(path.resolve(fileDirectory, this.fileName))) as\n | CacheData\n | undefined;\n\n if (!entry) {\n return null;\n }\n\n // `Infinity` (no expiry) is written to JSON as `null`; `null <= now`\n // is true, so only a real deadline may count as expired.\n if (typeof entry.expiresAt === \"number\" && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return entry;\n } catch {\n return null;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const fileDirectory = this.keyDirectory(parsedKey);\n\n let value: CacheData | undefined;\n\n try {\n value = (await getJsonFileAsync(path.resolve(fileDirectory, this.fileName))) as\n | CacheData\n | undefined;\n } catch {\n // Missing file (ENOENT) or corrupt JSON: a plain miss. Never remove the\n // directory here — it may be mid-write by another process.\n value = undefined;\n }\n\n if (value === undefined || value === null || typeof value !== \"object\") {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n // Only a successfully parsed but expired entry is removed (inside\n // parseCachedData).\n const result = await this.parseCachedData(parsedKey, value);\n\n if (result === null) {\n await this.emit(\"miss\", { key: parsedKey });\n } else {\n await this.emit(\"hit\", { key: parsedKey, value: result });\n }\n\n return result;\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 fileDirectory = this.keyDirectory(parsedKey);\n\n try {\n await removeDirectoryAsync(fileDirectory);\n\n this.log(\"removed\", parsedKey);\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n } catch (error) {\n //\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n await removeDirectoryAsync(this.directory);\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n this.log(\"connecting\");\n await ensureDirectoryAsync(this.directory);\n this.log(\"connected\");\n await this.emit(\"connected\");\n }\n}\n","/**\n * Tag index for the in-process drivers (memory, memoryExtended, lru, mock).\n *\n * Lives beside the driver's entry storage, never inside it: index sets are not\n * entries, so they are never subject to `maxSize` / LRU eviction or TTL\n * sweeps, and every operation is synchronous — concurrent tagged writes in one\n * process can never drop a member.\n *\n * Keys are the driver-parsed tag keys (`<prefix>.cache.tags.<tag>`); members\n * are the caller's normalized, un-prefixed cache keys.\n */\nexport class InMemoryTagIndex {\n protected readonly sets: Map<string, Set<string>> = new Map();\n\n /**\n * Add members to a tag set, creating it on first use.\n */\n public add(tagKey: string, members: readonly string[]): void {\n if (members.length === 0) {\n return;\n }\n\n let set = this.sets.get(tagKey);\n\n if (!set) {\n set = new Set();\n this.sets.set(tagKey, set);\n }\n\n for (const member of members) {\n set.add(member);\n }\n }\n\n /**\n * The members of a tag set (a copy; empty when the set does not exist).\n */\n public members(tagKey: string): string[] {\n return [...(this.sets.get(tagKey) ?? [])];\n }\n\n /**\n * Remove members from a tag set; an emptied set is dropped.\n */\n public remove(tagKey: string, members: readonly string[]): void {\n const set = this.sets.get(tagKey);\n\n if (!set) {\n return;\n }\n\n for (const member of members) {\n set.delete(member);\n }\n\n if (set.size === 0) {\n this.sets.delete(tagKey);\n }\n }\n\n /**\n * Drop a whole tag set.\n */\n public delete(tagKey: string): void {\n this.sets.delete(tagKey);\n }\n\n /**\n * Drop every tag set whose key equals `namespace` or sits under\n * `namespace.` — the same rule the drivers use for entries. An empty\n * namespace clears everything.\n */\n public removeNamespace(namespace: string): void {\n if (namespace === \"\") {\n this.sets.clear();\n\n return;\n }\n\n const prefix = namespace + \".\";\n\n for (const key of [...this.sets.keys()]) {\n if (key === namespace || key.startsWith(prefix)) {\n this.sets.delete(key);\n }\n }\n }\n\n /**\n * Drop every tag set.\n */\n public clear(): void {\n this.sets.clear();\n }\n}\n","import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n LRUMemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\nclass CacheNode {\n public next: CacheNode | null = null;\n public prev: CacheNode | null = null;\n public expiresAt?: number;\n /**\n * Freshness deadline (ms timestamp) — populated by `swr()`. Within\n * `expiresAt > now > staleAt` the entry is \"stale-but-revalidatable.\"\n */\n public staleAt?: number;\n /**\n * Optional embedding vector — populated when the entry was written with\n * `set({ vector })`. Scanned by `similar()`.\n */\n public vector?: number[];\n public constructor(\n public key: string,\n public value: any,\n ttl?: number,\n ) {\n if (ttl && ttl !== Infinity) {\n this.expiresAt = Date.now() + ttl * 1000;\n }\n }\n\n public get isExpired(): boolean {\n return this.expiresAt !== undefined && this.expiresAt < Date.now();\n }\n}\n\n/**\n * LRU Memory Cache Driver\n * The concept of LRU is to remove the least recently used data\n * whenever the cache is full\n * The question that resides here is how to tell the cache is full?\n */\nexport class LRUMemoryCacheDriver\n extends BaseCacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n implements CacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"lru\";\n\n /**\n * Cache map\n */\n protected cache: Map<string, CacheNode> = new Map();\n\n /**\n * Tag index, kept out of the LRU list so capacity pressure can never evict it.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Head of the cache\n */\n protected head: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Tail of the cache\n */\n protected tail: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.init();\n this.startCleanup();\n }\n\n /**\n * Initialize the cache\n */\n public init() {\n this.head.next = this.tail;\n this.tail.prev = this.head;\n }\n\n /**\n * Start the cleanup process for expired items\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, node] of this.cache) {\n if (node.expiresAt && node.expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * {@inheritdoc}\n *\n * Clears every entry whose key starts with the parsed namespace (followed\n * by a dot) or equals it exactly. Called with an empty namespace while a\n * `globalPrefix` is configured, clears everything under the prefix — which\n * is how `flush()` scopes cleanup per tenant.\n */\n public async removeNamespace(namespace: string) {\n const parsedNamespace = this.parseKey(namespace);\n\n this.log(\"clearing\", parsedNamespace || \"(all)\");\n\n const removed: string[] = [];\n\n this.tagIndex.removeNamespace(parsedNamespace);\n\n if (parsedNamespace === \"\") {\n for (const key of this.cache.keys()) {\n removed.push(key);\n }\n } else {\n const prefix = parsedNamespace + \".\";\n\n for (const key of this.cache.keys()) {\n if (key === parsedNamespace || key.startsWith(prefix)) {\n removed.push(key);\n }\n }\n }\n\n for (const key of removed) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n }\n await this.emit(\"removed\", { key });\n }\n\n this.log(\"cleared\", parsedNamespace || \"(all)\");\n\n return removed;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // For conditional writes, check existence via get() so expired entries\n // are treated as missing (get() handles expiry cleanup). The raw `cache.get`\n // node lookup would let stale entries block onConflict: \"create\".\n let existingNode = this.cache.get(parsedKey);\n if (existingNode && existingNode.isExpired) {\n this.removeNode(existingNode);\n this.cache.delete(parsedKey);\n existingNode = undefined;\n }\n\n const exists = Boolean(existingNode);\n\n if (onConflict === \"create\" && exists) {\n const result: CacheSetResult = {\n wasSet: false,\n existing: existingNode!.value,\n };\n return result;\n }\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n if (existingNode) {\n existingNode.value = value;\n if (ttl && ttl !== Infinity) {\n existingNode.expiresAt = Date.now() + ttl * 1000;\n } else {\n existingNode.expiresAt = undefined;\n }\n existingNode.staleAt = staleAt;\n if (vector) {\n existingNode.vector = vector.slice();\n }\n\n this.moveHead(existingNode);\n } else {\n const newNode = new CacheNode(parsedKey, value, ttl);\n newNode.staleAt = staleAt;\n if (vector) {\n newNode.vector = vector.slice();\n }\n\n this.cache.set(parsedKey, newNode);\n\n this.addNode(newNode);\n if (this.cache.size > this.capacity) {\n this.removeTail();\n }\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * Move the node to the head\n */\n protected moveHead(node: CacheNode) {\n this.removeNode(node);\n this.addNode(node);\n }\n\n /**\n * Remove the node from the cache\n */\n protected removeNode(node: CacheNode) {\n node.prev!.next = node.next;\n node.next!.prev = node.prev;\n }\n\n /**\n * Add the node to the head\n */\n protected addNode(node: CacheNode) {\n node.next = this.head.next;\n node.prev = this.head;\n this.head.next!.prev = node;\n this.head.next = node;\n }\n\n /**\n * Remove the tail node\n */\n protected removeTail() {\n const node = this.tail.prev!;\n\n this.removeNode(node);\n\n this.cache.delete(node.key);\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\n * Returns `null` for missing or expired nodes — `swr()` consumes this\n * to branch on freshness without going through `get()`'s clone-and-emit\n * path.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const node = this.cache.get(parsedKey);\n\n if (!node || node.isExpired) {\n return null;\n }\n\n return {\n data: node.value,\n expiresAt: node.expiresAt,\n staleAt: node.staleAt,\n };\n }\n\n /**\n * Live node for `parsedKey`, or `undefined` when missing / expired (an\n * expired node is unlinked). Synchronous.\n */\n protected liveNode(parsedKey: string): CacheNode | undefined {\n const node = this.cache.get(parsedKey);\n\n if (!node) {\n return undefined;\n }\n\n if (node.isExpired) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n\n return undefined;\n }\n\n return node;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * node keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n const node = this.liveNode(parsedKey);\n const current = node ? node.value : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (node) {\n node.value = newValue;\n this.moveHead(node);\n } else {\n const newNode = new CacheNode(parsedKey, newValue, this.ttl);\n\n this.cache.set(parsedKey, newNode);\n this.addNode(newNode);\n\n if (this.cache.size > this.capacity) {\n this.removeTail();\n }\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: this.ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and unlink run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const node = this.liveNode(parsedKey);\n\n if (!node) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.removeNode(node);\n this.cache.delete(parsedKey);\n\n const value =\n node.value !== null && typeof node.value === \"object\"\n ? structuredClone(node.value)\n : node.value;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (!node) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n // Check if expired\n if (node.isExpired) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n this.log(\"expired\", parsedKey);\n // Emit expired event\n await this.emit(\"expired\", { key: parsedKey });\n // Also emit miss since we're returning null\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.moveHead(node);\n\n this.log(\"fetched\", parsedKey);\n\n const value = node.value;\n\n // Apply cloning for immutability protection\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n try {\n const clonedValue = structuredClone(value);\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: clonedValue });\n return clonedValue;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\n throw error;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (node) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n }\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n *\n * When a `globalPrefix` is configured, `flush` scopes itself to that prefix\n * so multi-tenant caches don't accidentally wipe sibling tenants. Without\n * a prefix, clears everything.\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.cache.clear();\n this.tagIndex.clear();\n this.init();\n }\n\n this.log(\"flushed\");\n\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every cached node that carries a\n * vector. Suitable for development and small in-memory knowledge bases —\n * not for production beyond ~10k entries.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, node] of this.cache) {\n if (!node.vector) continue;\n if (node.isExpired) continue;\n if (tagFilter && !tagFilter.has(parsedKey)) continue;\n\n const score = cosineSimilarity(vector, node.vector);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n // Clone object values to match get() semantics.\n let value: any = node.value;\n if (value !== null && value !== undefined) {\n const t = typeof value;\n if (t !== \"string\" && t !== \"number\" && t !== \"boolean\") {\n value = structuredClone(value);\n }\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * Get lru capacity\n */\n public get capacity() {\n return this.options.capacity || 1000;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n","import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n MemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity, parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * Clone non-primitive values so cached state can't be mutated through\n * references held by callers. Primitives are immutable and returned as-is.\n */\nfunction cloneValue<T>(value: T): T {\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n return value;\n }\n\n return structuredClone(value);\n}\n\nexport class MemoryCacheDriver\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memory\";\n\n /**\n * Flat storage keyed by the parsed key. Insertion order doubles as LRU\n * order (least recently used first) — accessed entries are deleted and\n * re-inserted.\n */\n protected entries: Map<string, CacheData> = new Map();\n\n /**\n * Read-only object view of the stored entries, keyed by parsed key.\n * Kept for introspection; the values are the live stored wrappers.\n */\n public get data(): Record<string, CacheData> {\n return Object.fromEntries(this.entries);\n }\n\n /**\n * Expiry records for entries with a FINITE ttl only (parsed key => expiresAt ms).\n * The sweep iterates just these.\n */\n protected expiry: Map<string, number> = new Map();\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\n * `remove`, `flush`, expiry, namespace clear, overwrite without a vector,\n * and LRU eviction.\n */\n protected vectorIndex: Map<string, number[]> = new Map();\n\n /**\n * Tag index, kept apart from `entries`: never evicted by `maxSize`, never\n * swept by TTL, and updated synchronously.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.startCleanup();\n }\n\n /**\n * Start the cleanup process for entries that carry a finite ttl\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, expiresAt] of this.expiry) {\n if (expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n // the entry may have been rewritten/slid since the snapshot\n const expiresAt = this.expiry.get(key);\n\n if (expiresAt === undefined || expiresAt > now) {\n continue;\n }\n\n this.dropEntry(key);\n\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * Delete an entry and every side record (expiry, vector). Synchronous.\n */\n protected dropEntry(parsedKey: string) {\n this.entries.delete(parsedKey);\n this.expiry.delete(parsedKey);\n this.vectorIndex.delete(parsedKey);\n }\n\n /**\n * Raw, synchronous read of a live entry. An expired entry is deleted and\n * reported as missing. Never slides TTLs, never emits, never touches LRU order.\n */\n protected readLive(parsedKey: string): CacheData | undefined {\n const entry = this.entries.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.dropEntry(parsedKey);\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Mark an entry as most recently used (only tracked when `maxSize` is set).\n */\n protected touch(parsedKey: string, entry: CacheData) {\n if (!this.options.maxSize) return;\n\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, entry);\n }\n\n /**\n * When true, every live read slides the entry's expiration forward by its\n * ttl (the `memoryExtended` driver). Set in a subclass constructor rather\n * than redeclared, so subclasses stay structurally compatible with this\n * class (the `client` type relies on that).\n */\n protected slidingExpiration = false;\n\n /**\n * Invoked on a live read hit, before the value is returned. Runs only from\n * `get()` after the expiry check, so an expired entry is never resurrected,\n * and never from the internal existence check used by `onConflict`.\n */\n protected onRead(parsedKey: string, entry: CacheData) {\n if (!this.slidingExpiration) {\n return;\n }\n\n const rawTtl = entry.ttl ?? this.options.ttl;\n const ttl = rawTtl !== undefined ? parseTtl(rawTtl) : undefined;\n\n if (ttl && Number.isFinite(ttl)) {\n entry.expiresAt = this.getExpiresAt(ttl);\n this.expiry.set(parsedKey, entry.expiresAt as number);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.log(\"clearing\", namespace);\n\n namespace = this.parseKey(namespace);\n\n this.tagIndex.removeNamespace(namespace);\n\n if (namespace === \"\") {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n } else {\n const prefix = namespace + \".\";\n\n for (const key of [...this.entries.keys()]) {\n if (key === namespace || key.startsWith(prefix)) {\n this.dropEntry(key);\n }\n }\n }\n\n this.log(\"cleared\", namespace);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The conditional check and the write run with no `await` in between, so\n * `onConflict` decisions are atomic within the process.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // expired entries count as missing (readLive deletes them)\n const existing = onConflict === \"create\" || onConflict === \"update\" ? this.readLive(parsedKey) : undefined;\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: cloneValue(existing.data) };\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(cloneValue(value), ttl, staleAt);\n\n // delete + set moves the key to the most-recent position\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n } else {\n this.expiry.delete(parsedKey);\n }\n\n if (vector) {\n this.vectorIndex.set(parsedKey, vector.slice());\n } else {\n this.vectorIndex.delete(parsedKey);\n }\n\n this.enforceMaxSize(parsedKey);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The check and the delete run synchronously (no `await` between them), so\n * the compare-and-delete is atomic within the process.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry || entry.data !== expected) {\n return false;\n }\n\n await this.remove(key);\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * entry keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n this.touch(parsedKey, entry);\n } else {\n const data = this.prepareDataForStorage(newValue, this.ttl);\n\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n }\n\n this.enforceMaxSize(parsedKey);\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: entry?.ttl ?? this.ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and delete run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.dropEntry(parsedKey);\n\n const value = cloneValue(entry.data);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.onRead(parsedKey, entry);\n this.touch(parsedKey, entry);\n\n const result = await this.parseCachedData(parsedKey, entry);\n\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: result });\n\n return result;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper (cloned), including `staleAt` metadata.\n * Returns `null` for missing or expired entries so the SWR flow can branch\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const entry = this.readLive(this.parseKey(key));\n\n if (!entry) {\n return null;\n }\n\n return { ...entry, data: cloneValue(entry.data) };\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n this.dropEntry(parsedKey);\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set — concurrent tagged writes never drop members.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n this.tagIndex.clear();\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * Evict least recently used entries (Map insertion order) until the size is\n * within `maxSize`. Never evicts `protectedKey` (the key just written).\n */\n protected enforceMaxSize(protectedKey: string) {\n const maxSize = this.options.maxSize;\n\n if (!maxSize) {\n return;\n }\n\n for (const lruKey of [...this.entries.keys()]) {\n if (this.entries.size <= maxSize) {\n break;\n }\n\n if (lruKey === protectedKey) {\n continue;\n }\n\n this.log(\"removing\", lruKey);\n this.dropEntry(lruKey);\n this.log(\"removed\", lruKey);\n }\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every entry that was written with\n * `set({ vector })`. Suitable for development and small in-memory knowledge\n * bases — not for production beyond ~10k entries. Use the `pg` driver\n * (with pgvector) or `redis` (with RediSearch) at scale.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, stored] of [...this.vectorIndex]) {\n if (tagFilter && !tagFilter.has(parsedKey)) {\n continue;\n }\n\n // Read the entry directly by its already-parsed key — this.get() would\n // parse it again and apply globalPrefix twice, missing every entry.\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n continue;\n }\n\n const value: any = cloneValue(entry.data);\n\n const score = cosineSimilarity(vector, stored);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n","import type { CacheDriver, MemoryExtendedCacheOptions } from \"../types\";\nimport { MemoryCacheDriver } from \"./memory-cache-driver\";\n\n/**\n * Memory driver with a sliding ttl: every live read pushes the entry's\n * expiration forward by its ttl. The sliding itself lives in\n * `MemoryCacheDriver.onRead`, which checks expiry first, so an expired entry\n * is never resurrected and the internal `onConflict` existence check never\n * slides a held lock.\n */\nexport class MemoryExtendedCacheDriver\n extends MemoryCacheDriver\n implements CacheDriver<MemoryExtendedCacheDriver, MemoryExtendedCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memoryExtended\";\n\n public constructor() {\n super();\n\n this.slidingExpiration = true;\n }\n}\n","import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheCall,\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n MockCacheOptions,\n} from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * In-memory cache driver with introspection helpers, intended for use as a\n * test double in downstream packages.\n *\n * **Role.** Drop-in replacement for any real driver in test setups, with\n * extra surface that makes behavioral assertions easy: every public op gets\n * recorded into {@link MockCacheDriver.callLog}, and {@link wasCalled} /\n * {@link getStored} / {@link reset} let tests verify side effects without\n * pulling in a real Redis / Postgres / file system.\n *\n * **Responsibility.**\n * - Owns: in-memory storage backed by a `Map`, the `callLog`, TTL handling\n * via `parseCachedData`, `onConflict` policies, and tag-index storage.\n * - Does NOT own: similarity retrieval (vectors are recorded into the\n * `callLog` but `similar()` throws — use `MemoryCacheDriver` for tests\n * that need real nearest-neighbor scoring), connection lifecycle\n * (no-op `connect`/`disconnect`), or eviction (no `maxSize`).\n *\n * Register it like any other driver — sub-paths are not part of the\n * package's export convention; the same single barrel ships it next to\n * the production drivers.\n *\n * @example\n * import { cache, MockCacheDriver } from \"@warlock.js/cache\";\n *\n * beforeEach(async () => {\n * cache.setCacheConfigurations({\n * default: \"mock\",\n * drivers: { mock: MockCacheDriver },\n * options: { mock: {} },\n * });\n * await cache.init();\n * });\n *\n * it(\"invalidates the user cache after update\", async () => {\n * await userService.update(42, { name: \"Jane\" });\n *\n * const driver = cache.currentDriver as MockCacheDriver;\n * expect(driver.wasCalled(\"remove\", \"users.42\")).toBe(true);\n * });\n */\nexport class MockCacheDriver\n extends BaseCacheDriver<MockCacheDriver, MockCacheOptions>\n implements CacheDriver<MockCacheDriver, MockCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"mock\";\n\n /**\n * {@inheritdoc}\n */\n public options: MockCacheOptions = {};\n\n /**\n * Storage backing the mock — keyed by post-`parseKey` string. Public-readonly\n * so tests can introspect raw entries when {@link getStored} isn't enough.\n */\n public readonly storage: Map<string, CacheData> = new Map();\n\n /**\n * Tag index, kept apart from {@link storage} (same model as the memory driver).\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Ordered record of every public operation routed through this driver.\n * Pushed to before each op runs; tests assert via {@link wasCalled} or by\n * inspecting the array directly.\n */\n public readonly callLog: CacheCall[] = [];\n\n /**\n * Standard driver setup. Mirrors the null driver — no connection, no\n * resources to release.\n */\n public async connect(): Promise<void> {\n this.recordCall(\"connect\", undefined);\n await super.connect();\n }\n\n /**\n * Standard driver teardown.\n */\n public async disconnect(): Promise<void> {\n this.recordCall(\"disconnect\", undefined);\n await super.disconnect();\n }\n\n /**\n * Wipe everything under `namespace`. Matches `MemoryCacheDriver` semantics\n * — the namespace itself and any key with the namespace prefix is removed.\n */\n public async removeNamespace(namespace: string): Promise<void> {\n const parsed = this.parseKey(namespace);\n\n this.recordCall(\"removeNamespace\", parsed, [namespace]);\n this.log(\"clearing\", parsed);\n\n const prefix = parsed + \".\";\n\n this.tagIndex.removeNamespace(parsed);\n\n for (const key of [...this.storage.keys()]) {\n if (key === parsed || key.startsWith(prefix)) {\n this.storage.delete(key);\n }\n }\n\n this.log(\"cleared\", parsed);\n }\n\n /**\n * Standard `set` with full `onConflict` support. Honors scope-default TTL\n * via {@link BaseCacheDriver.resolveSetOptions}.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.recordCall(\"set\", parsedKey, [value, ttlOrOptions]);\n this.log(\"caching\", parsedKey);\n\n // Synchronous check + write (no await between) so onConflict is atomic.\n // Expired entries count as missing and are deleted.\n let existing = onConflict === \"create\" || onConflict === \"update\"\n ? this.storage.get(parsedKey)\n : undefined;\n\n if (existing && existing.expiresAt !== undefined && existing.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n existing = undefined;\n }\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: this.cloneValue(existing.data) };\n\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n\n return result;\n }\n\n const data = this.prepareDataForStorage(this.cloneValue(value), ttl, staleAt);\n this.storage.set(parsedKey, data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n\n return result;\n }\n\n return this;\n }\n\n /**\n * Clone non-primitive values so stored state can't be mutated by callers.\n */\n protected cloneValue<T>(value: T): T {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return value;\n }\n\n return structuredClone(value);\n }\n\n /**\n * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep\n * downstream metrics tests realistic.\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"get\", parsedKey);\n this.log(\"fetching\", parsedKey);\n\n const data = this.storage.get(parsedKey);\n\n if (!data) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = await this.parseCachedData(parsedKey, data);\n\n if (value === null) {\n // expired — the parse helper already logged + queued the cleanup\n this.storage.delete(parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.emit(\"hit\", { key: parsedKey, value });\n\n return value as T;\n }\n\n /**\n * Live entry for `parsedKey`, or `undefined` when missing / expired (an\n * expired entry is deleted). Synchronous.\n */\n protected liveEntry(parsedKey: string): CacheData | undefined {\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Synchronous read-add-write (atomic within the process). Keeps an existing\n * entry's expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"increment\", parsedKey, [value]);\n\n const entry = this.liveEntry(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n } else {\n this.storage.set(parsedKey, this.prepareDataForStorage(newValue, this.ttl));\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: this.ttl });\n\n return newValue;\n }\n\n /**\n * Synchronous read-and-delete: a value is handed out exactly once.\n */\n public async pull<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"pull\", parsedKey);\n\n const entry = this.liveEntry(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.storage.delete(parsedKey);\n\n const value = this.cloneValue(entry.data) as T;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from the in-memory `Map`,\n * including `staleAt` metadata. Returns `null` for missing or expired\n * entries — `swr()` consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return { ...entry, data: this.cloneValue(entry.data) };\n }\n\n /**\n * Standard `remove` — drops the entry and emits the `removed` event.\n */\n public async remove(key: CacheKey): Promise<void> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"remove\", parsedKey);\n this.log(\"removing\", parsedKey);\n\n this.storage.delete(parsedKey);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * Standard `flush` — wipes the entire mock store + tag index. Does NOT\n * touch the call log; use {@link reset} to clear that as well.\n */\n public async flush(): Promise<void> {\n this.recordCall(\"flush\", undefined);\n this.log(\"flushing\");\n\n this.storage.clear();\n this.tagIndex.clear();\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * Was a given operation invoked? When `key` is provided, the match is\n * post-`parseKey` so callers pass the same key shape they used at the\n * call site — strings or objects, both resolve to the same parsed key.\n *\n * @example\n * driver.wasCalled(\"set\"); // any set\n * driver.wasCalled(\"set\", \"users.42\"); // set on this specific key\n * driver.wasCalled(\"set\", { id: 42 }); // same — object key normalized\n */\n public wasCalled(operation: string, key?: CacheKey): boolean {\n if (key === undefined) {\n return this.callLog.some((call) => call.operation === operation);\n }\n\n const parsedKey = this.parseKey(key);\n\n return this.callLog.some(\n (call) => call.operation === operation && call.key === parsedKey,\n );\n }\n\n /**\n * Return the raw stored value for `key`, bypassing TTL handling and clone\n * protection. Useful when a test wants to assert on the persisted shape\n * (or assert that an entry expired without going through `get`).\n *\n * Returns `undefined` when the key isn't present.\n */\n public getStored<T = any>(key: CacheKey): T | undefined {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n return entry.data as T;\n }\n\n /**\n * Wipe everything — storage, tag index, and the call log. Pair with\n * Vitest's `beforeEach` to get clean isolation between tests.\n */\n public reset(): void {\n this.storage.clear();\n this.tagIndex.clear();\n this.callLog.length = 0;\n }\n\n /**\n * Append a row to {@link callLog}. Internal helper called by every\n * recorded op before the actual work runs.\n */\n protected recordCall(\n operation: string,\n key: string | undefined,\n args: unknown[] = [],\n ): void {\n this.callLog.push({\n operation,\n key,\n args,\n timestamp: Date.now(),\n });\n }\n}\n","import type { GenericObject } from \"@mongez/reinforcements\";\nimport { log, maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheTtl,\n NullCacheDriverOptions,\n RememberOptions,\n} from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\nexport class NullCacheDriver\n extends BaseCacheDriver<NullCacheDriver, NullCacheDriverOptions>\n implements CacheDriver<NullCacheDriver, NullCacheDriverOptions>\n{\n /**\n * Options list\n */\n public options: GenericObject = {};\n\n /**\n * {@inheritdoc}\n */\n public name = \"null\";\n\n /**\n * Cached data\n */\n public data: GenericObject = {};\n\n /**\n * {@inheritdoc}\n */\n public get client() {\n return this;\n }\n\n /**\n * Constructor\n */\n public constructor(options: GenericObject = {}) {\n super();\n this.setOptions(options);\n }\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: GenericObject) {\n this.options = options;\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public parseKey(key: CacheKey) {\n // Real parsed key so the base lock map never merges distinct keys.\n return super.parseKey(key);\n }\n\n /**\n * {@inheritdoc}\n *\n * Nothing is stored, so always run the callback and return its own result.\n */\n public async remember(\n _key: CacheKey,\n _ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<any>,\n ): Promise<any> {\n return callback();\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n const loggableNamespace = maskEmailsInText(namespace);\n\n log.info(\"cache\", \"clearing namespace\", loggableNamespace);\n\n log.success(\"cache\", \"namespace cleared\", loggableNamespace);\n\n return this;\n }\n\n /**\n * The key as it may appear in a log line: parsed, with email addresses masked\n */\n protected loggableKey(key: CacheKey): string {\n return maskEmailsInText(this.parseKey(key));\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n _value: any,\n _ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n log.info(\"cache\", \"setting key\", this.loggableKey(key));\n\n log.success(\"cache\", \"key set\", this.loggableKey(key));\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n log.info(\"cache\", \"fetching\", this.loggableKey(key));\n\n log.success(\"cache\", \"fetched\", this.loggableKey(key));\n\n return null;\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n log.info(\"cache\", \"removing\", this.loggableKey(key));\n\n log.success(\"cache\", \"removed\", this.loggableKey(key));\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n log.info(\"cache\", \"flushing\", \"all\");\n\n log.success(\"cache\", \"flushed\", \"all\");\n }\n\n /**\n * {@inheritdoc}\n *\n * The null driver is a black-hole — `similar()` mirrors `get()` and returns\n * an empty result set rather than throwing.\n */\n public async similar(): Promise<any[]> {\n return [];\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n log.success(\"cache\", \"connected\", \"Connected to null cache driver\");\n }\n}\n","import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n PgCacheOptions,\n PgClientLike,\n} from \"../types\";\nimport { CacheConfigurationError, CacheError, CacheUnsupportedError } from \"../types\";\nimport { parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\n/** Compare-and-set attempts before `update()` gives up under contention. */\nconst PG_UPDATE_MAX_ATTEMPTS = 10;\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 * {@inheritdoc}\n *\n * Single `DELETE … WHERE key AND value` — atomic in Postgres.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n\n const res = await this.pgClient.query(\n `DELETE FROM ${this.table}\n WHERE key = $1 AND value = $2::jsonb AND (expires_at IS NULL OR expires_at > now())\n RETURNING 1`,\n [parsedKey, JSON.stringify(expected)],\n );\n\n if (res.rows.length === 0) {\n return false;\n }\n\n await this.emit(\"removed\", { key: parsedKey });\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * One `INSERT … ON CONFLICT DO UPDATE` that unions the stored JSON array with\n * `members`. Postgres row-locks the conflicting row, so concurrent adders on\n * any number of servers serialize and none is lost.\n *\n * The index stays a row of the main table holding a JSON array — the same\n * shape earlier versions wrote — so no schema change or migration is needed\n * and existing indexes keep working. (The `tags` column only records tags\n * passed inline to `set()`, not relationships added afterwards, so it can't\n * serve as the invalidation index.)\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n const t = this.table;\n\n await this.pgClient.query(\n `/* cache:tag-add */ INSERT INTO ${t} (key, value)\n VALUES ($1, to_jsonb($2::text[]))\n ON CONFLICT (key) DO UPDATE\n SET value = (\n SELECT COALESCE(jsonb_agg(u.member), '[]'::jsonb)\n FROM (\n SELECT x.member FROM jsonb_array_elements_text(\n CASE WHEN jsonb_typeof(${t}.value) = 'array' THEN ${t}.value ELSE '[]'::jsonb END\n ) AS x(member)\n UNION\n SELECT y.member FROM unnest($2::text[]) AS y(member)\n ) AS u\n ),\n expires_at = NULL`,\n [this.parseKey(tagKey), members],\n );\n }\n\n /**\n * {@inheritdoc}\n *\n * One `UPDATE` that filters `members` out of the stored array (members added\n * concurrently are kept), then a conditional `DELETE` of the row if it ended\n * up empty.\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n const t = this.table;\n const parsedKey = this.parseKey(tagKey);\n\n await this.pgClient.query(\n `/* cache:tag-remove */ UPDATE ${t}\n SET value = (\n SELECT COALESCE(jsonb_agg(x.member), '[]'::jsonb)\n FROM jsonb_array_elements_text(\n CASE WHEN jsonb_typeof(${t}.value) = 'array' THEN ${t}.value ELSE '[]'::jsonb END\n ) AS x(member)\n WHERE x.member <> ALL($2::text[])\n )\n WHERE key = $1`,\n [parsedKey, members],\n );\n\n await this.pgClient.query(\n `/* cache:tag-drop-empty */ DELETE FROM ${t} WHERE key = $1 AND value = '[]'::jsonb`,\n [parsedKey],\n );\n }\n\n /**\n * TTL in seconds bound to `now() + make_interval(secs => $n)`, so expiry is\n * computed on the DB clock (immune to app/DB clock skew). `null` — from\n * `Infinity` / 0 / undefined — yields a NULL `expires_at` (never expires).\n */\n protected ttlToSeconds(ttl?: number): number | null {\n if (!ttl || ttl === Infinity) {\n return null;\n }\n\n return ttl;\n }\n\n /**\n * Delete expired rows in batches of `limit`. Returns how many were removed.\n * Also invoked opportunistically (~1 in 200 writes) by `set()`.\n */\n public async prune(limit = 1000): Promise<number> {\n const t = this.table;\n const res = await this.pgClient.query(\n `DELETE FROM ${t} WHERE ctid IN (SELECT ctid FROM ${t} WHERE expires_at < now() LIMIT $1)`,\n [limit],\n );\n\n return res.rowCount ?? 0;\n }\n\n /**\n * Fire-and-forget `prune()` on roughly 1 in 200 writes; errors are logged.\n */\n protected maybePrune(): void {\n if (Math.random() >= 1 / 200) {\n return;\n }\n\n this.prune().catch(error => this.logError(\"prune failed\", error));\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.ttlToSeconds(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 = ttl seconds (expires_at = now() + make_interval), $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 = [\n \"$1\",\n \"$2::jsonb\",\n \"now() + make_interval(secs => $3::double precision)\",\n \"$4\",\n \"$5\",\n ];\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(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n this.maybePrune();\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(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n this.maybePrune();\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(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n this.maybePrune();\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 * Decode a JSONB column value (node-postgres usually parses it already;\n * some pools hand back a string).\n */\n protected decodeJson(value: unknown): any {\n if (typeof value === \"string\") {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n *\n * One atomic `INSERT … ON CONFLICT DO UPDATE` statement, so concurrent\n * increments from any number of servers never lose an update. A live row\n * keeps its `expires_at`; a missing or expired row restarts from `value`\n * with the driver default TTL. A non-numeric live value is rejected.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n const t = this.table;\n const expired = `${t}.expires_at IS NOT NULL AND ${t}.expires_at <= now()`;\n\n const { rows } = await this.pgClient.query(\n `/* cache:increment */ INSERT INTO ${t}(key, value, expires_at)\n VALUES ($1, to_jsonb($2::numeric), now() + make_interval(secs => $3::double precision))\n ON CONFLICT (key) DO UPDATE\n SET value = CASE WHEN ${expired} THEN EXCLUDED.value\n ELSE to_jsonb((${t}.value #>> '{}')::numeric + $2::numeric) END,\n expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at ELSE ${t}.expires_at END,\n stale_at = CASE WHEN ${expired} THEN NULL ELSE ${t}.stale_at END\n WHERE (${expired}) OR jsonb_typeof(${t}.value) = 'number'\n RETURNING value`,\n [parsedKey, value, this.ttlToSeconds(this.ttl)],\n );\n\n if (rows.length === 0) {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = Number(this.decodeJson(rows[0].value));\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: this.ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * One atomic `DELETE … RETURNING`, so a value is handed out exactly once\n * across servers. Expired rows count as missing.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n\n const { rows } = await this.pgClient.query(\n `/* cache:pull */ DELETE FROM ${this.table}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())\n RETURNING value`,\n [parsedKey],\n );\n\n if (rows.length === 0) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = this.decodeJson(rows[0].value);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n *\n * Atomic across servers via optimistic compare-and-set: read the live value,\n * run `fn`, then write with `UPDATE … WHERE value = <what was read>` (or a\n * create-only insert for a missing key), retrying when another writer got\n * there first. `fn` may run more than once under contention; after\n * {@link PG_UPDATE_MAX_ATTEMPTS} lost races it throws {@link CacheError}.\n *\n * Without an explicit `ttl` the row keeps its `expires_at`; a new key gets\n * the driver default TTL. Returning `null` deletes the key (compare-and-delete).\n */\n public async 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 const parsedKey = this.parseKey(key);\n const t = this.table;\n const live = `(expires_at IS NULL OR expires_at > now())`;\n const explicitTtl = options.ttl !== undefined ? parseTtl(options.ttl) : undefined;\n\n for (let attempt = 0; attempt < PG_UPDATE_MAX_ATTEMPTS; attempt++) {\n const read = await this.pgClient.query(\n `SELECT value FROM ${t}\n WHERE key = $1 AND ${live}`,\n [parsedKey],\n );\n\n const exists = read.rows.length > 0;\n const current = exists ? (this.decodeJson(read.rows[0].value) as T) : null;\n const expected = exists ? JSON.stringify(current) : null;\n const result = await fn(current);\n\n if (result === null) {\n if (!exists) {\n return null;\n }\n\n const deleted = await this.pgClient.query(\n `/* cache:cas-delete */ DELETE FROM ${t}\n WHERE key = $1 AND value = $2::jsonb AND ${live}\n RETURNING 1`,\n [parsedKey, expected],\n );\n\n if (deleted.rows.length > 0) {\n await this.emit(\"removed\", { key: parsedKey });\n\n return null;\n }\n\n continue;\n }\n\n if (!exists) {\n const created = (await this.set(key, result, {\n onConflict: \"create\",\n ...(explicitTtl !== undefined ? { ttl: explicitTtl } : {}),\n })) as CacheSetResult;\n\n if (created.wasSet) {\n return result;\n }\n\n continue;\n }\n\n const params: unknown[] = [parsedKey, JSON.stringify(result), expected];\n let expirySql = \"\";\n\n if (explicitTtl !== undefined) {\n params.push(this.ttlToSeconds(explicitTtl));\n expirySql = `, expires_at = now() + make_interval(secs => $4::double precision)`;\n }\n\n const updated = await this.pgClient.query(\n `/* cache:cas-update */ UPDATE ${t}\n SET value = $2::jsonb, stale_at = NULL${expirySql}\n WHERE key = $1 AND value = $3::jsonb AND ${live}\n RETURNING 1`,\n params,\n );\n\n if (updated.rows.length > 0) {\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: explicitTtl });\n\n return result;\n }\n }\n\n throw new CacheError(\n `cache.update(): '${parsedKey}' changed on every attempt (${PG_UPDATE_MAX_ATTEMPTS} tries) — too much write contention.`,\n );\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\";\nimport type { createClient } from \"redis\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n RedisOptions,\n} from \"../types\";\nimport { CacheConfigurationError, CacheError, CacheUnsupportedError } from \"../types\";\nimport { parseTtl, safeErrorInfo } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\n// ============================================================\n// Lazy-loaded Redis SDK Types\n// ============================================================\n\n/**\n * Cached Redis module (loaded once, reused)\n */\nlet RedisClient: typeof import(\"redis\");\n\nlet isModuleExists: boolean | null = null;\n\n/**\n * Installation instructions for Redis package\n */\nconst REDIS_INSTALL_INSTRUCTIONS = `\nRedis cache driver requires the redis package.\nInstall it with:\n\n npm install redis\n\nOr with your preferred package manager:\n\n pnpm add redis\n yarn add redis\n`.trim();\n\n/**\n * Load Redis module\n */\nasync function loadRedis() {\n try {\n RedisClient = await import(\"redis\");\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n}\n\nloadRedis();\n\n// ============================================================\n// Lua scripts (atomic ownership primitives)\n// ============================================================\n\n/** KEYS[1]=key, ARGV[1]=expected raw value. 1 when deleted, 0 when not the owner. */\nexport const REDIS_COMPARE_DELETE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then redis.call('DEL', KEYS[1]) return 1 else return 0 end`;\n\n/**\n * Compare-and-set used by `update()`.\n *\n * KEYS[1]=key; ARGV[1]=expected raw value (ignored when ARGV[3]=='1');\n * ARGV[2]=new raw value; ARGV[3]='1' when the key is expected to be missing;\n * ARGV[4]=ttl seconds to apply ('0' = no expiry); ARGV[5]='keep' to keep the\n * existing TTL instead (only when the key existed).\n *\n * Returns 1 when written, 0 when the current value no longer matches.\n * `KEEPTTL` requires Redis >= 6.\n */\nexport const REDIS_CAS_SET_SCRIPT = `local cur = redis.call('GET', KEYS[1])\nif ARGV[3] == '1' then\n if cur then return 0 end\nelseif cur ~= ARGV[1] then\n return 0\nend\nlocal ttl = tonumber(ARGV[4])\nif ARGV[5] == 'keep' and ARGV[3] ~= '1' then\n redis.call('SET', KEYS[1], ARGV[2], 'KEEPTTL')\nelseif ttl > 0 then\n redis.call('SET', KEYS[1], ARGV[2], 'EX', ttl)\nelse\n redis.call('SET', KEYS[1], ARGV[2])\nend\nreturn 1`;\n\n/** KEYS[1]=key. Fallback for clients without `getDel`: returns the value and deletes it. */\nexport const REDIS_GET_DEL_SCRIPT = `local v = redis.call('GET', KEYS[1])\nif v then redis.call('DEL', KEYS[1]) end\nreturn v`;\n\n/**\n * Increment that gives a counter without an expiry the driver's default TTL,\n * in one round trip.\n *\n * KEYS[1]=key; ARGV[1]=amount; ARGV[2]=ttl seconds ('0' = none).\n *\n * Replaces `INCRBY` + `EXPIRE … NX`: the `NX` flag needs Redis >= 7.0, and on\n * Redis 6 the second command failed after the count had already gone up.\n * `TTL == -1` means \"exists without an expiry\", so a TTL set earlier (by\n * `set(..., { ttl })`) is never shortened or reset.\n */\nexport const REDIS_INCREMENT_SCRIPT = `local v = redis.call('INCRBY', KEYS[1], ARGV[1])\nlocal ttl = tonumber(ARGV[2])\nif ttl > 0 and redis.call('TTL', KEYS[1]) == -1 then\n redis.call('EXPIRE', KEYS[1], ttl)\nend\nreturn v`;\n\n/** Compare-and-set attempts before `update()` gives up under contention. */\nconst UPDATE_MAX_ATTEMPTS = 10;\n\n/** Keys deleted per UNLINK/DEL while draining a namespace. */\nconst REMOVE_BATCH_SIZE = 500;\n\n// ============================================================\n// RedisCacheDriver Class\n// ============================================================\n\nexport class RedisCacheDriver\n extends BaseCacheDriver<ReturnType<typeof createClient>, RedisOptions>\n implements CacheDriver<ReturnType<typeof createClient>, RedisOptions>\n{\n /**\n * Cache driver name\n */\n public name = \"redis\";\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: RedisOptions) {\n if (!options.url && !options.host) {\n throw new CacheConfigurationError(\n \"Redis driver requires either 'url' or 'host' option to be configured.\",\n );\n }\n\n return super.setOptions(options);\n }\n\n /**\n * {@inheritDoc}\n */\n public async removeNamespace(namespace: string) {\n namespace = this.parseKey(namespace);\n\n this.log(\"clearing\", namespace);\n\n // Escape Redis glob metacharacters so a namespace carrying `*`/`?`/`[`\n // cannot widen the match and delete keys outside its own prefix.\n const escaped = namespace.replace(/[\\\\*?[\\]]/g, \"\\\\$&\");\n\n // Match the namespace key itself plus `<ns>.*` — never bare `<ns>*`, which\n // would also swallow siblings like `users2`. An empty namespace (no prefix)\n // means \"everything\".\n const pattern = escaped === \"\" ? \"*\" : `${escaped}.*`;\n\n const deleted: string[] = [];\n\n if (this.client) {\n if (escaped !== \"\") {\n const removedSelf = await this.deleteKeys([namespace]);\n\n if (removedSelf > 0) {\n deleted.push(namespace);\n }\n }\n\n // `SCAN` (cursor-based, non-blocking) instead of `KEYS` — `KEYS` is O(N)\n // and blocks the single-threaded Redis event loop. Delete per batch while\n // scanning so memory stays bounded on large keyspaces.\n let batch: string[] = [];\n\n for await (const scanned of this.client.scanIterator({\n MATCH: pattern,\n COUNT: 100,\n })) {\n // node-redis 5 yields a batch of keys for each SCAN cursor response;\n // older supported clients yielded one key at a time. Normalize both\n // forms before passing keys to the command parser.\n const keys = Array.isArray(scanned) ? scanned : [scanned];\n\n for (const key of keys) {\n batch.push(key as unknown as string);\n\n if (batch.length >= REMOVE_BATCH_SIZE) {\n await this.deleteKeys(batch);\n deleted.push(...batch);\n batch = [];\n }\n }\n }\n\n if (batch.length > 0) {\n await this.deleteKeys(batch);\n deleted.push(...batch);\n }\n }\n\n if (deleted.length === 0) {\n this.log(\"notFound\", namespace);\n return;\n }\n\n this.log(\"cleared\", namespace);\n\n return deleted;\n }\n\n /**\n * Delete keys with `UNLINK` (non-blocking free), falling back to `DEL` when\n * the client lacks it. Returns the number of keys removed.\n */\n protected async deleteKeys(keys: string[]): Promise<number> {\n const client = this.client as any;\n\n if (typeof client.unlink === \"function\") {\n return Number(await client.unlink(keys)) || 0;\n }\n\n return Number(await client.del(keys)) || 0;\n }\n\n // ============================================================\n // Tag index — a native Redis SET per tag\n // ============================================================\n\n /**\n * {@inheritdoc}\n *\n * `SADD` — atomic across servers, so concurrent tagged writes on any number\n * of instances never drop a member. The set key lives inside the prefix\n * namespace, so `flush()` / `removeNamespace()` sweep it too.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n const parsedKey = this.parseKey(tagKey);\n\n await this.withTagSet(parsedKey, (client) => client.sAdd(parsedKey, members));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n const parsedKey = this.parseKey(tagKey);\n\n return this.withTagSet(parsedKey, async (client) => (await client.sMembers(parsedKey)) ?? []);\n }\n\n /**\n * {@inheritdoc}\n *\n * `SREM` — Redis drops the set once its last member is removed.\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n const parsedKey = this.parseKey(tagKey);\n\n await this.withTagSet(parsedKey, (client) => client.sRem(parsedKey, members));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n await this.client?.del(this.parseKey(tagKey));\n }\n\n /**\n * Run a SET command against a tag key, upgrading a pre-5.20 index first.\n *\n * Earlier versions stored the index as a JSON-array STRING under the same\n * key, which makes any SET command fail with `WRONGTYPE`. On that error the\n * legacy array is read, the string deleted, its members re-added with\n * `SADD`, and the command retried once.\n */\n protected async withTagSet<T>(parsedKey: string, run: (client: any) => Promise<T>): Promise<T> {\n const client = this.client as any;\n\n try {\n return await run(client);\n } catch (error) {\n if (!/WRONGTYPE/i.test(String((error as Error)?.message ?? error))) {\n throw error;\n }\n\n const raw: string | null = await client.get(parsedKey);\n let legacy: unknown = [];\n\n try {\n legacy = raw === null ? [] : JSON.parse(raw);\n } catch {\n legacy = [];\n }\n\n await client.del(parsedKey);\n\n const members = Array.isArray(legacy)\n ? legacy.filter((member): member is string => typeof member === \"string\")\n : [];\n\n if (members.length > 0) {\n await client.sAdd(parsedKey, members);\n }\n\n return run(client);\n }\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 throw new CacheUnsupportedError(\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.\",\n );\n }\n\n this.log(\"caching\", parsedKey);\n\n const serialized = JSON.stringify(value);\n const hasExpiry = Boolean(ttl) && ttl !== Infinity;\n\n let reply: string | null | undefined;\n\n if (onConflict === \"create\") {\n const options: { NX: true; EX?: number } = { NX: true };\n if (hasExpiry) {\n options.EX = ttl as number;\n }\n reply = await this.client?.set(parsedKey, serialized, options);\n } else if (onConflict === \"update\") {\n const options: { XX: true; EX?: number } = { XX: true };\n if (hasExpiry) {\n options.EX = ttl as number;\n }\n reply = await this.client?.set(parsedKey, serialized, options);\n } else if (hasExpiry) {\n reply = await this.client?.set(parsedKey, serialized, { EX: ttl as number });\n } else {\n reply = await this.client?.set(parsedKey, serialized);\n }\n\n const wasSet = reply === \"OK\";\n\n if ((onConflict === \"create\" || onConflict === \"update\") && !wasSet) {\n const existing = onConflict === \"create\" ? ((await this.get(key)) as any) : null;\n return { wasSet: false, existing } satisfies CacheSetResult;\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n if (staleAt === undefined) {\n // A plain write invalidates any freshness marker left by an earlier SWR\n // write, otherwise the old `staleAt` would apply to the new value.\n await this.client?.del(this.swrMetaKey(parsedKey));\n } else {\n // Sidecar key for SWR freshness — keeps the main value JSON\n // backwards-compatible with entries written before SWR landed.\n const sidecarOptions: { EX?: number } = {};\n\n if (hasExpiry) {\n sidecarOptions.EX = ttl as number;\n }\n\n await this.client?.set(this.swrMetaKey(parsedKey), String(staleAt), sidecarOptions);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n return { wasSet: true, existing: null } satisfies CacheSetResult;\n }\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n *\n * Values are stored as `JSON.stringify(value)`, so the compare runs against\n * the serialized form inside Lua.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n\n const deleted = await (this.client as any).eval(REDIS_COMPARE_DELETE_SCRIPT, {\n keys: [parsedKey],\n arguments: [JSON.stringify(expected)],\n });\n\n if (Number(deleted) !== 1) {\n return false;\n }\n\n // Mirror remove(): drop the SWR sidecar and emit the event.\n await this.client?.del(this.swrMetaKey(parsedKey));\n await this.emit(\"removed\", { key: parsedKey });\n\n return true;\n }\n\n /**\n * Build the sidecar key Redis uses to track SWR freshness without\n * wrapping the main value JSON.\n */\n protected swrMetaKey(parsedKey: string): string {\n return `${parsedKey}::swrmeta`;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, fetching the value and the\n * SWR sidecar in parallel. Returns `null` when the main key is missing\n * or expired (Redis handles expiry natively, so the absence of the\n * value alone tells us).\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n\n const [valueRaw, staleAtRaw] = await Promise.all([\n this.client?.get(parsedKey),\n this.client?.get(this.swrMetaKey(parsedKey)),\n ]);\n\n if (!valueRaw) {\n return null;\n }\n\n const data = JSON.parse(valueRaw);\n const staleAt = staleAtRaw ? Number(staleAtRaw) : undefined;\n\n return staleAt !== undefined ? { data, staleAt } : { data };\n }\n\n /**\n * {@inheritdoc}\n *\n * Redis tracks expiry natively (the payload carries no `expiresAt`), so read\n * the remaining lifetime with the `TTL` command. Redis returns `-2` for a\n * missing key and `-1` for a key with no expiry.\n */\n protected async getRemainingTtl(key: CacheKey): Promise<number | undefined> {\n const parsedKey = this.parseKey(key);\n const ttl = await this.client?.ttl(parsedKey);\n\n if (ttl === undefined || ttl === -2) {\n return undefined;\n }\n\n if (ttl === -1) {\n return Infinity;\n }\n\n return ttl;\n }\n\n /**\n * {@inheritDoc}\n */\n public async get(key: CacheKey) {\n key = this.parseKey(key);\n\n this.log(\"fetching\", key);\n\n const value = await this.client?.get(key);\n\n if (!value) {\n this.log(\"notFound\", key);\n // Emit miss event\n await this.emit(\"miss\", { key });\n return null;\n }\n\n this.log(\"fetched\", key);\n\n // Parse and return the value directly (Redis handles expiration natively)\n const parsedValue = JSON.parse(value);\n\n // Apply cloning for immutability protection\n if (parsedValue === null || parsedValue === undefined) {\n // Emit hit event\n await this.emit(\"hit\", { key, value: parsedValue });\n return parsedValue;\n }\n\n const type = typeof parsedValue;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n // Emit hit event\n await this.emit(\"hit\", { key, value: parsedValue });\n return parsedValue;\n }\n\n try {\n const clonedValue = structuredClone(parsedValue);\n // Emit hit event\n await this.emit(\"hit\", { key, value: clonedValue });\n return clonedValue;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${key}`, error);\n throw error;\n }\n }\n\n /**\n * {@inheritDoc}\n */\n public async remove(key: CacheKey) {\n key = this.parseKey(key);\n\n this.log(\"removing\", key);\n\n // Drop the SWR sidecar alongside the main key — keeps metadata from\n // surviving a `remove` and confusing a later `swr` read.\n // Two separate DELs (not one multi-key DEL) so it stays cluster-slot safe.\n await this.client?.del(key);\n await this.client?.del(this.swrMetaKey(key));\n\n this.log(\"removed\", key);\n\n await this.emit(\"removed\", { key });\n }\n\n /**\n * {@inheritDoc}\n *\n * WARNING: without a `globalPrefix` this runs `FLUSHDB`, which clears the\n * WHOLE selected Redis database (including keys not written by this driver).\n * Configure a `globalPrefix` to only delete this driver's keys.\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n await this.client?.flushDb();\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritDoc}\n */\n public async connect() {\n if (this.clientDriver) return;\n\n if (!isModuleExists) {\n throw new Error(REDIS_INSTALL_INSTRUCTIONS);\n }\n\n const options = this.options;\n\n if (options && !options.url && options.host) {\n const auth =\n options.password || options.username ? `${options.username}:${options.password}@` : \"\";\n\n if (!options.url) {\n const host = options.host || \"localhost\";\n const port = options.port || 6379;\n options.url = `redis://${auth}${host}:${port}`;\n }\n }\n\n const clientOptions = {\n ...options,\n ...(this.options.clientOptions || {}),\n };\n\n try {\n this.log(\"connecting\");\n const { createClient } = RedisClient;\n\n this.client = createClient(clientOptions);\n\n this.client.on(\"error\", (error: Error) => {\n // Never pass the raw `error` object to the logger — connection\n // errors can carry the connection URL (with password) in the\n // message/cause. Only the redacted message survives past this point.\n const { message } = safeErrorInfo(error);\n\n if ((error as any).code === \"ECONNREFUSED\") {\n this.log(\"connectionFailed\", message);\n } else {\n this.log(\"error\", message);\n }\n });\n\n await this.client.connect();\n\n this.log(\"connected\");\n await this.emit(\"connected\");\n } catch (error) {\n // Boot-time cache connection failure is unrecoverable in practice —\n // `fatal` aligns Redis with the cascade drivers and herald connector\n // for clean \"page on fatal only\" alerting. Only the redacted\n // `{ message, code }` shape is logged; the raw error (which may carry\n // the connection URL/password) never reaches stdout or the logger.\n log.fatal(\"cache\", \"redis\", \"Failed to connect\", safeErrorInfo(error));\n await this.emit(\"error\", { error });\n\n // Drop the half-initialised client so a later connect() can retry.\n this.clientDriver = undefined as unknown as typeof this.clientDriver;\n\n throw error;\n }\n }\n\n /**\n * {@inheritDoc}\n *\n * Guards against disconnecting when the client was never created. The base\n * `client` getter falls back to `this` when no client is set, so we check\n * the backing `clientDriver` directly — using `this.client` for this guard\n * would always be truthy and crash with \"this.quit is not a function\".\n */\n public async disconnect() {\n if (!this.clientDriver) {\n return;\n }\n\n this.log(\"disconnecting\");\n\n await this.clientDriver.quit();\n\n this.log(\"disconnected\");\n await this.emit(\"disconnected\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Atomic across servers: `GETDEL` (Redis >= 6.2), or an equivalent Lua\n * script when the client has no `getDel`. A value is handed out once.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const client = this.client as any;\n\n const raw: string | null | undefined =\n typeof client.getDel === \"function\"\n ? await client.getDel(parsedKey)\n : await client.eval(REDIS_GET_DEL_SCRIPT, { keys: [parsedKey], arguments: [] });\n\n if (raw === null || raw === undefined) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.client?.del(this.swrMetaKey(parsedKey));\n\n const value = JSON.parse(raw);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n *\n * Atomic across servers via optimistic compare-and-set: read the raw value,\n * run `fn`, then write only if the value is still what was read (Lua), and\n * retry otherwise. `fn` may therefore run more than once under contention;\n * after {@link UPDATE_MAX_ATTEMPTS} lost races it throws {@link CacheError}.\n *\n * Without an explicit `ttl` the existing expiry is kept (`KEEPTTL`); a new\n * key gets the driver default TTL. Returning `null` deletes the key\n * (compare-and-delete).\n */\n public async 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 const parsedKey = this.parseKey(key);\n const client = this.client as any;\n\n const explicitTtl = options.ttl !== undefined ? parseTtl(options.ttl) : undefined;\n\n for (let attempt = 0; attempt < UPDATE_MAX_ATTEMPTS; attempt++) {\n const raw: string | null = (await client.get(parsedKey)) ?? null;\n const current = raw === null ? null : (JSON.parse(raw) as T);\n const result = await fn(current);\n\n if (result === null) {\n if (raw === null) {\n return null;\n }\n\n const deleted = await client.eval(REDIS_COMPARE_DELETE_SCRIPT, {\n keys: [parsedKey],\n arguments: [raw],\n });\n\n if (Number(deleted) === 1) {\n await this.client?.del(this.swrMetaKey(parsedKey));\n await this.emit(\"removed\", { key: parsedKey });\n\n return null;\n }\n\n continue;\n }\n\n const isNew = raw === null;\n const ttlSeconds = explicitTtl ?? (isNew ? this.ttl : 0);\n const keep = explicitTtl === undefined && !isNew;\n\n const written = await client.eval(REDIS_CAS_SET_SCRIPT, {\n keys: [parsedKey],\n arguments: [\n raw ?? \"\",\n JSON.stringify(result),\n isNew ? \"1\" : \"0\",\n String(Number.isFinite(ttlSeconds) && ttlSeconds > 0 ? ttlSeconds : 0),\n keep ? \"keep\" : \"\",\n ],\n });\n\n if (Number(written) === 1) {\n // A plain write invalidates any SWR freshness marker.\n await this.client?.del(this.swrMetaKey(parsedKey));\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: ttlSeconds });\n\n return result;\n }\n }\n\n throw new CacheError(\n `cache.update(): '${parsedKey}' changed on every attempt (${UPDATE_MAX_ATTEMPTS} tries) — too much write contention.`,\n );\n }\n\n /**\n * Atomic increment using Redis native INCRBY command\n * {@inheritdoc}\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n\n this.log(\"caching\", parsedKey);\n\n // A fresh counter has no TTL; apply the driver default (only if none set),\n // atomically with the increment and without Redis 7's `EXPIRE … NX`.\n const defaultTtl = Number.isFinite(this.ttl) && this.ttl > 0 ? Math.ceil(this.ttl) : 0;\n const result =\n defaultTtl > 0\n ? Number(\n await (this.client as any)?.eval(REDIS_INCREMENT_SCRIPT, {\n keys: [parsedKey],\n arguments: [String(value), String(defaultTtl)],\n }),\n )\n : await this.client?.incrBy(parsedKey, value);\n\n this.log(\"cached\", parsedKey);\n\n // Emit set event\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: undefined });\n\n return result || 0;\n }\n\n /**\n * Atomic decrement using Redis native DECRBY command\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n\n this.log(\"caching\", parsedKey);\n\n const result = await this.client?.decrBy(parsedKey, value);\n\n this.log(\"cached\", parsedKey);\n\n // Emit set event\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: undefined });\n\n return result || 0;\n }\n\n /**\n * Set if not exists (atomic operation)\n * Returns true if key was set, false if key already existed\n */\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n\n this.log(\"caching\", parsedKey);\n\n if (ttl === undefined) {\n ttl = this.ttl;\n }\n\n let result: string | null;\n\n // Use Redis native SET with NX option\n if (ttl && ttl !== Infinity) {\n result = await this.client?.set(parsedKey, JSON.stringify(value), {\n NX: true,\n EX: ttl,\n });\n } else {\n result = await this.client?.set(parsedKey, JSON.stringify(value), {\n NX: true,\n });\n }\n\n const wasSet = result === \"OK\";\n\n if (wasSet) {\n this.log(\"cached\", parsedKey);\n // Emit set event\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n } else {\n this.log(\"notFound\", parsedKey);\n }\n\n return wasSet;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["log","fs"],"sources":["../../../../../../../packages/cache/src/metrics.ts","../../../../../../../packages/cache/src/types.ts","../../../../../../../packages/cache/src/utils.ts","../../../../../../../packages/cache/src/tagged-scoped-cache.ts","../../../../../../../packages/cache/src/scoped-cache.ts","../../../../../../../packages/cache/src/cache-manager.ts","../../../../../../../packages/cache/src/cached/auto-key.ts","../../../../../../../packages/cache/src/cached/normalize-args.ts","../../../../../../../packages/cache/src/cached/cached.ts","../../../../../../../packages/cache/src/list/memory-cache-list.ts","../../../../../../../packages/cache/src/tagged-cache.ts","../../../../../../../packages/cache/src/drivers/base-cache-driver.ts","../../../../../../../packages/cache/src/drivers/file-cache-driver.ts","../../../../../../../packages/cache/src/drivers/in-memory-tag-index.ts","../../../../../../../packages/cache/src/drivers/lru-memory-cache-driver.ts","../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts","../../../../../../../packages/cache/src/drivers/memory-extended-cache-driver.ts","../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts","../../../../../../../packages/cache/src/drivers/null-cache-driver.ts","../../../../../../../packages/cache/src/drivers/pg-cache-driver.ts","../../../../../../../packages/cache/src/drivers/redis-cache-driver.ts"],"sourcesContent":["import type { CacheEventData, CacheMetricsSnapshot } 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 // `sorted` is non-empty (the `samples.length === 0` early return above)\n // and `index` is clamped to its last element, so this always hits — but\n // the clamp is arithmetic the compiler does not evaluate. `?? 0` states\n // the floor without asserting: a percentile of an empty set is 0, which\n // is exactly what the early return above already answers.\n return sorted[index] ?? 0;\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\";\nimport type { RedisClientOptions } from \"redis\";\nimport type {\n BaseCacheDriver,\n FileCacheDriver,\n LRUMemoryCacheDriver,\n MemoryCacheDriver,\n MockCacheDriver,\n MemoryExtendedCacheDriver,\n NullCacheDriver,\n PgCacheDriver,\n RedisCacheDriver,\n} from \"./drivers\";\n\n/**\n * Base error class for cache-related errors\n */\nexport class CacheError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheError\";\n }\n}\n\n/**\n * Error thrown when cache connection fails\n */\nexport class CacheConnectionError extends CacheError {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheConnectionError\";\n }\n}\n\n/**\n * Error thrown when cache driver configuration is invalid\n */\nexport class CacheConfigurationError extends CacheError {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheConfigurationError\";\n }\n}\n\n/**\n * Error thrown when cache driver is not initialized\n */\nexport class CacheDriverNotInitializedError extends CacheError {\n public constructor(\n message: string = \"No cache driver initialized. Call cache.init() or cache.use() first.\",\n ) {\n super(message);\n this.name = \"CacheDriverNotInitializedError\";\n }\n}\n\n/**\n * Error thrown when a driver does not implement a requested operation.\n *\n * Raised when a caller invokes a method the driver cannot fulfill —\n * e.g. `update()` on the file driver before the file-lock primitive lands.\n */\nexport class CacheUnsupportedError extends CacheError {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheUnsupportedError\";\n }\n}\n\n/**\n * Error thrown when an optimistic-concurrency update exhausts its retry budget.\n */\nexport class CacheConcurrencyError extends CacheError {\n public constructor(message: string) {\n super(message);\n this.name = \"CacheConcurrencyError\";\n }\n}\n\n/**\n * TTL shape accepted by set/remember/config calls.\n *\n * - `number` — seconds\n * - `string` — human-readable duration parsed via `ms` (`\"1h\"`, `\"30m\"`, `\"7d\"`, `\"2 weeks\"`, …)\n * - `Infinity` — no expiration\n */\nexport type CacheTtl = number | string;\n\n/**\n * Conflict resolution policy for `set` operations.\n *\n * - `\"create\"` — set only if the key does not exist (Redis `NX`)\n * - `\"update\"` — set only if the key already exists (Redis `XX`)\n * - `\"upsert\"` — default; overwrite whether the key exists or not\n */\nexport type CacheConflictPolicy = \"create\" | \"update\" | \"upsert\";\n\n/**\n * Result of a conditional write (`onConflict: \"create\" | \"update\"`).\n */\nexport type CacheSetResult<T = any> = {\n /**\n * Whether the write actually took effect.\n */\n wasSet: boolean;\n /**\n * The existing value when the write was rejected. Undefined for successful writes\n * and for unconditional `upsert` writes.\n */\n existing: T | null;\n};\n\n/**\n * Options for `lock()` — TTL is required (forgotten locks would stay forever),\n * `owner` identifies who holds the lock (handy for debugging), `driver` routes\n * the call through a non-default driver.\n *\n * @example\n * await cache.lock(\"lock.import\", { ttl: \"5m\", driver: \"redis\" }, async () => {\n * await runImport();\n * });\n */\nexport type LockOptions = {\n /**\n * How long the lock lives before auto-release. Required — guards against\n * forgotten locks if the process crashes between acquire and release.\n */\n ttl: CacheTtl;\n /**\n * Identifying value stored under the lock key. Defaults to `pid.<process.pid>`.\n * Use a custom owner when you want a human-readable label (e.g. `\"worker.jobs-2\"`).\n */\n owner?: string;\n /**\n * Per-call driver override by registered name. Manager-level only.\n */\n driver?: string;\n};\n\n/**\n * Discriminated-union result of `lock()`. Unambiguous even when the wrapped\n * function legitimately returns `undefined` — the `acquired` flag tells you\n * whether `fn` ran at all.\n *\n * @example\n * const outcome = await cache.lock(\"lock.x\", \"1m\", async () => computeValue());\n * if (outcome.acquired) {\n * console.log(\"ran, got\", outcome.value);\n * } else {\n * console.log(\"skipped — someone else holds the lock\");\n * }\n */\nexport type LockOutcome<T> = { acquired: true; value: T } | { acquired: false };\n\n/**\n * Options for `remember()` when you need more than just a TTL — e.g. attaching\n * tags to the cache-miss write, or routing the single call to a non-default driver.\n *\n * Passed in the TTL position: `cache.remember(key, { ttl: \"1h\", tags: [\"users\"] }, fn)`.\n *\n * @example\n * await cache.remember(\"user.1\", { ttl: \"1h\", tags: [\"users\"] }, () =>\n * db.users.find(1),\n * );\n */\nexport type RememberOptions = {\n /**\n * TTL applied on cache miss. Accepts seconds (number) or duration string.\n */\n ttl?: CacheTtl;\n /**\n * Tags attached to the entry created on miss. Invalidate via `cache.tags([...]).invalidate()`.\n */\n tags?: string[];\n /**\n * Per-call driver override. Routes this remember call (both read and write)\n * to the named driver without mutating `currentDriver`.\n */\n driver?: string;\n};\n\n/**\n * Rich options for the `set` call-site. Mutually exclusive with a positional TTL.\n *\n * @example\n * await cache.set(\"user:1\", user, {\n * ttl: \"1h\",\n * tags: [\"users\", \"active\"],\n * onConflict: \"create\",\n * });\n */\nexport type CacheSetOptions = {\n /**\n * Relative TTL (seconds or duration string). Mutually exclusive with `expiresAt`.\n */\n ttl?: CacheTtl;\n /**\n * Absolute expiration as a Unix epoch in milliseconds or a Date. Mutually exclusive with `ttl`.\n */\n expiresAt?: number | Date;\n /**\n * Tags attached to this entry for tag-based invalidation. Inline equivalent of\n * `cache.tags([...]).set(key, value)`.\n */\n tags?: string[];\n /**\n * Conflict resolution policy. Defaults to `\"upsert\"`.\n */\n onConflict?: CacheConflictPolicy;\n /**\n * Per-call namespace override. Applied ahead of `globalPrefix` for this write only.\n */\n namespace?: string;\n /**\n * Per-call driver override by registered name. Routes this write to a non-default driver\n * without mutating `currentDriver`.\n */\n driver?: string;\n /**\n * Optional embedding vector indexed alongside the entry for similarity retrieval\n * via `cache.similar(...)`. Drivers without similarity support throw\n * {@link CacheUnsupportedError} when this option is supplied.\n *\n * Cache is embedding-agnostic — callers compute the vector with whichever embedder\n * they like and pass the result here.\n *\n * @example\n * await cache.set(\"doc.123\", doc, { vector: await embed(doc.text), ttl: \"1d\" });\n */\n vector?: number[];\n /**\n * Freshness deadline as a Unix epoch in milliseconds. Primarily set by\n * {@link CacheSwrOptions} flow — entries with `staleAt` in the future are\n * considered \"fresh\" by `cache.swr()`; entries past `staleAt` but before\n * `expiresAt` are \"stale-but-revalidatable.\"\n *\n * Direct callers can pin this manually when bypassing `swr()`, but the\n * common path is `cache.swr(key, { freshTtl, staleTtl }, fn)`.\n */\n staleAt?: number;\n};\n\n/**\n * Options for `cache.namespace(prefix, options)` — defaults applied to every\n * write produced through the returned scope.\n *\n * Per-call options always win over scope defaults. Tags merge additively\n * (scope tags + call tags, deduped). Nested scopes inherit from the parent;\n * the child's own values override.\n */\nexport type CacheNamespaceOptions = {\n /**\n * Default TTL for writes inside this scope. Same shape as everywhere else\n * — number of seconds or a duration string (`\"1h\"`, `\"30d\"`, …).\n */\n ttl?: CacheTtl;\n /**\n * Tags auto-attached to every write inside this scope. Useful for cross-scope\n * invalidation hooks (e.g. tag every entry with `user.<id>` so a single\n * `cache.flushTag(\"user.42\")` wipes the user's footprint everywhere).\n *\n * Merged additively with per-call tags; scope tags are never replaced.\n */\n tags?: string[];\n};\n\n/**\n * Options for `cache.swr(key, options, callback)` — stale-while-revalidate.\n *\n * Two TTLs split the entry's lifecycle into three windows:\n * - `now < freshTtl` → fresh, return cached value, no upstream call.\n * - `freshTtl <= now < staleTtl` → stale-but-revalidatable, return cached\n * value immediately, kick off `callback` in the background to refresh.\n * - `now >= staleTtl` → expired, block on `callback` like a normal miss.\n *\n * Concurrent SWR callers in the stale window all return the cached value;\n * the background refresh runs exactly once per key (deduped via the\n * driver's per-key lock map).\n *\n * If the background refresh fails, the stale entry is preserved and the\n * error is logged + emitted on the `error` event. Callers that received\n * the stale value never see the failure — they got their data.\n */\nexport type CacheSwrOptions = {\n /**\n * How long the value is considered fresh. Within this window, SWR returns\n * the cached value with no upstream call. Accepts seconds or duration\n * string (`\"1m\"`, `\"30s\"`).\n */\n freshTtl: CacheTtl;\n /**\n * Total lifetime of the entry (must be greater than `freshTtl`). Between\n * `freshTtl` and `staleTtl`, SWR returns the stale value and triggers a\n * background refresh. Past `staleTtl`, SWR blocks and refetches.\n */\n staleTtl: CacheTtl;\n /**\n * Optional tags applied on the first miss-fetch and on every successful\n * background refresh. Same semantics as `CacheSetOptions.tags`.\n */\n tags?: string[];\n /**\n * Per-call driver override by registered name. Routes both the read and\n * any write/refresh through the named driver without mutating\n * `currentDriver`. Same semantics as `RememberOptions.driver`.\n */\n driver?: string;\n};\n\n/**\n * Options for `cache.similar(vector, options)` — similarity retrieval against\n * stored entries previously written with `set({ vector })`.\n */\nexport type CacheSimilarOptions = {\n /**\n * Maximum number of hits to return. Required — no implicit default keeps\n * callers from accidentally pulling the entire candidate set.\n */\n topK: number;\n /**\n * Cosine similarity floor in `[0, 1]`. Hits scoring strictly below this are\n * filtered out before `topK` truncation.\n */\n threshold?: number;\n /**\n * Optional tag filter. Only entries tagged with at least one of the given\n * tags are considered (matches the union semantics elsewhere in the package).\n */\n tags?: string[];\n};\n\n/**\n * One result from a `similar()` query — the original key, its stored value,\n * and the cosine similarity to the query vector.\n */\nexport type CacheSimilarHit<T = unknown> = {\n /**\n * The original cache key, post-`parseKey` normalization.\n */\n key: string;\n /**\n * Stored value, deep-cloned to protect the cache from accidental mutation\n * (same semantics as `get`).\n */\n value: T;\n /**\n * Cosine similarity in `[0, 1]`. Higher means more similar.\n */\n score: number;\n};\n\n/**\n * Cache key type - can be a string or an object\n */\nexport type CacheKey = string | GenericObject;\n\nexport type CacheOperationType =\n | \"fetching\"\n | \"fetched\"\n | \"caching\"\n | \"cached\"\n | \"flushing\"\n | \"flushed\"\n | \"removing\"\n | \"removed\"\n | \"clearing\"\n | \"cleared\"\n | \"expired\"\n | \"notFound\"\n | \"connecting\"\n | \"error\"\n | \"connectionFailed\"\n | \"connected\"\n | \"disconnecting\"\n | \"disconnected\";\n\n/**\n * Cache event types for observability\n */\nexport type CacheEventType =\n | \"hit\"\n | \"miss\"\n | \"set\"\n | \"removed\"\n | \"invalidated\"\n | \"flushed\"\n | \"expired\"\n | \"connected\"\n | \"disconnected\"\n | \"error\";\n\n/**\n * Cache event data structure\n */\nexport type CacheEventData = {\n /**\n * The cache key involved in the event\n */\n key?: string;\n /**\n * The value (for set/hit events)\n */\n value?: any;\n /**\n * TTL in seconds (for set events)\n */\n ttl?: number;\n /**\n * Driver name that emitted the event\n */\n driver: string;\n /**\n * Error object (for error events)\n */\n error?: any;\n /**\n * Namespace (for namespace operations)\n */\n namespace?: string;\n /** Tags passed to `tags([...]).invalidate()` (`invalidated` events only). */\n tags?: string[];\n /** Keys a tag invalidation removed (`invalidated` events only). */\n keys?: string[];\n /** How long the operation took, in milliseconds (`invalidated` events only). */\n durationMs?: number;\n};\n\n/**\n * Event handler function type\n */\nexport type CacheEventHandler = (eventData: CacheEventData) => void | Promise<void>;\n\n/**\n * Per-operation latency + counter snapshot returned by `cache.metrics()`.\n *\n * The shape is a single recursive level — the top object covers all drivers\n * and each entry under `byDriver` repeats the same fields without nesting\n * further. Use this for ad-hoc dashboards, exporters to Prometheus / StatsD,\n * or just `console.log` during development.\n */\nexport type CacheMetricsSnapshot = {\n /** Cumulative cache hits across the lifetime of the collector. */\n hits: number;\n /** Cumulative cache misses (lookups that returned null). */\n misses: number;\n /** Cumulative successful writes. */\n sets: number;\n /** Cumulative key removals. */\n removed: number;\n /** Cumulative cache errors emitted via the `error` event. */\n errors: number;\n /** `hits / (hits + misses)` — `0` when no read ops have happened yet. */\n hitRate: number;\n /** Latency percentiles in milliseconds, computed from a circular buffer. */\n latencyMs: {\n p50: number;\n p95: number;\n p99: number;\n /** Size of the underlying buffer at snapshot time (capped at the configured max). */\n samples: number;\n };\n /** Per-driver breakdowns keyed by driver name (`\"memory\"`, `\"redis\"`, …). */\n byDriver: Record<string, Omit<CacheMetricsSnapshot, \"byDriver\">>;\n /** Millisecond timestamp the collector last reset (or was instantiated). */\n startedAt: number;\n};\n\n/**\n * Tagged cache interface for working with cache tags\n */\nexport interface TaggedCacheDriver {\n /**\n * Set a value in cache with tags\n */\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\n /**\n * Get a value from cache (checks tags)\n */\n get(key: CacheKey): Promise<any | null>;\n /**\n * Remove a specific key\n */\n remove(key: CacheKey): Promise<void>;\n /**\n * Invalidate (clear) all keys associated with the current tags\n */\n invalidate(): Promise<void>;\n /**\n * Flush all keys associated with the current tags\n * @deprecated Use invalidate() instead\n */\n flush(): Promise<void>;\n /**\n * Check if a key exists\n */\n has(key: CacheKey): Promise<boolean>;\n /**\n * Remember pattern with tags\n */\n remember(key: CacheKey, ttl: number, callback: () => Promise<any>): Promise<any>;\n /**\n * Pull value with tags\n */\n pull(key: CacheKey): Promise<any | null>;\n /**\n * Forever with tags\n */\n forever(key: CacheKey, value: any): Promise<any>;\n /**\n * Increment with tags\n */\n increment(key: CacheKey, value?: number, options?: { ttl?: CacheTtl }): Promise<number>;\n /**\n * Decrement with tags\n */\n decrement(key: CacheKey, value?: number): Promise<number>;\n}\n\nexport type MemoryCacheOptions = {\n /**\n * The global prefix for the cache key\n */\n globalPrefix?: string | (() => string);\n /**\n * The default TTL for the cache. Accepts a number of seconds or a human-readable\n * duration string like `\"1h\"`, `\"30m\"`, `\"7d\"`.\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n /**\n * Maximum number of items in cache\n * When exceeded, least recently used items will be evicted\n *\n * @default undefined (no limit)\n */\n maxSize?: number;\n};\n\nexport type MemoryExtendedCacheOptions = MemoryCacheOptions;\n\nexport type LRUMemoryCacheOptions = {\n /**\n * The maximum number of items in the cache\n *\n * @default 1000\n */\n capacity?: number;\n /**\n * The global prefix for the cache key. Applied via `parseCacheKey`, same\n * semantics as the other drivers.\n */\n globalPrefix?: string | (() => string);\n /**\n * The default TTL for new entries. Accepts a number of seconds or a\n * human-readable duration string like `\"1h\"`, `\"30m\"`, `\"7d\"`.\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n};\n\nexport type FileCacheOptions = {\n /**\n * The global prefix for the cache key\n */\n globalPrefix?: string | (() => string);\n /**\n * The default TTL for the cache. Accepts a number of seconds or a human-readable\n * duration string like `\"1h\"`, `\"30m\"`, `\"7d\"`.\n *\n * @default 0\n */\n ttl?: CacheTtl;\n /**\n * Storage cache directory\n *\n * @default storagePath(\"cache\")\n */\n directory: string | (() => string);\n /**\n * File name\n *\n * @default cache.json\n */\n fileName?: string | (() => string);\n};\n\nexport type RedisOptions = {\n /**\n * Redis Port\n *\n * @default 6379\n */\n port?: number;\n /**\n * Redis Host\n */\n host?: string;\n /**\n * Redis Username\n */\n username?: string;\n /**\n * Redis Password\n */\n password?: string;\n /**\n * Redis URL\n *\n * If used, it will override the host and port options\n */\n url?: string;\n /**\n * Global prefix for the cache key\n */\n globalPrefix?: string | (() => string);\n /**\n * Default TTL. Accepts a number of seconds or a human-readable duration string\n * like `\"1h\"`, `\"30m\"`, `\"7d\"`.\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n /**\n * Redis client options\n */\n clientOptions?: RedisClientOptions;\n};\n\nexport type NullCacheDriverOptions = GenericObject;\n\n/**\n * Options accepted by {@link MockCacheDriver}. Same shape as the memory\n * driver — only `globalPrefix` and `ttl` apply, since the mock's storage is\n * a plain `Map` with no eviction policy.\n */\nexport type MockCacheOptions = {\n /**\n * Global key prefix, applied via `parseKey` (matches the other drivers).\n */\n globalPrefix?: string | (() => string);\n /**\n * Default TTL for new entries. Accepts seconds or a duration string.\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n};\n\n/**\n * One row in {@link MockCacheDriver.callLog} — captures every public op\n * the driver received in arrival order. Useful for behavioral assertions\n * in downstream tests (\"did my service actually invalidate the cache?\").\n */\nexport type CacheCall = {\n /**\n * Operation name as it appears on the driver contract — `\"set\"`, `\"get\"`,\n * `\"remove\"`, `\"flush\"`, `\"removeNamespace\"`, `\"has\"`, etc.\n */\n operation: string;\n /**\n * Post-`parseKey` cache key when the op is key-addressed; `undefined` for\n * keyless ops (`flush`, `connect`, `disconnect`).\n */\n key?: string;\n /**\n * Raw arguments passed to the call site, in declaration order. Lets\n * callers assert on TTLs, options objects, vector payloads, etc.\n */\n args: unknown[];\n /**\n * `Date.now()` when the call was recorded. Useful for timing-related\n * assertions (e.g. \"the second invalidation came within 100ms\").\n */\n timestamp: number;\n};\n\n/**\n * Minimal `pg`-compatible client surface the cache driver depends on.\n *\n * Both `pg.Pool` and `pg.Client` from the `pg` package satisfy this — the\n * driver only ever calls `query(text, values)`. Typing it loosely keeps\n * `pg` strictly optional: consumers without the `pg` package installed\n * never hit a missing import.\n */\nexport type PgClientLike = {\n query(text: string, values?: unknown[]): Promise<{ rows: any[]; rowCount?: number | null }>;\n};\n\nexport type PgCacheOptions = {\n /**\n * User-supplied `pg.Pool` or `pg.Client`. The driver does NOT own the\n * connection lifecycle — `cache.disconnect()` will not close this client.\n */\n client: PgClientLike;\n /**\n * Database table name. Default: `\"warlock_cache\"`.\n *\n * Sanitized: only `[A-Za-z0-9_]` are allowed; anything else throws\n * {@link CacheConfigurationError} at `setOptions` time.\n */\n table?: string;\n /**\n * Global key prefix, applied via `parseKey` (same semantics as the other drivers).\n */\n globalPrefix?: string | (() => string);\n /**\n * Default TTL for new entries. Accepts seconds or a duration string\n * (`\"1h\"`, `\"7d\"`, …).\n *\n * @default Infinity\n */\n ttl?: CacheTtl;\n /**\n * Optional pgvector configuration. When present, the driver provisions an\n * `embedding VECTOR(dimensions)` column + similarity index, and `similar()`\n * queries via the `<=>` cosine-distance operator.\n *\n * Requires the pgvector extension (`CREATE EXTENSION vector;`) — the driver\n * verifies its presence on the first vector operation and throws\n * {@link CacheConfigurationError} if missing.\n *\n * Omit this block (or remove it) to run the driver in KV-only mode —\n * `set({ vector })` and `similar()` then throw {@link CacheUnsupportedError}.\n */\n vector?: {\n /**\n * Vector dimension count. Must match the embedder you use throughout the\n * lifetime of the table. Mixing dimensions on the same table is unsupported.\n */\n dimensions: number;\n /**\n * pgvector index strategy. `hnsw` (default) is faster to query and slightly\n * slower to build; `ivfflat` is faster to build but typically slower to query.\n *\n * @default \"hnsw\"\n */\n index?: \"hnsw\" | \"ivfflat\";\n };\n};\n\nexport interface CacheDriver<ClientType, Options> {\n /**\n * The cache driver options\n */\n options: Options;\n /**\n * Cache driver name\n */\n name: string;\n /**\n * Set logging state\n */\n setLoggingState(shouldLog: boolean): any;\n /**\n * Remove all cached items by namespace\n */\n removeNamespace(namespace: string): Promise<any>;\n /**\n * Set the cache driver options\n */\n setOptions(options: Options): any;\n /**\n * Parse the key to be used in the cache\n */\n parseKey(key: CacheKey): string;\n /**\n * Set a value in the cache.\n *\n * @param key The cache key, could be an object or string\n * @param value The value to be stored in the cache\n * @param ttlOrOptions Either a TTL (seconds number, or duration string like `\"1h\"`),\n * or a full {@link CacheSetOptions} object.\n *\n * @example\n * // Positional TTL (back-compat)\n * await cache.set(\"user:1\", user, 3600);\n *\n * @example\n * // Human-readable duration\n * await cache.set(\"user:1\", user, \"1h\");\n *\n * @example\n * // Rich options\n * await cache.set(\"user:1\", user, { ttl: \"1h\", tags: [\"users\"], onConflict: \"create\" });\n */\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\n /**\n * Get a value from the cache\n */\n get<T = any>(key: CacheKey): Promise<T | null>;\n /**\n * Remove a value from the cache\n */\n remove(key: CacheKey): Promise<void>;\n /**\n * Flush the entire cache\n */\n flush(): Promise<void>;\n /**\n * Connect to the cache driver\n */\n connect(): Promise<any>;\n /**\n * The cache client\n */\n client?: ClientType;\n /**\n * Disconnect the cache driver\n */\n disconnect(): Promise<void>;\n /**\n * Check if a key exists in the cache without fetching its value\n */\n has(key: CacheKey): Promise<boolean>;\n /**\n * Get value from cache or execute callback and cache the result.\n *\n * The second argument accepts a TTL (number of seconds or duration string like `\"1h\"`)\n * or a full {@link RememberOptions} object when you need to attach tags or route to\n * a non-default driver.\n *\n * @example\n * // Positional TTL — the common case\n * const user = await cache.remember(\"user.1\", \"1h\", () => db.users.find(1));\n *\n * @example\n * // Options form — tag the cache-miss write for bulk invalidation\n * const user = await cache.remember(\"user.1\", { ttl: \"1h\", tags: [\"users\"] }, () =>\n * db.users.find(1),\n * );\n */\n remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T>;\n /**\n * Stale-while-revalidate. Returns the cached value when fresh; returns\n * the stale value + kicks off a background refresh when within the\n * `freshTtl..staleTtl` window; blocks like a normal miss when past\n * `staleTtl`. Concurrent stale-window callers share one in-flight\n * refresh.\n *\n * Background refresh failures preserve the stale entry and emit an\n * `error` event — the stale-returning caller never sees the failure.\n *\n * @example\n * const product = await cache.swr(\n * \"product.42\",\n * { freshTtl: \"1m\", staleTtl: \"1h\" },\n * () => db.products.find(42),\n * );\n */\n swr<T = any>(key: CacheKey, options: CacheSwrOptions, callback: () => Promise<T>): Promise<T>;\n /**\n * Get a value and remove it, handing it out exactly once.\n *\n * Atomicity: across servers on `redis` (`GETDEL`) and `pg`\n * (`DELETE … RETURNING`); within the process on `memory`, `memoryExtended`,\n * `lru` and `mock`; serialized per key within the process only on `file`.\n */\n pull<T = any>(key: CacheKey): Promise<T | null>;\n /**\n * Set a value in cache permanently (no expiration)\n */\n forever<T = any>(key: CacheKey, value: T): Promise<T>;\n /**\n * Read, transform, and write a cached value without losing concurrent updates.\n *\n * The callback receives the current value (or `null` on miss) and returns the\n * next value. Returning `null` removes the key. TTL is preserved unless\n * explicitly overridden via options.\n *\n * Atomicity: across servers on `redis` and `pg` (optimistic\n * compare-and-set with retries — `fn` may run more than once under\n * contention, so keep it side-effect free). On every other driver updates\n * are serialized per key within ONE process only. `file` does not support it.\n *\n * @example\n * await cache.update<User>(\"user:1\", (current) => {\n * if (!current) return null;\n * return { ...current, lastSeen: Date.now() };\n * });\n */\n 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 /**\n * Shallow-merge a partial object into a cached value.\n *\n * If the key is missing, treats the current value as `{}`. Preserves TTL by default.\n *\n * @example\n * await cache.merge<User>(\"user:1\", { name: \"Jane\" });\n */\n merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T>;\n /**\n * List sub-API factory — returns a {@link CacheListAccessor} bound to the given key.\n *\n * @example\n * const recent = cache.list<Event>(\"recent-events\");\n * await recent.push(event);\n * const last10 = await recent.slice(0, 10);\n */\n list<T = any>(key: CacheKey): CacheListAccessor<T>;\n /**\n * Acquire a distributed lock, run `fn`, and auto-release. Returns a\n * {@link LockOutcome} so callers can distinguish \"ran and produced this value\"\n * from \"skipped because someone else holds the lock.\"\n *\n * Built on top of `set({ onConflict: \"create\" })` — Redis-native, emulated\n * on other drivers (single-process semantics elsewhere).\n *\n * @example\n * const outcome = await cache.lock(\"lock.import\", \"5m\", async () => {\n * await runImport();\n * return \"done\";\n * });\n */\n lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>>;\n /**\n * Increment a numeric value in cache. A missing key starts from 0 with the\n * per-call `options.ttl` (or driver default); an existing expiry is preserved.\n * An explicit TTL also repairs a live counter with no expiry.\n * Supply the TTL on every call for expiring counters: a separate seed may\n * expire before the atomic increment recreates the key.\n *\n * Atomicity: across servers on `redis` (`INCRBY`) and `pg` (single\n * upsert statement); within the process on `memory`, `memoryExtended`,\n * `lru` and `mock`; serialized per key within the process only on `file`.\n *\n * @param key The cache key\n * @param value The value to increment by (default 1)\n */\n increment(key: CacheKey, value?: number, options?: { ttl?: CacheTtl }): Promise<number>;\n /**\n * Decrement a numeric value in cache\n *\n * @param key The cache key\n * @param value The value to decrement by (default 1)\n */\n decrement(key: CacheKey, value?: number): Promise<number>;\n /**\n * Get multiple values from cache at once\n */\n many(keys: CacheKey[]): Promise<any[]>;\n /**\n * Set multiple values in cache at once\n */\n setMany(items: Record<string, any>, ttl?: number): Promise<void>;\n /**\n * Register an event listener\n */\n on(event: CacheEventType, handler: CacheEventHandler): this;\n /**\n * Remove an event listener\n */\n off(event: CacheEventType, handler: CacheEventHandler): this;\n /**\n * Register a one-time event listener\n */\n once(event: CacheEventType, handler: CacheEventHandler): this;\n /**\n * Set if not exists (atomic operation)\n * Returns true if key was set, false if key already existed\n * Note: Not all drivers support this operation\n */\n setNX?(key: CacheKey, value: any, ttl?: number): Promise<boolean>;\n /**\n * Create a tagged cache instance for the given tags\n */\n tags(tags: string[]): TaggedCacheDriver;\n /**\n * Tag-index primitive: add `members` (normalized, un-prefixed cache keys)\n * to the index stored under `tagKey`. Optional — `TaggedCache` falls back to\n * a get/set array when a driver has none.\n *\n * Built-in drivers: redis → a native SET (`SADD`, atomic across servers);\n * pg → one atomic upsert of a JSON array row; memory / lru / mock → an\n * in-process set that is never evicted or expired; file (base default) →\n * a get/set array serialized per tag key in this process only.\n */\n tagAdd?(tagKey: CacheKey, members: string[]): Promise<void>;\n /**\n * Tag-index primitive: the members stored under `tagKey` (empty when none).\n */\n tagMembers?(tagKey: CacheKey): Promise<string[]>;\n /**\n * Tag-index primitive: remove `members` from `tagKey`. Members added\n * concurrently and not listed are kept.\n */\n tagRemove?(tagKey: CacheKey, members: string[]): Promise<void>;\n /**\n * Tag-index primitive: drop the whole index stored under `tagKey`.\n */\n tagDelete?(tagKey: CacheKey): Promise<void>;\n /**\n * Similarity retrieval. Returns the nearest stored entries to `vector` by\n * cosine similarity, ordered by descending score.\n *\n * Drivers without a similarity index throw {@link CacheUnsupportedError}.\n * Memory-family drivers brute-force scan in O(N) — suitable for development\n * but not for production knowledge bases beyond a few thousand entries; use\n * the `pg` driver (with pgvector) or `redis` driver (with RediSearch) instead.\n *\n * @example\n * const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });\n */\n similar<T = any>(vector: number[], options: CacheSimilarOptions): Promise<CacheSimilarHit<T>[]>;\n}\n\n/**\n * Accessor for list-shaped cached values.\n *\n * Defaults to read-mutate-write semantics on non-native drivers (memory, file, LRU).\n * The Redis driver overrides this with native `LPUSH` / `RPUSH` / `LRANGE` / `LTRIM` for O(1) ops.\n */\nexport interface CacheListAccessor<T = any> {\n /**\n * Append one or more items to the tail of the list. Returns the new length.\n */\n push(...items: T[]): Promise<number>;\n /**\n * Prepend one or more items to the head of the list. Returns the new length.\n */\n unshift(...items: T[]): Promise<number>;\n /**\n * Remove and return the tail item, or `null` if the list is empty.\n */\n pop(): Promise<T | null>;\n /**\n * Remove and return the head item, or `null` if the list is empty.\n */\n shift(): Promise<T | null>;\n /**\n * Return a slice of the list. End is exclusive, mirroring `Array.prototype.slice`.\n */\n slice(start?: number, end?: number): Promise<T[]>;\n /**\n * Return the full list.\n */\n all(): Promise<T[]>;\n /**\n * Return the length of the list.\n */\n length(): Promise<number>;\n /**\n * Trim the list to the inclusive range `[start, end]`. Outside elements are dropped.\n */\n trim(start: number, end: number): Promise<void>;\n /**\n * Remove the entire list.\n */\n clear(): Promise<void>;\n}\n\n/**\n * One-shot tagged handle returned by `ScopedCacheContract.tags(...)`.\n *\n * Identical write surface to {@link TaggedCacheDriver}, except the underlying\n * scope's prefix is applied to every key and the scope's default TTL/tags\n * still flow through. Tags supplied here merge additively with scope-level\n * tags.\n */\nexport interface TaggedScopedCacheContract {\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\n get<T = any>(key: CacheKey): Promise<T | null>;\n has(key: CacheKey): Promise<boolean>;\n remove(key: CacheKey): Promise<void>;\n pull<T = any>(key: CacheKey): Promise<T | null>;\n forever<T = any>(key: CacheKey, value: T): Promise<T>;\n setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean>;\n remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T>;\n increment(key: CacheKey, value?: number, options?: { ttl?: CacheTtl }): Promise<number>;\n decrement(key: CacheKey, value?: number): Promise<number>;\n /**\n * Invalidate every entry tagged with the union of scope tags + handle tags.\n * Forwarded to the underlying tag index — tags are global, scope-agnostic.\n */\n invalidate(): Promise<void>;\n}\n\n/**\n * Scoped view over the cache. Returned by `cache.namespace(prefix, options?)`.\n *\n * A scope prepends its `prefix` to every key, applies optional default TTL/tags\n * to every write, and forwards everything else to the underlying source. It\n * stores nothing of its own — purely a convenience wrapper.\n *\n * @example\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\n * await chat.set(\"messages.1\", msg); // → \"chats.10.messages.1\", TTL 30d\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call ttl wins\n * await chat.clear(); // wipe the whole scope\n */\nexport interface ScopedCacheContract {\n /** The fully-qualified prefix this scope prepends to every key. */\n readonly prefix: string;\n\n /**\n * Nested scope. Inherits the parent's defaults; child's own options override.\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 namespace(prefix: string, options?: CacheNamespaceOptions): ScopedCacheContract;\n\n /**\n * One-shot tagged write handle. Tags merge additively with scope defaults.\n */\n tags(tags: string[]): TaggedScopedCacheContract;\n\n /** Wipe every entry under this scope's prefix. Sugar for `removeNamespace(prefix)`. */\n clear(): Promise<void>;\n\n // Reads\n get<T = any>(key: CacheKey): Promise<T | null>;\n has(key: CacheKey): Promise<boolean>;\n many(keys: CacheKey[]): Promise<any[]>;\n pull<T = any>(key: CacheKey): Promise<T | null>;\n\n // Writes\n set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): Promise<any>;\n setMany(items: Record<string, any>, ttl?: number): Promise<void>;\n setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean>;\n forever<T = any>(key: CacheKey, value: T): Promise<T>;\n remove(key: CacheKey): Promise<void>;\n\n // Read-or-compute\n remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T>;\n swr<T = any>(key: CacheKey, options: CacheSwrOptions, callback: () => Promise<T>): Promise<T>;\n\n // Mutations\n increment(key: CacheKey, value?: number, options?: { ttl?: CacheTtl }): Promise<number>;\n decrement(key: CacheKey, value?: number): Promise<number>;\n 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 merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T>;\n\n // Structured accessors\n list<T = any>(key: CacheKey): CacheListAccessor<T>;\n\n // Coordination\n lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>>;\n\n // Similarity (delegated; throws CacheUnsupportedError if the driver lacks it)\n similar<T = any>(vector: number[], options: CacheSimilarOptions): Promise<CacheSimilarHit<T>[]>;\n}\n\nexport type CacheData = {\n /**\n * Value stored in the cache\n */\n data: any;\n /**\n * The expiration date in milliseconds\n */\n expiresAt?: number;\n /**\n * Time to live in seconds\n */\n ttl?: number;\n /**\n * Freshness deadline as a millisecond timestamp. Used by `swr()` — entries\n * with `staleAt` in the future are \"fresh\"; past `staleAt` but before\n * `expiresAt` are \"stale-but-revalidatable\" and trigger a background\n * refresh on the next read. Optional — entries written through plain\n * `set()` skip this field entirely and `swr()` treats them as always-fresh.\n */\n staleAt?: number;\n};\n\nexport type DriverClass = new () => CacheDriver<any, any>;\n\ntype DefaultDrivers =\n | \"redis\"\n | \"file\"\n | \"memory\"\n | \"memoryExtended\"\n | \"null\"\n | \"lru\"\n | \"pg\"\n | \"mock\";\n\ntype MergeWithDefaultDrivers<T> = T extends undefined ? DefaultDrivers : DefaultDrivers | T;\n\nexport type CacheConfigurations<\n T extends string | undefined = undefined,\n DriverName = MergeWithDefaultDrivers<T>,\n> = {\n /**\n * The default cache driver name\n */\n default?: DriverName;\n /**\n * Determine whether to log or not\n *\n * @default true\n */\n logging?: boolean;\n /**\n * The cache drivers list\n */\n drivers: {\n redis?: typeof RedisCacheDriver;\n file?: typeof FileCacheDriver;\n null?: typeof NullCacheDriver;\n memory?: typeof MemoryCacheDriver;\n memoryExtended?: typeof MemoryExtendedCacheDriver;\n lru?: typeof LRUMemoryCacheDriver;\n pg?: typeof PgCacheDriver;\n mock?: typeof MockCacheDriver;\n } & {\n [key in Extract<T, string>]?: typeof BaseCacheDriver<any, any> | undefined;\n };\n /**\n * The cache driver options\n */\n options: {\n redis?: RedisOptions;\n file?: FileCacheOptions;\n memory?: MemoryCacheOptions;\n memoryExtended?: MemoryExtendedCacheOptions;\n null?: NullCacheDriverOptions;\n lru?: LRUMemoryCacheOptions;\n pg?: PgCacheOptions;\n mock?: MockCacheOptions;\n } & {\n [key in Extract<T, string>]?: GenericObject;\n };\n};\n","import { rtrim } from \"@mongez/reinforcements\";\nimport ms, { type StringValue } from \"ms\";\nimport type { CacheKey, CacheSetOptions, CacheTtl, RememberOptions } from \"./types\";\nimport { CacheConfigurationError } from \"./types\";\n\n/**\n * Make a proper key for the cache\n */\nexport function parseCacheKey(\n key: CacheKey,\n options: { globalPrefix?: string | (() => string) } = {},\n): string {\n if (typeof key === \"object\") {\n key = JSON.stringify(key);\n }\n\n // remove any curly braces and double quotes along with []\n key = key.replace(/[{}\"[\\]]/g, \"\").replaceAll(/[:,]/g, \".\");\n\n const cachePrefix =\n typeof options.globalPrefix === \"function\" ? options.globalPrefix() : options.globalPrefix;\n\n return rtrim(String(cachePrefix ? rtrim(cachePrefix, \".\") + \".\" + key : key), \".\");\n}\n\n/**\n * Parse a TTL value into seconds.\n *\n * Accepts:\n * - a number (seconds) — fractions are rounded UP to whole seconds\n * (so Redis `EX` never receives a fraction); `0` means \"no expiry\"\n * - `Infinity` — no expiration, returned unchanged\n * - a bare numeric string (e.g. `\"3600\"`, from env vars) — treated as seconds\n * - a human-readable duration string (e.g. `\"1h\"`, `\"30m\"`, `\"7d\"`) — parsed via `ms`,\n * then converted to seconds, rounding up\n *\n * Throws `CacheConfigurationError` on NaN, negative numbers, or unparseable strings.\n *\n * @example\n * parseTtl(3600); // 3600\n * parseTtl(0.5); // 1\n * parseTtl(\"3600\"); // 3600\n * parseTtl(\"500ms\"); // 1\n * parseTtl(\"1h\"); // 3600\n * parseTtl(\"7d\"); // 604800\n * parseTtl(Infinity); // Infinity\n */\nexport function parseTtl(input: CacheTtl): number {\n if (typeof input === \"number\") {\n if (Number.isNaN(input)) {\n throw new CacheConfigurationError(\"Invalid TTL: NaN.\");\n }\n\n if (input < 0) {\n throw new CacheConfigurationError(`Invalid TTL: negative number (${input}).`);\n }\n\n return Math.ceil(input);\n }\n\n if (typeof input !== \"string\" || input.trim() === \"\") {\n throw new CacheConfigurationError(\n `Invalid TTL: expected number or duration string, got ${typeof input}.`,\n );\n }\n\n const trimmed = input.trim();\n\n if (/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n return Math.ceil(Number(trimmed));\n }\n\n const milliseconds = ms(trimmed as StringValue);\n\n if (milliseconds === undefined || Number.isNaN(milliseconds)) {\n throw new CacheConfigurationError(\n `Invalid TTL duration string: \"${input}\". Expected forms like \"1h\", \"30m\", \"7d\".`,\n );\n }\n\n if (milliseconds < 0) {\n throw new CacheConfigurationError(`Invalid TTL: negative duration (\"${input}\").`);\n }\n\n return Math.ceil(milliseconds / 1000);\n}\n\n/**\n * Convert an absolute `expiresAt` (Date or epoch milliseconds) into a\n * relative TTL in seconds.\n *\n * Throws {@link CacheConfigurationError} when the deadline is in the past —\n * the caller almost certainly has a bug (stale timestamp, wrong unit, etc.)\n * and silently storing an already-expired entry would hide it.\n *\n * @example\n * expiresAtToTtl(new Date(Date.now() + 60_000)); // ~60\n * expiresAtToTtl(Date.now() + 30 * 60 * 1000); // ~1800\n */\nexport function expiresAtToTtl(expiresAt: number | Date): number {\n const deadline = expiresAt instanceof Date ? expiresAt.getTime() : expiresAt;\n const relativeMs = deadline - Date.now();\n\n if (relativeMs <= 0) {\n throw new CacheConfigurationError(\n `\\`expiresAt\\` must be in the future; got ${new Date(deadline).toISOString()}.`,\n );\n }\n\n return Math.ceil(relativeMs / 1000);\n}\n\n/**\n * Coerce the polymorphic 3rd `set` argument into a uniform `CacheSetOptions`\n * shape. Lets callers (and `BaseCacheDriver.resolveSetOptions`) skip per-shape\n * branching.\n *\n * - `undefined` / `null` → `{}` (resolves to driver-level defaults later)\n * - `number` / `string` (positional TTL) → `{ ttl }`\n * - already an options object → returned as-is\n */\nexport function normalizeToOptions(input?: CacheTtl | CacheSetOptions): CacheSetOptions {\n if (input === undefined || input === null) {\n return {};\n }\n\n if (typeof input === \"number\" || typeof input === \"string\") {\n return { ttl: input };\n }\n\n return input;\n}\n\n/**\n * Sibling of {@link normalizeToOptions} for the `remember()` call site, where\n * the polymorphic 2nd argument is `CacheTtl | RememberOptions` (no `expiresAt`,\n * no `onConflict`). Returns the same shape so callers can `{ ...opts, ... }`\n * without branching.\n *\n * @example\n * normalizeToRememberOptions(60); // { ttl: 60 }\n * normalizeToRememberOptions(\"1h\"); // { ttl: \"1h\" }\n * normalizeToRememberOptions({ ttl: \"1h\", tags: [\"x\"] }); // returned as-is\n */\nexport function normalizeToRememberOptions(input?: CacheTtl | RememberOptions): RememberOptions {\n if (input === undefined || input === null) {\n return {};\n }\n\n if (typeof input === \"number\" || typeof input === \"string\") {\n return { ttl: input };\n }\n\n return input;\n}\n\n/**\n * Resolve the final TTL in seconds for a `set` call. Precedence:\n *\n * 1. Caller's `ttl` (number or duration string) wins.\n * 2. Otherwise, caller's `expiresAt` is converted to relative seconds.\n * 3. Otherwise, `fallback` is used (driver-level default — typically\n * `Infinity` when no default is configured, meaning \"never expires\").\n *\n * Throws {@link CacheConfigurationError} when `ttl` and `expiresAt` are\n * supplied together (mutually exclusive).\n */\nexport function resolveTtl(\n ttl: CacheTtl | undefined,\n expiresAt: number | Date | undefined,\n fallback: number,\n): number {\n if (ttl !== undefined && expiresAt !== undefined) {\n throw new CacheConfigurationError(\n \"Cache set options cannot specify both `ttl` and `expiresAt` — choose one.\",\n );\n }\n\n if (ttl !== undefined) {\n return parseTtl(ttl);\n }\n\n if (expiresAt !== undefined) {\n return expiresAtToTtl(expiresAt);\n }\n\n return fallback;\n}\n\n/**\n * Combine any number of tag lists into a single deduped array, dropping\n * `undefined`/empty entries. Returns `undefined` when no tags survive — lets\n * callers skip emitting empty `tags: []` into option payloads.\n *\n * Used by scoped-cache merging where scope tags + handle tags + per-call tags\n * must union additively without duplicates.\n *\n * @example\n * mergeTagSets([\"a\", \"b\"], [\"b\", \"c\"]); // [\"a\", \"b\", \"c\"]\n * mergeTagSets(undefined, [\"x\"]); // [\"x\"]\n * mergeTagSets(undefined, undefined); // undefined\n * mergeTagSets([], []); // undefined\n */\nexport function mergeTagSets(...lists: (string[] | undefined)[]): string[] | undefined {\n const flat: string[] = [];\n\n for (const list of lists) {\n if (!list || list.length === 0) {\n continue;\n }\n\n flat.push(...list);\n }\n\n if (flat.length === 0) {\n return undefined;\n }\n\n return Array.from(new Set(flat));\n}\n\n/**\n * Add extra tags to any option-bag that already shapes `tags?: string[]`.\n * Pure — clones the input shape, never mutates. Tags are appended (caller\n * is responsible for de-duplication if needed; pair with {@link mergeTagSets}).\n *\n * @example\n * injectTags({ ttl: \"1h\" }, [\"unread\"]); // { ttl: \"1h\", tags: [\"unread\"] }\n * injectTags({ tags: [\"a\"] }, [\"b\"]); // { tags: [\"a\", \"b\"] }\n */\nexport function injectTags<T extends { tags?: string[] }>(options: T, extraTags: string[]): T {\n if (extraTags.length === 0) {\n return options;\n }\n\n return {\n ...options,\n tags: [...(options.tags ?? []), ...extraTags],\n };\n}\n\n/**\n * Cosine similarity between two equal-length numeric vectors.\n *\n * Returns a value in `[-1, 1]` where `1` means perfectly aligned, `0` means\n * orthogonal, and `-1` means opposing. For typical embedding spaces (where\n * vectors live in the positive cone) the practical range is `[0, 1]`.\n *\n * Throws {@link CacheConfigurationError} on dimension mismatch — fail loud at\n * the call site rather than silently returning a misleading score. A zero-norm\n * vector on either side returns `0` (no defined direction to compare).\n *\n * @example\n * cosineSimilarity([1, 0, 0], [1, 0, 0]); // 1\n * cosineSimilarity([1, 0, 0], [0, 1, 0]); // 0\n */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n if (a.length !== b.length) {\n throw new CacheConfigurationError(\n `Vector dimension mismatch: got ${a.length} and ${b.length}.`,\n );\n }\n\n if (a.length === 0) {\n throw new CacheConfigurationError(\n \"Vector dimension mismatch: empty vector cannot be compared.\",\n );\n }\n\n let dot = 0;\n let normA = 0;\n let normB = 0;\n\n for (let i = 0; i < a.length; i++) {\n const x = a[i];\n const y = b[i];\n\n if (x === undefined || y === undefined) {\n // Unreachable: both guards above establish `a.length === b.length` and\n // a non-zero length, so every index in range exists on both sides. Said\n // out loud rather than asserted with `as number`, because the two guards\n // that make it true are the kind of thing a later edit removes — and if\n // one ever did, an assertion would let `undefined` into the arithmetic\n // and return NaN as a similarity SCORE. A silently wrong ranking is\n // worse than a stopped one.\n break;\n }\n\n dot += x * y;\n normA += x * x;\n normB += y * y;\n }\n\n if (normA === 0 || normB === 0) {\n return 0;\n }\n\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n\n/**\n * Matches userinfo credentials embedded in a connection URL\n * (e.g. `redis://user:pass@host`). Node/Redis/Postgres client errors\n * frequently echo the target connection string — including the password —\n * back in `error.message` on connection failure.\n */\nconst CREDENTIALS_IN_URL = /(:\\/\\/)[^\\s/@]+:[^\\s/@]+@/g;\n\n/**\n * Strip embedded `user:pass@` credentials from a string before it reaches\n * any log sink.\n */\nexport function redactCredentials(message: string): string {\n return message.replace(CREDENTIALS_IN_URL, \"$1[REDACTED]@\");\n}\n\n/**\n * Reduce an unknown thrown value to a log-safe `{ message, code? }` shape.\n * Callers must never log the raw error object itself — it may carry the\n * connection URL (with password) in the top-level message, `cause`, or\n * driver-specific fields, all of which `console.log`/`util.inspect` would\n * still print in full.\n */\nexport function safeErrorInfo(error: unknown): { message: string; code?: string } {\n if (error instanceof Error) {\n const info: { message: string; code?: string } = {\n message: redactCredentials(error.message),\n };\n\n const code = (error as any).code;\n if (code) info.code = String(code);\n\n return info;\n }\n\n return { message: redactCredentials(String(error)) };\n}\n\nexport enum CACHE_FOR {\n /**\n * Cache for 30 Minutes (in seconds)\n */\n HALF_HOUR = 1800,\n /**\n * Cache for 1 Hour (in seconds)\n */\n ONE_HOUR = 3600,\n /**\n * Cache for 12 Hours (in seconds)\n */\n HALF_DAY = 43200,\n /**\n * Cache for 24 Hours (in seconds)\n */\n ONE_DAY = 86400,\n /**\n * Cache for 7 Days (in seconds)\n */\n ONE_WEEK = 604800,\n /**\n * Cache for 15 Days (in seconds)\n */\n HALF_MONTH = 1296000,\n /**\n * Cache for 30 Days (in seconds)\n */\n ONE_MONTH = 2592000,\n /**\n * Cache for 60 Days (in seconds)\n */\n TWO_MONTHS = 5184000,\n /**\n * Cache for 180 Days (in seconds)\n */\n SIX_MONTHS = 15768000,\n /**\n * Cache for 365 Days (in seconds)\n */\n ONE_YEAR = 31536000,\n}\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(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): 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 and take it out of every tag index this handle\n * writes to (scope tags + handle tags).\n */\n public async remove(key: CacheKey): Promise<void> {\n await this.scope.remove(key);\n await this.detachFromTags(key);\n }\n\n /**\n * Read-and-remove the scoped key, then take it out of this handle's tags.\n */\n public async pull<T = any>(key: CacheKey): Promise<T | null> {\n const value = await this.scope.pull<T>(key);\n\n if (value !== null) {\n await this.detachFromTags(key);\n }\n\n return value;\n }\n\n /**\n * Remove the un-prefixed scoped key from every tag index of this handle.\n */\n protected async detachFromTags(key: CacheKey): Promise<void> {\n const allTags = mergeTagSets(this.scope.defaults.tags, this.handleTags);\n\n if (!allTags || allTags.length === 0) {\n return;\n }\n\n const scopedKey = this.buildScopedKey(key);\n const tagged = this.scope.source.tags(allTags) as unknown as {\n detachFromTags: (indexedKey: string) => Promise<void>;\n };\n\n await tagged.detachFromTags(parseCacheKey(scopedKey));\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 // Register the un-prefixed scoped key — the tag index never holds the\n // driver-parsed form, so `invalidate()` can remove it via `remove(key)`.\n const scopedKey = this.buildScopedKey(key);\n const tagged = this.scope.source.tags(allTags) as unknown as {\n storeTagRelationship: (key: CacheKey) => Promise<void>;\n };\n\n await tagged.storeTagRelationship(scopedKey);\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(normalizeToRememberOptions(ttlOrOptions), this.handleTags);\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, options?: { ttl?: CacheTtl }): Promise<number> {\n return this.scope.increment(key, value, options);\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(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions): 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(`setNX is not supported by the underlying cache source: ${this.source.name}`);\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, options?: { ttl?: CacheTtl }): Promise<number> {\n return this.source.increment(this.scopedKey(key), value, {\n ttl: options?.ttl ?? this.defaults.ttl,\n });\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. An explicit `options.ttl` always wins. Otherwise\n * the scope's default `ttl` is applied only when the key does not exist yet;\n * for an existing key no ttl is passed so the source preserves the entry's\n * remaining TTL.\n */\n public async 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 const scopedKey = this.scopedKey(key);\n\n return this.source.update<T>(scopedKey, fn, await this.writeOptions(scopedKey, options));\n }\n\n /**\n * Shallow-merge a partial object into the scoped entry. Same TTL semantics\n * as {@link update}.\n */\n public async merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T> {\n const scopedKey = this.scopedKey(key);\n\n return this.source.merge<T>(scopedKey, partial, await this.writeOptions(scopedKey, options));\n }\n\n /**\n * Resolve the ttl options for update/merge: explicit ttl, else the scope\n * default only for keys that don't exist yet, else nothing (keep remaining).\n */\n private async writeOptions(\n scopedKey: CacheKey,\n options?: { ttl?: CacheTtl },\n ): Promise<{ ttl?: CacheTtl }> {\n if (options?.ttl !== undefined) {\n return { ttl: options.ttl };\n }\n\n if (this.defaults.ttl === undefined) {\n return {};\n }\n\n return (await this.source.has(scopedKey)) ? {} : { ttl: this.defaults.ttl };\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((hit) => hit.key === parsedPrefix || hit.key.startsWith(parsedPrefix + \".\"));\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(input?: CacheTtl | CacheSetOptions): CacheSetOptions | undefined {\n const options = normalizeToOptions(input);\n const ttl = 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(input: CacheTtl | RememberOptions): 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\";\nimport { ScopedCache } from \"./scoped-cache\";\nimport type {\n CacheConfigurations,\n CacheDriver,\n CacheEventHandler,\n CacheEventType,\n CacheKey,\n CacheListAccessor,\n CacheMetricsSnapshot,\n CacheNamespaceOptions,\n CacheSetOptions,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheSwrOptions,\n CacheTtl,\n DriverClass,\n LockOptions,\n LockOutcome,\n RememberOptions,\n ScopedCacheContract,\n TaggedCacheDriver,\n} from \"./types\";\nimport { CacheConfigurationError, CacheDriverNotInitializedError } from \"./types\";\n\nexport class CacheManager implements CacheDriver<any, any> {\n /**\n * Cache Driver\n */\n public currentDriver?: CacheDriver<any, any>;\n\n /**\n * Loaded drivers\n */\n public loadedDrivers: Record<string, CacheDriver<any, any>> = {};\n\n /**\n * In-flight `load()` promises, so concurrent callers share one `connect()`.\n */\n protected loadingDrivers = new Map<string, Promise<CacheDriver<any, any>>>();\n\n /**\n * Configurations list\n */\n protected configurations: CacheConfigurations = {\n drivers: {},\n options: {},\n };\n\n /**\n * Global event listeners\n */\n protected globalEventListeners: Map<CacheEventType, Set<CacheEventHandler>> = new Map();\n\n /**\n * Metrics collector — lazy on first {@link metrics} call so apps that\n * never read metrics pay zero cost. Once instantiated, it stays\n * subscribed to events for the manager's lifetime.\n */\n protected metricsCollector?: CacheMetricsCollector;\n\n /**\n * {@inheritdoc}\n */\n public name = \"cacheManager\";\n\n /**\n * {@inheritdoc}\n */\n public get client() {\n return this.currentDriver?.client;\n }\n\n /**\n * Set the cache configurations\n */\n public setCacheConfigurations(configurations: CacheConfigurations) {\n this.configurations.default = configurations.default;\n this.configurations.drivers = configurations.drivers;\n this.configurations.options = configurations.options;\n this.configurations.logging = configurations.logging;\n }\n\n /**\n * Set logging state\n */\n public setLoggingState(loggingState: boolean) {\n this.ensureDriverInitialized();\n\n this.currentDriver!.setLoggingState(loggingState);\n }\n\n /**\n * Switch the manager to a registered driver, optionally injecting runtime\n * options that merge over the static config.\n *\n * The string form looks the driver up in `setCacheConfigurations({ drivers })`,\n * loads it (or returns the cached instance), and sets it as `currentDriver`.\n * The instance form takes a pre-built driver and bypasses the registry; the\n * `runtimeOptions` argument is silently ignored in that case because the\n * instance was constructed externally.\n *\n * Runtime options merge over `config.options[name]` per-key — runtime wins.\n * Use this for constructor-only knobs that can't live in static config\n * (e.g. `pg`'s `client: pg.Pool`).\n *\n * @example\n * const pool = new Pool({ connectionString });\n * await cache.use(\"pg\", { client: pool });\n */\n public async use(driver: string | CacheDriver<any, any>, runtimeOptions?: Record<string, any>) {\n if (typeof driver === \"string\") {\n const driverInstance = await this.load(driver, runtimeOptions);\n\n if (!driverInstance) {\n throw new CacheConfigurationError(\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\n );\n }\n\n driver = driverInstance;\n }\n\n this.attachGlobalListeners(driver);\n\n if (this.configurations.logging !== undefined) {\n driver.setLoggingState(this.configurations.logging);\n }\n\n this.currentDriver = driver;\n\n return this;\n }\n\n /**\n * Ensure driver is initialized before operations\n */\n protected ensureDriverInitialized(): void {\n if (!this.currentDriver) {\n throw new CacheDriverNotInitializedError();\n }\n }\n\n /**\n * Return the running metrics snapshot — counters, hit-rate, latency\n * percentiles, per-driver breakdowns. Lazy-attaches the collector on\n * first call so apps that never read metrics pay zero cost.\n *\n * @example\n * const m = cache.metrics();\n * console.log(`hit rate: ${(m.hitRate * 100).toFixed(1)}%`);\n * console.log(`p95: ${m.latencyMs.p95.toFixed(2)}ms`);\n */\n public metrics(): CacheMetricsSnapshot {\n return this.ensureMetricsCollector().snapshot();\n }\n\n /**\n * Wipe every counter + latency sample and reset `startedAt` to now.\n * The collector itself stays subscribed to events.\n */\n public resetMetrics(): void {\n this.ensureMetricsCollector().reset();\n }\n\n /**\n * Lazy-construct the metrics collector and wire it to the global event\n * bus. Subsequent calls return the same instance — survives `cache.use()`\n * driver switches because handlers attach via `on()` and re-bind to every\n * loaded driver.\n */\n protected ensureMetricsCollector(): CacheMetricsCollector {\n if (this.metricsCollector) {\n return this.metricsCollector;\n }\n\n const collector = new CacheMetricsCollector();\n\n this.on(\"hit\", (data) => collector.recordEvent(\"hit\", data));\n this.on(\"miss\", (data) => collector.recordEvent(\"miss\", data));\n this.on(\"set\", (data) => collector.recordEvent(\"set\", data));\n this.on(\"removed\", (data) => collector.recordEvent(\"removed\", data));\n this.on(\"error\", (data) => collector.recordEvent(\"error\", data));\n\n this.metricsCollector = collector;\n\n return collector;\n }\n\n /**\n * Time the body, record the elapsed milliseconds against the metrics\n * collector for the given driver (defaults to the current driver's name).\n * Pass-through if the collector hasn't been instantiated yet — apps that\n * don't read metrics never pay for sample collection.\n */\n protected async timed<T>(body: () => Promise<T>, driverName?: string): Promise<T> {\n if (!this.metricsCollector) {\n return body();\n }\n\n const start = performance.now();\n\n try {\n return await body();\n } finally {\n const elapsed = performance.now() - start;\n const name = driverName ?? this.currentDriver?.name ?? \"unknown\";\n this.metricsCollector.recordLatency(name, elapsed);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n this.ensureDriverInitialized();\n return this.timed(() => this.currentDriver!.get<T>(key));\n }\n\n /**\n * Set a value in the cache.\n *\n * Accepts a positional TTL (number of seconds or duration string like `\"1h\"`)\n * or a rich {@link CacheSetOptions} object supporting `ttl`, `expiresAt`,\n * `tags`, `onConflict`, `namespace`, and per-call `driver` overrides.\n */\n public async set(key: CacheKey, value: any, ttlOrOptions?: CacheTtl | CacheSetOptions) {\n this.ensureDriverInitialized();\n\n const driverOverride =\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\n ? ttlOrOptions.driver\n : undefined;\n\n if (driverOverride) {\n const driver = await this.load(driverOverride);\n return this.timed(() => driver.set(key, value, ttlOrOptions), driver.name);\n }\n\n return this.timed(() => this.currentDriver!.set(key, value, ttlOrOptions));\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n this.ensureDriverInitialized();\n return this.timed(() => this.currentDriver!.remove(key));\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.ensureDriverInitialized();\n return this.currentDriver!.removeNamespace(namespace);\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.ensureDriverInitialized();\n return this.currentDriver!.flush();\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n this.ensureDriverInitialized();\n return this.currentDriver!.connect();\n }\n\n /**\n * {@inheritdoc}\n */\n public parseKey(key: CacheKey) {\n this.ensureDriverInitialized();\n return this.currentDriver!.parseKey(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public get options() {\n this.ensureDriverInitialized();\n return this.currentDriver!.options;\n }\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: Record<string, any>) {\n this.ensureDriverInitialized();\n return this.currentDriver!.setOptions(options || {});\n }\n\n /**\n * Return the loaded driver instance for `driverName`, loading it on first\n * call. Optional `runtimeOptions` follow the same merge-over-config rules\n * as {@link load}; passing options after the driver has already been\n * loaded throws to avoid silent swallowing.\n */\n public async driver(driverName: string, runtimeOptions?: Record<string, any>) {\n if (this.loadedDrivers[driverName]) {\n this.assertNoConflictingReload(driverName, runtimeOptions);\n\n return this.loadedDrivers[driverName];\n }\n\n return this.load(driverName, runtimeOptions);\n }\n\n /**\n * Initialize the cache manager and pick the default driver\n */\n public async init() {\n const defaultCacheDriverName = this.configurations.default;\n\n if (!defaultCacheDriverName) {\n return;\n }\n\n const driver = await this.driver(defaultCacheDriverName);\n\n await this.use(driver);\n }\n\n /**\n * Load and connect the registered driver named `driver`. First-call wins —\n * subsequent calls without `runtimeOptions` return the cached instance, and\n * subsequent calls *with* `runtimeOptions` throw {@link CacheConfigurationError}\n * to avoid silently dropping the new options.\n *\n * `runtimeOptions` merge over `config.options[driver]` per-key (runtime wins),\n * letting consumers split static knobs (table, ttl, globalPrefix) from\n * constructor-only ones (pg's `client`, custom adapters, etc.).\n *\n * @example\n * const pool = new Pool({ connectionString });\n * const pg = await cache.load(\"pg\", { client: pool });\n */\n public async load(driver: string, runtimeOptions?: Record<string, any>) {\n if (this.loadedDrivers[driver]) {\n this.assertNoConflictingReload(driver, runtimeOptions);\n\n return this.loadedDrivers[driver];\n }\n\n const inFlight = this.loadingDrivers.get(driver);\n\n if (inFlight) {\n this.assertNoConflictingReload(driver, runtimeOptions);\n\n return inFlight;\n }\n\n const Driver = this.configurations.drivers[\n driver as keyof typeof this.configurations.drivers\n ] as DriverClass | undefined;\n\n if (!Driver) {\n throw new CacheConfigurationError(\n `Cache driver ${driver} is not found, please declare it in the cache drivers in the configurations list.`,\n );\n }\n\n const driverInstance = new Driver();\n const configOptions =\n this.configurations.options[driver as keyof typeof this.configurations.options] || {};\n\n driverInstance.setOptions({ ...configOptions, ...(runtimeOptions ?? {}) });\n\n const promise = (async () => {\n try {\n await driverInstance.connect();\n\n this.attachGlobalListeners(driverInstance);\n\n this.loadedDrivers[driver] = driverInstance;\n\n return driverInstance as CacheDriver<any, any>;\n } finally {\n this.loadingDrivers.delete(driver);\n }\n })();\n\n this.loadingDrivers.set(driver, promise);\n\n return promise;\n }\n\n /**\n * Guard against silently dropping runtime options on a re-load. Once a\n * driver has been instantiated, its options are frozen — calling `load` /\n * `driver` / `use` again with a non-empty `runtimeOptions` would otherwise\n * appear to work but actually use the original options. We throw instead\n * so the misuse surfaces at the call site.\n */\n protected assertNoConflictingReload(\n driverName: string,\n runtimeOptions: Record<string, any> | undefined,\n ): void {\n if (runtimeOptions === undefined) {\n return;\n }\n\n if (Object.keys(runtimeOptions).length === 0) {\n return;\n }\n\n throw new CacheConfigurationError(\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.`,\n );\n }\n\n /**\n * Register and bind a driver\n */\n public registerDriver(driverName: string, driverClass: DriverClass) {\n (this.configurations.drivers as Record<string, DriverClass>)[driverName] = driverClass;\n }\n\n /**\n * Disconnect the cache manager\n */\n public async disconnect() {\n const drivers = new Set<any>(Object.values(this.loadedDrivers));\n\n if (this.currentDriver) {\n drivers.add(this.currentDriver);\n }\n\n await Promise.allSettled([...drivers].map((driver) => driver.disconnect()));\n\n for (const name of Object.keys(this.loadedDrivers)) {\n delete this.loadedDrivers[name];\n }\n\n this.loadingDrivers.clear();\n }\n\n /**\n * {@inheritdoc}\n */\n public async has(key: CacheKey): Promise<boolean> {\n this.ensureDriverInitialized();\n return this.currentDriver!.has(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n this.ensureDriverInitialized();\n\n const driverOverride =\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\n ? ttlOrOptions.driver\n : undefined;\n\n if (driverOverride) {\n const driver = await this.load(driverOverride);\n return driver.remember(key, ttlOrOptions, callback);\n }\n\n return this.currentDriver!.remember(key, ttlOrOptions, callback);\n }\n\n /**\n * Stale-while-revalidate. Returns cached when fresh, returns the stale\n * value plus a background refresh when within `freshTtl..staleTtl`,\n * blocks like a normal miss past `staleTtl`. Honors per-call `driver`\n * override the same way `remember()` does.\n *\n * @example\n * const product = await cache.swr(\n * \"product.42\",\n * { freshTtl: \"1m\", staleTtl: \"1h\" },\n * () => db.products.find(42),\n * );\n */\n public async swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n this.ensureDriverInitialized();\n\n const driverOverride = options.driver;\n\n if (driverOverride) {\n const driver = await this.load(driverOverride);\n\n return driver.swr<T>(key, options, callback);\n }\n\n return this.currentDriver!.swr<T>(key, options, callback);\n }\n\n /**\n * {@inheritdoc}\n */\n public async pull(key: CacheKey): Promise<any | null> {\n this.ensureDriverInitialized();\n return this.currentDriver!.pull(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async forever(key: CacheKey, value: any): Promise<any> {\n this.ensureDriverInitialized();\n return this.currentDriver!.forever(key, value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async increment(\n key: CacheKey,\n value?: number,\n options?: { ttl?: CacheTtl },\n ): Promise<number> {\n this.ensureDriverInitialized();\n return this.currentDriver!.increment(key, value, options);\n }\n\n /**\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value?: number): Promise<number> {\n this.ensureDriverInitialized();\n return this.currentDriver!.decrement(key, value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async many(keys: CacheKey[]): Promise<any[]> {\n this.ensureDriverInitialized();\n return this.currentDriver!.many(keys);\n }\n\n /**\n * {@inheritdoc}\n */\n public async setMany(items: Record<string, any>, ttl?: number): Promise<void> {\n this.ensureDriverInitialized();\n return this.currentDriver!.setMany(items, ttl);\n }\n\n /**\n * Register a global event listener (applies to all drivers)\n */\n public on(event: CacheEventType, handler: CacheEventHandler): this {\n if (!this.globalEventListeners.has(event)) {\n this.globalEventListeners.set(event, new Set());\n }\n this.globalEventListeners.get(event)!.add(handler);\n\n // Also attach to current driver if exists\n if (this.currentDriver) {\n this.currentDriver.on(event, handler);\n }\n\n // Attach to all loaded drivers\n for (const driver of Object.values(this.loadedDrivers)) {\n driver.on(event, handler);\n }\n\n return this;\n }\n\n /**\n * Remove a global event listener\n */\n public off(event: CacheEventType, handler: CacheEventHandler): this {\n const handlers = this.globalEventListeners.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n\n // Also remove from current driver\n if (this.currentDriver) {\n this.currentDriver.off(event, handler);\n }\n\n // Remove from all loaded drivers\n for (const driver of Object.values(this.loadedDrivers)) {\n driver.off(event, handler);\n }\n\n return this;\n }\n\n /**\n * Register a one-time global event listener\n */\n public once(event: CacheEventType, handler: CacheEventHandler): this {\n const onceHandler: CacheEventHandler = async (data) => {\n await handler(data);\n this.off(event, onceHandler);\n };\n return this.on(event, onceHandler);\n }\n\n /**\n * Attach global listeners to a driver\n */\n protected attachGlobalListeners(driver: CacheDriver<any, any>) {\n for (const [event, handlers] of this.globalEventListeners) {\n for (const handler of handlers) {\n driver.on(event, handler);\n }\n }\n }\n\n /**\n * Set if not exists (atomic operation)\n * Returns true if key was set, false if key already existed\n * Note: Only supported by drivers that implement setNX (e.g., Redis)\n */\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n this.ensureDriverInitialized();\n\n if (!this.currentDriver!.setNX) {\n throw new Error(\n `setNX is not supported by the current cache driver: ${this.currentDriver!.name}`,\n );\n }\n\n return this.currentDriver!.setNX(key, value, ttl);\n }\n\n /**\n * Create a tagged cache instance for the given tags\n */\n public tags(tags: string[]): TaggedCacheDriver {\n this.ensureDriverInitialized();\n return this.currentDriver!.tags(tags);\n }\n\n /**\n * Atomically read, transform, and write a cached value. Delegates to the current driver.\n */\n public async 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 this.ensureDriverInitialized();\n return this.currentDriver!.update<T>(key, fn, options);\n }\n\n /**\n * Shallow-merge a partial object into a cached value.\n */\n public async merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options?: { ttl?: CacheTtl },\n ): Promise<T> {\n this.ensureDriverInitialized();\n return this.currentDriver!.merge<T>(key, partial, options);\n }\n\n /**\n * Obtain a list accessor bound to the current driver.\n */\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\n this.ensureDriverInitialized();\n return this.currentDriver!.list<T>(key);\n }\n\n /**\n * Acquire a distributed lock, run `fn`, and auto-release. Returns a\n * {@link LockOutcome} discriminated union so callers can distinguish\n * \"ran and got this value\" from \"skipped because someone else holds it\".\n *\n * Honors the `driver` option for per-call driver override, same as `set`\n * and `remember`.\n *\n * @example\n * const outcome = await cache.lock(\"lock.import\", \"5m\", async () => {\n * await runImport();\n * return \"done\";\n * });\n * if (!outcome.acquired) {\n * console.log(\"another worker is already importing\");\n * }\n */\n public async lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | LockOptions,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>> {\n this.ensureDriverInitialized();\n\n const driverOverride =\n ttlOrOptions && typeof ttlOrOptions === \"object\" && \"driver\" in ttlOrOptions\n ? ttlOrOptions.driver\n : undefined;\n\n const driver = driverOverride ? await this.load(driverOverride) : this.currentDriver!;\n\n return driver.lock<T>(key, ttlOrOptions as CacheTtl | Omit<LockOptions, \"driver\">, fn);\n }\n\n /**\n * Similarity retrieval. Delegates to the current driver's `similar()` impl.\n *\n * Drivers that lack a similarity index throw {@link CacheUnsupportedError}.\n *\n * @example\n * const hits = await cache.similar(await embed(query), { topK: 5, threshold: 0.7 });\n */\n /**\n * Create a scoped view over the cache. Every key written through the\n * returned scope is automatically prefixed with `prefix`; optional defaults\n * (`ttl`, `tags`) flow through every write inside the scope.\n *\n * Per-call options always win over scope defaults. Scope tags merge\n * additively with per-call tags. Nested scopes inherit from the parent.\n *\n * @example\n * const chat = cache.namespace(\"chats.10\", { ttl: \"30d\" });\n * await chat.set(\"messages.1\", msg); // → \"chats.10.messages.1\", 30d\n * await chat.set(\"draft\", d, { ttl: \"1h\" }); // per-call ttl wins\n * await chat.namespace(\"typing\", { ttl: \"5s\" }).set(\"user.42\", true);\n * await chat.clear(); // wipe the whole scope\n */\n public namespace(prefix: string, options?: CacheNamespaceOptions): ScopedCacheContract {\n this.ensureDriverInitialized();\n return new ScopedCache(this, prefix, options);\n }\n\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n this.ensureDriverInitialized();\n return this.currentDriver!.similar<T>(vector, options);\n }\n}\n\nexport const cache = new CacheManager();\n","import { createHash } from \"node:crypto\";\nimport { CacheConfigurationError } from \"../types\";\n\n/**\n * Derive a cache key from a prefix and a set of function arguments.\n *\n * The key is `<prefix>.<hash>` where `hash` is the first 16 hex chars of the\n * sha1 of a stable JSON encoding of the args. Hashing keeps the mapping\n * injective — raw punctuation-joined segments can be folded together by\n * `parseCacheKey` (e.g. `(\"1.private\", \"\")` vs `(\"1\", \"private\")`).\n *\n * Encoding rules: object keys are sorted, `Date` → ISO string, `undefined` →\n * `{\"$u\":1}`, `bigint` → decimal string with an `n` suffix.\n *\n * No args → the prefix alone.\n *\n * Map, Set, functions, symbols and class instances cannot be encoded stably\n * and throw `CacheConfigurationError`; supply a custom `key` function instead.\n *\n * @example\n * deriveAutoKey(\"featured\", []); // \"featured\"\n * deriveAutoKey(\"user\", [42]); // \"user.<16 hex chars>\"\n */\nexport function deriveAutoKey(prefix: string, args: readonly unknown[]): string {\n if (args.length === 0) {\n return prefix;\n }\n\n let encoded: string;\n\n try {\n encoded = JSON.stringify(args.map((arg) => normalize(arg, prefix, new Set())));\n } catch (error) {\n if (error instanceof CacheConfigurationError) {\n throw error;\n }\n\n throw new CacheConfigurationError(\n `cached(): could not derive an auto-key from args for prefix \"${prefix}\". ` +\n `The args are not serializable. Use the options form with a custom key function. ` +\n `Original error: ${(error as Error).message}`,\n );\n }\n\n const hash = createHash(\"sha1\").update(encoded).digest(\"hex\").slice(0, 16);\n\n return `${prefix}.${hash}`;\n}\n\nfunction unsupported(prefix: string, what: string): never {\n throw new CacheConfigurationError(\n `cached(): cannot derive an auto-key for prefix \"${prefix}\" from ${what}. ` +\n `Use the options form with a custom key function.`,\n );\n}\n\nfunction normalize(value: unknown, prefix: string, seen: Set<object>): unknown {\n if (value === undefined) return { $u: 1 };\n if (value === null) return null;\n\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return value;\n case \"number\":\n // NaN / Infinity would collapse to null in JSON\n return Number.isFinite(value) ? value : { $n: String(value) };\n case \"bigint\":\n return `${value.toString()}n`;\n case \"function\":\n return unsupported(prefix, \"a function argument\");\n case \"symbol\":\n return unsupported(prefix, \"a symbol argument\");\n }\n\n const obj = value as object;\n\n if (obj instanceof Date) {\n return Number.isNaN(obj.getTime()) ? { $d: \"invalid\" } : obj.toISOString();\n }\n\n if (obj instanceof Map) return unsupported(prefix, \"a Map argument\");\n if (obj instanceof Set) return unsupported(prefix, \"a Set argument\");\n\n if (seen.has(obj)) {\n throw new CacheConfigurationError(\n `cached(): could not derive an auto-key for prefix \"${prefix}\": circular reference in args. ` +\n `Use the options form with a custom key function.`,\n );\n }\n\n seen.add(obj);\n\n let result: unknown;\n\n if (Array.isArray(obj)) {\n result = obj.map((item) => normalize(item, prefix, seen));\n } else {\n const proto = Object.getPrototypeOf(obj);\n\n if (proto !== Object.prototype && proto !== null) {\n unsupported(prefix, \"a class instance argument\");\n }\n\n const out: Record<string, unknown> = {};\n\n for (const key of Object.keys(obj).sort()) {\n out[key] = normalize((obj as Record<string, unknown>)[key], prefix, seen);\n }\n\n result = out;\n }\n\n seen.delete(obj);\n\n return result;\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 if (config.driver) {\n await (await cache.driver(config.driver)).remove(key);\n return;\n }\n\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 * runs through `driver.update()`, so it is serialized in-process and keeps the\n * entry's remaining TTL.\n *\n * **Responsibility.**\n * - Owns: translating list operations into array mutations + driver writes.\n * - Does NOT own: cross-process concurrency control (callers should wrap in a\n * distributed lock when multi-process writers are possible), or tagging of\n * 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 * Serialized read-modify-write through `driver.update()`. No ttl is passed,\n * so the driver keeps the entry's remaining TTL. An empty list removes the\n * entry to keep the store clean.\n */\n private async mutate<R>(mutation: (items: T[]) => R): Promise<R> {\n let result!: R;\n\n await this.driver.update<T[]>(this.key, (current) => {\n const items = Array.isArray(current) ? [...current] : [];\n\n result = mutation(items);\n\n return items.length === 0 ? null : items;\n });\n\n return result;\n }\n\n /**\n * {@inheritdoc}\n */\n public push(...items: T[]): Promise<number> {\n return this.mutate((current) => {\n current.push(...items);\n\n return current.length;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public unshift(...items: T[]): Promise<number> {\n return this.mutate((current) => {\n current.unshift(...items);\n\n return current.length;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public pop(): Promise<T | null> {\n return this.mutate((current) => (current.length === 0 ? null : (current.pop() as T)));\n }\n\n /**\n * {@inheritdoc}\n */\n public shift(): Promise<T | null> {\n return this.mutate((current) => (current.length === 0 ? null : (current.shift() as T)));\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 * Keep the inclusive range `[start, end]` like Redis LTRIM; negative\n * indexes count from the end.\n */\n public async trim(start: number, end: number): Promise<void> {\n await this.mutate((current) => {\n const length = current.length;\n const from = start < 0 ? Math.max(length + start, 0) : start;\n const to = end < 0 ? length + end : end;\n const kept = current.slice(from, to + 1);\n\n current.splice(0, current.length, ...kept);\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async clear(): Promise<void> {\n await this.driver.remove(this.key);\n }\n}\n","import type { CacheDriver, CacheKey, CacheSetOptions, CacheTtl, TaggedCacheDriver } from \"./types\";\nimport { parseCacheKey } from \"./utils\";\n\n/** Implemented by `BaseCacheDriver`; a custom driver may not have it. */\ntype TagsInvalidatedEmitter = {\n emitTagsInvalidated(data: { tags: string[]; keys: string[]; durationMs: number }): Promise<void>;\n};\n\n/**\n * Tagged Cache Wrapper\n * Wraps a cache driver to automatically manage tag relationships\n */\nexport class TaggedCache implements TaggedCacheDriver {\n /**\n * The tags associated with this tagged cache instance\n */\n protected cacheTags: string[];\n\n /**\n * The underlying cache driver\n */\n protected driver: CacheDriver<any, any>;\n\n /**\n * Constructor\n */\n public constructor(tags: string[], driver: CacheDriver<any, any>) {\n this.cacheTags = tags;\n this.driver = driver;\n }\n\n /**\n * Get the tag key prefix for storing tag-key relationships\n */\n protected tagKey(tag: string): string {\n return `cache:tags:${tag}`;\n }\n\n /**\n * Store tag-key relationship\n */\n protected async storeTaggedKey(key: CacheKey): Promise<void> {\n await this.storeTagRelationship(key);\n }\n\n /**\n * Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`\n * when tags are passed inline through `CacheSetOptions.tags`.\n *\n * Takes the caller's key, never the driver-parsed one — see {@link indexedKey}.\n *\n * @internal — public for cross-class use within this package; not part of the\n * stable consumer API.\n */\n public async storeTagRelationship(key: CacheKey): Promise<void> {\n const indexedKey = this.indexedKey(key);\n\n for (const tag of this.cacheTags) {\n const tagKey = this.tagKey(tag);\n\n await this.addToTag(tagKey, [indexedKey]);\n await this.pruneTag(tagKey, indexedKey);\n }\n }\n\n /**\n * Upper bound on member-existence checks per tagged write, so pruning stays\n * O(1) per write no matter how large an index grows.\n */\n protected static readonly PRUNE_CHECKS_PER_WRITE = 20;\n\n /**\n * Drop index members whose entry no longer exists (expired, evicted, or\n * removed outside a tagged handle). Checks a random window of at most\n * {@link PRUNE_CHECKS_PER_WRITE} members, so a long-lived index converges\n * to its live members without any write paying for a full scan.\n */\n protected async pruneTag(tagKey: string, justWritten: string): Promise<void> {\n const members = (await this.membersOf(tagKey)).filter((member) => member !== justWritten);\n\n if (members.length === 0) {\n return;\n }\n\n const limit = TaggedCache.PRUNE_CHECKS_PER_WRITE;\n const start = members.length > limit ? Math.floor(Math.random() * members.length) : 0;\n const window = Array.from(\n { length: Math.min(limit, members.length) },\n (_, index) => members[(start + index) % members.length]!,\n );\n\n const missing: string[] = [];\n\n for (const member of window) {\n if (!(await this.driver.has(member))) {\n missing.push(member);\n }\n }\n\n if (missing.length > 0) {\n await this.removeFromTag(tagKey, missing);\n }\n }\n\n // ------------------------------------------------------------\n // Tag-index access: the driver's primitives when it has them, else\n // the legacy get/set array (third-party drivers without primitives).\n // ------------------------------------------------------------\n\n protected async addToTag(tagKey: string, members: string[]): Promise<void> {\n if (typeof this.driver.tagAdd === \"function\") {\n return this.driver.tagAdd(tagKey, members);\n }\n\n const current = await this.legacyMembers(tagKey);\n const merged = [...new Set([...current, ...members])];\n\n if (merged.length !== current.length) {\n await this.driver.set(tagKey, merged, Infinity);\n }\n }\n\n protected async membersOf(tagKey: string): Promise<string[]> {\n if (typeof this.driver.tagMembers === \"function\") {\n return this.driver.tagMembers(tagKey);\n }\n\n return this.legacyMembers(tagKey);\n }\n\n protected async removeFromTag(tagKey: string, members: string[]): Promise<void> {\n if (typeof this.driver.tagRemove === \"function\") {\n return this.driver.tagRemove(tagKey, members);\n }\n\n const dropped = new Set(members);\n const current = await this.legacyMembers(tagKey);\n const kept = current.filter((member) => !dropped.has(member));\n\n if (kept.length === current.length) {\n return;\n }\n\n if (kept.length === 0) {\n await this.driver.remove(tagKey);\n } else {\n await this.driver.set(tagKey, kept, Infinity);\n }\n }\n\n protected async legacyMembers(tagKey: string): Promise<string[]> {\n const value = await this.driver.get(tagKey);\n\n return Array.isArray(value)\n ? value.filter((member): member is string => typeof member === \"string\")\n : [];\n }\n\n /**\n * The form a key takes inside a tag index: normalized but un-prefixed.\n *\n * The index is read back and deleted through `driver.remove(key)`, which\n * applies `globalPrefix` itself — so the index must hold the key *before*\n * the prefix, or invalidation would prefix it twice and delete nothing.\n * With a function prefix, the current prefix is re-applied at invalidation\n * time; the tag index key is resolved under that same prefix, so the two\n * agree as long as the prefix is stable for a given request/tenant.\n */\n protected indexedKey(key: CacheKey): string {\n return parseCacheKey(key);\n }\n\n /**\n * Get all keys associated with tags\n */\n protected async getTaggedKeys(): Promise<Set<string>> {\n const allKeys = new Set<string>();\n\n for (const tag of this.cacheTags) {\n for (const key of await this.membersOf(this.tagKey(tag))) {\n allKeys.add(key);\n }\n }\n\n return allKeys;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n await this.driver.set(key, value, ttlOrOptions);\n\n await this.storeTaggedKey(key);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey): Promise<any | null> {\n return this.driver.get(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey): Promise<void> {\n const indexedKey = this.indexedKey(key);\n\n // Remove the value\n await this.driver.remove(key);\n\n // Remove from all tag relationships\n await this.detachFromTags(indexedKey);\n }\n\n /**\n * Invalidate (clear) all keys associated with the current tags\n */\n public async invalidate(): Promise<void> {\n const startedAt = performance.now();\n const removed = new Set<string>();\n\n for (const tag of this.cacheTags) {\n const tagKey = this.tagKey(tag);\n const members = await this.membersOf(tagKey);\n\n // Remove the tagged entries — indexed un-prefixed, so `remove()` prefixes once\n for (const member of members) {\n await this.driver.remove(member);\n removed.add(member);\n }\n\n // Remove exactly the members read above rather than dropping the whole\n // index: a key tagged concurrently (after the read) keeps its entry in\n // the index, so a later invalidate still reaches it.\n await this.removeFromTag(tagKey, members);\n }\n\n // Drivers built on `BaseCacheDriver` emit `invalidated`; a custom driver\n // without the method simply emits nothing.\n const driver = this.driver as Partial<TagsInvalidatedEmitter>;\n\n await driver.emitTagsInvalidated?.({\n tags: [...this.cacheTags],\n keys: [...removed],\n durationMs: performance.now() - startedAt,\n });\n }\n\n /**\n * Remove `indexedKey` from every tag of this handle.\n */\n protected async detachFromTags(indexedKey: string): Promise<void> {\n for (const tag of this.cacheTags) {\n await this.removeFromTag(this.tagKey(tag), [indexedKey]);\n }\n }\n\n /**\n * Flush all keys associated with the current tags\n * @deprecated Use invalidate() instead for better semantics\n */\n public async flush(): Promise<void> {\n return this.invalidate();\n }\n\n /**\n * {@inheritdoc}\n */\n public async has(key: CacheKey): Promise<boolean> {\n return this.driver.has(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remember(key: CacheKey, ttl: number, callback: () => Promise<any>): Promise<any> {\n const value = await this.get(key);\n\n if (value !== null) {\n return value;\n }\n\n const result = await callback();\n await this.set(key, result, ttl);\n\n return result;\n }\n\n /**\n * {@inheritdoc}\n */\n public async pull(key: CacheKey): Promise<any | null> {\n // The driver's own `pull` (atomic on redis / pg / in-process drivers)\n // hands the value out once; then the key leaves this handle's tags.\n const value = await this.driver.pull(key);\n\n if (value !== null) {\n await this.detachFromTags(this.indexedKey(key));\n }\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async forever(key: CacheKey, value: any): Promise<any> {\n return this.set(key, value, Infinity);\n }\n\n /**\n * {@inheritdoc}\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options?: { ttl?: CacheTtl },\n ): Promise<number> {\n // The driver's atomic increment (INCRBY / pg upsert / synchronous\n // in-process), then record the tag relationship.\n const newValue = await this.driver.increment(key, value, options);\n\n await this.storeTaggedKey(key);\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\n return this.increment(key, -value);\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport { log, maskEmailsInText } from \"@warlock.js/logger\";\nimport { MemoryCacheList } from \"../list/memory-cache-list\";\nimport { TaggedCache } from \"../tagged-cache\";\nimport type {\n CacheConflictPolicy,\n CacheData,\n CacheDriver,\n CacheEventData,\n CacheEventHandler,\n CacheEventType,\n CacheKey,\n CacheListAccessor,\n CacheOperationType,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheSwrOptions,\n CacheTtl,\n LockOptions,\n LockOutcome,\n RememberOptions,\n} from \"../types\";\nimport { CacheConfigurationError, CacheUnsupportedError } from \"../types\";\nimport { normalizeToOptions, parseCacheKey, parseTtl, resolveTtl, safeErrorInfo } from \"../utils\";\n\n/**\n * Normalized form of the 3rd `set` argument.\n *\n * All drivers operate on this shape internally regardless of whether the caller\n * passed a positional TTL, a duration string, or a rich options object.\n */\nexport type NormalizedSetOptions = {\n /**\n * Final TTL in seconds — already merged with the driver-level default\n * (`this.options.ttl`). `Infinity` means \"no expiration\".\n *\n * Always populated. Drivers do NOT need to fall back to `this.ttl`\n * themselves — `resolveSetOptions` does the merge centrally so that\n * every driver respects the configured default without ceremony.\n */\n ttl: number;\n /**\n * Inline tag list, or undefined when none were provided.\n */\n tags?: string[];\n /**\n * Conflict policy. Defaults to `\"upsert\"`.\n */\n onConflict: CacheConflictPolicy;\n /**\n * Optional embedding vector for similarity retrieval. Drivers that do not\n * support similarity must throw {@link CacheUnsupportedError} when this is\n * present (rather than silently dropping it).\n */\n vector?: number[];\n /**\n * Optional freshness deadline as a millisecond timestamp. Set by `swr()`\n * to mark when the entry stops being \"fresh\" and becomes\n * \"stale-but-revalidatable.\" Drivers route this through\n * `prepareDataForStorage` so it persists in the wrapper.\n */\n staleAt?: number;\n};\n\nconst messages = {\n clearing: \"Clearing namespace\",\n cleared: \"Namespace cleared\",\n fetching: \"Fetching key\",\n fetched: \"Key fetched\",\n caching: \"Caching key\",\n cached: \"Key cached\",\n flushing: \"Flushing cache\",\n flushed: \"Cache flushed\",\n removing: \"Removing key\",\n removed: \"Key removed\",\n expired: \"Key expired\",\n notFound: \"Key not found\",\n connecting: \"Connecting to the cache engine.\",\n connected: \"Connected to the cache engine.\",\n disconnecting: \"Disconnecting from the cache engine.\",\n disconnected: \"Disconnected from the cache engine.\",\n connectionFailed: \"Failed to connect to the cache engine.\",\n error: \"Error occurred\",\n};\n\nexport abstract class BaseCacheDriver<\n ClientType,\n Options extends Record<string, any>,\n> implements CacheDriver<ClientType, Options> {\n /**\n * CLient driver\n */\n protected clientDriver!: ClientType;\n\n /**\n * Determine whether to log or not\n */\n protected shouldLog: boolean = true;\n\n /**\n * {@inheritdoc}\n */\n public get client() {\n return (this.clientDriver || this) as unknown as ClientType;\n }\n\n /**\n * Set logging state\n */\n public setLoggingState(shouldLog: boolean) {\n this.shouldLog = shouldLog;\n\n return this;\n }\n\n /**\n * Set client driver\n */\n public set client(client: ClientType) {\n this.clientDriver = client;\n }\n\n /**\n * Get the cache driver name\n */\n public abstract name: string;\n\n /**\n * Options list\n */\n public options!: Options;\n\n /**\n * Event listeners storage\n */\n protected eventListeners: Map<CacheEventType, Set<CacheEventHandler>> = new Map();\n\n /**\n * {@inheritdoc}\n */\n public parseKey(key: CacheKey) {\n return parseCacheKey(key, this.options);\n }\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: Options) {\n this.options = options || {};\n return this;\n }\n\n /**\n * Register an event listener\n */\n public on(event: CacheEventType, handler: CacheEventHandler): this {\n if (!this.eventListeners.has(event)) {\n this.eventListeners.set(event, new Set());\n }\n this.eventListeners.get(event)!.add(handler);\n return this;\n }\n\n /**\n * Remove an event listener\n */\n public off(event: CacheEventType, handler: CacheEventHandler): this {\n const handlers = this.eventListeners.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n return this;\n }\n\n /**\n * Register a one-time event listener\n */\n public once(event: CacheEventType, handler: CacheEventHandler): this {\n const onceHandler: CacheEventHandler = async (data) => {\n await handler(data);\n this.off(event, onceHandler);\n };\n return this.on(event, onceHandler);\n }\n\n /**\n * Emit an event to all registered listeners\n */\n protected async emit(event: CacheEventType, data: Partial<CacheEventData> = {}): Promise<void> {\n const handlers = this.eventListeners.get(event);\n if (!handlers || handlers.size === 0) return;\n\n const eventData: CacheEventData = {\n driver: this.name,\n ...data,\n };\n\n // Execute all handlers\n const promises: Promise<void>[] = [];\n for (const handler of handlers) {\n try {\n const result = handler(eventData);\n if (result instanceof Promise) {\n promises.push(result);\n }\n } catch (error) {\n this.logError(`Error in event handler for '${event}'`, error);\n }\n }\n\n // Wait for all async handlers\n if (promises.length > 0) {\n await Promise.allSettled(promises);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public abstract removeNamespace(namespace: string): Promise<any>;\n\n /**\n * {@inheritdoc}\n */\n public abstract set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any>;\n\n /**\n * Normalize the 3rd argument of a `set` call into a single shape every driver\n * can act on. Handles TTL parsing (number | string | Infinity), `expiresAt` →\n * relative TTL conversion, and mutual-exclusion validation.\n *\n * @throws {CacheConfigurationError} when `ttl` and `expiresAt` are passed together\n * or an unparseable duration string is supplied.\n */\n protected resolveSetOptions(ttlOrOptions?: CacheTtl | CacheSetOptions): NormalizedSetOptions {\n const options = normalizeToOptions(ttlOrOptions);\n\n return {\n ttl: resolveTtl(options.ttl, options.expiresAt, this.ttl),\n tags: options.tags,\n onConflict: options.onConflict ?? \"upsert\",\n vector: options.vector,\n staleAt: options.staleAt,\n };\n }\n\n /**\n * Resolve the union of cache keys associated with any of the given tags.\n * Used by `similar()` to narrow the candidate pool before similarity ranking.\n *\n * Returns `null` when no tags are passed (callers should treat that as \"no filter\").\n * Keys come back parsed (prefixed) — the index stores them un-prefixed, but\n * callers match them against their own parsed storage keys.\n */\n protected async getKeysForTags(tags: string[] | undefined): Promise<Set<string> | null> {\n if (!tags || tags.length === 0) {\n return null;\n }\n\n const allKeys = new Set<string>();\n for (const tag of tags) {\n const keys = await this.tagMembers(`cache:tags:${tag}`);\n for (const k of keys) {\n allKeys.add(this.parseKey(k));\n }\n }\n\n return allKeys;\n }\n\n // ============================================================\n // Tag index primitives\n // ============================================================\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: the index is an ordinary entry holding a string\n * array, read-modified-written under {@link runSerialized} — correct within\n * THIS process only. Drivers with a native primitive override it (redis SET,\n * pg atomic upsert, the in-process drivers' {@link InMemoryTagIndex}).\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n await this.runSerialized(this.parseKey(tagKey), async () => {\n const current = await this.readTagArray(tagKey);\n const merged = new Set(current);\n\n for (const member of members) {\n merged.add(member);\n }\n\n if (merged.size !== current.length) {\n await this.set(tagKey, [...merged], Infinity);\n }\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.readTagArray(tagKey);\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: serialized read-filter-write (process-local); an\n * emptied index is removed.\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n await this.runSerialized(this.parseKey(tagKey), async () => {\n const current = await this.readTagArray(tagKey);\n const dropped = new Set(members);\n const kept = current.filter((member) => !dropped.has(member));\n\n if (kept.length === current.length) {\n return;\n }\n\n if (kept.length === 0) {\n await this.remove(tagKey);\n } else {\n await this.set(tagKey, kept, Infinity);\n }\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n await this.runSerialized(this.parseKey(tagKey), () => this.remove(tagKey));\n }\n\n /**\n * The index stored as a plain entry, or `[]`. Non-string members (a corrupted\n * or foreign value) are ignored rather than trusted.\n */\n protected async readTagArray(tagKey: CacheKey): Promise<string[]> {\n const value = await this.get(tagKey);\n\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.filter((member): member is string => typeof member === \"string\");\n }\n\n /**\n * Apply tag relationships after a successful write. Called by drivers once\n * the value is in storage.\n *\n * Pass the caller's key, not the parsed one: the tag index stores keys\n * un-prefixed so `invalidate()` can delete them through `remove(key)`.\n */\n protected async applyTags(key: CacheKey, tags: string[]): Promise<void> {\n if (tags.length === 0) {\n return;\n }\n\n const tagged = this.tags(tags);\n await (tagged as TaggedCache).storeTagRelationship(key);\n }\n\n /**\n * {@inheritdoc}\n */\n public abstract get(key: CacheKey): Promise<any>;\n\n /**\n * {@inheritdoc}\n */\n public abstract remove(key: CacheKey): Promise<void>;\n\n /**\n * {@inheritdoc}\n */\n public abstract flush(): Promise<void>;\n\n /**\n * {@inheritdoc}\n */\n public async has(key: CacheKey): Promise<boolean> {\n const value = await this.get(key);\n // Event is emitted by get() method\n return value !== null;\n }\n\n /**\n * In-flight `remember()` computations, one per parsed key (stampede guard).\n */\n protected rememberInFlight: Map<string, Promise<any>> = new Map();\n\n /**\n * In-flight `swr()` cold-miss fetches, one per parsed key.\n */\n protected swrFetchInFlight: Map<string, Promise<any>> = new Map();\n\n /**\n * In-flight `swr()` stale-window background refreshes, one per parsed key.\n */\n protected swrRefreshes: Map<string, Promise<void>> = new Map();\n\n /**\n * Per-key serialization chains for the in-process read-modify-write\n * operations (`update`, and the default `increment` / `pull`). Kept apart\n * from the stampede maps so a `remember()` can never break an update chain,\n * and never receives an unrelated promise.\n */\n protected updateChains: Map<string, Promise<any>> = new Map();\n\n /**\n * Run `task` once per key at a time: concurrent callers for the same\n * `parsedKey` share the in-flight promise registered in `inFlight`.\n */\n protected singleFlight<T>(\n inFlight: Map<string, Promise<any>>,\n parsedKey: string,\n task: () => Promise<T>,\n ): Promise<T> {\n const existing = inFlight.get(parsedKey);\n\n if (existing) {\n return existing as Promise<T>;\n }\n\n const promise: Promise<T> = task().finally(() => {\n if (inFlight.get(parsedKey) === promise) {\n inFlight.delete(parsedKey);\n }\n });\n\n inFlight.set(parsedKey, promise);\n\n return promise;\n }\n\n /**\n * Chain `task` after every earlier serialized task for the same key, so\n * read-modify-write callers in THIS process run one at a time. This is\n * process-local: it does not coordinate with other servers.\n */\n protected runSerialized<T>(parsedKey: string, task: () => Promise<T>): Promise<T> {\n const previous = this.updateChains.get(parsedKey) ?? Promise.resolve();\n\n const next = previous.catch(() => undefined).then(task);\n\n this.updateChains.set(parsedKey, next);\n\n // Clean up the slot once this link finishes, but only if nobody chained a\n // follow-up onto it meanwhile. The trailing catch keeps a rejected link\n // from surfacing as an unhandled rejection here (the caller still sees it).\n next\n .finally(() => {\n if (this.updateChains.get(parsedKey) === next) {\n this.updateChains.delete(parsedKey);\n }\n })\n .catch(() => undefined);\n\n return next;\n }\n\n /**\n * {@inheritdoc}\n */\n public async remember(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<any>,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n\n // The options-form lets callers forward tags / driver-override through to\n // the cache-miss write. Normalize both shapes into a single CacheSetOptions\n // blob so there's one path from here on.\n const setOptions = this.normalizeRememberOptions(ttlOrOptions);\n\n // Only a real miss (`null`) recomputes: cached falsy values such as\n // `0`, `false` and `\"\"` are returned as-is.\n const cachedValue = await this.get(key);\n\n if (cachedValue !== null && cachedValue !== undefined) {\n return cachedValue;\n }\n\n return this.singleFlight(this.rememberInFlight, parsedKey, async () => {\n const result = await callback();\n\n await this.set(key, result, setOptions);\n\n return result;\n });\n }\n\n /**\n * Resolve the TTL-or-options arg of `remember` into a `CacheSetOptions` object\n * that can be passed straight to `set()`. Keeps the implementation unbranched.\n */\n protected normalizeRememberOptions(ttlOrOptions: CacheTtl | RememberOptions): CacheSetOptions {\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\n return { ttl: ttlOrOptions };\n }\n\n return {\n ttl: ttlOrOptions.ttl,\n tags: ttlOrOptions.tags,\n };\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: read raw entry, branch on freshness/staleness,\n * trigger background refresh in the stale window, fall through to\n * `callback` on miss/expiry. Concurrent cold-miss callers share one fetch\n * ({@link swrFetchInFlight}); concurrent stale-window callers share one\n * background refresh ({@link swrRefreshes}).\n *\n * Drivers without a real {@link getEntry} override degrade gracefully —\n * the synthetic entry has no `staleAt`, which the freshness check treats\n * as \"always fresh,\" so SWR behaves like a TTL-only cached read on those\n * drivers (no background refresh, but no double-fetch either).\n */\n public async swr<T = any>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n const parsedKey = this.parseKey(key);\n const freshSeconds = parseTtl(options.freshTtl);\n const staleSeconds = parseTtl(options.staleTtl);\n\n if (staleSeconds <= freshSeconds) {\n throw new Error(\n `cache.swr: 'staleTtl' (${staleSeconds}s) must be greater than 'freshTtl' (${freshSeconds}s).`,\n );\n }\n\n const entry = await this.getEntry(key);\n const now = Date.now();\n\n const isExpired = entry?.expiresAt !== undefined && entry.expiresAt <= now;\n\n if (!entry || isExpired) {\n return this.singleFlight(this.swrFetchInFlight, parsedKey, () =>\n this.swrFetchAndStore<T>(key, options, callback, freshSeconds, staleSeconds),\n );\n }\n\n const isFresh = entry.staleAt === undefined || entry.staleAt > now;\n\n if (isFresh) {\n return entry.data as T;\n }\n\n this.scheduleSwrRefresh<T>(parsedKey, key, options, callback, freshSeconds, staleSeconds);\n\n return entry.data as T;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper for a key, including any\n * `expiresAt` / `staleAt` metadata. Default implementation falls back to\n * `get()` and synthesizes a metadata-less wrapper — drivers that store\n * the wrapper directly (memory, lru, file, redis, pg, mock) override\n * this to return real metadata so SWR can branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const value = await this.get(key);\n\n if (value === null) {\n return null;\n }\n\n return { data: value };\n }\n\n /**\n * Remaining lifetime of an existing entry, in seconds — used by TTL-preserving\n * writes such as `update()` / `merge()` when the caller passes no explicit\n * `ttl`.\n *\n * - `Infinity` — the entry exists with no expiry (preserve \"never expires\").\n * - positive number — seconds left before the entry expires.\n * - `undefined` — the key is missing or already past its deadline; the caller\n * should fall back to the driver default TTL.\n *\n * Default reads `expiresAt` from {@link getEntry}, which the metadata-aware\n * drivers (memory, lru, mock, pg) populate. Drivers that track TTL natively\n * and don't carry `expiresAt` in their payload (Redis) override this.\n */\n protected async getRemainingTtl(key: CacheKey): Promise<number | undefined> {\n const entry = await this.getEntry(key);\n\n if (!entry) {\n return undefined;\n }\n\n if (!entry.expiresAt || entry.expiresAt === Infinity) {\n return Infinity;\n }\n\n const remainingSeconds = Math.ceil((entry.expiresAt - Date.now()) / 1000);\n\n return remainingSeconds > 0 ? remainingSeconds : undefined;\n }\n\n /**\n * Block-and-fetch path of `swr()`: invoked on miss or past-`staleTtl`\n * expiry. Writes through `set()` with the SWR options translated into\n * standard `CacheSetOptions` (ttl = staleTtl, staleAt = now + freshTtl).\n */\n protected async swrFetchAndStore<T>(\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n freshSeconds: number,\n staleSeconds: number,\n ): Promise<T> {\n const result = await callback();\n\n await this.set(key, result, {\n ttl: staleSeconds,\n staleAt: Date.now() + freshSeconds * 1000,\n tags: options.tags,\n });\n\n return result;\n }\n\n /**\n * Stale-window background refresh. Registers a single in-flight promise\n * per parsed key so concurrent SWR callers share one refresh. Failed\n * refreshes preserve the stale entry, log via `logError`, and emit on\n * `error` — the stale-returning caller never sees the failure.\n */\n protected scheduleSwrRefresh<T>(\n parsedKey: string,\n key: CacheKey,\n options: CacheSwrOptions,\n callback: () => Promise<T>,\n freshSeconds: number,\n staleSeconds: number,\n ): void {\n if (this.swrRefreshes.has(parsedKey)) {\n return;\n }\n\n let refresh!: Promise<void>;\n refresh = (async () => {\n try {\n const result = await callback();\n\n await this.set(key, result, {\n ttl: staleSeconds,\n staleAt: Date.now() + freshSeconds * 1000,\n tags: options.tags,\n });\n } catch (error) {\n this.logError(`SWR background refresh failed for ${parsedKey}`, error);\n await this.emit(\"error\", { key: parsedKey, error });\n } finally {\n if (this.swrRefreshes.get(parsedKey) === refresh) {\n this.swrRefreshes.delete(parsedKey);\n }\n }\n })();\n\n this.swrRefreshes.set(parsedKey, refresh);\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: get-then-remove, serialized per key in THIS\n * process. Drivers with a native primitive override it to be atomic across\n * servers (redis `GETDEL`, pg `DELETE … RETURNING`) or process-atomic\n * (memory, lru, mock).\n */\n public async pull(key: CacheKey): Promise<any | null> {\n return this.runSerialized(this.parseKey(key), async () => {\n // Events are emitted by get() and remove()\n const value = await this.get(key);\n\n if (value !== null) {\n await this.remove(key);\n }\n\n return value;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async forever(key: CacheKey, value: any): Promise<any> {\n // Event is emitted by set() method\n return this.set(key, value, Infinity);\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: read-add-write serialized per key in THIS process,\n * keeping the entry's deadline (a missing key starts from 0 with the driver\n * default TTL). Not atomic across servers: drivers with a native primitive\n * override it (redis `INCRBY`, pg single-statement upsert).\n *\n * The value and its deadline come from ONE read. Reading them separately\n * let a counter that expired in between be rewritten from its stale value\n * with no expiry at all (a login-throttle counter that then never resets).\n * The remaining TTL is rounded down, so repeated increments can never push\n * a window's deadline later.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const parsedKey = this.parseKey(key);\n\n return this.runSerialized(parsedKey, async () => {\n const entry = await this.getEntry(key);\n const now = Date.now();\n const expiresAt = entry?.expiresAt;\n const live = entry ? expiresAt == null || expiresAt > now : false;\n const current = entry && live ? entry.data : null;\n const base = current === null || current === undefined ? 0 : current;\n\n if (typeof base !== \"number\") {\n throw new Error(\n `Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`,\n );\n }\n\n const newValue = base + value;\n\n if (!live) {\n // missing, or expired since it was read: a fresh counter\n await this.set(key, newValue, { ttl });\n } else if (expiresAt == null || expiresAt === Infinity) {\n await this.set(key, newValue, { ttl: options.ttl === undefined ? Infinity : ttl });\n } else {\n await this.set(key, newValue, {\n ttl: Math.max(1, Math.floor((expiresAt - now) / 1000)),\n });\n }\n\n return newValue;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\n return this.increment(key, -value);\n }\n\n /**\n * {@inheritdoc}\n */\n public async many(keys: CacheKey[]): Promise<any[]> {\n return Promise.all(keys.map((key) => this.get(key)));\n }\n\n /**\n * {@inheritdoc}\n */\n public async setMany(items: Record<string, any>, ttl?: number): Promise<void> {\n await Promise.all(Object.entries(items).map(([key, value]) => this.set(key, value, ttl)));\n }\n\n /**\n * Log the operation\n */\n protected log(operation: CacheOperationType, key?: string | any) {\n if (!this.shouldLog) return;\n\n if (typeof key === \"string\") {\n // this will be likely used with file cache driver as it will convert the dot to slash\n // to make it consistent and not to confuse developers we will output the key by making sure it's a dot\n // An email inside a key (an app's `user.<email>` key, say) is personal\n // data in a log line; key-based redaction cannot reach it, so mask it.\n key = maskEmailsInText(key.replace(/\\//g, \".\"));\n }\n\n if (operation === \"connectionFailed\") {\n log.fatal(`cache.${this.name}`, operation, key);\n }\n\n if (operation == \"notFound\" || operation == \"expired\") {\n return log.info(\n \"cache.\" + this.name,\n operation,\n (key ? key + \" \" : \"\") + messages[operation],\n );\n }\n\n if (operation.endsWith(\"ed\")) {\n return log.success(\n \"cache.\" + this.name,\n operation,\n (key ? key + \" \" : \"\") + messages[operation],\n );\n }\n\n log.info(\"cache.\" + this.name, operation, (key ? key + \" \" : \"\") + messages[operation]);\n }\n\n /**\n * Log error message\n *\n * Never logs the raw `error` object — driver connection errors (Redis,\n * Postgres, ...) can carry the connection string, including the password,\n * in the message/cause. Only a redacted `{ message, code }` shape is\n * passed to the structured logger.\n */\n protected logError(message: string, error?: any) {\n // Error lines name the key (\"SWR background refresh failed for\n // user.alice@example.com\"), and they are written even with logging off,\n // so addresses are masked here as in `log()`.\n const maskedMessage = maskEmailsInText(message);\n\n if (error) {\n const info = safeErrorInfo(error);\n\n log.error(\"cache.\" + this.name, \"error\", maskedMessage, {\n ...info,\n message: maskEmailsInText(info.message),\n });\n } else {\n log.error(\"cache.\" + this.name, \"error\", maskedMessage);\n }\n }\n\n /**\n * Get the default TTL in seconds. Parses human-readable strings (`\"1h\"`, `\"30m\"`)\n * from driver options if present; falls back to `Infinity` when no default is set.\n */\n public get ttl() {\n if (this.options.ttl === undefined) {\n return Infinity;\n }\n\n return parseTtl(this.options.ttl);\n }\n\n /**\n * Get time to live value in milliseconds\n */\n public getExpiresAt(ttl: number = this.ttl) {\n if (ttl) {\n return new Date().getTime() + ttl * 1000;\n }\n }\n\n /**\n * Wrap a value with TTL and optional freshness metadata for backend\n * storage. `staleAt` persists alongside `expiresAt` when supplied — used\n * by the SWR flow to mark when the entry stops being fresh.\n */\n protected prepareDataForStorage(data: any, ttl?: number, staleAt?: number) {\n const preparedData: CacheData = {\n data,\n };\n\n if (ttl) {\n preparedData.ttl = ttl;\n preparedData.expiresAt = this.getExpiresAt(ttl);\n }\n\n if (staleAt !== undefined) {\n preparedData.staleAt = staleAt;\n }\n\n return preparedData;\n }\n\n /**\n * Parse fetched data from cache\n */\n protected async parseCachedData(key: string, data: CacheData) {\n this.log(\"fetched\", key);\n\n if (data.expiresAt && data.expiresAt < Date.now()) {\n this.remove(key);\n return null;\n }\n\n const value = data.data;\n\n // Skip cloning for primitives (immutable types)\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n return value;\n }\n\n // Deep clone objects/arrays to prevent cache mutation\n try {\n return structuredClone(value);\n } catch (error) {\n this.logError(\n `Failed to clone cached value for ${key}, typeof value: ${typeof value}`,\n error,\n );\n throw error;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n this.log(\"connecting\");\n this.log(\"connected\");\n await this.emit(\"connected\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n this.log(\"disconnected\");\n await this.emit(\"disconnected\");\n }\n\n /**\n * Create a tagged cache instance for the given tags\n */\n public tags(tags: string[]): any {\n return new TaggedCache(tags, this);\n }\n\n /**\n * Emit `invalidated` on behalf of a {@link TaggedCache} created by this\n * driver, once per `invalidate()` call. Listeners cannot fail the\n * invalidation: `emit` isolates them.\n *\n * @internal\n */\n public async emitTagsInvalidated(data: {\n tags: string[];\n keys: string[];\n durationMs: number;\n }): Promise<void> {\n await this.emit(\"invalidated\", data);\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: read → transform → write, serialized per key in\n * THIS process only. Drivers that can offer cross-server atomicity (redis,\n * pg: compare-and-set with retries) override it.\n */\n public async 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 // Chain each update onto the previous one for the same key so concurrent\n // callers are serialized end-to-end, not merely awakened together.\n return this.runSerialized(this.parseKey(key), async () => {\n const current = (await this.get(key)) as T | null;\n const result = await fn(current);\n\n if (result === null) {\n await this.remove(key);\n return null;\n }\n\n if (options.ttl !== undefined) {\n await this.set(key, result, { ttl: options.ttl });\n\n return result;\n }\n\n // No explicit TTL → preserve the existing entry's remaining lifetime\n // rather than resetting it to the driver default.\n const remainingTtl = await this.getRemainingTtl(key);\n\n if (remainingTtl !== undefined) {\n await this.set(key, result, { ttl: remainingTtl });\n } else {\n await this.set(key, result);\n }\n\n return result;\n });\n }\n\n /**\n * {@inheritdoc}\n */\n public async merge<T extends Record<string, any> = Record<string, any>>(\n key: CacheKey,\n partial: Partial<T>,\n options: { ttl?: CacheTtl } = {},\n ): Promise<T> {\n const result = await this.update<T>(\n key,\n (current) => {\n const base = (current ?? {}) as T;\n return { ...base, ...partial } as T;\n },\n options,\n );\n\n return result as T;\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation: read-mutate-write array backed by the underlying\n * cache entry. Concrete drivers (e.g. Redis) override with native commands.\n */\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\n return new MemoryCacheList<T>(this, key);\n }\n\n /**\n * {@inheritdoc}\n *\n * Built on top of `set({ onConflict: \"create\" })` — Redis-native `SET … NX EX`\n * under the hood on Redis, emulated via key-existence check on other drivers.\n * The lock value is the resolved `owner` (defaults to `pid.<process.pid>`).\n *\n * Always releases in `finally`, even if `fn` throws — the thrown error\n * propagates to the caller unchanged, and a failed release is logged rather\n * than replacing `fn`'s result or error. The ttl must be finite and\n * positive; there is no renewal, so it must exceed the worst-case duration.\n */\n public async lock<T>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\n fn: () => Promise<T>,\n ): Promise<LockOutcome<T>> {\n const { ttl, owner } = this.normalizeLockOptions(ttlOrOptions);\n\n // A lock with no expiry stays held forever if its holder crashes, on every\n // server. Refuse it up front (parseTtl also rejects undefined / NaN).\n const ttlSeconds = parseTtl(ttl);\n\n if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {\n throw new CacheConfigurationError(\n `cache.lock() requires a finite, positive ttl (got ${String(ttl)}). The ttl must exceed the worst-case duration of the locked work.`,\n );\n }\n // The stored value is `<owner>#<token>`: the token makes it unique per\n // acquisition so release can compare-and-delete instead of deleting blindly.\n const lockValue = `${owner ?? `pid.${process.pid}`}#${randomUUID()}`;\n\n const setResult = (await this.set(key, lockValue, {\n onConflict: \"create\",\n ttl,\n })) as CacheSetResult | unknown;\n\n // `onConflict` drivers return CacheSetResult. Drivers that no-op on set\n // (e.g. the null driver) may return anything else — treat that as\n // \"acquired\" since there's nothing to collide with.\n const wasSet =\n typeof setResult === \"object\" && setResult !== null && \"wasSet\" in setResult\n ? (setResult as CacheSetResult).wasSet\n : true;\n\n if (!wasSet) {\n return { acquired: false };\n }\n\n try {\n const value = await fn();\n return { acquired: true, value };\n } finally {\n // Only delete our own lock — if the TTL expired and a successor\n // acquired the key, its value differs and it is left alone. A release\n // failure (e.g. a Redis blip) is logged, never allowed to replace fn's\n // outcome: the lock still expires by its TTL.\n try {\n await this.deleteIfEquals(key, lockValue);\n } catch (error) {\n this.logError(`lock release failed for ${this.parseKey(key)}`, error);\n }\n }\n }\n\n /**\n * Compare-and-delete: remove `key` only when its current value equals\n * `expected`. Returns whether a delete happened.\n *\n * This default is get-then-remove and is NOT atomic across processes; drivers\n * with a real primitive (memory, redis, pg) override it.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n if ((await this.get(key)) !== expected) {\n return false;\n }\n\n await this.remove(key);\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * Default implementation throws {@link CacheUnsupportedError}. Drivers that\n * support similarity retrieval (memory family, `pg`, `redis` w/ RediSearch)\n * override this with a real impl.\n */\n public async similar<T = any>(\n _vector: number[],\n _options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n throw new CacheUnsupportedError(\n `'${this.name}' driver does not support similarity retrieval. Use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).`,\n );\n }\n\n /**\n * Resolve the TTL-or-options arg of `lock` into a uniform shape.\n */\n protected normalizeLockOptions(ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">): {\n ttl: CacheTtl;\n owner?: string;\n } {\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\n return { ttl: ttlOrOptions };\n }\n\n return { ttl: ttlOrOptions.ttl, owner: ttlOrOptions.owner };\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport {\n atomicWriteJsonAsync,\n ensureDirectoryAsync,\n getJsonFileAsync,\n listDirectoriesAsync,\n removeDirectoryAsync,\n} from \"@warlock.js/fs\";\nimport path from \"path\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n FileCacheOptions,\n} from \"../types\";\nimport { CacheConfigurationError, CacheError, CacheUnsupportedError } from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\nexport class FileCacheDriver\n extends BaseCacheDriver<FileCacheDriver, FileCacheOptions>\n implements CacheDriver<FileCacheDriver, FileCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"file\";\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: FileCacheOptions) {\n if (!options.directory) {\n throw new CacheConfigurationError(\n \"File driver requires 'directory' option to be configured.\",\n );\n }\n\n return super.setOptions(options);\n }\n\n /**\n * Get the cache directory\n */\n public get directory() {\n const directory = this.options.directory;\n\n if (typeof directory === \"function\") {\n return directory();\n }\n\n throw new CacheConfigurationError(\n \"Cache directory is not defined, please define it in the file driver options\",\n );\n }\n\n /**\n * Get file name\n */\n public get fileName() {\n const fileName = this.options.fileName;\n\n if (typeof fileName === \"function\") {\n return fileName();\n }\n\n return \"cache.json\";\n }\n\n /**\n * Map a parsed cache key (or namespace) to its on-disk directory.\n *\n * The key must become exactly one directory component: `%` and the path\n * separators are percent-encoded so a hostile key (`../../etc`,\n * `..\\\\..\\\\evil`) turns into an inert directory name instead of a path\n * traversal, while distinct keys can never collide after encoding. Dots are\n * left untouched — the `.`-delimited namespace scheme is purely logical and\n * only ever produces a single filesystem component here.\n */\n protected keyDirectory(parsedKey: string): string {\n // An empty key would resolve to the cache root — `remove(\"{}\")` must never\n // be able to wipe it.\n if (parsedKey === \"\") {\n throw new CacheConfigurationError(\"Cache key must not be empty.\");\n }\n\n return this.containedPath(this.encodeKeySegment(parsedKey));\n }\n\n /**\n * Percent-encode a parsed key into an inert, single-component directory\n * name. Dots are left untouched — see {@link keyDirectory}.\n */\n protected encodeKeySegment(parsedKey: string): string {\n return parsedKey.replace(/%/g, \"%25\").replace(/\\//g, \"%2F\").replace(/\\\\/g, \"%5C\");\n }\n\n /**\n * Reverse of {@link encodeKeySegment} — recovers the logical, dot-delimited\n * parsed key from an on-disk directory name. Used by {@link removeNamespace}\n * to test which sibling directories logically belong to a namespace, since\n * dotted keys (`ns.a`) are stored as sibling directories rather than nested\n * ones (see {@link keyDirectory}'s doc comment).\n */\n protected decodeKeySegment(encoded: string): string {\n return encoded.replace(/%2F/g, \"/\").replace(/%5C/g, \"\\\\\").replace(/%25/g, \"%\");\n }\n\n /**\n * Resolve `segment` against the cache root and throw when the result lands\n * outside it — the last line of defense against path traversal, independent\n * of how the key was encoded.\n */\n protected containedPath(segment: string): string {\n const base = path.resolve(this.directory);\n const resolved = path.resolve(base, segment);\n const relative = path.relative(base, resolved);\n\n if (relative === \"..\" || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {\n throw new CacheError(`Cache key resolves outside the cache directory: \"${segment}\"`);\n }\n\n return resolved;\n }\n\n /**\n * {@inheritdoc}\n *\n * Dotted keys (`ns.a`) are stored as sibling directories, not nested ones\n * (see {@link keyDirectory}) — a directory literally named `<namespace>`\n * rarely exists on its own. Namespace removal therefore has to scan the\n * cache root's immediate children and remove every directory whose\n * *logical* (decoded) name equals the namespace or starts with\n * `<namespace>.`, mirroring the `key = $1 OR key LIKE $2` boundary\n * semantics the `pg` driver already uses for the same contract.\n */\n public async removeNamespace(namespace: string) {\n const parsedNamespace = this.parseKey(namespace);\n\n this.log(\"clearing\", namespace);\n\n try {\n const root = this.containedPath(\"\");\n\n if (parsedNamespace === \"\") {\n await removeDirectoryAsync(root);\n this.log(\"cleared\", namespace);\n return this;\n }\n\n const prefix = `${parsedNamespace}.`;\n const entries = await listDirectoriesAsync(root).catch(() => [] as string[]);\n\n await Promise.all(\n entries.map(async (entryPath) => {\n const decoded = this.decodeKeySegment(path.basename(entryPath));\n\n if (decoded === parsedNamespace || decoded.startsWith(prefix)) {\n await removeDirectoryAsync(entryPath);\n }\n }),\n );\n\n this.log(\"cleared\", namespace);\n } catch (error) {\n //\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * `onConflict: \"create\"` is exclusive (temp file + hard link, so `lock()`\n * is safe across concurrent writers). `onConflict: \"update\"` is NOT atomic:\n * the existence probe and the write are separate steps.\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 throw new CacheUnsupportedError(\n \"'file' driver does not support similarity retrieval — use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).\",\n );\n }\n\n this.log(\"caching\", parsedKey);\n\n const existing =\n onConflict === \"upsert\" || onConflict === \"create\" ? null : await this.get(key);\n const exists = existing !== null;\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(value, ttl, staleAt);\n\n const fileDirectory = this.keyDirectory(parsedKey);\n\n await ensureDirectoryAsync(fileDirectory);\n\n const filePath = path.resolve(fileDirectory, this.fileName);\n\n if (onConflict === \"create\") {\n const conflict = await this.writeExclusive(filePath, data);\n\n if (conflict) {\n const result: CacheSetResult = { wasSet: false, existing: conflict.existing };\n return result;\n }\n } else {\n await atomicWriteJsonAsync(filePath, data);\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * Create `filePath` only if it does not exist. Writes a temp file, then\n * hard-links it to the target (EEXIST = already present). An existing but\n * expired entry is removed (the file only, never the directory) and the\n * link retried once.\n *\n * Returns `null` when written, or `{ existing }` on conflict.\n */\n protected async writeExclusive(\n filePath: string,\n data: CacheData,\n ): Promise<{ existing: any } | null> {\n const tempPath = `${filePath}.${randomBytes(6).toString(\"hex\")}.tmp`;\n\n try {\n await fs.promises.writeFile(tempPath, JSON.stringify(data, null, 2));\n\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n await fs.promises.link(tempPath, filePath);\n return null;\n } catch (error: any) {\n if (error?.code !== \"EEXIST\") {\n throw error;\n }\n }\n\n let current: CacheData | undefined;\n\n try {\n current = (await getJsonFileAsync(filePath)) as CacheData | undefined;\n } catch {\n current = undefined;\n }\n\n const expired =\n current?.expiresAt !== undefined &&\n current.expiresAt !== null &&\n current.expiresAt <= Date.now();\n\n if (current && !expired) {\n return { existing: current.data ?? null };\n }\n\n if (attempt === 0 && expired) {\n await fs.promises.rm(filePath, { force: true });\n continue;\n }\n\n // Unreadable (corrupt) file: treat as held, never delete it.\n return { existing: null };\n }\n\n return { existing: null };\n } finally {\n await fs.promises.unlink(tempPath).catch(() => undefined);\n }\n }\n\n /**\n * {@inheritdoc}\n *\n * File driver does not yet ship with a file-lock primitive, so concurrent\n * writers could clobber each other. Rather than ship an unsafe default, we\n * throw — consumers can fall back to memory/redis for `update` until a\n * proper file lock lands (tracked in `domains/cache/backlog.md`).\n */\n public async update(): Promise<never> {\n throw new CacheUnsupportedError(\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).\",\n );\n }\n\n /**\n * {@inheritdoc}\n */\n public async merge(): Promise<never> {\n throw new CacheUnsupportedError(\n \"`merge()` is not supported on the file driver. Use the memory or redis driver.\",\n );\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from disk, including `staleAt`\n * metadata. Returns `null` for missing or expired files — `swr()`\n * consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const fileDirectory = this.keyDirectory(parsedKey);\n\n try {\n const entry = (await getJsonFileAsync(path.resolve(fileDirectory, this.fileName))) as\n | CacheData\n | undefined;\n\n if (!entry) {\n return null;\n }\n\n // `Infinity` (no expiry) is written to JSON as `null`; `null <= now`\n // is true, so only a real deadline may count as expired.\n if (typeof entry.expiresAt === \"number\" && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return entry;\n } catch {\n return null;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const fileDirectory = this.keyDirectory(parsedKey);\n\n let value: CacheData | undefined;\n\n try {\n value = (await getJsonFileAsync(path.resolve(fileDirectory, this.fileName))) as\n | CacheData\n | undefined;\n } catch {\n // Missing file (ENOENT) or corrupt JSON: a plain miss. Never remove the\n // directory here — it may be mid-write by another process.\n value = undefined;\n }\n\n if (value === undefined || value === null || typeof value !== \"object\") {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n // Only a successfully parsed but expired entry is removed (inside\n // parseCachedData).\n const result = await this.parseCachedData(parsedKey, value);\n\n if (result === null) {\n await this.emit(\"miss\", { key: parsedKey });\n } else {\n await this.emit(\"hit\", { key: parsedKey, value: result });\n }\n\n return result;\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 fileDirectory = this.keyDirectory(parsedKey);\n\n try {\n await removeDirectoryAsync(fileDirectory);\n\n this.log(\"removed\", parsedKey);\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n } catch (error) {\n //\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n await removeDirectoryAsync(this.directory);\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n this.log(\"connecting\");\n await ensureDirectoryAsync(this.directory);\n this.log(\"connected\");\n await this.emit(\"connected\");\n }\n}\n","/**\n * Tag index for the in-process drivers (memory, memoryExtended, lru, mock).\n *\n * Lives beside the driver's entry storage, never inside it: index sets are not\n * entries, so they are never subject to `maxSize` / LRU eviction or TTL\n * sweeps, and every operation is synchronous — concurrent tagged writes in one\n * process can never drop a member.\n *\n * Keys are the driver-parsed tag keys (`<prefix>.cache.tags.<tag>`); members\n * are the caller's normalized, un-prefixed cache keys.\n */\nexport class InMemoryTagIndex {\n protected readonly sets: Map<string, Set<string>> = new Map();\n\n /**\n * Add members to a tag set, creating it on first use.\n */\n public add(tagKey: string, members: readonly string[]): void {\n if (members.length === 0) {\n return;\n }\n\n let set = this.sets.get(tagKey);\n\n if (!set) {\n set = new Set();\n this.sets.set(tagKey, set);\n }\n\n for (const member of members) {\n set.add(member);\n }\n }\n\n /**\n * The members of a tag set (a copy; empty when the set does not exist).\n */\n public members(tagKey: string): string[] {\n return [...(this.sets.get(tagKey) ?? [])];\n }\n\n /**\n * Remove members from a tag set; an emptied set is dropped.\n */\n public remove(tagKey: string, members: readonly string[]): void {\n const set = this.sets.get(tagKey);\n\n if (!set) {\n return;\n }\n\n for (const member of members) {\n set.delete(member);\n }\n\n if (set.size === 0) {\n this.sets.delete(tagKey);\n }\n }\n\n /**\n * Drop a whole tag set.\n */\n public delete(tagKey: string): void {\n this.sets.delete(tagKey);\n }\n\n /**\n * Drop every tag set whose key equals `namespace` or sits under\n * `namespace.` — the same rule the drivers use for entries. An empty\n * namespace clears everything.\n */\n public removeNamespace(namespace: string): void {\n if (namespace === \"\") {\n this.sets.clear();\n\n return;\n }\n\n const prefix = namespace + \".\";\n\n for (const key of [...this.sets.keys()]) {\n if (key === namespace || key.startsWith(prefix)) {\n this.sets.delete(key);\n }\n }\n }\n\n /**\n * Drop every tag set.\n */\n public clear(): void {\n this.sets.clear();\n }\n}\n","import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n LRUMemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity, parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\nclass CacheNode {\n public next: CacheNode | null = null;\n public prev: CacheNode | null = null;\n public expiresAt?: number;\n /**\n * Freshness deadline (ms timestamp) — populated by `swr()`. Within\n * `expiresAt > now > staleAt` the entry is \"stale-but-revalidatable.\"\n */\n public staleAt?: number;\n /**\n * Optional embedding vector — populated when the entry was written with\n * `set({ vector })`. Scanned by `similar()`.\n */\n public vector?: number[];\n public constructor(\n public key: string,\n public value: any,\n ttl?: number,\n ) {\n if (ttl && ttl !== Infinity) {\n this.expiresAt = Date.now() + ttl * 1000;\n }\n }\n\n public get isExpired(): boolean {\n return this.expiresAt !== undefined && this.expiresAt < Date.now();\n }\n}\n\n/**\n * LRU Memory Cache Driver\n * The concept of LRU is to remove the least recently used data\n * whenever the cache is full\n * The question that resides here is how to tell the cache is full?\n */\nexport class LRUMemoryCacheDriver\n extends BaseCacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n implements CacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"lru\";\n\n /**\n * Cache map\n */\n protected cache: Map<string, CacheNode> = new Map();\n\n /**\n * Tag index, kept out of the LRU list so capacity pressure can never evict it.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Head of the cache\n */\n protected head: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Tail of the cache\n */\n protected tail: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.init();\n this.startCleanup();\n }\n\n /**\n * Initialize the cache\n */\n public init() {\n this.head.next = this.tail;\n this.tail.prev = this.head;\n }\n\n /**\n * Start the cleanup process for expired items\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, node] of this.cache) {\n if (node.expiresAt && node.expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * {@inheritdoc}\n *\n * Clears every entry whose key starts with the parsed namespace (followed\n * by a dot) or equals it exactly. Called with an empty namespace while a\n * `globalPrefix` is configured, clears everything under the prefix — which\n * is how `flush()` scopes cleanup per tenant.\n */\n public async removeNamespace(namespace: string) {\n const parsedNamespace = this.parseKey(namespace);\n\n this.log(\"clearing\", parsedNamespace || \"(all)\");\n\n const removed: string[] = [];\n\n this.tagIndex.removeNamespace(parsedNamespace);\n\n if (parsedNamespace === \"\") {\n for (const key of this.cache.keys()) {\n removed.push(key);\n }\n } else {\n const prefix = parsedNamespace + \".\";\n\n for (const key of this.cache.keys()) {\n if (key === parsedNamespace || key.startsWith(prefix)) {\n removed.push(key);\n }\n }\n }\n\n for (const key of removed) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n }\n await this.emit(\"removed\", { key });\n }\n\n this.log(\"cleared\", parsedNamespace || \"(all)\");\n\n return removed;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // For conditional writes, check existence via get() so expired entries\n // are treated as missing (get() handles expiry cleanup). The raw `cache.get`\n // node lookup would let stale entries block onConflict: \"create\".\n let existingNode = this.cache.get(parsedKey);\n if (existingNode && existingNode.isExpired) {\n this.removeNode(existingNode);\n this.cache.delete(parsedKey);\n existingNode = undefined;\n }\n\n const exists = Boolean(existingNode);\n\n if (onConflict === \"create\" && exists) {\n const result: CacheSetResult = {\n wasSet: false,\n existing: existingNode!.value,\n };\n return result;\n }\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n if (existingNode) {\n existingNode.value = value;\n if (ttl && ttl !== Infinity) {\n existingNode.expiresAt = Date.now() + ttl * 1000;\n } else {\n existingNode.expiresAt = undefined;\n }\n existingNode.staleAt = staleAt;\n if (vector) {\n existingNode.vector = vector.slice();\n }\n\n this.moveHead(existingNode);\n } else {\n const newNode = new CacheNode(parsedKey, value, ttl);\n newNode.staleAt = staleAt;\n if (vector) {\n newNode.vector = vector.slice();\n }\n\n this.cache.set(parsedKey, newNode);\n\n this.addNode(newNode);\n if (this.cache.size > this.capacity) {\n this.removeTail();\n }\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * Move the node to the head\n */\n protected moveHead(node: CacheNode) {\n this.removeNode(node);\n this.addNode(node);\n }\n\n /**\n * Remove the node from the cache\n */\n protected removeNode(node: CacheNode) {\n node.prev!.next = node.next;\n node.next!.prev = node.prev;\n }\n\n /**\n * Add the node to the head\n */\n protected addNode(node: CacheNode) {\n node.next = this.head.next;\n node.prev = this.head;\n this.head.next!.prev = node;\n this.head.next = node;\n }\n\n /**\n * Remove the tail node\n */\n protected removeTail() {\n const node = this.tail.prev!;\n\n this.removeNode(node);\n\n this.cache.delete(node.key);\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\n * Returns `null` for missing or expired nodes — `swr()` consumes this\n * to branch on freshness without going through `get()`'s clone-and-emit\n * path.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const node = this.cache.get(parsedKey);\n\n if (!node || node.isExpired) {\n return null;\n }\n\n return {\n data: node.value,\n expiresAt: node.expiresAt,\n staleAt: node.staleAt,\n };\n }\n\n /**\n * Live node for `parsedKey`, or `undefined` when missing / expired (an\n * expired node is unlinked). Synchronous.\n */\n protected liveNode(parsedKey: string): CacheNode | undefined {\n const node = this.cache.get(parsedKey);\n\n if (!node) {\n return undefined;\n }\n\n if (node.isExpired) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n\n return undefined;\n }\n\n return node;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * node keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const parsedKey = this.parseKey(key);\n const node = this.liveNode(parsedKey);\n const current = node ? node.value : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (node) {\n node.value = newValue;\n if (\n options.ttl !== undefined &&\n !Number.isFinite(node.expiresAt) &&\n Number.isFinite(ttl) &&\n ttl > 0\n ) {\n node.expiresAt = Date.now() + ttl * 1000;\n }\n this.moveHead(node);\n } else {\n const newNode = new CacheNode(parsedKey, newValue, ttl);\n\n this.cache.set(parsedKey, newNode);\n this.addNode(newNode);\n\n if (this.cache.size > this.capacity) {\n this.removeTail();\n }\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and unlink run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const node = this.liveNode(parsedKey);\n\n if (!node) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.removeNode(node);\n this.cache.delete(parsedKey);\n\n const value =\n node.value !== null && typeof node.value === \"object\"\n ? structuredClone(node.value)\n : node.value;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (!node) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n // Check if expired\n if (node.isExpired) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n this.log(\"expired\", parsedKey);\n // Emit expired event\n await this.emit(\"expired\", { key: parsedKey });\n // Also emit miss since we're returning null\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.moveHead(node);\n\n this.log(\"fetched\", parsedKey);\n\n const value = node.value;\n\n // Apply cloning for immutability protection\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n try {\n const clonedValue = structuredClone(value);\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: clonedValue });\n return clonedValue;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\n throw error;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (node) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n }\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n *\n * When a `globalPrefix` is configured, `flush` scopes itself to that prefix\n * so multi-tenant caches don't accidentally wipe sibling tenants. Without\n * a prefix, clears everything.\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.cache.clear();\n this.tagIndex.clear();\n this.init();\n }\n\n this.log(\"flushed\");\n\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every cached node that carries a\n * vector. Suitable for development and small in-memory knowledge bases —\n * not for production beyond ~10k entries.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, node] of this.cache) {\n if (!node.vector) continue;\n if (node.isExpired) continue;\n if (tagFilter && !tagFilter.has(parsedKey)) continue;\n\n const score = cosineSimilarity(vector, node.vector);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n // Clone object values to match get() semantics.\n let value: any = node.value;\n if (value !== null && value !== undefined) {\n const t = typeof value;\n if (t !== \"string\" && t !== \"number\" && t !== \"boolean\") {\n value = structuredClone(value);\n }\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * Get lru capacity\n */\n public get capacity() {\n return this.options.capacity || 1000;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n","import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n MemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity, parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * Clone non-primitive values so cached state can't be mutated through\n * references held by callers. Primitives are immutable and returned as-is.\n */\nfunction cloneValue<T>(value: T): T {\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n return value;\n }\n\n return structuredClone(value);\n}\n\nexport class MemoryCacheDriver\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memory\";\n\n /**\n * Flat storage keyed by the parsed key. Insertion order doubles as LRU\n * order (least recently used first) — accessed entries are deleted and\n * re-inserted.\n */\n protected entries: Map<string, CacheData> = new Map();\n\n /**\n * Read-only object view of the stored entries, keyed by parsed key.\n * Kept for introspection; the values are the live stored wrappers.\n */\n public get data(): Record<string, CacheData> {\n return Object.fromEntries(this.entries);\n }\n\n /**\n * Expiry records for entries with a FINITE ttl only (parsed key => expiresAt ms).\n * The sweep iterates just these.\n */\n protected expiry: Map<string, number> = new Map();\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\n * `remove`, `flush`, expiry, namespace clear, overwrite without a vector,\n * and LRU eviction.\n */\n protected vectorIndex: Map<string, number[]> = new Map();\n\n /**\n * Tag index, kept apart from `entries`: never evicted by `maxSize`, never\n * swept by TTL, and updated synchronously.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.startCleanup();\n }\n\n /**\n * Start the cleanup process for entries that carry a finite ttl\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, expiresAt] of this.expiry) {\n if (expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n // the entry may have been rewritten/slid since the snapshot\n const expiresAt = this.expiry.get(key);\n\n if (expiresAt === undefined || expiresAt > now) {\n continue;\n }\n\n this.dropEntry(key);\n\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * Delete an entry and every side record (expiry, vector). Synchronous.\n */\n protected dropEntry(parsedKey: string) {\n this.entries.delete(parsedKey);\n this.expiry.delete(parsedKey);\n this.vectorIndex.delete(parsedKey);\n }\n\n /**\n * Raw, synchronous read of a live entry. An expired entry is deleted and\n * reported as missing. Never slides TTLs, never emits, never touches LRU order.\n */\n protected readLive(parsedKey: string): CacheData | undefined {\n const entry = this.entries.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.dropEntry(parsedKey);\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Mark an entry as most recently used (only tracked when `maxSize` is set).\n */\n protected touch(parsedKey: string, entry: CacheData) {\n if (!this.options.maxSize) return;\n\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, entry);\n }\n\n /**\n * When true, every live read slides the entry's expiration forward by its\n * ttl (the `memoryExtended` driver). Set in a subclass constructor rather\n * than redeclared, so subclasses stay structurally compatible with this\n * class (the `client` type relies on that).\n */\n protected slidingExpiration = false;\n\n /**\n * Invoked on a live read hit, before the value is returned. Runs only from\n * `get()` after the expiry check, so an expired entry is never resurrected,\n * and never from the internal existence check used by `onConflict`.\n */\n protected onRead(parsedKey: string, entry: CacheData) {\n if (!this.slidingExpiration) {\n return;\n }\n\n const rawTtl = entry.ttl ?? this.options.ttl;\n const ttl = rawTtl !== undefined ? parseTtl(rawTtl) : undefined;\n\n if (ttl && Number.isFinite(ttl)) {\n entry.expiresAt = this.getExpiresAt(ttl);\n this.expiry.set(parsedKey, entry.expiresAt as number);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.log(\"clearing\", namespace);\n\n namespace = this.parseKey(namespace);\n\n this.tagIndex.removeNamespace(namespace);\n\n if (namespace === \"\") {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n } else {\n const prefix = namespace + \".\";\n\n for (const key of [...this.entries.keys()]) {\n if (key === namespace || key.startsWith(prefix)) {\n this.dropEntry(key);\n }\n }\n }\n\n this.log(\"cleared\", namespace);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The conditional check and the write run with no `await` in between, so\n * `onConflict` decisions are atomic within the process.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // expired entries count as missing (readLive deletes them)\n const existing =\n onConflict === \"create\" || onConflict === \"update\" ? this.readLive(parsedKey) : undefined;\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: cloneValue(existing.data) };\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(cloneValue(value), ttl, staleAt);\n\n // delete + set moves the key to the most-recent position\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n } else {\n this.expiry.delete(parsedKey);\n }\n\n if (vector) {\n this.vectorIndex.set(parsedKey, vector.slice());\n } else {\n this.vectorIndex.delete(parsedKey);\n }\n\n this.enforceMaxSize(parsedKey);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The check and the delete run synchronously (no `await` between them), so\n * the compare-and-delete is atomic within the process.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry || entry.data !== expected) {\n return false;\n }\n\n await this.remove(key);\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * entry keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n if (\n options.ttl !== undefined &&\n !Number.isFinite(entry.expiresAt) &&\n Number.isFinite(ttl) &&\n ttl > 0\n ) {\n entry.ttl = ttl;\n entry.expiresAt = Date.now() + ttl * 1000;\n this.expiry.set(parsedKey, entry.expiresAt);\n }\n this.touch(parsedKey, entry);\n } else {\n const data = this.prepareDataForStorage(newValue, ttl);\n\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n }\n\n this.enforceMaxSize(parsedKey);\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: entry?.ttl ?? ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and delete run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.dropEntry(parsedKey);\n\n const value = cloneValue(entry.data);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.onRead(parsedKey, entry);\n this.touch(parsedKey, entry);\n\n const result = await this.parseCachedData(parsedKey, entry);\n\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: result });\n\n return result;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper (cloned), including `staleAt` metadata.\n * Returns `null` for missing or expired entries so the SWR flow can branch\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const entry = this.readLive(this.parseKey(key));\n\n if (!entry) {\n return null;\n }\n\n return { ...entry, data: cloneValue(entry.data) };\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n this.dropEntry(parsedKey);\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set — concurrent tagged writes never drop members.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n this.tagIndex.clear();\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * Evict least recently used entries (Map insertion order) until the size is\n * within `maxSize`. Never evicts `protectedKey` (the key just written).\n */\n protected enforceMaxSize(protectedKey: string) {\n const maxSize = this.options.maxSize;\n\n if (!maxSize) {\n return;\n }\n\n for (const lruKey of [...this.entries.keys()]) {\n if (this.entries.size <= maxSize) {\n break;\n }\n\n if (lruKey === protectedKey) {\n continue;\n }\n\n this.log(\"removing\", lruKey);\n this.dropEntry(lruKey);\n this.log(\"removed\", lruKey);\n }\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every entry that was written with\n * `set({ vector })`. Suitable for development and small in-memory knowledge\n * bases — not for production beyond ~10k entries. Use the `pg` driver\n * (with pgvector) or `redis` (with RediSearch) at scale.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, stored] of [...this.vectorIndex]) {\n if (tagFilter && !tagFilter.has(parsedKey)) {\n continue;\n }\n\n // Read the entry directly by its already-parsed key — this.get() would\n // parse it again and apply globalPrefix twice, missing every entry.\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n continue;\n }\n\n const value: any = cloneValue(entry.data);\n\n const score = cosineSimilarity(vector, stored);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n","import type { CacheDriver, MemoryExtendedCacheOptions } from \"../types\";\nimport { MemoryCacheDriver } from \"./memory-cache-driver\";\n\n/**\n * Memory driver with a sliding ttl: every live read pushes the entry's\n * expiration forward by its ttl. The sliding itself lives in\n * `MemoryCacheDriver.onRead`, which checks expiry first, so an expired entry\n * is never resurrected and the internal `onConflict` existence check never\n * slides a held lock.\n */\nexport class MemoryExtendedCacheDriver\n extends MemoryCacheDriver\n implements CacheDriver<MemoryExtendedCacheDriver, MemoryExtendedCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memoryExtended\";\n\n public constructor() {\n super();\n\n this.slidingExpiration = true;\n }\n}\n","import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheCall,\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n MockCacheOptions,\n} from \"../types\";\nimport { parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * In-memory cache driver with introspection helpers, intended for use as a\n * test double in downstream packages.\n *\n * **Role.** Drop-in replacement for any real driver in test setups, with\n * extra surface that makes behavioral assertions easy: every public op gets\n * recorded into {@link MockCacheDriver.callLog}, and {@link wasCalled} /\n * {@link getStored} / {@link reset} let tests verify side effects without\n * pulling in a real Redis / Postgres / file system.\n *\n * **Responsibility.**\n * - Owns: in-memory storage backed by a `Map`, the `callLog`, TTL handling\n * via `parseCachedData`, `onConflict` policies, and tag-index storage.\n * - Does NOT own: similarity retrieval (vectors are recorded into the\n * `callLog` but `similar()` throws — use `MemoryCacheDriver` for tests\n * that need real nearest-neighbor scoring), connection lifecycle\n * (no-op `connect`/`disconnect`), or eviction (no `maxSize`).\n *\n * Register it like any other driver — sub-paths are not part of the\n * package's export convention; the same single barrel ships it next to\n * the production drivers.\n *\n * @example\n * import { cache, MockCacheDriver } from \"@warlock.js/cache\";\n *\n * beforeEach(async () => {\n * cache.setCacheConfigurations({\n * default: \"mock\",\n * drivers: { mock: MockCacheDriver },\n * options: { mock: {} },\n * });\n * await cache.init();\n * });\n *\n * it(\"invalidates the user cache after update\", async () => {\n * await userService.update(42, { name: \"Jane\" });\n *\n * const driver = cache.currentDriver as MockCacheDriver;\n * expect(driver.wasCalled(\"remove\", \"users.42\")).toBe(true);\n * });\n */\nexport class MockCacheDriver\n extends BaseCacheDriver<MockCacheDriver, MockCacheOptions>\n implements CacheDriver<MockCacheDriver, MockCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"mock\";\n\n /**\n * {@inheritdoc}\n */\n public options: MockCacheOptions = {};\n\n /**\n * Storage backing the mock — keyed by post-`parseKey` string. Public-readonly\n * so tests can introspect raw entries when {@link getStored} isn't enough.\n */\n public readonly storage: Map<string, CacheData> = new Map();\n\n /**\n * Tag index, kept apart from {@link storage} (same model as the memory driver).\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Ordered record of every public operation routed through this driver.\n * Pushed to before each op runs; tests assert via {@link wasCalled} or by\n * inspecting the array directly.\n */\n public readonly callLog: CacheCall[] = [];\n\n /**\n * Standard driver setup. Mirrors the null driver — no connection, no\n * resources to release.\n */\n public async connect(): Promise<void> {\n this.recordCall(\"connect\", undefined);\n await super.connect();\n }\n\n /**\n * Standard driver teardown.\n */\n public async disconnect(): Promise<void> {\n this.recordCall(\"disconnect\", undefined);\n await super.disconnect();\n }\n\n /**\n * Wipe everything under `namespace`. Matches `MemoryCacheDriver` semantics\n * — the namespace itself and any key with the namespace prefix is removed.\n */\n public async removeNamespace(namespace: string): Promise<void> {\n const parsed = this.parseKey(namespace);\n\n this.recordCall(\"removeNamespace\", parsed, [namespace]);\n this.log(\"clearing\", parsed);\n\n const prefix = parsed + \".\";\n\n this.tagIndex.removeNamespace(parsed);\n\n for (const key of [...this.storage.keys()]) {\n if (key === parsed || key.startsWith(prefix)) {\n this.storage.delete(key);\n }\n }\n\n this.log(\"cleared\", parsed);\n }\n\n /**\n * Standard `set` with full `onConflict` support. Honors scope-default TTL\n * via {@link BaseCacheDriver.resolveSetOptions}.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.recordCall(\"set\", parsedKey, [value, ttlOrOptions]);\n this.log(\"caching\", parsedKey);\n\n // Synchronous check + write (no await between) so onConflict is atomic.\n // Expired entries count as missing and are deleted.\n let existing =\n onConflict === \"create\" || onConflict === \"update\" ? this.storage.get(parsedKey) : undefined;\n\n if (existing && existing.expiresAt !== undefined && existing.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n existing = undefined;\n }\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: this.cloneValue(existing.data) };\n\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n\n return result;\n }\n\n const data = this.prepareDataForStorage(this.cloneValue(value), ttl, staleAt);\n this.storage.set(parsedKey, data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n\n return result;\n }\n\n return this;\n }\n\n /**\n * Clone non-primitive values so stored state can't be mutated by callers.\n */\n protected cloneValue<T>(value: T): T {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return value;\n }\n\n return structuredClone(value);\n }\n\n /**\n * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep\n * downstream metrics tests realistic.\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"get\", parsedKey);\n this.log(\"fetching\", parsedKey);\n\n const data = this.storage.get(parsedKey);\n\n if (!data) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = await this.parseCachedData(parsedKey, data);\n\n if (value === null) {\n // expired — the parse helper already logged + queued the cleanup\n this.storage.delete(parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.emit(\"hit\", { key: parsedKey, value });\n\n return value as T;\n }\n\n /**\n * Live entry for `parsedKey`, or `undefined` when missing / expired (an\n * expired entry is deleted). Synchronous.\n */\n protected liveEntry(parsedKey: string): CacheData | undefined {\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Synchronous read-add-write (atomic within the process). Keeps an existing\n * entry's expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"increment\", parsedKey, [value]);\n\n const entry = this.liveEntry(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n if (\n options.ttl !== undefined &&\n !Number.isFinite(entry.expiresAt) &&\n Number.isFinite(ttl) &&\n ttl > 0\n ) {\n entry.ttl = ttl;\n entry.expiresAt = Date.now() + ttl * 1000;\n }\n } else {\n this.storage.set(parsedKey, this.prepareDataForStorage(newValue, ttl));\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl });\n\n return newValue;\n }\n\n /**\n * Synchronous read-and-delete: a value is handed out exactly once.\n */\n public async pull<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"pull\", parsedKey);\n\n const entry = this.liveEntry(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.storage.delete(parsedKey);\n\n const value = this.cloneValue(entry.data) as T;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from the in-memory `Map`,\n * including `staleAt` metadata. Returns `null` for missing or expired\n * entries — `swr()` consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return { ...entry, data: this.cloneValue(entry.data) };\n }\n\n /**\n * Standard `remove` — drops the entry and emits the `removed` event.\n */\n public async remove(key: CacheKey): Promise<void> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"remove\", parsedKey);\n this.log(\"removing\", parsedKey);\n\n this.storage.delete(parsedKey);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * Standard `flush` — wipes the entire mock store + tag index. Does NOT\n * touch the call log; use {@link reset} to clear that as well.\n */\n public async flush(): Promise<void> {\n this.recordCall(\"flush\", undefined);\n this.log(\"flushing\");\n\n this.storage.clear();\n this.tagIndex.clear();\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * Was a given operation invoked? When `key` is provided, the match is\n * post-`parseKey` so callers pass the same key shape they used at the\n * call site — strings or objects, both resolve to the same parsed key.\n *\n * @example\n * driver.wasCalled(\"set\"); // any set\n * driver.wasCalled(\"set\", \"users.42\"); // set on this specific key\n * driver.wasCalled(\"set\", { id: 42 }); // same — object key normalized\n */\n public wasCalled(operation: string, key?: CacheKey): boolean {\n if (key === undefined) {\n return this.callLog.some((call) => call.operation === operation);\n }\n\n const parsedKey = this.parseKey(key);\n\n return this.callLog.some((call) => call.operation === operation && call.key === parsedKey);\n }\n\n /**\n * Return the raw stored value for `key`, bypassing TTL handling and clone\n * protection. Useful when a test wants to assert on the persisted shape\n * (or assert that an entry expired without going through `get`).\n *\n * Returns `undefined` when the key isn't present.\n */\n public getStored<T = any>(key: CacheKey): T | undefined {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n return entry.data as T;\n }\n\n /**\n * Wipe everything — storage, tag index, and the call log. Pair with\n * Vitest's `beforeEach` to get clean isolation between tests.\n */\n public reset(): void {\n this.storage.clear();\n this.tagIndex.clear();\n this.callLog.length = 0;\n }\n\n /**\n * Append a row to {@link callLog}. Internal helper called by every\n * recorded op before the actual work runs.\n */\n protected recordCall(operation: string, key: string | undefined, args: unknown[] = []): void {\n this.callLog.push({\n operation,\n key,\n args,\n timestamp: Date.now(),\n });\n }\n}\n","import type { GenericObject } from \"@mongez/reinforcements\";\nimport { log, maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheTtl,\n NullCacheDriverOptions,\n RememberOptions,\n} from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\nexport class NullCacheDriver\n extends BaseCacheDriver<NullCacheDriver, NullCacheDriverOptions>\n implements CacheDriver<NullCacheDriver, NullCacheDriverOptions>\n{\n /**\n * Options list\n */\n public options: GenericObject = {};\n\n /**\n * {@inheritdoc}\n */\n public name = \"null\";\n\n /**\n * Cached data\n */\n public data: GenericObject = {};\n\n /**\n * {@inheritdoc}\n */\n public get client() {\n return this;\n }\n\n /**\n * Constructor\n */\n public constructor(options: GenericObject = {}) {\n super();\n this.setOptions(options);\n }\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: GenericObject) {\n this.options = options;\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public parseKey(key: CacheKey) {\n // Real parsed key so the base lock map never merges distinct keys.\n return super.parseKey(key);\n }\n\n /**\n * {@inheritdoc}\n *\n * Nothing is stored, so always run the callback and return its own result.\n */\n public async remember(\n _key: CacheKey,\n _ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<any>,\n ): Promise<any> {\n return callback();\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n const loggableNamespace = maskEmailsInText(namespace);\n\n log.info(\"cache\", \"clearing namespace\", loggableNamespace);\n\n log.success(\"cache\", \"namespace cleared\", loggableNamespace);\n\n return this;\n }\n\n /**\n * The key as it may appear in a log line: parsed, with email addresses masked\n */\n protected loggableKey(key: CacheKey): string {\n return maskEmailsInText(this.parseKey(key));\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n _value: any,\n _ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n log.info(\"cache\", \"setting key\", this.loggableKey(key));\n\n log.success(\"cache\", \"key set\", this.loggableKey(key));\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n log.info(\"cache\", \"fetching\", this.loggableKey(key));\n\n log.success(\"cache\", \"fetched\", this.loggableKey(key));\n\n return null;\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n log.info(\"cache\", \"removing\", this.loggableKey(key));\n\n log.success(\"cache\", \"removed\", this.loggableKey(key));\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n log.info(\"cache\", \"flushing\", \"all\");\n\n log.success(\"cache\", \"flushed\", \"all\");\n }\n\n /**\n * {@inheritdoc}\n *\n * The null driver is a black-hole — `similar()` mirrors `get()` and returns\n * an empty result set rather than throwing.\n */\n public async similar(): Promise<any[]> {\n return [];\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n log.success(\"cache\", \"connected\", \"Connected to null cache driver\");\n }\n}\n","import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n PgCacheOptions,\n PgClientLike,\n} from \"../types\";\nimport { CacheConfigurationError, CacheError, CacheUnsupportedError } from \"../types\";\nimport { parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\n/** Compare-and-set attempts before `update()` gives up under contention. */\nconst PG_UPDATE_MAX_ATTEMPTS = 10;\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 * {@inheritdoc}\n *\n * Single `DELETE … WHERE key AND value` — atomic in Postgres.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n\n const res = await this.pgClient.query(\n `DELETE FROM ${this.table}\n WHERE key = $1 AND value = $2::jsonb AND (expires_at IS NULL OR expires_at > now())\n RETURNING 1`,\n [parsedKey, JSON.stringify(expected)],\n );\n\n if (res.rows.length === 0) {\n return false;\n }\n\n await this.emit(\"removed\", { key: parsedKey });\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * One `INSERT … ON CONFLICT DO UPDATE` that unions the stored JSON array with\n * `members`. Postgres row-locks the conflicting row, so concurrent adders on\n * any number of servers serialize and none is lost.\n *\n * The index stays a row of the main table holding a JSON array — the same\n * shape earlier versions wrote — so no schema change or migration is needed\n * and existing indexes keep working. (The `tags` column only records tags\n * passed inline to `set()`, not relationships added afterwards, so it can't\n * serve as the invalidation index.)\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n const t = this.table;\n\n await this.pgClient.query(\n `/* cache:tag-add */ INSERT INTO ${t} (key, value)\n VALUES ($1, to_jsonb($2::text[]))\n ON CONFLICT (key) DO UPDATE\n SET value = (\n SELECT COALESCE(jsonb_agg(u.member), '[]'::jsonb)\n FROM (\n SELECT x.member FROM jsonb_array_elements_text(\n CASE WHEN jsonb_typeof(${t}.value) = 'array' THEN ${t}.value ELSE '[]'::jsonb END\n ) AS x(member)\n UNION\n SELECT y.member FROM unnest($2::text[]) AS y(member)\n ) AS u\n ),\n expires_at = NULL`,\n [this.parseKey(tagKey), members],\n );\n }\n\n /**\n * {@inheritdoc}\n *\n * One `UPDATE` that filters `members` out of the stored array (members added\n * concurrently are kept), then a conditional `DELETE` of the row if it ended\n * up empty.\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n const t = this.table;\n const parsedKey = this.parseKey(tagKey);\n\n await this.pgClient.query(\n `/* cache:tag-remove */ UPDATE ${t}\n SET value = (\n SELECT COALESCE(jsonb_agg(x.member), '[]'::jsonb)\n FROM jsonb_array_elements_text(\n CASE WHEN jsonb_typeof(${t}.value) = 'array' THEN ${t}.value ELSE '[]'::jsonb END\n ) AS x(member)\n WHERE x.member <> ALL($2::text[])\n )\n WHERE key = $1`,\n [parsedKey, members],\n );\n\n await this.pgClient.query(\n `/* cache:tag-drop-empty */ DELETE FROM ${t} WHERE key = $1 AND value = '[]'::jsonb`,\n [parsedKey],\n );\n }\n\n /**\n * TTL in seconds bound to `now() + make_interval(secs => $n)`, so expiry is\n * computed on the DB clock (immune to app/DB clock skew). `null` — from\n * `Infinity` / 0 / undefined — yields a NULL `expires_at` (never expires).\n */\n protected ttlToSeconds(ttl?: number): number | null {\n if (!ttl || ttl === Infinity) {\n return null;\n }\n\n return ttl;\n }\n\n /**\n * Delete expired rows in batches of `limit`. Returns how many were removed.\n * Also invoked opportunistically (~1 in 200 writes) by `set()`.\n */\n public async prune(limit = 1000): Promise<number> {\n const t = this.table;\n const res = await this.pgClient.query(\n `DELETE FROM ${t} WHERE ctid IN (SELECT ctid FROM ${t} WHERE expires_at < now() LIMIT $1)`,\n [limit],\n );\n\n return res.rowCount ?? 0;\n }\n\n /**\n * Fire-and-forget `prune()` on roughly 1 in 200 writes; errors are logged.\n */\n protected maybePrune(): void {\n if (Math.random() >= 1 / 200) {\n return;\n }\n\n this.prune().catch((error) => this.logError(\"prune failed\", error));\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.ttlToSeconds(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 = ttl seconds (expires_at = now() + make_interval), $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 = [\n \"$1\",\n \"$2::jsonb\",\n \"now() + make_interval(secs => $3::double precision)\",\n \"$4\",\n \"$5\",\n ];\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(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n this.maybePrune();\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(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n this.maybePrune();\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(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n this.maybePrune();\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 * Decode a JSONB column value (node-postgres usually parses it already;\n * some pools hand back a string).\n */\n protected decodeJson(value: unknown): any {\n if (typeof value === \"string\") {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n *\n * One atomic `INSERT … ON CONFLICT DO UPDATE` statement, so concurrent\n * increments from any number of servers never lose an update. A live row\n * keeps its `expires_at`; a missing or expired row restarts from `value`\n * with the driver default TTL. A non-numeric live value is rejected.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const parsedKey = this.parseKey(key);\n const t = this.table;\n const expired = `${t}.expires_at IS NOT NULL AND ${t}.expires_at <= now()`;\n\n const { rows } = await this.pgClient.query(\n `/* cache:increment */ INSERT INTO ${t}(key, value, expires_at)\n VALUES ($1, to_jsonb($2::numeric), now() + make_interval(secs => $3::double precision))\n ON CONFLICT (key) DO UPDATE\n SET value = CASE WHEN ${expired} THEN EXCLUDED.value\n ELSE to_jsonb((${t}.value #>> '{}')::numeric + $2::numeric) END,\n expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at\n WHEN $4::boolean AND ${t}.expires_at IS NULL THEN EXCLUDED.expires_at\n ELSE ${t}.expires_at END,\n stale_at = CASE WHEN ${expired} THEN NULL ELSE ${t}.stale_at END\n WHERE (${expired}) OR jsonb_typeof(${t}.value) = 'number'\n RETURNING value`,\n [\n parsedKey,\n value,\n this.ttlToSeconds(options.ttl === undefined ? this.ttl : parseTtl(options.ttl)),\n options.ttl !== undefined,\n ],\n );\n\n if (rows.length === 0) {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = Number(this.decodeJson(rows[0].value));\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: this.ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * One atomic `DELETE … RETURNING`, so a value is handed out exactly once\n * across servers. Expired rows count as missing.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n\n const { rows } = await this.pgClient.query(\n `/* cache:pull */ DELETE FROM ${this.table}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())\n RETURNING value`,\n [parsedKey],\n );\n\n if (rows.length === 0) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = this.decodeJson(rows[0].value);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n *\n * Atomic across servers via optimistic compare-and-set: read the live value,\n * run `fn`, then write with `UPDATE … WHERE value = <what was read>` (or a\n * create-only insert for a missing key), retrying when another writer got\n * there first. `fn` may run more than once under contention; after\n * {@link PG_UPDATE_MAX_ATTEMPTS} lost races it throws {@link CacheError}.\n *\n * Without an explicit `ttl` the row keeps its `expires_at`; a new key gets\n * the driver default TTL. Returning `null` deletes the key (compare-and-delete).\n */\n public async 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 const parsedKey = this.parseKey(key);\n const t = this.table;\n const live = `(expires_at IS NULL OR expires_at > now())`;\n const explicitTtl = options.ttl !== undefined ? parseTtl(options.ttl) : undefined;\n\n for (let attempt = 0; attempt < PG_UPDATE_MAX_ATTEMPTS; attempt++) {\n const read = await this.pgClient.query(\n `SELECT value FROM ${t}\n WHERE key = $1 AND ${live}`,\n [parsedKey],\n );\n\n const exists = read.rows.length > 0;\n const current = exists ? (this.decodeJson(read.rows[0].value) as T) : null;\n const expected = exists ? JSON.stringify(current) : null;\n const result = await fn(current);\n\n if (result === null) {\n if (!exists) {\n return null;\n }\n\n const deleted = await this.pgClient.query(\n `/* cache:cas-delete */ DELETE FROM ${t}\n WHERE key = $1 AND value = $2::jsonb AND ${live}\n RETURNING 1`,\n [parsedKey, expected],\n );\n\n if (deleted.rows.length > 0) {\n await this.emit(\"removed\", { key: parsedKey });\n\n return null;\n }\n\n continue;\n }\n\n if (!exists) {\n const created = (await this.set(key, result, {\n onConflict: \"create\",\n ...(explicitTtl !== undefined ? { ttl: explicitTtl } : {}),\n })) as CacheSetResult;\n\n if (created.wasSet) {\n return result;\n }\n\n continue;\n }\n\n const params: unknown[] = [parsedKey, JSON.stringify(result), expected];\n let expirySql = \"\";\n\n if (explicitTtl !== undefined) {\n params.push(this.ttlToSeconds(explicitTtl));\n expirySql = `, expires_at = now() + make_interval(secs => $4::double precision)`;\n }\n\n const updated = await this.pgClient.query(\n `/* cache:cas-update */ UPDATE ${t}\n SET value = $2::jsonb, stale_at = NULL${expirySql}\n WHERE key = $1 AND value = $3::jsonb AND ${live}\n RETURNING 1`,\n params,\n );\n\n if (updated.rows.length > 0) {\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: explicitTtl });\n\n return result;\n }\n }\n\n throw new CacheError(\n `cache.update(): '${parsedKey}' changed on every attempt (${PG_UPDATE_MAX_ATTEMPTS} tries) — too much write contention.`,\n );\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\";\nimport type { createClient } from \"redis\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n RedisOptions,\n} from \"../types\";\nimport { CacheConfigurationError, CacheError, CacheUnsupportedError } from \"../types\";\nimport { parseTtl, safeErrorInfo } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\n// ============================================================\n// Lazy-loaded Redis SDK Types\n// ============================================================\n\n/**\n * Cached Redis module (loaded once, reused)\n */\nlet RedisClient: typeof import(\"redis\");\n\nlet isModuleExists: boolean | null = null;\n\n/**\n * Installation instructions for Redis package\n */\nconst REDIS_INSTALL_INSTRUCTIONS = `\nRedis cache driver requires the redis package.\nInstall it with:\n\n npm install redis\n\nOr with your preferred package manager:\n\n pnpm add redis\n yarn add redis\n`.trim();\n\n/**\n * Load Redis module\n */\nasync function loadRedis() {\n try {\n RedisClient = await import(\"redis\");\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n}\n\nloadRedis();\n\n// ============================================================\n// Lua scripts (atomic ownership primitives)\n// ============================================================\n\n/** KEYS[1]=key, ARGV[1]=expected raw value. 1 when deleted, 0 when not the owner. */\nexport const REDIS_COMPARE_DELETE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then redis.call('DEL', KEYS[1]) return 1 else return 0 end`;\n\n/**\n * Compare-and-set used by `update()`.\n *\n * KEYS[1]=key; ARGV[1]=expected raw value (ignored when ARGV[3]=='1');\n * ARGV[2]=new raw value; ARGV[3]='1' when the key is expected to be missing;\n * ARGV[4]=ttl seconds to apply ('0' = no expiry); ARGV[5]='keep' to keep the\n * existing TTL instead (only when the key existed).\n *\n * Returns 1 when written, 0 when the current value no longer matches.\n * `KEEPTTL` requires Redis >= 6.\n */\nexport const REDIS_CAS_SET_SCRIPT = `local cur = redis.call('GET', KEYS[1])\nif ARGV[3] == '1' then\n if cur then return 0 end\nelseif cur ~= ARGV[1] then\n return 0\nend\nlocal ttl = tonumber(ARGV[4])\nif ARGV[5] == 'keep' and ARGV[3] ~= '1' then\n redis.call('SET', KEYS[1], ARGV[2], 'KEEPTTL')\nelseif ttl > 0 then\n redis.call('SET', KEYS[1], ARGV[2], 'EX', ttl)\nelse\n redis.call('SET', KEYS[1], ARGV[2])\nend\nreturn 1`;\n\n/** KEYS[1]=key. Fallback for clients without `getDel`: returns the value and deletes it. */\nexport const REDIS_GET_DEL_SCRIPT = `local v = redis.call('GET', KEYS[1])\nif v then redis.call('DEL', KEYS[1]) end\nreturn v`;\n\n/**\n * Increment that gives a counter without an expiry its per-call or default TTL,\n * in one round trip.\n *\n * KEYS[1]=key; ARGV[1]=amount; ARGV[2]=ttl seconds ('0' = none).\n *\n * Replaces `INCRBY` + `EXPIRE … NX`: the `NX` flag needs Redis >= 7.0, and on\n * Redis 6 the second command failed after the count had already gone up.\n * `TTL == -1` means \"exists without an expiry\", so a TTL set earlier (by\n * `set(..., { ttl })`) is never shortened or reset.\n */\nexport const REDIS_INCREMENT_SCRIPT = `local v = redis.call('INCRBY', KEYS[1], ARGV[1])\nlocal ttl = tonumber(ARGV[2])\nif ttl > 0 and redis.call('TTL', KEYS[1]) == -1 then\n redis.call('EXPIRE', KEYS[1], ttl)\nend\nreturn v`;\n\n/** Compare-and-set attempts before `update()` gives up under contention. */\nconst UPDATE_MAX_ATTEMPTS = 10;\n\n/** Keys deleted per UNLINK/DEL while draining a namespace. */\nconst REMOVE_BATCH_SIZE = 500;\n\n// ============================================================\n// RedisCacheDriver Class\n// ============================================================\n\nexport class RedisCacheDriver\n extends BaseCacheDriver<ReturnType<typeof createClient>, RedisOptions>\n implements CacheDriver<ReturnType<typeof createClient>, RedisOptions>\n{\n /**\n * Cache driver name\n */\n public name = \"redis\";\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: RedisOptions) {\n if (!options.url && !options.host) {\n throw new CacheConfigurationError(\n \"Redis driver requires either 'url' or 'host' option to be configured.\",\n );\n }\n\n return super.setOptions(options);\n }\n\n /**\n * {@inheritDoc}\n */\n public async removeNamespace(namespace: string) {\n namespace = this.parseKey(namespace);\n\n this.log(\"clearing\", namespace);\n\n // Escape Redis glob metacharacters so a namespace carrying `*`/`?`/`[`\n // cannot widen the match and delete keys outside its own prefix.\n const escaped = namespace.replace(/[\\\\*?[\\]]/g, \"\\\\$&\");\n\n // Match the namespace key itself plus `<ns>.*` — never bare `<ns>*`, which\n // would also swallow siblings like `users2`. An empty namespace (no prefix)\n // means \"everything\".\n const pattern = escaped === \"\" ? \"*\" : `${escaped}.*`;\n\n const deleted: string[] = [];\n\n if (this.client) {\n if (escaped !== \"\") {\n const removedSelf = await this.deleteKeys([namespace]);\n\n if (removedSelf > 0) {\n deleted.push(namespace);\n }\n }\n\n // `SCAN` (cursor-based, non-blocking) instead of `KEYS` — `KEYS` is O(N)\n // and blocks the single-threaded Redis event loop. Delete per batch while\n // scanning so memory stays bounded on large keyspaces.\n let batch: string[] = [];\n\n for await (const scanned of this.client.scanIterator({\n MATCH: pattern,\n COUNT: 100,\n })) {\n // node-redis 5 yields a batch of keys for each SCAN cursor response;\n // older supported clients yielded one key at a time. Normalize both\n // forms before passing keys to the command parser.\n const keys = Array.isArray(scanned) ? scanned : [scanned];\n\n for (const key of keys) {\n batch.push(key as unknown as string);\n\n if (batch.length >= REMOVE_BATCH_SIZE) {\n await this.deleteKeys(batch);\n deleted.push(...batch);\n batch = [];\n }\n }\n }\n\n if (batch.length > 0) {\n await this.deleteKeys(batch);\n deleted.push(...batch);\n }\n }\n\n if (deleted.length === 0) {\n this.log(\"notFound\", namespace);\n return;\n }\n\n this.log(\"cleared\", namespace);\n\n return deleted;\n }\n\n /**\n * Delete keys with `UNLINK` (non-blocking free), falling back to `DEL` when\n * the client lacks it. Returns the number of keys removed.\n */\n protected async deleteKeys(keys: string[]): Promise<number> {\n const client = this.client as any;\n\n if (typeof client.unlink === \"function\") {\n return Number(await client.unlink(keys)) || 0;\n }\n\n return Number(await client.del(keys)) || 0;\n }\n\n // ============================================================\n // Tag index — a native Redis SET per tag\n // ============================================================\n\n /**\n * {@inheritdoc}\n *\n * `SADD` — atomic across servers, so concurrent tagged writes on any number\n * of instances never drop a member. The set key lives inside the prefix\n * namespace, so `flush()` / `removeNamespace()` sweep it too.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n const parsedKey = this.parseKey(tagKey);\n\n await this.withTagSet(parsedKey, (client) => client.sAdd(parsedKey, members));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n const parsedKey = this.parseKey(tagKey);\n\n return this.withTagSet(parsedKey, async (client) => (await client.sMembers(parsedKey)) ?? []);\n }\n\n /**\n * {@inheritdoc}\n *\n * `SREM` — Redis drops the set once its last member is removed.\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n if (members.length === 0) {\n return;\n }\n\n const parsedKey = this.parseKey(tagKey);\n\n await this.withTagSet(parsedKey, (client) => client.sRem(parsedKey, members));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n await this.client?.del(this.parseKey(tagKey));\n }\n\n /**\n * Run a SET command against a tag key, upgrading a pre-5.20 index first.\n *\n * Earlier versions stored the index as a JSON-array STRING under the same\n * key, which makes any SET command fail with `WRONGTYPE`. On that error the\n * legacy array is read, the string deleted, its members re-added with\n * `SADD`, and the command retried once.\n */\n protected async withTagSet<T>(parsedKey: string, run: (client: any) => Promise<T>): Promise<T> {\n const client = this.client as any;\n\n try {\n return await run(client);\n } catch (error) {\n if (!/WRONGTYPE/i.test(String((error as Error)?.message ?? error))) {\n throw error;\n }\n\n const raw: string | null = await client.get(parsedKey);\n let legacy: unknown = [];\n\n try {\n legacy = raw === null ? [] : JSON.parse(raw);\n } catch {\n legacy = [];\n }\n\n await client.del(parsedKey);\n\n const members = Array.isArray(legacy)\n ? legacy.filter((member): member is string => typeof member === \"string\")\n : [];\n\n if (members.length > 0) {\n await client.sAdd(parsedKey, members);\n }\n\n return run(client);\n }\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 throw new CacheUnsupportedError(\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.\",\n );\n }\n\n this.log(\"caching\", parsedKey);\n\n const serialized = JSON.stringify(value);\n const hasExpiry = Boolean(ttl) && ttl !== Infinity;\n\n let reply: string | null | undefined;\n\n if (onConflict === \"create\") {\n const options: { NX: true; EX?: number } = { NX: true };\n if (hasExpiry) {\n options.EX = ttl as number;\n }\n reply = await this.client?.set(parsedKey, serialized, options);\n } else if (onConflict === \"update\") {\n const options: { XX: true; EX?: number } = { XX: true };\n if (hasExpiry) {\n options.EX = ttl as number;\n }\n reply = await this.client?.set(parsedKey, serialized, options);\n } else if (hasExpiry) {\n reply = await this.client?.set(parsedKey, serialized, { EX: ttl as number });\n } else {\n reply = await this.client?.set(parsedKey, serialized);\n }\n\n const wasSet = reply === \"OK\";\n\n if ((onConflict === \"create\" || onConflict === \"update\") && !wasSet) {\n const existing = onConflict === \"create\" ? ((await this.get(key)) as any) : null;\n return { wasSet: false, existing } satisfies CacheSetResult;\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n if (staleAt === undefined) {\n // A plain write invalidates any freshness marker left by an earlier SWR\n // write, otherwise the old `staleAt` would apply to the new value.\n await this.client?.del(this.swrMetaKey(parsedKey));\n } else {\n // Sidecar key for SWR freshness — keeps the main value JSON\n // backwards-compatible with entries written before SWR landed.\n const sidecarOptions: { EX?: number } = {};\n\n if (hasExpiry) {\n sidecarOptions.EX = ttl as number;\n }\n\n await this.client?.set(this.swrMetaKey(parsedKey), String(staleAt), sidecarOptions);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n return { wasSet: true, existing: null } satisfies CacheSetResult;\n }\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n *\n * Values are stored as `JSON.stringify(value)`, so the compare runs against\n * the serialized form inside Lua.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n\n const deleted = await (this.client as any).eval(REDIS_COMPARE_DELETE_SCRIPT, {\n keys: [parsedKey],\n arguments: [JSON.stringify(expected)],\n });\n\n if (Number(deleted) !== 1) {\n return false;\n }\n\n // Mirror remove(): drop the SWR sidecar and emit the event.\n await this.client?.del(this.swrMetaKey(parsedKey));\n await this.emit(\"removed\", { key: parsedKey });\n\n return true;\n }\n\n /**\n * Build the sidecar key Redis uses to track SWR freshness without\n * wrapping the main value JSON.\n */\n protected swrMetaKey(parsedKey: string): string {\n return `${parsedKey}::swrmeta`;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, fetching the value and the\n * SWR sidecar in parallel. Returns `null` when the main key is missing\n * or expired (Redis handles expiry natively, so the absence of the\n * value alone tells us).\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n\n const [valueRaw, staleAtRaw] = await Promise.all([\n this.client?.get(parsedKey),\n this.client?.get(this.swrMetaKey(parsedKey)),\n ]);\n\n if (!valueRaw) {\n return null;\n }\n\n const data = JSON.parse(valueRaw);\n const staleAt = staleAtRaw ? Number(staleAtRaw) : undefined;\n\n return staleAt !== undefined ? { data, staleAt } : { data };\n }\n\n /**\n * {@inheritdoc}\n *\n * Redis tracks expiry natively (the payload carries no `expiresAt`), so read\n * the remaining lifetime with the `TTL` command. Redis returns `-2` for a\n * missing key and `-1` for a key with no expiry.\n */\n protected async getRemainingTtl(key: CacheKey): Promise<number | undefined> {\n const parsedKey = this.parseKey(key);\n const ttl = await this.client?.ttl(parsedKey);\n\n if (ttl === undefined || ttl === -2) {\n return undefined;\n }\n\n if (ttl === -1) {\n return Infinity;\n }\n\n return ttl;\n }\n\n /**\n * {@inheritDoc}\n */\n public async get(key: CacheKey) {\n key = this.parseKey(key);\n\n this.log(\"fetching\", key);\n\n const value = await this.client?.get(key);\n\n if (!value) {\n this.log(\"notFound\", key);\n // Emit miss event\n await this.emit(\"miss\", { key });\n return null;\n }\n\n this.log(\"fetched\", key);\n\n // Parse and return the value directly (Redis handles expiration natively)\n const parsedValue = JSON.parse(value);\n\n // Apply cloning for immutability protection\n if (parsedValue === null || parsedValue === undefined) {\n // Emit hit event\n await this.emit(\"hit\", { key, value: parsedValue });\n return parsedValue;\n }\n\n const type = typeof parsedValue;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n // Emit hit event\n await this.emit(\"hit\", { key, value: parsedValue });\n return parsedValue;\n }\n\n try {\n const clonedValue = structuredClone(parsedValue);\n // Emit hit event\n await this.emit(\"hit\", { key, value: clonedValue });\n return clonedValue;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${key}`, error);\n throw error;\n }\n }\n\n /**\n * {@inheritDoc}\n */\n public async remove(key: CacheKey) {\n key = this.parseKey(key);\n\n this.log(\"removing\", key);\n\n // Drop the SWR sidecar alongside the main key — keeps metadata from\n // surviving a `remove` and confusing a later `swr` read.\n // Two separate DELs (not one multi-key DEL) so it stays cluster-slot safe.\n await this.client?.del(key);\n await this.client?.del(this.swrMetaKey(key));\n\n this.log(\"removed\", key);\n\n await this.emit(\"removed\", { key });\n }\n\n /**\n * {@inheritDoc}\n *\n * WARNING: without a `globalPrefix` this runs `FLUSHDB`, which clears the\n * WHOLE selected Redis database (including keys not written by this driver).\n * Configure a `globalPrefix` to only delete this driver's keys.\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n await this.client?.flushDb();\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritDoc}\n */\n public async connect() {\n if (this.clientDriver) return;\n\n if (!isModuleExists) {\n throw new Error(REDIS_INSTALL_INSTRUCTIONS);\n }\n\n const options = this.options;\n\n if (options && !options.url && options.host) {\n const auth =\n options.password || options.username ? `${options.username}:${options.password}@` : \"\";\n\n if (!options.url) {\n const host = options.host || \"localhost\";\n const port = options.port || 6379;\n options.url = `redis://${auth}${host}:${port}`;\n }\n }\n\n const clientOptions = {\n ...options,\n ...(this.options.clientOptions || {}),\n };\n\n try {\n this.log(\"connecting\");\n const { createClient } = RedisClient;\n\n this.client = createClient(clientOptions);\n\n this.client.on(\"error\", (error: Error) => {\n // Never pass the raw `error` object to the logger — connection\n // errors can carry the connection URL (with password) in the\n // message/cause. Only the redacted message survives past this point.\n const { message } = safeErrorInfo(error);\n\n if ((error as any).code === \"ECONNREFUSED\") {\n this.log(\"connectionFailed\", message);\n } else {\n this.log(\"error\", message);\n }\n });\n\n await this.client.connect();\n\n this.log(\"connected\");\n await this.emit(\"connected\");\n } catch (error) {\n // Boot-time cache connection failure is unrecoverable in practice —\n // `fatal` aligns Redis with the cascade drivers and herald connector\n // for clean \"page on fatal only\" alerting. Only the redacted\n // `{ message, code }` shape is logged; the raw error (which may carry\n // the connection URL/password) never reaches stdout or the logger.\n log.fatal(\"cache\", \"redis\", \"Failed to connect\", safeErrorInfo(error));\n await this.emit(\"error\", { error });\n\n // Drop the half-initialised client so a later connect() can retry.\n this.clientDriver = undefined as unknown as typeof this.clientDriver;\n\n throw error;\n }\n }\n\n /**\n * {@inheritDoc}\n *\n * Guards against disconnecting when the client was never created. The base\n * `client` getter falls back to `this` when no client is set, so we check\n * the backing `clientDriver` directly — using `this.client` for this guard\n * would always be truthy and crash with \"this.quit is not a function\".\n */\n public async disconnect() {\n if (!this.clientDriver) {\n return;\n }\n\n this.log(\"disconnecting\");\n\n await this.clientDriver.quit();\n\n this.log(\"disconnected\");\n await this.emit(\"disconnected\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Atomic across servers: `GETDEL` (Redis >= 6.2), or an equivalent Lua\n * script when the client has no `getDel`. A value is handed out once.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const client = this.client as any;\n\n const raw: string | null | undefined =\n typeof client.getDel === \"function\"\n ? await client.getDel(parsedKey)\n : await client.eval(REDIS_GET_DEL_SCRIPT, { keys: [parsedKey], arguments: [] });\n\n if (raw === null || raw === undefined) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.client?.del(this.swrMetaKey(parsedKey));\n\n const value = JSON.parse(raw);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n *\n * Atomic across servers via optimistic compare-and-set: read the raw value,\n * run `fn`, then write only if the value is still what was read (Lua), and\n * retry otherwise. `fn` may therefore run more than once under contention;\n * after {@link UPDATE_MAX_ATTEMPTS} lost races it throws {@link CacheError}.\n *\n * Without an explicit `ttl` the existing expiry is kept (`KEEPTTL`); a new\n * key gets the driver default TTL. Returning `null` deletes the key\n * (compare-and-delete).\n */\n public async 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 const parsedKey = this.parseKey(key);\n const client = this.client as any;\n\n const explicitTtl = options.ttl !== undefined ? parseTtl(options.ttl) : undefined;\n\n for (let attempt = 0; attempt < UPDATE_MAX_ATTEMPTS; attempt++) {\n const raw: string | null = (await client.get(parsedKey)) ?? null;\n const current = raw === null ? null : (JSON.parse(raw) as T);\n const result = await fn(current);\n\n if (result === null) {\n if (raw === null) {\n return null;\n }\n\n const deleted = await client.eval(REDIS_COMPARE_DELETE_SCRIPT, {\n keys: [parsedKey],\n arguments: [raw],\n });\n\n if (Number(deleted) === 1) {\n await this.client?.del(this.swrMetaKey(parsedKey));\n await this.emit(\"removed\", { key: parsedKey });\n\n return null;\n }\n\n continue;\n }\n\n const isNew = raw === null;\n const ttlSeconds = explicitTtl ?? (isNew ? this.ttl : 0);\n const keep = explicitTtl === undefined && !isNew;\n\n const written = await client.eval(REDIS_CAS_SET_SCRIPT, {\n keys: [parsedKey],\n arguments: [\n raw ?? \"\",\n JSON.stringify(result),\n isNew ? \"1\" : \"0\",\n String(Number.isFinite(ttlSeconds) && ttlSeconds > 0 ? ttlSeconds : 0),\n keep ? \"keep\" : \"\",\n ],\n });\n\n if (Number(written) === 1) {\n // A plain write invalidates any SWR freshness marker.\n await this.client?.del(this.swrMetaKey(parsedKey));\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: ttlSeconds });\n\n return result;\n }\n }\n\n throw new CacheError(\n `cache.update(): '${parsedKey}' changed on every attempt (${UPDATE_MAX_ATTEMPTS} tries) — too much write contention.`,\n );\n }\n\n /**\n * Atomic increment using Redis native INCRBY command\n * {@inheritdoc}\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const parsedKey = this.parseKey(key);\n\n this.log(\"caching\", parsedKey);\n\n // Apply the per-call TTL (or driver default) when INCRBY creates a key,\n // atomically with the increment and without Redis 7's `EXPIRE … NX`.\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const defaultTtl = Number.isFinite(ttl) && ttl > 0 ? Math.ceil(ttl) : 0;\n const result =\n defaultTtl > 0\n ? Number(\n await (this.client as any)?.eval(REDIS_INCREMENT_SCRIPT, {\n keys: [parsedKey],\n arguments: [String(value), String(defaultTtl)],\n }),\n )\n : await this.client?.incrBy(parsedKey, value);\n\n this.log(\"cached\", parsedKey);\n\n // Emit set event\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: undefined });\n\n return result || 0;\n }\n\n /**\n * Atomic decrement using Redis native DECRBY command\n * {@inheritdoc}\n */\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n\n this.log(\"caching\", parsedKey);\n\n const result = await this.client?.decrBy(parsedKey, value);\n\n this.log(\"cached\", parsedKey);\n\n // Emit set event\n await this.emit(\"set\", { key: parsedKey, value: result, ttl: undefined });\n\n return result || 0;\n }\n\n /**\n * Set if not exists (atomic operation)\n * Returns true if key was set, false if key already existed\n */\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n\n this.log(\"caching\", parsedKey);\n\n if (ttl === undefined) {\n ttl = this.ttl;\n }\n\n let result: string | null;\n\n // Use Redis native SET with NX option\n if (ttl && ttl !== Infinity) {\n result = await this.client?.set(parsedKey, JSON.stringify(value), {\n NX: true,\n EX: ttl,\n });\n } else {\n result = await this.client?.set(parsedKey, JSON.stringify(value), {\n NX: true,\n });\n }\n\n const wasSet = result === \"OK\";\n\n if (wasSet) {\n this.log(\"cached\", parsedKey);\n // Emit set event\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n } else {\n this.log(\"notFound\", parsedKey);\n }\n\n return wasSet;\n }\n}\n"],"mappings":"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"}
|