@warlock.js/cache 5.5.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"memory-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/memory-cache-driver.ts"],"mappings":";;;;;cAgBa,iBAAA,SACH,eAAA,CAAgB,iBAAA,EAAmB,kBAAA,aAChC,WAAA,CAAY,iBAAA,EAAmB,kBAAA;;AAF5C;;EAOS,IAAA;EANiB;;;EAWjB,IAAA,EAAM,aAAA;EAAA;;;EAAA,UAKH,aAAA,EAAe,MAAA;IAGrB,GAAA;IACA,SAAA;EAAA;EA2FwB;;;EAAA,UApFlB,eAAA,GAAkB,MAAA,CAAO,OAAA;EAkLL;;;EAAA,UA7KpB,WAAA;EA+LuB;;;;;EAAA,UAxLvB,WAAA,EAAa,GAAA;EAuTZ;;;;EA7VA;;;EAoDJ,YAAA;EArDiB;;;EAiFX,eAAA,CAAgB,SAAA,WAAiB,OAAA;EAhFJ;;;EA2G7B,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAhGO;;;EA2JG,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAhJpB;;;;;EAAA,UAkLM,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;;;;EAkBpC,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EA9Ia;;;EAuKjC,KAAA,IAAK,OAAA;EA1IhB;;;EAAA,UA6JQ,gBAAA,CAAiB,GAAA,EAAK,QAAA,EAAU,SAAA,UAAmB,GAAA;EA3J1D;;;EAAA,UAqKO,WAAA,CAAY,GAAA;EA1GQ;;;EAAA,UA0HpB,qBAAA,CAAsB,GAAA;EAxFS;;;;;;;EAAA,UAsGzB,cAAA,IAAc,OAAA;EAxCpB;;;EAAA,UAiEA,YAAA;EAjEmD;;;;;;;;;;EAgFhD,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;EADhB;;;EAuCE,UAAA,IAAU,OAAA;AAAA"}
1
+ {"version":3,"file":"memory-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/memory-cache-driver.ts"],"mappings":";;;;;cAgBa,iBAAA,SACH,eAAA,CAAgB,iBAAA,EAAmB,kBAAA,aAChC,WAAA,CAAY,iBAAA,EAAmB,kBAAA;;AAF5C;;EAOS,IAAA;EANiB;;;EAWjB,IAAA,EAAM,aAAA;EAAA;;;EAAA,UAKH,aAAA,EAAe,MAAA;IAGrB,GAAA;IACA,SAAA;EAAA;EAiGwB;;;EAAA,UA1FlB,eAAA,GAAkB,MAAA,CAAO,OAAA;EAwLL;;;EAAA,UAnLpB,WAAA;EAqMuB;;;;;EAAA,UA9LvB,WAAA,EAAa,GAAA;EA6TZ;;;;EAnWA;;;EAoDJ,YAAA;EArDiB;;;EAuFX,eAAA,CAAgB,SAAA,WAAiB,OAAA;EAtFJ;;;EAiH7B,GAAA,CACX,GAAA,EAAK,QAAA,EACL,KAAA,OACA,YAAA,GAAe,QAAA,GAAW,eAAA,GACzB,OAAA;EAtGO;;;EAiKG,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAtJpB;;;;;EAAA,UAwLM,QAAA,CAAS,GAAA,EAAK,QAAA,GAAW,OAAA,CAAQ,SAAA;;;;EAkBpC,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EA9Ia;;;EAuKjC,KAAA,IAAK,OAAA;EA1IhB;;;EAAA,UA6JQ,gBAAA,CAAiB,GAAA,EAAK,QAAA,EAAU,SAAA,UAAmB,GAAA;EA3J1D;;;EAAA,UAqKO,WAAA,CAAY,GAAA;EA1GQ;;;EAAA,UA0HpB,qBAAA,CAAsB,GAAA;EAxFS;;;;;;;EAAA,UAsGzB,cAAA,IAAc,OAAA;EAxCpB;;;EAAA,UAiEA,YAAA;EAjEmD;;;;;;;;;;EAgFhD,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;EADhB;;;EAuCE,UAAA,IAAU,OAAA;AAAA"}
@@ -23,8 +23,8 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
23
23
  if (this.cleanupInterval) clearInterval(this.cleanupInterval);
