@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.
- package/CHANGELOG.md +61 -1
- package/cjs/index.cjs +1439 -331
- package/cjs/index.cjs.map +1 -1
- package/esm/cache-manager.d.mts +4 -0
- package/esm/cache-manager.d.mts.map +1 -1
- package/esm/cache-manager.mjs +23 -5
- package/esm/cache-manager.mjs.map +1 -1
- package/esm/cached/auto-key.d.mts +14 -14
- package/esm/cached/auto-key.d.mts.map +1 -1
- package/esm/cached/auto-key.mjs +50 -34
- package/esm/cached/auto-key.mjs.map +1 -1
- package/esm/cached/cached.mjs +4 -0
- package/esm/cached/cached.mjs.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +84 -8
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +152 -42
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.d.mts +15 -0
- package/esm/drivers/file-cache-driver.d.mts.map +1 -1
- package/esm/drivers/file-cache-driver.mjs +68 -18
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts +44 -0
- package/esm/drivers/in-memory-tag-index.d.mts.map +1 -0
- package/esm/drivers/in-memory-tag-index.mjs +73 -0
- package/esm/drivers/in-memory-tag-index.mjs.map +1 -0
- package/esm/drivers/index.d.mts +1 -1
- package/esm/drivers/index.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts +43 -0
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +96 -0
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts +87 -30
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-cache-driver.mjs +214 -96
- package/esm/drivers/memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.d.mts +9 -5
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.mjs +10 -23
- package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
- package/esm/drivers/mock-cache-driver.d.mts +41 -0
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +105 -8
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/null-cache-driver.d.mts +8 -2
- package/esm/drivers/null-cache-driver.d.mts.map +1 -1
- package/esm/drivers/null-cache-driver.mjs +10 -2
- package/esm/drivers/null-cache-driver.mjs.map +1 -1
- package/esm/drivers/pg-cache-driver.d.mts +77 -3
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +229 -7
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.d.mts +86 -1
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +252 -16
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/index.mjs +2 -2
- package/esm/list/memory-cache-list.d.mts +11 -8
- package/esm/list/memory-cache-list.d.mts.map +1 -1
- package/esm/list/memory-cache-list.mjs +39 -37
- package/esm/list/memory-cache-list.mjs.map +1 -1
- package/esm/scoped-cache.d.mts +9 -3
- package/esm/scoped-cache.d.mts.map +1 -1
- package/esm/scoped-cache.mjs +19 -7
- package/esm/scoped-cache.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +20 -0
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +62 -24
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts +7 -3
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +19 -7
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/esm/types.d.mts +41 -3
- package/esm/types.d.mts.map +1 -1
- package/esm/types.mjs.map +1 -1
- package/esm/utils.d.mts +9 -3
- package/esm/utils.d.mts.map +1 -1
- package/esm/utils.mjs +16 -6
- package/esm/utils.mjs.map +1 -1
- package/llms-full.txt +1685 -1666
- package/llms.txt +18 -18
- package/package.json +4 -4
- package/skills/apply-cache-patterns/SKILL.md +98 -97
- package/skills/configure-pg-cache/SKILL.md +6 -5
- package/skills/configure-set-options/SKILL.md +102 -96
- package/skills/handle-cache-errors/SKILL.md +92 -92
- package/skills/index.md +20 -0
- package/skills/observe-cache/SKILL.md +103 -103
- package/skills/pick-cache-driver/SKILL.md +129 -123
- package/skills/test-cache-code/SKILL.md +219 -219
- package/skills/use-cache-atomic/SKILL.md +57 -65
- package/skills/use-cache-bulk/SKILL.md +57 -57
- package/skills/use-cache-list/SKILL.md +85 -85
- package/skills/use-cache-lock/SKILL.md +113 -104
- package/skills/use-cache-namespace/SKILL.md +88 -88
- package/skills/use-cache-similarity/SKILL.md +94 -94
- package/skills/use-cache-tags/SKILL.md +7 -4
- package/skills/use-cache-update-merge/SKILL.md +84 -84
- package/skills/use-cache-utils/SKILL.md +89 -89
- package/skills/use-cached-hof/SKILL.md +100 -99
- 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 {
|
|
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.
|
|
14
|
-
this.
|
|
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
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
42
|
-
if (namespace === "")
|
|
43
|
-
|
|
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
|
|
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
|
|
58
|
-
|
|
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:
|
|
130
|
+
existing: cloneValue(existing.data)
|
|
62
131
|
};
|
|
63
|
-
if (onConflict === "update" && !
|
|
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
|
-
|
|
69
|
-
set(
|
|
70
|
-
this.
|
|
71
|
-
|
|
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
|
|
93
|
-
if (!
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
116
|
-
const entry = get(this.data, parsedKey);
|
|
248
|
+
const entry = this.readLive(this.parseKey(key));
|
|
117
249
|
if (!entry) return null;
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
138
|
-
this.
|
|
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
|
-
*
|
|
274
|
+
* {@inheritdoc}
|
|
150
275
|
*/
|
|
151
|
-
|
|
152
|
-
this.
|
|
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
|
-
*
|
|
280
|
+
* {@inheritdoc}
|
|
159
281
|
*/
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
*
|
|
286
|
+
* {@inheritdoc}
|
|
168
287
|
*/
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
*
|
|
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
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
this.
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
196
|
-
|
|
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 =
|
|
336
|
+
const entry = this.readLive(parsedKey);
|
|
214
337
|
if (!entry) continue;
|
|
215
|
-
|
|
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":";;;;;;;;;AAmBA,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,WAAW;EAG5E,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,16 +1,20 @@
|
|
|
1
1
|
import { MemoryCacheDriver } from "./memory-cache-driver.mjs";
|
|
2
|
-
import { CacheDriver,
|
|
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":"
|
|
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(
|
|
8
|
-
super(
|
|
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
|
|
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"}
|