@warlock.js/cache 5.2.3 → 5.2.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"memory-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/memory-cache-driver.ts"],"sourcesContent":["import type { GenericObject } from \"@mongez/reinforcements\";\r\nimport { get, set, unset } from \"@mongez/reinforcements\";\r\nimport type {\r\n CacheData,\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheTtl,\r\n MemoryCacheOptions,\r\n} from \"../types\";\r\nimport { cosineSimilarity } from \"../utils\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nexport class MemoryCacheDriver\r\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\r\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\r\n{\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"memory\";\r\n\r\n /**\r\n * Cached data\r\n */\r\n public data: GenericObject = {};\r\n\r\n /**\r\n * List of data that will be cleared from cache\r\n */\r\n protected temporaryData: Record<\r\n string,\r\n {\r\n key: string;\r\n expiresAt: number;\r\n }\r\n > = {};\r\n\r\n /**\r\n * Cleanup interval reference\r\n */\r\n protected cleanupInterval?: NodeJS.Timeout;\r\n\r\n /**\r\n * Access order tracking for LRU eviction (when maxSize is set)\r\n */\r\n protected accessOrder: string[] = [];\r\n\r\n /**\r\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\r\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\r\n * `remove`, `flush`, expiry, namespace clear, and LRU eviction.\r\n */\r\n protected vectorIndex: Map<string, number[]> = new Map();\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public constructor() {\r\n super();\r\n\r\n this.startCleanup();\r\n }\r\n\r\n /**\r\n * Start the cleanup process whenever a data that has a cache key is set\r\n */\r\n public startCleanup() {\r\n // Clear existing interval if any\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n }\r\n\r\n this.cleanupInterval = setInterval(async () => {\r\n const now = Date.now();\r\n\r\n for (const key in this.temporaryData) {\r\n if (this.temporaryData[key].expiresAt <= now) {\r\n await this.remove(this.temporaryData[key].key);\r\n delete this.temporaryData[key];\r\n\r\n this.log(\"expired\", key);\r\n // Emit expired event\r\n await this.emit(\"expired\", { key });\r\n }\r\n }\r\n }, 1000);\r\n\r\n // do not block the process from exiting\r\n this.cleanupInterval.unref();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n this.log(\"clearing\", namespace);\r\n\r\n namespace = this.parseKey(namespace);\r\n\r\n unset(this.data, [namespace]);\r\n\r\n // Drop vector entries that fall under this namespace.\r\n if (namespace === \"\") {\r\n this.vectorIndex.clear();\r\n } else {\r\n const prefix = namespace + \".\";\r\n for (const k of [...this.vectorIndex.keys()]) {\r\n if (k === namespace || k.startsWith(prefix)) {\r\n this.vectorIndex.delete(k);\r\n }\r\n }\r\n }\r\n\r\n this.log(\"cleared\", namespace);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n // Use get() for the existence check so expired entries are treated as\r\n // missing (and cleaned up as a side effect). A raw map lookup would let\r\n // stale entries block onConflict: \"create\" even after their TTL elapsed.\r\n const existingValue = onConflict === \"upsert\" ? null : await this.get(key);\r\n const exists = existingValue !== null;\r\n\r\n if (onConflict === \"create\" && exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: existingValue };\r\n return result;\r\n }\r\n\r\n if (onConflict === \"update\" && !exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: null };\r\n return result;\r\n }\r\n\r\n const data = this.prepareDataForStorage(value, ttl, staleAt);\r\n\r\n if (ttl) {\r\n this.setTemporaryData(key, parsedKey, ttl);\r\n }\r\n\r\n set(this.data, parsedKey, data);\r\n\r\n this.trackAccess(parsedKey);\r\n\r\n if (!exists && this.options.maxSize) {\r\n await this.enforceMaxSize();\r\n }\r\n\r\n if (tags && tags.length > 0) {\r\n await this.applyTags(parsedKey, tags);\r\n }\r\n\r\n if (vector) {\r\n this.vectorIndex.set(parsedKey, vector.slice());\r\n }\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n await this.emit(\"set\", { key: parsedKey, value, ttl });\r\n\r\n if (onConflict === \"create\" || onConflict === \"update\") {\r\n const result: CacheSetResult = { wasSet: true, existing: null };\r\n return result;\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"fetching\", parsedKey);\r\n\r\n const value: CacheData = get(this.data, parsedKey);\r\n\r\n if (!value) {\r\n this.log(\"notFound\", parsedKey);\r\n // Emit miss event\r\n await this.emit(\"miss\", { key: parsedKey });\r\n return null;\r\n }\r\n\r\n const result = await this.parseCachedData(parsedKey, value);\r\n\r\n if (result === null) {\r\n // Expired\r\n await this.emit(\"miss\", { key: parsedKey });\r\n } else {\r\n // Track access for LRU\r\n this.trackAccess(parsedKey);\r\n // Emit hit event\r\n await this.emit(\"hit\", { key: parsedKey, value: result });\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\r\n * Returns `null` for missing or expired entries so the SWR flow can branch\r\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const parsedKey = this.parseKey(key);\r\n const entry: CacheData | undefined = get(this.data, parsedKey);\r\n\r\n if (!entry) {\r\n return null;\r\n }\r\n\r\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\r\n return null;\r\n }\r\n\r\n return entry;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"removing\", parsedKey);\r\n\r\n unset(this.data, [parsedKey]);\r\n\r\n // Clean up from temporaryData as well\r\n delete this.temporaryData[parsedKey];\r\n\r\n // Remove from access order\r\n this.removeFromAccessOrder(parsedKey);\r\n\r\n // Drop the vector index entry if any\r\n this.vectorIndex.delete(parsedKey);\r\n\r\n this.log(\"removed\", parsedKey);\r\n\r\n // Emit removed event\r\n await this.emit(\"removed\", { key: parsedKey });\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n this.log(\"flushing\");\r\n if (this.options.globalPrefix) {\r\n this.removeNamespace(\"\");\r\n } else {\r\n this.data = {};\r\n this.accessOrder = [];\r\n this.vectorIndex.clear();\r\n }\r\n\r\n this.log(\"flushed\");\r\n\r\n // Emit flushed event\r\n await this.emit(\"flushed\");\r\n }\r\n\r\n /**\r\n * Set the temporary data\r\n */\r\n protected setTemporaryData(key: CacheKey, parsedKey: string, ttl: number) {\r\n this.temporaryData[parsedKey] = {\r\n key: JSON.stringify(key),\r\n expiresAt: Date.now() + ttl * 1000,\r\n };\r\n }\r\n\r\n /**\r\n * Track access for LRU eviction\r\n */\r\n protected trackAccess(key: string) {\r\n if (!this.options.maxSize) return;\r\n\r\n // Remove key from current position\r\n const index = this.accessOrder.indexOf(key);\r\n if (index > -1) {\r\n this.accessOrder.splice(index, 1);\r\n }\r\n\r\n // Add to end (most recently used)\r\n this.accessOrder.push(key);\r\n }\r\n\r\n /**\r\n * Remove key from access order tracking\r\n */\r\n protected removeFromAccessOrder(key: string) {\r\n const index = this.accessOrder.indexOf(key);\r\n if (index > -1) {\r\n this.accessOrder.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Enforce max size by evicting least recently used items.\r\n *\r\n * Recomputes the live cache size on every iteration — a single snapshot at\r\n * the top of the loop would go stale and cause this routine to evict every\r\n * entry in `accessOrder` (including the just-inserted key).\r\n */\r\n protected async enforceMaxSize() {\r\n if (!this.options.maxSize) {\r\n return;\r\n }\r\n\r\n while (\r\n this.getCacheSize() > this.options.maxSize &&\r\n this.accessOrder.length > 0\r\n ) {\r\n const lruKey = this.accessOrder.shift();\r\n if (!lruKey) {\r\n break;\r\n }\r\n\r\n this.log(\"removing\", lruKey);\r\n unset(this.data, [lruKey]);\r\n delete this.temporaryData[lruKey];\r\n this.vectorIndex.delete(lruKey);\r\n this.log(\"removed\", lruKey);\r\n }\r\n }\r\n\r\n /**\r\n * Get current cache size (number of cached items)\r\n */\r\n protected getCacheSize(): number {\r\n // Count top-level keys in data object\r\n return Object.keys(this.data).length;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Brute-force O(N) cosine similarity over every entry that was written with\r\n * `set({ vector })`. Suitable for development and small in-memory knowledge\r\n * bases — not for production beyond ~10k entries. Use the `pg` driver\r\n * (with pgvector) or `redis` (with RediSearch) at scale.\r\n *\r\n * @warning Dev-only — O(N) per query.\r\n */\r\n public async similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n const tagFilter = await this.getKeysForTags(options.tags);\r\n\r\n const hits: CacheSimilarHit<T>[] = [];\r\n\r\n for (const [parsedKey, stored] of this.vectorIndex) {\r\n if (tagFilter && !tagFilter.has(parsedKey)) {\r\n continue;\r\n }\r\n\r\n const value = (await this.get(parsedKey)) as T | null;\r\n // get() returns null for expired entries — and remove() drops the vector\r\n // index, so the next pass won't see it. Skip in case of timing.\r\n if (value === null) {\r\n continue;\r\n }\r\n\r\n const score = cosineSimilarity(vector, stored);\r\n\r\n if (options.threshold !== undefined && score < options.threshold) {\r\n continue;\r\n }\r\n\r\n hits.push({ key: parsedKey, value, score });\r\n }\r\n\r\n hits.sort((a, b) => b.score - a.score);\r\n\r\n if (options.topK >= 0 && hits.length > options.topK) {\r\n hits.length = options.topK;\r\n }\r\n\r\n return hits;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async disconnect() {\r\n // Clear the cleanup interval to prevent memory leaks\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n this.cleanupInterval = undefined;\r\n }\r\n\r\n await super.disconnect();\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"memory-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/memory-cache-driver.ts"],"sourcesContent":["import type { GenericObject } from \"@mongez/reinforcements\";\r\nimport { get, set, unset } from \"@mongez/reinforcements\";\r\nimport type {\r\n CacheData,\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheTtl,\r\n MemoryCacheOptions,\r\n} from \"../types\";\r\nimport { cosineSimilarity } from \"../utils\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nexport class MemoryCacheDriver\r\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\r\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\r\n{\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"memory\";\r\n\r\n /**\r\n * Cached data\r\n */\r\n public data: GenericObject = {};\r\n\r\n /**\r\n * List of data that will be cleared from cache\r\n */\r\n protected temporaryData: Record<\r\n string,\r\n {\r\n key: string;\r\n expiresAt: number;\r\n }\r\n > = {};\r\n\r\n /**\r\n * Cleanup interval reference\r\n */\r\n protected cleanupInterval?: NodeJS.Timeout;\r\n\r\n /**\r\n * Access order tracking for LRU eviction (when maxSize is set)\r\n */\r\n protected accessOrder: string[] = [];\r\n\r\n /**\r\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\r\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\r\n * `remove`, `flush`, expiry, namespace clear, and LRU eviction.\r\n */\r\n protected vectorIndex: Map<string, number[]> = new Map();\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public constructor() {\r\n super();\r\n\r\n this.startCleanup();\r\n }\r\n\r\n /**\r\n * Start the cleanup process whenever a data that has a cache key is set\r\n */\r\n public startCleanup() {\r\n // Clear existing interval if any\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n }\r\n\r\n this.cleanupInterval = setInterval(async () => {\r\n const now = Date.now();\r\n\r\n for (const key in this.temporaryData) {\r\n if (this.temporaryData[key].expiresAt <= now) {\r\n await this.remove(this.temporaryData[key].key);\r\n delete this.temporaryData[key];\r\n\r\n this.log(\"expired\", key);\r\n // Emit expired event\r\n await this.emit(\"expired\", { key });\r\n }\r\n }\r\n }, 1000);\r\n\r\n // do not block the process from exiting\r\n this.cleanupInterval.unref();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async removeNamespace(namespace: string) {\r\n this.log(\"clearing\", namespace);\r\n\r\n namespace = this.parseKey(namespace);\r\n\r\n unset(this.data, [namespace]);\r\n\r\n // Drop vector entries that fall under this namespace.\r\n if (namespace === \"\") {\r\n this.vectorIndex.clear();\r\n } else {\r\n const prefix = namespace + \".\";\r\n for (const k of [...this.vectorIndex.keys()]) {\r\n if (k === namespace || k.startsWith(prefix)) {\r\n this.vectorIndex.delete(k);\r\n }\r\n }\r\n }\r\n\r\n this.log(\"cleared\", namespace);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n // Use get() for the existence check so expired entries are treated as\r\n // missing (and cleaned up as a side effect). A raw map lookup would let\r\n // stale entries block onConflict: \"create\" even after their TTL elapsed.\r\n const existingValue = onConflict === \"upsert\" ? null : await this.get(key);\r\n const exists = existingValue !== null;\r\n\r\n if (onConflict === \"create\" && exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: existingValue };\r\n return result;\r\n }\r\n\r\n if (onConflict === \"update\" && !exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: null };\r\n return result;\r\n }\r\n\r\n const data = this.prepareDataForStorage(value, ttl, staleAt);\r\n\r\n if (ttl) {\r\n this.setTemporaryData(key, parsedKey, ttl);\r\n }\r\n\r\n set(this.data, parsedKey, data);\r\n\r\n this.trackAccess(parsedKey);\r\n\r\n if (!exists && this.options.maxSize) {\r\n await this.enforceMaxSize();\r\n }\r\n\r\n if (tags && tags.length > 0) {\r\n await this.applyTags(parsedKey, tags);\r\n }\r\n\r\n if (vector) {\r\n this.vectorIndex.set(parsedKey, vector.slice());\r\n }\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n await this.emit(\"set\", { key: parsedKey, value, ttl });\r\n\r\n if (onConflict === \"create\" || onConflict === \"update\") {\r\n const result: CacheSetResult = { wasSet: true, existing: null };\r\n return result;\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"fetching\", parsedKey);\r\n\r\n const value: CacheData = get(this.data, parsedKey);\r\n\r\n if (!value) {\r\n this.log(\"notFound\", parsedKey);\r\n // Emit miss event\r\n await this.emit(\"miss\", { key: parsedKey });\r\n return null;\r\n }\r\n\r\n const result = await this.parseCachedData(parsedKey, value);\r\n\r\n if (result === null) {\r\n // Expired\r\n await this.emit(\"miss\", { key: parsedKey });\r\n } else {\r\n // Track access for LRU\r\n this.trackAccess(parsedKey);\r\n // Emit hit event\r\n await this.emit(\"hit\", { key: parsedKey, value: result });\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\r\n * Returns `null` for missing or expired entries so the SWR flow can branch\r\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const parsedKey = this.parseKey(key);\r\n const entry: CacheData | undefined = get(this.data, parsedKey);\r\n\r\n if (!entry) {\r\n return null;\r\n }\r\n\r\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\r\n return null;\r\n }\r\n\r\n return entry;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"removing\", parsedKey);\r\n\r\n unset(this.data, [parsedKey]);\r\n\r\n // Clean up from temporaryData as well\r\n delete this.temporaryData[parsedKey];\r\n\r\n // Remove from access order\r\n this.removeFromAccessOrder(parsedKey);\r\n\r\n // Drop the vector index entry if any\r\n this.vectorIndex.delete(parsedKey);\r\n\r\n this.log(\"removed\", parsedKey);\r\n\r\n // Emit removed event\r\n await this.emit(\"removed\", { key: parsedKey });\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n this.log(\"flushing\");\r\n if (this.options.globalPrefix) {\r\n this.removeNamespace(\"\");\r\n } else {\r\n this.data = {};\r\n this.accessOrder = [];\r\n this.vectorIndex.clear();\r\n }\r\n\r\n this.log(\"flushed\");\r\n\r\n // Emit flushed event\r\n await this.emit(\"flushed\");\r\n }\r\n\r\n /**\r\n * Set the temporary data\r\n */\r\n protected setTemporaryData(key: CacheKey, parsedKey: string, ttl: number) {\r\n this.temporaryData[parsedKey] = {\r\n key: JSON.stringify(key),\r\n expiresAt: Date.now() + ttl * 1000,\r\n };\r\n }\r\n\r\n /**\r\n * Track access for LRU eviction\r\n */\r\n protected trackAccess(key: string) {\r\n if (!this.options.maxSize) return;\r\n\r\n // Remove key from current position\r\n const index = this.accessOrder.indexOf(key);\r\n if (index > -1) {\r\n this.accessOrder.splice(index, 1);\r\n }\r\n\r\n // Add to end (most recently used)\r\n this.accessOrder.push(key);\r\n }\r\n\r\n /**\r\n * Remove key from access order tracking\r\n */\r\n protected removeFromAccessOrder(key: string) {\r\n const index = this.accessOrder.indexOf(key);\r\n if (index > -1) {\r\n this.accessOrder.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Enforce max size by evicting least recently used items.\r\n *\r\n * Recomputes the live cache size on every iteration — a single snapshot at\r\n * the top of the loop would go stale and cause this routine to evict every\r\n * entry in `accessOrder` (including the just-inserted key).\r\n */\r\n protected async enforceMaxSize() {\r\n if (!this.options.maxSize) {\r\n return;\r\n }\r\n\r\n while (\r\n this.getCacheSize() > this.options.maxSize &&\r\n this.accessOrder.length > 0\r\n ) {\r\n const lruKey = this.accessOrder.shift();\r\n if (!lruKey) {\r\n break;\r\n }\r\n\r\n this.log(\"removing\", lruKey);\r\n unset(this.data, [lruKey]);\r\n delete this.temporaryData[lruKey];\r\n this.vectorIndex.delete(lruKey);\r\n this.log(\"removed\", lruKey);\r\n }\r\n }\r\n\r\n /**\r\n * Get current cache size (number of cached items)\r\n */\r\n protected getCacheSize(): number {\r\n // Count top-level keys in data object\r\n return Object.keys(this.data).length;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Brute-force O(N) cosine similarity over every entry that was written with\r\n * `set({ vector })`. Suitable for development and small in-memory knowledge\r\n * bases — not for production beyond ~10k entries. Use the `pg` driver\r\n * (with pgvector) or `redis` (with RediSearch) at scale.\r\n *\r\n * @warning Dev-only — O(N) per query.\r\n */\r\n public async similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n const tagFilter = await this.getKeysForTags(options.tags);\r\n\r\n const hits: CacheSimilarHit<T>[] = [];\r\n\r\n for (const [parsedKey, stored] of this.vectorIndex) {\r\n if (tagFilter && !tagFilter.has(parsedKey)) {\r\n continue;\r\n }\r\n\r\n const value = (await this.get(parsedKey)) as T | null;\r\n // get() returns null for expired entries — and remove() drops the vector\r\n // index, so the next pass won't see it. Skip in case of timing.\r\n if (value === null) {\r\n continue;\r\n }\r\n\r\n const score = cosineSimilarity(vector, stored);\r\n\r\n if (options.threshold !== undefined && score < options.threshold) {\r\n continue;\r\n }\r\n\r\n hits.push({ key: parsedKey, value, score });\r\n }\r\n\r\n hits.sort((a, b) => b.score - a.score);\r\n\r\n if (options.topK >= 0 && hits.length > options.topK) {\r\n hits.length = options.topK;\r\n }\r\n\r\n return hits;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async disconnect() {\r\n // Clear the cleanup interval to prevent memory leaks\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n this.cleanupInterval = undefined;\r\n }\r\n\r\n await super.disconnect();\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mock-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/mock-cache-driver.ts"],"mappings":";;;;;;AAqDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,gBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,gBAAA;EAKjC;;;EAAA,IAAA;EAWkB;;;EANlB,OAAA,EAAS,gBAAA;EAmBH;;;;EAAA,SAbG,OAAA,EAAS,GAAA,SAAY,SAAA;EA8BR;;;;;EAAA,SAvBb,OAAA,EAAS,SAAA;EA+CR;;;;EAzCJ,OAAA,CAAA,GAAW,OAAA;EAuFP;;;EA/EJ,UAAA,CAAA,GAAc,OAAA;EA+EuB;;;;EAtErC,eAAA,CAAgB,SAAA,WAAoB,OAAA;EAyGA;;;;EApFpC,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAkHU;;;;EArEA,GAAA,SAAA,CAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAyFd;;;;;EAAA,UAtDpB,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAwF1C;;;EAtEM,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAkFlC;;AAAoB;;EAlET,KAAA,CAAA,GAAS,OAAA;;;;;;;;;;;EAoBf,SAAA,CAAU,SAAA,UAAmB,GAAA,GAAM,QAAA;;;;;;;;EAmBnC,SAAA,SAAA,CAAmB,GAAA,EAAK,QAAA,GAAW,CAAA;;;;;EAenC,KAAA,CAAA;;;;;YASG,UAAA,CACR,SAAA,UACA,GAAA,sBACA,IAAA;AAAA"}
1
+ {"version":3,"file":"mock-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/mock-cache-driver.ts"],"mappings":";;;;;;AAqDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,gBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,gBAAA;EAKjC;;;EAAA,IAAA;EAWkB;;;EANlB,OAAA,EAAS,gBAAA;EAmBH;;;;EAAA,SAbG,OAAA,EAAS,GAAA,SAAY,SAAA;EA8BR;;;;;EAAA,SAvBb,OAAA,EAAS,SAAA;EA+CR;;;;EAzCJ,OAAA,IAAW,OAAA;EAuFP;;;EA/EJ,UAAA,IAAc,OAAA;EA+EuB;;;;EAtErC,eAAA,CAAgB,SAAA,WAAoB,OAAA;EAyGA;;;;EApFpC,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAkHU;;;;EArEA,GAAA,UAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAyFd;;;;;EAAA,UAtDpB,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAwF1C;;;EAtEM,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAkFlC;;AAAoB;;EAlET,KAAA,IAAS,OAAA;;;;;;;;;;;EAoBf,SAAA,CAAU,SAAA,UAAmB,GAAA,GAAM,QAAA;;;;;;;;EAmBnC,SAAA,UAAmB,GAAA,EAAK,QAAA,GAAW,CAAA;;;;;EAenC,KAAA;;;;;YASG,UAAA,CACR,SAAA,UACA,GAAA,sBACA,IAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"null-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/null-cache-driver.ts"],"mappings":";;;;;cAWa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,sBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,sBAAA;;AAF1C;;EAOS,OAAA,EAAS,aAAA;EANQ;;;EAWjB,IAAA;EALS;;;EAUT,IAAA,EAAM,aAAA;EA4BS;;;EAAA,IAvBX,MAAA,CAAA;EA4CkB;;;cArCV,OAAA,GAAS,aAAA;EA4DH;;;EApDlB,UAAA,CAAW,OAAA,EAAS,aAAA;EAgFP;;;EAxEb,QAAA,CAAS,IAAA,EAAM,QAAA;EA3CA;;;EAkDT,eAAA,CAAgB,SAAA,WAAiB,OAAA;EAlDnC;;;EA6DE,GAAA,CACX,GAAA,EAAK,QAAA,EACL,MAAA,OACA,aAAA,GAAgB,QAAA,GAAW,eAAA,GAC1B,OAAA;EA5Da;;;EAuEH,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAxDnB;;;EAmEE,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EApD1B;;;EA6DM,KAAA,CAAA,GAAK,OAAA;EArDI;;;;;;EAiET,OAAA,CAAA,GAAW,OAAA;EA9CtB;;;EAqDW,OAAA,CAAA,GAAO,OAAA;AAAA"}
1
+ {"version":3,"file":"null-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/null-cache-driver.ts"],"mappings":";;;;;cAWa,eAAA,SACH,eAAA,CAAgB,eAAA,EAAiB,sBAAA,aAC9B,WAAA,CAAY,eAAA,EAAiB,sBAAA;;AAF1C;;EAOS,OAAA,EAAS,aAAA;EANQ;;;EAWjB,IAAA;EALS;;;EAUT,IAAA,EAAM,aAAA;EA4BS;;;EAAA,IAvBX,MAAA;EA4CkB;;;cArCV,OAAA,GAAS,aAAA;EA4DH;;;EApDlB,UAAA,CAAW,OAAA,EAAS,aAAA;EAgFP;;;EAxEb,QAAA,CAAS,IAAA,EAAM,QAAA;EA3CA;;;EAkDT,eAAA,CAAgB,SAAA,WAAiB,OAAA;EAlDnC;;;EA6DE,GAAA,CACX,GAAA,EAAK,QAAA,EACL,MAAA,OACA,aAAA,GAAgB,QAAA,GAAW,eAAA,GAC1B,OAAA;EA5Da;;;EAuEH,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAxDnB;;;EAmEE,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EApD1B;;;EA6DM,KAAA,IAAK,OAAA;EArDI;;;;;;EAiET,OAAA,IAAW,OAAA;EA9CtB;;;EAqDW,OAAA,IAAO,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"pg-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/pg-cache-driver.ts"],"mappings":";;;;;;AAkDA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,aAAA,SACH,eAAA,CAAgB,YAAA,EAAc,cAAA,aAC3B,WAAA,CAAY,YAAA,EAAc,cAAA;EA2eV;;;EAtepB,IAAA;EALI;;;;EAAA,UAWD,WAAA;EAZ4B;;;;;EAmB/B,UAAA,CAAW,OAAA,EAAS,cAAA;EAApB;;;;;;EAAA,UAsCS,iBAAA,CAAA,GAAqB,OAAA;EAqCvB;;;;;EAAA,UARJ,YAAA,CAAa,MAAA;EAuChB;;;;EAAA,cA/BO,KAAA,CAAA;EA2FD;;;;EAAA,cAnFC,QAAA,CAAA,GAAY,YAAA;EAsFE;;;;EAAA,UA9ElB,cAAA,CAAe,GAAA,YAAe,IAAA;EA6MvB;;;;;;;EA9LV,MAAA,CAAA;EAkSkB;;;EA3PZ,OAAA,CAAA,GAAO,OAAA;EA4QS;;;;;EA/PhB,UAAA,CAAA,GAAU,OAAA;EAyTrB;;;EAjTW,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EA+SQ;;;EAjLE,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;;;YAwDd,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;;;;EA4CpC,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;;;;EAiBpB,eAAA,CAAgB,SAAA,WAAiB,OAAA;;;;;;;;EA4BjC,KAAA,CAAA,GAAK,OAAA;;;;;;;;;;;;;;;;;EA6BL,OAAA,SAAA,CACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;AAAA"}
1
+ {"version":3,"file":"pg-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/pg-cache-driver.ts"],"mappings":";;;;;;AAkDA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,aAAA,SACH,eAAA,CAAgB,YAAA,EAAc,cAAA,aAC3B,WAAA,CAAY,YAAA,EAAc,cAAA;EA2eV;;;EAtepB,IAAA;EALI;;;;EAAA,UAWD,WAAA;EAZ4B;;;;;EAmB/B,UAAA,CAAW,OAAA,EAAS,cAAA;EAApB;;;;;;EAAA,UAsCS,iBAAA,IAAqB,OAAA;EAqCvB;;;;;EAAA,UARJ,YAAA,CAAa,MAAA;EAuChB;;;;EAAA,cA/BO,KAAA;EA2FD;;;;EAAA,cAnFC,QAAA,IAAY,YAAA;EAsFE;;;;EAAA,UA9ElB,cAAA,CAAe,GAAA,YAAe,IAAA;EA6MvB;;;;;;;EA9LV,MAAA;EAkSkB;;;EA3PZ,OAAA,IAAO,OAAA;EA4QS;;;;;EA/PhB,UAAA,IAAU,OAAA;EAyTrB;;;EAjTW,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EA+SQ;;;EAjLE,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;;;YAwDd,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;;;;EA4CpC,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;;;;;;;EAiBpB,eAAA,CAAgB,SAAA,WAAiB,OAAA;;;;;;;;EA4BjC,KAAA,IAAK,OAAA;;;;;;;;;;;;;;;;;EA6BL,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"pg-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/pg-cache-driver.ts"],"sourcesContent":["import type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n PgCacheOptions,\n PgClientLike,\n} from \"../types\";\nimport { CacheConfigurationError, CacheUnsupportedError } from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\n/**\n * Allowed characters in a Postgres identifier (table name). We accept the\n * conservative ASCII subset and reject anything else — interpolating an\n * arbitrary string into DDL would be a SQL-injection footgun.\n */\nconst SAFE_IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Postgres cache driver with optional pgvector similarity support.\n *\n * Connection lifecycle is the caller's responsibility — pass an already-built\n * `pg.Pool` or `pg.Client` via the `client` option. The driver never closes it\n * on `cache.disconnect()`, so the same pool can serve queries elsewhere in\n * the app.\n *\n * Schema is not auto-migrated. Call `driver.schema()` to get the DDL string\n * and run it through whichever migration tool you use.\n *\n * @example\n * import { Pool } from \"pg\";\n * import { cache, PgCacheDriver } from \"@warlock.js/cache\";\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n *\n * cache.setCacheConfigurations({\n * default: \"pg\",\n * drivers: { pg: PgCacheDriver },\n * options: { pg: { client: pool, table: \"warlock_cache\", ttl: \"1h\" } },\n * });\n *\n * await cache.init();\n *\n * // Run once, via your own migration tooling:\n * // await pool.query(driver.schema());\n */\nexport class PgCacheDriver\n extends BaseCacheDriver<PgClientLike, PgCacheOptions>\n implements CacheDriver<PgClientLike, PgCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"pg\";\n\n /**\n * Cached result of the pgvector extension check. Populated lazily on first\n * vector op so the driver doesn't probe the database at construction time.\n */\n protected vectorReady?: boolean;\n\n /**\n * {@inheritdoc}\n *\n * Validates `client` presence and the table name before storing options.\n */\n public setOptions(options: PgCacheOptions) {\n if (!options || !options.client || typeof options.client.query !== \"function\") {\n throw new CacheConfigurationError(\n \"Pg cache driver requires a 'client' option implementing { query(text, values) } — pass a pg.Pool or pg.Client.\",\n );\n }\n\n const table = options.table ?? \"warlock_cache\";\n if (!SAFE_IDENT.test(table)) {\n throw new CacheConfigurationError(\n `Pg cache driver: invalid table name '${table}'. Allowed: [A-Za-z_][A-Za-z0-9_]*.`,\n );\n }\n\n if (options.vector) {\n const dim = options.vector.dimensions;\n if (!Number.isInteger(dim) || dim <= 0) {\n throw new CacheConfigurationError(\n `Pg cache driver: vector.dimensions must be a positive integer; got ${dim}.`,\n );\n }\n const idx = options.vector.index ?? \"hnsw\";\n if (idx !== \"hnsw\" && idx !== \"ivfflat\") {\n throw new CacheConfigurationError(\n `Pg cache driver: vector.index must be 'hnsw' or 'ivfflat'; got '${idx}'.`,\n );\n }\n }\n\n return super.setOptions({ ...options, table });\n }\n\n /**\n * Lazy pgvector availability check. Runs once on the first vector op and\n * caches the result — subsequent ops are zero-overhead. Throws\n * {@link CacheConfigurationError} if the extension isn't installed; throws\n * {@link CacheUnsupportedError} if `vector` config wasn't provided at all.\n */\n protected async ensureVectorReady(): Promise<void> {\n if (!this.options.vector) {\n throw new CacheUnsupportedError(\n \"'pg' driver: similarity retrieval requires the 'vector' config block. Set options.vector.dimensions and reconnect.\",\n );\n }\n\n if (this.vectorReady === true) {\n return;\n }\n\n const { rows } = await this.pgClient.query(\n `SELECT 1 FROM pg_extension WHERE extname = 'vector'`,\n );\n\n if (rows.length === 0) {\n throw new CacheConfigurationError(\n \"'pg' driver: pgvector extension not installed. Run 'CREATE EXTENSION vector;' or remove the 'vector' config option.\",\n );\n }\n\n this.vectorReady = true;\n }\n\n /**\n * Format a numeric vector for pgvector ingestion. The `vector` type accepts\n * a string literal `'[1,2,3]'` cast via `::vector` — this avoids depending\n * on the binary protocol and works against any pg client.\n */\n protected formatVector(vector: number[]): string {\n return `[${vector.join(\",\")}]`;\n }\n\n /**\n * Resolved table name. Always defined post-`setOptions` — the validator\n * fills in the default.\n */\n protected get table(): string {\n return this.options.table ?? \"warlock_cache\";\n }\n\n /**\n * The user-supplied `pg.Pool` / `pg.Client`. Use this rather than `this.client`\n * (which has a generic fallback to `this`) for actual queries.\n */\n protected get pgClient(): PgClientLike {\n return this.options.client;\n }\n\n /**\n * Compute an absolute `expires_at` Date for the given relative TTL in seconds,\n * or `null` when the entry should not expire (`Infinity` / 0 / undefined).\n */\n protected ttlToExpiresAt(ttl?: number): Date | null {\n if (!ttl || ttl === Infinity) {\n return null;\n }\n\n return new Date(Date.now() + ttl * 1000);\n }\n\n /**\n * Return the SQL needed to provision the cache table + index. Run once via\n * the caller's migration tooling — the driver never auto-migrates.\n *\n * @example\n * await pool.query(driver.schema());\n */\n public schema(): string {\n const t = this.table;\n const vec = this.options.vector;\n\n const columns = [\n ` key TEXT PRIMARY KEY,`,\n ` value JSONB NOT NULL,`,\n ` expires_at TIMESTAMPTZ,`,\n ` stale_at TIMESTAMPTZ,`,\n ` tags TEXT[] NOT NULL DEFAULT '{}'::TEXT[]`,\n ];\n\n if (vec) {\n // Trailing comma on the previous line to make this a valid column list.\n columns[columns.length - 1] = columns[columns.length - 1] + \",\";\n columns.push(` embedding VECTOR(${vec.dimensions})`);\n }\n\n const lines = [\n `CREATE TABLE IF NOT EXISTS ${t} (`,\n ...columns,\n `);`,\n `CREATE INDEX IF NOT EXISTS idx_${t}_expires_at ON ${t} (expires_at);`,\n `CREATE INDEX IF NOT EXISTS idx_${t}_tags ON ${t} USING GIN (tags);`,\n ];\n\n if (vec) {\n const idx = vec.index ?? \"hnsw\";\n lines.push(\n `CREATE INDEX IF NOT EXISTS idx_${t}_embedding ON ${t} USING ${idx} (embedding vector_cosine_ops);`,\n );\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n // No-op — caller owns the connection. We emit `connected` for symmetry\n // with the other drivers' lifecycle events.\n this.log(\"connecting\");\n this.log(\"connected\");\n await this.emit(\"connected\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Does NOT close the user-supplied client — lifecycle stays with the caller.\n */\n public async disconnect() {\n this.log(\"disconnected\");\n await this.emit(\"disconnected\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n if (vector) {\n if (!this.options.vector) {\n throw new CacheUnsupportedError(\n \"'pg' driver: cannot index a vector without options.vector configuration — set { dimensions } and recreate the table via driver.schema().\",\n );\n }\n\n const expected = this.options.vector.dimensions;\n if (vector.length !== expected) {\n throw new CacheConfigurationError(\n `Pg cache driver: vector dimension mismatch — expected ${expected}, got ${vector.length}.`,\n );\n }\n\n await this.ensureVectorReady();\n }\n\n this.log(\"caching\", parsedKey);\n\n const expiresAt = this.ttlToExpiresAt(ttl);\n const staleAtDate = staleAt !== undefined ? new Date(staleAt) : null;\n const tagsArr = tags ?? [];\n const serialized = JSON.stringify(value);\n const vecLiteral = vector ? this.formatVector(vector) : null;\n\n const t = this.table;\n\n // Build column / placeholder / param triplets dynamically so the same code\n // path serves both KV-only and vector-aware writes. Param order is fixed:\n // $1 = key, $2 = value (jsonb), $3 = expires_at, $4 = stale_at,\n // $5 = tags, $6 = embedding::vector (only present when vecLiteral !== null).\n const cols = [\"key\", \"value\", \"expires_at\", \"stale_at\", \"tags\"];\n const placeholders = [\"$1\", \"$2::jsonb\", \"$3\", \"$4\", \"$5\"];\n const params: unknown[] = [parsedKey, serialized, expiresAt, staleAtDate, tagsArr];\n if (vecLiteral !== null) {\n cols.push(\"embedding\");\n placeholders.push(`$${params.length + 1}::vector`);\n params.push(vecLiteral);\n }\n const colList = cols.join(\", \");\n const valList = placeholders.join(\", \");\n const setClause = cols\n .slice(1)\n .map((c) => `${c} = EXCLUDED.${c}`)\n .join(\", \");\n const updateSetClause = cols\n .slice(1)\n .map((c, i) => `${c} = ${placeholders[i + 1]}`)\n .join(\", \");\n\n if (onConflict === \"create\") {\n // Race-safe insert: if another worker already holds the key (and the row\n // hasn't expired), DO NOTHING; we then SELECT to surface the existing value.\n const { rows } = await this.pgClient.query(\n `INSERT INTO ${t}(${colList})\n VALUES (${valList})\n ON CONFLICT (key) DO UPDATE\n SET ${setClause}\n WHERE ${t}.expires_at IS NOT NULL AND ${t}.expires_at < now()\n RETURNING value`,\n params,\n );\n\n if (rows.length === 0) {\n // Conflict + existing row not expired → fetch existing for the result.\n const existing = await this.get(key);\n return { wasSet: false, existing } satisfies CacheSetResult;\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n return { wasSet: true, existing: null } satisfies CacheSetResult;\n }\n\n if (onConflict === \"update\") {\n // Update only when the key exists AND hasn't expired.\n const { rows } = await this.pgClient.query(\n `UPDATE ${t}\n SET ${updateSetClause}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())\n RETURNING value`,\n params,\n );\n\n if (rows.length === 0) {\n return { wasSet: false, existing: null } satisfies CacheSetResult;\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n return { wasSet: true, existing: null } satisfies CacheSetResult;\n }\n\n // upsert (default)\n await this.pgClient.query(\n `INSERT INTO ${t}(${colList})\n VALUES (${valList})\n ON CONFLICT (key) DO UPDATE\n SET ${setClause}`,\n params,\n );\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n this.log(\"fetching\", parsedKey);\n\n const t = this.table;\n const { rows } = await this.pgClient.query(\n `SELECT value FROM ${t}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())`,\n [parsedKey],\n );\n\n if (rows.length === 0) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.log(\"fetched\", parsedKey);\n\n // pg's JSONB type round-trips through node-postgres as a parsed JS value\n // already — but some pool implementations may hand back a string. Be defensive.\n let value = rows[0].value;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n // Leave it as-is; cloning below will fail loud if it's truly broken.\n }\n }\n\n if (value === null || value === undefined) {\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n try {\n const cloned = structuredClone(value);\n await this.emit(\"hit\", { key: parsedKey, value: cloned });\n return cloned;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\n throw error;\n }\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\n * Returns `null` for missing or expired rows — `swr()` consumes this to\n * branch on freshness without going through `get()`'s clone-and-emit path.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const t = this.table;\n\n const { rows } = await this.pgClient.query(\n `SELECT value, expires_at, stale_at FROM ${t}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())`,\n [parsedKey],\n );\n\n if (rows.length === 0) {\n return null;\n }\n\n let data = rows[0].value;\n if (typeof data === \"string\") {\n try {\n data = JSON.parse(data);\n } catch {\n // Leave as-is — getEntry is metadata-shaped, the caller will hit the\n // same parse path on the public `get()` if they care.\n }\n }\n\n const entry: CacheData = { data };\n\n const expiresAtRaw = rows[0].expires_at as Date | string | null;\n if (expiresAtRaw) {\n const expiresAt = expiresAtRaw instanceof Date ? expiresAtRaw : new Date(expiresAtRaw);\n entry.expiresAt = expiresAt.getTime();\n }\n\n const staleAtRaw = rows[0].stale_at as Date | string | null;\n if (staleAtRaw) {\n const staleAt = staleAtRaw instanceof Date ? staleAtRaw : new Date(staleAtRaw);\n entry.staleAt = staleAt.getTime();\n }\n\n return entry;\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n this.log(\"removing\", parsedKey);\n\n const t = this.table;\n await this.pgClient.query(`DELETE FROM ${t} WHERE key = $1`, [parsedKey]);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Deletes all rows whose key equals the namespace exactly or starts with\n * `<namespace>.` — same boundary semantics as the other drivers.\n */\n public async removeNamespace(namespace: string) {\n const parsed = this.parseKey(namespace);\n this.log(\"clearing\", parsed || \"(all)\");\n\n const t = this.table;\n\n if (parsed === \"\") {\n await this.pgClient.query(`DELETE FROM ${t}`);\n } else {\n // Escape `_` and `%` in the prefix so they aren't treated as LIKE wildcards.\n const escaped = parsed.replace(/\\\\/g, \"\\\\\\\\\").replace(/_/g, \"\\\\_\").replace(/%/g, \"\\\\%\");\n await this.pgClient.query(`DELETE FROM ${t} WHERE key = $1 OR key LIKE $2 ESCAPE '\\\\'`, [\n parsed,\n `${escaped}.%`,\n ]);\n }\n\n this.log(\"cleared\", parsed || \"(all)\");\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * Honors `globalPrefix` — when configured, scopes the flush to entries\n * under the prefix rather than truncating the entire table (which could\n * wipe sibling tenants sharing the same Postgres database).\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n await this.pgClient.query(`DELETE FROM ${this.table}`);\n }\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n *\n * pgvector-backed similarity. Uses the `<=>` cosine-distance operator\n * (lower distance = higher similarity) and converts to cosine similarity\n * as `1 - distance` so the returned `score` matches the rest of the\n * package (`[0, 1]`, higher is more similar).\n *\n * Honors `topK`, `threshold`, and an optional `tags` filter (native\n * `tags && $tags` overlap query — much faster than the meta-key path).\n *\n * Throws {@link CacheUnsupportedError} when `options.vector` was not\n * configured at driver setup; throws {@link CacheConfigurationError} when\n * the pgvector extension is missing or the query vector's dimension count\n * doesn't match the configured one.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n if (!this.options.vector) {\n throw new CacheUnsupportedError(\n \"'pg' driver: similarity retrieval requires the 'vector' config block. Set options.vector.dimensions and reconnect.\",\n );\n }\n\n const expected = this.options.vector.dimensions;\n if (vector.length !== expected) {\n throw new CacheConfigurationError(\n `Pg cache driver: vector dimension mismatch — expected ${expected}, got ${vector.length}.`,\n );\n }\n\n if (!Number.isInteger(options.topK) || options.topK <= 0) {\n throw new CacheConfigurationError(\n `Pg cache driver: similar.topK must be a positive integer; got ${options.topK}.`,\n );\n }\n\n await this.ensureVectorReady();\n\n const t = this.table;\n const vecLiteral = this.formatVector(vector);\n const params: unknown[] = [vecLiteral];\n let tagFilter = \"\";\n\n if (options.tags && options.tags.length > 0) {\n params.push(options.tags);\n tagFilter = `AND tags && $${params.length}`;\n }\n\n params.push(options.topK);\n const topKParam = `$${params.length}`;\n\n const { rows } = await this.pgClient.query(\n `SELECT key, value, 1 - (embedding <=> $1::vector) AS score\n FROM ${t}\n WHERE embedding IS NOT NULL\n AND (expires_at IS NULL OR expires_at > now())\n ${tagFilter}\n ORDER BY embedding <=> $1::vector\n LIMIT ${topKParam}`,\n params,\n );\n\n const hits: CacheSimilarHit<T>[] = [];\n for (const row of rows) {\n const score = Number(row.score);\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n let value = row.value;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n // Surface as-is; if non-JSON crept in, the consumer will notice.\n }\n }\n\n // Match get() cloning semantics so consumers can't mutate cached state.\n if (value !== null && value !== undefined) {\n const ty = typeof value;\n if (ty !== \"string\" && ty !== \"number\" && ty !== \"boolean\") {\n value = structuredClone(value);\n }\n }\n\n hits.push({ key: row.key, value, score });\n }\n\n return hits;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pg-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/pg-cache-driver.ts"],"sourcesContent":["import type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n PgCacheOptions,\n PgClientLike,\n} from \"../types\";\nimport { CacheConfigurationError, CacheUnsupportedError } from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\n/**\n * Allowed characters in a Postgres identifier (table name). We accept the\n * conservative ASCII subset and reject anything else — interpolating an\n * arbitrary string into DDL would be a SQL-injection footgun.\n */\nconst SAFE_IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Postgres cache driver with optional pgvector similarity support.\n *\n * Connection lifecycle is the caller's responsibility — pass an already-built\n * `pg.Pool` or `pg.Client` via the `client` option. The driver never closes it\n * on `cache.disconnect()`, so the same pool can serve queries elsewhere in\n * the app.\n *\n * Schema is not auto-migrated. Call `driver.schema()` to get the DDL string\n * and run it through whichever migration tool you use.\n *\n * @example\n * import { Pool } from \"pg\";\n * import { cache, PgCacheDriver } from \"@warlock.js/cache\";\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n *\n * cache.setCacheConfigurations({\n * default: \"pg\",\n * drivers: { pg: PgCacheDriver },\n * options: { pg: { client: pool, table: \"warlock_cache\", ttl: \"1h\" } },\n * });\n *\n * await cache.init();\n *\n * // Run once, via your own migration tooling:\n * // await pool.query(driver.schema());\n */\nexport class PgCacheDriver\n extends BaseCacheDriver<PgClientLike, PgCacheOptions>\n implements CacheDriver<PgClientLike, PgCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"pg\";\n\n /**\n * Cached result of the pgvector extension check. Populated lazily on first\n * vector op so the driver doesn't probe the database at construction time.\n */\n protected vectorReady?: boolean;\n\n /**\n * {@inheritdoc}\n *\n * Validates `client` presence and the table name before storing options.\n */\n public setOptions(options: PgCacheOptions) {\n if (!options || !options.client || typeof options.client.query !== \"function\") {\n throw new CacheConfigurationError(\n \"Pg cache driver requires a 'client' option implementing { query(text, values) } — pass a pg.Pool or pg.Client.\",\n );\n }\n\n const table = options.table ?? \"warlock_cache\";\n if (!SAFE_IDENT.test(table)) {\n throw new CacheConfigurationError(\n `Pg cache driver: invalid table name '${table}'. Allowed: [A-Za-z_][A-Za-z0-9_]*.`,\n );\n }\n\n if (options.vector) {\n const dim = options.vector.dimensions;\n if (!Number.isInteger(dim) || dim <= 0) {\n throw new CacheConfigurationError(\n `Pg cache driver: vector.dimensions must be a positive integer; got ${dim}.`,\n );\n }\n const idx = options.vector.index ?? \"hnsw\";\n if (idx !== \"hnsw\" && idx !== \"ivfflat\") {\n throw new CacheConfigurationError(\n `Pg cache driver: vector.index must be 'hnsw' or 'ivfflat'; got '${idx}'.`,\n );\n }\n }\n\n return super.setOptions({ ...options, table });\n }\n\n /**\n * Lazy pgvector availability check. Runs once on the first vector op and\n * caches the result — subsequent ops are zero-overhead. Throws\n * {@link CacheConfigurationError} if the extension isn't installed; throws\n * {@link CacheUnsupportedError} if `vector` config wasn't provided at all.\n */\n protected async ensureVectorReady(): Promise<void> {\n if (!this.options.vector) {\n throw new CacheUnsupportedError(\n \"'pg' driver: similarity retrieval requires the 'vector' config block. Set options.vector.dimensions and reconnect.\",\n );\n }\n\n if (this.vectorReady === true) {\n return;\n }\n\n const { rows } = await this.pgClient.query(\n `SELECT 1 FROM pg_extension WHERE extname = 'vector'`,\n );\n\n if (rows.length === 0) {\n throw new CacheConfigurationError(\n \"'pg' driver: pgvector extension not installed. Run 'CREATE EXTENSION vector;' or remove the 'vector' config option.\",\n );\n }\n\n this.vectorReady = true;\n }\n\n /**\n * Format a numeric vector for pgvector ingestion. The `vector` type accepts\n * a string literal `'[1,2,3]'` cast via `::vector` — this avoids depending\n * on the binary protocol and works against any pg client.\n */\n protected formatVector(vector: number[]): string {\n return `[${vector.join(\",\")}]`;\n }\n\n /**\n * Resolved table name. Always defined post-`setOptions` — the validator\n * fills in the default.\n */\n protected get table(): string {\n return this.options.table ?? \"warlock_cache\";\n }\n\n /**\n * The user-supplied `pg.Pool` / `pg.Client`. Use this rather than `this.client`\n * (which has a generic fallback to `this`) for actual queries.\n */\n protected get pgClient(): PgClientLike {\n return this.options.client;\n }\n\n /**\n * Compute an absolute `expires_at` Date for the given relative TTL in seconds,\n * or `null` when the entry should not expire (`Infinity` / 0 / undefined).\n */\n protected ttlToExpiresAt(ttl?: number): Date | null {\n if (!ttl || ttl === Infinity) {\n return null;\n }\n\n return new Date(Date.now() + ttl * 1000);\n }\n\n /**\n * Return the SQL needed to provision the cache table + index. Run once via\n * the caller's migration tooling — the driver never auto-migrates.\n *\n * @example\n * await pool.query(driver.schema());\n */\n public schema(): string {\n const t = this.table;\n const vec = this.options.vector;\n\n const columns = [\n ` key TEXT PRIMARY KEY,`,\n ` value JSONB NOT NULL,`,\n ` expires_at TIMESTAMPTZ,`,\n ` stale_at TIMESTAMPTZ,`,\n ` tags TEXT[] NOT NULL DEFAULT '{}'::TEXT[]`,\n ];\n\n if (vec) {\n // Trailing comma on the previous line to make this a valid column list.\n columns[columns.length - 1] = columns[columns.length - 1] + \",\";\n columns.push(` embedding VECTOR(${vec.dimensions})`);\n }\n\n const lines = [\n `CREATE TABLE IF NOT EXISTS ${t} (`,\n ...columns,\n `);`,\n `CREATE INDEX IF NOT EXISTS idx_${t}_expires_at ON ${t} (expires_at);`,\n `CREATE INDEX IF NOT EXISTS idx_${t}_tags ON ${t} USING GIN (tags);`,\n ];\n\n if (vec) {\n const idx = vec.index ?? \"hnsw\";\n lines.push(\n `CREATE INDEX IF NOT EXISTS idx_${t}_embedding ON ${t} USING ${idx} (embedding vector_cosine_ops);`,\n );\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n // No-op — caller owns the connection. We emit `connected` for symmetry\n // with the other drivers' lifecycle events.\n this.log(\"connecting\");\n this.log(\"connected\");\n await this.emit(\"connected\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Does NOT close the user-supplied client — lifecycle stays with the caller.\n */\n public async disconnect() {\n this.log(\"disconnected\");\n await this.emit(\"disconnected\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n if (vector) {\n if (!this.options.vector) {\n throw new CacheUnsupportedError(\n \"'pg' driver: cannot index a vector without options.vector configuration — set { dimensions } and recreate the table via driver.schema().\",\n );\n }\n\n const expected = this.options.vector.dimensions;\n if (vector.length !== expected) {\n throw new CacheConfigurationError(\n `Pg cache driver: vector dimension mismatch — expected ${expected}, got ${vector.length}.`,\n );\n }\n\n await this.ensureVectorReady();\n }\n\n this.log(\"caching\", parsedKey);\n\n const expiresAt = this.ttlToExpiresAt(ttl);\n const staleAtDate = staleAt !== undefined ? new Date(staleAt) : null;\n const tagsArr = tags ?? [];\n const serialized = JSON.stringify(value);\n const vecLiteral = vector ? this.formatVector(vector) : null;\n\n const t = this.table;\n\n // Build column / placeholder / param triplets dynamically so the same code\n // path serves both KV-only and vector-aware writes. Param order is fixed:\n // $1 = key, $2 = value (jsonb), $3 = expires_at, $4 = stale_at,\n // $5 = tags, $6 = embedding::vector (only present when vecLiteral !== null).\n const cols = [\"key\", \"value\", \"expires_at\", \"stale_at\", \"tags\"];\n const placeholders = [\"$1\", \"$2::jsonb\", \"$3\", \"$4\", \"$5\"];\n const params: unknown[] = [parsedKey, serialized, expiresAt, staleAtDate, tagsArr];\n if (vecLiteral !== null) {\n cols.push(\"embedding\");\n placeholders.push(`$${params.length + 1}::vector`);\n params.push(vecLiteral);\n }\n const colList = cols.join(\", \");\n const valList = placeholders.join(\", \");\n const setClause = cols\n .slice(1)\n .map((c) => `${c} = EXCLUDED.${c}`)\n .join(\", \");\n const updateSetClause = cols\n .slice(1)\n .map((c, i) => `${c} = ${placeholders[i + 1]}`)\n .join(\", \");\n\n if (onConflict === \"create\") {\n // Race-safe insert: if another worker already holds the key (and the row\n // hasn't expired), DO NOTHING; we then SELECT to surface the existing value.\n const { rows } = await this.pgClient.query(\n `INSERT INTO ${t}(${colList})\n VALUES (${valList})\n ON CONFLICT (key) DO UPDATE\n SET ${setClause}\n WHERE ${t}.expires_at IS NOT NULL AND ${t}.expires_at < now()\n RETURNING value`,\n params,\n );\n\n if (rows.length === 0) {\n // Conflict + existing row not expired → fetch existing for the result.\n const existing = await this.get(key);\n return { wasSet: false, existing } satisfies CacheSetResult;\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n return { wasSet: true, existing: null } satisfies CacheSetResult;\n }\n\n if (onConflict === \"update\") {\n // Update only when the key exists AND hasn't expired.\n const { rows } = await this.pgClient.query(\n `UPDATE ${t}\n SET ${updateSetClause}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())\n RETURNING value`,\n params,\n );\n\n if (rows.length === 0) {\n return { wasSet: false, existing: null } satisfies CacheSetResult;\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n return { wasSet: true, existing: null } satisfies CacheSetResult;\n }\n\n // upsert (default)\n await this.pgClient.query(\n `INSERT INTO ${t}(${colList})\n VALUES (${valList})\n ON CONFLICT (key) DO UPDATE\n SET ${setClause}`,\n params,\n );\n\n if (tags && tags.length > 0) {\n await this.applyTags(parsedKey, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n this.log(\"fetching\", parsedKey);\n\n const t = this.table;\n const { rows } = await this.pgClient.query(\n `SELECT value FROM ${t}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())`,\n [parsedKey],\n );\n\n if (rows.length === 0) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.log(\"fetched\", parsedKey);\n\n // pg's JSONB type round-trips through node-postgres as a parsed JS value\n // already — but some pool implementations may hand back a string. Be defensive.\n let value = rows[0].value;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n // Leave it as-is; cloning below will fail loud if it's truly broken.\n }\n }\n\n if (value === null || value === undefined) {\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n try {\n const cloned = structuredClone(value);\n await this.emit(\"hit\", { key: parsedKey, value: cloned });\n return cloned;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\n throw error;\n }\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\n * Returns `null` for missing or expired rows — `swr()` consumes this to\n * branch on freshness without going through `get()`'s clone-and-emit path.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const t = this.table;\n\n const { rows } = await this.pgClient.query(\n `SELECT value, expires_at, stale_at FROM ${t}\n WHERE key = $1 AND (expires_at IS NULL OR expires_at > now())`,\n [parsedKey],\n );\n\n if (rows.length === 0) {\n return null;\n }\n\n let data = rows[0].value;\n if (typeof data === \"string\") {\n try {\n data = JSON.parse(data);\n } catch {\n // Leave as-is — getEntry is metadata-shaped, the caller will hit the\n // same parse path on the public `get()` if they care.\n }\n }\n\n const entry: CacheData = { data };\n\n const expiresAtRaw = rows[0].expires_at as Date | string | null;\n if (expiresAtRaw) {\n const expiresAt = expiresAtRaw instanceof Date ? expiresAtRaw : new Date(expiresAtRaw);\n entry.expiresAt = expiresAt.getTime();\n }\n\n const staleAtRaw = rows[0].stale_at as Date | string | null;\n if (staleAtRaw) {\n const staleAt = staleAtRaw instanceof Date ? staleAtRaw : new Date(staleAtRaw);\n entry.staleAt = staleAt.getTime();\n }\n\n return entry;\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n this.log(\"removing\", parsedKey);\n\n const t = this.table;\n await this.pgClient.query(`DELETE FROM ${t} WHERE key = $1`, [parsedKey]);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Deletes all rows whose key equals the namespace exactly or starts with\n * `<namespace>.` — same boundary semantics as the other drivers.\n */\n public async removeNamespace(namespace: string) {\n const parsed = this.parseKey(namespace);\n this.log(\"clearing\", parsed || \"(all)\");\n\n const t = this.table;\n\n if (parsed === \"\") {\n await this.pgClient.query(`DELETE FROM ${t}`);\n } else {\n // Escape `_` and `%` in the prefix so they aren't treated as LIKE wildcards.\n const escaped = parsed.replace(/\\\\/g, \"\\\\\\\\\").replace(/_/g, \"\\\\_\").replace(/%/g, \"\\\\%\");\n await this.pgClient.query(`DELETE FROM ${t} WHERE key = $1 OR key LIKE $2 ESCAPE '\\\\'`, [\n parsed,\n `${escaped}.%`,\n ]);\n }\n\n this.log(\"cleared\", parsed || \"(all)\");\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * Honors `globalPrefix` — when configured, scopes the flush to entries\n * under the prefix rather than truncating the entire table (which could\n * wipe sibling tenants sharing the same Postgres database).\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n await this.pgClient.query(`DELETE FROM ${this.table}`);\n }\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n *\n * pgvector-backed similarity. Uses the `<=>` cosine-distance operator\n * (lower distance = higher similarity) and converts to cosine similarity\n * as `1 - distance` so the returned `score` matches the rest of the\n * package (`[0, 1]`, higher is more similar).\n *\n * Honors `topK`, `threshold`, and an optional `tags` filter (native\n * `tags && $tags` overlap query — much faster than the meta-key path).\n *\n * Throws {@link CacheUnsupportedError} when `options.vector` was not\n * configured at driver setup; throws {@link CacheConfigurationError} when\n * the pgvector extension is missing or the query vector's dimension count\n * doesn't match the configured one.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n if (!this.options.vector) {\n throw new CacheUnsupportedError(\n \"'pg' driver: similarity retrieval requires the 'vector' config block. Set options.vector.dimensions and reconnect.\",\n );\n }\n\n const expected = this.options.vector.dimensions;\n if (vector.length !== expected) {\n throw new CacheConfigurationError(\n `Pg cache driver: vector dimension mismatch — expected ${expected}, got ${vector.length}.`,\n );\n }\n\n if (!Number.isInteger(options.topK) || options.topK <= 0) {\n throw new CacheConfigurationError(\n `Pg cache driver: similar.topK must be a positive integer; got ${options.topK}.`,\n );\n }\n\n await this.ensureVectorReady();\n\n const t = this.table;\n const vecLiteral = this.formatVector(vector);\n const params: unknown[] = [vecLiteral];\n let tagFilter = \"\";\n\n if (options.tags && options.tags.length > 0) {\n params.push(options.tags);\n tagFilter = `AND tags && $${params.length}`;\n }\n\n params.push(options.topK);\n const topKParam = `$${params.length}`;\n\n const { rows } = await this.pgClient.query(\n `SELECT key, value, 1 - (embedding <=> $1::vector) AS score\n FROM ${t}\n WHERE embedding IS NOT NULL\n AND (expires_at IS NULL OR expires_at > now())\n ${tagFilter}\n ORDER BY embedding <=> $1::vector\n LIMIT ${topKParam}`,\n params,\n );\n\n const hits: CacheSimilarHit<T>[] = [];\n for (const row of rows) {\n const score = Number(row.score);\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n let value = row.value;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n // Surface as-is; if non-JSON crept in, the consumer will notice.\n }\n }\n\n // Match get() cloning semantics so consumers can't mutate cached state.\n if (value !== null && value !== undefined) {\n const ty = typeof value;\n if (ty !== \"string\" && ty !== \"number\" && ty !== \"boolean\") {\n value = structuredClone(value);\n }\n }\n\n hits.push({ key: row.key, value, score });\n }\n\n return hits;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"redis-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/redis-cache-driver.ts"],"mappings":";;;;;cA2Da,gBAAA,SACH,eAAA,CAAgB,UAAA,QAAkB,YAAA,GAAe,YAAA,aAC9C,WAAA,CAAY,UAAA,QAAkB,YAAA,GAAe,YAAA;;AAF1D;;EAOS,IAAA;EANmC;;;EAWnC,UAAA,CAAW,OAAA,EAAS,YAAA;EAVJ;;;EAuBV,eAAA,CAAgB,SAAA,WAAiB,OAAA;EAuCvC;;;EADM,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAmF2B;;;;EAAA,UAVpB,UAAA,CAAW,SAAA;EAqDC;;;;;;EAAA,UA3CN,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAiNrB;;;;;;;EAAA,UAxLZ,eAAA,CAAgB,GAAA,EAAK,QAAA,GAAW,OAAA;EA7K1B;;;EA+LT,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAhMY;;;EA+O7B,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EA9OQ;;;EA+P5B,KAAA,CAAA,GAAK,OAAA;EArPS;;;EAuQd,OAAA,CAAA,GAAO,OAAA;EA1P0B;;;;;;;;EA6TjC,UAAA,CAAA,GAAU,OAAA;EA1Mb;;;;EA2NG,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EAjNjB;;;;EAoO5B,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EA3MV;;;;EA8NnC,KAAA,CAAM,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,GAAA,YAAe,OAAA;AAAA"}
1
+ {"version":3,"file":"redis-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/redis-cache-driver.ts"],"mappings":";;;;;cA2Da,gBAAA,SACH,eAAA,CAAgB,UAAA,QAAkB,YAAA,GAAe,YAAA,aAC9C,WAAA,CAAY,UAAA,QAAkB,YAAA,GAAe,YAAA;;AAF1D;;EAOS,IAAA;EANmC;;;EAWnC,UAAA,CAAW,OAAA,EAAS,YAAA;EAVJ;;;EAuBV,eAAA,CAAgB,SAAA,WAAiB,OAAA;EAuCvC;;;EADM,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAmF2B;;;;EAAA,UAVpB,UAAA,CAAW,SAAA;EAqDC;;;;;;EAAA,UA3CN,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;EAiNrB;;;;;;;EAAA,UAxLZ,eAAA,CAAgB,GAAA,EAAK,QAAA,GAAW,OAAA;EA7K1B;;;EA+LT,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAhMY;;;EA+O7B,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EA9OQ;;;EA+P5B,KAAA,IAAK,OAAA;EArPS;;;EAuQd,OAAA,IAAO,OAAA;EA1P0B;;;;;;;;EA6TjC,UAAA,IAAU,OAAA;EA1Mb;;;;EA2NG,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EAjNjB;;;;EAoO5B,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EA3MV;;;;EA8NnC,KAAA,CAAM,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,GAAA,YAAe,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"memory-cache-list.d.mts","names":[],"sources":["../../../../../../../cache/src/list/memory-cache-list.ts"],"mappings":";;;;;AAuBA;;;;;;;;;;;;;;;;;;;cAAa,eAAA,eAA8B,iBAAA,CAAkB,CAAA;EAAA,iBAExC,MAAA;EAAA,iBACA,GAAA;cADA,MAAA,EAAQ,WAAA,YACR,GAAA,EAAK,QAAA;EAHkC;;;EAAA,QAS5C,IAAA;EAPK;;;;EAAA,QAgBL,KAAA;EAfU;;;EA2BX,IAAA,CAAA,GAAQ,KAAA,EAAO,CAAA,KAAM,OAAA;EAArB;;;EAWA,OAAA,CAAA,GAAW,KAAA,EAAO,CAAA,KAAM,OAAA;EAAxB;;;EAWA,GAAA,CAAA,GAAO,OAAA,CAAQ,CAAA;EAAf;;;EAgBA,KAAA,CAAA,GAAS,OAAA,CAAQ,CAAA;EAAR;;;EAgBT,KAAA,CAAM,KAAA,WAAgB,GAAA,YAAe,OAAA,CAAQ,CAAA;EAAvB;;;EAQtB,GAAA,CAAA,GAAO,OAAA,CAAQ,CAAA;EAAR;;;EAOP,MAAA,CAAA,GAAU,OAAA;EAQV;;;EAAA,IAAA,CAAK,KAAA,UAAe,GAAA,WAAc,OAAA;EASlC;;;EAAA,KAAA,CAAA,GAAS,OAAA;AAAA"}
1
+ {"version":3,"file":"memory-cache-list.d.mts","names":[],"sources":["../../../../../../../cache/src/list/memory-cache-list.ts"],"mappings":";;;;;AAuBA;;;;;;;;;;;;;;;;;;;cAAa,eAAA,eAA8B,iBAAA,CAAkB,CAAA;EAAA,iBAExC,MAAA;EAAA,iBACA,GAAA;cADA,MAAA,EAAQ,WAAA,YACR,GAAA,EAAK,QAAA;EAHkC;;;EAAA,QAS5C,IAAA;EAPK;;;;EAAA,QAgBL,KAAA;EAfU;;;EA2BX,IAAA,IAAQ,KAAA,EAAO,CAAA,KAAM,OAAA;EAArB;;;EAWA,OAAA,IAAW,KAAA,EAAO,CAAA,KAAM,OAAA;EAAxB;;;EAWA,GAAA,IAAO,OAAA,CAAQ,CAAA;EAAf;;;EAgBA,KAAA,IAAS,OAAA,CAAQ,CAAA;EAAR;;;EAgBT,KAAA,CAAM,KAAA,WAAgB,GAAA,YAAe,OAAA,CAAQ,CAAA;EAAvB;;;EAQtB,GAAA,IAAO,OAAA,CAAQ,CAAA;EAAR;;;EAOP,MAAA,IAAU,OAAA;EAQV;;;EAAA,IAAA,CAAK,KAAA,UAAe,GAAA,WAAc,OAAA;EASlC;;;EAAA,KAAA,IAAS,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"memory-cache-list.mjs","names":[],"sources":["../../../../../../../cache/src/list/memory-cache-list.ts"],"sourcesContent":["import type { CacheDriver, CacheKey, CacheListAccessor } from \"../types\";\n\n/**\n * Generic array-backed {@link CacheListAccessor}.\n *\n * Stores the full list as a single cache entry and performs read-mutate-write\n * for every operation. Correct for any driver, but O(n) per op. The Redis\n * driver overrides `list()` to return a native-command accessor instead.\n *\n * **Role.** Fallback list accessor bound to a driver + key. Every mutation\n * fetches the array, transforms it in memory, and writes it back.\n *\n * **Responsibility.**\n * - Owns: translating list operations into array mutations + driver writes.\n * - Does NOT own: concurrency control (callers should wrap in a distributed\n * lock when multi-process writers are possible), TTL preservation across\n * ops (writes use driver defaults), or tagging of list entries.\n *\n * @example\n * // Never constructed directly — obtained via driver.list():\n * const list = cache.list<Event>(\"recent-events\");\n * await list.push(event);\n */\nexport class MemoryCacheList<T> implements CacheListAccessor<T> {\n public constructor(\n private readonly driver: CacheDriver<any, any>,\n private readonly key: CacheKey,\n ) {}\n\n /**\n * Read the backing array from the driver. Returns an empty array on miss.\n */\n private async read(): Promise<T[]> {\n const current = (await this.driver.get(this.key)) as T[] | null;\n return Array.isArray(current) ? [...current] : [];\n }\n\n /**\n * Persist the backing array. Removes the entry when empty to keep the\n * store clean.\n */\n private async write(items: T[]): Promise<void> {\n if (items.length === 0) {\n await this.driver.remove(this.key);\n return;\n }\n\n await this.driver.set(this.key, items);\n }\n\n /**\n * {@inheritdoc}\n */\n public async push(...items: T[]): Promise<number> {\n const current = await this.read();\n current.push(...items);\n await this.write(current);\n\n return current.length;\n }\n\n /**\n * {@inheritdoc}\n */\n public async unshift(...items: T[]): Promise<number> {\n const current = await this.read();\n current.unshift(...items);\n await this.write(current);\n\n return current.length;\n }\n\n /**\n * {@inheritdoc}\n */\n public async pop(): Promise<T | null> {\n const current = await this.read();\n\n if (current.length === 0) {\n return null;\n }\n\n const value = current.pop() as T;\n await this.write(current);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async shift(): Promise<T | null> {\n const current = await this.read();\n\n if (current.length === 0) {\n return null;\n }\n\n const value = current.shift() as T;\n await this.write(current);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async slice(start?: number, end?: number): Promise<T[]> {\n const current = await this.read();\n return current.slice(start, end);\n }\n\n /**\n * {@inheritdoc}\n */\n public async all(): Promise<T[]> {\n return this.read();\n }\n\n /**\n * {@inheritdoc}\n */\n public async length(): Promise<number> {\n const current = await this.read();\n return current.length;\n }\n\n /**\n * {@inheritdoc}\n */\n public async trim(start: number, end: number): Promise<void> {\n const current = await this.read();\n const trimmed = current.slice(start, end + 1);\n await this.write(trimmed);\n }\n\n /**\n * {@inheritdoc}\n */\n public async clear(): Promise<void> {\n await this.driver.remove(this.key);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,kBAAb,MAAgE;CAC9D,AAAO,YACL,AAAiB,QACjB,AAAiB,KACjB;EAFiB;EACA;CAChB;;;;CAKH,MAAc,OAAqB;EACjC,MAAM,UAAW,MAAM,KAAK,OAAO,IAAI,KAAK,GAAG;EAC/C,OAAO,MAAM,QAAQ,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC;CAClD;;;;;CAMA,MAAc,MAAM,OAA2B;EAC7C,IAAI,MAAM,WAAW,GAAG;GACtB,MAAM,KAAK,OAAO,OAAO,KAAK,GAAG;GACjC;EACF;EAEA,MAAM,KAAK,OAAO,IAAI,KAAK,KAAK,KAAK;CACvC;;;;CAKA,MAAa,KAAK,GAAG,OAA6B;EAChD,MAAM,UAAU,MAAM,KAAK,KAAK;EAChC,QAAQ,KAAK,GAAG,KAAK;EACrB,MAAM,KAAK,MAAM,OAAO;EAExB,OAAO,QAAQ;CACjB;;;;CAKA,MAAa,QAAQ,GAAG,OAA6B;EACnD,MAAM,UAAU,MAAM,KAAK,KAAK;EAChC,QAAQ,QAAQ,GAAG,KAAK;EACxB,MAAM,KAAK,MAAM,OAAO;EAExB,OAAO,QAAQ;CACjB;;;;CAKA,MAAa,MAAyB;EACpC,MAAM,UAAU,MAAM,KAAK,KAAK;EAEhC,IAAI,QAAQ,WAAW,GACrB,OAAO;EAGT,MAAM,QAAQ,QAAQ,IAAI;EAC1B,MAAM,KAAK,MAAM,OAAO;EAExB,OAAO;CACT;;;;CAKA,MAAa,QAA2B;EACtC,MAAM,UAAU,MAAM,KAAK,KAAK;EAEhC,IAAI,QAAQ,WAAW,GACrB,OAAO;EAGT,MAAM,QAAQ,QAAQ,MAAM;EAC5B,MAAM,KAAK,MAAM,OAAO;EAExB,OAAO;CACT;;;;CAKA,MAAa,MAAM,OAAgB,KAA4B;EAE7D,QAAO,MADe,KAAK,KAAK,GACjB,MAAM,OAAO,GAAG;CACjC;;;;CAKA,MAAa,MAAoB;EAC/B,OAAO,KAAK,KAAK;CACnB;;;;CAKA,MAAa,SAA0B;EAErC,QAAO,MADe,KAAK,KAAK,GACjB;CACjB;;;;CAKA,MAAa,KAAK,OAAe,KAA4B;EAE3D,MAAM,WAAU,MADM,KAAK,KAAK,GACR,MAAM,OAAO,MAAM,CAAC;EAC5C,MAAM,KAAK,MAAM,OAAO;CAC1B;;;;CAKA,MAAa,QAAuB;EAClC,MAAM,KAAK,OAAO,OAAO,KAAK,GAAG;CACnC;AACF"}
1
+ {"version":3,"file":"memory-cache-list.mjs","names":[],"sources":["../../../../../../../cache/src/list/memory-cache-list.ts"],"sourcesContent":["import type { CacheDriver, CacheKey, CacheListAccessor } from \"../types\";\n\n/**\n * Generic array-backed {@link CacheListAccessor}.\n *\n * Stores the full list as a single cache entry and performs read-mutate-write\n * for every operation. Correct for any driver, but O(n) per op. The Redis\n * driver overrides `list()` to return a native-command accessor instead.\n *\n * **Role.** Fallback list accessor bound to a driver + key. Every mutation\n * fetches the array, transforms it in memory, and writes it back.\n *\n * **Responsibility.**\n * - Owns: translating list operations into array mutations + driver writes.\n * - Does NOT own: concurrency control (callers should wrap in a distributed\n * lock when multi-process writers are possible), TTL preservation across\n * ops (writes use driver defaults), or tagging of list entries.\n *\n * @example\n * // Never constructed directly — obtained via driver.list():\n * const list = cache.list<Event>(\"recent-events\");\n * await list.push(event);\n */\nexport class MemoryCacheList<T> implements CacheListAccessor<T> {\n public constructor(\n private readonly driver: CacheDriver<any, any>,\n private readonly key: CacheKey,\n ) {}\n\n /**\n * Read the backing array from the driver. Returns an empty array on miss.\n */\n private async read(): Promise<T[]> {\n const current = (await this.driver.get(this.key)) as T[] | null;\n return Array.isArray(current) ? [...current] : [];\n }\n\n /**\n * Persist the backing array. Removes the entry when empty to keep the\n * store clean.\n */\n private async write(items: T[]): Promise<void> {\n if (items.length === 0) {\n await this.driver.remove(this.key);\n return;\n }\n\n await this.driver.set(this.key, items);\n }\n\n /**\n * {@inheritdoc}\n */\n public async push(...items: T[]): Promise<number> {\n const current = await this.read();\n current.push(...items);\n await this.write(current);\n\n return current.length;\n }\n\n /**\n * {@inheritdoc}\n */\n public async unshift(...items: T[]): Promise<number> {\n const current = await this.read();\n current.unshift(...items);\n await this.write(current);\n\n return current.length;\n }\n\n /**\n * {@inheritdoc}\n */\n public async pop(): Promise<T | null> {\n const current = await this.read();\n\n if (current.length === 0) {\n return null;\n }\n\n const value = current.pop() as T;\n await this.write(current);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async shift(): Promise<T | null> {\n const current = await this.read();\n\n if (current.length === 0) {\n return null;\n }\n\n const value = current.shift() as T;\n await this.write(current);\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async slice(start?: number, end?: number): Promise<T[]> {\n const current = await this.read();\n return current.slice(start, end);\n }\n\n /**\n * {@inheritdoc}\n */\n public async all(): Promise<T[]> {\n return this.read();\n }\n\n /**\n * {@inheritdoc}\n */\n public async length(): Promise<number> {\n const current = await this.read();\n return current.length;\n }\n\n /**\n * {@inheritdoc}\n */\n public async trim(start: number, end: number): Promise<void> {\n const current = await this.read();\n const trimmed = current.slice(start, end + 1);\n await this.write(trimmed);\n }\n\n /**\n * {@inheritdoc}\n */\n public async clear(): Promise<void> {\n await this.driver.remove(this.key);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,kBAAb,MAAgE;CAC9D,AAAO,YACL,AAAiB,QACjB,AAAiB,KACjB;EAFiB;EACA;CAChB;;;;CAKH,MAAc,OAAqB;EACjC,MAAM,UAAW,MAAM,KAAK,OAAO,IAAI,KAAK,GAAG;EAC/C,OAAO,MAAM,QAAQ,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC;CAClD;;;;;CAMA,MAAc,MAAM,OAA2B;EAC7C,IAAI,MAAM,WAAW,GAAG;GACtB,MAAM,KAAK,OAAO,OAAO,KAAK,GAAG;GACjC;EACF;EAEA,MAAM,KAAK,OAAO,IAAI,KAAK,KAAK,KAAK;CACvC;;;;CAKA,MAAa,KAAK,GAAG,OAA6B;EAChD,MAAM,UAAU,MAAM,KAAK,KAAK;EAChC,QAAQ,KAAK,GAAG,KAAK;EACrB,MAAM,KAAK,MAAM,OAAO;EAExB,OAAO,QAAQ;CACjB;;;;CAKA,MAAa,QAAQ,GAAG,OAA6B;EACnD,MAAM,UAAU,MAAM,KAAK,KAAK;EAChC,QAAQ,QAAQ,GAAG,KAAK;EACxB,MAAM,KAAK,MAAM,OAAO;EAExB,OAAO,QAAQ;CACjB;;;;CAKA,MAAa,MAAyB;EACpC,MAAM,UAAU,MAAM,KAAK,KAAK;EAEhC,IAAI,QAAQ,WAAW,GACrB,OAAO;EAGT,MAAM,QAAQ,QAAQ,IAAI;EAC1B,MAAM,KAAK,MAAM,OAAO;EAExB,OAAO;CACT;;;;CAKA,MAAa,QAA2B;EACtC,MAAM,UAAU,MAAM,KAAK,KAAK;EAEhC,IAAI,QAAQ,WAAW,GACrB,OAAO;EAGT,MAAM,QAAQ,QAAQ,MAAM;EAC5B,MAAM,KAAK,MAAM,OAAO;EAExB,OAAO;CACT;;;;CAKA,MAAa,MAAM,OAAgB,KAA4B;EAE7D,QAAO,MADe,KAAK,KAAK,EAClB,CAAC,MAAM,OAAO,GAAG;CACjC;;;;CAKA,MAAa,MAAoB;EAC/B,OAAO,KAAK,KAAK;CACnB;;;;CAKA,MAAa,SAA0B;EAErC,QAAO,MADe,KAAK,KAAK,EAClB,CAAC;CACjB;;;;CAKA,MAAa,KAAK,OAAe,KAA4B;EAE3D,MAAM,WAAU,MADM,KAAK,KAAK,EACT,CAAC,MAAM,OAAO,MAAM,CAAC;EAC5C,MAAM,KAAK,MAAM,OAAO;CAC1B;;;;CAKA,MAAa,QAAuB;EAClC,MAAM,KAAK,OAAO,OAAO,KAAK,GAAG;CACnC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.mts","names":[],"sources":["../../../../../../cache/src/metrics.ts"],"mappings":";;;;;AAGiB;;;;;KAgBZ,cAAA;EACH,IAAA;EACA,MAAA;EACA,IAAA;EACA,OAAA;EACA,MAAA;EACA,cAAA,YAEa;EAAb,aAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+BW,qBAAA;EAoEJ;;;;;EAAA,mBA9DY,UAAA;EAoGT;;;;EAAA,mBA9FS,SAAA,EAAW,cAAA;EA6Gd;;;;EAAA,mBAvGG,QAAA,EAAU,GAAA,SAAY,cAAA;EA6HI;EAAA,UA1HnC,SAAA;cAES,UAAA;EAgJK;;;;EAvIjB,WAAA,CACL,KAAA,gDACA,IAAA,EAAM,cAAA;EA8JwB;;;AAAc;;EA5HvC,aAAA,CAAc,MAAA,UAAgB,UAAA;;;;;;EAU9B,QAAA,CAAA,GAAY,oBAAA;;;;;EAkBZ,KAAA,CAAA;;;;;YAUG,SAAA,CAAU,UAAA,WAAqB,cAAA;;;;;YAe/B,KAAA,CAAM,MAAA,EAAQ,cAAA,GAAiB,IAAA,CAAK,oBAAA;;;;;;YAsBpC,cAAA,CAAe,OAAA,aAAoB,oBAAA;;;;;YAwBnC,aAAA,CAAc,MAAA,EAAQ,cAAA,EAAgB,UAAA;;YAYtC,cAAA,CAAA,GAAkB,cAAA;;YAalB,aAAA,CAAc,MAAA,EAAQ,cAAA;AAAA"}
1
+ {"version":3,"file":"metrics.d.mts","names":[],"sources":["../../../../../../cache/src/metrics.ts"],"mappings":";;;;;AAGiB;;;;;KAgBZ,cAAA;EACH,IAAA;EACA,MAAA;EACA,IAAA;EACA,OAAA;EACA,MAAA;EACA,cAAA,YAEa;EAAb,aAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+BW,qBAAA;EAoEJ;;;;;EAAA,mBA9DY,UAAA;EAoGT;;;;EAAA,mBA9FS,SAAA,EAAW,cAAA;EA6Gd;;;;EAAA,mBAvGG,QAAA,EAAU,GAAA,SAAY,cAAA;EA6HI;EAAA,UA1HnC,SAAA;cAES,UAAA;EAgJK;;;;EAvIjB,WAAA,CACL,KAAA,gDACA,IAAA,EAAM,cAAA;EA8JwB;;;AAAc;;EA5HvC,aAAA,CAAc,MAAA,UAAgB,UAAA;;;;;;EAU9B,QAAA,IAAY,oBAAA;;;;;EAkBZ,KAAA;;;;;YAUG,SAAA,CAAU,UAAA,WAAqB,cAAA;;;;;YAe/B,KAAA,CAAM,MAAA,EAAQ,cAAA,GAAiB,IAAA,CAAK,oBAAA;;;;;;YAsBpC,cAAA,CAAe,OAAA,aAAoB,oBAAA;;;;;YAwBnC,aAAA,CAAc,MAAA,EAAQ,cAAA,EAAgB,UAAA;;YAYtC,cAAA,IAAkB,cAAA;;YAalB,aAAA,CAAc,MAAA,EAAQ,cAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.mjs","names":[],"sources":["../../../../../../cache/src/metrics.ts"],"sourcesContent":["import type {\n CacheEventData,\n CacheMetricsSnapshot,\n} from \"./types\";\n\n/**\n * Default size of the circular latency-sample buffer. 1000 samples covers\n * \"tell me the current p95\" for every realistic workload while keeping the\n * memory cost negligible (8KB at 8 bytes per number).\n */\nconst DEFAULT_LATENCY_BUFFER_SIZE = 1000;\n\n/**\n * Per-driver counter row tracked inside {@link CacheMetricsCollector}.\n *\n * Same shape as the public {@link CacheMetricsSnapshot} except for the lack\n * of `byDriver` (which is the breakdown itself) and the `latencySamples`\n * array — the buffer that p50/p95/p99 are computed from at snapshot time.\n */\ntype DriverCounters = {\n hits: number;\n misses: number;\n sets: number;\n removed: number;\n errors: number;\n latencySamples: number[];\n /** Pointer into `latencySamples` for the next write — circular buffer. */\n latencyCursor: number;\n};\n\n/**\n * Listens to `CacheManager` events and accumulates running counters + a\n * circular latency buffer per driver. Returned to consumers via\n * `cache.metrics()` as a {@link CacheMetricsSnapshot}.\n *\n * **Role.** Single-instance observability layer attached to the manager.\n * Subscribes once at construction; survives `cache.use()` driver switches\n * because the global event registry on the manager re-attaches handlers to\n * every loaded driver.\n *\n * **Responsibility.**\n * - Owns: per-driver and aggregate counters (`hits`, `misses`, `sets`,\n * `removed`, `errors`), the latency circular buffer, and snapshot\n * computation including hit-rate + percentile calculation.\n * - Does NOT own: event emission (driven by drivers via `BaseCacheDriver.emit`),\n * timing instrumentation (done at the manager level via `recordLatency`),\n * or persistence — every metric resets on `resetMetrics()` and on process\n * restart.\n *\n * @example\n * cache.metrics();\n * // {\n * // hits: 9821, misses: 173, hitRate: 0.983,\n * // latencyMs: { p50: 0.4, p95: 2.1, p99: 8.2, samples: 1000 },\n * // byDriver: { memory: { ... }, redis: { ... } },\n * // startedAt: 1714185600000,\n * // }\n */\nexport class CacheMetricsCollector {\n /**\n * Maximum number of latency samples retained per driver. Older samples\n * are overwritten in arrival order — quantile calc operates on whatever\n * window is currently in the buffer.\n */\n protected readonly bufferSize: number;\n\n /**\n * Aggregate counters across every driver. Mirrors what one driver bucket\n * holds — the snapshot reports both totals and per-driver breakdowns.\n */\n protected readonly aggregate: DriverCounters;\n\n /**\n * Per-driver buckets keyed by driver name (`\"memory\"`, `\"redis\"`, …).\n * Lazy-allocated on first event.\n */\n protected readonly byDriver: Map<string, DriverCounters> = new Map();\n\n /** Millisecond timestamp the collector last reset. */\n protected startedAt: number = Date.now();\n\n public constructor(bufferSize: number = DEFAULT_LATENCY_BUFFER_SIZE) {\n this.bufferSize = bufferSize;\n this.aggregate = this.createCounters();\n }\n\n /**\n * Increment the appropriate counters for a cache event. Called from the\n * manager's global listeners (one per event type).\n */\n public recordEvent(\n event: \"hit\" | \"miss\" | \"set\" | \"removed\" | \"error\",\n data: CacheEventData,\n ): void {\n const driverBucket = this.bucketFor(data.driver);\n const aggregate = this.aggregate;\n\n switch (event) {\n case \"hit\":\n aggregate.hits += 1;\n driverBucket.hits += 1;\n break;\n case \"miss\":\n aggregate.misses += 1;\n driverBucket.misses += 1;\n break;\n case \"set\":\n aggregate.sets += 1;\n driverBucket.sets += 1;\n break;\n case \"removed\":\n aggregate.removed += 1;\n driverBucket.removed += 1;\n break;\n case \"error\":\n aggregate.errors += 1;\n driverBucket.errors += 1;\n break;\n }\n }\n\n /**\n * Append a latency sample for `driver`. Called by the manager from its\n * timed wrappers around `get` / `set` / `remove`. Uses circular-buffer\n * semantics: oldest samples are overwritten once the buffer is full.\n */\n public recordLatency(driver: string, durationMs: number): void {\n this.appendLatency(this.aggregate, durationMs);\n this.appendLatency(this.bucketFor(driver), durationMs);\n }\n\n /**\n * Compute and return the current snapshot. Latency percentiles are\n * derived from a sorted copy of the buffer at call time — O(N log N)\n * on N=1000 is cheap enough that we don't bother caching.\n */\n public snapshot(): CacheMetricsSnapshot {\n const byDriver: Record<string, Omit<CacheMetricsSnapshot, \"byDriver\">> = {};\n\n for (const [driverName, bucket] of this.byDriver) {\n byDriver[driverName] = this.toRow(bucket);\n }\n\n return {\n ...this.toRow(this.aggregate),\n byDriver,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Wipe every counter, drop every latency sample, and reset `startedAt`.\n * The collector itself stays subscribed to events.\n */\n public reset(): void {\n this.resetCounters(this.aggregate);\n this.byDriver.clear();\n this.startedAt = Date.now();\n }\n\n /**\n * Locate the per-driver bucket, creating it on first reference. Driver\n * names are taken verbatim from `CacheEventData.driver`.\n */\n protected bucketFor(driverName: string): DriverCounters {\n let bucket = this.byDriver.get(driverName);\n\n if (!bucket) {\n bucket = this.createCounters();\n this.byDriver.set(driverName, bucket);\n }\n\n return bucket;\n }\n\n /**\n * Convert raw counters into the public snapshot row shape. Computes\n * `hitRate` and the latency percentiles on the fly.\n */\n protected toRow(bucket: DriverCounters): Omit<CacheMetricsSnapshot, \"byDriver\"> {\n const totalReads = bucket.hits + bucket.misses;\n const hitRate = totalReads === 0 ? 0 : bucket.hits / totalReads;\n const latency = this.computeLatency(bucket.latencySamples);\n\n return {\n hits: bucket.hits,\n misses: bucket.misses,\n sets: bucket.sets,\n removed: bucket.removed,\n errors: bucket.errors,\n hitRate,\n latencyMs: latency,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Sort a copy of the buffer and pick the percentile entries directly.\n * Empty buffers return zeroed percentiles so consumers can render\n * dashboards without null-checking.\n */\n protected computeLatency(samples: number[]): CacheMetricsSnapshot[\"latencyMs\"] {\n if (samples.length === 0) {\n return { p50: 0, p95: 0, p99: 0, samples: 0 };\n }\n\n const sorted = [...samples].sort((a, b) => a - b);\n const pick = (quantile: number): number => {\n const index = Math.min(sorted.length - 1, Math.floor(quantile * sorted.length));\n\n return sorted[index];\n };\n\n return {\n p50: pick(0.5),\n p95: pick(0.95),\n p99: pick(0.99),\n samples: sorted.length,\n };\n }\n\n /**\n * Append a latency sample using circular-buffer semantics — overwrite the\n * oldest entry once the buffer is full instead of growing unbounded.\n */\n protected appendLatency(bucket: DriverCounters, durationMs: number): void {\n if (bucket.latencySamples.length < this.bufferSize) {\n bucket.latencySamples.push(durationMs);\n\n return;\n }\n\n bucket.latencySamples[bucket.latencyCursor] = durationMs;\n bucket.latencyCursor = (bucket.latencyCursor + 1) % this.bufferSize;\n }\n\n /** Build a fresh counter row with zeroed totals and an empty buffer. */\n protected createCounters(): DriverCounters {\n return {\n hits: 0,\n misses: 0,\n sets: 0,\n removed: 0,\n errors: 0,\n latencySamples: [],\n latencyCursor: 0,\n };\n }\n\n /** Reset an existing counter row in place. Used by `reset()` for the aggregate. */\n protected resetCounters(bucket: DriverCounters): void {\n bucket.hits = 0;\n bucket.misses = 0;\n bucket.sets = 0;\n bucket.removed = 0;\n bucket.errors = 0;\n bucket.latencySamples.length = 0;\n bucket.latencyCursor = 0;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.mjs","names":[],"sources":["../../../../../../cache/src/metrics.ts"],"sourcesContent":["import type {\n CacheEventData,\n CacheMetricsSnapshot,\n} from \"./types\";\n\n/**\n * Default size of the circular latency-sample buffer. 1000 samples covers\n * \"tell me the current p95\" for every realistic workload while keeping the\n * memory cost negligible (8KB at 8 bytes per number).\n */\nconst DEFAULT_LATENCY_BUFFER_SIZE = 1000;\n\n/**\n * Per-driver counter row tracked inside {@link CacheMetricsCollector}.\n *\n * Same shape as the public {@link CacheMetricsSnapshot} except for the lack\n * of `byDriver` (which is the breakdown itself) and the `latencySamples`\n * array — the buffer that p50/p95/p99 are computed from at snapshot time.\n */\ntype DriverCounters = {\n hits: number;\n misses: number;\n sets: number;\n removed: number;\n errors: number;\n latencySamples: number[];\n /** Pointer into `latencySamples` for the next write — circular buffer. */\n latencyCursor: number;\n};\n\n/**\n * Listens to `CacheManager` events and accumulates running counters + a\n * circular latency buffer per driver. Returned to consumers via\n * `cache.metrics()` as a {@link CacheMetricsSnapshot}.\n *\n * **Role.** Single-instance observability layer attached to the manager.\n * Subscribes once at construction; survives `cache.use()` driver switches\n * because the global event registry on the manager re-attaches handlers to\n * every loaded driver.\n *\n * **Responsibility.**\n * - Owns: per-driver and aggregate counters (`hits`, `misses`, `sets`,\n * `removed`, `errors`), the latency circular buffer, and snapshot\n * computation including hit-rate + percentile calculation.\n * - Does NOT own: event emission (driven by drivers via `BaseCacheDriver.emit`),\n * timing instrumentation (done at the manager level via `recordLatency`),\n * or persistence — every metric resets on `resetMetrics()` and on process\n * restart.\n *\n * @example\n * cache.metrics();\n * // {\n * // hits: 9821, misses: 173, hitRate: 0.983,\n * // latencyMs: { p50: 0.4, p95: 2.1, p99: 8.2, samples: 1000 },\n * // byDriver: { memory: { ... }, redis: { ... } },\n * // startedAt: 1714185600000,\n * // }\n */\nexport class CacheMetricsCollector {\n /**\n * Maximum number of latency samples retained per driver. Older samples\n * are overwritten in arrival order — quantile calc operates on whatever\n * window is currently in the buffer.\n */\n protected readonly bufferSize: number;\n\n /**\n * Aggregate counters across every driver. Mirrors what one driver bucket\n * holds — the snapshot reports both totals and per-driver breakdowns.\n */\n protected readonly aggregate: DriverCounters;\n\n /**\n * Per-driver buckets keyed by driver name (`\"memory\"`, `\"redis\"`, …).\n * Lazy-allocated on first event.\n */\n protected readonly byDriver: Map<string, DriverCounters> = new Map();\n\n /** Millisecond timestamp the collector last reset. */\n protected startedAt: number = Date.now();\n\n public constructor(bufferSize: number = DEFAULT_LATENCY_BUFFER_SIZE) {\n this.bufferSize = bufferSize;\n this.aggregate = this.createCounters();\n }\n\n /**\n * Increment the appropriate counters for a cache event. Called from the\n * manager's global listeners (one per event type).\n */\n public recordEvent(\n event: \"hit\" | \"miss\" | \"set\" | \"removed\" | \"error\",\n data: CacheEventData,\n ): void {\n const driverBucket = this.bucketFor(data.driver);\n const aggregate = this.aggregate;\n\n switch (event) {\n case \"hit\":\n aggregate.hits += 1;\n driverBucket.hits += 1;\n break;\n case \"miss\":\n aggregate.misses += 1;\n driverBucket.misses += 1;\n break;\n case \"set\":\n aggregate.sets += 1;\n driverBucket.sets += 1;\n break;\n case \"removed\":\n aggregate.removed += 1;\n driverBucket.removed += 1;\n break;\n case \"error\":\n aggregate.errors += 1;\n driverBucket.errors += 1;\n break;\n }\n }\n\n /**\n * Append a latency sample for `driver`. Called by the manager from its\n * timed wrappers around `get` / `set` / `remove`. Uses circular-buffer\n * semantics: oldest samples are overwritten once the buffer is full.\n */\n public recordLatency(driver: string, durationMs: number): void {\n this.appendLatency(this.aggregate, durationMs);\n this.appendLatency(this.bucketFor(driver), durationMs);\n }\n\n /**\n * Compute and return the current snapshot. Latency percentiles are\n * derived from a sorted copy of the buffer at call time — O(N log N)\n * on N=1000 is cheap enough that we don't bother caching.\n */\n public snapshot(): CacheMetricsSnapshot {\n const byDriver: Record<string, Omit<CacheMetricsSnapshot, \"byDriver\">> = {};\n\n for (const [driverName, bucket] of this.byDriver) {\n byDriver[driverName] = this.toRow(bucket);\n }\n\n return {\n ...this.toRow(this.aggregate),\n byDriver,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Wipe every counter, drop every latency sample, and reset `startedAt`.\n * The collector itself stays subscribed to events.\n */\n public reset(): void {\n this.resetCounters(this.aggregate);\n this.byDriver.clear();\n this.startedAt = Date.now();\n }\n\n /**\n * Locate the per-driver bucket, creating it on first reference. Driver\n * names are taken verbatim from `CacheEventData.driver`.\n */\n protected bucketFor(driverName: string): DriverCounters {\n let bucket = this.byDriver.get(driverName);\n\n if (!bucket) {\n bucket = this.createCounters();\n this.byDriver.set(driverName, bucket);\n }\n\n return bucket;\n }\n\n /**\n * Convert raw counters into the public snapshot row shape. Computes\n * `hitRate` and the latency percentiles on the fly.\n */\n protected toRow(bucket: DriverCounters): Omit<CacheMetricsSnapshot, \"byDriver\"> {\n const totalReads = bucket.hits + bucket.misses;\n const hitRate = totalReads === 0 ? 0 : bucket.hits / totalReads;\n const latency = this.computeLatency(bucket.latencySamples);\n\n return {\n hits: bucket.hits,\n misses: bucket.misses,\n sets: bucket.sets,\n removed: bucket.removed,\n errors: bucket.errors,\n hitRate,\n latencyMs: latency,\n startedAt: this.startedAt,\n };\n }\n\n /**\n * Sort a copy of the buffer and pick the percentile entries directly.\n * Empty buffers return zeroed percentiles so consumers can render\n * dashboards without null-checking.\n */\n protected computeLatency(samples: number[]): CacheMetricsSnapshot[\"latencyMs\"] {\n if (samples.length === 0) {\n return { p50: 0, p95: 0, p99: 0, samples: 0 };\n }\n\n const sorted = [...samples].sort((a, b) => a - b);\n const pick = (quantile: number): number => {\n const index = Math.min(sorted.length - 1, Math.floor(quantile * sorted.length));\n\n return sorted[index];\n };\n\n return {\n p50: pick(0.5),\n p95: pick(0.95),\n p99: pick(0.99),\n samples: sorted.length,\n };\n }\n\n /**\n * Append a latency sample using circular-buffer semantics — overwrite the\n * oldest entry once the buffer is full instead of growing unbounded.\n */\n protected appendLatency(bucket: DriverCounters, durationMs: number): void {\n if (bucket.latencySamples.length < this.bufferSize) {\n bucket.latencySamples.push(durationMs);\n\n return;\n }\n\n bucket.latencySamples[bucket.latencyCursor] = durationMs;\n bucket.latencyCursor = (bucket.latencyCursor + 1) % this.bufferSize;\n }\n\n /** Build a fresh counter row with zeroed totals and an empty buffer. */\n protected createCounters(): DriverCounters {\n return {\n hits: 0,\n misses: 0,\n sets: 0,\n removed: 0,\n errors: 0,\n latencySamples: [],\n latencyCursor: 0,\n };\n }\n\n /** Reset an existing counter row in place. Used by `reset()` for the aggregate. */\n protected resetCounters(bucket: DriverCounters): void {\n bucket.hits = 0;\n bucket.misses = 0;\n bucket.sets = 0;\n bucket.removed = 0;\n bucket.errors = 0;\n bucket.latencySamples.length = 0;\n bucket.latencyCursor = 0;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"scoped-cache.d.mts","names":[],"sources":["../../../../../../cache/src/scoped-cache.ts"],"mappings":"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"}
1
+ {"version":3,"file":"scoped-cache.d.mts","names":[],"sources":["../../../../../../cache/src/scoped-cache.ts"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tagged-cache.d.mts","names":[],"sources":["../../../../../../cache/src/tagged-cache.ts"],"mappings":";;;;;AAYA;;cAAa,WAAA,YAAuB,iBAAA;EAShB;;;EAAA,UALR,SAAA;EAmD+B;;;EAAA,UA9C/B,MAAA,EAAQ,WAAA;EAmEU;;;cA9DT,IAAA,YAAgB,MAAA,EAAQ,WAAA;EAmFlB;;;EAAA,UA3Ef,MAAA,CAAO,GAAA;EAsHK;;;EAAA,UA/GN,cAAA,CAAe,GAAA,WAAc,OAAA;EA0H1C;;;;;;;EA/GU,oBAAA,CAAqB,SAAA,WAAoB,OAAA;EAqKI;;;EAAA,UAtJ1C,aAAA,CAAA,GAAiB,OAAA,CAAQ,GAAA;EAvDP;;;EAyErB,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;;;;EAaU,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EApEvB;;;EA2EG,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EApES;;;EAsFhC,UAAA,CAAA,GAAc,OAAA;EA5DX;;;;EA8EH,KAAA,CAAA,GAAS,OAAA;EA3DpB;;;EAkEW,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAhE/B;;;EAuEW,QAAA,CACX,GAAA,EAAK,QAAA,EACL,GAAA,UACA,QAAA,QAAgB,OAAA,QACf,OAAA;EA7Dc;;;EA6EJ,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;EAtEd;;;EAmFP,OAAA,CAAQ,GAAA,EAAK,QAAA,EAAU,KAAA,QAAa,OAAA;EA/CpC;;;EAsDA,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EA/CzC;;;EAiEJ,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;AAAA"}
1
+ {"version":3,"file":"tagged-cache.d.mts","names":[],"sources":["../../../../../../cache/src/tagged-cache.ts"],"mappings":";;;;;AAYA;;cAAa,WAAA,YAAuB,iBAAA;EAShB;;;EAAA,UALR,SAAA;EAmD+B;;;EAAA,UA9C/B,MAAA,EAAQ,WAAA;EAmEU;;;cA9DT,IAAA,YAAgB,MAAA,EAAQ,WAAA;EAmFlB;;;EAAA,UA3Ef,MAAA,CAAO,GAAA;EAsHK;;;EAAA,UA/GN,cAAA,CAAe,GAAA,WAAc,OAAA;EA0H1C;;;;;;;EA/GU,oBAAA,CAAqB,SAAA,WAAoB,OAAA;EAqKI;;;EAAA,UAtJ1C,aAAA,IAAiB,OAAA,CAAQ,GAAA;EAvDP;;;EAyErB,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;;;;EAaU,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EApEvB;;;EA2EG,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EApES;;;EAsFhC,UAAA,IAAc,OAAA;EA5DX;;;;EA8EH,KAAA,IAAS,OAAA;EA3DpB;;;EAkEW,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAhE/B;;;EAuEW,QAAA,CACX,GAAA,EAAK,QAAA,EACL,GAAA,UACA,QAAA,QAAgB,OAAA,QACf,OAAA;EA7Dc;;;EA6EJ,IAAA,CAAK,GAAA,EAAK,QAAA,GAAW,OAAA;EAtEd;;;EAmFP,OAAA,CAAQ,GAAA,EAAK,QAAA,EAAU,KAAA,QAAa,OAAA;EA/CpC;;;EAsDA,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;EA/CzC;;;EAiEJ,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAoB,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"tagged-cache.mjs","names":[],"sources":["../../../../../../cache/src/tagged-cache.ts"],"sourcesContent":["import type {\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheTtl,\r\n TaggedCacheDriver,\r\n} from \"./types\";\r\n\r\n/**\r\n * Tagged Cache Wrapper\r\n * Wraps a cache driver to automatically manage tag relationships\r\n */\r\nexport class TaggedCache implements TaggedCacheDriver {\r\n /**\r\n * The tags associated with this tagged cache instance\r\n */\r\n protected cacheTags: string[];\r\n\r\n /**\r\n * The underlying cache driver\r\n */\r\n protected driver: CacheDriver<any, any>;\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor(tags: string[], driver: CacheDriver<any, any>) {\r\n this.cacheTags = tags;\r\n this.driver = driver;\r\n }\r\n\r\n /**\r\n * Get the tag key prefix for storing tag-key relationships\r\n */\r\n protected tagKey(tag: string): string {\r\n return `cache:tags:${tag}`;\r\n }\r\n\r\n /**\r\n * Store tag-key relationship\r\n */\r\n protected async storeTaggedKey(key: string): Promise<void> {\r\n await this.storeTagRelationship(key);\r\n }\r\n\r\n /**\r\n * Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`\r\n * when tags are passed inline through `CacheSetOptions.tags`.\r\n *\r\n * @internal — public for cross-class use within this package; not part of the\r\n * stable consumer API.\r\n */\r\n public async storeTagRelationship(parsedKey: string): Promise<void> {\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n\r\n if (!keys.includes(parsedKey)) {\r\n keys.push(parsedKey);\r\n await this.driver.set(tagKey, keys, Infinity);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Get all keys associated with tags\r\n */\r\n protected async getTaggedKeys(): Promise<Set<string>> {\r\n const allKeys = new Set<string>();\r\n\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n\r\n for (const key of keys) {\r\n allKeys.add(key);\r\n }\r\n }\r\n\r\n return allKeys;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.driver.parseKey(key);\r\n\r\n await this.driver.set(key, value, ttlOrOptions);\r\n\r\n await this.storeTaggedKey(parsedKey);\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey): Promise<any | null> {\r\n return this.driver.get(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey): Promise<void> {\r\n const parsedKey = this.driver.parseKey(key);\r\n\r\n // Remove the value\r\n await this.driver.remove(key);\r\n\r\n // Remove from all tag relationships\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n const updatedKeys = keys.filter((k: string) => k !== parsedKey);\r\n await this.driver.set(tagKey, updatedKeys, Infinity);\r\n }\r\n }\r\n\r\n /**\r\n * Invalidate (clear) all keys associated with the current tags\r\n */\r\n public async invalidate(): Promise<void> {\r\n const keysToRemove = await this.getTaggedKeys();\r\n\r\n // Remove all tagged keys\r\n for (const key of keysToRemove) {\r\n await this.driver.remove(key);\r\n }\r\n\r\n // Clear tag relationship keys\r\n for (const tag of this.cacheTags) {\r\n await this.driver.remove(this.tagKey(tag));\r\n }\r\n }\r\n\r\n /**\r\n * Flush all keys associated with the current tags\r\n * @deprecated Use invalidate() instead for better semantics\r\n */\r\n public async flush(): Promise<void> {\r\n return this.invalidate();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n return this.driver.has(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember(\r\n key: CacheKey,\r\n ttl: number,\r\n callback: () => Promise<any>,\r\n ): Promise<any> {\r\n const value = await this.get(key);\r\n\r\n if (value !== null) {\r\n return value;\r\n }\r\n\r\n const result = await callback();\r\n await this.set(key, result, ttl);\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n const value = await this.get(key);\r\n\r\n if (value !== null) {\r\n await this.remove(key);\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n return this.set(key, value, Infinity);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\r\n const current = (await this.get(key)) || 0;\r\n\r\n if (typeof current !== \"number\") {\r\n throw new Error(\r\n `Cannot increment non-numeric value for key: ${this.driver.parseKey(key)}`,\r\n );\r\n }\r\n\r\n const newValue = current + value;\r\n await this.set(key, newValue);\r\n\r\n return newValue;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\r\n return this.increment(key, -value);\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"tagged-cache.mjs","names":[],"sources":["../../../../../../cache/src/tagged-cache.ts"],"sourcesContent":["import type {\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheTtl,\r\n TaggedCacheDriver,\r\n} from \"./types\";\r\n\r\n/**\r\n * Tagged Cache Wrapper\r\n * Wraps a cache driver to automatically manage tag relationships\r\n */\r\nexport class TaggedCache implements TaggedCacheDriver {\r\n /**\r\n * The tags associated with this tagged cache instance\r\n */\r\n protected cacheTags: string[];\r\n\r\n /**\r\n * The underlying cache driver\r\n */\r\n protected driver: CacheDriver<any, any>;\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor(tags: string[], driver: CacheDriver<any, any>) {\r\n this.cacheTags = tags;\r\n this.driver = driver;\r\n }\r\n\r\n /**\r\n * Get the tag key prefix for storing tag-key relationships\r\n */\r\n protected tagKey(tag: string): string {\r\n return `cache:tags:${tag}`;\r\n }\r\n\r\n /**\r\n * Store tag-key relationship\r\n */\r\n protected async storeTaggedKey(key: string): Promise<void> {\r\n await this.storeTagRelationship(key);\r\n }\r\n\r\n /**\r\n * Public alias of the tag-index writer. Called by `BaseCacheDriver.applyTags`\r\n * when tags are passed inline through `CacheSetOptions.tags`.\r\n *\r\n * @internal — public for cross-class use within this package; not part of the\r\n * stable consumer API.\r\n */\r\n public async storeTagRelationship(parsedKey: string): Promise<void> {\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n\r\n if (!keys.includes(parsedKey)) {\r\n keys.push(parsedKey);\r\n await this.driver.set(tagKey, keys, Infinity);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Get all keys associated with tags\r\n */\r\n protected async getTaggedKeys(): Promise<Set<string>> {\r\n const allKeys = new Set<string>();\r\n\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n\r\n for (const key of keys) {\r\n allKeys.add(key);\r\n }\r\n }\r\n\r\n return allKeys;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.driver.parseKey(key);\r\n\r\n await this.driver.set(key, value, ttlOrOptions);\r\n\r\n await this.storeTaggedKey(parsedKey);\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey): Promise<any | null> {\r\n return this.driver.get(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey): Promise<void> {\r\n const parsedKey = this.driver.parseKey(key);\r\n\r\n // Remove the value\r\n await this.driver.remove(key);\r\n\r\n // Remove from all tag relationships\r\n for (const tag of this.cacheTags) {\r\n const tagKey = this.tagKey(tag);\r\n const keys = (await this.driver.get(tagKey)) || [];\r\n const updatedKeys = keys.filter((k: string) => k !== parsedKey);\r\n await this.driver.set(tagKey, updatedKeys, Infinity);\r\n }\r\n }\r\n\r\n /**\r\n * Invalidate (clear) all keys associated with the current tags\r\n */\r\n public async invalidate(): Promise<void> {\r\n const keysToRemove = await this.getTaggedKeys();\r\n\r\n // Remove all tagged keys\r\n for (const key of keysToRemove) {\r\n await this.driver.remove(key);\r\n }\r\n\r\n // Clear tag relationship keys\r\n for (const tag of this.cacheTags) {\r\n await this.driver.remove(this.tagKey(tag));\r\n }\r\n }\r\n\r\n /**\r\n * Flush all keys associated with the current tags\r\n * @deprecated Use invalidate() instead for better semantics\r\n */\r\n public async flush(): Promise<void> {\r\n return this.invalidate();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n return this.driver.has(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember(\r\n key: CacheKey,\r\n ttl: number,\r\n callback: () => Promise<any>,\r\n ): Promise<any> {\r\n const value = await this.get(key);\r\n\r\n if (value !== null) {\r\n return value;\r\n }\r\n\r\n const result = await callback();\r\n await this.set(key, result, ttl);\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n const value = await this.get(key);\r\n\r\n if (value !== null) {\r\n await this.remove(key);\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n return this.set(key, value, Infinity);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\r\n const current = (await this.get(key)) || 0;\r\n\r\n if (typeof current !== \"number\") {\r\n throw new Error(\r\n `Cannot increment non-numeric value for key: ${this.driver.parseKey(key)}`,\r\n );\r\n }\r\n\r\n const newValue = current + value;\r\n await this.set(key, newValue);\r\n\r\n return newValue;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\r\n return this.increment(key, -value);\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tagged-scoped-cache.d.mts","names":[],"sources":["../../../../../../cache/src/tagged-scoped-cache.ts"],"mappings":";;;;;;AA4CA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,iBAAA,YAA6B,yBAAA;EA+Gb;;;;;EAAA,mBAzGR,KAAA,EAAO,WAAA;EAwHuB;;;;EAAA,mBAlH9B,UAAA;EAZqB;;;;cAkBrB,KAAA,EAAO,WAAA,EAAa,UAAA;EAZb;;;;EAqBnB,GAAA,CACL,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAboC;;;EAsBhC,GAAA,SAAA,CAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAX1C;;;EAkBK,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAhBxB;;;;EAwBI,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAfM;;;EAsB7B,IAAA,SAAA,CAAc,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAflC;;;;EAuBJ,OAAA,SAAA,CAAiB,GAAA,EAAK,QAAA,EAAU,KAAA,EAAO,CAAA,GAAI,OAAA,CAAQ,CAAA;EAf5B;;;;;EA2BjB,KAAA,CAAM,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,GAAA,YAAe,OAAA;EApBhB;;;EA+CtC,QAAA,SAAA,CACL,GAAA,EAAK,QAAA,EACL,YAAA,EAAc,QAAA,GAAW,eAAA,EACzB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IACvB,OAAA,CAAQ,CAAA;EA3Ca;;;;EAwDjB,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAiB,OAAA;EA5CpC;;;EAmDN,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAiB,OAAA;EAnDH;;;;;EA4DjC,UAAA,CAAA,GAAc,OAAA;EA/BX;;;;;EAAA,UA8CN,cAAA,CAAe,GAAA,EAAK,QAAA;AAAA"}
1
+ {"version":3,"file":"tagged-scoped-cache.d.mts","names":[],"sources":["../../../../../../cache/src/tagged-scoped-cache.ts"],"mappings":";;;;;;AA4CA;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,iBAAA,YAA6B,yBAAA;EA+Gb;;;;;EAAA,mBAzGR,KAAA,EAAO,WAAA;EAwHuB;;;;EAAA,mBAlH9B,UAAA;EAZqB;;;;cAkBrB,KAAA,EAAO,WAAA,EAAa,UAAA;EAZb;;;;EAqBnB,GAAA,CACL,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAboC;;;EAsBhC,GAAA,UAAa,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAX1C;;;EAkBK,GAAA,CAAI,GAAA,EAAK,QAAA,GAAW,OAAA;EAhBxB;;;;EAwBI,MAAA,CAAO,GAAA,EAAK,QAAA,GAAW,OAAA;EAfM;;;EAsB7B,IAAA,UAAc,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,CAAA;EAflC;;;;EAuBJ,OAAA,UAAiB,GAAA,EAAK,QAAA,EAAU,KAAA,EAAO,CAAA,GAAI,OAAA,CAAQ,CAAA;EAf5B;;;;;EA2BjB,KAAA,CAAM,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,GAAA,YAAe,OAAA;EApBhB;;;EA+CtC,QAAA,UACL,GAAA,EAAK,QAAA,EACL,YAAA,EAAc,QAAA,GAAW,eAAA,EACzB,QAAA,QAAgB,OAAA,CAAQ,CAAA,IACvB,OAAA,CAAQ,CAAA;EA3Ca;;;;EAwDjB,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAiB,OAAA;EA5CpC;;;EAmDN,SAAA,CAAU,GAAA,EAAK,QAAA,EAAU,KAAA,YAAiB,OAAA;EAnDH;;;;;EA4DjC,UAAA,IAAc,OAAA;EA/BX;;;;;EAAA,UA8CN,cAAA,CAAe,GAAA,EAAK,QAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"tagged-scoped-cache.mjs","names":[],"sources":["../../../../../../cache/src/tagged-scoped-cache.ts"],"sourcesContent":["import type { ScopedCache } from \"./scoped-cache\";\nimport type {\n CacheKey,\n CacheSetOptions,\n CacheTtl,\n RememberOptions,\n TaggedScopedCacheContract,\n} from \"./types\";\nimport {\n injectTags,\n mergeTagSets,\n normalizeToOptions,\n normalizeToRememberOptions,\n parseCacheKey,\n} from \"./utils\";\n\n/**\n * One-shot tagged write handle on top of a {@link ScopedCache}.\n *\n * **Role.** Returned by `scope.tags([...])`. Adds a fixed list of tags to\n * every write produced through this handle, on top of any tags the parent\n * scope already contributes. Stateless except for the captured tag list.\n *\n * **Responsibility.**\n * - Owns: appending the handle's tags to writes, delegating tag-index\n * bookkeeping for `setNX` (which lacks an inline `tags` knob on the driver\n * contract), and computing the union for `invalidate()` calls.\n * - Does NOT own: storage, prefix-prepending (delegated to the scope),\n * default `ttl` (delegated to the scope), or any kind of long-lived state.\n *\n * Tags compose additively: scope tags + handle tags + per-call tags, all\n * unioned and deduped. The handle never replaces scope tags — `invalidate()`\n * always sees the full union.\n *\n * @example\n * // Inside application code — scope provides the per-user tag automatically:\n * const feed = cache.namespace(`feed.${userId}`, { tags: [`user.${userId}`] });\n *\n * await feed.tags([\"unread\"]).set(\"messages.1\", message);\n * // → tagged with [user.<id>, unread]\n *\n * await feed.tags([\"unread\"]).invalidate();\n * // → wipes everything tagged with user.<id> OR unread.\n */\nexport class TaggedScopedCache implements TaggedScopedCacheContract {\n /**\n * The {@link ScopedCache} this handle delegates to. Held by reference so\n * scope-default changes (none today, but the option is preserved for the\n * future) are visible without rebuilding the handle.\n */\n protected readonly scope: ScopedCache;\n\n /**\n * Tags this handle contributes to every write. Cloned at the call site so\n * later mutation of the input array doesn't leak in.\n */\n protected readonly handleTags: string[];\n\n /**\n * Build a tagged handle. Constructed via `scope.tags([...])` — users never\n * call this directly.\n */\n public constructor(scope: ScopedCache, handleTags: string[]) {\n this.scope = scope;\n this.handleTags = [...handleTags];\n }\n\n /**\n * Write the scoped key with the handle's tags appended to whatever the\n * caller passed. Scope-level tags are added on top by the scope itself.\n */\n public set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const options = injectTags(normalizeToOptions(ttlOrOptions), this.handleTags);\n\n return this.scope.set(key, value, options);\n }\n\n /**\n * Read the scoped key. Tags don't affect reads — pass-through.\n */\n public get<T = any>(key: CacheKey): Promise<T | null> {\n return this.scope.get<T>(key);\n }\n\n /**\n * Check presence of the scoped key.\n */\n public has(key: CacheKey): Promise<boolean> {\n return this.scope.has(key);\n }\n\n /**\n * Remove the scoped key. The tag-index entry will eventually be cleaned up\n * by `invalidate()`; we don't proactively rewrite it here for cost reasons.\n */\n public remove(key: CacheKey): Promise<void> {\n return this.scope.remove(key);\n }\n\n /**\n * Read-and-remove the scoped key.\n */\n public pull<T = any>(key: CacheKey): Promise<T | null> {\n return this.scope.pull<T>(key);\n }\n\n /**\n * Permanent write with handle tags applied. Bypasses both the scope's and\n * the caller's TTL (forever means forever) — only tags get injected.\n */\n public forever<T = any>(key: CacheKey, value: T): Promise<T> {\n return this.scope.set(key, value, {\n ttl: Infinity,\n tags: this.handleTags,\n }) as Promise<T>;\n }\n\n /**\n * Atomic create-or-skip with the handle's tags applied on success. The\n * driver contract has no inline `tags` knob on `setNX`, so we register the\n * tag relationship manually after a successful write.\n */\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n const wasSet = await this.scope.setNX(key, value, ttl);\n\n if (!wasSet) {\n return false;\n }\n\n const allTags = mergeTagSets(this.scope.defaults.tags, this.handleTags);\n\n if (!allTags || allTags.length === 0) {\n return true;\n }\n\n const scopedKey = this.buildScopedKey(key);\n const parsedKey = this.scope.source.parseKey(scopedKey);\n const tagged = this.scope.source.tags(allTags) as unknown as {\n storeTagRelationship: (parsed: string) => Promise<void>;\n };\n\n await tagged.storeTagRelationship(parsedKey);\n\n return true;\n }\n\n /**\n * Read-or-compute with handle tags appended on the cache-miss write.\n */\n public remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n const options = injectTags(\n normalizeToRememberOptions(ttlOrOptions),\n this.handleTags,\n );\n\n return this.scope.remember<T>(key, options, callback);\n }\n\n /**\n * Atomic counter increment on the scoped key. Tags aren't applied to\n * subsequent increments — they're attached at first-write time.\n */\n public increment(key: CacheKey, value?: number): Promise<number> {\n return this.scope.increment(key, value);\n }\n\n /**\n * Atomic counter decrement on the scoped key. See {@link increment}.\n */\n public decrement(key: CacheKey, value?: number): Promise<number> {\n return this.scope.decrement(key, value);\n }\n\n /**\n * Wipe every entry tagged with the union of (scope tags + handle tags).\n * Tags are global across the package, so this reaches outside the scope's\n * prefix when scope tags are also used elsewhere.\n */\n public async invalidate(): Promise<void> {\n const allTags = mergeTagSets(this.scope.defaults.tags, this.handleTags);\n\n if (!allTags || allTags.length === 0) {\n return;\n }\n\n await this.scope.source.tags(allTags).invalidate();\n }\n\n /**\n * Compute the source-side key the same way `ScopedCache.scopedKey` does —\n * needed for `setNX`, where we have to register the tag relationship by\n * hand because the driver contract doesn't accept inline tags there.\n */\n protected buildScopedKey(key: CacheKey): string {\n const keyString = typeof key === \"string\" ? key : parseCacheKey(key);\n\n if (!keyString) {\n return this.scope.prefix;\n }\n\n return `${this.scope.prefix}.${keyString}`;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,IAAa,oBAAb,MAAoE;;;;;CAkBlE,AAAO,YAAY,OAAoB,YAAsB;EAC3D,KAAK,QAAQ;EACb,KAAK,aAAa,CAAC,GAAG,UAAU;CAClC;;;;;CAMA,AAAO,IACL,KACA,OACA,cACc;EACd,MAAM,UAAU,WAAW,mBAAmB,YAAY,GAAG,KAAK,UAAU;EAE5E,OAAO,KAAK,MAAM,IAAI,KAAK,OAAO,OAAO;CAC3C;;;;CAKA,AAAO,IAAa,KAAkC;EACpD,OAAO,KAAK,MAAM,IAAO,GAAG;CAC9B;;;;CAKA,AAAO,IAAI,KAAiC;EAC1C,OAAO,KAAK,MAAM,IAAI,GAAG;CAC3B;;;;;CAMA,AAAO,OAAO,KAA8B;EAC1C,OAAO,KAAK,MAAM,OAAO,GAAG;CAC9B;;;;CAKA,AAAO,KAAc,KAAkC;EACrD,OAAO,KAAK,MAAM,KAAQ,GAAG;CAC/B;;;;;CAMA,AAAO,QAAiB,KAAe,OAAsB;EAC3D,OAAO,KAAK,MAAM,IAAI,KAAK,OAAO;GAChC,KAAK;GACL,MAAM,KAAK;EACb,CAAC;CACH;;;;;;CAOA,MAAa,MAAM,KAAe,OAAY,KAAgC;EAG5E,IAAI,CAAC,MAFgB,KAAK,MAAM,MAAM,KAAK,OAAO,GAAG,GAGnD,OAAO;EAGT,MAAM,UAAU,aAAa,KAAK,MAAM,SAAS,MAAM,KAAK,UAAU;EAEtE,IAAI,CAAC,WAAW,QAAQ,WAAW,GACjC,OAAO;EAGT,MAAM,YAAY,KAAK,eAAe,GAAG;EACzC,MAAM,YAAY,KAAK,MAAM,OAAO,SAAS,SAAS;EAKtD,MAJe,KAAK,MAAM,OAAO,KAAK,OAI3B,EAAE,qBAAqB,SAAS;EAE3C,OAAO;CACT;;;;CAKA,AAAO,SACL,KACA,cACA,UACY;EACZ,MAAM,UAAU,WACd,2BAA2B,YAAY,GACvC,KAAK,UACP;EAEA,OAAO,KAAK,MAAM,SAAY,KAAK,SAAS,QAAQ;CACtD;;;;;CAMA,AAAO,UAAU,KAAe,OAAiC;EAC/D,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK;CACxC;;;;CAKA,AAAO,UAAU,KAAe,OAAiC;EAC/D,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK;CACxC;;;;;;CAOA,MAAa,aAA4B;EACvC,MAAM,UAAU,aAAa,KAAK,MAAM,SAAS,MAAM,KAAK,UAAU;EAEtE,IAAI,CAAC,WAAW,QAAQ,WAAW,GACjC;EAGF,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,EAAE,WAAW;CACnD;;;;;;CAOA,AAAU,eAAe,KAAuB;EAC9C,MAAM,YAAY,OAAO,QAAQ,WAAW,MAAM,cAAc,GAAG;EAEnE,IAAI,CAAC,WACH,OAAO,KAAK,MAAM;EAGpB,OAAO,GAAG,KAAK,MAAM,OAAO,GAAG;CACjC;AACF"}
1
+ {"version":3,"file":"tagged-scoped-cache.mjs","names":[],"sources":["../../../../../../cache/src/tagged-scoped-cache.ts"],"sourcesContent":["import type { ScopedCache } from \"./scoped-cache\";\nimport type {\n CacheKey,\n CacheSetOptions,\n CacheTtl,\n RememberOptions,\n TaggedScopedCacheContract,\n} from \"./types\";\nimport {\n injectTags,\n mergeTagSets,\n normalizeToOptions,\n normalizeToRememberOptions,\n parseCacheKey,\n} from \"./utils\";\n\n/**\n * One-shot tagged write handle on top of a {@link ScopedCache}.\n *\n * **Role.** Returned by `scope.tags([...])`. Adds a fixed list of tags to\n * every write produced through this handle, on top of any tags the parent\n * scope already contributes. Stateless except for the captured tag list.\n *\n * **Responsibility.**\n * - Owns: appending the handle's tags to writes, delegating tag-index\n * bookkeeping for `setNX` (which lacks an inline `tags` knob on the driver\n * contract), and computing the union for `invalidate()` calls.\n * - Does NOT own: storage, prefix-prepending (delegated to the scope),\n * default `ttl` (delegated to the scope), or any kind of long-lived state.\n *\n * Tags compose additively: scope tags + handle tags + per-call tags, all\n * unioned and deduped. The handle never replaces scope tags — `invalidate()`\n * always sees the full union.\n *\n * @example\n * // Inside application code — scope provides the per-user tag automatically:\n * const feed = cache.namespace(`feed.${userId}`, { tags: [`user.${userId}`] });\n *\n * await feed.tags([\"unread\"]).set(\"messages.1\", message);\n * // → tagged with [user.<id>, unread]\n *\n * await feed.tags([\"unread\"]).invalidate();\n * // → wipes everything tagged with user.<id> OR unread.\n */\nexport class TaggedScopedCache implements TaggedScopedCacheContract {\n /**\n * The {@link ScopedCache} this handle delegates to. Held by reference so\n * scope-default changes (none today, but the option is preserved for the\n * future) are visible without rebuilding the handle.\n */\n protected readonly scope: ScopedCache;\n\n /**\n * Tags this handle contributes to every write. Cloned at the call site so\n * later mutation of the input array doesn't leak in.\n */\n protected readonly handleTags: string[];\n\n /**\n * Build a tagged handle. Constructed via `scope.tags([...])` — users never\n * call this directly.\n */\n public constructor(scope: ScopedCache, handleTags: string[]) {\n this.scope = scope;\n this.handleTags = [...handleTags];\n }\n\n /**\n * Write the scoped key with the handle's tags appended to whatever the\n * caller passed. Scope-level tags are added on top by the scope itself.\n */\n public set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const options = injectTags(normalizeToOptions(ttlOrOptions), this.handleTags);\n\n return this.scope.set(key, value, options);\n }\n\n /**\n * Read the scoped key. Tags don't affect reads — pass-through.\n */\n public get<T = any>(key: CacheKey): Promise<T | null> {\n return this.scope.get<T>(key);\n }\n\n /**\n * Check presence of the scoped key.\n */\n public has(key: CacheKey): Promise<boolean> {\n return this.scope.has(key);\n }\n\n /**\n * Remove the scoped key. The tag-index entry will eventually be cleaned up\n * by `invalidate()`; we don't proactively rewrite it here for cost reasons.\n */\n public remove(key: CacheKey): Promise<void> {\n return this.scope.remove(key);\n }\n\n /**\n * Read-and-remove the scoped key.\n */\n public pull<T = any>(key: CacheKey): Promise<T | null> {\n return this.scope.pull<T>(key);\n }\n\n /**\n * Permanent write with handle tags applied. Bypasses both the scope's and\n * the caller's TTL (forever means forever) — only tags get injected.\n */\n public forever<T = any>(key: CacheKey, value: T): Promise<T> {\n return this.scope.set(key, value, {\n ttl: Infinity,\n tags: this.handleTags,\n }) as Promise<T>;\n }\n\n /**\n * Atomic create-or-skip with the handle's tags applied on success. The\n * driver contract has no inline `tags` knob on `setNX`, so we register the\n * tag relationship manually after a successful write.\n */\n public async setNX(key: CacheKey, value: any, ttl?: number): Promise<boolean> {\n const wasSet = await this.scope.setNX(key, value, ttl);\n\n if (!wasSet) {\n return false;\n }\n\n const allTags = mergeTagSets(this.scope.defaults.tags, this.handleTags);\n\n if (!allTags || allTags.length === 0) {\n return true;\n }\n\n const scopedKey = this.buildScopedKey(key);\n const parsedKey = this.scope.source.parseKey(scopedKey);\n const tagged = this.scope.source.tags(allTags) as unknown as {\n storeTagRelationship: (parsed: string) => Promise<void>;\n };\n\n await tagged.storeTagRelationship(parsedKey);\n\n return true;\n }\n\n /**\n * Read-or-compute with handle tags appended on the cache-miss write.\n */\n public remember<T = any>(\n key: CacheKey,\n ttlOrOptions: CacheTtl | RememberOptions,\n callback: () => Promise<T>,\n ): Promise<T> {\n const options = injectTags(\n normalizeToRememberOptions(ttlOrOptions),\n this.handleTags,\n );\n\n return this.scope.remember<T>(key, options, callback);\n }\n\n /**\n * Atomic counter increment on the scoped key. Tags aren't applied to\n * subsequent increments — they're attached at first-write time.\n */\n public increment(key: CacheKey, value?: number): Promise<number> {\n return this.scope.increment(key, value);\n }\n\n /**\n * Atomic counter decrement on the scoped key. See {@link increment}.\n */\n public decrement(key: CacheKey, value?: number): Promise<number> {\n return this.scope.decrement(key, value);\n }\n\n /**\n * Wipe every entry tagged with the union of (scope tags + handle tags).\n * Tags are global across the package, so this reaches outside the scope's\n * prefix when scope tags are also used elsewhere.\n */\n public async invalidate(): Promise<void> {\n const allTags = mergeTagSets(this.scope.defaults.tags, this.handleTags);\n\n if (!allTags || allTags.length === 0) {\n return;\n }\n\n await this.scope.source.tags(allTags).invalidate();\n }\n\n /**\n * Compute the source-side key the same way `ScopedCache.scopedKey` does —\n * needed for `setNX`, where we have to register the tag relationship by\n * hand because the driver contract doesn't accept inline tags there.\n */\n protected buildScopedKey(key: CacheKey): string {\n const keyString = typeof key === \"string\" ? key : parseCacheKey(key);\n\n if (!keyString) {\n return this.scope.prefix;\n }\n\n return `${this.scope.prefix}.${keyString}`;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,IAAa,oBAAb,MAAoE;;;;;CAkBlE,AAAO,YAAY,OAAoB,YAAsB;EAC3D,KAAK,QAAQ;EACb,KAAK,aAAa,CAAC,GAAG,UAAU;CAClC;;;;;CAMA,AAAO,IACL,KACA,OACA,cACc;EACd,MAAM,UAAU,WAAW,mBAAmB,YAAY,GAAG,KAAK,UAAU;EAE5E,OAAO,KAAK,MAAM,IAAI,KAAK,OAAO,OAAO;CAC3C;;;;CAKA,AAAO,IAAa,KAAkC;EACpD,OAAO,KAAK,MAAM,IAAO,GAAG;CAC9B;;;;CAKA,AAAO,IAAI,KAAiC;EAC1C,OAAO,KAAK,MAAM,IAAI,GAAG;CAC3B;;;;;CAMA,AAAO,OAAO,KAA8B;EAC1C,OAAO,KAAK,MAAM,OAAO,GAAG;CAC9B;;;;CAKA,AAAO,KAAc,KAAkC;EACrD,OAAO,KAAK,MAAM,KAAQ,GAAG;CAC/B;;;;;CAMA,AAAO,QAAiB,KAAe,OAAsB;EAC3D,OAAO,KAAK,MAAM,IAAI,KAAK,OAAO;GAChC,KAAK;GACL,MAAM,KAAK;EACb,CAAC;CACH;;;;;;CAOA,MAAa,MAAM,KAAe,OAAY,KAAgC;EAG5E,IAAI,CAAC,MAFgB,KAAK,MAAM,MAAM,KAAK,OAAO,GAAG,GAGnD,OAAO;EAGT,MAAM,UAAU,aAAa,KAAK,MAAM,SAAS,MAAM,KAAK,UAAU;EAEtE,IAAI,CAAC,WAAW,QAAQ,WAAW,GACjC,OAAO;EAGT,MAAM,YAAY,KAAK,eAAe,GAAG;EACzC,MAAM,YAAY,KAAK,MAAM,OAAO,SAAS,SAAS;EAKtD,MAJe,KAAK,MAAM,OAAO,KAAK,OAI3B,CAAC,CAAC,qBAAqB,SAAS;EAE3C,OAAO;CACT;;;;CAKA,AAAO,SACL,KACA,cACA,UACY;EACZ,MAAM,UAAU,WACd,2BAA2B,YAAY,GACvC,KAAK,UACP;EAEA,OAAO,KAAK,MAAM,SAAY,KAAK,SAAS,QAAQ;CACtD;;;;;CAMA,AAAO,UAAU,KAAe,OAAiC;EAC/D,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK;CACxC;;;;CAKA,AAAO,UAAU,KAAe,OAAiC;EAC/D,OAAO,KAAK,MAAM,UAAU,KAAK,KAAK;CACxC;;;;;;CAOA,MAAa,aAA4B;EACvC,MAAM,UAAU,aAAa,KAAK,MAAM,SAAS,MAAM,KAAK,UAAU;EAEtE,IAAI,CAAC,WAAW,QAAQ,WAAW,GACjC;EAGF,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,CAAC,CAAC,WAAW;CACnD;;;;;;CAOA,AAAU,eAAe,KAAuB;EAC9C,MAAM,YAAY,OAAO,QAAQ,WAAW,MAAM,cAAc,GAAG;EAEnE,IAAI,CAAC,WACH,OAAO,KAAK,MAAM;EAGpB,OAAO,GAAG,KAAK,MAAM,OAAO,GAAG;CACjC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.mts","names":[],"sources":["../../../../../../cache/src/utils.ts"],"mappings":";;;;;AAQA;iBAAgB,aAAA,CACd,GAAA,EAAK,QAAQ,EACb,OAAA;EAAW,YAAA;AAAA;;;;;;AAA6C;AA+B1D;;;;AAAwC;AAsCxC;;;;AAAuD;iBAtCvC,QAAA,CAAS,KAAe,EAAR,QAAQ;;;;;;;;;;;;;iBAsCxB,cAAA,CAAe,SAAwB,WAAJ,IAAI;AA+CvD;;;;;;;;;AAAA,iBAzBgB,kBAAA,CACd,KAAA,GAAQ,QAAA,GAAW,eAAA,GAClB,eAAA;;;;AAyBe;AAuBlB;;;;;;;iBAzBgB,0BAAA,CACd,KAAA,GAAQ,QAAA,GAAW,eAAA,GAClB,eAAA;;;AA0Be;AAiClB;;;;AACU;AA4BV;;;iBAjEgB,UAAA,CACd,GAAA,EAAK,QAAA,cACL,SAAA,WAAoB,IAAI,cACxB,QAAA;;;;;;;;AAiEE;AA0BJ;;;;AAAyD;AA4CzD;iBAtGgB,YAAA,CAAA,GACX,KAAK;;;AAqGuC;AAWjD;;;;;;iBApFgB,UAAA;EAAuB,IAAA;AAAA,EAAA,CACrC,OAAA,EAAS,CAAA,EACT,SAAA,aACC,CAAC;AAgGJ;;;;;;;;;;;;;;;AAAA,iBAtEgB,gBAAA,CAAiB,CAAA,YAAa,CAAW;;;;;iBA4CzC,iBAAA,CAAkB,OAAe;;;;;;;;iBAWjC,aAAA,CAAc,KAAA;EAAmB,OAAA;EAAiB,IAAA;AAAA;AAAA,aAetD,SAAA;;;;EAIV,SAAA;;;;EAIA,QAAA;;;;EAIA,QAAA;;;;EAIA,OAAA;;;;EAIA,QAAA;;;;EAIA,UAAA;;;;EAIA,SAAA;;;;EAIA,UAAA;;;;EAIA,UAAA;;;;EAIA,QAAA;AAAA"}
1
+ {"version":3,"file":"utils.d.mts","names":[],"sources":["../../../../../../cache/src/utils.ts"],"mappings":";;;;;AAQA;iBAAgB,aAAA,CACd,GAAA,EAAK,QAAQ,EACb,OAAA;EAAW,YAAA;AAAA;;;;;;AAA6C;AA+B1D;;;;AAAwC;AAsCxC;;;;AAAuD;iBAtCvC,QAAA,CAAS,KAAe,EAAR,QAAQ;;;;;;;;;;;;;iBAsCxB,cAAA,CAAe,SAAwB,WAAJ,IAAI;AA+CvD;;;;;;;;;AAAA,iBAzBgB,kBAAA,CACd,KAAA,GAAQ,QAAA,GAAW,eAAA,GAClB,eAAA;;;;AAyBe;AAuBlB;;;;;;;iBAzBgB,0BAAA,CACd,KAAA,GAAQ,QAAA,GAAW,eAAA,GAClB,eAAA;;;AA0Be;AAiClB;;;;AACU;AA4BV;;;iBAjEgB,UAAA,CACd,GAAA,EAAK,QAAA,cACL,SAAA,WAAoB,IAAI,cACxB,QAAA;;;;;;;;AAiEE;AA0BJ;;;;AAAyD;AA4CzD;iBAtGgB,YAAA,IACX,KAAK;;;AAqGuC;AAWjD;;;;;;iBApFgB,UAAA;EAAuB,IAAA;AAAA,GACrC,OAAA,EAAS,CAAA,EACT,SAAA,aACC,CAAC;AAgGJ;;;;;;;;;;;;;;;AAAA,iBAtEgB,gBAAA,CAAiB,CAAA,YAAa,CAAW;;;;;iBA4CzC,iBAAA,CAAkB,OAAe;;;;;;;;iBAWjC,aAAA,CAAc,KAAA;EAAmB,OAAA;EAAiB,IAAA;AAAA;AAAA,aAetD,SAAA;;;;EAIV,SAAA;;;;EAIA,QAAA;;;;EAIA,QAAA;;;;EAIA,OAAA;;;;EAIA,QAAA;;;;EAIA,UAAA;;;;EAIA,SAAA;;;;EAIA,UAAA;;;;EAIA,UAAA;;;;EAIA,QAAA;AAAA"}
package/esm/utils.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../cache/src/utils.ts"],"sourcesContent":["import { rtrim } from \"@mongez/reinforcements\";\r\nimport ms, { type StringValue } from \"ms\";\r\nimport type { CacheKey, CacheSetOptions, CacheTtl, RememberOptions } from \"./types\";\r\nimport { CacheConfigurationError } from \"./types\";\r\n\r\n/**\r\n * Make a proper key for the cache\r\n */\r\nexport function parseCacheKey(\r\n key: CacheKey,\r\n options: { globalPrefix?: string | (() => string) } = {},\r\n): string {\r\n if (typeof key === \"object\") {\r\n key = JSON.stringify(key);\r\n }\r\n\r\n // remove any curly braces and double quotes along with []\r\n key = key.replace(/[{}\"[\\]]/g, \"\").replaceAll(/[:,]/g, \".\");\r\n\r\n const cachePrefix =\r\n typeof options.globalPrefix === \"function\" ? options.globalPrefix() : options.globalPrefix;\r\n\r\n return rtrim(String(cachePrefix ? rtrim(cachePrefix, \".\") + \".\" + key : key), \".\");\r\n}\r\n\r\n/**\r\n * Parse a TTL value into seconds.\r\n *\r\n * Accepts:\r\n * - a number (already in seconds) — returned unchanged\r\n * - `Infinity` — no expiration, returned unchanged\r\n * - a human-readable duration string (e.g. `\"1h\"`, `\"30m\"`, `\"7d\"`) — parsed via `ms` then converted to seconds\r\n *\r\n * Throws `CacheConfigurationError` on unparseable strings or negative numbers.\r\n *\r\n * @example\r\n * parseTtl(3600); // 3600\r\n * parseTtl(\"1h\"); // 3600\r\n * parseTtl(\"7d\"); // 604800\r\n * parseTtl(Infinity); // Infinity\r\n */\r\nexport function parseTtl(input: CacheTtl): number {\r\n if (typeof input === \"number\") {\r\n if (input < 0) {\r\n throw new CacheConfigurationError(`Invalid TTL: negative number (${input}).`);\r\n }\r\n\r\n return input;\r\n }\r\n\r\n if (typeof input !== \"string\" || input.trim() === \"\") {\r\n throw new CacheConfigurationError(\r\n `Invalid TTL: expected number or duration string, got ${typeof input}.`,\r\n );\r\n }\r\n\r\n const milliseconds = ms(input as StringValue);\r\n\r\n if (milliseconds === undefined || Number.isNaN(milliseconds)) {\r\n throw new CacheConfigurationError(\r\n `Invalid TTL duration string: \"${input}\". Expected forms like \"1h\", \"30m\", \"7d\".`,\r\n );\r\n }\r\n\r\n return Math.floor(milliseconds / 1000);\r\n}\r\n\r\n/**\r\n * Convert an absolute `expiresAt` (Date or epoch milliseconds) into a\r\n * relative TTL in seconds.\r\n *\r\n * Throws {@link CacheConfigurationError} when the deadline is in the past —\r\n * the caller almost certainly has a bug (stale timestamp, wrong unit, etc.)\r\n * and silently storing an already-expired entry would hide it.\r\n *\r\n * @example\r\n * expiresAtToTtl(new Date(Date.now() + 60_000)); // ~60\r\n * expiresAtToTtl(Date.now() + 30 * 60 * 1000); // ~1800\r\n */\r\nexport function expiresAtToTtl(expiresAt: number | Date): number {\r\n const deadline = expiresAt instanceof Date ? expiresAt.getTime() : expiresAt;\r\n const relativeMs = deadline - Date.now();\r\n\r\n if (relativeMs <= 0) {\r\n throw new CacheConfigurationError(\r\n `\\`expiresAt\\` must be in the future; got ${new Date(deadline).toISOString()}.`,\r\n );\r\n }\r\n\r\n return Math.ceil(relativeMs / 1000);\r\n}\r\n\r\n/**\r\n * Coerce the polymorphic 3rd `set` argument into a uniform `CacheSetOptions`\r\n * shape. Lets callers (and `BaseCacheDriver.resolveSetOptions`) skip per-shape\r\n * branching.\r\n *\r\n * - `undefined` / `null` → `{}` (resolves to driver-level defaults later)\r\n * - `number` / `string` (positional TTL) → `{ ttl }`\r\n * - already an options object → returned as-is\r\n */\r\nexport function normalizeToOptions(\r\n input?: CacheTtl | CacheSetOptions,\r\n): CacheSetOptions {\r\n if (input === undefined || input === null) {\r\n return {};\r\n }\r\n\r\n if (typeof input === \"number\" || typeof input === \"string\") {\r\n return { ttl: input };\r\n }\r\n\r\n return input;\r\n}\r\n\r\n/**\r\n * Sibling of {@link normalizeToOptions} for the `remember()` call site, where\r\n * the polymorphic 2nd argument is `CacheTtl | RememberOptions` (no `expiresAt`,\r\n * no `onConflict`). Returns the same shape so callers can `{ ...opts, ... }`\r\n * without branching.\r\n *\r\n * @example\r\n * normalizeToRememberOptions(60); // { ttl: 60 }\r\n * normalizeToRememberOptions(\"1h\"); // { ttl: \"1h\" }\r\n * normalizeToRememberOptions({ ttl: \"1h\", tags: [\"x\"] }); // returned as-is\r\n */\r\nexport function normalizeToRememberOptions(\r\n input?: CacheTtl | RememberOptions,\r\n): RememberOptions {\r\n if (input === undefined || input === null) {\r\n return {};\r\n }\r\n\r\n if (typeof input === \"number\" || typeof input === \"string\") {\r\n return { ttl: input };\r\n }\r\n\r\n return input;\r\n}\r\n\r\n/**\r\n * Resolve the final TTL in seconds for a `set` call. Precedence:\r\n *\r\n * 1. Caller's `ttl` (number or duration string) wins.\r\n * 2. Otherwise, caller's `expiresAt` is converted to relative seconds.\r\n * 3. Otherwise, `fallback` is used (driver-level default — typically\r\n * `Infinity` when no default is configured, meaning \"never expires\").\r\n *\r\n * Throws {@link CacheConfigurationError} when `ttl` and `expiresAt` are\r\n * supplied together (mutually exclusive).\r\n */\r\nexport function resolveTtl(\r\n ttl: CacheTtl | undefined,\r\n expiresAt: number | Date | undefined,\r\n fallback: number,\r\n): number {\r\n if (ttl !== undefined && expiresAt !== undefined) {\r\n throw new CacheConfigurationError(\r\n \"Cache set options cannot specify both `ttl` and `expiresAt` — choose one.\",\r\n );\r\n }\r\n\r\n if (ttl !== undefined) {\r\n return parseTtl(ttl);\r\n }\r\n\r\n if (expiresAt !== undefined) {\r\n return expiresAtToTtl(expiresAt);\r\n }\r\n\r\n return fallback;\r\n}\r\n\r\n/**\r\n * Combine any number of tag lists into a single deduped array, dropping\r\n * `undefined`/empty entries. Returns `undefined` when no tags survive — lets\r\n * callers skip emitting empty `tags: []` into option payloads.\r\n *\r\n * Used by scoped-cache merging where scope tags + handle tags + per-call tags\r\n * must union additively without duplicates.\r\n *\r\n * @example\r\n * mergeTagSets([\"a\", \"b\"], [\"b\", \"c\"]); // [\"a\", \"b\", \"c\"]\r\n * mergeTagSets(undefined, [\"x\"]); // [\"x\"]\r\n * mergeTagSets(undefined, undefined); // undefined\r\n * mergeTagSets([], []); // undefined\r\n */\r\nexport function mergeTagSets(\r\n ...lists: (string[] | undefined)[]\r\n): string[] | undefined {\r\n const flat: string[] = [];\r\n\r\n for (const list of lists) {\r\n if (!list || list.length === 0) {\r\n continue;\r\n }\r\n\r\n flat.push(...list);\r\n }\r\n\r\n if (flat.length === 0) {\r\n return undefined;\r\n }\r\n\r\n return Array.from(new Set(flat));\r\n}\r\n\r\n/**\r\n * Add extra tags to any option-bag that already shapes `tags?: string[]`.\r\n * Pure — clones the input shape, never mutates. Tags are appended (caller\r\n * is responsible for de-duplication if needed; pair with {@link mergeTagSets}).\r\n *\r\n * @example\r\n * injectTags({ ttl: \"1h\" }, [\"unread\"]); // { ttl: \"1h\", tags: [\"unread\"] }\r\n * injectTags({ tags: [\"a\"] }, [\"b\"]); // { tags: [\"a\", \"b\"] }\r\n */\r\nexport function injectTags<T extends { tags?: string[] }>(\r\n options: T,\r\n extraTags: string[],\r\n): T {\r\n if (extraTags.length === 0) {\r\n return options;\r\n }\r\n\r\n return {\r\n ...options,\r\n tags: [...(options.tags ?? []), ...extraTags],\r\n };\r\n}\r\n\r\n/**\r\n * Cosine similarity between two equal-length numeric vectors.\r\n *\r\n * Returns a value in `[-1, 1]` where `1` means perfectly aligned, `0` means\r\n * orthogonal, and `-1` means opposing. For typical embedding spaces (where\r\n * vectors live in the positive cone) the practical range is `[0, 1]`.\r\n *\r\n * Throws {@link CacheConfigurationError} on dimension mismatch — fail loud at\r\n * the call site rather than silently returning a misleading score. A zero-norm\r\n * vector on either side returns `0` (no defined direction to compare).\r\n *\r\n * @example\r\n * cosineSimilarity([1, 0, 0], [1, 0, 0]); // 1\r\n * cosineSimilarity([1, 0, 0], [0, 1, 0]); // 0\r\n */\r\nexport function cosineSimilarity(a: number[], b: number[]): number {\r\n if (a.length !== b.length) {\r\n throw new CacheConfigurationError(\r\n `Vector dimension mismatch: got ${a.length} and ${b.length}.`,\r\n );\r\n }\r\n\r\n if (a.length === 0) {\r\n throw new CacheConfigurationError(\r\n \"Vector dimension mismatch: empty vector cannot be compared.\",\r\n );\r\n }\r\n\r\n let dot = 0;\r\n let normA = 0;\r\n let normB = 0;\r\n\r\n for (let i = 0; i < a.length; i++) {\r\n const x = a[i];\r\n const y = b[i];\r\n dot += x * y;\r\n normA += x * x;\r\n normB += y * y;\r\n }\r\n\r\n if (normA === 0 || normB === 0) {\r\n return 0;\r\n }\r\n\r\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\r\n}\r\n\r\n/**\r\n * Matches userinfo credentials embedded in a connection URL\r\n * (e.g. `redis://user:pass@host`). Node/Redis/Postgres client errors\r\n * frequently echo the target connection string — including the password —\r\n * back in `error.message` on connection failure.\r\n */\r\nconst CREDENTIALS_IN_URL = /(:\\/\\/)[^\\s/@]+:[^\\s/@]+@/g;\r\n\r\n/**\r\n * Strip embedded `user:pass@` credentials from a string before it reaches\r\n * any log sink.\r\n */\r\nexport function redactCredentials(message: string): string {\r\n return message.replace(CREDENTIALS_IN_URL, \"$1[REDACTED]@\");\r\n}\r\n\r\n/**\r\n * Reduce an unknown thrown value to a log-safe `{ message, code? }` shape.\r\n * Callers must never log the raw error object itself — it may carry the\r\n * connection URL (with password) in the top-level message, `cause`, or\r\n * driver-specific fields, all of which `console.log`/`util.inspect` would\r\n * still print in full.\r\n */\r\nexport function safeErrorInfo(error: unknown): { message: string; code?: string } {\r\n if (error instanceof Error) {\r\n const info: { message: string; code?: string } = {\r\n message: redactCredentials(error.message),\r\n };\r\n\r\n const code = (error as any).code;\r\n if (code) info.code = String(code);\r\n\r\n return info;\r\n }\r\n\r\n return { message: redactCredentials(String(error)) };\r\n}\r\n\r\nexport enum CACHE_FOR {\r\n /**\r\n * Cache for 30 Minutes (in seconds)\r\n */\r\n HALF_HOUR = 1800,\r\n /**\r\n * Cache for 1 Hour (in seconds)\r\n */\r\n ONE_HOUR = 3600,\r\n /**\r\n * Cache for 12 Hours (in seconds)\r\n */\r\n HALF_DAY = 43200,\r\n /**\r\n * Cache for 24 Hours (in seconds)\r\n */\r\n ONE_DAY = 86400,\r\n /**\r\n * Cache for 7 Days (in seconds)\r\n */\r\n ONE_WEEK = 604800,\r\n /**\r\n * Cache for 15 Days (in seconds)\r\n */\r\n HALF_MONTH = 1296000,\r\n /**\r\n * Cache for 30 Days (in seconds)\r\n */\r\n ONE_MONTH = 2592000,\r\n /**\r\n * Cache for 60 Days (in seconds)\r\n */\r\n TWO_MONTHS = 5184000,\r\n /**\r\n * Cache for 180 Days (in seconds)\r\n */\r\n SIX_MONTHS = 15768000,\r\n /**\r\n * Cache for 365 Days (in seconds)\r\n */\r\n ONE_YEAR = 31536000,\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../cache/src/utils.ts"],"sourcesContent":["import { rtrim } from \"@mongez/reinforcements\";\r\nimport ms, { type StringValue } from \"ms\";\r\nimport type { CacheKey, CacheSetOptions, CacheTtl, RememberOptions } from \"./types\";\r\nimport { CacheConfigurationError } from \"./types\";\r\n\r\n/**\r\n * Make a proper key for the cache\r\n */\r\nexport function parseCacheKey(\r\n key: CacheKey,\r\n options: { globalPrefix?: string | (() => string) } = {},\r\n): string {\r\n if (typeof key === \"object\") {\r\n key = JSON.stringify(key);\r\n }\r\n\r\n // remove any curly braces and double quotes along with []\r\n key = key.replace(/[{}\"[\\]]/g, \"\").replaceAll(/[:,]/g, \".\");\r\n\r\n const cachePrefix =\r\n typeof options.globalPrefix === \"function\" ? options.globalPrefix() : options.globalPrefix;\r\n\r\n return rtrim(String(cachePrefix ? rtrim(cachePrefix, \".\") + \".\" + key : key), \".\");\r\n}\r\n\r\n/**\r\n * Parse a TTL value into seconds.\r\n *\r\n * Accepts:\r\n * - a number (already in seconds) — returned unchanged\r\n * - `Infinity` — no expiration, returned unchanged\r\n * - a human-readable duration string (e.g. `\"1h\"`, `\"30m\"`, `\"7d\"`) — parsed via `ms` then converted to seconds\r\n *\r\n * Throws `CacheConfigurationError` on unparseable strings or negative numbers.\r\n *\r\n * @example\r\n * parseTtl(3600); // 3600\r\n * parseTtl(\"1h\"); // 3600\r\n * parseTtl(\"7d\"); // 604800\r\n * parseTtl(Infinity); // Infinity\r\n */\r\nexport function parseTtl(input: CacheTtl): number {\r\n if (typeof input === \"number\") {\r\n if (input < 0) {\r\n throw new CacheConfigurationError(`Invalid TTL: negative number (${input}).`);\r\n }\r\n\r\n return input;\r\n }\r\n\r\n if (typeof input !== \"string\" || input.trim() === \"\") {\r\n throw new CacheConfigurationError(\r\n `Invalid TTL: expected number or duration string, got ${typeof input}.`,\r\n );\r\n }\r\n\r\n const milliseconds = ms(input as StringValue);\r\n\r\n if (milliseconds === undefined || Number.isNaN(milliseconds)) {\r\n throw new CacheConfigurationError(\r\n `Invalid TTL duration string: \"${input}\". Expected forms like \"1h\", \"30m\", \"7d\".`,\r\n );\r\n }\r\n\r\n return Math.floor(milliseconds / 1000);\r\n}\r\n\r\n/**\r\n * Convert an absolute `expiresAt` (Date or epoch milliseconds) into a\r\n * relative TTL in seconds.\r\n *\r\n * Throws {@link CacheConfigurationError} when the deadline is in the past —\r\n * the caller almost certainly has a bug (stale timestamp, wrong unit, etc.)\r\n * and silently storing an already-expired entry would hide it.\r\n *\r\n * @example\r\n * expiresAtToTtl(new Date(Date.now() + 60_000)); // ~60\r\n * expiresAtToTtl(Date.now() + 30 * 60 * 1000); // ~1800\r\n */\r\nexport function expiresAtToTtl(expiresAt: number | Date): number {\r\n const deadline = expiresAt instanceof Date ? expiresAt.getTime() : expiresAt;\r\n const relativeMs = deadline - Date.now();\r\n\r\n if (relativeMs <= 0) {\r\n throw new CacheConfigurationError(\r\n `\\`expiresAt\\` must be in the future; got ${new Date(deadline).toISOString()}.`,\r\n );\r\n }\r\n\r\n return Math.ceil(relativeMs / 1000);\r\n}\r\n\r\n/**\r\n * Coerce the polymorphic 3rd `set` argument into a uniform `CacheSetOptions`\r\n * shape. Lets callers (and `BaseCacheDriver.resolveSetOptions`) skip per-shape\r\n * branching.\r\n *\r\n * - `undefined` / `null` → `{}` (resolves to driver-level defaults later)\r\n * - `number` / `string` (positional TTL) → `{ ttl }`\r\n * - already an options object → returned as-is\r\n */\r\nexport function normalizeToOptions(\r\n input?: CacheTtl | CacheSetOptions,\r\n): CacheSetOptions {\r\n if (input === undefined || input === null) {\r\n return {};\r\n }\r\n\r\n if (typeof input === \"number\" || typeof input === \"string\") {\r\n return { ttl: input };\r\n }\r\n\r\n return input;\r\n}\r\n\r\n/**\r\n * Sibling of {@link normalizeToOptions} for the `remember()` call site, where\r\n * the polymorphic 2nd argument is `CacheTtl | RememberOptions` (no `expiresAt`,\r\n * no `onConflict`). Returns the same shape so callers can `{ ...opts, ... }`\r\n * without branching.\r\n *\r\n * @example\r\n * normalizeToRememberOptions(60); // { ttl: 60 }\r\n * normalizeToRememberOptions(\"1h\"); // { ttl: \"1h\" }\r\n * normalizeToRememberOptions({ ttl: \"1h\", tags: [\"x\"] }); // returned as-is\r\n */\r\nexport function normalizeToRememberOptions(\r\n input?: CacheTtl | RememberOptions,\r\n): RememberOptions {\r\n if (input === undefined || input === null) {\r\n return {};\r\n }\r\n\r\n if (typeof input === \"number\" || typeof input === \"string\") {\r\n return { ttl: input };\r\n }\r\n\r\n return input;\r\n}\r\n\r\n/**\r\n * Resolve the final TTL in seconds for a `set` call. Precedence:\r\n *\r\n * 1. Caller's `ttl` (number or duration string) wins.\r\n * 2. Otherwise, caller's `expiresAt` is converted to relative seconds.\r\n * 3. Otherwise, `fallback` is used (driver-level default — typically\r\n * `Infinity` when no default is configured, meaning \"never expires\").\r\n *\r\n * Throws {@link CacheConfigurationError} when `ttl` and `expiresAt` are\r\n * supplied together (mutually exclusive).\r\n */\r\nexport function resolveTtl(\r\n ttl: CacheTtl | undefined,\r\n expiresAt: number | Date | undefined,\r\n fallback: number,\r\n): number {\r\n if (ttl !== undefined && expiresAt !== undefined) {\r\n throw new CacheConfigurationError(\r\n \"Cache set options cannot specify both `ttl` and `expiresAt` — choose one.\",\r\n );\r\n }\r\n\r\n if (ttl !== undefined) {\r\n return parseTtl(ttl);\r\n }\r\n\r\n if (expiresAt !== undefined) {\r\n return expiresAtToTtl(expiresAt);\r\n }\r\n\r\n return fallback;\r\n}\r\n\r\n/**\r\n * Combine any number of tag lists into a single deduped array, dropping\r\n * `undefined`/empty entries. Returns `undefined` when no tags survive — lets\r\n * callers skip emitting empty `tags: []` into option payloads.\r\n *\r\n * Used by scoped-cache merging where scope tags + handle tags + per-call tags\r\n * must union additively without duplicates.\r\n *\r\n * @example\r\n * mergeTagSets([\"a\", \"b\"], [\"b\", \"c\"]); // [\"a\", \"b\", \"c\"]\r\n * mergeTagSets(undefined, [\"x\"]); // [\"x\"]\r\n * mergeTagSets(undefined, undefined); // undefined\r\n * mergeTagSets([], []); // undefined\r\n */\r\nexport function mergeTagSets(\r\n ...lists: (string[] | undefined)[]\r\n): string[] | undefined {\r\n const flat: string[] = [];\r\n\r\n for (const list of lists) {\r\n if (!list || list.length === 0) {\r\n continue;\r\n }\r\n\r\n flat.push(...list);\r\n }\r\n\r\n if (flat.length === 0) {\r\n return undefined;\r\n }\r\n\r\n return Array.from(new Set(flat));\r\n}\r\n\r\n/**\r\n * Add extra tags to any option-bag that already shapes `tags?: string[]`.\r\n * Pure — clones the input shape, never mutates. Tags are appended (caller\r\n * is responsible for de-duplication if needed; pair with {@link mergeTagSets}).\r\n *\r\n * @example\r\n * injectTags({ ttl: \"1h\" }, [\"unread\"]); // { ttl: \"1h\", tags: [\"unread\"] }\r\n * injectTags({ tags: [\"a\"] }, [\"b\"]); // { tags: [\"a\", \"b\"] }\r\n */\r\nexport function injectTags<T extends { tags?: string[] }>(\r\n options: T,\r\n extraTags: string[],\r\n): T {\r\n if (extraTags.length === 0) {\r\n return options;\r\n }\r\n\r\n return {\r\n ...options,\r\n tags: [...(options.tags ?? []), ...extraTags],\r\n };\r\n}\r\n\r\n/**\r\n * Cosine similarity between two equal-length numeric vectors.\r\n *\r\n * Returns a value in `[-1, 1]` where `1` means perfectly aligned, `0` means\r\n * orthogonal, and `-1` means opposing. For typical embedding spaces (where\r\n * vectors live in the positive cone) the practical range is `[0, 1]`.\r\n *\r\n * Throws {@link CacheConfigurationError} on dimension mismatch — fail loud at\r\n * the call site rather than silently returning a misleading score. A zero-norm\r\n * vector on either side returns `0` (no defined direction to compare).\r\n *\r\n * @example\r\n * cosineSimilarity([1, 0, 0], [1, 0, 0]); // 1\r\n * cosineSimilarity([1, 0, 0], [0, 1, 0]); // 0\r\n */\r\nexport function cosineSimilarity(a: number[], b: number[]): number {\r\n if (a.length !== b.length) {\r\n throw new CacheConfigurationError(\r\n `Vector dimension mismatch: got ${a.length} and ${b.length}.`,\r\n );\r\n }\r\n\r\n if (a.length === 0) {\r\n throw new CacheConfigurationError(\r\n \"Vector dimension mismatch: empty vector cannot be compared.\",\r\n );\r\n }\r\n\r\n let dot = 0;\r\n let normA = 0;\r\n let normB = 0;\r\n\r\n for (let i = 0; i < a.length; i++) {\r\n const x = a[i];\r\n const y = b[i];\r\n dot += x * y;\r\n normA += x * x;\r\n normB += y * y;\r\n }\r\n\r\n if (normA === 0 || normB === 0) {\r\n return 0;\r\n }\r\n\r\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\r\n}\r\n\r\n/**\r\n * Matches userinfo credentials embedded in a connection URL\r\n * (e.g. `redis://user:pass@host`). Node/Redis/Postgres client errors\r\n * frequently echo the target connection string — including the password —\r\n * back in `error.message` on connection failure.\r\n */\r\nconst CREDENTIALS_IN_URL = /(:\\/\\/)[^\\s/@]+:[^\\s/@]+@/g;\r\n\r\n/**\r\n * Strip embedded `user:pass@` credentials from a string before it reaches\r\n * any log sink.\r\n */\r\nexport function redactCredentials(message: string): string {\r\n return message.replace(CREDENTIALS_IN_URL, \"$1[REDACTED]@\");\r\n}\r\n\r\n/**\r\n * Reduce an unknown thrown value to a log-safe `{ message, code? }` shape.\r\n * Callers must never log the raw error object itself — it may carry the\r\n * connection URL (with password) in the top-level message, `cause`, or\r\n * driver-specific fields, all of which `console.log`/`util.inspect` would\r\n * still print in full.\r\n */\r\nexport function safeErrorInfo(error: unknown): { message: string; code?: string } {\r\n if (error instanceof Error) {\r\n const info: { message: string; code?: string } = {\r\n message: redactCredentials(error.message),\r\n };\r\n\r\n const code = (error as any).code;\r\n if (code) info.code = String(code);\r\n\r\n return info;\r\n }\r\n\r\n return { message: redactCredentials(String(error)) };\r\n}\r\n\r\nexport enum CACHE_FOR {\r\n /**\r\n * Cache for 30 Minutes (in seconds)\r\n */\r\n HALF_HOUR = 1800,\r\n /**\r\n * Cache for 1 Hour (in seconds)\r\n */\r\n ONE_HOUR = 3600,\r\n /**\r\n * Cache for 12 Hours (in seconds)\r\n */\r\n HALF_DAY = 43200,\r\n /**\r\n * Cache for 24 Hours (in seconds)\r\n */\r\n ONE_DAY = 86400,\r\n /**\r\n * Cache for 7 Days (in seconds)\r\n */\r\n ONE_WEEK = 604800,\r\n /**\r\n * Cache for 15 Days (in seconds)\r\n */\r\n HALF_MONTH = 1296000,\r\n /**\r\n * Cache for 30 Days (in seconds)\r\n */\r\n ONE_MONTH = 2592000,\r\n /**\r\n * Cache for 60 Days (in seconds)\r\n */\r\n TWO_MONTHS = 5184000,\r\n /**\r\n * Cache for 180 Days (in seconds)\r\n */\r\n SIX_MONTHS = 15768000,\r\n /**\r\n * Cache for 365 Days (in seconds)\r\n */\r\n ONE_YEAR = 31536000,\r\n}\r\n"],"mappings":"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"}
package/package.json CHANGED
@@ -5,9 +5,9 @@
5
5
  "environment": "server"
6
6
  },
7
7
  "dependencies": {
8
- "@warlock.js/fs": "5.2.3",
8
+ "@warlock.js/fs": "5.2.4",
9
9
  "@mongez/reinforcements": "^4.0.1",
10
- "@warlock.js/logger": "5.2.3",
10
+ "@warlock.js/logger": "5.2.4",
11
11
  "ms": "^2.1.3"
12
12
  },
13
13
  "peerDependencies": {
@@ -46,7 +46,7 @@
46
46
  ],
47
47
  "author": "hassanzohdy",
48
48
  "license": "MIT",
49
- "version": "5.2.3",
49
+ "version": "5.2.4",
50
50
  "main": "./cjs/index.cjs",
51
51
  "module": "./esm/index.mjs",
52
52
  "types": "./esm/index.d.mts",