@warlock.js/cache 5.19.1 → 5.21.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/cjs/index.cjs +1439 -331
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/cache-manager.d.mts +4 -0
  5. package/esm/cache-manager.d.mts.map +1 -1
  6. package/esm/cache-manager.mjs +23 -5
  7. package/esm/cache-manager.mjs.map +1 -1
  8. package/esm/cached/auto-key.d.mts +14 -14
  9. package/esm/cached/auto-key.d.mts.map +1 -1
  10. package/esm/cached/auto-key.mjs +50 -34
  11. package/esm/cached/auto-key.mjs.map +1 -1
  12. package/esm/cached/cached.mjs +4 -0
  13. package/esm/cached/cached.mjs.map +1 -1
  14. package/esm/drivers/base-cache-driver.d.mts +84 -8
  15. package/esm/drivers/base-cache-driver.d.mts.map +1 -1
  16. package/esm/drivers/base-cache-driver.mjs +152 -42
  17. package/esm/drivers/base-cache-driver.mjs.map +1 -1
  18. package/esm/drivers/file-cache-driver.d.mts +15 -0
  19. package/esm/drivers/file-cache-driver.d.mts.map +1 -1
  20. package/esm/drivers/file-cache-driver.mjs +68 -18
  21. package/esm/drivers/file-cache-driver.mjs.map +1 -1
  22. package/esm/drivers/in-memory-tag-index.d.mts +44 -0
  23. package/esm/drivers/in-memory-tag-index.d.mts.map +1 -0
  24. package/esm/drivers/in-memory-tag-index.mjs +73 -0
  25. package/esm/drivers/in-memory-tag-index.mjs.map +1 -0
  26. package/esm/drivers/index.d.mts +1 -1
  27. package/esm/drivers/index.mjs +1 -1
  28. package/esm/drivers/lru-memory-cache-driver.d.mts +43 -0
  29. package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
  30. package/esm/drivers/lru-memory-cache-driver.mjs +96 -0
  31. package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
  32. package/esm/drivers/memory-cache-driver.d.mts +87 -30
  33. package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
  34. package/esm/drivers/memory-cache-driver.mjs +214 -96
  35. package/esm/drivers/memory-cache-driver.mjs.map +1 -1
  36. package/esm/drivers/memory-extended-cache-driver.d.mts +9 -5
  37. package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
  38. package/esm/drivers/memory-extended-cache-driver.mjs +10 -23
  39. package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
  40. package/esm/drivers/mock-cache-driver.d.mts +41 -0
  41. package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
  42. package/esm/drivers/mock-cache-driver.mjs +105 -8
  43. package/esm/drivers/mock-cache-driver.mjs.map +1 -1
  44. package/esm/drivers/null-cache-driver.d.mts +8 -2
  45. package/esm/drivers/null-cache-driver.d.mts.map +1 -1
  46. package/esm/drivers/null-cache-driver.mjs +10 -2
  47. package/esm/drivers/null-cache-driver.mjs.map +1 -1
  48. package/esm/drivers/pg-cache-driver.d.mts +77 -3
  49. package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
  50. package/esm/drivers/pg-cache-driver.mjs +229 -7
  51. package/esm/drivers/pg-cache-driver.mjs.map +1 -1
  52. package/esm/drivers/redis-cache-driver.d.mts +86 -1
  53. package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
  54. package/esm/drivers/redis-cache-driver.mjs +252 -16
  55. package/esm/drivers/redis-cache-driver.mjs.map +1 -1
  56. package/esm/index.d.mts +2 -2
  57. package/esm/index.mjs +2 -2
  58. package/esm/list/memory-cache-list.d.mts +11 -8
  59. package/esm/list/memory-cache-list.d.mts.map +1 -1
  60. package/esm/list/memory-cache-list.mjs +39 -37
  61. package/esm/list/memory-cache-list.mjs.map +1 -1
  62. package/esm/scoped-cache.d.mts +9 -3
  63. package/esm/scoped-cache.d.mts.map +1 -1
  64. package/esm/scoped-cache.mjs +19 -7
  65. package/esm/scoped-cache.mjs.map +1 -1
  66. package/esm/tagged-cache.d.mts +20 -0
  67. package/esm/tagged-cache.d.mts.map +1 -1
  68. package/esm/tagged-cache.mjs +62 -24
  69. package/esm/tagged-cache.mjs.map +1 -1
  70. package/esm/tagged-scoped-cache.d.mts +7 -3
  71. package/esm/tagged-scoped-cache.d.mts.map +1 -1
  72. package/esm/tagged-scoped-cache.mjs +19 -7
  73. package/esm/tagged-scoped-cache.mjs.map +1 -1
  74. package/esm/types.d.mts +41 -3
  75. package/esm/types.d.mts.map +1 -1
  76. package/esm/types.mjs.map +1 -1
  77. package/esm/utils.d.mts +9 -3
  78. package/esm/utils.d.mts.map +1 -1
  79. package/esm/utils.mjs +16 -6
  80. package/esm/utils.mjs.map +1 -1
  81. package/llms-full.txt +1685 -1666
  82. package/llms.txt +18 -18
  83. package/package.json +4 -4
  84. package/skills/apply-cache-patterns/SKILL.md +98 -97
  85. package/skills/configure-pg-cache/SKILL.md +6 -5
  86. package/skills/configure-set-options/SKILL.md +102 -96
  87. package/skills/handle-cache-errors/SKILL.md +92 -92
  88. package/skills/index.md +20 -0
  89. package/skills/observe-cache/SKILL.md +103 -103
  90. package/skills/pick-cache-driver/SKILL.md +129 -123
  91. package/skills/test-cache-code/SKILL.md +219 -219
  92. package/skills/use-cache-atomic/SKILL.md +57 -65
  93. package/skills/use-cache-bulk/SKILL.md +57 -57
  94. package/skills/use-cache-list/SKILL.md +85 -85
  95. package/skills/use-cache-lock/SKILL.md +113 -104
  96. package/skills/use-cache-namespace/SKILL.md +88 -88
  97. package/skills/use-cache-similarity/SKILL.md +94 -94
  98. package/skills/use-cache-tags/SKILL.md +7 -4
  99. package/skills/use-cache-update-merge/SKILL.md +84 -84
  100. package/skills/use-cache-utils/SKILL.md +89 -89
  101. package/skills/use-cached-hof/SKILL.md +100 -99
  102. package/skills/use-swr/SKILL.md +104 -104