24
24
  this.cleanupInterval = setInterval(async () => {
25
25
  const now = Date.now();
26
- for (const key in this.temporaryData) if (this.temporaryData[key].expiresAt <= now) {
27
- await this.remove(this.temporaryData[key].key);
26
+ for (const [key, entry] of Object.entries(this.temporaryData)) if (entry.expiresAt <= now) {
27
+ await this.remove(entry.key);
28
28
  delete this.temporaryData[key];
29
29
  this.log("expired", key);
30
30
  await this.emit("expired", { key });
@@ -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 // `Object.entries` rather than `for...in` + index. Two reasons, and the\r\n // second is not about types at all: under `noUncheckedIndexedAccess` the\r\n // index read is `T | undefined`, AND this loop DELETES from the object it\r\n // is iterating. `for...in` over a mutating object has\r\n // implementation-defined behaviour for keys not yet visited; entries\r\n // snapshots first, so the deletion below is unambiguous.\r\n for (const [key, entry] of Object.entries(this.temporaryData)) {\r\n if (entry.expiresAt <= now) {\r\n await this.remove(entry.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":"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
+ {"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;EAqJK;;;;EA5IjB,WAAA,CACL,KAAA,gDACA,IAAA,EAAM,cAAA;EAmKwB;;;AAAc;;EAjIvC,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;;;;;YA6BnC,aAAA,CAAc,MAAA,EAAQ,cAAA,EAAgB,UAAA;;YAYtC,cAAA,IAAkB,cAAA;;YAalB,aAAA,CAAc,MAAA,EAAQ,cAAA;AAAA"}
package/esm/metrics.mjs CHANGED
@@ -147,7 +147,7 @@ var CacheMetricsCollector = class {
147
147
  };
148
148
  const sorted = [...samples].sort((a, b) => a - b);
149
149
  const pick = (quantile) => {
150
- return sorted[Math.min(sorted.length - 1, Math.floor(quantile * sorted.length))];
150
+ return sorted[Math.min(sorted.length - 1, Math.floor(quantile * sorted.length))] ?? 0;
151
151
  };
152
152
  return {
153
153
  p50: pick(.5),
@@ -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 // `sorted` is non-empty (the `samples.length === 0` early return above)\n // and `index` is clamped to its last element, so this always hits — but\n // the clamp is arithmetic the compiler does not evaluate. `?? 0` states\n // the floor without asserting: a percentile of an empty set is 0, which\n // is exactly what the early return above already answers.\n return sorted[index] ?? 0;\n };\n\n return {\n p50: pick(0.5),\n p95: pick(0.95),\n p99: pick(0.99),\n samples: sorted.length,\n };\n }\n\n /**\n * Append a latency sample using circular-buffer semantics — overwrite the\n * oldest entry once the buffer is full instead of growing unbounded.\n */\n protected appendLatency(bucket: DriverCounters, durationMs: number): void {\n if (bucket.latencySamples.length < this.bufferSize) {\n bucket.latencySamples.push(durationMs);\n\n return;\n }\n\n bucket.latencySamples[bucket.latencyCursor] = durationMs;\n bucket.latencyCursor = (bucket.latencyCursor + 1) % this.bufferSize;\n }\n\n /** Build a fresh counter row with zeroed totals and an empty buffer. */\n protected createCounters(): DriverCounters {\n return {\n hits: 0,\n misses: 0,\n sets: 0,\n removed: 0,\n errors: 0,\n latencySamples: [],\n latencyCursor: 0,\n };\n }\n\n /** Reset an existing counter row in place. Used by `reset()` for the aggregate. */\n protected resetCounters(bucket: DriverCounters): void {\n bucket.hits = 0;\n bucket.misses = 0;\n bucket.sets = 0;\n bucket.removed = 0;\n bucket.errors = 0;\n bucket.latencySamples.length = 0;\n bucket.latencyCursor = 0;\n }\n}\n"],"mappings":"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"}
@@ -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,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"}
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;AAwDzD;iBAlHgB,YAAA,IACX,KAAK;;;AAiHuC;AAWjD;;;;;;iBAhGgB,UAAA;EAAuB,IAAA;AAAA,GACrC,OAAA,EAAS,CAAA,EACT,SAAA,aACC,CAAC;AA4GJ;;;;;;;;;;;;;;;AAAA,iBAlFgB,gBAAA,CAAiB,CAAA,YAAa,CAAW;;;;;iBAwDzC,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 CHANGED
@@ -166,6 +166,7 @@ function cosineSimilarity(a, b) {
166
166
  for (let i = 0; i < a.length; i++) {
167
167
  const x = a[i];
168
168
  const y = b[i];
169
+ if (x === void 0 || y === void 0) break;
169
170
  dot += x * y;
170
171
  normA += x * x;
171
172
  normB += y * y;
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\r\n if (x === undefined || y === undefined) {\r\n // Unreachable: both guards above establish `a.length === b.length` and\r\n // a non-zero length, so every index in range exists on both sides. Said\r\n // out loud rather than asserted with `as number`, because the two guards\r\n // that make it true are the kind of thing a later edit removes — and if\r\n // one ever did, an assertion would let `undefined` into the arithmetic\r\n // and return NaN as a similarity SCORE. A silently wrong ranking is\r\n // worse than a stopped one.\r\n break;\r\n }\r\n\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":";;;;;;;;AAQA,SAAgB,cACd,KACA,UAAsD,CAAC,GAC/C;CACR,IAAI,OAAO,QAAQ,UACjB,MAAM,KAAK,UAAU,GAAG;CAI1B,MAAM,IAAI,QAAQ,aAAa,EAAE,CAAC,CAAC,WAAW,SAAS,GAAG;CAE1D,MAAM,cACJ,OAAO,QAAQ,iBAAiB,aAAa,QAAQ,aAAa,IAAI,QAAQ;CAEhF,OAAO,MAAM,OAAO,cAAc,MAAM,aAAa,GAAG,IAAI,MAAM,MAAM,GAAG,GAAG,GAAG;AACnF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,SAAS,OAAyB;CAChD,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,QAAQ,GACV,MAAM,IAAI,wBAAwB,iCAAiC,MAAM,GAAG;EAG9E,OAAO;CACT;CAEA,IAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,IAChD,MAAM,IAAI,wBACR,wDAAwD,OAAO,MAAM,EACvE;CAGF,MAAM,eAAe,GAAG,KAAoB;CAE5C,IAAI,iBAAiB,UAAa,OAAO,MAAM,YAAY,GACzD,MAAM,IAAI,wBACR,iCAAiC,MAAM,0CACzC;CAGF,OAAO,KAAK,MAAM,eAAe,GAAI;AACvC;;;;;;;;;;;;;AAcA,SAAgB,eAAe,WAAkC;CAC/D,MAAM,WAAW,qBAAqB,OAAO,UAAU,QAAQ,IAAI;CACnE,MAAM,aAAa,WAAW,KAAK,IAAI;CAEvC,IAAI,cAAc,GAChB,MAAM,IAAI,wBACR,4CAA4C,IAAI,KAAK,QAAQ,CAAC,CAAC,YAAY,EAAE,EAC/E;CAGF,OAAO,KAAK,KAAK,aAAa,GAAI;AACpC;;;;;;;;;;AAWA,SAAgB,mBACd,OACiB;CACjB,IAAI,UAAU,UAAa,UAAU,MACnC,OAAO,CAAC;CAGV,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAChD,OAAO,EAAE,KAAK,MAAM;CAGtB,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,2BACd,OACiB;CACjB,IAAI,UAAU,UAAa,UAAU,MACnC,OAAO,CAAC;CAGV,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAChD,OAAO,EAAE,KAAK,MAAM;CAGtB,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,WACd,KACA,WACA,UACQ;CACR,IAAI,QAAQ,UAAa,cAAc,QACrC,MAAM,IAAI,wBACR,2EACF;CAGF,IAAI,QAAQ,QACV,OAAO,SAAS,GAAG;CAGrB,IAAI,cAAc,QAChB,OAAO,eAAe,SAAS;CAGjC,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,SAAgB,aACd,GAAG,OACmB;CACtB,MAAM,OAAiB,CAAC;CAExB,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,CAAC,QAAQ,KAAK,WAAW,GAC3B;EAGF,KAAK,KAAK,GAAG,IAAI;CACnB;CAEA,IAAI,KAAK,WAAW,GAClB;CAGF,OAAO,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC;AACjC;;;;;;;;;;AAWA,SAAgB,WACd,SACA,WACG;CACH,IAAI,UAAU,WAAW,GACvB,OAAO;CAGT,OAAO;EACL,GAAG;EACH,MAAM,CAAC,GAAI,QAAQ,QAAQ,CAAC,GAAI,GAAG,SAAS;CAC9C;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,GAAa,GAAqB;CACjE,IAAI,EAAE,WAAW,EAAE,QACjB,MAAM,IAAI,wBACR,kCAAkC,EAAE,OAAO,OAAO,EAAE,OAAO,EAC7D;CAGF,IAAI,EAAE,WAAW,GACf,MAAM,IAAI,wBACR,6DACF;CAGF,IAAI,MAAM;CACV,IAAI,QAAQ;CACZ,IAAI,QAAQ;CAEZ,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;EACjC,MAAM,IAAI,EAAE;EACZ,MAAM,IAAI,EAAE;EAEZ,IAAI,MAAM,UAAa,MAAM,QAQ3B;EAGF,OAAO,IAAI;EACX,SAAS,IAAI;EACb,SAAS,IAAI;CACf;CAEA,IAAI,UAAU,KAAK,UAAU,GAC3B,OAAO;CAGT,OAAO,OAAO,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK;AAClD;;;;;;;AAQA,MAAM,qBAAqB;;;;;AAM3B,SAAgB,kBAAkB,SAAyB;CACzD,OAAO,QAAQ,QAAQ,oBAAoB,eAAe;AAC5D;;;;;;;;AASA,SAAgB,cAAc,OAAoD;CAChF,IAAI,iBAAiB,OAAO;EAC1B,MAAM,OAA2C,EAC/C,SAAS,kBAAkB,MAAM,OAAO,EAC1C;EAEA,MAAM,OAAQ,MAAc;EAC5B,IAAI,MAAM,KAAK,OAAO,OAAO,IAAI;EAEjC,OAAO;CACT;CAEA,OAAO,EAAE,SAAS,kBAAkB,OAAO,KAAK,CAAC,EAAE;AACrD;AAEA,IAAY,YAAL;;;;CAIL;;;;CAIA;;;;CAIA;;;;CAIA;;;;CAIA;;;;CAIA;;;;CAIA;;;;CAIA;;;;CAIA;;;;CAIA;;AACF"}
package/package.json CHANGED
@@ -5,9 +5,9 @@
5
5
  "environment": "server"
6
6
  },
7
7
  "dependencies": {
8
- "@warlock.js/fs": "5.5.0",
8
+ "@warlock.js/fs": "5.7.0",
9
9
  "@mongez/reinforcements": "^4.0.1",
10
- "@warlock.js/logger": "5.5.0",
10
+ "@warlock.js/logger": "5.7.0",
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.5.0",
49
+ "version": "5.7.0",
50
50
  "main": "./cjs/index.cjs",
51
51
  "module": "./esm/index.mjs",
52
52
  "types": "./esm/index.d.mts",