@warlock.js/cache 5.30.0 → 5.32.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 +26 -8
- package/README.md +12 -21
- package/cjs/index.cjs +50 -31
- package/cjs/index.cjs.map +1 -1
- package/esm/cache-manager.d.mts +5 -5
- package/esm/cache-manager.d.mts.map +1 -1
- package/esm/cache-manager.mjs +2 -2
- package/esm/cache-manager.mjs.map +1 -1
- package/esm/cached/auto-key.d.mts +1 -2
- package/esm/cached/auto-key.d.mts.map +1 -1
- package/esm/cached/cached.d.mts +4 -6
- package/esm/cached/cached.d.mts.map +1 -1
- package/esm/cached/index.d.mts +2 -1
- package/esm/cached/index.mjs +1 -1
- package/esm/cached/normalize-args.d.mts +3 -5
- package/esm/cached/normalize-args.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +5 -5
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +4 -3
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.d.mts +1 -3
- package/esm/drivers/file-cache-driver.d.mts.map +1 -1
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts +1 -2
- package/esm/drivers/in-memory-tag-index.d.mts.map +1 -1
- package/esm/drivers/index.d.mts +2 -1
- package/esm/drivers/index.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts +5 -4
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +6 -4
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts +4 -4
- 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/memory-extended-cache-driver.d.mts +1 -3
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.d.mts +4 -4
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +11 -4
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/null-cache-driver.d.mts +1 -3
- package/esm/drivers/null-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.d.mts +4 -4
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +6 -3
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.d.mts +9 -9
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +4 -3
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/index.d.mts +3 -0
- package/esm/list/index.d.mts +2 -1
- package/esm/list/memory-cache-list.d.mts +1 -3
- package/esm/list/memory-cache-list.d.mts.map +1 -1
- package/esm/list/memory-cache-list.mjs.map +1 -1
- package/esm/metrics.d.mts +4 -4
- package/esm/metrics.d.mts.map +1 -1
- package/esm/metrics.mjs +2 -2
- package/esm/metrics.mjs.map +1 -1
- package/esm/scoped-cache.d.mts +4 -4
- package/esm/scoped-cache.d.mts.map +1 -1
- package/esm/scoped-cache.mjs +2 -2
- package/esm/scoped-cache.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +4 -4
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +3 -3
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts +4 -4
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +2 -2
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/esm/types.d.mts +86 -62
- package/esm/types.d.mts.map +1 -1
- package/esm/types.mjs.map +1 -1
- package/esm/utils.d.mts +12 -14
- package/esm/utils.d.mts.map +1 -1
- package/esm/utils.mjs.map +1 -1
- package/llms-full.txt +229 -224
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/apply-cache-patterns/SKILL.md +3 -2
- package/skills/cache-basics/SKILL.md +24 -24
- package/skills/configure-pg-cache/SKILL.md +2 -2
- package/skills/configure-set-options/SKILL.md +14 -14
- package/skills/handle-cache-errors/SKILL.md +8 -8
- package/skills/index.md +4 -1
- package/skills/observe-cache/SKILL.md +8 -8
- package/skills/pick-cache-driver/SKILL.md +24 -23
- package/skills/test-cache-code/SKILL.md +15 -10
- package/skills/use-cache-atomic/SKILL.md +17 -11
- package/skills/use-cache-bulk/SKILL.md +8 -5
- package/skills/use-cache-list/SKILL.md +13 -13
- package/skills/use-cache-lock/SKILL.md +11 -17
- package/skills/use-cache-namespace/SKILL.md +11 -13
- package/skills/use-cache-similarity/SKILL.md +9 -9
- package/skills/use-cache-tags/SKILL.md +13 -13
- package/skills/use-cache-update-merge/SKILL.md +10 -10
- package/skills/use-cache-utils/SKILL.md +17 -17
- package/skills/use-cached-hof/SKILL.md +8 -7
- package/skills/use-swr/SKILL.md +14 -18
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { BaseCacheDriver } from "./base-cache-driver.mjs";
|
|
2
2
|
import { InMemoryTagIndex } from "./in-memory-tag-index.mjs";
|
|
3
3
|
import { CacheData, CacheDriver, CacheKey, CacheSetOptions, CacheSimilarHit, CacheSimilarOptions, CacheTtl, MemoryCacheOptions } from "../types.mjs";
|
|
4
|
-
|
|
5
4
|
//#region ../../packages/cache/src/drivers/memory-cache-driver.d.ts
|
|
6
|
-
declare class MemoryCacheDriver extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions> implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions> {
|
|
5
|
+
export declare class MemoryCacheDriver extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions> implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions> {
|
|
7
6
|
/**
|
|
8
7
|
* {@inheritdoc}
|
|
9
8
|
*/
|
|
@@ -99,7 +98,9 @@ declare class MemoryCacheDriver extends BaseCacheDriver<MemoryCacheDriver, Memor
|
|
|
99
98
|
* concurrent increments in this process never lose an update. An existing
|
|
100
99
|
* entry keeps its expiry; a missing key starts from 0 with the default TTL.
|
|
101
100
|
*/
|
|
102
|
-
increment(key: CacheKey, value?: number
|
|
101
|
+
increment(key: CacheKey, value?: number, options?: {
|
|
102
|
+
ttl?: CacheTtl;
|
|
103
|
+
}): Promise<number>;
|
|
103
104
|
/**
|
|
104
105
|
* {@inheritdoc}
|
|
105
106
|
*
|
|
@@ -165,5 +166,4 @@ declare class MemoryCacheDriver extends BaseCacheDriver<MemoryCacheDriver, Memor
|
|
|
165
166
|
disconnect(): Promise<void>;
|
|
166
167
|
}
|
|
167
168
|
//#endregion
|
|
168
|
-
export { MemoryCacheDriver };
|
|
169
169
|
//# sourceMappingURL=memory-cache-driver.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"memory-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts"],"mappings":";;;;qBAkCa,0BACH,gBAAgB,mBAAmB,+BAChC,YAAY,mBAAmB;;;;EAKnC;;;;;;YAOG,SAAS,YAAY;;;;;MAMpB,QAAQ,eAAe;;;;;YAQxB,QAAQ;;;;YAKR,kBAAkB,OAAO;;;;;;;YAQzB,aAAa;;;;;YAMb,UAAU;;;;EAKpB;;;;EASO;;;;YAuCG,UAAU;;;;;YAUV,SAAS,oBAAoB;;;;YAkB7B,MAAM,mBAAmB,OAAO;;;;;;;YAahC;;;;;;YAOA,OAAO,mBAAmB,OAAO;;;;EAiB9B,gBAAgB,oBAAiB;;;;;;;EAgCjC,IACX,KAAK,UACL,YACA,eAAe,WAAW,kBACzB;;;;;;;YA8Da,eAAe,KAAK,UAAU,oBAAoB;;;;;;;;EAoBrD,UACX,KAAK,UACL,gBACA;IAAW,MAAM;MAChB;;;;;;;EAgDU,KAAK,KAAK,WAAW;;;;EAuBrB,IAAI,KAAK,WAAQ;;;;;;YA8Bd,SAAS,KAAK,WAAW,QAAQ;;;;EAapC,OAAO,KAAK,WAAQ;;;;;;EAkBpB,OAAO,QAAQ,UAAU,oBAAoB;;;;EAO7C,WAAW,QAAQ,WAAW;;;;EAO9B,UAAU,QAAQ,UAAU,oBAAoB;;;;EAOhD,UAAU,QAAQ,WAAW;;;;EAO7B,SAAK;;;;;YAqBR,eAAe;;;;;;;;;;;EAgCZ,QAAQ,SACnB,kBACA,SAAS,sBACR,QAAQ,gBAAgB;;;;EAyCd,cAAU"}
|
|
@@ -175,7 +175,8 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
175
175
|
* concurrent increments in this process never lose an update. An existing
|
|
176
176
|
* entry keeps its expiry; a missing key starts from 0 with the default TTL.
|
|
177
177
|
*/
|
|
178
|
-
async increment(key, value = 1) {
|
|
178
|
+
async increment(key, value = 1, options = {}) {
|
|
179
|
+
const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
|
|
179
180
|
const parsedKey = this.parseKey(key);
|
|
180
181
|
const entry = this.readLive(parsedKey);
|
|
181
182
|
const current = entry ? entry.data : 0;
|
|
@@ -183,9 +184,14 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
183
184
|
const newValue = current + value;
|
|
184
185
|
if (entry) {
|
|
185
186
|
entry.data = newValue;
|
|
187
|
+
if (options.ttl !== void 0 && !Number.isFinite(entry.expiresAt) && Number.isFinite(ttl) && ttl > 0) {
|
|
188
|
+
entry.ttl = ttl;
|
|
189
|
+
entry.expiresAt = Date.now() + ttl * 1e3;
|
|
190
|
+
this.expiry.set(parsedKey, entry.expiresAt);
|
|
191
|
+
}
|
|
186
192
|
this.touch(parsedKey, entry);
|
|
187
193
|
} else {
|
|
188
|
-
const data = this.prepareDataForStorage(newValue,
|
|
194
|
+
const data = this.prepareDataForStorage(newValue, ttl);
|
|
189
195
|
this.entries.set(parsedKey, data);
|
|
190
196
|
if (data.expiresAt !== void 0 && Number.isFinite(data.expiresAt)) this.expiry.set(parsedKey, data.expiresAt);
|
|
191
197
|
this.enforceMaxSize(parsedKey);
|
|
@@ -193,7 +199,7 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
|
|
|
193
199
|
await this.emit("set", {
|
|
194
200
|
key: parsedKey,
|
|
195
201
|
value: newValue,
|
|
196
|
-
ttl: entry?.ttl ??
|
|
202
|
+
ttl: entry?.ttl ?? ttl
|
|
197
203
|
});
|
|
198
204
|
return newValue;
|
|
199
205
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n MemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity, parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * Clone non-primitive values so cached state can't be mutated through\n * references held by callers. Primitives are immutable and returned as-is.\n */\nfunction cloneValue<T>(value: T): T {\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n return value;\n }\n\n return structuredClone(value);\n}\n\nexport class MemoryCacheDriver\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memory\";\n\n /**\n * Flat storage keyed by the parsed key. Insertion order doubles as LRU\n * order (least recently used first) — accessed entries are deleted and\n * re-inserted.\n */\n protected entries: Map<string, CacheData> = new Map();\n\n /**\n * Read-only object view of the stored entries, keyed by parsed key.\n * Kept for introspection; the values are the live stored wrappers.\n */\n public get data(): Record<string, CacheData> {\n return Object.fromEntries(this.entries);\n }\n\n /**\n * Expiry records for entries with a FINITE ttl only (parsed key => expiresAt ms).\n * The sweep iterates just these.\n */\n protected expiry: Map<string, number> = new Map();\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\n * `remove`, `flush`, expiry, namespace clear, overwrite without a vector,\n * and LRU eviction.\n */\n protected vectorIndex: Map<string, number[]> = new Map();\n\n /**\n * Tag index, kept apart from `entries`: never evicted by `maxSize`, never\n * swept by TTL, and updated synchronously.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.startCleanup();\n }\n\n /**\n * Start the cleanup process for entries that carry a finite ttl\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, expiresAt] of this.expiry) {\n if (expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n // the entry may have been rewritten/slid since the snapshot\n const expiresAt = this.expiry.get(key);\n\n if (expiresAt === undefined || expiresAt > now) {\n continue;\n }\n\n this.dropEntry(key);\n\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * Delete an entry and every side record (expiry, vector). Synchronous.\n */\n protected dropEntry(parsedKey: string) {\n this.entries.delete(parsedKey);\n this.expiry.delete(parsedKey);\n this.vectorIndex.delete(parsedKey);\n }\n\n /**\n * Raw, synchronous read of a live entry. An expired entry is deleted and\n * reported as missing. Never slides TTLs, never emits, never touches LRU order.\n */\n protected readLive(parsedKey: string): CacheData | undefined {\n const entry = this.entries.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.dropEntry(parsedKey);\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Mark an entry as most recently used (only tracked when `maxSize` is set).\n */\n protected touch(parsedKey: string, entry: CacheData) {\n if (!this.options.maxSize) return;\n\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, entry);\n }\n\n /**\n * When true, every live read slides the entry's expiration forward by its\n * ttl (the `memoryExtended` driver). Set in a subclass constructor rather\n * than redeclared, so subclasses stay structurally compatible with this\n * class (the `client` type relies on that).\n */\n protected slidingExpiration = false;\n\n /**\n * Invoked on a live read hit, before the value is returned. Runs only from\n * `get()` after the expiry check, so an expired entry is never resurrected,\n * and never from the internal existence check used by `onConflict`.\n */\n protected onRead(parsedKey: string, entry: CacheData) {\n if (!this.slidingExpiration) {\n return;\n }\n\n const rawTtl = entry.ttl ?? this.options.ttl;\n const ttl = rawTtl !== undefined ? parseTtl(rawTtl) : undefined;\n\n if (ttl && Number.isFinite(ttl)) {\n entry.expiresAt = this.getExpiresAt(ttl);\n this.expiry.set(parsedKey, entry.expiresAt as number);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.log(\"clearing\", namespace);\n\n namespace = this.parseKey(namespace);\n\n this.tagIndex.removeNamespace(namespace);\n\n if (namespace === \"\") {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n } else {\n const prefix = namespace + \".\";\n\n for (const key of [...this.entries.keys()]) {\n if (key === namespace || key.startsWith(prefix)) {\n this.dropEntry(key);\n }\n }\n }\n\n this.log(\"cleared\", namespace);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The conditional check and the write run with no `await` in between, so\n * `onConflict` decisions are atomic within the process.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // expired entries count as missing (readLive deletes them)\n const existing = onConflict === \"create\" || onConflict === \"update\" ? this.readLive(parsedKey) : undefined;\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: cloneValue(existing.data) };\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(cloneValue(value), ttl, staleAt);\n\n // delete + set moves the key to the most-recent position\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n } else {\n this.expiry.delete(parsedKey);\n }\n\n if (vector) {\n this.vectorIndex.set(parsedKey, vector.slice());\n } else {\n this.vectorIndex.delete(parsedKey);\n }\n\n this.enforceMaxSize(parsedKey);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The check and the delete run synchronously (no `await` between them), so\n * the compare-and-delete is atomic within the process.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry || entry.data !== expected) {\n return false;\n }\n\n await this.remove(key);\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * entry keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n this.touch(parsedKey, entry);\n } else {\n const data = this.prepareDataForStorage(newValue, this.ttl);\n\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n }\n\n this.enforceMaxSize(parsedKey);\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: entry?.ttl ?? this.ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and delete run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.dropEntry(parsedKey);\n\n const value = cloneValue(entry.data);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.onRead(parsedKey, entry);\n this.touch(parsedKey, entry);\n\n const result = await this.parseCachedData(parsedKey, entry);\n\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: result });\n\n return result;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper (cloned), including `staleAt` metadata.\n * Returns `null` for missing or expired entries so the SWR flow can branch\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const entry = this.readLive(this.parseKey(key));\n\n if (!entry) {\n return null;\n }\n\n return { ...entry, data: cloneValue(entry.data) };\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n this.dropEntry(parsedKey);\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set — concurrent tagged writes never drop members.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n this.tagIndex.clear();\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * Evict least recently used entries (Map insertion order) until the size is\n * within `maxSize`. Never evicts `protectedKey` (the key just written).\n */\n protected enforceMaxSize(protectedKey: string) {\n const maxSize = this.options.maxSize;\n\n if (!maxSize) {\n return;\n }\n\n for (const lruKey of [...this.entries.keys()]) {\n if (this.entries.size <= maxSize) {\n break;\n }\n\n if (lruKey === protectedKey) {\n continue;\n }\n\n this.log(\"removing\", lruKey);\n this.dropEntry(lruKey);\n this.log(\"removed\", lruKey);\n }\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every entry that was written with\n * `set({ vector })`. Suitable for development and small in-memory knowledge\n * bases — not for production beyond ~10k entries. Use the `pg` driver\n * (with pgvector) or `redis` (with RediSearch) at scale.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, stored] of [...this.vectorIndex]) {\n if (tagFilter && !tagFilter.has(parsedKey)) {\n continue;\n }\n\n // Read the entry directly by its already-parsed key — this.get() would\n // parse it again and apply globalPrefix twice, missing every entry.\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n continue;\n }\n\n const value: any = cloneValue(entry.data);\n\n const score = cosineSimilarity(vector, stored);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAS,WAAc,OAAa;CAClC,IAAI,UAAU,QAAQ,UAAU,QAC9B,OAAO;CAGT,MAAM,OAAO,OAAO;CAEpB,IAAI,SAAS,YAAY,SAAS,YAAY,SAAS,WACrD,OAAO;CAGT,OAAO,gBAAgB,KAAK;AAC9B;AAEA,IAAa,oBAAb,cACU,gBAEV;;;;;CAiBE,IAAW,OAAkC;EAC3C,OAAO,OAAO,YAAY,KAAK,OAAO;CACxC;;;;CA8BA,AAAO,cAAc;EACnB,MAAM;cA9CM;iCAO8B,IAAI,IAAI;gCAcZ,IAAI,IAAI;qCAaD,IAAI,IAAI;kBAMhB,IAAI,iBAAiB;2BA8F9B;EAtF5B,KAAK,aAAa;CACpB;;;;CAKA,AAAO,eAAe;EAEpB,IAAI,KAAK,iBACP,cAAc,KAAK,eAAe;EAGpC,KAAK,kBAAkB,YAAY,YAAY;GAC7C,MAAM,MAAM,KAAK,IAAI;GACrB,MAAM,cAAwB,CAAC;GAE/B,KAAK,MAAM,CAAC,KAAK,cAAc,KAAK,QAClC,IAAI,aAAa,KACf,YAAY,KAAK,GAAG;GAIxB,KAAK,MAAM,OAAO,aAAa;IAE7B,MAAM,YAAY,KAAK,OAAO,IAAI,GAAG;IAErC,IAAI,cAAc,UAAa,YAAY,KACzC;IAGF,KAAK,UAAU,GAAG;IAElB,KAAK,IAAI,WAAW,GAAG;IAEvB,MAAM,KAAK,KAAK,WAAW,EAAE,IAAI,CAAC;GACpC;EACF,GAAG,GAAI;EAGP,KAAK,gBAAgB,MAAM;CAC7B;;;;CAKA,AAAU,UAAU,WAAmB;EACrC,KAAK,QAAQ,OAAO,SAAS;EAC7B,KAAK,OAAO,OAAO,SAAS;EAC5B,KAAK,YAAY,OAAO,SAAS;CACnC;;;;;CAMA,AAAU,SAAS,WAA0C;EAC3D,MAAM,QAAQ,KAAK,QAAQ,IAAI,SAAS;EAExC,IAAI,CAAC,OACH;EAGF,IAAI,MAAM,cAAc,UAAa,MAAM,aAAa,KAAK,IAAI,GAAG;GAClE,KAAK,UAAU,SAAS;GACxB;EACF;EAEA,OAAO;CACT;;;;CAKA,AAAU,MAAM,WAAmB,OAAkB;EACnD,IAAI,CAAC,KAAK,QAAQ,SAAS;EAE3B,KAAK,QAAQ,OAAO,SAAS;EAC7B,KAAK,QAAQ,IAAI,WAAW,KAAK;CACnC;;;;;;CAeA,AAAU,OAAO,WAAmB,OAAkB;EACpD,IAAI,CAAC,KAAK,mBACR;EAGF,MAAM,SAAS,MAAM,OAAO,KAAK,QAAQ;EACzC,MAAM,MAAM,WAAW,SAAY,SAAS,MAAM,IAAI;EAEtD,IAAI,OAAO,OAAO,SAAS,GAAG,GAAG;GAC/B,MAAM,YAAY,KAAK,aAAa,GAAG;GACvC,KAAK,OAAO,IAAI,WAAW,MAAM,SAAmB;EACtD;CACF;;;;CAKA,MAAa,gBAAgB,WAAmB;EAC9C,KAAK,IAAI,YAAY,SAAS;EAE9B,YAAY,KAAK,SAAS,SAAS;EAEnC,KAAK,SAAS,gBAAgB,SAAS;EAEvC,IAAI,cAAc,IAAI;GACpB,KAAK,QAAQ,MAAM;GACnB,KAAK,OAAO,MAAM;GAClB,KAAK,YAAY,MAAM;EACzB,OAAO;GACL,MAAM,SAAS,YAAY;GAE3B,KAAK,MAAM,OAAO,CAAC,GAAG,KAAK,QAAQ,KAAK,CAAC,GACvC,IAAI,QAAQ,aAAa,IAAI,WAAW,MAAM,GAC5C,KAAK,UAAU,GAAG;EAGxB;EAEA,KAAK,IAAI,WAAW,SAAS;EAE7B,OAAO;CACT;;;;;;;CAQA,MAAa,IACX,KACA,OACA,cACc;EACd,MAAM,YAAY,KAAK,SAAS,GAAG;EACnC,MAAM,EAAE,KAAK,MAAM,YAAY,QAAQ,YAAY,KAAK,kBAAkB,YAAY;EAEtF,KAAK,IAAI,WAAW,SAAS;EAG7B,MAAM,WAAW,eAAe,YAAY,eAAe,WAAW,KAAK,SAAS,SAAS,IAAI;EAEjG,IAAI,eAAe,YAAY,UAE7B,OAAO;GAD0B,QAAQ;GAAO,UAAU,WAAW,SAAS,IAAI;EACtE;EAGd,IAAI,eAAe,YAAY,CAAC,UAE9B,OAAO;GAD0B,QAAQ;GAAO,UAAU;EAC9C;EAGd,MAAM,OAAO,KAAK,sBAAsB,WAAW,KAAK,GAAG,KAAK,OAAO;EAGvE,KAAK,QAAQ,OAAO,SAAS;EAC7B,KAAK,QAAQ,IAAI,WAAW,IAAI;EAEhC,IAAI,KAAK,cAAc,UAAa,OAAO,SAAS,KAAK,SAAS,GAChE,KAAK,OAAO,IAAI,WAAW,KAAK,SAAS;OAEzC,KAAK,OAAO,OAAO,SAAS;EAG9B,IAAI,QACF,KAAK,YAAY,IAAI,WAAW,OAAO,MAAM,CAAC;OAE9C,KAAK,YAAY,OAAO,SAAS;EAGnC,KAAK,eAAe,SAAS;EAE7B,IAAI,QAAQ,KAAK,SAAS,GACxB,MAAM,KAAK,UAAU,KAAK,IAAI;EAGhC,KAAK,IAAI,UAAU,SAAS;EAE5B,MAAM,KAAK,KAAK,OAAO;GAAE,KAAK;GAAW;GAAO;EAAI,CAAC;EAErD,IAAI,eAAe,YAAY,eAAe,UAE5C,OAAO;GAD0B,QAAQ;GAAM,UAAU;EAC7C;EAGd,OAAO;CACT;;;;;;;CAQA,MAAgB,eAAe,KAAe,UAAqC;EACjF,MAAM,YAAY,KAAK,SAAS,GAAG;EACnC,MAAM,QAAQ,KAAK,SAAS,SAAS;EAErC,IAAI,CAAC,SAAS,MAAM,SAAS,UAC3B,OAAO;EAGT,MAAM,KAAK,OAAO,GAAG;EAErB,OAAO;CACT;;;;;;;;CASA,MAAa,UAAU,KAAe,QAAgB,GAAoB;EACxE,MAAM,YAAY,KAAK,SAAS,GAAG;EACnC,MAAM,QAAQ,KAAK,SAAS,SAAS;EACrC,MAAM,UAAU,QAAQ,MAAM,OAAO;EAErC,IAAI,OAAO,YAAY,UACrB,MAAM,IAAI,MAAM,+CAA+C,iBAAiB,SAAS,GAAG;EAG9F,MAAM,WAAW,UAAU;EAE3B,IAAI,OAAO;GACT,MAAM,OAAO;GACb,KAAK,MAAM,WAAW,KAAK;EAC7B,OAAO;GACL,MAAM,OAAO,KAAK,sBAAsB,UAAU,KAAK,GAAG;GAE1D,KAAK,QAAQ,IAAI,WAAW,IAAI;GAEhC,IAAI,KAAK,cAAc,UAAa,OAAO,SAAS,KAAK,SAAS,GAChE,KAAK,OAAO,IAAI,WAAW,KAAK,SAAS;GAG3C,KAAK,eAAe,SAAS;EAC/B;EAEA,MAAM,KAAK,KAAK,OAAO;GAAE,KAAK;GAAW,OAAO;GAAU,KAAK,OAAO,OAAO,KAAK;EAAI,CAAC;EAEvF,OAAO;CACT;;;;;;;CAQA,MAAa,KAAK,KAAoC;EACpD,MAAM,YAAY,KAAK,SAAS,GAAG;EACnC,MAAM,QAAQ,KAAK,SAAS,SAAS;EAErC,IAAI,CAAC,OAAO;GACV,MAAM,KAAK,KAAK,QAAQ,EAAE,KAAK,UAAU,CAAC;GAE1C,OAAO;EACT;EAEA,KAAK,UAAU,SAAS;EAExB,MAAM,QAAQ,WAAW,MAAM,IAAI;EAEnC,MAAM,KAAK,KAAK,OAAO;GAAE,KAAK;GAAW;EAAM,CAAC;EAChD,MAAM,KAAK,KAAK,WAAW,EAAE,KAAK,UAAU,CAAC;EAE7C,OAAO;CACT;;;;CAKA,MAAa,IAAI,KAAe;EAC9B,MAAM,YAAY,KAAK,SAAS,GAAG;EAEnC,KAAK,IAAI,YAAY,SAAS;EAE9B,MAAM,QAAQ,KAAK,SAAS,SAAS;EAErC,IAAI,CAAC,OAAO;GACV,KAAK,IAAI,YAAY,SAAS;GAE9B,MAAM,KAAK,KAAK,QAAQ,EAAE,KAAK,UAAU,CAAC;GAC1C,OAAO;EACT;EAEA,KAAK,OAAO,WAAW,KAAK;EAC5B,KAAK,MAAM,WAAW,KAAK;EAE3B,MAAM,SAAS,MAAM,KAAK,gBAAgB,WAAW,KAAK;EAG1D,MAAM,KAAK,KAAK,OAAO;GAAE,KAAK;GAAW,OAAO;EAAO,CAAC;EAExD,OAAO;CACT;;;;;;CAOA,MAAgB,SAAS,KAA0C;EACjE,MAAM,QAAQ,KAAK,SAAS,KAAK,SAAS,GAAG,CAAC;EAE9C,IAAI,CAAC,OACH,OAAO;EAGT,OAAO;GAAE,GAAG;GAAO,MAAM,WAAW,MAAM,IAAI;EAAE;CAClD;;;;CAKA,MAAa,OAAO,KAAe;EACjC,MAAM,YAAY,KAAK,SAAS,GAAG;EAEnC,KAAK,IAAI,YAAY,SAAS;EAE9B,KAAK,UAAU,SAAS;EAExB,KAAK,IAAI,WAAW,SAAS;EAG7B,MAAM,KAAK,KAAK,WAAW,EAAE,KAAK,UAAU,CAAC;CAC/C;;;;;;CAOA,MAAa,OAAO,QAAkB,SAAkC;EACtE,KAAK,SAAS,IAAI,KAAK,SAAS,MAAM,GAAG,OAAO;CAClD;;;;CAKA,MAAa,WAAW,QAAqC;EAC3D,OAAO,KAAK,SAAS,QAAQ,KAAK,SAAS,MAAM,CAAC;CACpD;;;;CAKA,MAAa,UAAU,QAAkB,SAAkC;EACzE,KAAK,SAAS,OAAO,KAAK,SAAS,MAAM,GAAG,OAAO;CACrD;;;;CAKA,MAAa,UAAU,QAAiC;EACtD,KAAK,SAAS,OAAO,KAAK,SAAS,MAAM,CAAC;CAC5C;;;;CAKA,MAAa,QAAQ;EACnB,KAAK,IAAI,UAAU;EACnB,IAAI,KAAK,QAAQ,cACf,MAAM,KAAK,gBAAgB,EAAE;OACxB;GACL,KAAK,QAAQ,MAAM;GACnB,KAAK,OAAO,MAAM;GAClB,KAAK,YAAY,MAAM;GACvB,KAAK,SAAS,MAAM;EACtB;EAEA,KAAK,IAAI,SAAS;EAGlB,MAAM,KAAK,KAAK,SAAS;CAC3B;;;;;CAMA,AAAU,eAAe,cAAsB;EAC7C,MAAM,UAAU,KAAK,QAAQ;EAE7B,IAAI,CAAC,SACH;EAGF,KAAK,MAAM,UAAU,CAAC,GAAG,KAAK,QAAQ,KAAK,CAAC,GAAG;GAC7C,IAAI,KAAK,QAAQ,QAAQ,SACvB;GAGF,IAAI,WAAW,cACb;GAGF,KAAK,IAAI,YAAY,MAAM;GAC3B,KAAK,UAAU,MAAM;GACrB,KAAK,IAAI,WAAW,MAAM;EAC5B;CACF;;;;;;;;;;;CAYA,MAAa,QACX,QACA,SAC+B;EAC/B,MAAM,YAAY,MAAM,KAAK,eAAe,QAAQ,IAAI;EAExD,MAAM,OAA6B,CAAC;EAEpC,KAAK,MAAM,CAAC,WAAW,WAAW,CAAC,GAAG,KAAK,WAAW,GAAG;GACvD,IAAI,aAAa,CAAC,UAAU,IAAI,SAAS,GACvC;GAKF,MAAM,QAAQ,KAAK,SAAS,SAAS;GAErC,IAAI,CAAC,OACH;GAGF,MAAM,QAAa,WAAW,MAAM,IAAI;GAExC,MAAM,QAAQ,iBAAiB,QAAQ,MAAM;GAE7C,IAAI,QAAQ,cAAc,UAAa,QAAQ,QAAQ,WACrD;GAGF,KAAK,KAAK;IAAE,KAAK;IAAW;IAAO;GAAM,CAAC;EAC5C;EAEA,KAAK,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;EAErC,IAAI,QAAQ,QAAQ,KAAK,KAAK,SAAS,QAAQ,MAC7C,KAAK,SAAS,QAAQ;EAGxB,OAAO;CACT;;;;CAKA,MAAa,aAAa;EAExB,IAAI,KAAK,iBAAiB;GACxB,cAAc,KAAK,eAAe;GAClC,KAAK,kBAAkB;EACzB;EAEA,MAAM,MAAM,WAAW;CACzB;AACF"}
|
|
1
|
+
{"version":3,"file":"memory-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheSimilarHit,\n CacheSimilarOptions,\n CacheTtl,\n MemoryCacheOptions,\n} from \"../types\";\nimport { cosineSimilarity, parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * Clone non-primitive values so cached state can't be mutated through\n * references held by callers. Primitives are immutable and returned as-is.\n */\nfunction cloneValue<T>(value: T): T {\n if (value === null || value === undefined) {\n return value;\n }\n\n const type = typeof value;\n\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n return value;\n }\n\n return structuredClone(value);\n}\n\nexport class MemoryCacheDriver\n extends BaseCacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n implements CacheDriver<MemoryCacheDriver, MemoryCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memory\";\n\n /**\n * Flat storage keyed by the parsed key. Insertion order doubles as LRU\n * order (least recently used first) — accessed entries are deleted and\n * re-inserted.\n */\n protected entries: Map<string, CacheData> = new Map();\n\n /**\n * Read-only object view of the stored entries, keyed by parsed key.\n * Kept for introspection; the values are the live stored wrappers.\n */\n public get data(): Record<string, CacheData> {\n return Object.fromEntries(this.entries);\n }\n\n /**\n * Expiry records for entries with a FINITE ttl only (parsed key => expiresAt ms).\n * The sweep iterates just these.\n */\n protected expiry: Map<string, number> = new Map();\n\n /**\n * Cleanup interval reference\n */\n protected cleanupInterval?: NodeJS.Timeout;\n\n /**\n * Parallel vector index keyed by parsedKey. Populated by `set({ vector })`,\n * scanned by `similar()`. Lifetime mirrors the main entry — cleared on\n * `remove`, `flush`, expiry, namespace clear, overwrite without a vector,\n * and LRU eviction.\n */\n protected vectorIndex: Map<string, number[]> = new Map();\n\n /**\n * Tag index, kept apart from `entries`: never evicted by `maxSize`, never\n * swept by TTL, and updated synchronously.\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * {@inheritdoc}\n */\n public constructor() {\n super();\n\n this.startCleanup();\n }\n\n /**\n * Start the cleanup process for entries that carry a finite ttl\n */\n public startCleanup() {\n // Clear existing interval if any\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n this.cleanupInterval = setInterval(async () => {\n const now = Date.now();\n const expiredKeys: string[] = [];\n\n for (const [key, expiresAt] of this.expiry) {\n if (expiresAt <= now) {\n expiredKeys.push(key);\n }\n }\n\n for (const key of expiredKeys) {\n // the entry may have been rewritten/slid since the snapshot\n const expiresAt = this.expiry.get(key);\n\n if (expiresAt === undefined || expiresAt > now) {\n continue;\n }\n\n this.dropEntry(key);\n\n this.log(\"expired\", key);\n // Emit expired event\n await this.emit(\"expired\", { key });\n }\n }, 1000);\n\n // do not block the process from exiting\n this.cleanupInterval.unref();\n }\n\n /**\n * Delete an entry and every side record (expiry, vector). Synchronous.\n */\n protected dropEntry(parsedKey: string) {\n this.entries.delete(parsedKey);\n this.expiry.delete(parsedKey);\n this.vectorIndex.delete(parsedKey);\n }\n\n /**\n * Raw, synchronous read of a live entry. An expired entry is deleted and\n * reported as missing. Never slides TTLs, never emits, never touches LRU order.\n */\n protected readLive(parsedKey: string): CacheData | undefined {\n const entry = this.entries.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.dropEntry(parsedKey);\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Mark an entry as most recently used (only tracked when `maxSize` is set).\n */\n protected touch(parsedKey: string, entry: CacheData) {\n if (!this.options.maxSize) return;\n\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, entry);\n }\n\n /**\n * When true, every live read slides the entry's expiration forward by its\n * ttl (the `memoryExtended` driver). Set in a subclass constructor rather\n * than redeclared, so subclasses stay structurally compatible with this\n * class (the `client` type relies on that).\n */\n protected slidingExpiration = false;\n\n /**\n * Invoked on a live read hit, before the value is returned. Runs only from\n * `get()` after the expiry check, so an expired entry is never resurrected,\n * and never from the internal existence check used by `onConflict`.\n */\n protected onRead(parsedKey: string, entry: CacheData) {\n if (!this.slidingExpiration) {\n return;\n }\n\n const rawTtl = entry.ttl ?? this.options.ttl;\n const ttl = rawTtl !== undefined ? parseTtl(rawTtl) : undefined;\n\n if (ttl && Number.isFinite(ttl)) {\n entry.expiresAt = this.getExpiresAt(ttl);\n this.expiry.set(parsedKey, entry.expiresAt as number);\n }\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.log(\"clearing\", namespace);\n\n namespace = this.parseKey(namespace);\n\n this.tagIndex.removeNamespace(namespace);\n\n if (namespace === \"\") {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n } else {\n const prefix = namespace + \".\";\n\n for (const key of [...this.entries.keys()]) {\n if (key === namespace || key.startsWith(prefix)) {\n this.dropEntry(key);\n }\n }\n }\n\n this.log(\"cleared\", namespace);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The conditional check and the write run with no `await` in between, so\n * `onConflict` decisions are atomic within the process.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.log(\"caching\", parsedKey);\n\n // expired entries count as missing (readLive deletes them)\n const existing =\n onConflict === \"create\" || onConflict === \"update\" ? this.readLive(parsedKey) : undefined;\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: cloneValue(existing.data) };\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n return result;\n }\n\n const data = this.prepareDataForStorage(cloneValue(value), ttl, staleAt);\n\n // delete + set moves the key to the most-recent position\n this.entries.delete(parsedKey);\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n } else {\n this.expiry.delete(parsedKey);\n }\n\n if (vector) {\n this.vectorIndex.set(parsedKey, vector.slice());\n } else {\n this.vectorIndex.delete(parsedKey);\n }\n\n this.enforceMaxSize(parsedKey);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n return result;\n }\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n *\n * The check and the delete run synchronously (no `await` between them), so\n * the compare-and-delete is atomic within the process.\n */\n protected async deleteIfEquals(key: CacheKey, expected: unknown): Promise<boolean> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry || entry.data !== expected) {\n return false;\n }\n\n await this.remove(key);\n\n return true;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read, add and write run synchronously (no `await` in between), so\n * concurrent increments in this process never lose an update. An existing\n * entry keeps its expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n if (\n options.ttl !== undefined &&\n !Number.isFinite(entry.expiresAt) &&\n Number.isFinite(ttl) &&\n ttl > 0\n ) {\n entry.ttl = ttl;\n entry.expiresAt = Date.now() + ttl * 1000;\n this.expiry.set(parsedKey, entry.expiresAt);\n }\n this.touch(parsedKey, entry);\n } else {\n const data = this.prepareDataForStorage(newValue, ttl);\n\n this.entries.set(parsedKey, data);\n\n if (data.expiresAt !== undefined && Number.isFinite(data.expiresAt)) {\n this.expiry.set(parsedKey, data.expiresAt);\n }\n\n this.enforceMaxSize(parsedKey);\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: entry?.ttl ?? ttl });\n\n return newValue;\n }\n\n /**\n * {@inheritdoc}\n *\n * Read and delete run synchronously, so a value is handed out exactly once\n * even when concurrent callers pull the same key.\n */\n public async pull(key: CacheKey): Promise<any | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.dropEntry(parsedKey);\n\n const value = cloneValue(entry.data);\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n this.log(\"notFound\", parsedKey);\n // Emit miss event\n await this.emit(\"miss\", { key: parsedKey });\n return null;\n }\n\n this.onRead(parsedKey, entry);\n this.touch(parsedKey, entry);\n\n const result = await this.parseCachedData(parsedKey, entry);\n\n // Emit hit event\n await this.emit(\"hit\", { key: parsedKey, value: result });\n\n return result;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper (cloned), including `staleAt` metadata.\n * Returns `null` for missing or expired entries so the SWR flow can branch\n * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const entry = this.readLive(this.parseKey(key));\n\n if (!entry) {\n return null;\n }\n\n return { ...entry, data: cloneValue(entry.data) };\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n this.dropEntry(parsedKey);\n\n this.log(\"removed\", parsedKey);\n\n // Emit removed event\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set — concurrent tagged writes never drop members.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n if (this.options.globalPrefix) {\n await this.removeNamespace(\"\");\n } else {\n this.entries.clear();\n this.expiry.clear();\n this.vectorIndex.clear();\n this.tagIndex.clear();\n }\n\n this.log(\"flushed\");\n\n // Emit flushed event\n await this.emit(\"flushed\");\n }\n\n /**\n * Evict least recently used entries (Map insertion order) until the size is\n * within `maxSize`. Never evicts `protectedKey` (the key just written).\n */\n protected enforceMaxSize(protectedKey: string) {\n const maxSize = this.options.maxSize;\n\n if (!maxSize) {\n return;\n }\n\n for (const lruKey of [...this.entries.keys()]) {\n if (this.entries.size <= maxSize) {\n break;\n }\n\n if (lruKey === protectedKey) {\n continue;\n }\n\n this.log(\"removing\", lruKey);\n this.dropEntry(lruKey);\n this.log(\"removed\", lruKey);\n }\n }\n\n /**\n * {@inheritdoc}\n *\n * Brute-force O(N) cosine similarity over every entry that was written with\n * `set({ vector })`. Suitable for development and small in-memory knowledge\n * bases — not for production beyond ~10k entries. Use the `pg` driver\n * (with pgvector) or `redis` (with RediSearch) at scale.\n *\n * @warning Dev-only — O(N) per query.\n */\n public async similar<T = any>(\n vector: number[],\n options: CacheSimilarOptions,\n ): Promise<CacheSimilarHit<T>[]> {\n const tagFilter = await this.getKeysForTags(options.tags);\n\n const hits: CacheSimilarHit<T>[] = [];\n\n for (const [parsedKey, stored] of [...this.vectorIndex]) {\n if (tagFilter && !tagFilter.has(parsedKey)) {\n continue;\n }\n\n // Read the entry directly by its already-parsed key — this.get() would\n // parse it again and apply globalPrefix twice, missing every entry.\n const entry = this.readLive(parsedKey);\n\n if (!entry) {\n continue;\n }\n\n const value: any = cloneValue(entry.data);\n\n const score = cosineSimilarity(vector, stored);\n\n if (options.threshold !== undefined && score < options.threshold) {\n continue;\n }\n\n hits.push({ key: parsedKey, value, score });\n }\n\n hits.sort((a, b) => b.score - a.score);\n\n if (options.topK >= 0 && hits.length > options.topK) {\n hits.length = options.topK;\n }\n\n return hits;\n }\n\n /**\n * {@inheritdoc}\n */\n public async disconnect() {\n // Clear the cleanup interval to prevent memory leaks\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n\n await super.disconnect();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MemoryCacheDriver } from "./memory-cache-driver.mjs";
|
|
2
2
|
import { CacheDriver, MemoryExtendedCacheOptions } from "../types.mjs";
|
|
3
|
-
|
|
4
3
|
//#region ../../packages/cache/src/drivers/memory-extended-cache-driver.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Memory driver with a sliding ttl: every live read pushes the entry's
|
|
@@ -9,7 +8,7 @@ import { CacheDriver, MemoryExtendedCacheOptions } from "../types.mjs";
|
|
|
9
8
|
* is never resurrected and the internal `onConflict` existence check never
|
|
10
9
|
* slides a held lock.
|
|
11
10
|
*/
|
|
12
|
-
declare class MemoryExtendedCacheDriver extends MemoryCacheDriver implements CacheDriver<MemoryExtendedCacheDriver, MemoryExtendedCacheOptions> {
|
|
11
|
+
export declare class MemoryExtendedCacheDriver extends MemoryCacheDriver implements CacheDriver<MemoryExtendedCacheDriver, MemoryExtendedCacheOptions> {
|
|
13
12
|
/**
|
|
14
13
|
* {@inheritdoc}
|
|
15
14
|
*/
|
|
@@ -17,5 +16,4 @@ declare class MemoryExtendedCacheDriver extends MemoryCacheDriver implements Cac
|
|
|
17
16
|
constructor();
|
|
18
17
|
}
|
|
19
18
|
//#endregion
|
|
20
|
-
export { MemoryExtendedCacheDriver };
|
|
21
19
|
//# sourceMappingURL=memory-extended-cache-driver.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-extended-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-extended-cache-driver.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"memory-extended-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/memory-extended-cache-driver.ts"],"mappings":";;;;;;;;;;qBAUa,kCACH,6BACG,YAAY,2BAA2B;;;;EAK3C;EAEP"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { BaseCacheDriver } from "./base-cache-driver.mjs";
|
|
2
2
|
import { InMemoryTagIndex } from "./in-memory-tag-index.mjs";
|
|
3
3
|
import { CacheCall, CacheData, CacheDriver, CacheKey, CacheSetOptions, CacheTtl, MockCacheOptions } from "../types.mjs";
|
|
4
|
-
|
|
5
4
|
//#region ../../packages/cache/src/drivers/mock-cache-driver.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* In-memory cache driver with introspection helpers, intended for use as a
|
|
@@ -44,7 +43,7 @@ import { CacheCall, CacheData, CacheDriver, CacheKey, CacheSetOptions, CacheTtl,
|
|
|
44
43
|
* expect(driver.wasCalled("remove", "users.42")).toBe(true);
|
|
45
44
|
* });
|
|
46
45
|
*/
|
|
47
|
-
declare class MockCacheDriver extends BaseCacheDriver<MockCacheDriver, MockCacheOptions> implements CacheDriver<MockCacheDriver, MockCacheOptions> {
|
|
46
|
+
export declare class MockCacheDriver extends BaseCacheDriver<MockCacheDriver, MockCacheOptions> implements CacheDriver<MockCacheDriver, MockCacheOptions> {
|
|
48
47
|
/**
|
|
49
48
|
* {@inheritdoc}
|
|
50
49
|
*/
|
|
@@ -105,7 +104,9 @@ declare class MockCacheDriver extends BaseCacheDriver<MockCacheDriver, MockCache
|
|
|
105
104
|
* Synchronous read-add-write (atomic within the process). Keeps an existing
|
|
106
105
|
* entry's expiry; a missing key starts from 0 with the default TTL.
|
|
107
106
|
*/
|
|
108
|
-
increment(key: CacheKey, value?: number
|
|
107
|
+
increment(key: CacheKey, value?: number, options?: {
|
|
108
|
+
ttl?: CacheTtl;
|
|
109
|
+
}): Promise<number>;
|
|
109
110
|
/**
|
|
110
111
|
* Synchronous read-and-delete: a value is handed out exactly once.
|
|
111
112
|
*/
|
|
@@ -174,5 +175,4 @@ declare class MockCacheDriver extends BaseCacheDriver<MockCacheDriver, MockCache
|
|
|
174
175
|
protected recordCall(operation: string, key: string | undefined, args?: unknown[]): void;
|
|
175
176
|
}
|
|
176
177
|
//#endregion
|
|
177
|
-
export { MockCacheDriver };
|
|
178
178
|
//# sourceMappingURL=mock-cache-driver.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"mock-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAwDa,wBACH,gBAAgB,iBAAiB,6BAC9B,YAAY,iBAAiB;;;;EAKjC;;;;EAKA,SAAS;;;;;WAMA,SAAS,YAAY;;;;YAK3B,UAAU;;;;;;WAOJ,SAAS;;;;;EAMZ,WAAW;;;;EAQX,cAAc;;;;;EASd,gBAAgB,oBAAoB;;;;;EAuBpC,IACX,KAAK,UACL,YACA,eAAe,WAAW,kBACzB;;;;YAmDO,WAAW,GAAG,OAAO,IAAI;;;;;EAYtB,IAAI,SAAS,KAAK,WAAW,QAAQ;;;;;YAkCxC,UAAU,oBAAoB;;;;;EAoB3B,UACX,KAAK,UACL,gBACA;IAAW,MAAM;MAChB;;;;EAsCU,KAAK,SAAS,KAAK,WAAW,QAAQ;;;;;;YA4BnC,SAAS,KAAK,WAAW,QAAQ;;;;EAkBpC,OAAO,KAAK,WAAW;;;;;;EAiBvB,OAAO,QAAQ,UAAU,oBAAoB;;;;EAO7C,WAAW,QAAQ,WAAW;;;;EAO9B,UAAU,QAAQ,UAAU,oBAAoB;;;;EAOhD,UAAU,QAAQ,WAAW;;;;;EAQ7B,SAAS;;;;;;;;;;;EAqBf,UAAU,mBAAmB,MAAM;;;;;;;;EAiBnC,UAAU,SAAS,KAAK,WAAW;;;;;EAenC;;;;;YAUG,WAAW,mBAAmB,yBAAyB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseTtl } from "../utils.mjs";
|
|
1
2
|
import { BaseCacheDriver } from "./base-cache-driver.mjs";
|
|
2
3
|
import { InMemoryTagIndex } from "./in-memory-tag-index.mjs";
|
|
3
4
|
import { maskEmailsInText } from "@warlock.js/logger";
|
|
@@ -168,19 +169,25 @@ var MockCacheDriver = class extends BaseCacheDriver {
|
|
|
168
169
|
* Synchronous read-add-write (atomic within the process). Keeps an existing
|
|
169
170
|
* entry's expiry; a missing key starts from 0 with the default TTL.
|
|
170
171
|
*/
|
|
171
|
-
async increment(key, value = 1) {
|
|
172
|
+
async increment(key, value = 1, options = {}) {
|
|
173
|
+
const ttl = options.ttl === void 0 ? this.ttl : parseTtl(options.ttl);
|
|
172
174
|
const parsedKey = this.parseKey(key);
|
|
173
175
|
this.recordCall("increment", parsedKey, [value]);
|
|
174
176
|
const entry = this.liveEntry(parsedKey);
|
|
175
177
|
const current = entry ? entry.data : 0;
|
|
176
178
|
if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);
|
|
177
179
|
const newValue = current + value;
|
|
178
|
-
if (entry)
|
|
179
|
-
|
|
180
|
+
if (entry) {
|
|
181
|
+
entry.data = newValue;
|
|
182
|
+
if (options.ttl !== void 0 && !Number.isFinite(entry.expiresAt) && Number.isFinite(ttl) && ttl > 0) {
|
|
183
|
+
entry.ttl = ttl;
|
|
184
|
+
entry.expiresAt = Date.now() + ttl * 1e3;
|
|
185
|
+
}
|
|
186
|
+
} else this.storage.set(parsedKey, this.prepareDataForStorage(newValue, ttl));
|
|
180
187
|
await this.emit("set", {
|
|
181
188
|
key: parsedKey,
|
|
182
189
|
value: newValue,
|
|
183
|
-
ttl
|
|
190
|
+
ttl
|
|
184
191
|
});
|
|
185
192
|
return newValue;
|
|
186
193
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheCall,\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n MockCacheOptions,\n} from \"../types\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * In-memory cache driver with introspection helpers, intended for use as a\n * test double in downstream packages.\n *\n * **Role.** Drop-in replacement for any real driver in test setups, with\n * extra surface that makes behavioral assertions easy: every public op gets\n * recorded into {@link MockCacheDriver.callLog}, and {@link wasCalled} /\n * {@link getStored} / {@link reset} let tests verify side effects without\n * pulling in a real Redis / Postgres / file system.\n *\n * **Responsibility.**\n * - Owns: in-memory storage backed by a `Map`, the `callLog`, TTL handling\n * via `parseCachedData`, `onConflict` policies, and tag-index storage.\n * - Does NOT own: similarity retrieval (vectors are recorded into the\n * `callLog` but `similar()` throws — use `MemoryCacheDriver` for tests\n * that need real nearest-neighbor scoring), connection lifecycle\n * (no-op `connect`/`disconnect`), or eviction (no `maxSize`).\n *\n * Register it like any other driver — sub-paths are not part of the\n * package's export convention; the same single barrel ships it next to\n * the production drivers.\n *\n * @example\n * import { cache, MockCacheDriver } from \"@warlock.js/cache\";\n *\n * beforeEach(async () => {\n * cache.setCacheConfigurations({\n * default: \"mock\",\n * drivers: { mock: MockCacheDriver },\n * options: { mock: {} },\n * });\n * await cache.init();\n * });\n *\n * it(\"invalidates the user cache after update\", async () => {\n * await userService.update(42, { name: \"Jane\" });\n *\n * const driver = cache.currentDriver as MockCacheDriver;\n * expect(driver.wasCalled(\"remove\", \"users.42\")).toBe(true);\n * });\n */\nexport class MockCacheDriver\n extends BaseCacheDriver<MockCacheDriver, MockCacheOptions>\n implements CacheDriver<MockCacheDriver, MockCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"mock\";\n\n /**\n * {@inheritdoc}\n */\n public options: MockCacheOptions = {};\n\n /**\n * Storage backing the mock — keyed by post-`parseKey` string. Public-readonly\n * so tests can introspect raw entries when {@link getStored} isn't enough.\n */\n public readonly storage: Map<string, CacheData> = new Map();\n\n /**\n * Tag index, kept apart from {@link storage} (same model as the memory driver).\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Ordered record of every public operation routed through this driver.\n * Pushed to before each op runs; tests assert via {@link wasCalled} or by\n * inspecting the array directly.\n */\n public readonly callLog: CacheCall[] = [];\n\n /**\n * Standard driver setup. Mirrors the null driver — no connection, no\n * resources to release.\n */\n public async connect(): Promise<void> {\n this.recordCall(\"connect\", undefined);\n await super.connect();\n }\n\n /**\n * Standard driver teardown.\n */\n public async disconnect(): Promise<void> {\n this.recordCall(\"disconnect\", undefined);\n await super.disconnect();\n }\n\n /**\n * Wipe everything under `namespace`. Matches `MemoryCacheDriver` semantics\n * — the namespace itself and any key with the namespace prefix is removed.\n */\n public async removeNamespace(namespace: string): Promise<void> {\n const parsed = this.parseKey(namespace);\n\n this.recordCall(\"removeNamespace\", parsed, [namespace]);\n this.log(\"clearing\", parsed);\n\n const prefix = parsed + \".\";\n\n this.tagIndex.removeNamespace(parsed);\n\n for (const key of [...this.storage.keys()]) {\n if (key === parsed || key.startsWith(prefix)) {\n this.storage.delete(key);\n }\n }\n\n this.log(\"cleared\", parsed);\n }\n\n /**\n * Standard `set` with full `onConflict` support. Honors scope-default TTL\n * via {@link BaseCacheDriver.resolveSetOptions}.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.recordCall(\"set\", parsedKey, [value, ttlOrOptions]);\n this.log(\"caching\", parsedKey);\n\n // Synchronous check + write (no await between) so onConflict is atomic.\n // Expired entries count as missing and are deleted.\n let existing = onConflict === \"create\" || onConflict === \"update\"\n ? this.storage.get(parsedKey)\n : undefined;\n\n if (existing && existing.expiresAt !== undefined && existing.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n existing = undefined;\n }\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: this.cloneValue(existing.data) };\n\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n\n return result;\n }\n\n const data = this.prepareDataForStorage(this.cloneValue(value), ttl, staleAt);\n this.storage.set(parsedKey, data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n\n return result;\n }\n\n return this;\n }\n\n /**\n * Clone non-primitive values so stored state can't be mutated by callers.\n */\n protected cloneValue<T>(value: T): T {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return value;\n }\n\n return structuredClone(value);\n }\n\n /**\n * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep\n * downstream metrics tests realistic.\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"get\", parsedKey);\n this.log(\"fetching\", parsedKey);\n\n const data = this.storage.get(parsedKey);\n\n if (!data) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = await this.parseCachedData(parsedKey, data);\n\n if (value === null) {\n // expired — the parse helper already logged + queued the cleanup\n this.storage.delete(parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.emit(\"hit\", { key: parsedKey, value });\n\n return value as T;\n }\n\n /**\n * Live entry for `parsedKey`, or `undefined` when missing / expired (an\n * expired entry is deleted). Synchronous.\n */\n protected liveEntry(parsedKey: string): CacheData | undefined {\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Synchronous read-add-write (atomic within the process). Keeps an existing\n * entry's expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(key: CacheKey, value: number = 1): Promise<number> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"increment\", parsedKey, [value]);\n\n const entry = this.liveEntry(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n } else {\n this.storage.set(parsedKey, this.prepareDataForStorage(newValue, this.ttl));\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl: this.ttl });\n\n return newValue;\n }\n\n /**\n * Synchronous read-and-delete: a value is handed out exactly once.\n */\n public async pull<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"pull\", parsedKey);\n\n const entry = this.liveEntry(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.storage.delete(parsedKey);\n\n const value = this.cloneValue(entry.data) as T;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from the in-memory `Map`,\n * including `staleAt` metadata. Returns `null` for missing or expired\n * entries — `swr()` consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return { ...entry, data: this.cloneValue(entry.data) };\n }\n\n /**\n * Standard `remove` — drops the entry and emits the `removed` event.\n */\n public async remove(key: CacheKey): Promise<void> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"remove\", parsedKey);\n this.log(\"removing\", parsedKey);\n\n this.storage.delete(parsedKey);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * Standard `flush` — wipes the entire mock store + tag index. Does NOT\n * touch the call log; use {@link reset} to clear that as well.\n */\n public async flush(): Promise<void> {\n this.recordCall(\"flush\", undefined);\n this.log(\"flushing\");\n\n this.storage.clear();\n this.tagIndex.clear();\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * Was a given operation invoked? When `key` is provided, the match is\n * post-`parseKey` so callers pass the same key shape they used at the\n * call site — strings or objects, both resolve to the same parsed key.\n *\n * @example\n * driver.wasCalled(\"set\"); // any set\n * driver.wasCalled(\"set\", \"users.42\"); // set on this specific key\n * driver.wasCalled(\"set\", { id: 42 }); // same — object key normalized\n */\n public wasCalled(operation: string, key?: CacheKey): boolean {\n if (key === undefined) {\n return this.callLog.some((call) => call.operation === operation);\n }\n\n const parsedKey = this.parseKey(key);\n\n return this.callLog.some(\n (call) => call.operation === operation && call.key === parsedKey,\n );\n }\n\n /**\n * Return the raw stored value for `key`, bypassing TTL handling and clone\n * protection. Useful when a test wants to assert on the persisted shape\n * (or assert that an entry expired without going through `get`).\n *\n * Returns `undefined` when the key isn't present.\n */\n public getStored<T = any>(key: CacheKey): T | undefined {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n return entry.data as T;\n }\n\n /**\n * Wipe everything — storage, tag index, and the call log. Pair with\n * Vitest's `beforeEach` to get clean isolation between tests.\n */\n public reset(): void {\n this.storage.clear();\n this.tagIndex.clear();\n this.callLog.length = 0;\n }\n\n /**\n * Append a row to {@link callLog}. Internal helper called by every\n * recorded op before the actual work runs.\n */\n protected recordCall(\n operation: string,\n key: string | undefined,\n args: unknown[] = [],\n ): void {\n this.callLog.push({\n operation,\n key,\n args,\n timestamp: Date.now(),\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mock-cache-driver.mjs","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/mock-cache-driver.ts"],"sourcesContent":["import { maskEmailsInText } from \"@warlock.js/logger\";\nimport type {\n CacheCall,\n CacheData,\n CacheDriver,\n CacheKey,\n CacheSetOptions,\n CacheSetResult,\n CacheTtl,\n MockCacheOptions,\n} from \"../types\";\nimport { parseTtl } from \"../utils\";\nimport { BaseCacheDriver } from \"./base-cache-driver\";\nimport { InMemoryTagIndex } from \"./in-memory-tag-index\";\n\n/**\n * In-memory cache driver with introspection helpers, intended for use as a\n * test double in downstream packages.\n *\n * **Role.** Drop-in replacement for any real driver in test setups, with\n * extra surface that makes behavioral assertions easy: every public op gets\n * recorded into {@link MockCacheDriver.callLog}, and {@link wasCalled} /\n * {@link getStored} / {@link reset} let tests verify side effects without\n * pulling in a real Redis / Postgres / file system.\n *\n * **Responsibility.**\n * - Owns: in-memory storage backed by a `Map`, the `callLog`, TTL handling\n * via `parseCachedData`, `onConflict` policies, and tag-index storage.\n * - Does NOT own: similarity retrieval (vectors are recorded into the\n * `callLog` but `similar()` throws — use `MemoryCacheDriver` for tests\n * that need real nearest-neighbor scoring), connection lifecycle\n * (no-op `connect`/`disconnect`), or eviction (no `maxSize`).\n *\n * Register it like any other driver — sub-paths are not part of the\n * package's export convention; the same single barrel ships it next to\n * the production drivers.\n *\n * @example\n * import { cache, MockCacheDriver } from \"@warlock.js/cache\";\n *\n * beforeEach(async () => {\n * cache.setCacheConfigurations({\n * default: \"mock\",\n * drivers: { mock: MockCacheDriver },\n * options: { mock: {} },\n * });\n * await cache.init();\n * });\n *\n * it(\"invalidates the user cache after update\", async () => {\n * await userService.update(42, { name: \"Jane\" });\n *\n * const driver = cache.currentDriver as MockCacheDriver;\n * expect(driver.wasCalled(\"remove\", \"users.42\")).toBe(true);\n * });\n */\nexport class MockCacheDriver\n extends BaseCacheDriver<MockCacheDriver, MockCacheOptions>\n implements CacheDriver<MockCacheDriver, MockCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"mock\";\n\n /**\n * {@inheritdoc}\n */\n public options: MockCacheOptions = {};\n\n /**\n * Storage backing the mock — keyed by post-`parseKey` string. Public-readonly\n * so tests can introspect raw entries when {@link getStored} isn't enough.\n */\n public readonly storage: Map<string, CacheData> = new Map();\n\n /**\n * Tag index, kept apart from {@link storage} (same model as the memory driver).\n */\n protected tagIndex: InMemoryTagIndex = new InMemoryTagIndex();\n\n /**\n * Ordered record of every public operation routed through this driver.\n * Pushed to before each op runs; tests assert via {@link wasCalled} or by\n * inspecting the array directly.\n */\n public readonly callLog: CacheCall[] = [];\n\n /**\n * Standard driver setup. Mirrors the null driver — no connection, no\n * resources to release.\n */\n public async connect(): Promise<void> {\n this.recordCall(\"connect\", undefined);\n await super.connect();\n }\n\n /**\n * Standard driver teardown.\n */\n public async disconnect(): Promise<void> {\n this.recordCall(\"disconnect\", undefined);\n await super.disconnect();\n }\n\n /**\n * Wipe everything under `namespace`. Matches `MemoryCacheDriver` semantics\n * — the namespace itself and any key with the namespace prefix is removed.\n */\n public async removeNamespace(namespace: string): Promise<void> {\n const parsed = this.parseKey(namespace);\n\n this.recordCall(\"removeNamespace\", parsed, [namespace]);\n this.log(\"clearing\", parsed);\n\n const prefix = parsed + \".\";\n\n this.tagIndex.removeNamespace(parsed);\n\n for (const key of [...this.storage.keys()]) {\n if (key === parsed || key.startsWith(prefix)) {\n this.storage.delete(key);\n }\n }\n\n this.log(\"cleared\", parsed);\n }\n\n /**\n * Standard `set` with full `onConflict` support. Honors scope-default TTL\n * via {@link BaseCacheDriver.resolveSetOptions}.\n */\n public async set(\n key: CacheKey,\n value: any,\n ttlOrOptions?: CacheTtl | CacheSetOptions,\n ): Promise<any> {\n const parsedKey = this.parseKey(key);\n const { ttl, tags, onConflict, staleAt } = this.resolveSetOptions(ttlOrOptions);\n\n this.recordCall(\"set\", parsedKey, [value, ttlOrOptions]);\n this.log(\"caching\", parsedKey);\n\n // Synchronous check + write (no await between) so onConflict is atomic.\n // Expired entries count as missing and are deleted.\n let existing =\n onConflict === \"create\" || onConflict === \"update\" ? this.storage.get(parsedKey) : undefined;\n\n if (existing && existing.expiresAt !== undefined && existing.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n existing = undefined;\n }\n\n if (onConflict === \"create\" && existing) {\n const result: CacheSetResult = { wasSet: false, existing: this.cloneValue(existing.data) };\n\n return result;\n }\n\n if (onConflict === \"update\" && !existing) {\n const result: CacheSetResult = { wasSet: false, existing: null };\n\n return result;\n }\n\n const data = this.prepareDataForStorage(this.cloneValue(value), ttl, staleAt);\n this.storage.set(parsedKey, data);\n\n if (tags && tags.length > 0) {\n await this.applyTags(key, tags);\n }\n\n this.log(\"cached\", parsedKey);\n await this.emit(\"set\", { key: parsedKey, value, ttl });\n\n if (onConflict === \"create\" || onConflict === \"update\") {\n const result: CacheSetResult = { wasSet: true, existing: null };\n\n return result;\n }\n\n return this;\n }\n\n /**\n * Clone non-primitive values so stored state can't be mutated by callers.\n */\n protected cloneValue<T>(value: T): T {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return value;\n }\n\n return structuredClone(value);\n }\n\n /**\n * Standard `get` with TTL handling. Emits `hit` / `miss` events to keep\n * downstream metrics tests realistic.\n */\n public async get<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"get\", parsedKey);\n this.log(\"fetching\", parsedKey);\n\n const data = this.storage.get(parsedKey);\n\n if (!data) {\n this.log(\"notFound\", parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n const value = await this.parseCachedData(parsedKey, data);\n\n if (value === null) {\n // expired — the parse helper already logged + queued the cleanup\n this.storage.delete(parsedKey);\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n await this.emit(\"hit\", { key: parsedKey, value });\n\n return value as T;\n }\n\n /**\n * Live entry for `parsedKey`, or `undefined` when missing / expired (an\n * expired entry is deleted). Synchronous.\n */\n protected liveEntry(parsedKey: string): CacheData | undefined {\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.storage.delete(parsedKey);\n\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Synchronous read-add-write (atomic within the process). Keeps an existing\n * entry's expiry; a missing key starts from 0 with the default TTL.\n */\n public async increment(\n key: CacheKey,\n value: number = 1,\n options: { ttl?: CacheTtl } = {},\n ): Promise<number> {\n const ttl = options.ttl === undefined ? this.ttl : parseTtl(options.ttl);\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"increment\", parsedKey, [value]);\n\n const entry = this.liveEntry(parsedKey);\n const current = entry ? entry.data : 0;\n\n if (typeof current !== \"number\") {\n throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);\n }\n\n const newValue = current + value;\n\n if (entry) {\n entry.data = newValue;\n if (\n options.ttl !== undefined &&\n !Number.isFinite(entry.expiresAt) &&\n Number.isFinite(ttl) &&\n ttl > 0\n ) {\n entry.ttl = ttl;\n entry.expiresAt = Date.now() + ttl * 1000;\n }\n } else {\n this.storage.set(parsedKey, this.prepareDataForStorage(newValue, ttl));\n }\n\n await this.emit(\"set\", { key: parsedKey, value: newValue, ttl });\n\n return newValue;\n }\n\n /**\n * Synchronous read-and-delete: a value is handed out exactly once.\n */\n public async pull<T = any>(key: CacheKey): Promise<T | null> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"pull\", parsedKey);\n\n const entry = this.liveEntry(parsedKey);\n\n if (!entry) {\n await this.emit(\"miss\", { key: parsedKey });\n\n return null;\n }\n\n this.storage.delete(parsedKey);\n\n const value = this.cloneValue(entry.data) as T;\n\n await this.emit(\"hit\", { key: parsedKey, value });\n await this.emit(\"removed\", { key: parsedKey });\n\n return value;\n }\n\n /**\n * Read the raw {@link CacheData} wrapper from the in-memory `Map`,\n * including `staleAt` metadata. Returns `null` for missing or expired\n * entries — `swr()` consumes this to branch on freshness.\n */\n protected async getEntry(key: CacheKey): Promise<CacheData | null> {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return null;\n }\n\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n return null;\n }\n\n return { ...entry, data: this.cloneValue(entry.data) };\n }\n\n /**\n * Standard `remove` — drops the entry and emits the `removed` event.\n */\n public async remove(key: CacheKey): Promise<void> {\n const parsedKey = this.parseKey(key);\n\n this.recordCall(\"remove\", parsedKey);\n this.log(\"removing\", parsedKey);\n\n this.storage.delete(parsedKey);\n\n this.log(\"removed\", parsedKey);\n await this.emit(\"removed\", { key: parsedKey });\n }\n\n /**\n * {@inheritdoc}\n *\n * Synchronous in-process set, never evicted or expired with the entries.\n */\n public async tagAdd(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.add(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagMembers(tagKey: CacheKey): Promise<string[]> {\n return this.tagIndex.members(this.parseKey(tagKey));\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagRemove(tagKey: CacheKey, members: string[]): Promise<void> {\n this.tagIndex.remove(this.parseKey(tagKey), members);\n }\n\n /**\n * {@inheritdoc}\n */\n public async tagDelete(tagKey: CacheKey): Promise<void> {\n this.tagIndex.delete(this.parseKey(tagKey));\n }\n\n /**\n * Standard `flush` — wipes the entire mock store + tag index. Does NOT\n * touch the call log; use {@link reset} to clear that as well.\n */\n public async flush(): Promise<void> {\n this.recordCall(\"flush\", undefined);\n this.log(\"flushing\");\n\n this.storage.clear();\n this.tagIndex.clear();\n\n this.log(\"flushed\");\n await this.emit(\"flushed\");\n }\n\n /**\n * Was a given operation invoked? When `key` is provided, the match is\n * post-`parseKey` so callers pass the same key shape they used at the\n * call site — strings or objects, both resolve to the same parsed key.\n *\n * @example\n * driver.wasCalled(\"set\"); // any set\n * driver.wasCalled(\"set\", \"users.42\"); // set on this specific key\n * driver.wasCalled(\"set\", { id: 42 }); // same — object key normalized\n */\n public wasCalled(operation: string, key?: CacheKey): boolean {\n if (key === undefined) {\n return this.callLog.some((call) => call.operation === operation);\n }\n\n const parsedKey = this.parseKey(key);\n\n return this.callLog.some((call) => call.operation === operation && call.key === parsedKey);\n }\n\n /**\n * Return the raw stored value for `key`, bypassing TTL handling and clone\n * protection. Useful when a test wants to assert on the persisted shape\n * (or assert that an entry expired without going through `get`).\n *\n * Returns `undefined` when the key isn't present.\n */\n public getStored<T = any>(key: CacheKey): T | undefined {\n const parsedKey = this.parseKey(key);\n const entry = this.storage.get(parsedKey);\n\n if (!entry) {\n return undefined;\n }\n\n return entry.data as T;\n }\n\n /**\n * Wipe everything — storage, tag index, and the call log. Pair with\n * Vitest's `beforeEach` to get clean isolation between tests.\n */\n public reset(): void {\n this.storage.clear();\n this.tagIndex.clear();\n this.callLog.length = 0;\n }\n\n /**\n * Append a row to {@link callLog}. Internal helper called by every\n * recorded op before the actual work runs.\n */\n protected recordCall(operation: string, key: string | undefined, args: unknown[] = []): void {\n this.callLog.push({\n operation,\n key,\n args,\n timestamp: Date.now(),\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { BaseCacheDriver } from "./base-cache-driver.mjs";
|
|
2
2
|
import { CacheDriver, CacheKey, CacheSetOptions, CacheTtl, NullCacheDriverOptions, RememberOptions } from "../types.mjs";
|
|
3
3
|
import { GenericObject } from "@mongez/reinforcements";
|
|
4
|
-
|
|
5
4
|
//#region ../../packages/cache/src/drivers/null-cache-driver.d.ts
|
|
6
|
-
declare class NullCacheDriver extends BaseCacheDriver<NullCacheDriver, NullCacheDriverOptions> implements CacheDriver<NullCacheDriver, NullCacheDriverOptions> {
|
|
5
|
+
export declare class NullCacheDriver extends BaseCacheDriver<NullCacheDriver, NullCacheDriverOptions> implements CacheDriver<NullCacheDriver, NullCacheDriverOptions> {
|
|
7
6
|
/**
|
|
8
7
|
* Options list
|
|
9
8
|
*/
|
|
@@ -75,5 +74,4 @@ declare class NullCacheDriver extends BaseCacheDriver<NullCacheDriver, NullCache
|
|
|
75
74
|
connect(): Promise<void>;
|
|
76
75
|
}
|
|
77
76
|
//#endregion
|
|
78
|
-
export { NullCacheDriver };
|
|
79
77
|
//# sourceMappingURL=null-cache-driver.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"null-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/null-cache-driver.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"null-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/null-cache-driver.ts"],"mappings":";;;;qBAYa,wBACH,gBAAgB,iBAAiB,mCAC9B,YAAY,iBAAiB;;;;EAKjC,SAAS;;;;EAKT;;;;EAKA,MAAM;;;;MAKF;;;;EAOX,YAAmB,UAAS;;;;EAQrB,WAAW,SAAS;;;;EAQpB,SAAS,KAAK;;;;;;EAUR,SACX,MAAM,UACN,eAAe,WAAW,iBAC1B,gBAAgB,eACf;;;;EAOU,gBAAgB,oBAAiB;;;;YAapC,YAAY,KAAK;;;;EAOd,IACX,KAAK,UACL,aACA,gBAAgB,WAAW,kBAC1B;;;;EAWU,IAAI,KAAK,WAAQ;;;;EAWjB,OAAO,KAAK,WAAQ;;;;EASpB,SAAK;;;;;;;EAYL,WAAW;;;;EAOX,WAAO"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { BaseCacheDriver } from "./base-cache-driver.mjs";
|
|
2
2
|
import { CacheData, CacheDriver, CacheKey, CacheSetOptions, CacheSimilarHit, CacheSimilarOptions, CacheTtl, PgCacheOptions, PgClientLike } from "../types.mjs";
|
|
3
|
-
|
|
4
3
|
//#region ../../packages/cache/src/drivers/pg-cache-driver.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Postgres cache driver with optional pgvector similarity support.
|
|
@@ -30,7 +29,7 @@ import { CacheData, CacheDriver, CacheKey, CacheSetOptions, CacheSimilarHit, Cac
|
|
|
30
29
|
* // Run once, via your own migration tooling:
|
|
31
30
|
* // await pool.query(driver.schema());
|
|
32
31
|
*/
|
|
33
|
-
declare class PgCacheDriver extends BaseCacheDriver<PgClientLike, PgCacheOptions> implements CacheDriver<PgClientLike, PgCacheOptions> {
|
|
32
|
+
export declare class PgCacheDriver extends BaseCacheDriver<PgClientLike, PgCacheOptions> implements CacheDriver<PgClientLike, PgCacheOptions> {
|
|
34
33
|
/**
|
|
35
34
|
* {@inheritdoc}
|
|
36
35
|
*/
|
|
@@ -157,7 +156,9 @@ declare class PgCacheDriver extends BaseCacheDriver<PgClientLike, PgCacheOptions
|
|
|
157
156
|
* keeps its `expires_at`; a missing or expired row restarts from `value`
|
|
158
157
|
* with the driver default TTL. A non-numeric live value is rejected.
|
|
159
158
|
*/
|
|
160
|
-
increment(key: CacheKey, value?: number
|
|
159
|
+
increment(key: CacheKey, value?: number, options?: {
|
|
160
|
+
ttl?: CacheTtl;
|
|
161
|
+
}): Promise<number>;
|
|
161
162
|
/**
|
|
162
163
|
* {@inheritdoc}
|
|
163
164
|
*
|
|
@@ -218,5 +219,4 @@ declare class PgCacheDriver extends BaseCacheDriver<PgClientLike, PgCacheOptions
|
|
|
218
219
|
similar<T = any>(vector: number[], options: CacheSimilarOptions): Promise<CacheSimilarHit<T>[]>;
|
|
219
220
|
}
|
|
220
221
|
//#endregion
|
|
221
|
-
export { PgCacheDriver };
|
|
222
222
|
//# sourceMappingURL=pg-cache-driver.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/pg-cache-driver.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"pg-cache-driver.d.mts","names":[],"sources":["../../../../../../../../packages/cache/src/drivers/pg-cache-driver.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAuDa,sBACH,gBAAgB,cAAc,2BAC3B,YAAY,cAAc;;;;EAK9B;;;;;YAMG;;;;;;EAOH,WAAW,SAAS;;;;;;;YAsCX,qBAAqB;;;;;;YA6B3B,aAAa;;;;;gBAQT;;;;;gBAQA,YAAY;;;;;;YASV,eAAe,KAAK,UAAU,oBAAoB;;;;;;;;;;;;;;EAgCrD,OAAO,QAAQ,UAAU,oBAAoB;;;;;;;;EAiC7C,UAAU,QAAQ,UAAU,oBAAoB;;;;;;YAgCnD,aAAa;;;;;EAYV,MAAM,iBAAe;;;;YAaxB;;;;;;;;EAeH;;;;EAuCM,WAAO;;;;;;EAaP,cAAU;;;;EAQV,IACX,KAAK,UACL,YACA,eAAe,WAAW,kBACzB;;;;EAuIU,IAAI,KAAK,WAAQ;;;;;;YAwDd,SAAS,KAAK,WAAW,QAAQ;;;;;YA6CvC,WAAW;;;;;;;;;EAoBR,UACX,KAAK,UACL,gBACA;IAAW,MAAM;MAChB;;;;;;;EA0CU,KAAK,KAAK,WAAW;;;;;;;;;;;;;EAoCrB,OAAO,SAClB,KAAK,UACL,KAAK,SAAS,aAAa,WAAW,QAAQ,WAC9C;IAAW,MAAM;MAChB,QAAQ;;;;EAmFE,OAAO,KAAK,WAAQ;;;;;;;EAiBpB,gBAAgB,oBAAiB;;;;;;;;EA4BjC,SAAK;;;;;;;;;;;;;;;;;EA6BL,QAAQ,SACnB,kBACA,SAAS,sBACR,QAAQ,gBAAgB"}
|
|
@@ -438,7 +438,7 @@ var PgCacheDriver = class extends BaseCacheDriver {
|
|
|
438
438
|
* keeps its `expires_at`; a missing or expired row restarts from `value`
|
|
439
439
|
* with the driver default TTL. A non-numeric live value is rejected.
|
|
440
440
|
*/
|
|
441
|
-
async increment(key, value = 1) {
|
|
441
|
+
async increment(key, value = 1, options = {}) {
|
|
442
442
|
const parsedKey = this.parseKey(key);
|
|
443
443
|
const t = this.table;
|
|
444
444
|
const expired = `${t}.expires_at IS NOT NULL AND ${t}.expires_at <= now()`;
|
|
@@ -447,13 +447,16 @@ var PgCacheDriver = class extends BaseCacheDriver {
|
|
|
447
447
|
ON CONFLICT (key) DO UPDATE
|
|
448
448
|
SET value = CASE WHEN ${expired} THEN EXCLUDED.value
|
|
449
449
|
ELSE to_jsonb((${t}.value #>> '{}')::numeric + $2::numeric) END,
|
|
450
|
-
expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at
|
|
450
|
+
expires_at = CASE WHEN ${expired} THEN EXCLUDED.expires_at
|
|
451
|
+
WHEN $4::boolean AND ${t}.expires_at IS NULL THEN EXCLUDED.expires_at
|
|
452
|
+
ELSE ${t}.expires_at END,
|
|
451
453
|
stale_at = CASE WHEN ${expired} THEN NULL ELSE ${t}.stale_at END
|
|
452
454
|
WHERE (${expired}) OR jsonb_typeof(${t}.value) = 'number'
|
|
453
455
|
RETURNING value`, [
|
|
454
456
|
parsedKey,
|
|
455
457
|
value,
|
|
456
|
-
this.ttlToSeconds(this.ttl)
|
|
458
|
+
this.ttlToSeconds(options.ttl === void 0 ? this.ttl : parseTtl(options.ttl)),
|
|
459
|
+
options.ttl !== void 0
|
|
457
460
|
]);
|
|
458
461
|
if (rows.length === 0) throw new Error(`Cannot increment non-numeric value for key: ${maskEmailsInText(parsedKey)}`);
|
|
459
462
|
const newValue = Number(this.decodeJson(rows[0].value));
|