@@ -1,31 +1,52 @@
1
- import { cosineSimilarity } from "../utils.mjs";
1
+ import { cosineSimilarity, parseTtl } from "../utils.mjs";
2
2
  import { BaseCacheDriver } from "./base-cache-driver.mjs";
3
- import { get, set, unset } from "@mongez/reinforcements";
3
+ import { InMemoryTagIndex } from "./in-memory-tag-index.mjs";
4
4
 
5
5
  //#region ../cache/src/drivers/memory-cache-driver.ts
6
+ /**
7
+ * Clone non-primitive values so cached state can't be mutated through
8
+ * references held by callers. Primitives are immutable and returned as-is.
9
+ */
10
+ function cloneValue(value) {
11
+ if (value === null || value === void 0) return value;
12
+ const type = typeof value;
13
+ if (type === "string" || type === "number" || type === "boolean") return value;
14
+ return structuredClone(value);
15
+ }
6
16
  var MemoryCacheDriver = class extends BaseCacheDriver {
17
+ /**
18
+ * Read-only object view of the stored entries, keyed by parsed key.
19
+ * Kept for introspection; the values are the live stored wrappers.
20
+ */
21
+ get data() {
22
+ return Object.fromEntries(this.entries);
23
+ }
7
24
  /**
8
25
  * {@inheritdoc}
9
26
  */
10
27
  constructor() {
11
28
  super();
12
29
  this.name = "memory";
13
- this.data = {};
14
- this.temporaryData = {};
15
- this.accessOrder = [];
30
+ this.entries = /* @__PURE__ */ new Map();
31
+ this.expiry = /* @__PURE__ */ new Map();
16
32
  this.vectorIndex = /* @__PURE__ */ new Map();
33
+ this.tagIndex = new InMemoryTagIndex();
34
+ this.slidingExpiration = false;
17
35
  this.startCleanup();
18
36
  }
19
37
  /**
20
- * Start the cleanup process whenever a data that has a cache key is set
38
+ * Start the cleanup process for entries that carry a finite ttl
21
39
  */
22
40
  startCleanup() {
23
41
  if (this.cleanupInterval) clearInterval(this.cleanupInterval);
24
42
  this.cleanupInterval = setInterval(async () => {
25
43
  const now = Date.now();
26
- for (const [key, entry] of Object.entries(this.temporaryData)) if (entry.expiresAt <= now) {
27
- await this.remove(entry.key);
28
- delete this.temporaryData[key];
44
+ const expiredKeys = [];
45
+ for (const [key, expiresAt] of this.expiry) if (expiresAt <= now) expiredKeys.push(key);
46
+ for (const key of expiredKeys) {
47
+ const expiresAt = this.expiry.get(key);
48
+ if (expiresAt === void 0 || expiresAt > now) continue;
49
+ this.dropEntry(key);
29
50
  this.log("expired", key);
30
51
  await this.emit("expired", { key });
31
52
  }
@@ -33,44 +54,94 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
33
54
  this.cleanupInterval.unref();
34
55
  }
35
56
  /**
57
+ * Delete an entry and every side record (expiry, vector). Synchronous.
58
+ */
59
+ dropEntry(parsedKey) {
60
+ this.entries.delete(parsedKey);
61
+ this.expiry.delete(parsedKey);
62
+ this.vectorIndex.delete(parsedKey);
63
+ }
64
+ /**
65
+ * Raw, synchronous read of a live entry. An expired entry is deleted and
66
+ * reported as missing. Never slides TTLs, never emits, never touches LRU order.
67
+ */
68
+ readLive(parsedKey) {
69
+ const entry = this.entries.get(parsedKey);
70
+ if (!entry) return;
71
+ if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) {
72
+ this.dropEntry(parsedKey);
73
+ return;
74
+ }
75
+ return entry;
76
+ }
77
+ /**
78
+ * Mark an entry as most recently used (only tracked when `maxSize` is set).
79
+ */
80
+ touch(parsedKey, entry) {
81
+ if (!this.options.maxSize) return;
82
+ this.entries.delete(parsedKey);
83
+ this.entries.set(parsedKey, entry);
84
+ }
85
+ /**
86
+ * Invoked on a live read hit, before the value is returned. Runs only from
87
+ * `get()` after the expiry check, so an expired entry is never resurrected,
88
+ * and never from the internal existence check used by `onConflict`.
89
+ */
90
+ onRead(parsedKey, entry) {
91
+ if (!this.slidingExpiration) return;
92
+ const rawTtl = entry.ttl ?? this.options.ttl;
93
+ const ttl = rawTtl !== void 0 ? parseTtl(rawTtl) : void 0;
94
+ if (ttl && Number.isFinite(ttl)) {
95
+ entry.expiresAt = this.getExpiresAt(ttl);
96
+ this.expiry.set(parsedKey, entry.expiresAt);
97
+ }
98
+ }
99
+ /**
36
100
  * {@inheritdoc}
37
101
  */
38
102
  async removeNamespace(namespace) {
39
103
  this.log("clearing", namespace);
40
104
  namespace = this.parseKey(namespace);
41
- unset(this.data, [namespace]);
42
- if (namespace === "") this.vectorIndex.clear();
43
- else {
105
+ this.tagIndex.removeNamespace(namespace);
106
+ if (namespace === "") {
107
+ this.entries.clear();
108
+ this.expiry.clear();
109
+ this.vectorIndex.clear();
110
+ } else {
44
111
  const prefix = namespace + ".";
45
- for (const k of [...this.vectorIndex.keys()]) if (k === namespace || k.startsWith(prefix)) this.vectorIndex.delete(k);
112
+ for (const key of [...this.entries.keys()]) if (key === namespace || key.startsWith(prefix)) this.dropEntry(key);
46
113
  }
47
114
  this.log("cleared", namespace);
48
115
  return this;
49
116
  }
50
117
  /**
51
118
  * {@inheritdoc}
119
+ *
120
+ * The conditional check and the write run with no `await` in between, so
121
+ * `onConflict` decisions are atomic within the process.
52
122
  */
53
123
  async set(key, value, ttlOrOptions) {
54
124
  const parsedKey = this.parseKey(key);
55
125
  const { ttl, tags, onConflict, vector, staleAt } = this.resolveSetOptions(ttlOrOptions);
56
126
  this.log("caching", parsedKey);
57
- const existingValue = onConflict === "upsert" ? null : await this.get(key);
58
- const exists = existingValue !== null;
59
- if (onConflict === "create" && exists) return {
127
+ const existing = onConflict === "create" || onConflict === "update" ? this.readLive(parsedKey) : void 0;
128
+ if (onConflict === "create" && existing) return {
60
129
  wasSet: false,
61
- existing: existingValue
130
+ existing: cloneValue(existing.data)
62
131
  };
63
- if (onConflict === "update" && !exists) return {
132
+ if (onConflict === "update" && !existing) return {
64
133
  wasSet: false,
65
134
  existing: null
66
135
  };
67
- const data = this.prepareDataForStorage(value, ttl, staleAt);
68
- if (ttl) this.setTemporaryData(key, parsedKey, ttl);
69
- set(this.data, parsedKey, data);
70
- this.trackAccess(parsedKey);
71
- if (!exists && this.options.maxSize) await this.enforceMaxSize();
72
- if (tags && tags.length > 0) await this.applyTags(key, tags);
136
+ const data = this.prepareDataForStorage(cloneValue(value), ttl, staleAt);
137
+ this.entries.delete(parsedKey);
138
+ this.entries.set(parsedKey, data);
139
+ if (data.expiresAt !== void 0 && Number.isFinite(data.expiresAt)) this.expiry.set(parsedKey, data.expiresAt);
140
+ else this.expiry.delete(parsedKey);
73
141
  if (vector) this.vectorIndex.set(parsedKey, vector.slice());
142
+ else this.vectorIndex.delete(parsedKey);
143
+ this.enforceMaxSize(parsedKey);
144
+ if (tags && tags.length > 0) await this.applyTags(key, tags);
74
145
  this.log("cached", parsedKey);
75
146
  await this.emit("set", {
76
147
  key: parsedKey,
@@ -85,38 +156,101 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
85
156
  }
86
157
  /**
87
158
  * {@inheritdoc}
159
+ *
160
+ * The check and the delete run synchronously (no `await` between them), so
161
+ * the compare-and-delete is atomic within the process.
162
+ */
163
+ async deleteIfEquals(key, expected) {
164
+ const parsedKey = this.parseKey(key);
165
+ const entry = this.readLive(parsedKey);
166
+ if (!entry || entry.data !== expected) return false;
167
+ await this.remove(key);
168
+ return true;
169
+ }
170
+ /**
171
+ * {@inheritdoc}
172
+ *
173
+ * Read, add and write run synchronously (no `await` in between), so
174
+ * concurrent increments in this process never lose an update. An existing
175
+ * entry keeps its expiry; a missing key starts from 0 with the default TTL.
176
+ */
177
+ async increment(key, value = 1) {
178
+ const parsedKey = this.parseKey(key);
179
+ const entry = this.readLive(parsedKey);
180
+ const current = entry ? entry.data : 0;
181
+ if (typeof current !== "number") throw new Error(`Cannot increment non-numeric value for key: ${parsedKey}`);
182
+ const newValue = current + value;
183
+ if (entry) {
184
+ entry.data = newValue;
185
+ this.touch(parsedKey, entry);
186
+ } else {
187
+ const data = this.prepareDataForStorage(newValue, this.ttl);
188
+ this.entries.set(parsedKey, data);
189
+ if (data.expiresAt !== void 0 && Number.isFinite(data.expiresAt)) this.expiry.set(parsedKey, data.expiresAt);
190
+ this.enforceMaxSize(parsedKey);
191
+ }
192
+ await this.emit("set", {
193
+ key: parsedKey,
194
+ value: newValue,
195
+ ttl: entry?.ttl ?? this.ttl
196
+ });
197
+ return newValue;
198
+ }
199
+ /**
200
+ * {@inheritdoc}
201
+ *
202
+ * Read and delete run synchronously, so a value is handed out exactly once
203
+ * even when concurrent callers pull the same key.
204
+ */
205
+ async pull(key) {
206
+ const parsedKey = this.parseKey(key);
207
+ const entry = this.readLive(parsedKey);
208
+ if (!entry) {
209
+ await this.emit("miss", { key: parsedKey });
210
+ return null;
211
+ }
212
+ this.dropEntry(parsedKey);
213
+ const value = cloneValue(entry.data);
214
+ await this.emit("hit", {
215
+ key: parsedKey,
216
+ value
217
+ });
218
+ await this.emit("removed", { key: parsedKey });
219
+ return value;
220
+ }
221
+ /**
222
+ * {@inheritdoc}
88
223
  */
89
224
  async get(key) {
90
225
  const parsedKey = this.parseKey(key);
91
226
  this.log("fetching", parsedKey);
92
- const value = get(this.data, parsedKey);
93
- if (!value) {
227
+ const entry = this.readLive(parsedKey);
228
+ if (!entry) {
94
229
  this.log("notFound", parsedKey);
95
230
  await this.emit("miss", { key: parsedKey });
96
231
  return null;
97
232
  }
98
- const result = await this.parseCachedData(parsedKey, value);
99
- if (result === null) await this.emit("miss", { key: parsedKey });
100
- else {
101
- this.trackAccess(parsedKey);
102
- await this.emit("hit", {
103
- key: parsedKey,
104
- value: result
105
- });
106
- }
233
+ this.onRead(parsedKey, entry);
234
+ this.touch(parsedKey, entry);
235
+ const result = await this.parseCachedData(parsedKey, entry);
236
+ await this.emit("hit", {
237
+ key: parsedKey,
238
+ value: result
239
+ });
107
240
  return result;
108
241
  }
109
242
  /**
110
- * Read the raw {@link CacheData} wrapper, including `staleAt` metadata.
243
+ * Read the raw {@link CacheData} wrapper (cloned), including `staleAt` metadata.
111
244
  * Returns `null` for missing or expired entries so the SWR flow can branch
112
245
  * cleanly. Does not emit `hit`/`miss` events — that's `get()`'s job.
113
246
  */
114
247
  async getEntry(key) {
115
- const parsedKey = this.parseKey(key);
116
- const entry = get(this.data, parsedKey);
248
+ const entry = this.readLive(this.parseKey(key));
117
249
  if (!entry) return null;
118
- if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) return null;
119
- return entry;
250
+ return {
251
+ ...entry,
252
+ data: cloneValue(entry.data)
253
+ };
120
254
  }
121
255
  /**
122
256
  * {@inheritdoc}
@@ -124,76 +258,65 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
124
258
  async remove(key) {
125
259
  const parsedKey = this.parseKey(key);
126
260
  this.log("removing", parsedKey);
127
- unset(this.data, [parsedKey]);
128
- delete this.temporaryData[parsedKey];
129
- this.removeFromAccessOrder(parsedKey);
130
- this.vectorIndex.delete(parsedKey);
261
+ this.dropEntry(parsedKey);
131
262
  this.log("removed", parsedKey);
132
263
  await this.emit("removed", { key: parsedKey });
133
264
  }
134
265
  /**
135
266
  * {@inheritdoc}
267
+ *
268
+ * Synchronous in-process set — concurrent tagged writes never drop members.
136
269
  */
137
- async flush() {
138
- this.log("flushing");
139
- if (this.options.globalPrefix) this.removeNamespace("");
140
- else {
141
- this.data = {};
142
- this.accessOrder = [];
143
- this.vectorIndex.clear();
144
- }
145
- this.log("flushed");
146
- await this.emit("flushed");
270
+ async tagAdd(tagKey, members) {
271
+ this.tagIndex.add(this.parseKey(tagKey), members);
147
272
  }
148
273
  /**
149
- * Set the temporary data
274
+ * {@inheritdoc}
150
275
  */
151
- setTemporaryData(key, parsedKey, ttl) {
152
- this.temporaryData[parsedKey] = {
153
- key: JSON.stringify(key),
154
- expiresAt: Date.now() + ttl * 1e3
155
- };
276
+ async tagMembers(tagKey) {
277
+ return this.tagIndex.members(this.parseKey(tagKey));
156
278
  }
157
279
  /**
158
- * Track access for LRU eviction
280
+ * {@inheritdoc}
159
281
  */
160
- trackAccess(key) {
161
- if (!this.options.maxSize) return;
162
- const index = this.accessOrder.indexOf(key);
163
- if (index > -1) this.accessOrder.splice(index, 1);
164
- this.accessOrder.push(key);
282
+ async tagRemove(tagKey, members) {
283
+ this.tagIndex.remove(this.parseKey(tagKey), members);
165
284
  }
166
285
  /**
167
- * Remove key from access order tracking
286
+ * {@inheritdoc}
168
287
  */
169
- removeFromAccessOrder(key) {
170
- const index = this.accessOrder.indexOf(key);
171
- if (index > -1) this.accessOrder.splice(index, 1);
288
+ async tagDelete(tagKey) {
289
+ this.tagIndex.delete(this.parseKey(tagKey));
172
290
  }
173
291
  /**
174
- * Enforce max size by evicting least recently used items.
175
- *
176
- * Recomputes the live cache size on every iteration — a single snapshot at
177
- * the top of the loop would go stale and cause this routine to evict every
178
- * entry in `accessOrder` (including the just-inserted key).
292
+ * {@inheritdoc}
179
293
  */
180
- async enforceMaxSize() {
181
- if (!this.options.maxSize) return;
182
- while (this.getCacheSize() > this.options.maxSize && this.accessOrder.length > 0) {
183
- const lruKey = this.accessOrder.shift();
184
- if (!lruKey) break;
185
- this.log("removing", lruKey);
186
- unset(this.data, [lruKey]);
187
- delete this.temporaryData[lruKey];
188
- this.vectorIndex.delete(lruKey);
189
- this.log("removed", lruKey);
294
+ async flush() {
295
+ this.log("flushing");
296
+ if (this.options.globalPrefix) await this.removeNamespace("");
297
+ else {
298
+ this.entries.clear();
299
+ this.expiry.clear();
300
+ this.vectorIndex.clear();
301
+ this.tagIndex.clear();
190
302
  }
303
+ this.log("flushed");
304
+ await this.emit("flushed");
191
305
  }
192
306
  /**
193
- * Get current cache size (number of cached items)
307
+ * Evict least recently used entries (Map insertion order) until the size is
308
+ * within `maxSize`. Never evicts `protectedKey` (the key just written).
194
309
  */
195
- getCacheSize() {
196
- return Object.keys(this.data).length;
310
+ enforceMaxSize(protectedKey) {
311
+ const maxSize = this.options.maxSize;
312
+ if (!maxSize) return;
313
+ for (const lruKey of [...this.entries.keys()]) {
314
+ if (this.entries.size <= maxSize) break;
315
+ if (lruKey === protectedKey) continue;
316
+ this.log("removing", lruKey);
317
+ this.dropEntry(lruKey);
318
+ this.log("removed", lruKey);
319
+ }
197
320
  }
198
321
  /**
199
322
  * {@inheritdoc}
@@ -208,16 +331,11 @@ var MemoryCacheDriver = class extends BaseCacheDriver {
208
331
  async similar(vector, options) {
209
332
  const tagFilter = await this.getKeysForTags(options.tags);
210
333
  const hits = [];
211
- for (const [parsedKey, stored] of this.vectorIndex) {
334
+ for (const [parsedKey, stored] of [...this.vectorIndex]) {
212
335
  if (tagFilter && !tagFilter.has(parsedKey)) continue;
213
- const entry = get(this.data, parsedKey);
336
+ const entry = this.readLive(parsedKey);
214
337
  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
- }
338
+ const value = cloneValue(entry.data);
221
339
  const score = cosineSimilarity(vector, stored);
222
340
  if (options.threshold !== void 0 && score < options.threshold) continue;
223
341
  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\";\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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"}
1
+ {"version":3,"file":"memory-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/memory-cache-driver.ts"],"sourcesContent":["import 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: ${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":"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"}
@@ -1,16 +1,20 @@
1
1
  import { MemoryCacheDriver } from "./memory-cache-driver.mjs";
2
- import { CacheDriver, CacheKey, MemoryExtendedCacheOptions } from "../types.mjs";
2
+ import { CacheDriver, MemoryExtendedCacheOptions } from "../types.mjs";
3
3
 
4
4
  //#region ../cache/src/drivers/memory-extended-cache-driver.d.ts
5
+ /**
6
+ * Memory driver with a sliding ttl: every live read pushes the entry's
7
+ * expiration forward by its ttl. The sliding itself lives in
8
+ * `MemoryCacheDriver.onRead`, which checks expiry first, so an expired entry
9
+ * is never resurrected and the internal `onConflict` existence check never
10
+ * slides a held lock.
11
+ */
5
12
  declare class MemoryExtendedCacheDriver extends MemoryCacheDriver implements CacheDriver<MemoryExtendedCacheDriver, MemoryExtendedCacheOptions> {
6
13
  /**
7
14
  * {@inheritdoc}
8
15
  */
9
16
  name: string;
10
- /**
11
- * {@inheritdoc}
12
- */
13
- get(key: CacheKey): Promise<any>;
17
+ constructor();
14
18
  }
15
19
  //#endregion
16
20
  export { MemoryExtendedCacheDriver };
@@ -1 +1 @@
1
- {"version":3,"file":"memory-extended-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/memory-extended-cache-driver.ts"],"mappings":";;;;cAKa,yBAAA,SACH,iBAAA,YACG,WAAA,CAAY,yBAAA,EAA2B,0BAAA;;AAFpD;;EAOS,IAAA;EALgB;;;EAUV,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;AAAA"}
1
+ {"version":3,"file":"memory-extended-cache-driver.d.mts","names":[],"sources":["../../../../../../../cache/src/drivers/memory-extended-cache-driver.ts"],"mappings":";;;;;;AAUA;;;;;cAAa,yBAAA,SACH,iBAAA,YACG,WAAA,CAAY,yBAAA,EAA2B,0BAAA;EAAvC;;;EAKJ,IAAA"}
@@ -1,31 +1,18 @@
1
- import { parseTtl } from "../utils.mjs";
2
1
  import { MemoryCacheDriver } from "./memory-cache-driver.mjs";
3
- import { get } from "@mongez/reinforcements";
4
2
 
5
3
  //#region ../cache/src/drivers/memory-extended-cache-driver.ts
4
+ /**
5
+ * Memory driver with a sliding ttl: every live read pushes the entry's
6
+ * expiration forward by its ttl. The sliding itself lives in
7
+ * `MemoryCacheDriver.onRead`, which checks expiry first, so an expired entry
8
+ * is never resurrected and the internal `onConflict` existence check never
9
+ * slides a held lock.
10
+ */
6
11
  var MemoryExtendedCacheDriver = class extends MemoryCacheDriver {
7
- constructor(..._args) {
8
- super(..._args);
12
+ constructor() {
13
+ super();
9
14
  this.name = "memoryExtended";
10
- }
11
- /**
12
- * {@inheritdoc}
13
- */
14
- async get(key) {
15
- const parsedKey = this.parseKey(key);
16
- this.log("fetching", parsedKey);
17
- const value = get(this.data, parsedKey);
18
- if (!value) {
19
- this.log("notFound", parsedKey);
20
- return null;
21
- }
22
- const rawTtl = value.ttl ?? this.options.ttl;
23
- const ttl = rawTtl !== void 0 ? parseTtl(rawTtl) : void 0;
24
- if (ttl) {
25
- this.setTemporaryData(key, parsedKey, ttl);
26
- value.expiresAt = this.getExpiresAt(ttl);
27
- }
28
- return this.parseCachedData(parsedKey, value);
15
+ this.slidingExpiration = true;
29
16
  }
30
17
  };
31
18
 
@@ -1 +1 @@
1
- {"version":3,"file":"memory-extended-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/memory-extended-cache-driver.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\r\nimport type { CacheData, CacheDriver, CacheKey, MemoryExtendedCacheOptions } from \"../types\";\r\nimport { parseTtl } from \"../utils\";\r\nimport { MemoryCacheDriver } from \"./memory-cache-driver\";\r\n\r\nexport class MemoryExtendedCacheDriver\r\n extends MemoryCacheDriver\r\n implements CacheDriver<MemoryExtendedCacheDriver, MemoryExtendedCacheOptions>\r\n{\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"memoryExtended\";\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 return null;\r\n }\r\n\r\n const rawTtl = value.ttl ?? this.options.ttl;\r\n const ttl = rawTtl !== undefined ? parseTtl(rawTtl) : undefined;\r\n\r\n if (ttl) {\r\n // reset the expiration time\r\n this.setTemporaryData(key, parsedKey, ttl);\r\n value.expiresAt = this.getExpiresAt(ttl);\r\n }\r\n\r\n return this.parseCachedData(parsedKey, value);\r\n }\r\n}\r\n"],"mappings":";;;;;AAKA,IAAa,4BAAb,cACU,kBAEV;;;cAIgB;;;;;CAKd,MAAa,IAAI,KAAe;EAC9B,MAAM,YAAY,KAAK,SAAS,GAAG;EAEnC,KAAK,IAAI,YAAY,SAAS;EAE9B,MAAM,QAAmB,IAAI,KAAK,MAAM,SAAS;EAEjD,IAAI,CAAC,OAAO;GACV,KAAK,IAAI,YAAY,SAAS;GAC9B,OAAO;EACT;EAEA,MAAM,SAAS,MAAM,OAAO,KAAK,QAAQ;EACzC,MAAM,MAAM,WAAW,SAAY,SAAS,MAAM,IAAI;EAEtD,IAAI,KAAK;GAEP,KAAK,iBAAiB,KAAK,WAAW,GAAG;GACzC,MAAM,YAAY,KAAK,aAAa,GAAG;EACzC;EAEA,OAAO,KAAK,gBAAgB,WAAW,KAAK;CAC9C;AACF"}
1
+ {"version":3,"file":"memory-extended-cache-driver.mjs","names":[],"sources":["../../../../../../../cache/src/drivers/memory-extended-cache-driver.ts"],"sourcesContent":["import type { CacheDriver, MemoryExtendedCacheOptions } from \"../types\";\nimport { MemoryCacheDriver } from \"./memory-cache-driver\";\n\n/**\n * Memory driver with a sliding ttl: every live read pushes the entry's\n * expiration forward by its ttl. The sliding itself lives in\n * `MemoryCacheDriver.onRead`, which checks expiry first, so an expired entry\n * is never resurrected and the internal `onConflict` existence check never\n * slides a held lock.\n */\nexport class MemoryExtendedCacheDriver\n extends MemoryCacheDriver\n implements CacheDriver<MemoryExtendedCacheDriver, MemoryExtendedCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"memoryExtended\";\n\n public constructor() {\n super();\n\n this.slidingExpiration = true;\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,IAAa,4BAAb,cACU,kBAEV;CAME,AAAO,cAAc;EACnB,MAAM;cAHM;EAKZ,KAAK,oBAAoB;CAC3B;AACF"}