@warlock.js/cache 5.15.0 → 5.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/cjs/index.cjs +48 -23
- package/cjs/index.cjs.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +6 -1
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +9 -4
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.mjs +1 -1
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-cache-driver.mjs +9 -3
- package/esm/drivers/memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +1 -1
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +3 -3
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +1 -1
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +15 -2
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +24 -7
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +1 -2
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/package.json +3 -3
|
@@ -118,13 +118,18 @@ declare abstract class BaseCacheDriver<ClientType, Options extends Record<string
|
|
|
118
118
|
* Used by `similar()` to narrow the candidate pool before similarity ranking.
|
|
119
119
|
*
|
|
120
120
|
* Returns `null` when no tags are passed (callers should treat that as "no filter").
|
|
121
|
+
* Keys come back parsed (prefixed) — the index stores them un-prefixed, but
|
|
122
|
+
* callers match them against their own parsed storage keys.
|
|
121
123
|
*/
|
|
122
124
|
protected getKeysForTags(tags: string[] | undefined): Promise<Set<string> | null>;
|
|
123
125
|
/**
|
|
124
126
|
* Apply tag relationships after a successful write. Called by drivers once
|
|
125
127
|
* the value is in storage.
|
|
128
|
+
*
|
|
129
|
+
* Pass the caller's key, not the parsed one: the tag index stores keys
|
|
130
|
+
* un-prefixed so `invalidate()` can delete them through `remove(key)`.
|
|
126
131
|
*/
|
|
127
|
-
protected applyTags(
|
|
132
|
+
protected applyTags(key: CacheKey, tags: string[]): Promise<void>;
|
|
128
133
|
/**
|
|
129
134
|
* {@inheritdoc}
|
|
130
135
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/base-cache-driver.ts"],"mappings":";;;;;AAsCA;;;;KAAY,oBAAA;EAaV;;;;;;AAiBO;AAwBT;EA7CE,GAAA;EA6CmC;;;EAzCnC,IAAA;EAgDyB;;;EA5CzB,UAAA,EAAY,mBAAmB;EAuFD;;;;;EAjF9B,MAAA;EAsGiB;;;;;;EA/FjB,OAAA;AAAA;AAAA,uBAwBoB,eAAA,6BAEJ,MAAA,0BACL,WAAA,CAAY,UAAA,EAAY,OAAA;EAwGhC;;;EAAA,UApGO,YAAA,EAAe,UAAA;EA2IG;;;EAAA,UAtIlB,SAAA;EAmJP;;;EAAA,IA9IQ,MAAA,IAgBe,UAAA;
|
|
1
|
+
{"version":3,"file":"base-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/base-cache-driver.ts"],"mappings":"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"}
|
|
@@ -131,6 +131,8 @@ var BaseCacheDriver = class {
|
|
|
131
131
|
* Used by `similar()` to narrow the candidate pool before similarity ranking.
|
|
132
132
|
*
|
|
133
133
|
* Returns `null` when no tags are passed (callers should treat that as "no filter").
|
|
134
|
+
* Keys come back parsed (prefixed) — the index stores them un-prefixed, but
|
|
135
|
+
* callers match them against their own parsed storage keys.
|
|
134
136
|
*/
|
|
135
137
|
async getKeysForTags(tags) {
|
|
136
138
|
if (!tags || tags.length === 0) return null;
|
|
@@ -138,17 +140,20 @@ var BaseCacheDriver = class {
|
|
|
138
140
|
for (const tag of tags) {
|
|
139
141
|
const tagKey = `cache:tags:${tag}`;
|
|
140
142
|
const keys = await this.get(tagKey) || [];
|
|
141
|
-
for (const k of keys) allKeys.add(k);
|
|
143
|
+
for (const k of keys) allKeys.add(this.parseKey(k));
|
|
142
144
|
}
|
|
143
145
|
return allKeys;
|
|
144
146
|
}
|
|
145
147
|
/**
|
|
146
148
|
* Apply tag relationships after a successful write. Called by drivers once
|
|
147
149
|
* the value is in storage.
|
|
150
|
+
*
|
|
151
|
+
* Pass the caller's key, not the parsed one: the tag index stores keys
|
|
152
|
+
* un-prefixed so `invalidate()` can delete them through `remove(key)`.
|
|
148
153
|
*/
|
|
149
|
-
async applyTags(
|
|
154
|
+
async applyTags(key, tags) {
|
|
150
155
|
if (tags.length === 0) return;
|
|
151
|
-
await this.tags(tags).storeTagRelationship(
|
|
156
|
+
await this.tags(tags).storeTagRelationship(key);
|
|
152
157
|
}
|
|
153
158
|
/**
|
|
154
159
|
* {@inheritdoc}
|
|
@@ -339,7 +344,7 @@ var BaseCacheDriver = class {
|
|
|
339
344
|
if (!this.shouldLog) return;
|
|
340
345
|
if (typeof key === "string") key = key.replace(/\//g, ".");
|
|
341
346
|
if (operation === "connectionFailed") log.fatal(`cache.${this.name}`, operation, key);
|
|
342
|
-
if (operation == "notFound" || operation == "expired") return log.
|
|
347
|
+
if (operation == "notFound" || operation == "expired") return log.info("cache." + this.name, operation, (key ? key + " " : "") + messages[operation]);
|
|
343
348
|
if (operation.endsWith("ed")) return log.success("cache." + this.name, operation, (key ? key + " " : "") + messages[operation]);
|
|
344
349
|
log.info("cache." + this.name, operation, (key ? key + " " : "") + messages[operation]);
|
|
345
350
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/base-cache-driver.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\r\nimport { MemoryCacheList } from \"../list/memory-cache-list\";\r\nimport { TaggedCache } from \"../tagged-cache\";\r\nimport type {\r\n CacheConflictPolicy,\r\n CacheData,\r\n CacheDriver,\r\n CacheEventData,\r\n CacheEventHandler,\r\n CacheEventType,\r\n CacheKey,\r\n CacheListAccessor,\r\n CacheOperationType,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheSwrOptions,\r\n CacheTtl,\r\n LockOptions,\r\n LockOutcome,\r\n RememberOptions,\r\n} from \"../types\";\r\nimport { CacheUnsupportedError } from \"../types\";\r\nimport {\r\n normalizeToOptions,\r\n parseCacheKey,\r\n parseTtl,\r\n resolveTtl,\r\n safeErrorInfo,\r\n} from \"../utils\";\r\n\r\n/**\r\n * Normalized form of the 3rd `set` argument.\r\n *\r\n * All drivers operate on this shape internally regardless of whether the caller\r\n * passed a positional TTL, a duration string, or a rich options object.\r\n */\r\nexport type NormalizedSetOptions = {\r\n /**\r\n * Final TTL in seconds — already merged with the driver-level default\r\n * (`this.options.ttl`). `Infinity` means \"no expiration\".\r\n *\r\n * Always populated. Drivers do NOT need to fall back to `this.ttl`\r\n * themselves — `resolveSetOptions` does the merge centrally so that\r\n * every driver respects the configured default without ceremony.\r\n */\r\n ttl: number;\r\n /**\r\n * Inline tag list, or undefined when none were provided.\r\n */\r\n tags?: string[];\r\n /**\r\n * Conflict policy. Defaults to `\"upsert\"`.\r\n */\r\n onConflict: CacheConflictPolicy;\r\n /**\r\n * Optional embedding vector for similarity retrieval. Drivers that do not\r\n * support similarity must throw {@link CacheUnsupportedError} when this is\r\n * present (rather than silently dropping it).\r\n */\r\n vector?: number[];\r\n /**\r\n * Optional freshness deadline as a millisecond timestamp. Set by `swr()`\r\n * to mark when the entry stops being \"fresh\" and becomes\r\n * \"stale-but-revalidatable.\" Drivers route this through\r\n * `prepareDataForStorage` so it persists in the wrapper.\r\n */\r\n staleAt?: number;\r\n};\r\n\r\nconst messages = {\r\n clearing: \"Clearing namespace\",\r\n cleared: \"Namespace cleared\",\r\n fetching: \"Fetching key\",\r\n fetched: \"Key fetched\",\r\n caching: \"Caching key\",\r\n cached: \"Key cached\",\r\n flushing: \"Flushing cache\",\r\n flushed: \"Cache flushed\",\r\n removing: \"Removing key\",\r\n removed: \"Key removed\",\r\n expired: \"Key expired\",\r\n notFound: \"Key not found\",\r\n connecting: \"Connecting to the cache engine.\",\r\n connected: \"Connected to the cache engine.\",\r\n disconnecting: \"Disconnecting from the cache engine.\",\r\n disconnected: \"Disconnected from the cache engine.\",\r\n connectionFailed: \"Failed to connect to the cache engine.\",\r\n error: \"Error occurred\",\r\n};\r\n\r\nexport abstract class BaseCacheDriver<\r\n ClientType,\r\n Options extends Record<string, any>,\r\n> implements CacheDriver<ClientType, Options> {\r\n /**\r\n * CLient driver\r\n */\r\n protected clientDriver!: ClientType;\r\n\r\n /**\r\n * Determine whether to log or not\r\n */\r\n protected shouldLog: boolean = true;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get client() {\r\n return (this.clientDriver || this) as unknown as ClientType;\r\n }\r\n\r\n /**\r\n * Set logging state\r\n */\r\n public setLoggingState(shouldLog: boolean) {\r\n this.shouldLog = shouldLog;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set client driver\r\n */\r\n public set client(client: ClientType) {\r\n this.clientDriver = client;\r\n }\r\n\r\n /**\r\n * Get the cache driver name\r\n */\r\n public abstract name: string;\r\n\r\n /**\r\n * Options list\r\n */\r\n public options!: Options;\r\n\r\n /**\r\n * Event listeners storage\r\n */\r\n protected eventListeners: Map<CacheEventType, Set<CacheEventHandler>> =\r\n new Map();\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public parseKey(key: CacheKey) {\r\n return parseCacheKey(key, this.options);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: Options) {\r\n this.options = options || {};\r\n return this;\r\n }\r\n\r\n /**\r\n * Register an event listener\r\n */\r\n public on(event: CacheEventType, handler: CacheEventHandler): this {\r\n if (!this.eventListeners.has(event)) {\r\n this.eventListeners.set(event, new Set());\r\n }\r\n this.eventListeners.get(event)!.add(handler);\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove an event listener\r\n */\r\n public off(event: CacheEventType, handler: CacheEventHandler): this {\r\n const handlers = this.eventListeners.get(event);\r\n if (handlers) {\r\n handlers.delete(handler);\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Register a one-time event listener\r\n */\r\n public once(event: CacheEventType, handler: CacheEventHandler): this {\r\n const onceHandler: CacheEventHandler = async (data) => {\r\n await handler(data);\r\n this.off(event, onceHandler);\r\n };\r\n return this.on(event, onceHandler);\r\n }\r\n\r\n /**\r\n * Emit an event to all registered listeners\r\n */\r\n protected async emit(\r\n event: CacheEventType,\r\n data: Partial<CacheEventData> = {},\r\n ): Promise<void> {\r\n const handlers = this.eventListeners.get(event);\r\n if (!handlers || handlers.size === 0) return;\r\n\r\n const eventData: CacheEventData = {\r\n driver: this.name,\r\n ...data,\r\n };\r\n\r\n // Execute all handlers\r\n const promises: Promise<void>[] = [];\r\n for (const handler of handlers) {\r\n try {\r\n const result = handler(eventData);\r\n if (result instanceof Promise) {\r\n promises.push(result);\r\n }\r\n } catch (error) {\r\n this.logError(`Error in event handler for '${event}'`, error);\r\n }\r\n }\r\n\r\n // Wait for all async handlers\r\n if (promises.length > 0) {\r\n await Promise.allSettled(promises);\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract removeNamespace(namespace: string): Promise<any>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any>;\r\n\r\n /**\r\n * Normalize the 3rd argument of a `set` call into a single shape every driver\r\n * can act on. Handles TTL parsing (number | string | Infinity), `expiresAt` →\r\n * relative TTL conversion, and mutual-exclusion validation.\r\n *\r\n * @throws {CacheConfigurationError} when `ttl` and `expiresAt` are passed together\r\n * or an unparseable duration string is supplied.\r\n */\r\n protected resolveSetOptions(\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): NormalizedSetOptions {\r\n const options = normalizeToOptions(ttlOrOptions);\r\n\r\n return {\r\n ttl: resolveTtl(options.ttl, options.expiresAt, this.ttl),\r\n tags: options.tags,\r\n onConflict: options.onConflict ?? \"upsert\",\r\n vector: options.vector,\r\n staleAt: options.staleAt,\r\n };\r\n }\r\n\r\n /**\r\n * Resolve the union of cache keys associated with any of the given tags.\r\n * Used by `similar()` to narrow the candidate pool before similarity ranking.\r\n *\r\n * Returns `null` when no tags are passed (callers should treat that as \"no filter\").\r\n */\r\n protected async getKeysForTags(\r\n tags: string[] | undefined,\r\n ): Promise<Set<string> | null> {\r\n if (!tags || tags.length === 0) {\r\n return null;\r\n }\r\n\r\n const allKeys = new Set<string>();\r\n for (const tag of tags) {\r\n const tagKey = `cache:tags:${tag}`;\r\n const keys = ((await this.get(tagKey)) as string[] | null) || [];\r\n for (const k of keys) {\r\n allKeys.add(k);\r\n }\r\n }\r\n\r\n return allKeys;\r\n }\r\n\r\n /**\r\n * Apply tag relationships after a successful write. Called by drivers once\r\n * the value is in storage.\r\n */\r\n protected async applyTags(parsedKey: string, tags: string[]): Promise<void> {\r\n if (tags.length === 0) {\r\n return;\r\n }\r\n\r\n const tagged = this.tags(tags);\r\n await (tagged as TaggedCache).storeTagRelationship(parsedKey);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract get(key: CacheKey): Promise<any>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract remove(key: CacheKey): Promise<void>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract flush(): Promise<void>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n const value = await this.get(key);\r\n // Event is emitted by get() method\r\n return value !== null;\r\n }\r\n\r\n /**\r\n * Lock storage for preventing cache stampede\r\n */\r\n protected locks: Map<string, Promise<any>> = new Map();\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<any>,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n // The options-form lets callers forward tags / driver-override through to\r\n // the cache-miss write. Normalize both shapes into a single CacheSetOptions\r\n // blob so there's one path from here on.\r\n const setOptions = this.normalizeRememberOptions(ttlOrOptions);\r\n\r\n const cachedValue = await this.get(key);\r\n if (cachedValue) {\r\n return cachedValue;\r\n }\r\n\r\n const existingLock = this.locks.get(parsedKey);\r\n if (existingLock) {\r\n return existingLock;\r\n }\r\n\r\n const promise = callback()\r\n .then(async (result) => {\r\n await this.set(key, result, setOptions);\r\n this.locks.delete(parsedKey);\r\n return result;\r\n })\r\n .catch((err) => {\r\n this.locks.delete(parsedKey);\r\n throw err;\r\n });\r\n\r\n this.locks.set(parsedKey, promise);\r\n return promise;\r\n }\r\n\r\n /**\r\n * Resolve the TTL-or-options arg of `remember` into a `CacheSetOptions` object\r\n * that can be passed straight to `set()`. Keeps the implementation unbranched.\r\n */\r\n protected normalizeRememberOptions(\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n ): CacheSetOptions {\r\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\r\n return { ttl: ttlOrOptions };\r\n }\r\n\r\n return {\r\n ttl: ttlOrOptions.ttl,\r\n tags: ttlOrOptions.tags,\r\n };\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation: read raw entry, branch on freshness/staleness,\r\n * trigger background refresh in the stale window, fall through to\r\n * `callback` on miss/expiry. Concurrent stale-window callers share a\r\n * single in-flight refresh via {@link locks}.\r\n *\r\n * Drivers without a real {@link getEntry} override degrade gracefully —\r\n * the synthetic entry has no `staleAt`, which the freshness check treats\r\n * as \"always fresh,\" so SWR behaves like a TTL-only cached read on those\r\n * drivers (no background refresh, but no double-fetch either).\r\n */\r\n public async swr<T = any>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T> {\r\n const parsedKey = this.parseKey(key);\r\n const freshSeconds = parseTtl(options.freshTtl);\r\n const staleSeconds = parseTtl(options.staleTtl);\r\n\r\n if (staleSeconds <= freshSeconds) {\r\n throw new Error(\r\n `cache.swr: 'staleTtl' (${staleSeconds}s) must be greater than 'freshTtl' (${freshSeconds}s).`,\r\n );\r\n }\r\n\r\n const entry = await this.getEntry(key);\r\n const now = Date.now();\r\n\r\n const isExpired = entry?.expiresAt !== undefined && entry.expiresAt <= now;\r\n\r\n if (!entry || isExpired) {\r\n return this.swrFetchAndStore<T>(\r\n key,\r\n options,\r\n callback,\r\n freshSeconds,\r\n staleSeconds,\r\n );\r\n }\r\n\r\n const isFresh = entry.staleAt === undefined || entry.staleAt > now;\r\n\r\n if (isFresh) {\r\n return entry.data as T;\r\n }\r\n\r\n this.scheduleSwrRefresh<T>(\r\n parsedKey,\r\n key,\r\n options,\r\n callback,\r\n freshSeconds,\r\n staleSeconds,\r\n );\r\n\r\n return entry.data as T;\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper for a key, including any\r\n * `expiresAt` / `staleAt` metadata. Default implementation falls back to\r\n * `get()` and synthesizes a metadata-less wrapper — drivers that store\r\n * the wrapper directly (memory, lru, file, redis, pg, mock) override\r\n * this to return real metadata so SWR can branch on freshness.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const value = await this.get(key);\r\n\r\n if (value === null) {\r\n return null;\r\n }\r\n\r\n return { data: value };\r\n }\r\n\r\n /**\r\n * Remaining lifetime of an existing entry, in seconds — used by TTL-preserving\r\n * writes such as `update()` / `merge()` when the caller passes no explicit\r\n * `ttl`.\r\n *\r\n * - `Infinity` — the entry exists with no expiry (preserve \"never expires\").\r\n * - positive number — seconds left before the entry expires.\r\n * - `undefined` — the key is missing or already past its deadline; the caller\r\n * should fall back to the driver default TTL.\r\n *\r\n * Default reads `expiresAt` from {@link getEntry}, which the metadata-aware\r\n * drivers (memory, lru, mock, pg) populate. Drivers that track TTL natively\r\n * and don't carry `expiresAt` in their payload (Redis) override this.\r\n */\r\n protected async getRemainingTtl(key: CacheKey): Promise<number | undefined> {\r\n const entry = await this.getEntry(key);\r\n\r\n if (!entry) {\r\n return undefined;\r\n }\r\n\r\n if (!entry.expiresAt || entry.expiresAt === Infinity) {\r\n return Infinity;\r\n }\r\n\r\n const remainingSeconds = Math.ceil((entry.expiresAt - Date.now()) / 1000);\r\n\r\n return remainingSeconds > 0 ? remainingSeconds : undefined;\r\n }\r\n\r\n /**\r\n * Block-and-fetch path of `swr()`: invoked on miss or past-`staleTtl`\r\n * expiry. Writes through `set()` with the SWR options translated into\r\n * standard `CacheSetOptions` (ttl = staleTtl, staleAt = now + freshTtl).\r\n */\r\n protected async swrFetchAndStore<T>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n freshSeconds: number,\r\n staleSeconds: number,\r\n ): Promise<T> {\r\n const result = await callback();\r\n\r\n await this.set(key, result, {\r\n ttl: staleSeconds,\r\n staleAt: Date.now() + freshSeconds * 1000,\r\n tags: options.tags,\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Stale-window background refresh. Registers a single in-flight promise\r\n * per parsed key so concurrent SWR callers share one refresh. Failed\r\n * refreshes preserve the stale entry, log via `logError`, and emit on\r\n * `error` — the stale-returning caller never sees the failure.\r\n */\r\n protected scheduleSwrRefresh<T>(\r\n parsedKey: string,\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n freshSeconds: number,\r\n staleSeconds: number,\r\n ): void {\r\n if (this.locks.has(parsedKey)) {\r\n return;\r\n }\r\n\r\n let refresh!: Promise<void>;\r\n refresh = (async () => {\r\n try {\r\n const result = await callback();\r\n\r\n await this.set(key, result, {\r\n ttl: staleSeconds,\r\n staleAt: Date.now() + freshSeconds * 1000,\r\n tags: options.tags,\r\n });\r\n } catch (error) {\r\n this.logError(`SWR background refresh failed for ${parsedKey}`, error);\r\n await this.emit(\"error\", { key: parsedKey, error });\r\n } finally {\r\n if (this.locks.get(parsedKey) === refresh) {\r\n this.locks.delete(parsedKey);\r\n }\r\n }\r\n })();\r\n\r\n this.locks.set(parsedKey, refresh);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n const value = await this.get(key);\r\n if (value !== null) {\r\n await this.remove(key);\r\n }\r\n // Events are emitted by get() and remove() methods\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n // Event is emitted by set() method\r\n return this.set(key, value, Infinity);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\r\n const current = (await this.get(key)) || 0;\r\n\r\n if (typeof current !== \"number\") {\r\n throw new Error(\r\n `Cannot increment non-numeric value for key: ${this.parseKey(key)}`,\r\n );\r\n }\r\n\r\n const newValue = current + value;\r\n await this.set(key, newValue);\r\n return newValue;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\r\n return this.increment(key, -value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async many(keys: CacheKey[]): Promise<any[]> {\r\n return Promise.all(keys.map((key) => this.get(key)));\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async setMany(\r\n items: Record<string, any>,\r\n ttl?: number,\r\n ): Promise<void> {\r\n await Promise.all(\r\n Object.entries(items).map(([key, value]) => this.set(key, value, ttl)),\r\n );\r\n }\r\n\r\n /**\r\n * Log the operation\r\n */\r\n protected log(operation: CacheOperationType, key?: string | any) {\r\n if (!this.shouldLog) return;\r\n\r\n if (typeof key === \"string\") {\r\n // this will be likely used with file cache driver as it will convert the dot to slash\r\n // to make it consistent and not to confuse developers we will output the key by making sure it's a dot\r\n key = key.replace(/\\//g, \".\");\r\n }\r\n\r\n if (operation === \"connectionFailed\") {\r\n log.fatal(`cache.${this.name}`, operation, key);\r\n }\r\n\r\n if (operation == \"notFound\" || operation == \"expired\") {\r\n return log.warn(\r\n \"cache.\" + this.name,\r\n operation,\r\n (key ? key + \" \" : \"\") + messages[operation],\r\n );\r\n }\r\n\r\n if (operation.endsWith(\"ed\")) {\r\n return log.success(\r\n \"cache.\" + this.name,\r\n operation,\r\n (key ? key + \" \" : \"\") + messages[operation],\r\n );\r\n }\r\n\r\n log.info(\r\n \"cache.\" + this.name,\r\n operation,\r\n (key ? key + \" \" : \"\") + messages[operation],\r\n );\r\n }\r\n\r\n /**\r\n * Log error message\r\n *\r\n * Never logs the raw `error` object — driver connection errors (Redis,\r\n * Postgres, ...) can carry the connection string, including the password,\r\n * in the message/cause. Only a redacted `{ message, code }` shape is\r\n * passed to the structured logger.\r\n */\r\n protected logError(message: string, error?: any) {\r\n if (error) {\r\n log.error(\"cache.\" + this.name, \"error\", message, safeErrorInfo(error));\r\n } else {\r\n log.error(\"cache.\" + this.name, \"error\", message);\r\n }\r\n }\r\n\r\n /**\r\n * Get the default TTL in seconds. Parses human-readable strings (`\"1h\"`, `\"30m\"`)\r\n * from driver options if present; falls back to `Infinity` when no default is set.\r\n */\r\n public get ttl() {\r\n if (this.options.ttl === undefined) {\r\n return Infinity;\r\n }\r\n\r\n return parseTtl(this.options.ttl);\r\n }\r\n\r\n /**\r\n * Get time to live value in milliseconds\r\n */\r\n public getExpiresAt(ttl: number = this.ttl) {\r\n if (ttl) {\r\n return new Date().getTime() + ttl * 1000;\r\n }\r\n }\r\n\r\n /**\r\n * Wrap a value with TTL and optional freshness metadata for backend\r\n * storage. `staleAt` persists alongside `expiresAt` when supplied — used\r\n * by the SWR flow to mark when the entry stops being fresh.\r\n */\r\n protected prepareDataForStorage(data: any, ttl?: number, staleAt?: number) {\r\n const preparedData: CacheData = {\r\n data,\r\n };\r\n\r\n if (ttl) {\r\n preparedData.ttl = ttl;\r\n preparedData.expiresAt = this.getExpiresAt(ttl);\r\n }\r\n\r\n if (staleAt !== undefined) {\r\n preparedData.staleAt = staleAt;\r\n }\r\n\r\n return preparedData;\r\n }\r\n\r\n /**\r\n * Parse fetched data from cache\r\n */\r\n protected async parseCachedData(key: string, data: CacheData) {\r\n this.log(\"fetched\", key);\r\n\r\n if (data.expiresAt && data.expiresAt < Date.now()) {\r\n this.remove(key);\r\n return null;\r\n }\r\n\r\n const value = data.data;\r\n\r\n // Skip cloning for primitives (immutable types)\r\n if (value === null || value === undefined) {\r\n return value;\r\n }\r\n\r\n const type = typeof value;\r\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\r\n return value;\r\n }\r\n\r\n // Deep clone objects/arrays to prevent cache mutation\r\n try {\r\n return structuredClone(value);\r\n } catch (error) {\r\n this.logError(\r\n `Failed to clone cached value for ${key}, typeof value: ${typeof value}`,\r\n error,\r\n );\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n this.log(\"connecting\");\r\n this.log(\"connected\");\r\n await this.emit(\"connected\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async disconnect() {\r\n this.log(\"disconnected\");\r\n await this.emit(\"disconnected\");\r\n }\r\n\r\n /**\r\n * Create a tagged cache instance for the given tags\r\n */\r\n public tags(tags: string[]): any {\r\n return new TaggedCache(tags, this);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation: read → transform → write under a per-key in-process\r\n * lock. Drivers that can offer stronger semantics (Redis via `WATCH`/`MULTI`)\r\n * should override.\r\n */\r\n public async update<T = any>(\r\n key: CacheKey,\r\n fn: (current: T | null) => T | null | Promise<T | null>,\r\n options: { ttl?: CacheTtl } = {},\r\n ): Promise<T | null> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n // Chain each update onto the previous one for the same key so concurrent\r\n // callers are serialized end-to-end, not merely awakened together when an\r\n // earlier lock resolves.\r\n const previous = this.locks.get(parsedKey) ?? Promise.resolve();\r\n\r\n const next = previous\r\n .catch(() => undefined)\r\n .then(async () => {\r\n const current = (await this.get(key)) as T | null;\r\n const result = await fn(current);\r\n\r\n if (result === null) {\r\n await this.remove(key);\r\n return null;\r\n }\r\n\r\n if (options.ttl !== undefined) {\r\n await this.set(key, result, { ttl: options.ttl });\r\n\r\n return result;\r\n }\r\n\r\n // No explicit TTL → preserve the existing entry's remaining lifetime\r\n // rather than resetting it to the driver default.\r\n const remainingTtl = await this.getRemainingTtl(key);\r\n\r\n if (remainingTtl !== undefined) {\r\n await this.set(key, result, { ttl: remainingTtl });\r\n } else {\r\n await this.set(key, result);\r\n }\r\n\r\n return result;\r\n });\r\n\r\n this.locks.set(parsedKey, next);\r\n\r\n // Clean up the slot once this link finishes — but only if nobody chained\r\n // a follow-up onto it in the meantime.\r\n next.finally(() => {\r\n if (this.locks.get(parsedKey) === next) {\r\n this.locks.delete(parsedKey);\r\n }\r\n });\r\n\r\n return next;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async merge<T extends Record<string, any> = Record<string, any>>(\r\n key: CacheKey,\r\n partial: Partial<T>,\r\n options: { ttl?: CacheTtl } = {},\r\n ): Promise<T> {\r\n const result = await this.update<T>(\r\n key,\r\n (current) => {\r\n const base = (current ?? {}) as T;\r\n return { ...base, ...partial } as T;\r\n },\r\n options,\r\n );\r\n\r\n return result as T;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation: read-mutate-write array backed by the underlying\r\n * cache entry. Concrete drivers (e.g. Redis) override with native commands.\r\n */\r\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\r\n return new MemoryCacheList<T>(this, key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Built on top of `set({ onConflict: \"create\" })` — Redis-native `SET … NX EX`\r\n * under the hood on Redis, emulated via key-existence check on other drivers.\r\n * The lock value is the resolved `owner` (defaults to `pid.<process.pid>`).\r\n *\r\n * Always releases in `finally`, even if `fn` throws — the thrown error\r\n * propagates to the caller unchanged.\r\n */\r\n public async lock<T>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\r\n fn: () => Promise<T>,\r\n ): Promise<LockOutcome<T>> {\r\n const { ttl, owner } = this.normalizeLockOptions(ttlOrOptions);\r\n const lockOwner = owner ?? `pid.${process.pid}`;\r\n\r\n const setResult = (await this.set(key, lockOwner, {\r\n onConflict: \"create\",\r\n ttl,\r\n })) as CacheSetResult | unknown;\r\n\r\n // `onConflict` drivers return CacheSetResult. Drivers that no-op on set\r\n // (e.g. the null driver) may return anything else — treat that as\r\n // \"acquired\" since there's nothing to collide with.\r\n const wasSet =\r\n typeof setResult === \"object\" &&\r\n setResult !== null &&\r\n \"wasSet\" in setResult\r\n ? (setResult as CacheSetResult).wasSet\r\n : true;\r\n\r\n if (!wasSet) {\r\n return { acquired: false };\r\n }\r\n\r\n try {\r\n const value = await fn();\r\n return { acquired: true, value };\r\n } finally {\r\n await this.remove(key);\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation throws {@link CacheUnsupportedError}. Drivers that\r\n * support similarity retrieval (memory family, `pg`, `redis` w/ RediSearch)\r\n * override this with a real impl.\r\n */\r\n public async similar<T = any>(\r\n _vector: number[],\r\n _options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n throw new CacheUnsupportedError(\r\n `'${this.name}' driver does not support similarity retrieval. Use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).`,\r\n );\r\n }\r\n\r\n /**\r\n * Resolve the TTL-or-options arg of `lock` into a uniform shape.\r\n */\r\n protected normalizeLockOptions(\r\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\r\n ): {\r\n ttl: CacheTtl;\r\n owner?: string;\r\n } {\r\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\r\n return { ttl: ttlOrOptions };\r\n }\r\n\r\n return { ttl: ttlOrOptions.ttl, owner: ttlOrOptions.owner };\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/base-cache-driver.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\r\nimport { MemoryCacheList } from \"../list/memory-cache-list\";\r\nimport { TaggedCache } from \"../tagged-cache\";\r\nimport type {\r\n CacheConflictPolicy,\r\n CacheData,\r\n CacheDriver,\r\n CacheEventData,\r\n CacheEventHandler,\r\n CacheEventType,\r\n CacheKey,\r\n CacheListAccessor,\r\n CacheOperationType,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheSwrOptions,\r\n CacheTtl,\r\n LockOptions,\r\n LockOutcome,\r\n RememberOptions,\r\n} from \"../types\";\r\nimport { CacheUnsupportedError } from \"../types\";\r\nimport {\r\n normalizeToOptions,\r\n parseCacheKey,\r\n parseTtl,\r\n resolveTtl,\r\n safeErrorInfo,\r\n} from \"../utils\";\r\n\r\n/**\r\n * Normalized form of the 3rd `set` argument.\r\n *\r\n * All drivers operate on this shape internally regardless of whether the caller\r\n * passed a positional TTL, a duration string, or a rich options object.\r\n */\r\nexport type NormalizedSetOptions = {\r\n /**\r\n * Final TTL in seconds — already merged with the driver-level default\r\n * (`this.options.ttl`). `Infinity` means \"no expiration\".\r\n *\r\n * Always populated. Drivers do NOT need to fall back to `this.ttl`\r\n * themselves — `resolveSetOptions` does the merge centrally so that\r\n * every driver respects the configured default without ceremony.\r\n */\r\n ttl: number;\r\n /**\r\n * Inline tag list, or undefined when none were provided.\r\n */\r\n tags?: string[];\r\n /**\r\n * Conflict policy. Defaults to `\"upsert\"`.\r\n */\r\n onConflict: CacheConflictPolicy;\r\n /**\r\n * Optional embedding vector for similarity retrieval. Drivers that do not\r\n * support similarity must throw {@link CacheUnsupportedError} when this is\r\n * present (rather than silently dropping it).\r\n */\r\n vector?: number[];\r\n /**\r\n * Optional freshness deadline as a millisecond timestamp. Set by `swr()`\r\n * to mark when the entry stops being \"fresh\" and becomes\r\n * \"stale-but-revalidatable.\" Drivers route this through\r\n * `prepareDataForStorage` so it persists in the wrapper.\r\n */\r\n staleAt?: number;\r\n};\r\n\r\nconst messages = {\r\n clearing: \"Clearing namespace\",\r\n cleared: \"Namespace cleared\",\r\n fetching: \"Fetching key\",\r\n fetched: \"Key fetched\",\r\n caching: \"Caching key\",\r\n cached: \"Key cached\",\r\n flushing: \"Flushing cache\",\r\n flushed: \"Cache flushed\",\r\n removing: \"Removing key\",\r\n removed: \"Key removed\",\r\n expired: \"Key expired\",\r\n notFound: \"Key not found\",\r\n connecting: \"Connecting to the cache engine.\",\r\n connected: \"Connected to the cache engine.\",\r\n disconnecting: \"Disconnecting from the cache engine.\",\r\n disconnected: \"Disconnected from the cache engine.\",\r\n connectionFailed: \"Failed to connect to the cache engine.\",\r\n error: \"Error occurred\",\r\n};\r\n\r\nexport abstract class BaseCacheDriver<\r\n ClientType,\r\n Options extends Record<string, any>,\r\n> implements CacheDriver<ClientType, Options> {\r\n /**\r\n * CLient driver\r\n */\r\n protected clientDriver!: ClientType;\r\n\r\n /**\r\n * Determine whether to log or not\r\n */\r\n protected shouldLog: boolean = true;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public get client() {\r\n return (this.clientDriver || this) as unknown as ClientType;\r\n }\r\n\r\n /**\r\n * Set logging state\r\n */\r\n public setLoggingState(shouldLog: boolean) {\r\n this.shouldLog = shouldLog;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set client driver\r\n */\r\n public set client(client: ClientType) {\r\n this.clientDriver = client;\r\n }\r\n\r\n /**\r\n * Get the cache driver name\r\n */\r\n public abstract name: string;\r\n\r\n /**\r\n * Options list\r\n */\r\n public options!: Options;\r\n\r\n /**\r\n * Event listeners storage\r\n */\r\n protected eventListeners: Map<CacheEventType, Set<CacheEventHandler>> =\r\n new Map();\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public parseKey(key: CacheKey) {\r\n return parseCacheKey(key, this.options);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: Options) {\r\n this.options = options || {};\r\n return this;\r\n }\r\n\r\n /**\r\n * Register an event listener\r\n */\r\n public on(event: CacheEventType, handler: CacheEventHandler): this {\r\n if (!this.eventListeners.has(event)) {\r\n this.eventListeners.set(event, new Set());\r\n }\r\n this.eventListeners.get(event)!.add(handler);\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove an event listener\r\n */\r\n public off(event: CacheEventType, handler: CacheEventHandler): this {\r\n const handlers = this.eventListeners.get(event);\r\n if (handlers) {\r\n handlers.delete(handler);\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Register a one-time event listener\r\n */\r\n public once(event: CacheEventType, handler: CacheEventHandler): this {\r\n const onceHandler: CacheEventHandler = async (data) => {\r\n await handler(data);\r\n this.off(event, onceHandler);\r\n };\r\n return this.on(event, onceHandler);\r\n }\r\n\r\n /**\r\n * Emit an event to all registered listeners\r\n */\r\n protected async emit(\r\n event: CacheEventType,\r\n data: Partial<CacheEventData> = {},\r\n ): Promise<void> {\r\n const handlers = this.eventListeners.get(event);\r\n if (!handlers || handlers.size === 0) return;\r\n\r\n const eventData: CacheEventData = {\r\n driver: this.name,\r\n ...data,\r\n };\r\n\r\n // Execute all handlers\r\n const promises: Promise<void>[] = [];\r\n for (const handler of handlers) {\r\n try {\r\n const result = handler(eventData);\r\n if (result instanceof Promise) {\r\n promises.push(result);\r\n }\r\n } catch (error) {\r\n this.logError(`Error in event handler for '${event}'`, error);\r\n }\r\n }\r\n\r\n // Wait for all async handlers\r\n if (promises.length > 0) {\r\n await Promise.allSettled(promises);\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract removeNamespace(namespace: string): Promise<any>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any>;\r\n\r\n /**\r\n * Normalize the 3rd argument of a `set` call into a single shape every driver\r\n * can act on. Handles TTL parsing (number | string | Infinity), `expiresAt` →\r\n * relative TTL conversion, and mutual-exclusion validation.\r\n *\r\n * @throws {CacheConfigurationError} when `ttl` and `expiresAt` are passed together\r\n * or an unparseable duration string is supplied.\r\n */\r\n protected resolveSetOptions(\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): NormalizedSetOptions {\r\n const options = normalizeToOptions(ttlOrOptions);\r\n\r\n return {\r\n ttl: resolveTtl(options.ttl, options.expiresAt, this.ttl),\r\n tags: options.tags,\r\n onConflict: options.onConflict ?? \"upsert\",\r\n vector: options.vector,\r\n staleAt: options.staleAt,\r\n };\r\n }\r\n\r\n /**\r\n * Resolve the union of cache keys associated with any of the given tags.\r\n * Used by `similar()` to narrow the candidate pool before similarity ranking.\r\n *\r\n * Returns `null` when no tags are passed (callers should treat that as \"no filter\").\r\n * Keys come back parsed (prefixed) — the index stores them un-prefixed, but\r\n * callers match them against their own parsed storage keys.\r\n */\r\n protected async getKeysForTags(\r\n tags: string[] | undefined,\r\n ): Promise<Set<string> | null> {\r\n if (!tags || tags.length === 0) {\r\n return null;\r\n }\r\n\r\n const allKeys = new Set<string>();\r\n for (const tag of tags) {\r\n const tagKey = `cache:tags:${tag}`;\r\n const keys = ((await this.get(tagKey)) as string[] | null) || [];\r\n for (const k of keys) {\r\n allKeys.add(this.parseKey(k));\r\n }\r\n }\r\n\r\n return allKeys;\r\n }\r\n\r\n /**\r\n * Apply tag relationships after a successful write. Called by drivers once\r\n * the value is in storage.\r\n *\r\n * Pass the caller's key, not the parsed one: the tag index stores keys\r\n * un-prefixed so `invalidate()` can delete them through `remove(key)`.\r\n */\r\n protected async applyTags(key: CacheKey, tags: string[]): Promise<void> {\r\n if (tags.length === 0) {\r\n return;\r\n }\r\n\r\n const tagged = this.tags(tags);\r\n await (tagged as TaggedCache).storeTagRelationship(key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract get(key: CacheKey): Promise<any>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract remove(key: CacheKey): Promise<void>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public abstract flush(): Promise<void>;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async has(key: CacheKey): Promise<boolean> {\r\n const value = await this.get(key);\r\n // Event is emitted by get() method\r\n return value !== null;\r\n }\r\n\r\n /**\r\n * Lock storage for preventing cache stampede\r\n */\r\n protected locks: Map<string, Promise<any>> = new Map();\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remember(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n callback: () => Promise<any>,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n // The options-form lets callers forward tags / driver-override through to\r\n // the cache-miss write. Normalize both shapes into a single CacheSetOptions\r\n // blob so there's one path from here on.\r\n const setOptions = this.normalizeRememberOptions(ttlOrOptions);\r\n\r\n const cachedValue = await this.get(key);\r\n if (cachedValue) {\r\n return cachedValue;\r\n }\r\n\r\n const existingLock = this.locks.get(parsedKey);\r\n if (existingLock) {\r\n return existingLock;\r\n }\r\n\r\n const promise = callback()\r\n .then(async (result) => {\r\n await this.set(key, result, setOptions);\r\n this.locks.delete(parsedKey);\r\n return result;\r\n })\r\n .catch((err) => {\r\n this.locks.delete(parsedKey);\r\n throw err;\r\n });\r\n\r\n this.locks.set(parsedKey, promise);\r\n return promise;\r\n }\r\n\r\n /**\r\n * Resolve the TTL-or-options arg of `remember` into a `CacheSetOptions` object\r\n * that can be passed straight to `set()`. Keeps the implementation unbranched.\r\n */\r\n protected normalizeRememberOptions(\r\n ttlOrOptions: CacheTtl | RememberOptions,\r\n ): CacheSetOptions {\r\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\r\n return { ttl: ttlOrOptions };\r\n }\r\n\r\n return {\r\n ttl: ttlOrOptions.ttl,\r\n tags: ttlOrOptions.tags,\r\n };\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation: read raw entry, branch on freshness/staleness,\r\n * trigger background refresh in the stale window, fall through to\r\n * `callback` on miss/expiry. Concurrent stale-window callers share a\r\n * single in-flight refresh via {@link locks}.\r\n *\r\n * Drivers without a real {@link getEntry} override degrade gracefully —\r\n * the synthetic entry has no `staleAt`, which the freshness check treats\r\n * as \"always fresh,\" so SWR behaves like a TTL-only cached read on those\r\n * drivers (no background refresh, but no double-fetch either).\r\n */\r\n public async swr<T = any>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n ): Promise<T> {\r\n const parsedKey = this.parseKey(key);\r\n const freshSeconds = parseTtl(options.freshTtl);\r\n const staleSeconds = parseTtl(options.staleTtl);\r\n\r\n if (staleSeconds <= freshSeconds) {\r\n throw new Error(\r\n `cache.swr: 'staleTtl' (${staleSeconds}s) must be greater than 'freshTtl' (${freshSeconds}s).`,\r\n );\r\n }\r\n\r\n const entry = await this.getEntry(key);\r\n const now = Date.now();\r\n\r\n const isExpired = entry?.expiresAt !== undefined && entry.expiresAt <= now;\r\n\r\n if (!entry || isExpired) {\r\n return this.swrFetchAndStore<T>(\r\n key,\r\n options,\r\n callback,\r\n freshSeconds,\r\n staleSeconds,\r\n );\r\n }\r\n\r\n const isFresh = entry.staleAt === undefined || entry.staleAt > now;\r\n\r\n if (isFresh) {\r\n return entry.data as T;\r\n }\r\n\r\n this.scheduleSwrRefresh<T>(\r\n parsedKey,\r\n key,\r\n options,\r\n callback,\r\n freshSeconds,\r\n staleSeconds,\r\n );\r\n\r\n return entry.data as T;\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper for a key, including any\r\n * `expiresAt` / `staleAt` metadata. Default implementation falls back to\r\n * `get()` and synthesizes a metadata-less wrapper — drivers that store\r\n * the wrapper directly (memory, lru, file, redis, pg, mock) override\r\n * this to return real metadata so SWR can branch on freshness.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const value = await this.get(key);\r\n\r\n if (value === null) {\r\n return null;\r\n }\r\n\r\n return { data: value };\r\n }\r\n\r\n /**\r\n * Remaining lifetime of an existing entry, in seconds — used by TTL-preserving\r\n * writes such as `update()` / `merge()` when the caller passes no explicit\r\n * `ttl`.\r\n *\r\n * - `Infinity` — the entry exists with no expiry (preserve \"never expires\").\r\n * - positive number — seconds left before the entry expires.\r\n * - `undefined` — the key is missing or already past its deadline; the caller\r\n * should fall back to the driver default TTL.\r\n *\r\n * Default reads `expiresAt` from {@link getEntry}, which the metadata-aware\r\n * drivers (memory, lru, mock, pg) populate. Drivers that track TTL natively\r\n * and don't carry `expiresAt` in their payload (Redis) override this.\r\n */\r\n protected async getRemainingTtl(key: CacheKey): Promise<number | undefined> {\r\n const entry = await this.getEntry(key);\r\n\r\n if (!entry) {\r\n return undefined;\r\n }\r\n\r\n if (!entry.expiresAt || entry.expiresAt === Infinity) {\r\n return Infinity;\r\n }\r\n\r\n const remainingSeconds = Math.ceil((entry.expiresAt - Date.now()) / 1000);\r\n\r\n return remainingSeconds > 0 ? remainingSeconds : undefined;\r\n }\r\n\r\n /**\r\n * Block-and-fetch path of `swr()`: invoked on miss or past-`staleTtl`\r\n * expiry. Writes through `set()` with the SWR options translated into\r\n * standard `CacheSetOptions` (ttl = staleTtl, staleAt = now + freshTtl).\r\n */\r\n protected async swrFetchAndStore<T>(\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n freshSeconds: number,\r\n staleSeconds: number,\r\n ): Promise<T> {\r\n const result = await callback();\r\n\r\n await this.set(key, result, {\r\n ttl: staleSeconds,\r\n staleAt: Date.now() + freshSeconds * 1000,\r\n tags: options.tags,\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Stale-window background refresh. Registers a single in-flight promise\r\n * per parsed key so concurrent SWR callers share one refresh. Failed\r\n * refreshes preserve the stale entry, log via `logError`, and emit on\r\n * `error` — the stale-returning caller never sees the failure.\r\n */\r\n protected scheduleSwrRefresh<T>(\r\n parsedKey: string,\r\n key: CacheKey,\r\n options: CacheSwrOptions,\r\n callback: () => Promise<T>,\r\n freshSeconds: number,\r\n staleSeconds: number,\r\n ): void {\r\n if (this.locks.has(parsedKey)) {\r\n return;\r\n }\r\n\r\n let refresh!: Promise<void>;\r\n refresh = (async () => {\r\n try {\r\n const result = await callback();\r\n\r\n await this.set(key, result, {\r\n ttl: staleSeconds,\r\n staleAt: Date.now() + freshSeconds * 1000,\r\n tags: options.tags,\r\n });\r\n } catch (error) {\r\n this.logError(`SWR background refresh failed for ${parsedKey}`, error);\r\n await this.emit(\"error\", { key: parsedKey, error });\r\n } finally {\r\n if (this.locks.get(parsedKey) === refresh) {\r\n this.locks.delete(parsedKey);\r\n }\r\n }\r\n })();\r\n\r\n this.locks.set(parsedKey, refresh);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async pull(key: CacheKey): Promise<any | null> {\r\n const value = await this.get(key);\r\n if (value !== null) {\r\n await this.remove(key);\r\n }\r\n // Events are emitted by get() and remove() methods\r\n return value;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async forever(key: CacheKey, value: any): Promise<any> {\r\n // Event is emitted by set() method\r\n return this.set(key, value, Infinity);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\r\n const current = (await this.get(key)) || 0;\r\n\r\n if (typeof current !== \"number\") {\r\n throw new Error(\r\n `Cannot increment non-numeric value for key: ${this.parseKey(key)}`,\r\n );\r\n }\r\n\r\n const newValue = current + value;\r\n await this.set(key, newValue);\r\n return newValue;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async decrement(key: CacheKey, value: number = 1): Promise<number> {\r\n return this.increment(key, -value);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async many(keys: CacheKey[]): Promise<any[]> {\r\n return Promise.all(keys.map((key) => this.get(key)));\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async setMany(\r\n items: Record<string, any>,\r\n ttl?: number,\r\n ): Promise<void> {\r\n await Promise.all(\r\n Object.entries(items).map(([key, value]) => this.set(key, value, ttl)),\r\n );\r\n }\r\n\r\n /**\r\n * Log the operation\r\n */\r\n protected log(operation: CacheOperationType, key?: string | any) {\r\n if (!this.shouldLog) return;\r\n\r\n if (typeof key === \"string\") {\r\n // this will be likely used with file cache driver as it will convert the dot to slash\r\n // to make it consistent and not to confuse developers we will output the key by making sure it's a dot\r\n key = key.replace(/\\//g, \".\");\r\n }\r\n\r\n if (operation === \"connectionFailed\") {\r\n log.fatal(`cache.${this.name}`, operation, key);\r\n }\r\n\r\n if (operation == \"notFound\" || operation == \"expired\") {\r\n return log.info(\r\n \"cache.\" + this.name,\r\n operation,\r\n (key ? key + \" \" : \"\") + messages[operation],\r\n );\r\n }\r\n\r\n if (operation.endsWith(\"ed\")) {\r\n return log.success(\r\n \"cache.\" + this.name,\r\n operation,\r\n (key ? key + \" \" : \"\") + messages[operation],\r\n );\r\n }\r\n\r\n log.info(\r\n \"cache.\" + this.name,\r\n operation,\r\n (key ? key + \" \" : \"\") + messages[operation],\r\n );\r\n }\r\n\r\n /**\r\n * Log error message\r\n *\r\n * Never logs the raw `error` object — driver connection errors (Redis,\r\n * Postgres, ...) can carry the connection string, including the password,\r\n * in the message/cause. Only a redacted `{ message, code }` shape is\r\n * passed to the structured logger.\r\n */\r\n protected logError(message: string, error?: any) {\r\n if (error) {\r\n log.error(\"cache.\" + this.name, \"error\", message, safeErrorInfo(error));\r\n } else {\r\n log.error(\"cache.\" + this.name, \"error\", message);\r\n }\r\n }\r\n\r\n /**\r\n * Get the default TTL in seconds. Parses human-readable strings (`\"1h\"`, `\"30m\"`)\r\n * from driver options if present; falls back to `Infinity` when no default is set.\r\n */\r\n public get ttl() {\r\n if (this.options.ttl === undefined) {\r\n return Infinity;\r\n }\r\n\r\n return parseTtl(this.options.ttl);\r\n }\r\n\r\n /**\r\n * Get time to live value in milliseconds\r\n */\r\n public getExpiresAt(ttl: number = this.ttl) {\r\n if (ttl) {\r\n return new Date().getTime() + ttl * 1000;\r\n }\r\n }\r\n\r\n /**\r\n * Wrap a value with TTL and optional freshness metadata for backend\r\n * storage. `staleAt` persists alongside `expiresAt` when supplied — used\r\n * by the SWR flow to mark when the entry stops being fresh.\r\n */\r\n protected prepareDataForStorage(data: any, ttl?: number, staleAt?: number) {\r\n const preparedData: CacheData = {\r\n data,\r\n };\r\n\r\n if (ttl) {\r\n preparedData.ttl = ttl;\r\n preparedData.expiresAt = this.getExpiresAt(ttl);\r\n }\r\n\r\n if (staleAt !== undefined) {\r\n preparedData.staleAt = staleAt;\r\n }\r\n\r\n return preparedData;\r\n }\r\n\r\n /**\r\n * Parse fetched data from cache\r\n */\r\n protected async parseCachedData(key: string, data: CacheData) {\r\n this.log(\"fetched\", key);\r\n\r\n if (data.expiresAt && data.expiresAt < Date.now()) {\r\n this.remove(key);\r\n return null;\r\n }\r\n\r\n const value = data.data;\r\n\r\n // Skip cloning for primitives (immutable types)\r\n if (value === null || value === undefined) {\r\n return value;\r\n }\r\n\r\n const type = typeof value;\r\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\r\n return value;\r\n }\r\n\r\n // Deep clone objects/arrays to prevent cache mutation\r\n try {\r\n return structuredClone(value);\r\n } catch (error) {\r\n this.logError(\r\n `Failed to clone cached value for ${key}, typeof value: ${typeof value}`,\r\n error,\r\n );\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n this.log(\"connecting\");\r\n this.log(\"connected\");\r\n await this.emit(\"connected\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async disconnect() {\r\n this.log(\"disconnected\");\r\n await this.emit(\"disconnected\");\r\n }\r\n\r\n /**\r\n * Create a tagged cache instance for the given tags\r\n */\r\n public tags(tags: string[]): any {\r\n return new TaggedCache(tags, this);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation: read → transform → write under a per-key in-process\r\n * lock. Drivers that can offer stronger semantics (Redis via `WATCH`/`MULTI`)\r\n * should override.\r\n */\r\n public async update<T = any>(\r\n key: CacheKey,\r\n fn: (current: T | null) => T | null | Promise<T | null>,\r\n options: { ttl?: CacheTtl } = {},\r\n ): Promise<T | null> {\r\n const parsedKey = this.parseKey(key);\r\n\r\n // Chain each update onto the previous one for the same key so concurrent\r\n // callers are serialized end-to-end, not merely awakened together when an\r\n // earlier lock resolves.\r\n const previous = this.locks.get(parsedKey) ?? Promise.resolve();\r\n\r\n const next = previous\r\n .catch(() => undefined)\r\n .then(async () => {\r\n const current = (await this.get(key)) as T | null;\r\n const result = await fn(current);\r\n\r\n if (result === null) {\r\n await this.remove(key);\r\n return null;\r\n }\r\n\r\n if (options.ttl !== undefined) {\r\n await this.set(key, result, { ttl: options.ttl });\r\n\r\n return result;\r\n }\r\n\r\n // No explicit TTL → preserve the existing entry's remaining lifetime\r\n // rather than resetting it to the driver default.\r\n const remainingTtl = await this.getRemainingTtl(key);\r\n\r\n if (remainingTtl !== undefined) {\r\n await this.set(key, result, { ttl: remainingTtl });\r\n } else {\r\n await this.set(key, result);\r\n }\r\n\r\n return result;\r\n });\r\n\r\n this.locks.set(parsedKey, next);\r\n\r\n // Clean up the slot once this link finishes — but only if nobody chained\r\n // a follow-up onto it in the meantime.\r\n next.finally(() => {\r\n if (this.locks.get(parsedKey) === next) {\r\n this.locks.delete(parsedKey);\r\n }\r\n });\r\n\r\n return next;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async merge<T extends Record<string, any> = Record<string, any>>(\r\n key: CacheKey,\r\n partial: Partial<T>,\r\n options: { ttl?: CacheTtl } = {},\r\n ): Promise<T> {\r\n const result = await this.update<T>(\r\n key,\r\n (current) => {\r\n const base = (current ?? {}) as T;\r\n return { ...base, ...partial } as T;\r\n },\r\n options,\r\n );\r\n\r\n return result as T;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation: read-mutate-write array backed by the underlying\r\n * cache entry. Concrete drivers (e.g. Redis) override with native commands.\r\n */\r\n public list<T = any>(key: CacheKey): CacheListAccessor<T> {\r\n return new MemoryCacheList<T>(this, key);\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Built on top of `set({ onConflict: \"create\" })` — Redis-native `SET … NX EX`\r\n * under the hood on Redis, emulated via key-existence check on other drivers.\r\n * The lock value is the resolved `owner` (defaults to `pid.<process.pid>`).\r\n *\r\n * Always releases in `finally`, even if `fn` throws — the thrown error\r\n * propagates to the caller unchanged.\r\n */\r\n public async lock<T>(\r\n key: CacheKey,\r\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\r\n fn: () => Promise<T>,\r\n ): Promise<LockOutcome<T>> {\r\n const { ttl, owner } = this.normalizeLockOptions(ttlOrOptions);\r\n const lockOwner = owner ?? `pid.${process.pid}`;\r\n\r\n const setResult = (await this.set(key, lockOwner, {\r\n onConflict: \"create\",\r\n ttl,\r\n })) as CacheSetResult | unknown;\r\n\r\n // `onConflict` drivers return CacheSetResult. Drivers that no-op on set\r\n // (e.g. the null driver) may return anything else — treat that as\r\n // \"acquired\" since there's nothing to collide with.\r\n const wasSet =\r\n typeof setResult === \"object\" &&\r\n setResult !== null &&\r\n \"wasSet\" in setResult\r\n ? (setResult as CacheSetResult).wasSet\r\n : true;\r\n\r\n if (!wasSet) {\r\n return { acquired: false };\r\n }\r\n\r\n try {\r\n const value = await fn();\r\n return { acquired: true, value };\r\n } finally {\r\n await this.remove(key);\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Default implementation throws {@link CacheUnsupportedError}. Drivers that\r\n * support similarity retrieval (memory family, `pg`, `redis` w/ RediSearch)\r\n * override this with a real impl.\r\n */\r\n public async similar<T = any>(\r\n _vector: number[],\r\n _options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n throw new CacheUnsupportedError(\r\n `'${this.name}' driver does not support similarity retrieval. Use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).`,\r\n );\r\n }\r\n\r\n /**\r\n * Resolve the TTL-or-options arg of `lock` into a uniform shape.\r\n */\r\n protected normalizeLockOptions(\r\n ttlOrOptions: CacheTtl | Omit<LockOptions, \"driver\">,\r\n ): {\r\n ttl: CacheTtl;\r\n owner?: string;\r\n } {\r\n if (typeof ttlOrOptions === \"number\" || typeof ttlOrOptions === \"string\") {\r\n return { ttl: ttlOrOptions };\r\n }\r\n\r\n return { ttl: ttlOrOptions.ttl, owner: ttlOrOptions.owner };\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -127,7 +127,7 @@ var FileCacheDriver = class extends BaseCacheDriver {
|
|
|
127
127
|
const fileDirectory = this.keyDirectory(parsedKey);
|
|
128
128
|
await ensureDirectoryAsync(fileDirectory);
|
|
129
129
|
await putJsonFileAsync(path.resolve(fileDirectory, this.fileName), data);
|
|
130
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
130
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
131
131
|
this.log("cached", parsedKey);
|
|
132
132
|
await this.emit("set", {
|
|
133
133
|
key: parsedKey,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/file-cache-driver.ts"],"sourcesContent":["import {\r\n ensureDirectoryAsync,\r\n getJsonFileAsync,\r\n listDirectoriesAsync,\r\n putJsonFileAsync,\r\n removeDirectoryAsync,\r\n} from \"@warlock.js/fs\";\r\nimport path from \"path\";\r\nimport type {\r\n CacheData,\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheTtl,\r\n FileCacheOptions,\r\n} from \"../types\";\r\nimport { CacheConfigurationError, CacheError, CacheUnsupportedError } from \"../types\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nexport class FileCacheDriver\r\n extends BaseCacheDriver<FileCacheDriver, FileCacheOptions>\r\n implements CacheDriver<FileCacheDriver, FileCacheOptions>\r\n{\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"file\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public setOptions(options: FileCacheOptions) {\r\n if (!options.directory) {\r\n throw new CacheConfigurationError(\r\n \"File driver requires 'directory' option to be configured.\",\r\n );\r\n }\r\n\r\n return super.setOptions(options);\r\n }\r\n\r\n /**\r\n * Get the cache directory\r\n */\r\n public get directory() {\r\n const directory = this.options.directory;\r\n\r\n if (typeof directory === \"function\") {\r\n return directory();\r\n }\r\n\r\n throw new CacheConfigurationError(\r\n \"Cache directory is not defined, please define it in the file driver options\",\r\n );\r\n }\r\n\r\n /**\r\n * Get file name\r\n */\r\n public get fileName() {\r\n const fileName = this.options.fileName;\r\n\r\n if (typeof fileName === \"function\") {\r\n return fileName();\r\n }\r\n\r\n return \"cache.json\";\r\n }\r\n\r\n /**\r\n * Map a parsed cache key (or namespace) to its on-disk directory.\r\n *\r\n * The key must become exactly one directory component: `%` and the path\r\n * separators are percent-encoded so a hostile key (`../../etc`,\r\n * `..\\\\..\\\\evil`) turns into an inert directory name instead of a path\r\n * traversal, while distinct keys can never collide after encoding. Dots are\r\n * left untouched — the `.`-delimited namespace scheme is purely logical and\r\n * only ever produces a single filesystem component here.\r\n */\r\n protected keyDirectory(parsedKey: string): string {\r\n return this.containedPath(this.encodeKeySegment(parsedKey));\r\n }\r\n\r\n /**\r\n * Percent-encode a parsed key into an inert, single-component directory\r\n * name. Dots are left untouched — see {@link keyDirectory}.\r\n */\r\n protected encodeKeySegment(parsedKey: string): string {\r\n return parsedKey\r\n .replace(/%/g, \"%25\")\r\n .replace(/\\//g, \"%2F\")\r\n .replace(/\\\\/g, \"%5C\");\r\n }\r\n\r\n /**\r\n * Reverse of {@link encodeKeySegment} — recovers the logical, dot-delimited\r\n * parsed key from an on-disk directory name. Used by {@link removeNamespace}\r\n * to test which sibling directories logically belong to a namespace, since\r\n * dotted keys (`ns.a`) are stored as sibling directories rather than nested\r\n * ones (see {@link keyDirectory}'s doc comment).\r\n */\r\n protected decodeKeySegment(encoded: string): string {\r\n return encoded\r\n .replace(/%2F/g, \"/\")\r\n .replace(/%5C/g, \"\\\\\")\r\n .replace(/%25/g, \"%\");\r\n }\r\n\r\n /**\r\n * Resolve `segment` against the cache root and throw when the result lands\r\n * outside it — the last line of defense against path traversal, independent\r\n * of how the key was encoded.\r\n */\r\n protected containedPath(segment: string): string {\r\n const base = path.resolve(this.directory);\r\n const resolved = path.resolve(base, segment);\r\n const relative = path.relative(base, resolved);\r\n\r\n if (\r\n relative === \"..\" ||\r\n relative.startsWith(`..${path.sep}`) ||\r\n path.isAbsolute(relative)\r\n ) {\r\n throw new CacheError(\r\n `Cache key resolves outside the cache directory: \"${segment}\"`,\r\n );\r\n }\r\n\r\n return resolved;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Dotted keys (`ns.a`) are stored as sibling directories, not nested ones\r\n * (see {@link keyDirectory}) — a directory literally named `<namespace>`\r\n * rarely exists on its own. Namespace removal therefore has to scan the\r\n * cache root's immediate children and remove every directory whose\r\n * *logical* (decoded) name equals the namespace or starts with\r\n * `<namespace>.`, mirroring the `key = $1 OR key LIKE $2` boundary\r\n * semantics the `pg` driver already uses for the same contract.\r\n */\r\n public async removeNamespace(namespace: string) {\r\n const parsedNamespace = this.parseKey(namespace);\r\n\r\n this.log(\"clearing\", namespace);\r\n\r\n try {\r\n const root = this.containedPath(\"\");\r\n\r\n if (parsedNamespace === \"\") {\r\n await removeDirectoryAsync(root);\r\n this.log(\"cleared\", namespace);\r\n return this;\r\n }\r\n\r\n const prefix = `${parsedNamespace}.`;\r\n const entries = await listDirectoriesAsync(root).catch(() => [] as string[]);\r\n\r\n await Promise.all(\r\n entries.map(async (entryPath) => {\r\n const decoded = this.decodeKeySegment(path.basename(entryPath));\r\n\r\n if (decoded === parsedNamespace || decoded.startsWith(prefix)) {\r\n await removeDirectoryAsync(entryPath);\r\n }\r\n }),\r\n );\r\n\r\n this.log(\"cleared\", namespace);\r\n } catch (error) {\r\n //\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\r\n\r\n if (vector) {\r\n throw new CacheUnsupportedError(\r\n \"'file' driver does not support similarity retrieval — use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).\",\r\n );\r\n }\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n const existing = onConflict === \"upsert\" ? null : await this.get(key);\r\n const exists = existing !== null;\r\n\r\n if (onConflict === \"create\" && exists) {\r\n const result: CacheSetResult = { wasSet: false, existing };\r\n return result;\r\n }\r\n\r\n if (onConflict === \"update\" && !exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: null };\r\n return result;\r\n }\r\n\r\n const data = this.prepareDataForStorage(value, ttl, staleAt);\r\n\r\n const fileDirectory = this.keyDirectory(parsedKey);\r\n\r\n await ensureDirectoryAsync(fileDirectory);\r\n\r\n await putJsonFileAsync(path.resolve(fileDirectory, this.fileName), data);\r\n\r\n if (tags && tags.length > 0) {\r\n await this.applyTags(parsedKey, tags);\r\n }\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n await this.emit(\"set\", { key: parsedKey, value, ttl });\r\n\r\n if (onConflict === \"create\" || onConflict === \"update\") {\r\n const result: CacheSetResult = { wasSet: true, existing: null };\r\n return result;\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * File driver does not yet ship with a file-lock primitive, so concurrent\r\n * writers could clobber each other. Rather than ship an unsafe default, we\r\n * throw — consumers can fall back to memory/redis for `update` until a\r\n * proper file lock lands (tracked in `domains/cache/backlog.md`).\r\n */\r\n public async update(): Promise<never> {\r\n throw new CacheUnsupportedError(\r\n \"`update()` is not supported on the file driver. Use the memory or redis driver, or wait for the file-lock primitive (see domains/cache/backlog.md).\",\r\n );\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async merge(): Promise<never> {\r\n throw new CacheUnsupportedError(\r\n \"`merge()` is not supported on the file driver. Use the memory or redis driver.\",\r\n );\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper from disk, including `staleAt`\r\n * metadata. Returns `null` for missing or expired files — `swr()`\r\n * consumes this to branch on freshness.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const parsedKey = this.parseKey(key);\r\n const fileDirectory = this.keyDirectory(parsedKey);\r\n\r\n try {\r\n const entry = (await getJsonFileAsync(path.resolve(fileDirectory, this.fileName))) as\r\n | CacheData\r\n | undefined;\r\n\r\n if (!entry) {\r\n return null;\r\n }\r\n\r\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\r\n return null;\r\n }\r\n\r\n return entry;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"fetching\", parsedKey);\r\n\r\n const fileDirectory = this.keyDirectory(parsedKey);\r\n\r\n try {\r\n const value = await getJsonFileAsync(path.resolve(fileDirectory, this.fileName));\r\n\r\n const result = await this.parseCachedData(parsedKey, value as CacheData);\r\n\r\n if (result === null) {\r\n // Expired\r\n await this.emit(\"miss\", { key: parsedKey });\r\n } else {\r\n // Emit hit event\r\n await this.emit(\"hit\", { key: parsedKey, value: result });\r\n }\r\n\r\n return result;\r\n } catch (error) {\r\n this.log(\"notFound\", parsedKey);\r\n // Emit miss event\r\n await this.emit(\"miss\", { key: parsedKey });\r\n // Await the cleanup so it fully settles before returning. Leaving this\r\n // fire-and-forget let the async directory removal race a follow-up write\r\n // to the same key (e.g. the existence probe inside a `set({ onConflict })`\r\n // call), surfacing as an ENOENT mkdir/rm collision on Windows.\r\n await this.remove(key);\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n this.log(\"removing\", parsedKey);\r\n\r\n const fileDirectory = this.keyDirectory(parsedKey);\r\n\r\n try {\r\n await removeDirectoryAsync(fileDirectory);\r\n\r\n this.log(\"removed\", parsedKey);\r\n // Emit removed event\r\n await this.emit(\"removed\", { key: parsedKey });\r\n } catch (error) {\r\n //\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async flush() {\r\n this.log(\"flushing\");\r\n\r\n if (this.options.globalPrefix) {\r\n await this.removeNamespace(\"\");\r\n } else {\r\n await removeDirectoryAsync(this.directory);\r\n }\r\n\r\n this.log(\"flushed\");\r\n\r\n // Emit flushed event\r\n await this.emit(\"flushed\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async connect() {\r\n this.log(\"connecting\");\r\n await ensureDirectoryAsync(this.directory);\r\n this.log(\"connected\");\r\n await this.emit(\"connected\");\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"file-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/file-cache-driver.ts"],"sourcesContent":["import {\n ensureDirectoryAsync,\n getJsonFileAsync,\n listDirectoriesAsync,\n putJsonFileAsync,\n removeDirectoryAsync,\n} from \"@warlock.js/fs\";\nimport path from \"path\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n FileCacheOptions,\n} from \"../types\";\nimport { CacheConfigurationError, CacheError, CacheUnsupportedError } from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\nexport class FileCacheDriver\n extends BaseCacheDriver<FileCacheDriver, FileCacheOptions>\n implements CacheDriver<FileCacheDriver, FileCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"file\";\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: FileCacheOptions) {\n if (!options.directory) {\n throw new CacheConfigurationError(\n \"File driver requires 'directory' option to be configured.\",\n );\n }\n\n return super.setOptions(options);\n }\n\n /**\n * Get the cache directory\n */\n public get directory() {\n const directory = this.options.directory;\n\n if (typeof directory === \"function\") {\n return directory();\n }\n\n throw new CacheConfigurationError(\n \"Cache directory is not defined, please define it in the file driver options\",\n );\n }\n\n /**\n * Get file name\n */\n public get fileName() {\n const fileName = this.options.fileName;\n\n if (typeof fileName === \"function\") {\n return fileName();\n }\n\n return \"cache.json\";\n }\n\n /**\n * Map a parsed cache key (or namespace) to its on-disk directory.\n *\n * The key must become exactly one directory component: `%` and the path\n * separators are percent-encoded so a hostile key (`../../etc`,\n * `..\\\\..\\\\evil`) turns into an inert directory name instead of a path\n * traversal, while distinct keys can never collide after encoding. Dots are\n * left untouched — the `.`-delimited namespace scheme is purely logical and\n * only ever produces a single filesystem component here.\n */\n protected keyDirectory(parsedKey: string): string {\n return this.containedPath(this.encodeKeySegment(parsedKey));\n }\n\n /**\n * Percent-encode a parsed key into an inert, single-component directory\n * name. Dots are left untouched — see {@link keyDirectory}.\n */\n protected encodeKeySegment(parsedKey: string): string {\n return parsedKey\n .replace(/%/g, \"%25\")\n .replace(/\\//g, \"%2F\")\n .replace(/\\\\/g, \"%5C\");\n }\n\n /**\n * Reverse of {@link encodeKeySegment} — recovers the logical, dot-delimited\n * parsed key from an on-disk directory name. Used by {@link removeNamespace}\n * to test which sibling directories logically belong to a namespace, since\n * dotted keys (`ns.a`) are stored as sibling directories rather than nested\n * ones (see {@link keyDirectory}'s doc comment).\n */\n protected decodeKeySegment(encoded: string): string {\n return encoded\n .replace(/%2F/g, \"/\")\n .replace(/%5C/g, \"\\\\\")\n .replace(/%25/g, \"%\");\n }\n\n /**\n * Resolve `segment` against the cache root and throw when the result lands\n * outside it — the last line of defense against path traversal, independent\n * of how the key was encoded.\n */\n protected containedPath(segment: string): string {\n const base = path.resolve(this.directory);\n const resolved = path.resolve(base, segment);\n const relative = path.relative(base, resolved);\n\n if (\n relative === \"..\" ||\n relative.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relative)\n ) {\n throw new CacheError(\n `Cache key resolves outside the cache directory: \"${segment}\"`,\n );\n }\n\n return resolved;\n }\n\n /**\n * {@inheritdoc}\n *\n * Dotted keys (`ns.a`) are stored as sibling directories, not nested ones\n * (see {@link keyDirectory}) — a directory literally named `<namespace>`\n * rarely exists on its own. Namespace removal therefore has to scan the\n * cache root's immediate children and remove every directory whose\n * *logical* (decoded) name equals the namespace or starts with\n * `<namespace>.`, mirroring the `key = $1 OR key LIKE $2` boundary\n * semantics the `pg` driver already uses for the same contract.\n */\n public async removeNamespace(namespace: string) {\n const parsedNamespace = this.parseKey(namespace);\n\n this.log(\"clearing\", namespace);\n\n try {\n const root = this.containedPath(\"\");\n\n if (parsedNamespace === \"\") {\n await removeDirectoryAsync(root);\n this.log(\"cleared\", namespace);\n return this;\n }\n\n const prefix = `${parsedNamespace}.`;\n const entries = await listDirectoriesAsync(root).catch(() => [] as string[]);\n\n await Promise.all(\n entries.map(async (entryPath) => {\n const decoded = this.decodeKeySegment(path.basename(entryPath));\n\n if (decoded === parsedNamespace || decoded.startsWith(prefix)) {\n await removeDirectoryAsync(entryPath);\n }\n }),\n );\n\n this.log(\"cleared\", namespace);\n } catch (error) {\n //\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n if (vector) {\n throw new CacheUnsupportedError(\n \"'file' driver does not support similarity retrieval — use a memory driver, 'pg' (with pgvector), or 'redis' (with RediSearch).\",\n );\n }\n\n this.log(\"caching\", parsedKey);\n\n const existing = onConflict === \"upsert\" ? null : await this.get(key);\n const exists = existing !== null;\n\n if (onConflict === \"create\" && exists) {\n const result: CacheSetResult = { wasSet: false, existing };\n return result;\n }\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(value, ttl, staleAt);\n\n const fileDirectory = this.keyDirectory(parsedKey);\n\n await ensureDirectoryAsync(fileDirectory);\n\n await putJsonFileAsync(path.resolve(fileDirectory, this.fileName), data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * File driver does not yet ship with a file-lock primitive, so concurrent\n * writers could clobber each other. Rather than ship an unsafe default, we\n * throw — consumers can fall back to memory/redis for `update` until a\n * proper file lock lands (tracked in `domains/cache/backlog.md`).\n */\n public async update(): Promise<never> {\n throw new CacheUnsupportedError(\n \"`update()` is not supported on the file driver. Use the memory or redis driver, or wait for the file-lock primitive (see domains/cache/backlog.md).\",\n );\n }\n\n /**\n * {@inheritdoc}\n */\n public async merge(): Promise<never> {\n throw new CacheUnsupportedError(\n \"`merge()` is not supported on the file driver. Use the memory or redis driver.\",\n );\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from disk, including `staleAt`\n * metadata. Returns `null` for missing or expired files — `swr()`\n * consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const fileDirectory = this.keyDirectory(parsedKey);\n\n try {\n const entry = (await getJsonFileAsync(path.resolve(fileDirectory, this.fileName))) as\n | CacheData\n | undefined;\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return entry;\n } catch {\n return null;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const fileDirectory = this.keyDirectory(parsedKey);\n\n try {\n const value = await getJsonFileAsync(path.resolve(fileDirectory, this.fileName));\n\n const result = await this.parseCachedData(parsedKey, value as CacheData);\n\n if (result === null) {\n // Expired\n await this.emit(\"miss\", { key: parsedKey });\n } else {\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: result });\n }\n\n return result;\n } catch (error) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n // Await the cleanup so it fully settles before returning. Leaving this\n // fire-and-forget let the async directory removal race a follow-up write\n // to the same key (e.g. the existence probe inside a `set({ onConflict })`\n // call), surfacing as an ENOENT mkdir/rm collision on Windows.\n await this.remove(key);\n return null;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n this.log(\"removing\", parsedKey);\n\n const fileDirectory = this.keyDirectory(parsedKey);\n\n try {\n await removeDirectoryAsync(fileDirectory);\n\n this.log(\"removed\", parsedKey);\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n } catch (error) {\n //\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n await removeDirectoryAsync(this.directory);\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n */\n public async connect() {\n this.log(\"connecting\");\n await ensureDirectoryAsync(this.directory);\n this.log(\"connected\");\n await this.emit(\"connected\");\n }\n}\n"],"mappings":"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"}
|
|
@@ -126,7 +126,7 @@ var LRUMemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
126
126
|
this.addNode(newNode);
|
|
127
127
|
if (this.cache.size > this.capacity) this.removeTail();
|
|
128
128
|
}
|
|
129
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
129
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
130
130
|
this.log("cached", parsedKey);
|
|
131
131
|
await this.emit("set", {
|
|
132
132
|
key: parsedKey,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lru-memory-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/lru-memory-cache-driver.ts"],"sourcesContent":["import type {\r\n CacheData,\r\n CacheDriver,\r\n CacheKey,\r\n CacheSetOptions,\r\n CacheSetResult,\r\n CacheSimilarHit,\r\n CacheSimilarOptions,\r\n CacheTtl,\r\n LRUMemoryCacheOptions,\r\n} from \"../types\";\r\nimport { cosineSimilarity } from \"../utils\";\r\nimport { BaseCacheDriver } from \"./base-cache-driver\";\r\n\r\nclass CacheNode {\r\n public next: CacheNode | null = null;\r\n public prev: CacheNode | null = null;\r\n public expiresAt?: number;\r\n /**\r\n * Freshness deadline (ms timestamp) — populated by `swr()`. Within\r\n * `expiresAt > now > staleAt` the entry is \"stale-but-revalidatable.\"\r\n */\r\n public staleAt?: number;\r\n /**\r\n * Optional embedding vector — populated when the entry was written with\r\n * `set({ vector })`. Scanned by `similar()`.\r\n */\r\n public vector?: number[];\r\n public constructor(\r\n public key: string,\r\n public value: any,\r\n ttl?: number,\r\n ) {\r\n if (ttl && ttl !== Infinity) {\r\n this.expiresAt = Date.now() + ttl * 1000;\r\n }\r\n }\r\n\r\n public get isExpired(): boolean {\r\n return this.expiresAt !== undefined && this.expiresAt < Date.now();\r\n }\r\n}\r\n\r\n/**\r\n * LRU Memory Cache Driver\r\n * The concept of LRU is to remove the least recently used data\r\n * whenever the cache is full\r\n * The question that resides here is how to tell the cache is full?\r\n */\r\nexport class LRUMemoryCacheDriver\r\n extends BaseCacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\r\n implements CacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\r\n{\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"lru\";\r\n\r\n /**\r\n * Cache map\r\n */\r\n protected cache: Map<string, CacheNode> = new Map();\r\n\r\n /**\r\n * Head of the cache\r\n */\r\n protected head: CacheNode = new CacheNode(\"\", null);\r\n\r\n /**\r\n * Tail of the cache\r\n */\r\n protected tail: CacheNode = new CacheNode(\"\", null);\r\n\r\n /**\r\n * Cleanup interval reference\r\n */\r\n protected cleanupInterval?: NodeJS.Timeout;\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public constructor() {\r\n super();\r\n\r\n this.init();\r\n this.startCleanup();\r\n }\r\n\r\n /**\r\n * Initialize the cache\r\n */\r\n public init() {\r\n this.head.next = this.tail;\r\n this.tail.prev = this.head;\r\n }\r\n\r\n /**\r\n * Start the cleanup process for expired items\r\n */\r\n public startCleanup() {\r\n // Clear existing interval if any\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n }\r\n\r\n this.cleanupInterval = setInterval(async () => {\r\n const now = Date.now();\r\n const expiredKeys: string[] = [];\r\n\r\n for (const [key, node] of this.cache) {\r\n if (node.expiresAt && node.expiresAt <= now) {\r\n expiredKeys.push(key);\r\n }\r\n }\r\n\r\n for (const key of expiredKeys) {\r\n const node = this.cache.get(key);\r\n if (node) {\r\n this.removeNode(node);\r\n this.cache.delete(key);\r\n this.log(\"expired\", key);\r\n // Emit expired event\r\n await this.emit(\"expired\", { key });\r\n }\r\n }\r\n }, 1000);\r\n\r\n // do not block the process from exiting\r\n this.cleanupInterval.unref();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Clears every entry whose key starts with the parsed namespace (followed\r\n * by a dot) or equals it exactly. Called with an empty namespace while a\r\n * `globalPrefix` is configured, clears everything under the prefix — which\r\n * is how `flush()` scopes cleanup per tenant.\r\n */\r\n public async removeNamespace(namespace: string) {\r\n const parsedNamespace = this.parseKey(namespace);\r\n\r\n this.log(\"clearing\", parsedNamespace || \"(all)\");\r\n\r\n const removed: string[] = [];\r\n\r\n if (parsedNamespace === \"\") {\r\n for (const key of this.cache.keys()) {\r\n removed.push(key);\r\n }\r\n } else {\r\n const prefix = parsedNamespace + \".\";\r\n\r\n for (const key of this.cache.keys()) {\r\n if (key === parsedNamespace || key.startsWith(prefix)) {\r\n removed.push(key);\r\n }\r\n }\r\n }\r\n\r\n for (const key of removed) {\r\n const node = this.cache.get(key);\r\n if (node) {\r\n this.removeNode(node);\r\n this.cache.delete(key);\r\n }\r\n await this.emit(\"removed\", { key });\r\n }\r\n\r\n this.log(\"cleared\", parsedNamespace || \"(all)\");\r\n\r\n return removed;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async set(\r\n key: CacheKey,\r\n value: any,\r\n ttlOrOptions?: CacheTtl | CacheSetOptions,\r\n ): Promise<any> {\r\n const parsedKey = this.parseKey(key);\r\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\r\n\r\n this.log(\"caching\", parsedKey);\r\n\r\n // For conditional writes, check existence via get() so expired entries\r\n // are treated as missing (get() handles expiry cleanup). The raw `cache.get`\r\n // node lookup would let stale entries block onConflict: \"create\".\r\n let existingNode = this.cache.get(parsedKey);\r\n if (existingNode && existingNode.isExpired) {\r\n this.removeNode(existingNode);\r\n this.cache.delete(parsedKey);\r\n existingNode = undefined;\r\n }\r\n\r\n const exists = Boolean(existingNode);\r\n\r\n if (onConflict === \"create\" && exists) {\r\n const result: CacheSetResult = {\r\n wasSet: false,\r\n existing: existingNode!.value,\r\n };\r\n return result;\r\n }\r\n\r\n if (onConflict === \"update\" && !exists) {\r\n const result: CacheSetResult = { wasSet: false, existing: null };\r\n return result;\r\n }\r\n\r\n if (existingNode) {\r\n existingNode.value = value;\r\n if (ttl && ttl !== Infinity) {\r\n existingNode.expiresAt = Date.now() + ttl * 1000;\r\n } else {\r\n existingNode.expiresAt = undefined;\r\n }\r\n existingNode.staleAt = staleAt;\r\n if (vector) {\r\n existingNode.vector = vector.slice();\r\n }\r\n\r\n this.moveHead(existingNode);\r\n } else {\r\n const newNode = new CacheNode(parsedKey, value, ttl);\r\n newNode.staleAt = staleAt;\r\n if (vector) {\r\n newNode.vector = vector.slice();\r\n }\r\n\r\n this.cache.set(parsedKey, newNode);\r\n\r\n this.addNode(newNode);\r\n if (this.cache.size > this.capacity) {\r\n this.removeTail();\r\n }\r\n }\r\n\r\n if (tags && tags.length > 0) {\r\n await this.applyTags(parsedKey, tags);\r\n }\r\n\r\n this.log(\"cached\", parsedKey);\r\n\r\n await this.emit(\"set\", { key: parsedKey, value, ttl });\r\n\r\n if (onConflict === \"create\" || onConflict === \"update\") {\r\n const result: CacheSetResult = { wasSet: true, existing: null };\r\n return result;\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Move the node to the head\r\n */\r\n protected moveHead(node: CacheNode) {\r\n this.removeNode(node);\r\n this.addNode(node);\r\n }\r\n\r\n /**\r\n * Remove the node from the cache\r\n */\r\n protected removeNode(node: CacheNode) {\r\n node.prev!.next = node.next;\r\n node.next!.prev = node.prev;\r\n }\r\n\r\n /**\r\n * Add the node to the head\r\n */\r\n protected addNode(node: CacheNode) {\r\n node.next = this.head.next;\r\n node.prev = this.head;\r\n this.head.next!.prev = node;\r\n this.head.next = node;\r\n }\r\n\r\n /**\r\n * Remove the tail node\r\n */\r\n protected removeTail() {\r\n const node = this.tail.prev!;\r\n\r\n this.removeNode(node);\r\n\r\n this.cache.delete(node.key);\r\n }\r\n\r\n /**\r\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\r\n * Returns `null` for missing or expired nodes — `swr()` consumes this\r\n * to branch on freshness without going through `get()`'s clone-and-emit\r\n * path.\r\n */\r\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\r\n const parsedKey = this.parseKey(key);\r\n const node = this.cache.get(parsedKey);\r\n\r\n if (!node || node.isExpired) {\r\n return null;\r\n }\r\n\r\n return {\r\n data: node.value,\r\n expiresAt: node.expiresAt,\r\n staleAt: node.staleAt,\r\n };\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async get(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"fetching\", parsedKey);\r\n\r\n const node = this.cache.get(parsedKey);\r\n\r\n if (!node) {\r\n this.log(\"notFound\", parsedKey);\r\n // Emit miss event\r\n await this.emit(\"miss\", { key: parsedKey });\r\n return null;\r\n }\r\n\r\n // Check if expired\r\n if (node.isExpired) {\r\n this.removeNode(node);\r\n this.cache.delete(parsedKey);\r\n this.log(\"expired\", parsedKey);\r\n // Emit expired event\r\n await this.emit(\"expired\", { key: parsedKey });\r\n // Also emit miss since we're returning null\r\n await this.emit(\"miss\", { key: parsedKey });\r\n return null;\r\n }\r\n\r\n this.moveHead(node);\r\n\r\n this.log(\"fetched\", parsedKey);\r\n\r\n const value = node.value;\r\n\r\n // Apply cloning for immutability protection\r\n if (value === null || value === undefined) {\r\n return value;\r\n }\r\n\r\n const type = typeof value;\r\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\r\n // Emit hit event\r\n await this.emit(\"hit\", { key: parsedKey, value });\r\n return value;\r\n }\r\n\r\n try {\r\n const clonedValue = structuredClone(value);\r\n // Emit hit event\r\n await this.emit(\"hit\", { key: parsedKey, value: clonedValue });\r\n return clonedValue;\r\n } catch (error) {\r\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async remove(key: CacheKey) {\r\n const parsedKey = this.parseKey(key);\r\n\r\n this.log(\"removing\", parsedKey);\r\n\r\n const node = this.cache.get(parsedKey);\r\n\r\n if (node) {\r\n this.removeNode(node);\r\n this.cache.delete(parsedKey);\r\n }\r\n\r\n this.log(\"removed\", parsedKey);\r\n\r\n // Emit removed event\r\n await this.emit(\"removed\", { key: parsedKey });\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * When a `globalPrefix` is configured, `flush` scopes itself to that prefix\r\n * so multi-tenant caches don't accidentally wipe sibling tenants. Without\r\n * a prefix, clears everything.\r\n */\r\n public async flush() {\r\n this.log(\"flushing\");\r\n\r\n if (this.options.globalPrefix) {\r\n await this.removeNamespace(\"\");\r\n } else {\r\n this.cache.clear();\r\n this.init();\r\n }\r\n\r\n this.log(\"flushed\");\r\n\r\n await this.emit(\"flushed\");\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n *\r\n * Brute-force O(N) cosine similarity over every cached node that carries a\r\n * vector. Suitable for development and small in-memory knowledge bases —\r\n * not for production beyond ~10k entries.\r\n *\r\n * @warning Dev-only — O(N) per query.\r\n */\r\n public async similar<T = any>(\r\n vector: number[],\r\n options: CacheSimilarOptions,\r\n ): Promise<CacheSimilarHit<T>[]> {\r\n const tagFilter = await this.getKeysForTags(options.tags);\r\n\r\n const hits: CacheSimilarHit<T>[] = [];\r\n\r\n for (const [parsedKey, node] of this.cache) {\r\n if (!node.vector) continue;\r\n if (node.isExpired) continue;\r\n if (tagFilter && !tagFilter.has(parsedKey)) continue;\r\n\r\n const score = cosineSimilarity(vector, node.vector);\r\n\r\n if (options.threshold !== undefined && score < options.threshold) {\r\n continue;\r\n }\r\n\r\n // Clone object values to match get() semantics.\r\n let value: any = node.value;\r\n if (value !== null && value !== undefined) {\r\n const t = typeof value;\r\n if (t !== \"string\" && t !== \"number\" && t !== \"boolean\") {\r\n value = structuredClone(value);\r\n }\r\n }\r\n\r\n hits.push({ key: parsedKey, value, score });\r\n }\r\n\r\n hits.sort((a, b) => b.score - a.score);\r\n\r\n if (options.topK >= 0 && hits.length > options.topK) {\r\n hits.length = options.topK;\r\n }\r\n\r\n return hits;\r\n }\r\n\r\n /**\r\n * Get lru capacity\r\n */\r\n public get capacity() {\r\n return this.options.capacity || 1000;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async disconnect() {\r\n // Clear the cleanup interval to prevent memory leaks\r\n if (this.cleanupInterval) {\r\n clearInterval(this.cleanupInterval);\r\n this.cleanupInterval = undefined;\r\n }\r\n\r\n await super.disconnect();\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lru-memory-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/lru-memory-cache-driver.ts"],"sourcesContent":["import type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n LRUMemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\nclass CacheNode {\n public next: CacheNode | null = null;\n public prev: CacheNode | null = null;\n public expiresAt?: number;\n /**\n * Freshness deadline (ms timestamp) — populated by `swr()`. Within\n * `expiresAt > now > staleAt` the entry is \"stale-but-revalidatable.\"\n */\n public staleAt?: number;\n /**\n * Optional embedding vector — populated when the entry was written with\n * `set({ vector })`. Scanned by `similar()`.\n */\n public vector?: number[];\n public constructor(\n public key: string,\n public value: any,\n ttl?: number,\n ) {\n if (ttl && ttl !== Infinity) {\n this.expiresAt = Date.now() + ttl * 1000;\n }\n }\n\n public get isExpired(): boolean {\n return this.expiresAt !== undefined && this.expiresAt < Date.now();\n }\n}\n\n/**\n * LRU Memory Cache Driver\n * The concept of LRU is to remove the least recently used data\n * whenever the cache is full\n * The question that resides here is how to tell the cache is full?\n */\nexport class LRUMemoryCacheDriver\n extends BaseCacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n implements CacheDriver<LRUMemoryCacheDriver, LRUMemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"lru\";\n\n /**\n * Cache map\n */\n protected cache: Map<string, CacheNode> = new Map();\n\n /**\n * Head of the cache\n */\n protected head: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Tail of the cache\n */\n protected tail: CacheNode = new CacheNode(\"\", null);\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.init();\n this.startCleanup();\n }\n\n /**\n * Initialize the cache\n */\n public init() {\n this.head.next = this.tail;\n this.tail.prev = this.head;\n }\n\n /**\n * Start the cleanup process for expired items\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, node] of this.cache) {\n if (node.expiresAt && node.expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * {@inheritdoc}\n *\n * Clears every entry whose key starts with the parsed namespace (followed\n * by a dot) or equals it exactly. Called with an empty namespace while a\n * `globalPrefix` is configured, clears everything under the prefix — which\n * is how `flush()` scopes cleanup per tenant.\n */\n public async removeNamespace(namespace: string) {\n const parsedNamespace = this.parseKey(namespace);\n\n this.log(\"clearing\", parsedNamespace || \"(all)\");\n\n const removed: string[] = [];\n\n if (parsedNamespace === \"\") {\n for (const key of this.cache.keys()) {\n removed.push(key);\n }\n } else {\n const prefix = parsedNamespace + \".\";\n\n for (const key of this.cache.keys()) {\n if (key === parsedNamespace || key.startsWith(prefix)) {\n removed.push(key);\n }\n }\n }\n\n for (const key of removed) {\n const node = this.cache.get(key);\n if (node) {\n this.removeNode(node);\n this.cache.delete(key);\n }\n await this.emit(\"removed\", { key });\n }\n\n this.log(\"cleared\", parsedNamespace || \"(all)\");\n\n return removed;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // For conditional writes, check existence via get() so expired entries\n // are treated as missing (get() handles expiry cleanup). The raw `cache.get`\n // node lookup would let stale entries block onConflict: \"create\".\n let existingNode = this.cache.get(parsedKey);\n if (existingNode && existingNode.isExpired) {\n this.removeNode(existingNode);\n this.cache.delete(parsedKey);\n existingNode = undefined;\n }\n\n const exists = Boolean(existingNode);\n\n if (onConflict === \"create\" && exists) {\n const result: CacheSetResult = {\n wasSet: false,\n existing: existingNode!.value,\n };\n return result;\n }\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n if (existingNode) {\n existingNode.value = value;\n if (ttl && ttl !== Infinity) {\n existingNode.expiresAt = Date.now() + ttl * 1000;\n } else {\n existingNode.expiresAt = undefined;\n }\n existingNode.staleAt = staleAt;\n if (vector) {\n existingNode.vector = vector.slice();\n }\n\n this.moveHead(existingNode);\n } else {\n const newNode = new CacheNode(parsedKey, value, ttl);\n newNode.staleAt = staleAt;\n if (vector) {\n newNode.vector = vector.slice();\n }\n\n this.cache.set(parsedKey, newNode);\n\n this.addNode(newNode);\n if (this.cache.size > this.capacity) {\n this.removeTail();\n }\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * Move the node to the head\n */\n protected moveHead(node: CacheNode) {\n this.removeNode(node);\n this.addNode(node);\n }\n\n /**\n * Remove the node from the cache\n */\n protected removeNode(node: CacheNode) {\n node.prev!.next = node.next;\n node.next!.prev = node.prev;\n }\n\n /**\n * Add the node to the head\n */\n protected addNode(node: CacheNode) {\n node.next = this.head.next;\n node.prev = this.head;\n this.head.next!.prev = node;\n this.head.next = node;\n }\n\n /**\n * Remove the tail node\n */\n protected removeTail() {\n const node = this.tail.prev!;\n\n this.removeNode(node);\n\n this.cache.delete(node.key);\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\n * Returns `null` for missing or expired nodes — `swr()` consumes this\n * to branch on freshness without going through `get()`'s clone-and-emit\n * path.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const node = this.cache.get(parsedKey);\n\n if (!node || node.isExpired) {\n return null;\n }\n\n return {\n data: node.value,\n expiresAt: node.expiresAt,\n staleAt: node.staleAt,\n };\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (!node) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n // Check if expired\n if (node.isExpired) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n this.log(\"expired\", parsedKey);\n // Emit expired event\n await this.emit(\"expired\", { key: parsedKey });\n // Also emit miss since we're returning null\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.moveHead(node);\n\n this.log(\"fetched\", parsedKey);\n\n const value = node.value;\n\n // Apply cloning for immutability protection\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value });\n return value;\n }\n\n try {\n const clonedValue = structuredClone(value);\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: clonedValue });\n return clonedValue;\n } catch (error) {\n this.logError(`Failed to clone cached value for ${parsedKey}`, error);\n throw error;\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n const node = this.cache.get(parsedKey);\n\n if (node) {\n this.removeNode(node);\n this.cache.delete(parsedKey);\n }\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * When a `globalPrefix` is configured, `flush` scopes itself to that prefix\n * so multi-tenant caches don't accidentally wipe sibling tenants. Without\n * a prefix, clears everything.\n */\n public async flush() {\n this.log(\"flushing\");\n\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.cache.clear();\n this.init();\n }\n\n this.log(\"flushed\");\n\n await this.emit(\"flushed\");\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every cached node that carries a\n * vector. Suitable for development and small in-memory knowledge bases —\n * not for production beyond ~10k entries.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, node] of this.cache) {\n if (!node.vector) continue;\n if (node.isExpired) continue;\n if (tagFilter && !tagFilter.has(parsedKey)) continue;\n\n const score = cosineSimilarity(vector, node.vector);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n // Clone object values to match get() semantics.\n let value: any = node.value;\n if (value !== null && value !== undefined) {\n const t = typeof value;\n if (t !== \"string\" && t !== \"number\" && t !== \"boolean\") {\n value = structuredClone(value);\n }\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * Get lru capacity\n */\n public get capacity() {\n return this.options.capacity || 1000;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +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;EAiGwB;;;EAAA,UA1FlB,eAAA,GAAkB,MAAA,CAAO,OAAA;EAwLL;;;EAAA,UAnLpB,WAAA;EAqMuB;;;;;EAAA,UA9LvB,WAAA,EAAa,GAAA;
|
|
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;EA0TZ;;;;EAhWA;;;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,UA8DA,YAAA;EA9DmD;;;;;;;;;;EA6EhD,OAAA,UACX,MAAA,YACA,OAAA,EAAS,mBAAA,GACR,OAAA,CAAQ,eAAA,CAAgB,CAAA;EADhB;;;EAoDE,UAAA,IAAU,OAAA;AAAA"}
|
|
@@ -69,7 +69,7 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
69
69
|
set(this.data, parsedKey, data);
|
|
70
70
|
this.trackAccess(parsedKey);
|
|
71
71
|
if (!exists && this.options.maxSize) await this.enforceMaxSize();
|
|
72
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
72
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
73
73
|
if (vector) this.vectorIndex.set(parsedKey, vector.slice());
|
|
74
74
|
this.log("cached", parsedKey);
|
|
75
75
|
await this.emit("set", {
|
|
@@ -210,8 +210,14 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
210
210
|
const hits = [];
|
|
211
211
|
for (const [parsedKey, stored] of this.vectorIndex) {
|
|
212
212
|
if (tagFilter && !tagFilter.has(parsedKey)) continue;
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
213
|
+
const entry = get(this.data, parsedKey);
|
|
214
|
+
if (!entry) continue;
|
|
215
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) continue;
|
|
216
|
+
let value = entry.data;
|
|
217
|
+
if (value !== null && value !== void 0) {
|
|
218
|
+
const type = typeof value;
|
|
219
|
+
if (type !== "string" && type !== "number" && type !== "boolean") value = structuredClone(value);
|
|
220
|
+
}
|
|
215
221
|
const score = cosineSimilarity(vector, stored);
|
|
216
222
|
if (options.threshold !== void 0 && score < options.threshold) continue;
|
|
217
223
|
hits.push({
|
|
@@ -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 // `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
|
+
{"version":3,"file":"memory-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/memory-cache-driver.ts"],"sourcesContent":["import type { GenericObject } from \"@mongez/reinforcements\";\nimport { get, set, unset } from \"@mongez/reinforcements\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n MemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\n\nexport class MemoryCacheDriver\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memory\";\n\n /**\n * Cached data\n */\n public data: GenericObject = {};\n\n /**\n * List of data that will be cleared from cache\n */\n protected temporaryData: Record<\n string,\n {\n key: string;\n expiresAt: number;\n }\n > = {};\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * Access order tracking for LRU eviction (when maxSize is set)\n */\n protected accessOrder: string[] = [];\n\n /**\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\n * `remove`, `flush`, expiry, namespace clear, and LRU eviction.\n */\n protected vectorIndex: Map<string, number[]> = new Map();\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.startCleanup();\n }\n\n /**\n * Start the cleanup process whenever a data that has a cache key is set\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n\n // `Object.entries` rather than `for...in` + index. Two reasons, and the\n // second is not about types at all: under `noUncheckedIndexedAccess` the\n // index read is `T | undefined`, AND this loop DELETES from the object it\n // is iterating. `for...in` over a mutating object has\n // implementation-defined behaviour for keys not yet visited; entries\n // snapshots first, so the deletion below is unambiguous.\n for (const [key, entry] of Object.entries(this.temporaryData)) {\n if (entry.expiresAt <= now) {\n await this.remove(entry.key);\n delete this.temporaryData[key];\n\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.log(\"clearing\", namespace);\n\n namespace = this.parseKey(namespace);\n\n unset(this.data, [namespace]);\n\n // Drop vector entries that fall under this namespace.\n if (namespace === \"\") {\n this.vectorIndex.clear();\n } else {\n const prefix = namespace + \".\";\n for (const k of [...this.vectorIndex.keys()]) {\n if (k === namespace || k.startsWith(prefix)) {\n this.vectorIndex.delete(k);\n }\n }\n }\n\n this.log(\"cleared\", namespace);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // Use get() for the existence check so expired entries are treated as\n // missing (and cleaned up as a side effect). A raw map lookup would let\n // stale entries block onConflict: \"create\" even after their TTL elapsed.\n const existingValue = onConflict === \"upsert\" ? null : await this.get(key);\n const exists = existingValue !== null;\n\n if (onConflict === \"create\" && exists) {\n const result: CacheSetResult = { wasSet: false, existing: existingValue };\n return result;\n }\n\n if (onConflict === \"update\" && !exists) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(value, ttl, staleAt);\n\n if (ttl) {\n this.setTemporaryData(key, parsedKey, ttl);\n }\n\n set(this.data, parsedKey, data);\n\n this.trackAccess(parsedKey);\n\n if (!exists && this.options.maxSize) {\n await this.enforceMaxSize();\n }\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n if (vector) {\n this.vectorIndex.set(parsedKey, vector.slice());\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const value: CacheData = get(this.data, parsedKey);\n\n if (!value) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n const result = await this.parseCachedData(parsedKey, value);\n\n if (result === null) {\n // Expired\n await this.emit(\"miss\", { key: parsedKey });\n } else {\n // Track access for LRU\n this.trackAccess(parsedKey);\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: result });\n }\n\n return result;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.\n * Returns `null` for missing or expired entries so the SWR flow can branch\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const entry: CacheData | undefined = get(this.data, parsedKey);\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return entry;\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n unset(this.data, [parsedKey]);\n\n // Clean up from temporaryData as well\n delete this.temporaryData[parsedKey];\n\n // Remove from access order\n this.removeFromAccessOrder(parsedKey);\n\n // Drop the vector index entry if any\n this.vectorIndex.delete(parsedKey);\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n if (this.options.globalPrefix) {\n this.removeNamespace(\"\");\n } else {\n this.data = {};\n this.accessOrder = [];\n this.vectorIndex.clear();\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * Set the temporary data\n */\n protected setTemporaryData(key: CacheKey, parsedKey: string, ttl: number) {\n this.temporaryData[parsedKey] = {\n key: JSON.stringify(key),\n expiresAt: Date.now() + ttl * 1000,\n };\n }\n\n /**\n * Track access for LRU eviction\n */\n protected trackAccess(key: string) {\n if (!this.options.maxSize) return;\n\n // Remove key from current position\n const index = this.accessOrder.indexOf(key);\n if (index > -1) {\n this.accessOrder.splice(index, 1);\n }\n\n // Add to end (most recently used)\n this.accessOrder.push(key);\n }\n\n /**\n * Remove key from access order tracking\n */\n protected removeFromAccessOrder(key: string) {\n const index = this.accessOrder.indexOf(key);\n if (index > -1) {\n this.accessOrder.splice(index, 1);\n }\n }\n\n /**\n * Enforce max size by evicting least recently used items.\n *\n * Recomputes the live cache size on every iteration — a single snapshot at\n * the top of the loop would go stale and cause this routine to evict every\n * entry in `accessOrder` (including the just-inserted key).\n */\n protected async enforceMaxSize() {\n if (!this.options.maxSize) {\n return;\n }\n\n while (this.getCacheSize() > this.options.maxSize && this.accessOrder.length > 0) {\n const lruKey = this.accessOrder.shift();\n if (!lruKey) {\n break;\n }\n\n this.log(\"removing\", lruKey);\n unset(this.data, [lruKey]);\n delete this.temporaryData[lruKey];\n this.vectorIndex.delete(lruKey);\n this.log(\"removed\", lruKey);\n }\n }\n\n /**\n * Get current cache size (number of cached items)\n */\n protected getCacheSize(): number {\n // Count top-level keys in data object\n return Object.keys(this.data).length;\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every entry that was written with\n * `set({ vector })`. Suitable for development and small in-memory knowledge\n * bases — not for production beyond ~10k entries. Use the `pg` driver\n * (with pgvector) or `redis` (with RediSearch) at scale.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, stored] of this.vectorIndex) {\n if (tagFilter && !tagFilter.has(parsedKey)) {\n continue;\n }\n\n // Read the entry directly by its already-parsed key — this.get() would\n // parse it again and apply globalPrefix twice, missing every entry.\n const entry: CacheData | undefined = get(this.data, parsedKey);\n\n if (!entry) {\n continue;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n continue;\n }\n\n let value: any = entry.data;\n if (value !== null && value !== undefined) {\n const type = typeof value;\n if (type !== \"string\" && type !== \"number\" && type !== \"boolean\") {\n value = structuredClone(value);\n }\n }\n\n const score = cosineSimilarity(vector, stored);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n"],"mappings":"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"}
|
|
@@ -98,7 +98,7 @@ var MockCacheDriver = class extends BaseCacheDriver {
|
|
|
98
98
|
};
|
|
99
99
|
const data = this.prepareDataForStorage(value, ttl, staleAt);
|
|
100
100
|
this.storage.set(parsedKey, data);
|
|
101
|
-
if (tags && tags.length > 0) await this.applyTags(
|
|
101
|
+
if (tags && tags.length > 0) await this.applyTags(key, tags);
|
|
102
102
|
this.log("cached", parsedKey);
|
|
103
103
|
await this.emit("set", {
|
|
104
104
|
key: parsedKey,
|