@gravito/stasis 3.1.1 → 3.2.1
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/README.md +28 -5
- package/dist/CacheManager.d.ts +484 -0
- package/dist/CacheRepository.d.ts +495 -0
- package/dist/RateLimiter.d.ts +152 -0
- package/dist/cache-events.d.ts +58 -0
- package/dist/index.d.ts +29 -2676
- package/dist/index.js +349 -1706
- package/dist/index.js.map +27 -0
- package/dist/locks.d.ts +193 -0
- package/dist/prediction/AccessPredictor.d.ts +64 -0
- package/dist/store.d.ts +200 -0
- package/dist/stores/CircuitBreakerStore.d.ts +78 -0
- package/dist/stores/FileStore.d.ts +36 -0
- package/dist/stores/MemoryStore.d.ts +261 -0
- package/dist/stores/NullStore.d.ts +115 -0
- package/dist/stores/PredictiveStore.d.ts +40 -0
- package/dist/stores/RedisStore.d.ts +83 -0
- package/dist/stores/TieredStore.d.ts +37 -0
- package/dist/tagged-store.d.ts +29 -0
- package/dist/types.d.ts +149 -0
- package/dist/utils/LRUCache.d.ts +104 -0
- package/package.json +11 -10
- package/dist/index.cjs +0 -3279
- package/dist/index.d.cts +0 -2989
package/dist/index.js
CHANGED
|
@@ -1,7 +1,60 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/cache-events.ts
|
|
5
|
+
function emitCacheEvent(event, payload, events, config) {
|
|
6
|
+
const { mode, throwOnError, onError } = config;
|
|
7
|
+
if (mode === "off") {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const fn = events?.[event];
|
|
11
|
+
if (!fn) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const invoke = () => {
|
|
15
|
+
if (event === "flush") {
|
|
16
|
+
return fn();
|
|
17
|
+
}
|
|
18
|
+
const key = payload.key ?? "";
|
|
19
|
+
return fn(key);
|
|
20
|
+
};
|
|
21
|
+
const reportError = (error) => {
|
|
22
|
+
try {
|
|
23
|
+
onError?.(error, event, payload);
|
|
24
|
+
} catch {}
|
|
25
|
+
};
|
|
26
|
+
if (mode === "sync") {
|
|
27
|
+
try {
|
|
28
|
+
return Promise.resolve(invoke()).catch((error) => {
|
|
29
|
+
reportError(error);
|
|
30
|
+
if (throwOnError) {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
reportError(error);
|
|
36
|
+
if (throwOnError) {
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
queueMicrotask(() => {
|
|
43
|
+
try {
|
|
44
|
+
const result = invoke();
|
|
45
|
+
if (result && typeof result.catch === "function") {
|
|
46
|
+
result.catch(reportError);
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
reportError(error);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
1
54
|
// src/locks.ts
|
|
2
|
-
|
|
55
|
+
class LockTimeoutError extends Error {
|
|
3
56
|
name = "LockTimeoutError";
|
|
4
|
-
}
|
|
57
|
+
}
|
|
5
58
|
function sleep(ms) {
|
|
6
59
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
60
|
}
|
|
@@ -19,8 +72,8 @@ function normalizeCacheKey(key) {
|
|
|
19
72
|
return key;
|
|
20
73
|
}
|
|
21
74
|
function ttlToExpiresAt(ttl, now = Date.now()) {
|
|
22
|
-
if (ttl ===
|
|
23
|
-
return
|
|
75
|
+
if (ttl === undefined) {
|
|
76
|
+
return;
|
|
24
77
|
}
|
|
25
78
|
if (ttl === null) {
|
|
26
79
|
return null;
|
|
@@ -32,42 +85,87 @@ function ttlToExpiresAt(ttl, now = Date.now()) {
|
|
|
32
85
|
if (ttl <= 0) {
|
|
33
86
|
return now;
|
|
34
87
|
}
|
|
35
|
-
return now + ttl *
|
|
88
|
+
return now + ttl * 1000;
|
|
36
89
|
}
|
|
37
|
-
return
|
|
90
|
+
return;
|
|
38
91
|
}
|
|
39
92
|
function isExpired(expiresAt, now = Date.now()) {
|
|
40
93
|
if (expiresAt === null) {
|
|
41
94
|
return false;
|
|
42
95
|
}
|
|
43
|
-
if (expiresAt ===
|
|
96
|
+
if (expiresAt === undefined) {
|
|
44
97
|
return false;
|
|
45
98
|
}
|
|
46
99
|
return now >= expiresAt;
|
|
47
100
|
}
|
|
48
101
|
|
|
102
|
+
// src/tagged-store.ts
|
|
103
|
+
class TaggedStore {
|
|
104
|
+
store;
|
|
105
|
+
tags;
|
|
106
|
+
constructor(store, tags) {
|
|
107
|
+
this.store = store;
|
|
108
|
+
this.tags = tags;
|
|
109
|
+
}
|
|
110
|
+
tagged(key) {
|
|
111
|
+
return this.store.tagKey(normalizeCacheKey(key), this.tags);
|
|
112
|
+
}
|
|
113
|
+
async get(key) {
|
|
114
|
+
return this.store.get(this.tagged(key));
|
|
115
|
+
}
|
|
116
|
+
async put(key, value, ttl) {
|
|
117
|
+
const taggedKey = this.tagged(key);
|
|
118
|
+
await this.store.put(taggedKey, value, ttl);
|
|
119
|
+
this.store.tagIndexAdd(this.tags, taggedKey);
|
|
120
|
+
}
|
|
121
|
+
async add(key, value, ttl) {
|
|
122
|
+
const taggedKey = this.tagged(key);
|
|
123
|
+
const ok = await this.store.add(taggedKey, value, ttl);
|
|
124
|
+
if (ok) {
|
|
125
|
+
this.store.tagIndexAdd(this.tags, taggedKey);
|
|
126
|
+
}
|
|
127
|
+
return ok;
|
|
128
|
+
}
|
|
129
|
+
async forget(key) {
|
|
130
|
+
return this.store.forget(this.tagged(key));
|
|
131
|
+
}
|
|
132
|
+
async flush() {
|
|
133
|
+
return this.store.flushTags(this.tags);
|
|
134
|
+
}
|
|
135
|
+
async increment(key, value) {
|
|
136
|
+
const taggedKey = this.tagged(key);
|
|
137
|
+
const next = await this.store.increment(taggedKey, value);
|
|
138
|
+
this.store.tagIndexAdd(this.tags, taggedKey);
|
|
139
|
+
return next;
|
|
140
|
+
}
|
|
141
|
+
async decrement(key, value) {
|
|
142
|
+
const taggedKey = this.tagged(key);
|
|
143
|
+
const next = await this.store.decrement(taggedKey, value);
|
|
144
|
+
this.store.tagIndexAdd(this.tags, taggedKey);
|
|
145
|
+
return next;
|
|
146
|
+
}
|
|
147
|
+
lock(name, seconds) {
|
|
148
|
+
return this.store.lock ? this.store.lock(this.tagged(name), seconds) : undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
49
152
|
// src/CacheRepository.ts
|
|
50
|
-
|
|
153
|
+
class CacheRepository {
|
|
154
|
+
store;
|
|
155
|
+
options;
|
|
156
|
+
refreshSemaphore = new Map;
|
|
157
|
+
coalesceSemaphore = new Map;
|
|
158
|
+
flexibleStats = { refreshCount: 0, refreshFailures: 0, totalTime: 0 };
|
|
159
|
+
eventEmitterConfig;
|
|
51
160
|
constructor(store, options = {}) {
|
|
52
161
|
this.store = store;
|
|
53
162
|
this.options = options;
|
|
163
|
+
this.eventEmitterConfig = {
|
|
164
|
+
mode: options.eventsMode ?? "async",
|
|
165
|
+
throwOnError: options.throwOnEventError,
|
|
166
|
+
onError: options.onEventError
|
|
167
|
+
};
|
|
54
168
|
}
|
|
55
|
-
refreshSemaphore = /* @__PURE__ */ new Map();
|
|
56
|
-
coalesceSemaphore = /* @__PURE__ */ new Map();
|
|
57
|
-
flexibleStats = { refreshCount: 0, refreshFailures: 0, totalTime: 0 };
|
|
58
|
-
/**
|
|
59
|
-
* Retrieve statistics about flexible cache operations.
|
|
60
|
-
*
|
|
61
|
-
* Useful for monitoring the health and performance of background refreshes.
|
|
62
|
-
*
|
|
63
|
-
* @returns Current statistics for background refresh operations.
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```typescript
|
|
67
|
-
* const stats = cache.getFlexibleStats();
|
|
68
|
-
* console.log(`Refreshed ${stats.refreshCount} times`);
|
|
69
|
-
* ```
|
|
70
|
-
*/
|
|
71
169
|
getFlexibleStats() {
|
|
72
170
|
return {
|
|
73
171
|
refreshCount: this.flexibleStats.refreshCount,
|
|
@@ -76,53 +174,7 @@ var CacheRepository = class _CacheRepository {
|
|
|
76
174
|
};
|
|
77
175
|
}
|
|
78
176
|
emit(event, payload = {}) {
|
|
79
|
-
|
|
80
|
-
if (mode === "off") {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const fn = this.options.events?.[event];
|
|
84
|
-
if (!fn) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const invoke = () => {
|
|
88
|
-
if (event === "flush") {
|
|
89
|
-
return fn();
|
|
90
|
-
}
|
|
91
|
-
const key = payload.key ?? "";
|
|
92
|
-
return fn(key);
|
|
93
|
-
};
|
|
94
|
-
const reportError = (error) => {
|
|
95
|
-
try {
|
|
96
|
-
this.options.onEventError?.(error, event, payload);
|
|
97
|
-
} catch {
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
if (mode === "sync") {
|
|
101
|
-
try {
|
|
102
|
-
return Promise.resolve(invoke()).catch((error) => {
|
|
103
|
-
reportError(error);
|
|
104
|
-
if (this.options.throwOnEventError) {
|
|
105
|
-
throw error;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
} catch (error) {
|
|
109
|
-
reportError(error);
|
|
110
|
-
if (this.options.throwOnEventError) {
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
queueMicrotask(() => {
|
|
117
|
-
try {
|
|
118
|
-
const result = invoke();
|
|
119
|
-
if (result && typeof result.catch === "function") {
|
|
120
|
-
void result.catch(reportError);
|
|
121
|
-
}
|
|
122
|
-
} catch (error) {
|
|
123
|
-
reportError(error);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
177
|
+
return emitCacheEvent(event, payload, this.options.events, this.eventEmitterConfig);
|
|
126
178
|
}
|
|
127
179
|
key(key) {
|
|
128
180
|
const normalized = normalizeCacheKey(key);
|
|
@@ -138,23 +190,6 @@ var CacheRepository = class _CacheRepository {
|
|
|
138
190
|
async forgetMetaKey(metaKey) {
|
|
139
191
|
await this.store.forget(metaKey);
|
|
140
192
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Retrieve an item from the cache by its key.
|
|
143
|
-
*
|
|
144
|
-
* Fetches the value from the underlying store. If not found, returns the
|
|
145
|
-
* provided default value or executes the factory function.
|
|
146
|
-
*
|
|
147
|
-
* @param key - The unique cache key.
|
|
148
|
-
* @param defaultValue - A default value or factory function to use if the key is not found.
|
|
149
|
-
* @returns The cached value, or the default value if not found.
|
|
150
|
-
* @throws {Error} If the underlying store fails to retrieve the value.
|
|
151
|
-
*
|
|
152
|
-
* @example
|
|
153
|
-
* ```typescript
|
|
154
|
-
* const user = await cache.get('user:1', { name: 'Guest' });
|
|
155
|
-
* const settings = await cache.get('settings', () => fetchSettings());
|
|
156
|
-
* ```
|
|
157
|
-
*/
|
|
158
193
|
async get(key, defaultValue) {
|
|
159
194
|
const fullKey = this.key(key);
|
|
160
195
|
const raw = await this.store.get(fullKey);
|
|
@@ -169,7 +204,7 @@ var CacheRepository = class _CacheRepository {
|
|
|
169
204
|
if (e) {
|
|
170
205
|
await e;
|
|
171
206
|
}
|
|
172
|
-
if (defaultValue ===
|
|
207
|
+
if (defaultValue === undefined) {
|
|
173
208
|
return null;
|
|
174
209
|
}
|
|
175
210
|
if (typeof defaultValue === "function") {
|
|
@@ -177,59 +212,12 @@ var CacheRepository = class _CacheRepository {
|
|
|
177
212
|
}
|
|
178
213
|
return defaultValue;
|
|
179
214
|
}
|
|
180
|
-
/**
|
|
181
|
-
* Determine if an item exists in the cache.
|
|
182
|
-
*
|
|
183
|
-
* Checks for the presence of a key without necessarily returning its value.
|
|
184
|
-
*
|
|
185
|
-
* @param key - The cache key.
|
|
186
|
-
* @returns True if the item exists, false otherwise.
|
|
187
|
-
* @throws {Error} If the underlying store fails to check existence.
|
|
188
|
-
*
|
|
189
|
-
* @example
|
|
190
|
-
* ```typescript
|
|
191
|
-
* if (await cache.has('session:active')) {
|
|
192
|
-
* // ...
|
|
193
|
-
* }
|
|
194
|
-
* ```
|
|
195
|
-
*/
|
|
196
215
|
async has(key) {
|
|
197
216
|
return await this.get(key) !== null;
|
|
198
217
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Determine if an item is missing from the cache.
|
|
201
|
-
*
|
|
202
|
-
* Inverse of `has()`, used for cleaner conditional logic.
|
|
203
|
-
*
|
|
204
|
-
* @param key - The cache key.
|
|
205
|
-
* @returns True if the item is missing, false otherwise.
|
|
206
|
-
* @throws {Error} If the underlying store fails to check existence.
|
|
207
|
-
*
|
|
208
|
-
* @example
|
|
209
|
-
* ```typescript
|
|
210
|
-
* if (await cache.missing('config:loaded')) {
|
|
211
|
-
* await loadConfig();
|
|
212
|
-
* }
|
|
213
|
-
* ```
|
|
214
|
-
*/
|
|
215
218
|
async missing(key) {
|
|
216
219
|
return !await this.has(key);
|
|
217
220
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Store an item in the cache for a specific duration.
|
|
220
|
-
*
|
|
221
|
-
* Persists the value in the underlying store with the given TTL.
|
|
222
|
-
*
|
|
223
|
-
* @param key - Unique cache key.
|
|
224
|
-
* @param value - Value to store.
|
|
225
|
-
* @param ttl - Expiration duration.
|
|
226
|
-
* @throws {Error} If the underlying store fails to persist the value or serialization fails.
|
|
227
|
-
*
|
|
228
|
-
* @example
|
|
229
|
-
* ```typescript
|
|
230
|
-
* await cache.put('token', 'xyz123', 3600);
|
|
231
|
-
* ```
|
|
232
|
-
*/
|
|
233
221
|
async put(key, value, ttl) {
|
|
234
222
|
const fullKey = this.key(key);
|
|
235
223
|
const data = await this.compress(value);
|
|
@@ -239,41 +227,10 @@ var CacheRepository = class _CacheRepository {
|
|
|
239
227
|
await e;
|
|
240
228
|
}
|
|
241
229
|
}
|
|
242
|
-
/**
|
|
243
|
-
* Store an item in the cache for a specific duration.
|
|
244
|
-
*
|
|
245
|
-
* Uses the repository's default TTL if none is provided.
|
|
246
|
-
*
|
|
247
|
-
* @param key - The unique cache key.
|
|
248
|
-
* @param value - The value to store.
|
|
249
|
-
* @param ttl - Optional time-to-live.
|
|
250
|
-
* @throws {Error} If the underlying store fails to persist the value.
|
|
251
|
-
*
|
|
252
|
-
* @example
|
|
253
|
-
* ```typescript
|
|
254
|
-
* await cache.set('theme', 'dark');
|
|
255
|
-
* ```
|
|
256
|
-
*/
|
|
257
230
|
async set(key, value, ttl) {
|
|
258
231
|
const resolved = ttl ?? this.options.defaultTtl;
|
|
259
232
|
await this.put(key, value, resolved);
|
|
260
233
|
}
|
|
261
|
-
/**
|
|
262
|
-
* Store an item in the cache only if the key does not already exist.
|
|
263
|
-
*
|
|
264
|
-
* Atomic operation to prevent overwriting existing data.
|
|
265
|
-
*
|
|
266
|
-
* @param key - The unique cache key.
|
|
267
|
-
* @param value - The value to store.
|
|
268
|
-
* @param ttl - Optional time-to-live.
|
|
269
|
-
* @returns True if the item was added, false otherwise.
|
|
270
|
-
* @throws {Error} If the underlying store fails the atomic operation.
|
|
271
|
-
*
|
|
272
|
-
* @example
|
|
273
|
-
* ```typescript
|
|
274
|
-
* const added = await cache.add('lock:process', true, 60);
|
|
275
|
-
* ```
|
|
276
|
-
*/
|
|
277
234
|
async add(key, value, ttl) {
|
|
278
235
|
const fullKey = this.key(key);
|
|
279
236
|
const resolved = ttl ?? this.options.defaultTtl;
|
|
@@ -286,40 +243,9 @@ var CacheRepository = class _CacheRepository {
|
|
|
286
243
|
}
|
|
287
244
|
return ok;
|
|
288
245
|
}
|
|
289
|
-
/**
|
|
290
|
-
* Store an item in the cache indefinitely.
|
|
291
|
-
*
|
|
292
|
-
* Sets the TTL to null, indicating the value should not expire automatically.
|
|
293
|
-
*
|
|
294
|
-
* @param key - The unique cache key.
|
|
295
|
-
* @param value - The value to store.
|
|
296
|
-
* @throws {Error} If the underlying store fails to persist the value.
|
|
297
|
-
*
|
|
298
|
-
* @example
|
|
299
|
-
* ```typescript
|
|
300
|
-
* await cache.forever('system:id', 'node-01');
|
|
301
|
-
* ```
|
|
302
|
-
*/
|
|
303
246
|
async forever(key, value) {
|
|
304
247
|
await this.put(key, value, null);
|
|
305
248
|
}
|
|
306
|
-
/**
|
|
307
|
-
* Get an item from the cache, or execute the given callback and store the result.
|
|
308
|
-
*
|
|
309
|
-
* Implements the "Cache-Aside" pattern, ensuring the callback is only executed
|
|
310
|
-
* on a cache miss.
|
|
311
|
-
*
|
|
312
|
-
* @param key - The unique cache key.
|
|
313
|
-
* @param ttl - Time-to-live.
|
|
314
|
-
* @param callback - The callback to execute if the key is not found.
|
|
315
|
-
* @returns The cached value or the result of the callback.
|
|
316
|
-
* @throws {Error} If the callback or the underlying store fails.
|
|
317
|
-
*
|
|
318
|
-
* @example
|
|
319
|
-
* ```typescript
|
|
320
|
-
* const data = await cache.remember('users:all', 300, () => db.users.findMany());
|
|
321
|
-
* ```
|
|
322
|
-
*/
|
|
323
249
|
async remember(key, ttl, callback) {
|
|
324
250
|
const fullKey = this.key(key);
|
|
325
251
|
if (this.coalesceSemaphore.has(fullKey)) {
|
|
@@ -341,38 +267,9 @@ var CacheRepository = class _CacheRepository {
|
|
|
341
267
|
this.coalesceSemaphore.set(fullKey, promise);
|
|
342
268
|
return promise;
|
|
343
269
|
}
|
|
344
|
-
/**
|
|
345
|
-
* Get an item from the cache, or execute the given callback and store the result indefinitely.
|
|
346
|
-
*
|
|
347
|
-
* Similar to `remember()`, but the value is stored without an expiration time.
|
|
348
|
-
*
|
|
349
|
-
* @param key - The unique cache key.
|
|
350
|
-
* @param callback - The callback to execute if the key is not found.
|
|
351
|
-
* @returns The cached value or the result of the callback.
|
|
352
|
-
* @throws {Error} If the callback or the underlying store fails.
|
|
353
|
-
*
|
|
354
|
-
* @example
|
|
355
|
-
* ```typescript
|
|
356
|
-
* const config = await cache.rememberForever('app:config', () => loadConfig());
|
|
357
|
-
* ```
|
|
358
|
-
*/
|
|
359
270
|
async rememberForever(key, callback) {
|
|
360
271
|
return this.remember(key, null, callback);
|
|
361
272
|
}
|
|
362
|
-
/**
|
|
363
|
-
* Retrieve multiple items from the cache by their keys.
|
|
364
|
-
*
|
|
365
|
-
* Efficiently fetches multiple values, returning a map of keys to values.
|
|
366
|
-
*
|
|
367
|
-
* @param keys - An array of unique cache keys.
|
|
368
|
-
* @returns An object where keys are the original keys and values are the cached values.
|
|
369
|
-
* @throws {Error} If the underlying store fails to retrieve values.
|
|
370
|
-
*
|
|
371
|
-
* @example
|
|
372
|
-
* ```typescript
|
|
373
|
-
* const results = await cache.many(['user:1', 'user:2']);
|
|
374
|
-
* ```
|
|
375
|
-
*/
|
|
376
273
|
async many(keys) {
|
|
377
274
|
const out = {};
|
|
378
275
|
for (const key of keys) {
|
|
@@ -380,47 +277,15 @@ var CacheRepository = class _CacheRepository {
|
|
|
380
277
|
}
|
|
381
278
|
return out;
|
|
382
279
|
}
|
|
383
|
-
/**
|
|
384
|
-
* Store multiple items in the cache for a specific duration.
|
|
385
|
-
*
|
|
386
|
-
* Persists multiple key-value pairs in a single operation if supported by the store.
|
|
387
|
-
*
|
|
388
|
-
* @param values - An object where keys are the unique cache keys and values are the values to store.
|
|
389
|
-
* @param ttl - Time-to-live.
|
|
390
|
-
* @throws {Error} If the underlying store fails to persist values.
|
|
391
|
-
*
|
|
392
|
-
* @example
|
|
393
|
-
* ```typescript
|
|
394
|
-
* await cache.putMany({ 'a': 1, 'b': 2 }, 60);
|
|
395
|
-
* ```
|
|
396
|
-
*/
|
|
397
280
|
async putMany(values, ttl) {
|
|
398
281
|
await Promise.all(Object.entries(values).map(([k, v]) => this.put(k, v, ttl)));
|
|
399
282
|
}
|
|
400
|
-
/**
|
|
401
|
-
* Laravel-like flexible cache (stale-while-revalidate).
|
|
402
|
-
*
|
|
403
|
-
* Serves stale content while revalidating the cache in the background. This
|
|
404
|
-
* minimizes latency for users by avoiding synchronous revalidation.
|
|
405
|
-
*
|
|
406
|
-
* @param key - The unique cache key.
|
|
407
|
-
* @param ttlSeconds - How long the value is considered fresh.
|
|
408
|
-
* @param staleSeconds - How long the stale value may be served while a refresh happens.
|
|
409
|
-
* @param callback - The callback to execute to refresh the cache.
|
|
410
|
-
* @returns The fresh or stale cached value.
|
|
411
|
-
* @throws {Error} If the callback fails on a cache miss.
|
|
412
|
-
*
|
|
413
|
-
* @example
|
|
414
|
-
* ```typescript
|
|
415
|
-
* const value = await cache.flexible('stats', 60, 30, () => fetchStats());
|
|
416
|
-
* ```
|
|
417
|
-
*/
|
|
418
283
|
async flexible(key, ttlSeconds, staleSeconds, callback) {
|
|
419
284
|
const fullKey = this.key(key);
|
|
420
285
|
const metaKey = this.flexibleFreshUntilKey(fullKey);
|
|
421
286
|
const now = Date.now();
|
|
422
|
-
const ttlMillis = Math.max(0, ttlSeconds) *
|
|
423
|
-
const staleMillis = Math.max(0, staleSeconds) *
|
|
287
|
+
const ttlMillis = Math.max(0, ttlSeconds) * 1000;
|
|
288
|
+
const staleMillis = Math.max(0, staleSeconds) * 1000;
|
|
424
289
|
const [freshUntil, cachedValue] = await Promise.all([
|
|
425
290
|
this.store.get(metaKey),
|
|
426
291
|
this.store.get(fullKey)
|
|
@@ -438,7 +303,7 @@ var CacheRepository = class _CacheRepository {
|
|
|
438
303
|
if (e2) {
|
|
439
304
|
await e2;
|
|
440
305
|
}
|
|
441
|
-
|
|
306
|
+
this.refreshFlexible(fullKey, metaKey, ttlSeconds, staleSeconds, callback);
|
|
442
307
|
return cachedValue;
|
|
443
308
|
}
|
|
444
309
|
}
|
|
@@ -474,18 +339,16 @@ var CacheRepository = class _CacheRepository {
|
|
|
474
339
|
}
|
|
475
340
|
const startTime = Date.now();
|
|
476
341
|
try {
|
|
477
|
-
const timeoutMillis = this.options.refreshTimeout ??
|
|
342
|
+
const timeoutMillis = this.options.refreshTimeout ?? 30000;
|
|
478
343
|
const maxRetries = this.options.maxRetries ?? 0;
|
|
479
344
|
const retryDelay = this.options.retryDelay ?? 50;
|
|
480
345
|
let lastError;
|
|
481
346
|
let value;
|
|
482
|
-
for (let attempt = 0;
|
|
347
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
483
348
|
try {
|
|
484
349
|
value = await Promise.race([
|
|
485
350
|
Promise.resolve(callback()),
|
|
486
|
-
new Promise(
|
|
487
|
-
(_, reject) => setTimeout(() => reject(new Error("Refresh timeout")), timeoutMillis)
|
|
488
|
-
)
|
|
351
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Refresh timeout")), timeoutMillis))
|
|
489
352
|
]);
|
|
490
353
|
break;
|
|
491
354
|
} catch (err) {
|
|
@@ -495,13 +358,13 @@ var CacheRepository = class _CacheRepository {
|
|
|
495
358
|
}
|
|
496
359
|
}
|
|
497
360
|
}
|
|
498
|
-
if (value ===
|
|
361
|
+
if (value === undefined && lastError) {
|
|
499
362
|
throw lastError;
|
|
500
363
|
}
|
|
501
364
|
const totalTtl = ttlSeconds + staleSeconds;
|
|
502
365
|
const now = Date.now();
|
|
503
366
|
await this.put(fullKey, value, totalTtl);
|
|
504
|
-
await this.putMetaKey(metaKey, now + Math.max(0, ttlSeconds) *
|
|
367
|
+
await this.putMetaKey(metaKey, now + Math.max(0, ttlSeconds) * 1000, totalTtl);
|
|
505
368
|
this.flexibleStats.refreshCount++;
|
|
506
369
|
this.flexibleStats.totalTime += Date.now() - startTime;
|
|
507
370
|
} catch {
|
|
@@ -510,41 +373,11 @@ var CacheRepository = class _CacheRepository {
|
|
|
510
373
|
await lock.release();
|
|
511
374
|
}
|
|
512
375
|
}
|
|
513
|
-
/**
|
|
514
|
-
* Retrieve an item from the cache and delete it.
|
|
515
|
-
*
|
|
516
|
-
* Atomic-like operation to fetch and immediately remove a value, often used
|
|
517
|
-
* for one-time tokens or flash messages.
|
|
518
|
-
*
|
|
519
|
-
* @param key - The unique cache key.
|
|
520
|
-
* @param defaultValue - A default value to use if the key is not found.
|
|
521
|
-
* @returns The cached value, or the default value if not found.
|
|
522
|
-
* @throws {Error} If the underlying store fails to retrieve or forget the value.
|
|
523
|
-
*
|
|
524
|
-
* @example
|
|
525
|
-
* ```typescript
|
|
526
|
-
* const message = await cache.pull('flash:status');
|
|
527
|
-
* ```
|
|
528
|
-
*/
|
|
529
376
|
async pull(key, defaultValue) {
|
|
530
377
|
const value = await this.get(key, defaultValue);
|
|
531
378
|
await this.forget(key);
|
|
532
379
|
return value;
|
|
533
380
|
}
|
|
534
|
-
/**
|
|
535
|
-
* Remove an item from the cache by its key.
|
|
536
|
-
*
|
|
537
|
-
* Deletes the value and any associated metadata from the underlying store.
|
|
538
|
-
*
|
|
539
|
-
* @param key - The cache key to remove.
|
|
540
|
-
* @returns True if the item existed and was removed.
|
|
541
|
-
* @throws {Error} If the underlying store fails to remove the value.
|
|
542
|
-
*
|
|
543
|
-
* @example
|
|
544
|
-
* ```typescript
|
|
545
|
-
* const deleted = await cache.forget('user:session');
|
|
546
|
-
* ```
|
|
547
|
-
*/
|
|
548
381
|
async forget(key) {
|
|
549
382
|
const fullKey = this.key(key);
|
|
550
383
|
const metaKey = this.flexibleFreshUntilKey(fullKey);
|
|
@@ -558,36 +391,9 @@ var CacheRepository = class _CacheRepository {
|
|
|
558
391
|
}
|
|
559
392
|
return ok;
|
|
560
393
|
}
|
|
561
|
-
/**
|
|
562
|
-
* Alias for `forget`.
|
|
563
|
-
*
|
|
564
|
-
* Provides compatibility with standard `Map`-like or `Storage` APIs.
|
|
565
|
-
*
|
|
566
|
-
* @param key - The cache key to remove.
|
|
567
|
-
* @returns True if the item existed and was removed.
|
|
568
|
-
* @throws {Error} If the underlying store fails to remove the value.
|
|
569
|
-
*
|
|
570
|
-
* @example
|
|
571
|
-
* ```typescript
|
|
572
|
-
* await cache.delete('temp:data');
|
|
573
|
-
* ```
|
|
574
|
-
*/
|
|
575
394
|
async delete(key) {
|
|
576
395
|
return this.forget(key);
|
|
577
396
|
}
|
|
578
|
-
/**
|
|
579
|
-
* Remove all items from the cache storage.
|
|
580
|
-
*
|
|
581
|
-
* Clears the entire underlying store. Use with caution as this affects all
|
|
582
|
-
* keys regardless of prefix.
|
|
583
|
-
*
|
|
584
|
-
* @throws {Error} If the underlying store fails to flush.
|
|
585
|
-
*
|
|
586
|
-
* @example
|
|
587
|
-
* ```typescript
|
|
588
|
-
* await cache.flush();
|
|
589
|
-
* ```
|
|
590
|
-
*/
|
|
591
397
|
async flush() {
|
|
592
398
|
await this.store.flush();
|
|
593
399
|
const e = this.emit("flush");
|
|
@@ -595,148 +401,52 @@ var CacheRepository = class _CacheRepository {
|
|
|
595
401
|
await e;
|
|
596
402
|
}
|
|
597
403
|
}
|
|
598
|
-
/**
|
|
599
|
-
* Alias for `flush`.
|
|
600
|
-
*
|
|
601
|
-
* Provides compatibility with standard `Map`-like or `Storage` APIs.
|
|
602
|
-
*
|
|
603
|
-
* @throws {Error} If the underlying store fails to clear.
|
|
604
|
-
*
|
|
605
|
-
* @example
|
|
606
|
-
* ```typescript
|
|
607
|
-
* await cache.clear();
|
|
608
|
-
* ```
|
|
609
|
-
*/
|
|
610
404
|
async clear() {
|
|
611
405
|
return this.flush();
|
|
612
406
|
}
|
|
613
|
-
/**
|
|
614
|
-
* Increment the value of a numeric item in the cache.
|
|
615
|
-
*
|
|
616
|
-
* Atomically increases the value of a key. If the key does not exist, it is
|
|
617
|
-
* typically initialized to 0 before incrementing.
|
|
618
|
-
*
|
|
619
|
-
* @param key - The cache key.
|
|
620
|
-
* @param value - The amount to increment by.
|
|
621
|
-
* @returns The new value.
|
|
622
|
-
* @throws {Error} If the underlying store fails the atomic increment.
|
|
623
|
-
*
|
|
624
|
-
* @example
|
|
625
|
-
* ```typescript
|
|
626
|
-
* const count = await cache.increment('page:views');
|
|
627
|
-
* ```
|
|
628
|
-
*/
|
|
629
407
|
increment(key, value) {
|
|
630
408
|
return this.store.increment(this.key(key), value);
|
|
631
409
|
}
|
|
632
|
-
/**
|
|
633
|
-
* Decrement the value of a numeric item in the cache.
|
|
634
|
-
*
|
|
635
|
-
* Atomically decreases the value of a key.
|
|
636
|
-
*
|
|
637
|
-
* @param key - The cache key.
|
|
638
|
-
* @param value - The amount to decrement by.
|
|
639
|
-
* @returns The new value.
|
|
640
|
-
* @throws {Error} If the underlying store fails the atomic decrement.
|
|
641
|
-
*
|
|
642
|
-
* @example
|
|
643
|
-
* ```typescript
|
|
644
|
-
* const remaining = await cache.decrement('stock:count');
|
|
645
|
-
* ```
|
|
646
|
-
*/
|
|
647
410
|
decrement(key, value) {
|
|
648
411
|
return this.store.decrement(this.key(key), value);
|
|
649
412
|
}
|
|
650
|
-
/**
|
|
651
|
-
* Get a distributed lock instance for the given name.
|
|
652
|
-
*
|
|
653
|
-
* Provides a mechanism for exclusive access to resources across multiple
|
|
654
|
-
* processes or servers.
|
|
655
|
-
*
|
|
656
|
-
* @param name - The lock name.
|
|
657
|
-
* @param seconds - Optional default duration for the lock in seconds.
|
|
658
|
-
* @returns A `CacheLock` instance if supported, otherwise undefined.
|
|
659
|
-
*
|
|
660
|
-
* @example
|
|
661
|
-
* ```typescript
|
|
662
|
-
* const lock = cache.lock('process:heavy', 10);
|
|
663
|
-
* if (await lock.acquire()) {
|
|
664
|
-
* try {
|
|
665
|
-
* // ...
|
|
666
|
-
* } finally {
|
|
667
|
-
* await lock.release();
|
|
668
|
-
* }
|
|
669
|
-
* }
|
|
670
|
-
* ```
|
|
671
|
-
*/
|
|
672
413
|
lock(name, seconds) {
|
|
673
|
-
return this.store.lock ? this.store.lock(this.key(name), seconds) :
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Create a new repository instance with the given tags.
|
|
677
|
-
*
|
|
678
|
-
* Enables grouping of cache entries for collective operations like flushing
|
|
679
|
-
* all keys associated with specific tags.
|
|
680
|
-
*
|
|
681
|
-
* @param tags - An array of tag names.
|
|
682
|
-
* @returns A new `CacheRepository` instance that uses the given tags.
|
|
683
|
-
* @throws {Error} If the underlying store does not support tagging.
|
|
684
|
-
*
|
|
685
|
-
* @example
|
|
686
|
-
* ```typescript
|
|
687
|
-
* await cache.tags(['users', 'profiles']).put('user:1', data, 3600);
|
|
688
|
-
* await cache.tags(['users']).flush();
|
|
689
|
-
* ```
|
|
690
|
-
*/
|
|
414
|
+
return this.store.lock ? this.store.lock(this.key(name), seconds) : undefined;
|
|
415
|
+
}
|
|
691
416
|
tags(tags) {
|
|
692
417
|
if (!isTaggableStore(this.store)) {
|
|
693
418
|
throw new Error("This cache store does not support tags.");
|
|
694
419
|
}
|
|
695
|
-
return new
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* Retrieve the underlying cache store.
|
|
699
|
-
*
|
|
700
|
-
* Provides direct access to the low-level store implementation for advanced
|
|
701
|
-
* use cases or debugging.
|
|
702
|
-
*
|
|
703
|
-
* @returns The low-level cache store instance.
|
|
704
|
-
*
|
|
705
|
-
* @example
|
|
706
|
-
* ```typescript
|
|
707
|
-
* const store = cache.getStore();
|
|
708
|
-
* ```
|
|
709
|
-
*/
|
|
420
|
+
return new CacheRepository(new TaggedStore(this.store, tags), this.options);
|
|
421
|
+
}
|
|
710
422
|
getStore() {
|
|
711
423
|
return this.store;
|
|
712
424
|
}
|
|
713
|
-
/**
|
|
714
|
-
* Compress a value before storage if compression is enabled and thresholds are met.
|
|
715
|
-
*/
|
|
716
425
|
async compress(value) {
|
|
717
426
|
const opts = this.options.compression;
|
|
718
|
-
if (!opts?.enabled || value === null || value ===
|
|
427
|
+
if (!opts?.enabled || value === null || value === undefined) {
|
|
719
428
|
return value;
|
|
720
429
|
}
|
|
721
430
|
const json = JSON.stringify(value);
|
|
722
431
|
if (json.length < (opts.minSize ?? 1024)) {
|
|
723
432
|
return value;
|
|
724
433
|
}
|
|
725
|
-
const {
|
|
726
|
-
const
|
|
434
|
+
const { getCompressionAdapter } = await import("@gravito/core");
|
|
435
|
+
const adapter = getCompressionAdapter();
|
|
436
|
+
const input = new TextEncoder().encode(json);
|
|
437
|
+
const compressed = adapter.gzipSync(input, { level: opts.level ?? 6 });
|
|
727
438
|
return {
|
|
728
439
|
__gravito_compressed: true,
|
|
729
|
-
data: compressed.toString("base64")
|
|
440
|
+
data: Buffer.from(compressed).toString("base64")
|
|
730
441
|
};
|
|
731
442
|
}
|
|
732
|
-
/**
|
|
733
|
-
* Decompress a value after retrieval if it was previously compressed.
|
|
734
|
-
*/
|
|
735
443
|
async decompress(value) {
|
|
736
444
|
if (value !== null && typeof value === "object" && "__gravito_compressed" in value && value.__gravito_compressed === true) {
|
|
737
|
-
const {
|
|
738
|
-
const
|
|
739
|
-
const
|
|
445
|
+
const { getCompressionAdapter } = await import("@gravito/core");
|
|
446
|
+
const adapter = getCompressionAdapter();
|
|
447
|
+
const buffer = new Uint8Array(Buffer.from(value.data, "base64"));
|
|
448
|
+
const decompressedBytes = adapter.gunzipSync(buffer);
|
|
449
|
+
const decompressed = new TextDecoder().decode(decompressedBytes);
|
|
740
450
|
try {
|
|
741
451
|
return JSON.parse(decompressed);
|
|
742
452
|
} catch {
|
|
@@ -745,93 +455,17 @@ var CacheRepository = class _CacheRepository {
|
|
|
745
455
|
}
|
|
746
456
|
return value;
|
|
747
457
|
}
|
|
748
|
-
}
|
|
749
|
-
var TaggedStore = class {
|
|
750
|
-
constructor(store, tags) {
|
|
751
|
-
this.store = store;
|
|
752
|
-
this.tags = tags;
|
|
753
|
-
}
|
|
754
|
-
tagged(key) {
|
|
755
|
-
return this.store.tagKey(normalizeCacheKey(key), this.tags);
|
|
756
|
-
}
|
|
757
|
-
async get(key) {
|
|
758
|
-
return this.store.get(this.tagged(key));
|
|
759
|
-
}
|
|
760
|
-
async put(key, value, ttl) {
|
|
761
|
-
const taggedKey = this.tagged(key);
|
|
762
|
-
await this.store.put(taggedKey, value, ttl);
|
|
763
|
-
this.store.tagIndexAdd(this.tags, taggedKey);
|
|
764
|
-
}
|
|
765
|
-
async add(key, value, ttl) {
|
|
766
|
-
const taggedKey = this.tagged(key);
|
|
767
|
-
const ok = await this.store.add(taggedKey, value, ttl);
|
|
768
|
-
if (ok) {
|
|
769
|
-
this.store.tagIndexAdd(this.tags, taggedKey);
|
|
770
|
-
}
|
|
771
|
-
return ok;
|
|
772
|
-
}
|
|
773
|
-
async forget(key) {
|
|
774
|
-
return this.store.forget(this.tagged(key));
|
|
775
|
-
}
|
|
776
|
-
async flush() {
|
|
777
|
-
return this.store.flushTags(this.tags);
|
|
778
|
-
}
|
|
779
|
-
async increment(key, value) {
|
|
780
|
-
const taggedKey = this.tagged(key);
|
|
781
|
-
const next = await this.store.increment(taggedKey, value);
|
|
782
|
-
this.store.tagIndexAdd(this.tags, taggedKey);
|
|
783
|
-
return next;
|
|
784
|
-
}
|
|
785
|
-
async decrement(key, value) {
|
|
786
|
-
const taggedKey = this.tagged(key);
|
|
787
|
-
const next = await this.store.decrement(taggedKey, value);
|
|
788
|
-
this.store.tagIndexAdd(this.tags, taggedKey);
|
|
789
|
-
return next;
|
|
790
|
-
}
|
|
791
|
-
lock(name, seconds) {
|
|
792
|
-
return this.store.lock ? this.store.lock(this.tagged(name), seconds) : void 0;
|
|
793
|
-
}
|
|
794
|
-
};
|
|
458
|
+
}
|
|
795
459
|
|
|
796
460
|
// src/RateLimiter.ts
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
* Creates a new RateLimiter instance.
|
|
800
|
-
*
|
|
801
|
-
* @param store - Cache backend used to persist attempt counts.
|
|
802
|
-
*
|
|
803
|
-
* @example
|
|
804
|
-
* ```typescript
|
|
805
|
-
* const limiter = new RateLimiter(new MemoryStore());
|
|
806
|
-
* ```
|
|
807
|
-
*/
|
|
461
|
+
class RateLimiter {
|
|
462
|
+
store;
|
|
808
463
|
constructor(store) {
|
|
809
464
|
this.store = store;
|
|
810
465
|
}
|
|
811
|
-
/**
|
|
812
|
-
* Attempt to consume a slot in the rate limit window.
|
|
813
|
-
*
|
|
814
|
-
* This method checks the current attempt count for the given key. If the
|
|
815
|
-
* count is below the limit, it increments the count and allows the request.
|
|
816
|
-
* Otherwise, it returns a rejected status with retry information.
|
|
817
|
-
*
|
|
818
|
-
* @param key - The unique identifier for the rate limit (e.g., IP address or user ID).
|
|
819
|
-
* @param maxAttempts - Maximum number of attempts allowed within the decay period.
|
|
820
|
-
* @param decaySeconds - Duration of the rate limit window in seconds.
|
|
821
|
-
* @returns A response indicating if the attempt was successful and the remaining capacity.
|
|
822
|
-
* @throws {Error} If the underlying cache store fails to read or write data.
|
|
823
|
-
*
|
|
824
|
-
* @example
|
|
825
|
-
* ```typescript
|
|
826
|
-
* const response = await limiter.attempt('api-client:123', 100, 3600);
|
|
827
|
-
* if (response.allowed) {
|
|
828
|
-
* // Proceed with request
|
|
829
|
-
* }
|
|
830
|
-
* ```
|
|
831
|
-
*/
|
|
832
466
|
async attempt(key, maxAttempts, decaySeconds) {
|
|
833
467
|
const current = await this.store.get(key);
|
|
834
|
-
const now = Math.floor(Date.now() /
|
|
468
|
+
const now = Math.floor(Date.now() / 1000);
|
|
835
469
|
if (current === null) {
|
|
836
470
|
await this.store.put(key, 1, decaySeconds);
|
|
837
471
|
return {
|
|
@@ -856,18 +490,6 @@ var RateLimiter = class {
|
|
|
856
490
|
reset: now + decaySeconds
|
|
857
491
|
};
|
|
858
492
|
}
|
|
859
|
-
/**
|
|
860
|
-
* Calculate the number of seconds until the rate limit resets.
|
|
861
|
-
*
|
|
862
|
-
* This helper method attempts to retrieve the TTL from the store. If the
|
|
863
|
-
* store does not support TTL inspection, it falls back to the provided
|
|
864
|
-
* decay period.
|
|
865
|
-
*
|
|
866
|
-
* @param key - Unique identifier for the rate limit.
|
|
867
|
-
* @param decaySeconds - Default decay period to use as a fallback.
|
|
868
|
-
* @returns Number of seconds until the key expires.
|
|
869
|
-
* @throws {Error} If the store fails to retrieve TTL metadata.
|
|
870
|
-
*/
|
|
871
493
|
async availableIn(key, decaySeconds) {
|
|
872
494
|
if (typeof this.store.ttl === "function") {
|
|
873
495
|
const remaining = await this.store.ttl(key);
|
|
@@ -877,27 +499,9 @@ var RateLimiter = class {
|
|
|
877
499
|
}
|
|
878
500
|
return decaySeconds;
|
|
879
501
|
}
|
|
880
|
-
/**
|
|
881
|
-
* Get detailed information about the current rate limit status without consuming an attempt.
|
|
882
|
-
*
|
|
883
|
-
* Useful for returning rate limit headers (e.g., X-RateLimit-Limit) in
|
|
884
|
-
* middleware or for pre-flight checks.
|
|
885
|
-
*
|
|
886
|
-
* @param key - The unique identifier for the rate limit.
|
|
887
|
-
* @param maxAttempts - Maximum number of attempts allowed.
|
|
888
|
-
* @param decaySeconds - Duration of the rate limit window in seconds.
|
|
889
|
-
* @returns Current status including limit, remaining attempts, and reset time.
|
|
890
|
-
* @throws {Error} If the underlying cache store fails to retrieve data.
|
|
891
|
-
*
|
|
892
|
-
* @example
|
|
893
|
-
* ```typescript
|
|
894
|
-
* const info = await limiter.getInfo('user:42', 60, 60);
|
|
895
|
-
* console.log(`Remaining: ${info.remaining}/${info.limit}`);
|
|
896
|
-
* ```
|
|
897
|
-
*/
|
|
898
502
|
async getInfo(key, maxAttempts, decaySeconds) {
|
|
899
503
|
const current = await this.store.get(key);
|
|
900
|
-
const now = Math.floor(Date.now() /
|
|
504
|
+
const now = Math.floor(Date.now() / 1000);
|
|
901
505
|
if (current === null) {
|
|
902
506
|
return {
|
|
903
507
|
limit: maxAttempts,
|
|
@@ -906,7 +510,7 @@ var RateLimiter = class {
|
|
|
906
510
|
};
|
|
907
511
|
}
|
|
908
512
|
const remaining = Math.max(0, maxAttempts - current);
|
|
909
|
-
const retryAfter = remaining === 0 ? await this.availableIn(key, decaySeconds) :
|
|
513
|
+
const retryAfter = remaining === 0 ? await this.availableIn(key, decaySeconds) : undefined;
|
|
910
514
|
return {
|
|
911
515
|
limit: maxAttempts,
|
|
912
516
|
remaining,
|
|
@@ -914,82 +518,27 @@ var RateLimiter = class {
|
|
|
914
518
|
retryAfter
|
|
915
519
|
};
|
|
916
520
|
}
|
|
917
|
-
/**
|
|
918
|
-
* Reset the rate limit counter for a specific key.
|
|
919
|
-
*
|
|
920
|
-
* Use this to manually clear a block, for example after a successful
|
|
921
|
-
* login or when an administrator manually unblocks a user.
|
|
922
|
-
*
|
|
923
|
-
* @param key - The unique identifier to clear.
|
|
924
|
-
* @throws {Error} If the store fails to delete the key.
|
|
925
|
-
*
|
|
926
|
-
* @example
|
|
927
|
-
* ```typescript
|
|
928
|
-
* await limiter.clear('login-attempts:user@example.com');
|
|
929
|
-
* ```
|
|
930
|
-
*/
|
|
931
521
|
async clear(key) {
|
|
932
522
|
await this.store.forget(key);
|
|
933
523
|
}
|
|
934
|
-
}
|
|
524
|
+
}
|
|
935
525
|
|
|
936
526
|
// src/CacheManager.ts
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
* @param events - Optional event handlers for cache lifecycle hooks.
|
|
944
|
-
* @param eventOptions - Configuration for how events are dispatched and handled.
|
|
945
|
-
*/
|
|
527
|
+
class CacheManager {
|
|
528
|
+
storeFactory;
|
|
529
|
+
config;
|
|
530
|
+
events;
|
|
531
|
+
eventOptions;
|
|
532
|
+
stores = new Map;
|
|
946
533
|
constructor(storeFactory, config = {}, events, eventOptions) {
|
|
947
534
|
this.storeFactory = storeFactory;
|
|
948
535
|
this.config = config;
|
|
949
536
|
this.events = events;
|
|
950
537
|
this.eventOptions = eventOptions;
|
|
951
538
|
}
|
|
952
|
-
/**
|
|
953
|
-
* Internal registry of initialized cache repositories.
|
|
954
|
-
*/
|
|
955
|
-
stores = /* @__PURE__ */ new Map();
|
|
956
|
-
/**
|
|
957
|
-
* Get a rate limiter instance for a specific store.
|
|
958
|
-
*
|
|
959
|
-
* Provides a specialized interface for throttling actions based on cache keys,
|
|
960
|
-
* leveraging the underlying storage for persistence.
|
|
961
|
-
*
|
|
962
|
-
* @param name - Store name (defaults to the configured default store).
|
|
963
|
-
* @returns A RateLimiter instance bound to the requested store.
|
|
964
|
-
* @throws {Error} If the requested store cannot be initialized.
|
|
965
|
-
*
|
|
966
|
-
* @example
|
|
967
|
-
* ```typescript
|
|
968
|
-
* const limiter = cache.limiter('redis');
|
|
969
|
-
* if (await limiter.tooManyAttempts('login:1', 5)) {
|
|
970
|
-
* throw new Error('Too many attempts');
|
|
971
|
-
* }
|
|
972
|
-
* ```
|
|
973
|
-
*/
|
|
974
539
|
limiter(name) {
|
|
975
540
|
return new RateLimiter(this.store(name).getStore());
|
|
976
541
|
}
|
|
977
|
-
/**
|
|
978
|
-
* Resolve a named cache repository.
|
|
979
|
-
*
|
|
980
|
-
* Lazily initializes and caches the repository instance for the given store name.
|
|
981
|
-
* If no name is provided, it falls back to the default store.
|
|
982
|
-
*
|
|
983
|
-
* @param name - Store name to retrieve.
|
|
984
|
-
* @returns Initialized CacheRepository instance.
|
|
985
|
-
* @throws {Error} If the store factory fails to create the underlying store or the driver is unsupported.
|
|
986
|
-
*
|
|
987
|
-
* @example
|
|
988
|
-
* ```typescript
|
|
989
|
-
* const redis = cache.store('redis');
|
|
990
|
-
* await redis.put('key', 'value', 60);
|
|
991
|
-
* ```
|
|
992
|
-
*/
|
|
993
542
|
store(name) {
|
|
994
543
|
const storeName = name ?? this.config.default ?? "memory";
|
|
995
544
|
const existing = this.stores.get(storeName);
|
|
@@ -1007,381 +556,79 @@ var CacheManager = class {
|
|
|
1007
556
|
this.stores.set(storeName, repo);
|
|
1008
557
|
return repo;
|
|
1009
558
|
}
|
|
1010
|
-
// Laravel-like proxy methods (default store)
|
|
1011
|
-
/**
|
|
1012
|
-
* Retrieve an item from the default cache store.
|
|
1013
|
-
*
|
|
1014
|
-
* If the key is missing, the provided default value or the result of the
|
|
1015
|
-
* default value closure will be returned.
|
|
1016
|
-
*
|
|
1017
|
-
* @param key - Unique cache key.
|
|
1018
|
-
* @param defaultValue - Fallback value or factory to execute on cache miss.
|
|
1019
|
-
* @returns Cached value or the resolved default.
|
|
1020
|
-
* @throws {Error} If the underlying store driver encounters a read error or connection failure.
|
|
1021
|
-
*
|
|
1022
|
-
* @example
|
|
1023
|
-
* ```typescript
|
|
1024
|
-
* const value = await cache.get('user:1', { name: 'Guest' });
|
|
1025
|
-
* ```
|
|
1026
|
-
*/
|
|
1027
559
|
get(key, defaultValue) {
|
|
1028
560
|
return this.store().get(key, defaultValue);
|
|
1029
561
|
}
|
|
1030
|
-
/**
|
|
1031
|
-
* Determine if an item exists in the default cache store.
|
|
1032
|
-
*
|
|
1033
|
-
* @param key - The unique cache key.
|
|
1034
|
-
* @returns True if the key exists and has not expired.
|
|
1035
|
-
* @throws {Error} If the underlying store driver encounters a connection error.
|
|
1036
|
-
*
|
|
1037
|
-
* @example
|
|
1038
|
-
* ```typescript
|
|
1039
|
-
* if (await cache.has('session:active')) {
|
|
1040
|
-
* // ...
|
|
1041
|
-
* }
|
|
1042
|
-
* ```
|
|
1043
|
-
*/
|
|
1044
562
|
has(key) {
|
|
1045
563
|
return this.store().has(key);
|
|
1046
564
|
}
|
|
1047
|
-
/**
|
|
1048
|
-
* Determine if an item is missing from the default cache store.
|
|
1049
|
-
*
|
|
1050
|
-
* @param key - The unique cache key.
|
|
1051
|
-
* @returns True if the key does not exist or has expired.
|
|
1052
|
-
* @throws {Error} If the underlying store driver encounters a connection error.
|
|
1053
|
-
*
|
|
1054
|
-
* @example
|
|
1055
|
-
* ```typescript
|
|
1056
|
-
* if (await cache.missing('config:loaded')) {
|
|
1057
|
-
* await loadConfig();
|
|
1058
|
-
* }
|
|
1059
|
-
* ```
|
|
1060
|
-
*/
|
|
1061
565
|
missing(key) {
|
|
1062
566
|
return this.store().missing(key);
|
|
1063
567
|
}
|
|
1064
|
-
/**
|
|
1065
|
-
* Store an item in the default cache store for a specific duration.
|
|
1066
|
-
*
|
|
1067
|
-
* @param key - The unique cache key.
|
|
1068
|
-
* @param value - The data to be cached.
|
|
1069
|
-
* @param ttl - Expiration time in seconds or a specific Date.
|
|
1070
|
-
* @returns A promise that resolves when the write is complete.
|
|
1071
|
-
* @throws {Error} If the value cannot be serialized or the store is read-only.
|
|
1072
|
-
*
|
|
1073
|
-
* @example
|
|
1074
|
-
* ```typescript
|
|
1075
|
-
* await cache.put('key', 'value', 60); // 60 seconds
|
|
1076
|
-
* ```
|
|
1077
|
-
*/
|
|
1078
568
|
put(key, value, ttl) {
|
|
1079
569
|
return this.store().put(key, value, ttl);
|
|
1080
570
|
}
|
|
1081
|
-
/**
|
|
1082
|
-
* Store an item in the default cache store (alias for put).
|
|
1083
|
-
*
|
|
1084
|
-
* @param key - The unique cache key.
|
|
1085
|
-
* @param value - The data to be cached.
|
|
1086
|
-
* @param ttl - Optional expiration time.
|
|
1087
|
-
* @returns A promise that resolves when the write is complete.
|
|
1088
|
-
* @throws {Error} If the underlying store driver encounters a write error.
|
|
1089
|
-
*
|
|
1090
|
-
* @example
|
|
1091
|
-
* ```typescript
|
|
1092
|
-
* await cache.set('theme', 'dark');
|
|
1093
|
-
* ```
|
|
1094
|
-
*/
|
|
1095
571
|
set(key, value, ttl) {
|
|
1096
572
|
return this.store().set(key, value, ttl);
|
|
1097
573
|
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Store an item in the default cache store only if it does not already exist.
|
|
1100
|
-
*
|
|
1101
|
-
* @param key - The unique cache key.
|
|
1102
|
-
* @param value - The data to be cached.
|
|
1103
|
-
* @param ttl - Optional expiration time.
|
|
1104
|
-
* @returns True if the item was added, false if it already existed.
|
|
1105
|
-
* @throws {Error} If the underlying store driver encounters a write error.
|
|
1106
|
-
*
|
|
1107
|
-
* @example
|
|
1108
|
-
* ```typescript
|
|
1109
|
-
* const added = await cache.add('lock:process', true, 30);
|
|
1110
|
-
* ```
|
|
1111
|
-
*/
|
|
1112
574
|
add(key, value, ttl) {
|
|
1113
575
|
return this.store().add(key, value, ttl);
|
|
1114
576
|
}
|
|
1115
|
-
/**
|
|
1116
|
-
* Store an item in the default cache store indefinitely.
|
|
1117
|
-
*
|
|
1118
|
-
* @param key - The unique cache key.
|
|
1119
|
-
* @param value - The data to be cached.
|
|
1120
|
-
* @returns A promise that resolves when the write is complete.
|
|
1121
|
-
* @throws {Error} If the underlying store driver encounters a write error.
|
|
1122
|
-
*
|
|
1123
|
-
* @example
|
|
1124
|
-
* ```typescript
|
|
1125
|
-
* await cache.forever('system:version', '1.0.0');
|
|
1126
|
-
* ```
|
|
1127
|
-
*/
|
|
1128
577
|
forever(key, value) {
|
|
1129
578
|
return this.store().forever(key, value);
|
|
1130
579
|
}
|
|
1131
|
-
/**
|
|
1132
|
-
* Get an item from the cache, or execute the callback and store the result.
|
|
1133
|
-
*
|
|
1134
|
-
* Ensures the value is cached after the first miss. This provides an atomic-like
|
|
1135
|
-
* "get or set" flow to prevent multiple concurrent fetches of the same data.
|
|
1136
|
-
*
|
|
1137
|
-
* @param key - Unique cache key.
|
|
1138
|
-
* @param ttl - Duration to cache the result if a miss occurs.
|
|
1139
|
-
* @param callback - Logic to execute to fetch fresh data.
|
|
1140
|
-
* @returns Cached or freshly fetched value.
|
|
1141
|
-
* @throws {Error} If the callback fails or the store write operation errors.
|
|
1142
|
-
*
|
|
1143
|
-
* @example
|
|
1144
|
-
* ```typescript
|
|
1145
|
-
* const user = await cache.remember('user:1', 60, async () => {
|
|
1146
|
-
* return await db.findUser(1);
|
|
1147
|
-
* });
|
|
1148
|
-
* ```
|
|
1149
|
-
*/
|
|
1150
580
|
remember(key, ttl, callback) {
|
|
1151
581
|
return this.store().remember(key, ttl, callback);
|
|
1152
582
|
}
|
|
1153
|
-
/**
|
|
1154
|
-
* Get an item from the cache, or execute the callback and store the result forever.
|
|
1155
|
-
*
|
|
1156
|
-
* @param key - The unique cache key.
|
|
1157
|
-
* @param callback - The closure to execute to fetch the fresh data.
|
|
1158
|
-
* @returns The cached or freshly fetched value.
|
|
1159
|
-
* @throws {Error} If the callback throws or the store write fails.
|
|
1160
|
-
*
|
|
1161
|
-
* @example
|
|
1162
|
-
* ```typescript
|
|
1163
|
-
* const settings = await cache.rememberForever('global:settings', () => {
|
|
1164
|
-
* return fetchSettingsFromApi();
|
|
1165
|
-
* });
|
|
1166
|
-
* ```
|
|
1167
|
-
*/
|
|
1168
583
|
rememberForever(key, callback) {
|
|
1169
584
|
return this.store().rememberForever(key, callback);
|
|
1170
585
|
}
|
|
1171
|
-
/**
|
|
1172
|
-
* Retrieve multiple items from the default cache store by their keys.
|
|
1173
|
-
*
|
|
1174
|
-
* @param keys - An array of unique cache keys.
|
|
1175
|
-
* @returns An object mapping keys to their cached values (or null if missing).
|
|
1176
|
-
* @throws {Error} If the underlying store driver encounters a read error.
|
|
1177
|
-
*
|
|
1178
|
-
* @example
|
|
1179
|
-
* ```typescript
|
|
1180
|
-
* const values = await cache.many(['key1', 'key2']);
|
|
1181
|
-
* ```
|
|
1182
|
-
*/
|
|
1183
586
|
many(keys) {
|
|
1184
587
|
return this.store().many(keys);
|
|
1185
588
|
}
|
|
1186
|
-
/**
|
|
1187
|
-
* Store multiple items in the default cache store for a specific duration.
|
|
1188
|
-
*
|
|
1189
|
-
* @param values - An object mapping keys to the values to be stored.
|
|
1190
|
-
* @param ttl - Expiration time in seconds or a specific Date.
|
|
1191
|
-
* @returns A promise that resolves when all writes are complete.
|
|
1192
|
-
* @throws {Error} If the underlying store driver encounters a write error.
|
|
1193
|
-
*
|
|
1194
|
-
* @example
|
|
1195
|
-
* ```typescript
|
|
1196
|
-
* await cache.putMany({ a: 1, b: 2 }, 60);
|
|
1197
|
-
* ```
|
|
1198
|
-
*/
|
|
1199
589
|
putMany(values, ttl) {
|
|
1200
590
|
return this.store().putMany(values, ttl);
|
|
1201
591
|
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Get an item from the cache, allowing stale data while refreshing in background.
|
|
1204
|
-
*
|
|
1205
|
-
* Implements the Stale-While-Revalidate pattern to minimize latency for
|
|
1206
|
-
* frequently accessed but expensive data.
|
|
1207
|
-
*
|
|
1208
|
-
* @param key - The unique cache key.
|
|
1209
|
-
* @param ttlSeconds - How long the value is considered fresh.
|
|
1210
|
-
* @param staleSeconds - How long to serve stale data while refreshing.
|
|
1211
|
-
* @param callback - The closure to execute to refresh the data.
|
|
1212
|
-
* @returns The cached (possibly stale) or freshly fetched value.
|
|
1213
|
-
* @throws {Error} If the callback throws during an initial fetch.
|
|
1214
|
-
*
|
|
1215
|
-
* @example
|
|
1216
|
-
* ```typescript
|
|
1217
|
-
* const data = await cache.flexible('stats', 60, 30, () => fetchStats());
|
|
1218
|
-
* ```
|
|
1219
|
-
*/
|
|
1220
592
|
flexible(key, ttlSeconds, staleSeconds, callback) {
|
|
1221
593
|
return this.store().flexible(key, ttlSeconds, staleSeconds, callback);
|
|
1222
594
|
}
|
|
1223
|
-
/**
|
|
1224
|
-
* Retrieve an item from the default cache store and then delete it.
|
|
1225
|
-
*
|
|
1226
|
-
* Useful for one-time notifications or temporary tokens.
|
|
1227
|
-
*
|
|
1228
|
-
* @param key - The unique cache key.
|
|
1229
|
-
* @param defaultValue - Fallback value if the key is missing.
|
|
1230
|
-
* @returns The cached value before deletion, or the default.
|
|
1231
|
-
* @throws {Error} If the underlying store driver encounters a read or delete error.
|
|
1232
|
-
*
|
|
1233
|
-
* @example
|
|
1234
|
-
* ```typescript
|
|
1235
|
-
* const token = await cache.pull('temp_token');
|
|
1236
|
-
* ```
|
|
1237
|
-
*/
|
|
1238
595
|
pull(key, defaultValue) {
|
|
1239
596
|
return this.store().pull(key, defaultValue);
|
|
1240
597
|
}
|
|
1241
|
-
/**
|
|
1242
|
-
* Remove an item from the default cache store.
|
|
1243
|
-
*
|
|
1244
|
-
* @param key - The unique cache key.
|
|
1245
|
-
* @returns True if the item was removed, false otherwise.
|
|
1246
|
-
* @throws {Error} If the underlying store driver encounters a delete error.
|
|
1247
|
-
*
|
|
1248
|
-
* @example
|
|
1249
|
-
* ```typescript
|
|
1250
|
-
* await cache.forget('user:1');
|
|
1251
|
-
* ```
|
|
1252
|
-
*/
|
|
1253
598
|
forget(key) {
|
|
1254
599
|
return this.store().forget(key);
|
|
1255
600
|
}
|
|
1256
|
-
/**
|
|
1257
|
-
* Remove an item from the default cache store (alias for forget).
|
|
1258
|
-
*
|
|
1259
|
-
* @param key - The unique cache key.
|
|
1260
|
-
* @returns True if the item was removed, false otherwise.
|
|
1261
|
-
* @throws {Error} If the underlying store driver encounters a delete error.
|
|
1262
|
-
*
|
|
1263
|
-
* @example
|
|
1264
|
-
* ```typescript
|
|
1265
|
-
* await cache.delete('old_key');
|
|
1266
|
-
* ```
|
|
1267
|
-
*/
|
|
1268
601
|
delete(key) {
|
|
1269
602
|
return this.store().delete(key);
|
|
1270
603
|
}
|
|
1271
|
-
/**
|
|
1272
|
-
* Remove all items from the default cache store.
|
|
1273
|
-
*
|
|
1274
|
-
* @returns A promise that resolves when the flush is complete.
|
|
1275
|
-
* @throws {Error} If the underlying store driver encounters a flush error.
|
|
1276
|
-
*
|
|
1277
|
-
* @example
|
|
1278
|
-
* ```typescript
|
|
1279
|
-
* await cache.flush();
|
|
1280
|
-
* ```
|
|
1281
|
-
*/
|
|
1282
604
|
flush() {
|
|
1283
605
|
return this.store().flush();
|
|
1284
606
|
}
|
|
1285
|
-
/**
|
|
1286
|
-
* Clear the entire default cache store (alias for flush).
|
|
1287
|
-
*
|
|
1288
|
-
* @returns A promise that resolves when the clear is complete.
|
|
1289
|
-
* @throws {Error} If the underlying store driver encounters a clear error.
|
|
1290
|
-
*
|
|
1291
|
-
* @example
|
|
1292
|
-
* ```typescript
|
|
1293
|
-
* await cache.clear();
|
|
1294
|
-
* ```
|
|
1295
|
-
*/
|
|
1296
607
|
clear() {
|
|
1297
608
|
return this.store().clear();
|
|
1298
609
|
}
|
|
1299
|
-
/**
|
|
1300
|
-
* Increment the value of an integer item in the default cache store.
|
|
1301
|
-
*
|
|
1302
|
-
* @param key - Unique cache key.
|
|
1303
|
-
* @param value - Amount to add.
|
|
1304
|
-
* @returns New value after incrementing.
|
|
1305
|
-
* @throws {Error} If existing value is not a number or the store is read-only.
|
|
1306
|
-
*
|
|
1307
|
-
* @example
|
|
1308
|
-
* ```typescript
|
|
1309
|
-
* const count = await cache.increment('page_views');
|
|
1310
|
-
* ```
|
|
1311
|
-
*/
|
|
1312
610
|
increment(key, value) {
|
|
1313
611
|
return this.store().increment(key, value);
|
|
1314
612
|
}
|
|
1315
|
-
/**
|
|
1316
|
-
* Decrement the value of an integer item in the default cache store.
|
|
1317
|
-
*
|
|
1318
|
-
* @param key - Unique cache key.
|
|
1319
|
-
* @param value - Amount to subtract.
|
|
1320
|
-
* @returns New value after decrementing.
|
|
1321
|
-
* @throws {Error} If existing value is not a number or the store is read-only.
|
|
1322
|
-
*
|
|
1323
|
-
* @example
|
|
1324
|
-
* ```typescript
|
|
1325
|
-
* const remaining = await cache.decrement('stock:1');
|
|
1326
|
-
* ```
|
|
1327
|
-
*/
|
|
1328
613
|
decrement(key, value) {
|
|
1329
614
|
return this.store().decrement(key, value);
|
|
1330
615
|
}
|
|
1331
|
-
/**
|
|
1332
|
-
* Get a distributed lock instance from the default cache store.
|
|
1333
|
-
*
|
|
1334
|
-
* @param name - The unique name of the lock.
|
|
1335
|
-
* @param seconds - The duration the lock should be held for.
|
|
1336
|
-
* @returns A CacheLock instance.
|
|
1337
|
-
* @throws {Error} If the underlying store does not support locking.
|
|
1338
|
-
*
|
|
1339
|
-
* @example
|
|
1340
|
-
* ```typescript
|
|
1341
|
-
* const lock = cache.lock('process_data', 10);
|
|
1342
|
-
* if (await lock.get()) {
|
|
1343
|
-
* // ...
|
|
1344
|
-
* await lock.release();
|
|
1345
|
-
* }
|
|
1346
|
-
* ```
|
|
1347
|
-
*/
|
|
1348
616
|
lock(name, seconds) {
|
|
1349
617
|
return this.store().lock(name, seconds);
|
|
1350
618
|
}
|
|
1351
|
-
/**
|
|
1352
|
-
* Access a tagged cache section for grouped operations.
|
|
1353
|
-
*
|
|
1354
|
-
* Tags allow you to clear groups of cache entries simultaneously.
|
|
1355
|
-
* Note: This is only supported by specific drivers like 'memory'.
|
|
1356
|
-
*
|
|
1357
|
-
* @param tags - An array of tag names.
|
|
1358
|
-
* @returns A tagged cache repository instance.
|
|
1359
|
-
* @throws {Error} If the underlying store driver does not support tags.
|
|
1360
|
-
*
|
|
1361
|
-
* @example
|
|
1362
|
-
* ```typescript
|
|
1363
|
-
* await cache.tags(['users', 'profiles']).put('user:1', data, 60);
|
|
1364
|
-
* await cache.tags(['users']).flush(); // Clears all 'users' tagged entries
|
|
1365
|
-
* ```
|
|
1366
|
-
*/
|
|
1367
619
|
tags(tags) {
|
|
1368
620
|
return this.store().tags(tags);
|
|
1369
621
|
}
|
|
1370
|
-
}
|
|
622
|
+
}
|
|
1371
623
|
|
|
1372
624
|
// src/prediction/AccessPredictor.ts
|
|
1373
|
-
|
|
1374
|
-
transitions =
|
|
625
|
+
class MarkovPredictor {
|
|
626
|
+
transitions = new Map;
|
|
1375
627
|
lastKey = null;
|
|
1376
628
|
maxNodes;
|
|
1377
629
|
maxEdgesPerNode;
|
|
1378
|
-
/**
|
|
1379
|
-
* Initialize a new MarkovPredictor.
|
|
1380
|
-
*
|
|
1381
|
-
* @param options - Limits for internal transition graph to manage memory.
|
|
1382
|
-
*/
|
|
1383
630
|
constructor(options = {}) {
|
|
1384
|
-
this.maxNodes = options.maxNodes ??
|
|
631
|
+
this.maxNodes = options.maxNodes ?? 1000;
|
|
1385
632
|
this.maxEdgesPerNode = options.maxEdgesPerNode ?? 10;
|
|
1386
633
|
}
|
|
1387
634
|
record(key) {
|
|
@@ -1390,7 +637,7 @@ var MarkovPredictor = class {
|
|
|
1390
637
|
if (this.transitions.size >= this.maxNodes) {
|
|
1391
638
|
this.transitions.clear();
|
|
1392
639
|
}
|
|
1393
|
-
this.transitions.set(this.lastKey,
|
|
640
|
+
this.transitions.set(this.lastKey, new Map);
|
|
1394
641
|
}
|
|
1395
642
|
const edges = this.transitions.get(this.lastKey);
|
|
1396
643
|
const count = edges.get(key) ?? 0;
|
|
@@ -1422,22 +669,23 @@ var MarkovPredictor = class {
|
|
|
1422
669
|
this.transitions.clear();
|
|
1423
670
|
this.lastKey = null;
|
|
1424
671
|
}
|
|
1425
|
-
}
|
|
672
|
+
}
|
|
1426
673
|
|
|
1427
674
|
// src/stores/CircuitBreakerStore.ts
|
|
1428
|
-
|
|
675
|
+
class CircuitBreakerStore {
|
|
676
|
+
primary;
|
|
677
|
+
state = "CLOSED";
|
|
678
|
+
failures = 0;
|
|
679
|
+
lastErrorTime = 0;
|
|
680
|
+
options;
|
|
1429
681
|
constructor(primary, options = {}) {
|
|
1430
682
|
this.primary = primary;
|
|
1431
683
|
this.options = {
|
|
1432
684
|
maxFailures: options.maxFailures ?? 5,
|
|
1433
|
-
resetTimeout: options.resetTimeout ??
|
|
685
|
+
resetTimeout: options.resetTimeout ?? 60000,
|
|
1434
686
|
fallback: options.fallback
|
|
1435
687
|
};
|
|
1436
688
|
}
|
|
1437
|
-
state = "CLOSED";
|
|
1438
|
-
failures = 0;
|
|
1439
|
-
lastErrorTime = 0;
|
|
1440
|
-
options;
|
|
1441
689
|
async execute(operation, fallbackResult = null) {
|
|
1442
690
|
if (this.state === "OPEN") {
|
|
1443
691
|
if (Date.now() - this.lastErrorTime > this.options.resetTimeout) {
|
|
@@ -1470,8 +718,7 @@ var CircuitBreakerStore = class {
|
|
|
1470
718
|
if (this.options.fallback) {
|
|
1471
719
|
try {
|
|
1472
720
|
return await operation(this.options.fallback);
|
|
1473
|
-
} catch {
|
|
1474
|
-
}
|
|
721
|
+
} catch {}
|
|
1475
722
|
}
|
|
1476
723
|
return fallbackResult;
|
|
1477
724
|
}
|
|
@@ -1499,102 +746,71 @@ var CircuitBreakerStore = class {
|
|
|
1499
746
|
async ttl(key) {
|
|
1500
747
|
return this.execute(async (s) => s.ttl ? s.ttl(key) : null);
|
|
1501
748
|
}
|
|
1502
|
-
/**
|
|
1503
|
-
* Returns current state for monitoring.
|
|
1504
|
-
*
|
|
1505
|
-
* @returns Current state of the circuit breaker.
|
|
1506
|
-
*
|
|
1507
|
-
* @example
|
|
1508
|
-
* ```typescript
|
|
1509
|
-
* const state = store.getState();
|
|
1510
|
-
* if (state === 'OPEN') {
|
|
1511
|
-
* console.warn('Primary cache is unavailable');
|
|
1512
|
-
* }
|
|
1513
|
-
* ```
|
|
1514
|
-
*/
|
|
1515
749
|
getState() {
|
|
1516
750
|
return this.state;
|
|
1517
751
|
}
|
|
1518
|
-
}
|
|
752
|
+
}
|
|
1519
753
|
|
|
1520
754
|
// src/stores/FileStore.ts
|
|
1521
|
-
import {
|
|
1522
|
-
import {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
755
|
+
import { dirname, join } from "node:path";
|
|
756
|
+
import {
|
|
757
|
+
getRuntimeAdapter,
|
|
758
|
+
runtimeMkdir,
|
|
759
|
+
runtimeReadDir,
|
|
760
|
+
runtimeReadText,
|
|
761
|
+
runtimeRemoveRecursive,
|
|
762
|
+
runtimeRename,
|
|
763
|
+
runtimeStatFull,
|
|
764
|
+
runtimeWriteFileExclusive
|
|
765
|
+
} from "@gravito/core";
|
|
766
|
+
import { NativeHasher } from "@gravito/core/ffi";
|
|
767
|
+
class FileStore {
|
|
768
|
+
options;
|
|
769
|
+
cleanupTimer = null;
|
|
770
|
+
runtime = getRuntimeAdapter();
|
|
1530
771
|
constructor(options) {
|
|
1531
772
|
this.options = options;
|
|
1532
773
|
if (options.enableCleanup !== false) {
|
|
1533
|
-
this.startCleanupDaemon(options.cleanupInterval ??
|
|
774
|
+
this.startCleanupDaemon(options.cleanupInterval ?? 60000);
|
|
1534
775
|
}
|
|
1535
776
|
}
|
|
1536
|
-
cleanupTimer = null;
|
|
1537
|
-
/**
|
|
1538
|
-
* Starts the background process for periodic cache maintenance.
|
|
1539
|
-
*
|
|
1540
|
-
* @param interval - Time between cleanup cycles in milliseconds.
|
|
1541
|
-
* @internal
|
|
1542
|
-
*/
|
|
1543
777
|
startCleanupDaemon(interval) {
|
|
1544
778
|
this.cleanupTimer = setInterval(() => {
|
|
1545
|
-
this.cleanExpiredFiles().catch(() => {
|
|
1546
|
-
});
|
|
779
|
+
this.cleanExpiredFiles().catch(() => {});
|
|
1547
780
|
}, interval);
|
|
1548
781
|
if (this.cleanupTimer.unref) {
|
|
1549
782
|
this.cleanupTimer.unref();
|
|
1550
783
|
}
|
|
1551
784
|
}
|
|
1552
|
-
/**
|
|
1553
|
-
* Scans the cache directory to remove expired files and enforce capacity limits.
|
|
1554
|
-
*
|
|
1555
|
-
* This method performs a recursive scan of the storage directory. It deletes
|
|
1556
|
-
* files that have passed their expiration time and, if `maxFiles` is configured,
|
|
1557
|
-
* evicts the oldest files to stay within the limit.
|
|
1558
|
-
*
|
|
1559
|
-
* @returns The total number of files removed during this operation.
|
|
1560
|
-
* @throws {Error} If the directory cannot be read or files cannot be deleted.
|
|
1561
|
-
*
|
|
1562
|
-
* @example
|
|
1563
|
-
* ```typescript
|
|
1564
|
-
* const removedCount = await store.cleanExpiredFiles();
|
|
1565
|
-
* console.log(`Cleaned up ${removedCount} files.`);
|
|
1566
|
-
* ```
|
|
1567
|
-
*/
|
|
1568
785
|
async cleanExpiredFiles() {
|
|
1569
786
|
await this.ensureDir();
|
|
1570
787
|
let cleaned = 0;
|
|
1571
788
|
const validFiles = [];
|
|
1572
789
|
const scanDir = async (dir) => {
|
|
1573
|
-
const entries = await
|
|
790
|
+
const entries = await runtimeReadDir(this.runtime, dir);
|
|
1574
791
|
for (const entry of entries) {
|
|
1575
792
|
const fullPath = join(dir, entry.name);
|
|
1576
|
-
if (entry.isDirectory
|
|
793
|
+
if (entry.isDirectory) {
|
|
1577
794
|
await scanDir(fullPath);
|
|
1578
795
|
try {
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
}
|
|
1582
|
-
} else if (entry.isFile
|
|
796
|
+
const { rmdir } = await import("node:fs/promises");
|
|
797
|
+
await rmdir(fullPath);
|
|
798
|
+
} catch {}
|
|
799
|
+
} else if (entry.isFile) {
|
|
1583
800
|
if (!entry.name.endsWith(".json") || entry.name.startsWith(".lock-")) {
|
|
1584
801
|
continue;
|
|
1585
802
|
}
|
|
1586
803
|
try {
|
|
1587
|
-
const raw = await
|
|
804
|
+
const raw = await runtimeReadText(this.runtime, fullPath);
|
|
1588
805
|
const data = JSON.parse(raw);
|
|
1589
806
|
if (isExpired(data.expiresAt)) {
|
|
1590
|
-
await
|
|
807
|
+
await runtimeRemoveRecursive(this.runtime, fullPath);
|
|
1591
808
|
cleaned++;
|
|
1592
809
|
} else if (this.options.maxFiles) {
|
|
1593
|
-
const stats = await
|
|
810
|
+
const stats = await runtimeStatFull(this.runtime, fullPath);
|
|
1594
811
|
validFiles.push({ path: fullPath, mtime: stats.mtimeMs });
|
|
1595
812
|
}
|
|
1596
|
-
} catch {
|
|
1597
|
-
}
|
|
813
|
+
} catch {}
|
|
1598
814
|
}
|
|
1599
815
|
}
|
|
1600
816
|
};
|
|
@@ -1602,52 +818,25 @@ var FileStore = class {
|
|
|
1602
818
|
if (this.options.maxFiles && validFiles.length > this.options.maxFiles) {
|
|
1603
819
|
validFiles.sort((a, b) => a.mtime - b.mtime);
|
|
1604
820
|
const toRemove = validFiles.slice(0, validFiles.length - this.options.maxFiles);
|
|
1605
|
-
await Promise.all(
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
}
|
|
1612
|
-
})
|
|
1613
|
-
);
|
|
821
|
+
await Promise.all(toRemove.map(async (f) => {
|
|
822
|
+
try {
|
|
823
|
+
await runtimeRemoveRecursive(this.runtime, f.path);
|
|
824
|
+
cleaned++;
|
|
825
|
+
} catch {}
|
|
826
|
+
}));
|
|
1614
827
|
}
|
|
1615
828
|
return cleaned;
|
|
1616
829
|
}
|
|
1617
|
-
/**
|
|
1618
|
-
* Stops the cleanup daemon and releases associated resources.
|
|
1619
|
-
*
|
|
1620
|
-
* Should be called when the store is no longer needed to prevent memory leaks
|
|
1621
|
-
* and allow the process to exit gracefully.
|
|
1622
|
-
*
|
|
1623
|
-
* @example
|
|
1624
|
-
* ```typescript
|
|
1625
|
-
* await store.destroy();
|
|
1626
|
-
* ```
|
|
1627
|
-
*/
|
|
1628
830
|
async destroy() {
|
|
1629
831
|
if (this.cleanupTimer) {
|
|
1630
832
|
clearInterval(this.cleanupTimer);
|
|
1631
833
|
this.cleanupTimer = null;
|
|
1632
834
|
}
|
|
1633
835
|
}
|
|
1634
|
-
/**
|
|
1635
|
-
* Ensures that the base storage directory exists.
|
|
1636
|
-
*
|
|
1637
|
-
* @throws {Error} If the directory cannot be created due to permissions or path conflicts.
|
|
1638
|
-
* @internal
|
|
1639
|
-
*/
|
|
1640
836
|
async ensureDir() {
|
|
1641
|
-
await
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
* Resolves the filesystem path for a given cache key.
|
|
1645
|
-
*
|
|
1646
|
-
* @param key - Normalized cache key.
|
|
1647
|
-
* @returns Absolute path to the JSON file representing the key.
|
|
1648
|
-
* @internal
|
|
1649
|
-
*/
|
|
1650
|
-
filePathForKey(key) {
|
|
837
|
+
await runtimeMkdir(this.runtime, this.options.directory, { recursive: true });
|
|
838
|
+
}
|
|
839
|
+
async filePathForKey(key) {
|
|
1651
840
|
const hashed = hashKey(key);
|
|
1652
841
|
if (this.options.useSubdirectories) {
|
|
1653
842
|
const d1 = hashed.substring(0, 2);
|
|
@@ -1656,26 +845,12 @@ var FileStore = class {
|
|
|
1656
845
|
}
|
|
1657
846
|
return join(this.options.directory, `${hashed}.json`);
|
|
1658
847
|
}
|
|
1659
|
-
/**
|
|
1660
|
-
* Retrieves an item from the cache by its key.
|
|
1661
|
-
*
|
|
1662
|
-
* If the item exists but has expired, it will be deleted and `null` will be returned.
|
|
1663
|
-
*
|
|
1664
|
-
* @param key - The unique identifier for the cache item.
|
|
1665
|
-
* @returns The cached value, or `null` if not found or expired.
|
|
1666
|
-
* @throws {Error} If the file exists but cannot be read or parsed.
|
|
1667
|
-
*
|
|
1668
|
-
* @example
|
|
1669
|
-
* ```typescript
|
|
1670
|
-
* const value = await store.get<string>('my-key');
|
|
1671
|
-
* ```
|
|
1672
|
-
*/
|
|
1673
848
|
async get(key) {
|
|
1674
849
|
const normalized = normalizeCacheKey(key);
|
|
1675
850
|
await this.ensureDir();
|
|
1676
|
-
const file = this.filePathForKey(normalized);
|
|
851
|
+
const file = await this.filePathForKey(normalized);
|
|
1677
852
|
try {
|
|
1678
|
-
const raw = await
|
|
853
|
+
const raw = await runtimeReadText(this.runtime, file);
|
|
1679
854
|
const data = JSON.parse(raw);
|
|
1680
855
|
if (isExpired(data.expiresAt)) {
|
|
1681
856
|
await this.forget(normalized);
|
|
@@ -1686,58 +861,28 @@ var FileStore = class {
|
|
|
1686
861
|
return null;
|
|
1687
862
|
}
|
|
1688
863
|
}
|
|
1689
|
-
/**
|
|
1690
|
-
* Stores an item in the cache with a specified expiration time.
|
|
1691
|
-
*
|
|
1692
|
-
* Uses an atomic write strategy (write to temp file then rename) to ensure
|
|
1693
|
-
* data integrity even if the process crashes during the write operation.
|
|
1694
|
-
*
|
|
1695
|
-
* @param key - The unique identifier for the cache item.
|
|
1696
|
-
* @param value - The data to be cached.
|
|
1697
|
-
* @param ttl - Time-to-live in seconds or a Date object for absolute expiration.
|
|
1698
|
-
* @throws {Error} If the file system is not writable or disk is full.
|
|
1699
|
-
*
|
|
1700
|
-
* @example
|
|
1701
|
-
* ```typescript
|
|
1702
|
-
* await store.put('settings', { theme: 'dark' }, 86400);
|
|
1703
|
-
* ```
|
|
1704
|
-
*/
|
|
1705
864
|
async put(key, value, ttl) {
|
|
1706
865
|
const normalized = normalizeCacheKey(key);
|
|
1707
866
|
await this.ensureDir();
|
|
1708
867
|
const expiresAt = ttlToExpiresAt(ttl);
|
|
1709
|
-
if (expiresAt !== null && expiresAt !==
|
|
868
|
+
if (expiresAt !== null && expiresAt !== undefined && expiresAt <= Date.now()) {
|
|
1710
869
|
await this.forget(normalized);
|
|
1711
870
|
return;
|
|
1712
871
|
}
|
|
1713
|
-
const file = this.filePathForKey(normalized);
|
|
872
|
+
const file = await this.filePathForKey(normalized);
|
|
1714
873
|
if (this.options.useSubdirectories) {
|
|
1715
|
-
await
|
|
874
|
+
await runtimeMkdir(this.runtime, dirname(file), { recursive: true });
|
|
1716
875
|
}
|
|
1717
|
-
const tempFile = `${file}.tmp.${Date.now()}.${randomUUID()}`;
|
|
876
|
+
const tempFile = `${file}.tmp.${Date.now()}.${crypto.randomUUID()}`;
|
|
1718
877
|
const payload = { expiresAt: expiresAt ?? null, value };
|
|
1719
878
|
try {
|
|
1720
|
-
await writeFile(tempFile, JSON.stringify(payload)
|
|
1721
|
-
await
|
|
879
|
+
await this.runtime.writeFile(tempFile, JSON.stringify(payload));
|
|
880
|
+
await runtimeRename(this.runtime, tempFile, file);
|
|
1722
881
|
} catch (error) {
|
|
1723
|
-
await
|
|
1724
|
-
});
|
|
882
|
+
await runtimeRemoveRecursive(this.runtime, tempFile).catch(() => {});
|
|
1725
883
|
throw error;
|
|
1726
884
|
}
|
|
1727
885
|
}
|
|
1728
|
-
/**
|
|
1729
|
-
* Stores an item in the cache only if it does not already exist.
|
|
1730
|
-
*
|
|
1731
|
-
* @param key - The unique identifier for the cache item.
|
|
1732
|
-
* @param value - The data to be cached.
|
|
1733
|
-
* @param ttl - Time-to-live in seconds or a Date object.
|
|
1734
|
-
* @returns `true` if the item was stored, `false` if it already existed.
|
|
1735
|
-
*
|
|
1736
|
-
* @example
|
|
1737
|
-
* ```typescript
|
|
1738
|
-
* const success = await store.add('unique-task', { status: 'pending' }, 60);
|
|
1739
|
-
* ```
|
|
1740
|
-
*/
|
|
1741
886
|
async add(key, value, ttl) {
|
|
1742
887
|
const normalized = normalizeCacheKey(key);
|
|
1743
888
|
const existing = await this.get(normalized);
|
|
@@ -1747,61 +892,22 @@ var FileStore = class {
|
|
|
1747
892
|
await this.put(normalized, value, ttl);
|
|
1748
893
|
return true;
|
|
1749
894
|
}
|
|
1750
|
-
/**
|
|
1751
|
-
* Removes an item from the cache by its key.
|
|
1752
|
-
*
|
|
1753
|
-
* @param key - The unique identifier for the cache item.
|
|
1754
|
-
* @returns `true` if the file was deleted or didn't exist, `false` on failure.
|
|
1755
|
-
*
|
|
1756
|
-
* @example
|
|
1757
|
-
* ```typescript
|
|
1758
|
-
* await store.forget('my-key');
|
|
1759
|
-
* ```
|
|
1760
|
-
*/
|
|
1761
895
|
async forget(key) {
|
|
1762
896
|
const normalized = normalizeCacheKey(key);
|
|
1763
897
|
await this.ensureDir();
|
|
1764
|
-
const file = this.filePathForKey(normalized);
|
|
898
|
+
const file = await this.filePathForKey(normalized);
|
|
1765
899
|
try {
|
|
1766
|
-
await
|
|
900
|
+
await runtimeRemoveRecursive(this.runtime, file);
|
|
1767
901
|
return true;
|
|
1768
902
|
} catch {
|
|
1769
903
|
return false;
|
|
1770
904
|
}
|
|
1771
905
|
}
|
|
1772
|
-
/**
|
|
1773
|
-
* Removes all items from the cache directory.
|
|
1774
|
-
*
|
|
1775
|
-
* This operation deletes the entire cache directory and recreates it.
|
|
1776
|
-
* Use with caution as it is destructive and non-reversible.
|
|
1777
|
-
*
|
|
1778
|
-
* @throws {Error} If the directory cannot be removed or recreated.
|
|
1779
|
-
*
|
|
1780
|
-
* @example
|
|
1781
|
-
* ```typescript
|
|
1782
|
-
* await store.flush();
|
|
1783
|
-
* ```
|
|
1784
|
-
*/
|
|
1785
906
|
async flush() {
|
|
1786
907
|
await this.ensureDir();
|
|
1787
|
-
await
|
|
908
|
+
await runtimeRemoveRecursive(this.runtime, this.options.directory);
|
|
1788
909
|
await this.ensureDir();
|
|
1789
910
|
}
|
|
1790
|
-
/**
|
|
1791
|
-
* Increments the value of an integer item in the cache.
|
|
1792
|
-
*
|
|
1793
|
-
* If the key does not exist, it is initialized to 0 before incrementing.
|
|
1794
|
-
*
|
|
1795
|
-
* @param key - The unique identifier for the cache item.
|
|
1796
|
-
* @param value - The amount to increment by.
|
|
1797
|
-
* @returns The new value after incrementing.
|
|
1798
|
-
* @throws {Error} If the existing value is not a number.
|
|
1799
|
-
*
|
|
1800
|
-
* @example
|
|
1801
|
-
* ```typescript
|
|
1802
|
-
* const newCount = await store.increment('page-views');
|
|
1803
|
-
* ```
|
|
1804
|
-
*/
|
|
1805
911
|
async increment(key, value = 1) {
|
|
1806
912
|
const normalized = normalizeCacheKey(key);
|
|
1807
913
|
const current = await this.get(normalized);
|
|
@@ -1809,77 +915,30 @@ var FileStore = class {
|
|
|
1809
915
|
await this.put(normalized, next, null);
|
|
1810
916
|
return next;
|
|
1811
917
|
}
|
|
1812
|
-
/**
|
|
1813
|
-
* Decrements the value of an integer item in the cache.
|
|
1814
|
-
*
|
|
1815
|
-
* If the key does not exist, it is initialized to 0 before decrementing.
|
|
1816
|
-
*
|
|
1817
|
-
* @param key - The unique identifier for the cache item.
|
|
1818
|
-
* @param value - The amount to decrement by.
|
|
1819
|
-
* @returns The new value after decrementing.
|
|
1820
|
-
* @throws {Error} If the existing value is not a number.
|
|
1821
|
-
*
|
|
1822
|
-
* @example
|
|
1823
|
-
* ```typescript
|
|
1824
|
-
* const newCount = await store.decrement('stock-level');
|
|
1825
|
-
* ```
|
|
1826
|
-
*/
|
|
1827
918
|
async decrement(key, value = 1) {
|
|
1828
919
|
return this.increment(key, -value);
|
|
1829
920
|
}
|
|
1830
|
-
/**
|
|
1831
|
-
* Retrieves the remaining time-to-live for a cache item in seconds.
|
|
1832
|
-
*
|
|
1833
|
-
* @param key - The unique identifier for the cache item.
|
|
1834
|
-
* @returns The seconds remaining until expiration, or `null` if it never expires or doesn't exist.
|
|
1835
|
-
*
|
|
1836
|
-
* @example
|
|
1837
|
-
* ```typescript
|
|
1838
|
-
* const secondsLeft = await store.ttl('session:123');
|
|
1839
|
-
* ```
|
|
1840
|
-
*/
|
|
1841
921
|
async ttl(key) {
|
|
1842
922
|
const normalized = normalizeCacheKey(key);
|
|
1843
|
-
const file = this.filePathForKey(normalized);
|
|
923
|
+
const file = await this.filePathForKey(normalized);
|
|
1844
924
|
try {
|
|
1845
|
-
const raw = await
|
|
925
|
+
const raw = await runtimeReadText(this.runtime, file);
|
|
1846
926
|
const data = JSON.parse(raw);
|
|
1847
927
|
if (data.expiresAt === null) {
|
|
1848
928
|
return null;
|
|
1849
929
|
}
|
|
1850
|
-
const remaining = Math.ceil((data.expiresAt - Date.now()) /
|
|
930
|
+
const remaining = Math.ceil((data.expiresAt - Date.now()) / 1000);
|
|
1851
931
|
return remaining > 0 ? remaining : null;
|
|
1852
932
|
} catch {
|
|
1853
933
|
return null;
|
|
1854
934
|
}
|
|
1855
935
|
}
|
|
1856
|
-
/**
|
|
1857
|
-
* Creates a distributed lock instance based on the filesystem.
|
|
1858
|
-
*
|
|
1859
|
-
* Locks are implemented using atomic file creation (`wx` flag). They include
|
|
1860
|
-
* protection against stale locks by checking process IDs and expiration times.
|
|
1861
|
-
*
|
|
1862
|
-
* @param name - The unique name of the lock.
|
|
1863
|
-
* @param seconds - The duration in seconds for which the lock should be held.
|
|
1864
|
-
* @returns A `CacheLock` instance for managing the lock lifecycle.
|
|
1865
|
-
*
|
|
1866
|
-
* @example
|
|
1867
|
-
* ```typescript
|
|
1868
|
-
* const lock = store.lock('process-report', 60);
|
|
1869
|
-
* if (await lock.acquire()) {
|
|
1870
|
-
* try {
|
|
1871
|
-
* // Critical section
|
|
1872
|
-
* } finally {
|
|
1873
|
-
* await lock.release();
|
|
1874
|
-
* }
|
|
1875
|
-
* }
|
|
1876
|
-
* ```
|
|
1877
|
-
*/
|
|
1878
936
|
lock(name, seconds = 10) {
|
|
1879
937
|
const normalizedName = normalizeCacheKey(name);
|
|
1880
|
-
const lockFile = join(this.options.directory, `.lock-${
|
|
1881
|
-
const ttlMillis = Math.max(1, seconds) *
|
|
1882
|
-
const owner = randomUUID();
|
|
938
|
+
const lockFile = join(this.options.directory, `.lock-${syncHashKey(normalizedName)}`);
|
|
939
|
+
const ttlMillis = Math.max(1, seconds) * 1000;
|
|
940
|
+
const owner = crypto.randomUUID();
|
|
941
|
+
const runtime = this.runtime;
|
|
1883
942
|
const isProcessAlive = (pid) => {
|
|
1884
943
|
try {
|
|
1885
944
|
process.kill(pid, 0);
|
|
@@ -1891,62 +950,41 @@ var FileStore = class {
|
|
|
1891
950
|
const tryAcquire = async () => {
|
|
1892
951
|
await this.ensureDir();
|
|
1893
952
|
try {
|
|
1894
|
-
const handle = await open(lockFile, "wx");
|
|
1895
953
|
const lockData = {
|
|
1896
954
|
owner,
|
|
1897
955
|
expiresAt: Date.now() + ttlMillis,
|
|
1898
956
|
pid: process.pid
|
|
1899
957
|
};
|
|
1900
|
-
await
|
|
1901
|
-
await handle.close();
|
|
958
|
+
await runtimeWriteFileExclusive(runtime, lockFile, JSON.stringify(lockData));
|
|
1902
959
|
return true;
|
|
1903
960
|
} catch {
|
|
1904
961
|
try {
|
|
1905
|
-
const raw = await
|
|
962
|
+
const raw = await runtimeReadText(runtime, lockFile);
|
|
1906
963
|
const data = JSON.parse(raw);
|
|
1907
|
-
const
|
|
964
|
+
const isExpiredLock = !data.expiresAt || Date.now() > data.expiresAt;
|
|
1908
965
|
const isProcessDead = data.pid && !isProcessAlive(data.pid);
|
|
1909
|
-
if (
|
|
1910
|
-
await
|
|
966
|
+
if (isExpiredLock || isProcessDead) {
|
|
967
|
+
await runtimeRemoveRecursive(runtime, lockFile);
|
|
1911
968
|
}
|
|
1912
|
-
} catch {
|
|
1913
|
-
}
|
|
969
|
+
} catch {}
|
|
1914
970
|
return false;
|
|
1915
971
|
}
|
|
1916
972
|
};
|
|
1917
973
|
return {
|
|
1918
|
-
/**
|
|
1919
|
-
* Attempts to acquire the lock immediately.
|
|
1920
|
-
*
|
|
1921
|
-
* @returns `true` if the lock was successfully acquired, `false` otherwise.
|
|
1922
|
-
*/
|
|
1923
974
|
async acquire() {
|
|
1924
975
|
return tryAcquire();
|
|
1925
976
|
},
|
|
1926
|
-
/**
|
|
1927
|
-
* Releases the lock if it is owned by the current instance.
|
|
1928
|
-
*/
|
|
1929
977
|
async release() {
|
|
1930
978
|
try {
|
|
1931
|
-
const raw = await
|
|
979
|
+
const raw = await runtimeReadText(runtime, lockFile);
|
|
1932
980
|
const data = JSON.parse(raw);
|
|
1933
981
|
if (data.owner === owner) {
|
|
1934
|
-
await
|
|
982
|
+
await runtimeRemoveRecursive(runtime, lockFile);
|
|
1935
983
|
}
|
|
1936
|
-
} catch {
|
|
1937
|
-
}
|
|
984
|
+
} catch {}
|
|
1938
985
|
},
|
|
1939
|
-
/**
|
|
1940
|
-
* Executes a callback within the lock, waiting if necessary.
|
|
1941
|
-
*
|
|
1942
|
-
* @param secondsToWait - Maximum time to wait for the lock in seconds.
|
|
1943
|
-
* @param callback - The function to execute once the lock is acquired.
|
|
1944
|
-
* @param options - Polling configuration.
|
|
1945
|
-
* @returns The result of the callback.
|
|
1946
|
-
* @throws {LockTimeoutError} If the lock cannot be acquired within the wait time.
|
|
1947
|
-
*/
|
|
1948
986
|
async block(secondsToWait, callback, options) {
|
|
1949
|
-
const deadline = Date.now() + Math.max(0, secondsToWait) *
|
|
987
|
+
const deadline = Date.now() + Math.max(0, secondsToWait) * 1000;
|
|
1950
988
|
const sleepMillis = options?.sleepMillis ?? 150;
|
|
1951
989
|
while (Date.now() <= deadline) {
|
|
1952
990
|
if (await this.acquire()) {
|
|
@@ -1958,94 +996,52 @@ var FileStore = class {
|
|
|
1958
996
|
}
|
|
1959
997
|
await sleep(sleepMillis);
|
|
1960
998
|
}
|
|
1961
|
-
throw new LockTimeoutError(
|
|
1962
|
-
`Failed to acquire lock '${name}' within ${secondsToWait} seconds.`
|
|
1963
|
-
);
|
|
999
|
+
throw new LockTimeoutError(`Failed to acquire lock '${name}' within ${secondsToWait} seconds.`);
|
|
1964
1000
|
}
|
|
1965
1001
|
};
|
|
1966
1002
|
}
|
|
1967
|
-
}
|
|
1003
|
+
}
|
|
1968
1004
|
function hashKey(key) {
|
|
1969
|
-
return
|
|
1005
|
+
return NativeHasher.sha256(key);
|
|
1006
|
+
}
|
|
1007
|
+
function syncHashKey(key) {
|
|
1008
|
+
let hash = 5381;
|
|
1009
|
+
for (let i = 0;i < key.length; i++) {
|
|
1010
|
+
hash = (hash << 5) + hash ^ key.charCodeAt(i);
|
|
1011
|
+
hash = hash >>> 0;
|
|
1012
|
+
}
|
|
1013
|
+
return hash.toString(16).padStart(8, "0");
|
|
1970
1014
|
}
|
|
1971
|
-
|
|
1972
|
-
// src/stores/MemoryStore.ts
|
|
1973
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1974
1015
|
|
|
1975
1016
|
// src/utils/LRUCache.ts
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
*/
|
|
1017
|
+
class LRUCache {
|
|
1018
|
+
maxSize;
|
|
1019
|
+
onEvict;
|
|
1020
|
+
map = new Map;
|
|
1021
|
+
head = null;
|
|
1022
|
+
tail = null;
|
|
1983
1023
|
constructor(maxSize, onEvict) {
|
|
1984
1024
|
this.maxSize = maxSize;
|
|
1985
1025
|
this.onEvict = onEvict;
|
|
1986
1026
|
}
|
|
1987
|
-
map = /* @__PURE__ */ new Map();
|
|
1988
|
-
head = null;
|
|
1989
|
-
tail = null;
|
|
1990
|
-
/**
|
|
1991
|
-
* The current number of items stored in the cache.
|
|
1992
|
-
*/
|
|
1993
1027
|
get size() {
|
|
1994
1028
|
return this.map.size;
|
|
1995
1029
|
}
|
|
1996
|
-
/**
|
|
1997
|
-
* Checks if a key exists in the cache without updating its access order.
|
|
1998
|
-
*
|
|
1999
|
-
* @param key - The identifier to look for.
|
|
2000
|
-
* @returns True if the key exists, false otherwise.
|
|
2001
|
-
*/
|
|
2002
1030
|
has(key) {
|
|
2003
1031
|
return this.map.has(key);
|
|
2004
1032
|
}
|
|
2005
|
-
/**
|
|
2006
|
-
* Retrieves an item from the cache and marks it as most recently used.
|
|
2007
|
-
*
|
|
2008
|
-
* @param key - The identifier of the item to retrieve.
|
|
2009
|
-
* @returns The cached value, or undefined if not found.
|
|
2010
|
-
*
|
|
2011
|
-
* @example
|
|
2012
|
-
* ```typescript
|
|
2013
|
-
* const value = cache.get('my-key');
|
|
2014
|
-
* ```
|
|
2015
|
-
*/
|
|
2016
1033
|
get(key) {
|
|
2017
1034
|
const node = this.map.get(key);
|
|
2018
1035
|
if (!node) {
|
|
2019
|
-
return
|
|
1036
|
+
return;
|
|
2020
1037
|
}
|
|
2021
1038
|
this.moveToHead(node);
|
|
2022
1039
|
return node.value;
|
|
2023
1040
|
}
|
|
2024
|
-
/**
|
|
2025
|
-
* Retrieves an item from the cache without updating its access order.
|
|
2026
|
-
*
|
|
2027
|
-
* Useful for inspecting the cache without affecting eviction priority.
|
|
2028
|
-
*
|
|
2029
|
-
* @param key - The identifier of the item to peek.
|
|
2030
|
-
* @returns The cached value, or undefined if not found.
|
|
2031
|
-
*/
|
|
2032
1041
|
peek(key) {
|
|
2033
1042
|
const node = this.map.get(key);
|
|
2034
1043
|
return node?.value;
|
|
2035
1044
|
}
|
|
2036
|
-
/**
|
|
2037
|
-
* Adds or updates an item in the cache, marking it as most recently used.
|
|
2038
|
-
*
|
|
2039
|
-
* If the cache is at capacity, the least recently used item will be evicted.
|
|
2040
|
-
*
|
|
2041
|
-
* @param key - The identifier for the item.
|
|
2042
|
-
* @param value - The data to store.
|
|
2043
|
-
*
|
|
2044
|
-
* @example
|
|
2045
|
-
* ```typescript
|
|
2046
|
-
* cache.set('user:1', { name: 'Alice' });
|
|
2047
|
-
* ```
|
|
2048
|
-
*/
|
|
2049
1045
|
set(key, value) {
|
|
2050
1046
|
const existingNode = this.map.get(key);
|
|
2051
1047
|
if (existingNode) {
|
|
@@ -2071,12 +1067,6 @@ var LRUCache = class {
|
|
|
2071
1067
|
}
|
|
2072
1068
|
this.map.set(key, newNode);
|
|
2073
1069
|
}
|
|
2074
|
-
/**
|
|
2075
|
-
* Removes an item from the cache.
|
|
2076
|
-
*
|
|
2077
|
-
* @param key - The identifier of the item to remove.
|
|
2078
|
-
* @returns True if the item was found and removed, false otherwise.
|
|
2079
|
-
*/
|
|
2080
1070
|
delete(key) {
|
|
2081
1071
|
const node = this.map.get(key);
|
|
2082
1072
|
if (!node) {
|
|
@@ -2086,19 +1076,11 @@ var LRUCache = class {
|
|
|
2086
1076
|
this.map.delete(key);
|
|
2087
1077
|
return true;
|
|
2088
1078
|
}
|
|
2089
|
-
/**
|
|
2090
|
-
* Removes all items from the cache.
|
|
2091
|
-
*/
|
|
2092
1079
|
clear() {
|
|
2093
1080
|
this.map.clear();
|
|
2094
1081
|
this.head = null;
|
|
2095
1082
|
this.tail = null;
|
|
2096
1083
|
}
|
|
2097
|
-
/**
|
|
2098
|
-
* Moves a node to the head of the linked list (most recently used).
|
|
2099
|
-
*
|
|
2100
|
-
* @param node - The node to promote.
|
|
2101
|
-
*/
|
|
2102
1084
|
moveToHead(node) {
|
|
2103
1085
|
if (node === this.head) {
|
|
2104
1086
|
return;
|
|
@@ -2119,11 +1101,6 @@ var LRUCache = class {
|
|
|
2119
1101
|
}
|
|
2120
1102
|
this.head = node;
|
|
2121
1103
|
}
|
|
2122
|
-
/**
|
|
2123
|
-
* Removes a node from the linked list.
|
|
2124
|
-
*
|
|
2125
|
-
* @param node - The node to remove.
|
|
2126
|
-
*/
|
|
2127
1104
|
removeNode(node) {
|
|
2128
1105
|
if (node.prev) {
|
|
2129
1106
|
node.prev.next = node.next;
|
|
@@ -2138,11 +1115,6 @@ var LRUCache = class {
|
|
|
2138
1115
|
node.prev = null;
|
|
2139
1116
|
node.next = null;
|
|
2140
1117
|
}
|
|
2141
|
-
/**
|
|
2142
|
-
* Evicts the least recently used item (the tail of the list).
|
|
2143
|
-
*
|
|
2144
|
-
* Triggers the `onEvict` callback if provided.
|
|
2145
|
-
*/
|
|
2146
1118
|
evict() {
|
|
2147
1119
|
if (!this.tail) {
|
|
2148
1120
|
return;
|
|
@@ -2154,37 +1126,21 @@ var LRUCache = class {
|
|
|
2154
1126
|
this.removeNode(node);
|
|
2155
1127
|
this.map.delete(node.key);
|
|
2156
1128
|
}
|
|
2157
|
-
}
|
|
1129
|
+
}
|
|
2158
1130
|
|
|
2159
1131
|
// src/stores/MemoryStore.ts
|
|
2160
|
-
|
|
1132
|
+
class MemoryStore {
|
|
2161
1133
|
entries;
|
|
2162
|
-
locks =
|
|
1134
|
+
locks = new Map;
|
|
2163
1135
|
stats = { hits: 0, misses: 0, evictions: 0 };
|
|
2164
|
-
tagToKeys =
|
|
2165
|
-
keyToTags =
|
|
2166
|
-
/**
|
|
2167
|
-
* Creates a new MemoryStore instance.
|
|
2168
|
-
*
|
|
2169
|
-
* @param options - Configuration for capacity and eviction.
|
|
2170
|
-
*/
|
|
1136
|
+
tagToKeys = new Map;
|
|
1137
|
+
keyToTags = new Map;
|
|
2171
1138
|
constructor(options = {}) {
|
|
2172
1139
|
this.entries = new LRUCache(options.maxItems ?? 0, (key) => {
|
|
2173
1140
|
this.tagIndexRemove(key);
|
|
2174
1141
|
this.stats.evictions++;
|
|
2175
1142
|
});
|
|
2176
1143
|
}
|
|
2177
|
-
/**
|
|
2178
|
-
* Retrieves current performance metrics.
|
|
2179
|
-
*
|
|
2180
|
-
* @returns A snapshot of hits, misses, size, and eviction counts.
|
|
2181
|
-
*
|
|
2182
|
-
* @example
|
|
2183
|
-
* ```typescript
|
|
2184
|
-
* const stats = store.getStats();
|
|
2185
|
-
* console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
|
|
2186
|
-
* ```
|
|
2187
|
-
*/
|
|
2188
1144
|
getStats() {
|
|
2189
1145
|
const total = this.stats.hits + this.stats.misses;
|
|
2190
1146
|
return {
|
|
@@ -2201,22 +1157,9 @@ var MemoryStore = class {
|
|
|
2201
1157
|
return;
|
|
2202
1158
|
}
|
|
2203
1159
|
if (isExpired(entry.expiresAt, now)) {
|
|
2204
|
-
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
/**
|
|
2208
|
-
* Retrieves an item from the cache by its key.
|
|
2209
|
-
*
|
|
2210
|
-
* If the item is expired, it will be automatically removed and `null` will be returned.
|
|
2211
|
-
*
|
|
2212
|
-
* @param key - The unique identifier for the cached item.
|
|
2213
|
-
* @returns The cached value, or `null` if not found or expired.
|
|
2214
|
-
*
|
|
2215
|
-
* @example
|
|
2216
|
-
* ```typescript
|
|
2217
|
-
* const value = await store.get('my-key');
|
|
2218
|
-
* ```
|
|
2219
|
-
*/
|
|
1160
|
+
this.forget(key);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
2220
1163
|
async get(key) {
|
|
2221
1164
|
const normalized = normalizeCacheKey(key);
|
|
2222
1165
|
const entry = this.entries.get(normalized);
|
|
@@ -2232,42 +1175,15 @@ var MemoryStore = class {
|
|
|
2232
1175
|
this.stats.hits++;
|
|
2233
1176
|
return entry.value;
|
|
2234
1177
|
}
|
|
2235
|
-
/**
|
|
2236
|
-
* Stores an item in the cache with a specific TTL.
|
|
2237
|
-
*
|
|
2238
|
-
* If the key already exists, it will be overwritten.
|
|
2239
|
-
*
|
|
2240
|
-
* @param key - The unique identifier for the item.
|
|
2241
|
-
* @param value - The data to store.
|
|
2242
|
-
* @param ttl - Time-to-live in seconds, or a Date object for absolute expiration.
|
|
2243
|
-
*
|
|
2244
|
-
* @example
|
|
2245
|
-
* ```typescript
|
|
2246
|
-
* await store.put('settings', { theme: 'dark' }, 3600);
|
|
2247
|
-
* ```
|
|
2248
|
-
*/
|
|
2249
1178
|
async put(key, value, ttl) {
|
|
2250
1179
|
const normalized = normalizeCacheKey(key);
|
|
2251
1180
|
const expiresAt = ttlToExpiresAt(ttl);
|
|
2252
|
-
if (expiresAt !== null && expiresAt !==
|
|
1181
|
+
if (expiresAt !== null && expiresAt !== undefined && expiresAt <= Date.now()) {
|
|
2253
1182
|
await this.forget(normalized);
|
|
2254
1183
|
return;
|
|
2255
1184
|
}
|
|
2256
1185
|
this.entries.set(normalized, { value, expiresAt: expiresAt ?? null });
|
|
2257
1186
|
}
|
|
2258
|
-
/**
|
|
2259
|
-
* Stores an item only if it does not already exist in the cache.
|
|
2260
|
-
*
|
|
2261
|
-
* @param key - The unique identifier for the item.
|
|
2262
|
-
* @param value - The data to store.
|
|
2263
|
-
* @param ttl - Time-to-live in seconds or absolute expiration.
|
|
2264
|
-
* @returns `true` if the item was added, `false` if it already existed.
|
|
2265
|
-
*
|
|
2266
|
-
* @example
|
|
2267
|
-
* ```typescript
|
|
2268
|
-
* const added = await store.add('unique-task', data, 60);
|
|
2269
|
-
* ```
|
|
2270
|
-
*/
|
|
2271
1187
|
async add(key, value, ttl) {
|
|
2272
1188
|
const normalized = normalizeCacheKey(key);
|
|
2273
1189
|
this.cleanupExpired(normalized);
|
|
@@ -2277,50 +1193,17 @@ var MemoryStore = class {
|
|
|
2277
1193
|
await this.put(normalized, value, ttl);
|
|
2278
1194
|
return true;
|
|
2279
1195
|
}
|
|
2280
|
-
/**
|
|
2281
|
-
* Removes an item from the cache.
|
|
2282
|
-
*
|
|
2283
|
-
* @param key - The unique identifier for the item to remove.
|
|
2284
|
-
* @returns `true` if the item existed and was removed, `false` otherwise.
|
|
2285
|
-
*
|
|
2286
|
-
* @example
|
|
2287
|
-
* ```typescript
|
|
2288
|
-
* await store.forget('user:session');
|
|
2289
|
-
* ```
|
|
2290
|
-
*/
|
|
2291
1196
|
async forget(key) {
|
|
2292
1197
|
const normalized = normalizeCacheKey(key);
|
|
2293
1198
|
const existed = this.entries.delete(normalized);
|
|
2294
1199
|
this.tagIndexRemove(normalized);
|
|
2295
1200
|
return existed;
|
|
2296
1201
|
}
|
|
2297
|
-
/**
|
|
2298
|
-
* Removes all items from the cache and resets all internal indexes.
|
|
2299
|
-
*
|
|
2300
|
-
* @example
|
|
2301
|
-
* ```typescript
|
|
2302
|
-
* await store.flush();
|
|
2303
|
-
* ```
|
|
2304
|
-
*/
|
|
2305
1202
|
async flush() {
|
|
2306
1203
|
this.entries.clear();
|
|
2307
1204
|
this.tagToKeys.clear();
|
|
2308
1205
|
this.keyToTags.clear();
|
|
2309
1206
|
}
|
|
2310
|
-
/**
|
|
2311
|
-
* Increments the value of an item in the cache.
|
|
2312
|
-
*
|
|
2313
|
-
* If the key does not exist, it starts from 0.
|
|
2314
|
-
*
|
|
2315
|
-
* @param key - The identifier for the numeric value.
|
|
2316
|
-
* @param value - The amount to increment by (defaults to 1).
|
|
2317
|
-
* @returns The new incremented value.
|
|
2318
|
-
*
|
|
2319
|
-
* @example
|
|
2320
|
-
* ```typescript
|
|
2321
|
-
* const count = await store.increment('page_views');
|
|
2322
|
-
* ```
|
|
2323
|
-
*/
|
|
2324
1207
|
async increment(key, value = 1) {
|
|
2325
1208
|
const normalized = normalizeCacheKey(key);
|
|
2326
1209
|
const current = await this.get(normalized);
|
|
@@ -2328,32 +1211,9 @@ var MemoryStore = class {
|
|
|
2328
1211
|
await this.put(normalized, next, null);
|
|
2329
1212
|
return next;
|
|
2330
1213
|
}
|
|
2331
|
-
/**
|
|
2332
|
-
* Decrements the value of an item in the cache.
|
|
2333
|
-
*
|
|
2334
|
-
* @param key - The identifier for the numeric value.
|
|
2335
|
-
* @param value - The amount to decrement by (defaults to 1).
|
|
2336
|
-
* @returns The new decremented value.
|
|
2337
|
-
*
|
|
2338
|
-
* @example
|
|
2339
|
-
* ```typescript
|
|
2340
|
-
* const remaining = await store.decrement('stock_count', 5);
|
|
2341
|
-
* ```
|
|
2342
|
-
*/
|
|
2343
1214
|
async decrement(key, value = 1) {
|
|
2344
1215
|
return this.increment(key, -value);
|
|
2345
1216
|
}
|
|
2346
|
-
/**
|
|
2347
|
-
* Gets the remaining time-to-live for a cached item.
|
|
2348
|
-
*
|
|
2349
|
-
* @param key - The identifier for the cached item.
|
|
2350
|
-
* @returns Remaining seconds, or `null` if the item has no expiration or does not exist.
|
|
2351
|
-
*
|
|
2352
|
-
* @example
|
|
2353
|
-
* ```typescript
|
|
2354
|
-
* const secondsLeft = await store.ttl('token');
|
|
2355
|
-
* ```
|
|
2356
|
-
*/
|
|
2357
1217
|
async ttl(key) {
|
|
2358
1218
|
const normalized = normalizeCacheKey(key);
|
|
2359
1219
|
const entry = this.entries.peek(normalized);
|
|
@@ -2365,30 +1225,11 @@ var MemoryStore = class {
|
|
|
2365
1225
|
await this.forget(normalized);
|
|
2366
1226
|
return null;
|
|
2367
1227
|
}
|
|
2368
|
-
return Math.max(0, Math.ceil((entry.expiresAt - now) /
|
|
2369
|
-
}
|
|
2370
|
-
/**
|
|
2371
|
-
* Creates a lock instance for managing exclusive access to a resource.
|
|
2372
|
-
*
|
|
2373
|
-
* @param name - The name of the lock.
|
|
2374
|
-
* @param seconds - The duration the lock should be held (defaults to 10).
|
|
2375
|
-
* @returns A `CacheLock` instance.
|
|
2376
|
-
*
|
|
2377
|
-
* @example
|
|
2378
|
-
* ```typescript
|
|
2379
|
-
* const lock = store.lock('process-report', 30);
|
|
2380
|
-
* if (await lock.acquire()) {
|
|
2381
|
-
* try {
|
|
2382
|
-
* // Critical section
|
|
2383
|
-
* } finally {
|
|
2384
|
-
* await lock.release();
|
|
2385
|
-
* }
|
|
2386
|
-
* }
|
|
2387
|
-
* ```
|
|
2388
|
-
*/
|
|
1228
|
+
return Math.max(0, Math.ceil((entry.expiresAt - now) / 1000));
|
|
1229
|
+
}
|
|
2389
1230
|
lock(name, seconds = 10) {
|
|
2390
1231
|
const lockKey = `lock:${normalizeCacheKey(name)}`;
|
|
2391
|
-
const ttlMillis = Math.max(1, seconds) *
|
|
1232
|
+
const ttlMillis = Math.max(1, seconds) * 1000;
|
|
2392
1233
|
const locks = this.locks;
|
|
2393
1234
|
const acquire = async () => {
|
|
2394
1235
|
const now = Date.now();
|
|
@@ -2396,17 +1237,12 @@ var MemoryStore = class {
|
|
|
2396
1237
|
if (existing && existing.expiresAt > now) {
|
|
2397
1238
|
return { ok: false };
|
|
2398
1239
|
}
|
|
2399
|
-
const owner2 =
|
|
1240
|
+
const owner2 = crypto.randomUUID();
|
|
2400
1241
|
locks.set(lockKey, { owner: owner2, expiresAt: now + ttlMillis });
|
|
2401
1242
|
return { ok: true, owner: owner2 };
|
|
2402
1243
|
};
|
|
2403
1244
|
let owner;
|
|
2404
1245
|
return {
|
|
2405
|
-
/**
|
|
2406
|
-
* Attempts to acquire the lock.
|
|
2407
|
-
*
|
|
2408
|
-
* @returns `true` if acquired, `false` if already held by another process.
|
|
2409
|
-
*/
|
|
2410
1246
|
async acquire() {
|
|
2411
1247
|
const result = await acquire();
|
|
2412
1248
|
if (!result.ok) {
|
|
@@ -2415,9 +1251,6 @@ var MemoryStore = class {
|
|
|
2415
1251
|
owner = result.owner;
|
|
2416
1252
|
return true;
|
|
2417
1253
|
},
|
|
2418
|
-
/**
|
|
2419
|
-
* Releases the lock if it is held by the current owner.
|
|
2420
|
-
*/
|
|
2421
1254
|
async release() {
|
|
2422
1255
|
if (!owner) {
|
|
2423
1256
|
return;
|
|
@@ -2426,26 +1259,10 @@ var MemoryStore = class {
|
|
|
2426
1259
|
if (existing?.owner === owner) {
|
|
2427
1260
|
locks.delete(lockKey);
|
|
2428
1261
|
}
|
|
2429
|
-
owner =
|
|
1262
|
+
owner = undefined;
|
|
2430
1263
|
},
|
|
2431
|
-
/**
|
|
2432
|
-
* Attempts to acquire the lock and execute a callback, waiting if necessary.
|
|
2433
|
-
*
|
|
2434
|
-
* @param secondsToWait - How long to wait for the lock before timing out.
|
|
2435
|
-
* @param callback - The logic to execute while holding the lock.
|
|
2436
|
-
* @param options - Polling configuration.
|
|
2437
|
-
* @returns The result of the callback.
|
|
2438
|
-
* @throws {LockTimeoutError} If the lock cannot be acquired within the wait time.
|
|
2439
|
-
*
|
|
2440
|
-
* @example
|
|
2441
|
-
* ```typescript
|
|
2442
|
-
* await lock.block(5, async () => {
|
|
2443
|
-
* // This code runs exclusively
|
|
2444
|
-
* });
|
|
2445
|
-
* ```
|
|
2446
|
-
*/
|
|
2447
1264
|
async block(secondsToWait, callback, options) {
|
|
2448
|
-
const deadline = Date.now() + Math.max(0, secondsToWait) *
|
|
1265
|
+
const deadline = Date.now() + Math.max(0, secondsToWait) * 1000;
|
|
2449
1266
|
const sleepMillis = options?.sleepMillis ?? 150;
|
|
2450
1267
|
while (Date.now() <= deadline) {
|
|
2451
1268
|
if (await this.acquire()) {
|
|
@@ -2457,21 +1274,10 @@ var MemoryStore = class {
|
|
|
2457
1274
|
}
|
|
2458
1275
|
await sleep(sleepMillis);
|
|
2459
1276
|
}
|
|
2460
|
-
throw new LockTimeoutError(
|
|
2461
|
-
`Failed to acquire lock '${name}' within ${secondsToWait} seconds.`
|
|
2462
|
-
);
|
|
1277
|
+
throw new LockTimeoutError(`Failed to acquire lock '${name}' within ${secondsToWait} seconds.`);
|
|
2463
1278
|
}
|
|
2464
1279
|
};
|
|
2465
1280
|
}
|
|
2466
|
-
/**
|
|
2467
|
-
* Generates a tagged key for storage.
|
|
2468
|
-
*
|
|
2469
|
-
* Used internally to prefix keys with their associated tags.
|
|
2470
|
-
*
|
|
2471
|
-
* @param key - The original cache key.
|
|
2472
|
-
* @param tags - List of tags to associate with the key.
|
|
2473
|
-
* @returns A formatted string containing tags and the key.
|
|
2474
|
-
*/
|
|
2475
1281
|
tagKey(key, tags) {
|
|
2476
1282
|
const normalizedKey = normalizeCacheKey(key);
|
|
2477
1283
|
const normalizedTags = [...tags].map(String).filter(Boolean).sort();
|
|
@@ -2480,12 +1286,6 @@ var MemoryStore = class {
|
|
|
2480
1286
|
}
|
|
2481
1287
|
return `tags:${normalizedTags.join("|")}:${normalizedKey}`;
|
|
2482
1288
|
}
|
|
2483
|
-
/**
|
|
2484
|
-
* Indexes a tagged key for bulk invalidation.
|
|
2485
|
-
*
|
|
2486
|
-
* @param tags - The tags to index.
|
|
2487
|
-
* @param taggedKey - The full key (including tag prefix) to store.
|
|
2488
|
-
*/
|
|
2489
1289
|
tagIndexAdd(tags, taggedKey) {
|
|
2490
1290
|
const normalizedTags = [...tags].map(String).filter(Boolean);
|
|
2491
1291
|
if (normalizedTags.length === 0) {
|
|
@@ -2494,25 +1294,20 @@ var MemoryStore = class {
|
|
|
2494
1294
|
for (const tag of normalizedTags) {
|
|
2495
1295
|
let keys = this.tagToKeys.get(tag);
|
|
2496
1296
|
if (!keys) {
|
|
2497
|
-
keys =
|
|
1297
|
+
keys = new Set;
|
|
2498
1298
|
this.tagToKeys.set(tag, keys);
|
|
2499
1299
|
}
|
|
2500
1300
|
keys.add(taggedKey);
|
|
2501
1301
|
}
|
|
2502
1302
|
let tagSet = this.keyToTags.get(taggedKey);
|
|
2503
1303
|
if (!tagSet) {
|
|
2504
|
-
tagSet =
|
|
1304
|
+
tagSet = new Set;
|
|
2505
1305
|
this.keyToTags.set(taggedKey, tagSet);
|
|
2506
1306
|
}
|
|
2507
1307
|
for (const tag of normalizedTags) {
|
|
2508
1308
|
tagSet.add(tag);
|
|
2509
1309
|
}
|
|
2510
1310
|
}
|
|
2511
|
-
/**
|
|
2512
|
-
* Removes a key from the tag indexes.
|
|
2513
|
-
*
|
|
2514
|
-
* @param taggedKey - The key to remove from all tag sets.
|
|
2515
|
-
*/
|
|
2516
1311
|
tagIndexRemove(taggedKey) {
|
|
2517
1312
|
const tags = this.keyToTags.get(taggedKey);
|
|
2518
1313
|
if (!tags) {
|
|
@@ -2530,22 +1325,12 @@ var MemoryStore = class {
|
|
|
2530
1325
|
}
|
|
2531
1326
|
this.keyToTags.delete(taggedKey);
|
|
2532
1327
|
}
|
|
2533
|
-
/**
|
|
2534
|
-
* Invalidates all cache entries associated with any of the given tags.
|
|
2535
|
-
*
|
|
2536
|
-
* @param tags - The tags to flush.
|
|
2537
|
-
*
|
|
2538
|
-
* @example
|
|
2539
|
-
* ```typescript
|
|
2540
|
-
* await store.flushTags(['users', 'profiles']);
|
|
2541
|
-
* ```
|
|
2542
|
-
*/
|
|
2543
1328
|
async flushTags(tags) {
|
|
2544
1329
|
const normalizedTags = [...tags].map(String).filter(Boolean);
|
|
2545
1330
|
if (normalizedTags.length === 0) {
|
|
2546
1331
|
return;
|
|
2547
1332
|
}
|
|
2548
|
-
const keysToDelete =
|
|
1333
|
+
const keysToDelete = new Set;
|
|
2549
1334
|
for (const tag of normalizedTags) {
|
|
2550
1335
|
const keys = this.tagToKeys.get(tag);
|
|
2551
1336
|
if (!keys) {
|
|
@@ -2559,131 +1344,42 @@ var MemoryStore = class {
|
|
|
2559
1344
|
await this.forget(key);
|
|
2560
1345
|
}
|
|
2561
1346
|
}
|
|
2562
|
-
}
|
|
1347
|
+
}
|
|
2563
1348
|
|
|
2564
1349
|
// src/stores/NullStore.ts
|
|
2565
|
-
|
|
2566
|
-
/**
|
|
2567
|
-
* Simulates a cache miss for any given key.
|
|
2568
|
-
*
|
|
2569
|
-
* @param _key - Identifier for the cached item.
|
|
2570
|
-
* @returns Always `null` regardless of requested key.
|
|
2571
|
-
*
|
|
2572
|
-
* @example
|
|
2573
|
-
* ```typescript
|
|
2574
|
-
* const value = await store.get('my-key');
|
|
2575
|
-
* ```
|
|
2576
|
-
*/
|
|
1350
|
+
class NullStore {
|
|
2577
1351
|
async get(_key) {
|
|
2578
1352
|
return null;
|
|
2579
1353
|
}
|
|
2580
|
-
|
|
2581
|
-
* Discards the provided value instead of storing it.
|
|
2582
|
-
*
|
|
2583
|
-
* @param _key - The identifier for the item.
|
|
2584
|
-
* @param _value - The data to be cached.
|
|
2585
|
-
* @param _ttl - Time-to-live in seconds.
|
|
2586
|
-
* @returns Resolves immediately after discarding the data.
|
|
2587
|
-
*
|
|
2588
|
-
* @example
|
|
2589
|
-
* ```typescript
|
|
2590
|
-
* await store.put('user:1', { id: 1 }, 3600);
|
|
2591
|
-
* ```
|
|
2592
|
-
*/
|
|
2593
|
-
async put(_key, _value, _ttl) {
|
|
2594
|
-
}
|
|
2595
|
-
/**
|
|
2596
|
-
* Simulates a failed attempt to add an item to the cache.
|
|
2597
|
-
*
|
|
2598
|
-
* Since NullStore does not store data, this method always indicates that
|
|
2599
|
-
* the item was not added.
|
|
2600
|
-
*
|
|
2601
|
-
* @param _key - The identifier for the item.
|
|
2602
|
-
* @param _value - The data to be cached.
|
|
2603
|
-
* @param _ttl - Time-to-live in seconds.
|
|
2604
|
-
* @returns Always returns `false`.
|
|
2605
|
-
*
|
|
2606
|
-
* @example
|
|
2607
|
-
* ```typescript
|
|
2608
|
-
* const added = await store.add('key', 'value', 60); // false
|
|
2609
|
-
* ```
|
|
2610
|
-
*/
|
|
1354
|
+
async put(_key, _value, _ttl) {}
|
|
2611
1355
|
async add(_key, _value, _ttl) {
|
|
2612
1356
|
return false;
|
|
2613
1357
|
}
|
|
2614
|
-
/**
|
|
2615
|
-
* Simulates a failed attempt to remove an item from the cache.
|
|
2616
|
-
*
|
|
2617
|
-
* Since no data is ever stored, there is nothing to remove.
|
|
2618
|
-
*
|
|
2619
|
-
* @param _key - The identifier for the item to remove.
|
|
2620
|
-
* @returns Always returns `false`.
|
|
2621
|
-
*
|
|
2622
|
-
* @example
|
|
2623
|
-
* ```typescript
|
|
2624
|
-
* const forgotten = await store.forget('key'); // false
|
|
2625
|
-
* ```
|
|
2626
|
-
*/
|
|
2627
1358
|
async forget(_key) {
|
|
2628
1359
|
return false;
|
|
2629
1360
|
}
|
|
2630
|
-
|
|
2631
|
-
* Performs a no-op flush operation.
|
|
2632
|
-
*
|
|
2633
|
-
* @returns Resolves immediately as there is no data to clear.
|
|
2634
|
-
*
|
|
2635
|
-
* @example
|
|
2636
|
-
* ```typescript
|
|
2637
|
-
* await store.flush();
|
|
2638
|
-
* ```
|
|
2639
|
-
*/
|
|
2640
|
-
async flush() {
|
|
2641
|
-
}
|
|
2642
|
-
/**
|
|
2643
|
-
* Simulates an increment operation on a non-existent key.
|
|
2644
|
-
*
|
|
2645
|
-
* @param _key - The identifier for the numeric item.
|
|
2646
|
-
* @param _value - The amount to increment by.
|
|
2647
|
-
* @returns Always returns `0`.
|
|
2648
|
-
*
|
|
2649
|
-
* @example
|
|
2650
|
-
* ```typescript
|
|
2651
|
-
* const newValue = await store.increment('counter', 1); // 0
|
|
2652
|
-
* ```
|
|
2653
|
-
*/
|
|
1361
|
+
async flush() {}
|
|
2654
1362
|
async increment(_key, _value = 1) {
|
|
2655
1363
|
return 0;
|
|
2656
1364
|
}
|
|
2657
|
-
/**
|
|
2658
|
-
* Simulates a decrement operation on a non-existent key.
|
|
2659
|
-
*
|
|
2660
|
-
* @param _key - The identifier for the numeric item.
|
|
2661
|
-
* @param _value - The amount to decrement by.
|
|
2662
|
-
* @returns Always returns `0`.
|
|
2663
|
-
*
|
|
2664
|
-
* @example
|
|
2665
|
-
* ```typescript
|
|
2666
|
-
* const newValue = await store.decrement('counter', 1); // 0
|
|
2667
|
-
* ```
|
|
2668
|
-
*/
|
|
2669
1365
|
async decrement(_key, _value = 1) {
|
|
2670
1366
|
return 0;
|
|
2671
1367
|
}
|
|
2672
|
-
}
|
|
1368
|
+
}
|
|
2673
1369
|
|
|
2674
1370
|
// src/stores/PredictiveStore.ts
|
|
2675
|
-
|
|
1371
|
+
class PredictiveStore {
|
|
1372
|
+
store;
|
|
1373
|
+
predictor;
|
|
2676
1374
|
constructor(store, options = {}) {
|
|
2677
1375
|
this.store = store;
|
|
2678
|
-
this.predictor = options.predictor ?? new MarkovPredictor
|
|
1376
|
+
this.predictor = options.predictor ?? new MarkovPredictor;
|
|
2679
1377
|
}
|
|
2680
|
-
predictor;
|
|
2681
1378
|
async get(key) {
|
|
2682
1379
|
this.predictor.record(key);
|
|
2683
1380
|
const candidates = this.predictor.predict(key);
|
|
2684
1381
|
if (candidates.length > 0) {
|
|
2685
|
-
|
|
2686
|
-
})));
|
|
1382
|
+
Promise.all(candidates.map((k) => this.store.get(k).catch(() => {})));
|
|
2687
1383
|
}
|
|
2688
1384
|
return this.store.get(key);
|
|
2689
1385
|
}
|
|
@@ -2709,41 +1405,23 @@ var PredictiveStore = class {
|
|
|
2709
1405
|
return this.store.decrement(key, value);
|
|
2710
1406
|
}
|
|
2711
1407
|
lock(name, seconds) {
|
|
2712
|
-
return this.store.lock ? this.store.lock(name, seconds) :
|
|
1408
|
+
return this.store.lock ? this.store.lock(name, seconds) : undefined;
|
|
2713
1409
|
}
|
|
2714
1410
|
async ttl(key) {
|
|
2715
1411
|
return this.store.ttl ? this.store.ttl(key) : null;
|
|
2716
1412
|
}
|
|
2717
|
-
}
|
|
1413
|
+
}
|
|
2718
1414
|
|
|
2719
1415
|
// src/stores/RedisStore.ts
|
|
2720
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
2721
1416
|
import { Redis } from "@gravito/plasma";
|
|
2722
|
-
|
|
1417
|
+
class RedisStore {
|
|
2723
1418
|
connectionName;
|
|
2724
|
-
/**
|
|
2725
|
-
* Initialize a new RedisStore instance.
|
|
2726
|
-
*
|
|
2727
|
-
* @param options - Redis connection and prefix settings.
|
|
2728
|
-
*
|
|
2729
|
-
* @example
|
|
2730
|
-
* ```typescript
|
|
2731
|
-
* const store = new RedisStore({ prefix: 'app:' });
|
|
2732
|
-
* ```
|
|
2733
|
-
*/
|
|
2734
1419
|
constructor(options = {}) {
|
|
2735
1420
|
this.connectionName = options.connection;
|
|
2736
1421
|
}
|
|
2737
1422
|
get client() {
|
|
2738
1423
|
return Redis.connection(this.connectionName);
|
|
2739
1424
|
}
|
|
2740
|
-
/**
|
|
2741
|
-
* Retrieve an item from Redis.
|
|
2742
|
-
*
|
|
2743
|
-
* @param key - Unique cache key identifier.
|
|
2744
|
-
* @returns Parsed JSON value or null if missing/expired.
|
|
2745
|
-
* @throws {Error} If Redis connection fails or read errors occur.
|
|
2746
|
-
*/
|
|
2747
1425
|
async get(key) {
|
|
2748
1426
|
const normalized = normalizeCacheKey(key);
|
|
2749
1427
|
const value = await this.client.get(normalized);
|
|
@@ -2756,14 +1434,6 @@ var RedisStore = class {
|
|
|
2756
1434
|
return value;
|
|
2757
1435
|
}
|
|
2758
1436
|
}
|
|
2759
|
-
/**
|
|
2760
|
-
* Store an item in Redis.
|
|
2761
|
-
*
|
|
2762
|
-
* @param key - Unique cache key identifier.
|
|
2763
|
-
* @param value - Value to serialize and store.
|
|
2764
|
-
* @param ttl - Expiration duration.
|
|
2765
|
-
* @throws {Error} If Redis connection fails or write errors occur.
|
|
2766
|
-
*/
|
|
2767
1437
|
async put(key, value, ttl) {
|
|
2768
1438
|
const normalized = normalizeCacheKey(key);
|
|
2769
1439
|
const serialized = JSON.stringify(value);
|
|
@@ -2814,14 +1484,6 @@ var RedisStore = class {
|
|
|
2814
1484
|
}
|
|
2815
1485
|
return await this.client.incrby(normalized, value);
|
|
2816
1486
|
}
|
|
2817
|
-
/**
|
|
2818
|
-
* Decrement a numeric value in Redis.
|
|
2819
|
-
*
|
|
2820
|
-
* @param key - Unique cache key identifier.
|
|
2821
|
-
* @param value - Amount to subtract.
|
|
2822
|
-
* @returns Updated numeric value.
|
|
2823
|
-
* @throws {Error} If key is not numeric or Redis errors occur.
|
|
2824
|
-
*/
|
|
2825
1487
|
async decrement(key, value = 1) {
|
|
2826
1488
|
const normalized = normalizeCacheKey(key);
|
|
2827
1489
|
if (value === 1) {
|
|
@@ -2829,9 +1491,6 @@ var RedisStore = class {
|
|
|
2829
1491
|
}
|
|
2830
1492
|
return await this.client.decrby(normalized, value);
|
|
2831
1493
|
}
|
|
2832
|
-
// ============================================================================
|
|
2833
|
-
// Tags
|
|
2834
|
-
// ============================================================================
|
|
2835
1494
|
tagKey(key, _tags) {
|
|
2836
1495
|
return key;
|
|
2837
1496
|
}
|
|
@@ -2870,7 +1529,7 @@ var RedisStore = class {
|
|
|
2870
1529
|
pipeline.smembers(tagKey);
|
|
2871
1530
|
}
|
|
2872
1531
|
const results = await pipeline.exec();
|
|
2873
|
-
const keysToDelete =
|
|
1532
|
+
const keysToDelete = new Set;
|
|
2874
1533
|
for (const [err, keys] of results) {
|
|
2875
1534
|
if (!err && Array.isArray(keys)) {
|
|
2876
1535
|
for (const k of keys) {
|
|
@@ -2883,9 +1542,6 @@ var RedisStore = class {
|
|
|
2883
1542
|
}
|
|
2884
1543
|
await this.client.del(...tagKeys);
|
|
2885
1544
|
}
|
|
2886
|
-
// ============================================================================
|
|
2887
|
-
// Locks
|
|
2888
|
-
// ============================================================================
|
|
2889
1545
|
async ttl(key) {
|
|
2890
1546
|
const normalized = normalizeCacheKey(key);
|
|
2891
1547
|
const result = await this.client.ttl(normalized);
|
|
@@ -2893,8 +1549,8 @@ var RedisStore = class {
|
|
|
2893
1549
|
}
|
|
2894
1550
|
lock(name, seconds = 10) {
|
|
2895
1551
|
const lockKey = `lock:${normalizeCacheKey(name)}`;
|
|
2896
|
-
const owner =
|
|
2897
|
-
const ttlMs = Math.max(1, seconds) *
|
|
1552
|
+
const owner = crypto.randomUUID();
|
|
1553
|
+
const ttlMs = Math.max(1, seconds) * 1000;
|
|
2898
1554
|
const client = this.client;
|
|
2899
1555
|
return {
|
|
2900
1556
|
async acquire() {
|
|
@@ -2923,13 +1579,7 @@ var RedisStore = class {
|
|
|
2923
1579
|
end
|
|
2924
1580
|
`;
|
|
2925
1581
|
const evalClient = client;
|
|
2926
|
-
const result = await evalClient.eval(
|
|
2927
|
-
luaScript,
|
|
2928
|
-
1,
|
|
2929
|
-
lockKey,
|
|
2930
|
-
owner,
|
|
2931
|
-
extensionSeconds.toString()
|
|
2932
|
-
);
|
|
1582
|
+
const result = await evalClient.eval(luaScript, 1, lockKey, owner, extensionSeconds.toString());
|
|
2933
1583
|
return result === 1;
|
|
2934
1584
|
},
|
|
2935
1585
|
async getRemainingTime() {
|
|
@@ -2939,7 +1589,7 @@ var RedisStore = class {
|
|
|
2939
1589
|
const retryInterval = options?.retryInterval ?? options?.sleepMillis ?? 100;
|
|
2940
1590
|
const maxRetries = options?.maxRetries ?? Number.POSITIVE_INFINITY;
|
|
2941
1591
|
const signal = options?.signal;
|
|
2942
|
-
const deadline = Date.now() + Math.max(0, secondsToWait) *
|
|
1592
|
+
const deadline = Date.now() + Math.max(0, secondsToWait) * 1000;
|
|
2943
1593
|
let attempt = 0;
|
|
2944
1594
|
while (Date.now() <= deadline && attempt < maxRetries) {
|
|
2945
1595
|
if (signal?.aborted) {
|
|
@@ -2953,25 +1603,19 @@ var RedisStore = class {
|
|
|
2953
1603
|
}
|
|
2954
1604
|
}
|
|
2955
1605
|
attempt++;
|
|
2956
|
-
const delay = Math.min(retryInterval * 1.5 ** Math.min(attempt, 10),
|
|
1606
|
+
const delay = Math.min(retryInterval * 1.5 ** Math.min(attempt, 10), 1000);
|
|
2957
1607
|
await sleep(delay);
|
|
2958
1608
|
}
|
|
2959
|
-
throw new LockTimeoutError(
|
|
2960
|
-
`Failed to acquire lock '${name}' within ${secondsToWait} seconds.`
|
|
2961
|
-
);
|
|
1609
|
+
throw new LockTimeoutError(`Failed to acquire lock '${name}' within ${secondsToWait} seconds.`);
|
|
2962
1610
|
}
|
|
2963
1611
|
};
|
|
2964
1612
|
}
|
|
2965
|
-
}
|
|
1613
|
+
}
|
|
2966
1614
|
|
|
2967
1615
|
// src/stores/TieredStore.ts
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
*
|
|
2972
|
-
* @param local - The L1 cache store (usually MemoryStore).
|
|
2973
|
-
* @param remote - The L2 cache store (usually RedisStore or FileStore).
|
|
2974
|
-
*/
|
|
1616
|
+
class TieredStore {
|
|
1617
|
+
local;
|
|
1618
|
+
remote;
|
|
2975
1619
|
constructor(local, remote) {
|
|
2976
1620
|
this.local = local;
|
|
2977
1621
|
this.remote = remote;
|
|
@@ -3020,11 +1664,11 @@ var TieredStore = class {
|
|
|
3020
1664
|
}
|
|
3021
1665
|
return this.local.ttl ? this.local.ttl(key) : null;
|
|
3022
1666
|
}
|
|
3023
|
-
}
|
|
1667
|
+
}
|
|
3024
1668
|
|
|
3025
1669
|
// src/index.ts
|
|
3026
|
-
|
|
3027
|
-
store = new MemoryStore
|
|
1670
|
+
class MemoryCacheProvider {
|
|
1671
|
+
store = new MemoryStore;
|
|
3028
1672
|
async get(key) {
|
|
3029
1673
|
return this.store.get(key);
|
|
3030
1674
|
}
|
|
@@ -3037,7 +1681,7 @@ var MemoryCacheProvider = class {
|
|
|
3037
1681
|
async clear() {
|
|
3038
1682
|
await this.store.flush();
|
|
3039
1683
|
}
|
|
3040
|
-
}
|
|
1684
|
+
}
|
|
3041
1685
|
function resolveStoreConfig(core, options) {
|
|
3042
1686
|
if (options) {
|
|
3043
1687
|
return options;
|
|
@@ -3055,15 +1699,15 @@ function createStoreFactory(config) {
|
|
|
3055
1699
|
const hasExplicitStores = Object.keys(stores).length > 0;
|
|
3056
1700
|
if (!storeConfig) {
|
|
3057
1701
|
if (name === "memory") {
|
|
3058
|
-
return new MemoryStore
|
|
1702
|
+
return new MemoryStore;
|
|
3059
1703
|
}
|
|
3060
1704
|
if (name === "null") {
|
|
3061
|
-
return new NullStore
|
|
1705
|
+
return new NullStore;
|
|
3062
1706
|
}
|
|
3063
1707
|
if (hasExplicitStores) {
|
|
3064
1708
|
throw new Error(`Cache store '${name}' is not defined.`);
|
|
3065
1709
|
}
|
|
3066
|
-
return new MemoryStore
|
|
1710
|
+
return new MemoryStore;
|
|
3067
1711
|
}
|
|
3068
1712
|
if (storeConfig.driver === "memory") {
|
|
3069
1713
|
return new MemoryStore({ maxItems: storeConfig.maxItems });
|
|
@@ -3075,7 +1719,7 @@ function createStoreFactory(config) {
|
|
|
3075
1719
|
return new RedisStore({ connection: storeConfig.connection, prefix: storeConfig.prefix });
|
|
3076
1720
|
}
|
|
3077
1721
|
if (storeConfig.driver === "null") {
|
|
3078
|
-
return new NullStore
|
|
1722
|
+
return new NullStore;
|
|
3079
1723
|
}
|
|
3080
1724
|
if (storeConfig.driver === "custom") {
|
|
3081
1725
|
return storeConfig.store;
|
|
@@ -3128,7 +1772,7 @@ function createStoreFactory(config) {
|
|
|
3128
1772
|
if (storeConfig.driver === "circuit-breaker") {
|
|
3129
1773
|
const factory = createStoreFactory(config);
|
|
3130
1774
|
const primary = factory(storeConfig.primary);
|
|
3131
|
-
const fallback = storeConfig.fallback ? factory(storeConfig.fallback) :
|
|
1775
|
+
const fallback = storeConfig.fallback ? factory(storeConfig.fallback) : undefined;
|
|
3132
1776
|
return new CircuitBreakerStore(primary, {
|
|
3133
1777
|
maxFailures: storeConfig.maxFailures,
|
|
3134
1778
|
resetTimeout: storeConfig.resetTimeout,
|
|
@@ -3138,16 +1782,18 @@ function createStoreFactory(config) {
|
|
|
3138
1782
|
throw new Error(`Unsupported cache driver '${storeConfig.driver}'.`);
|
|
3139
1783
|
};
|
|
3140
1784
|
}
|
|
3141
|
-
|
|
1785
|
+
|
|
1786
|
+
class OrbitStasis {
|
|
1787
|
+
options;
|
|
1788
|
+
manager;
|
|
3142
1789
|
constructor(options) {
|
|
3143
1790
|
this.options = options;
|
|
3144
1791
|
}
|
|
3145
|
-
manager;
|
|
3146
1792
|
install(core) {
|
|
3147
1793
|
const resolvedConfig = resolveStoreConfig(core, this.options);
|
|
3148
1794
|
const exposeAs = resolvedConfig.exposeAs ?? "cache";
|
|
3149
1795
|
const defaultStore = resolvedConfig.default ?? (resolvedConfig.provider ? "default" : "memory");
|
|
3150
|
-
const defaultTtl = resolvedConfig.defaultTtl ?? (typeof resolvedConfig.defaultTTL === "number" ? resolvedConfig.defaultTTL :
|
|
1796
|
+
const defaultTtl = resolvedConfig.defaultTtl ?? (typeof resolvedConfig.defaultTTL === "number" ? resolvedConfig.defaultTTL : undefined) ?? 60;
|
|
3151
1797
|
const prefix = resolvedConfig.prefix ?? "";
|
|
3152
1798
|
const logger = core.logger;
|
|
3153
1799
|
logger.info(`[OrbitCache] Initializing Cache (Exposed as: ${exposeAs})`);
|
|
@@ -3162,21 +1808,16 @@ var OrbitStasis = class {
|
|
|
3162
1808
|
const key = payload.key ? ` (key: ${payload.key})` : "";
|
|
3163
1809
|
logger.error(`[OrbitCache] cache event '${event}' failed${key}`, error);
|
|
3164
1810
|
});
|
|
3165
|
-
const stores = resolvedConfig.stores ?? (resolvedConfig.provider ? { default: { driver: "provider", provider: resolvedConfig.provider } } :
|
|
3166
|
-
const manager = new CacheManager(
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
mode: resolvedConfig.eventsMode ?? "async",
|
|
3176
|
-
throwOnError: resolvedConfig.throwOnEventError,
|
|
3177
|
-
onError: onEventError
|
|
3178
|
-
}
|
|
3179
|
-
);
|
|
1811
|
+
const stores = resolvedConfig.stores ?? (resolvedConfig.provider ? { default: { driver: "provider", provider: resolvedConfig.provider } } : undefined);
|
|
1812
|
+
const manager = new CacheManager(createStoreFactory({ ...resolvedConfig, stores }), {
|
|
1813
|
+
default: defaultStore,
|
|
1814
|
+
prefix,
|
|
1815
|
+
defaultTtl
|
|
1816
|
+
}, events, {
|
|
1817
|
+
mode: resolvedConfig.eventsMode ?? "async",
|
|
1818
|
+
throwOnError: resolvedConfig.throwOnEventError,
|
|
1819
|
+
onError: onEventError
|
|
1820
|
+
});
|
|
3180
1821
|
this.manager = manager;
|
|
3181
1822
|
core.adapter.use("*", async (c, next) => {
|
|
3182
1823
|
c.set(exposeAs, manager);
|
|
@@ -3191,7 +1832,7 @@ var OrbitStasis = class {
|
|
|
3191
1832
|
}
|
|
3192
1833
|
return this.manager;
|
|
3193
1834
|
}
|
|
3194
|
-
}
|
|
1835
|
+
}
|
|
3195
1836
|
function orbitCache(core, options = {}) {
|
|
3196
1837
|
const orbit = new OrbitStasis(options);
|
|
3197
1838
|
orbit.install(core);
|
|
@@ -3199,25 +1840,27 @@ function orbitCache(core, options = {}) {
|
|
|
3199
1840
|
}
|
|
3200
1841
|
var OrbitCache = OrbitStasis;
|
|
3201
1842
|
export {
|
|
3202
|
-
|
|
3203
|
-
CacheRepository,
|
|
3204
|
-
CircuitBreakerStore,
|
|
3205
|
-
FileStore,
|
|
3206
|
-
LockTimeoutError,
|
|
3207
|
-
MarkovPredictor,
|
|
3208
|
-
MemoryCacheProvider,
|
|
3209
|
-
MemoryStore,
|
|
3210
|
-
NullStore,
|
|
3211
|
-
OrbitCache,
|
|
3212
|
-
OrbitStasis,
|
|
3213
|
-
PredictiveStore,
|
|
3214
|
-
RateLimiter,
|
|
3215
|
-
RedisStore,
|
|
3216
|
-
TieredStore,
|
|
3217
|
-
orbitCache as default,
|
|
3218
|
-
isExpired,
|
|
3219
|
-
isTaggableStore,
|
|
3220
|
-
normalizeCacheKey,
|
|
1843
|
+
ttlToExpiresAt,
|
|
3221
1844
|
sleep,
|
|
3222
|
-
|
|
1845
|
+
normalizeCacheKey,
|
|
1846
|
+
isTaggableStore,
|
|
1847
|
+
isExpired,
|
|
1848
|
+
orbitCache as default,
|
|
1849
|
+
TieredStore,
|
|
1850
|
+
RedisStore,
|
|
1851
|
+
RateLimiter,
|
|
1852
|
+
PredictiveStore,
|
|
1853
|
+
OrbitStasis,
|
|
1854
|
+
OrbitCache,
|
|
1855
|
+
NullStore,
|
|
1856
|
+
MemoryStore,
|
|
1857
|
+
MemoryCacheProvider,
|
|
1858
|
+
MarkovPredictor,
|
|
1859
|
+
LockTimeoutError,
|
|
1860
|
+
FileStore,
|
|
1861
|
+
CircuitBreakerStore,
|
|
1862
|
+
CacheRepository,
|
|
1863
|
+
CacheManager
|
|
3223
1864
|
};
|
|
1865
|
+
|
|
1866
|
+
//# debugId=17E2109F0F3F0E8764756E2164756E21
|