layercache 1.4.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-7KMKQ6QZ.js → chunk-6X7NV5BG.js} +33 -0
- package/dist/{chunk-FFZCC7EQ.js → chunk-IVX6ABFX.js} +87 -0
- package/dist/cli.cjs +33 -0
- package/dist/cli.js +1 -1
- package/dist/{edge-D2FpRlyS.d.cts → edge-BCU8D-Yd.d.cts} +504 -0
- package/dist/{edge-D2FpRlyS.d.ts → edge-BCU8D-Yd.d.ts} +504 -0
- package/dist/edge.cjs +87 -0
- package/dist/edge.d.cts +1 -1
- package/dist/edge.d.ts +1 -1
- package/dist/edge.js +1 -1
- package/dist/index.cjs +485 -0
- package/dist/index.d.cts +244 -2
- package/dist/index.d.ts +244 -2
- package/dist/index.js +367 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,12 +12,12 @@ import {
|
|
|
12
12
|
validateTag,
|
|
13
13
|
validateTags,
|
|
14
14
|
validateTtlPolicy
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-6X7NV5BG.js";
|
|
16
16
|
import {
|
|
17
17
|
MemoryLayer,
|
|
18
18
|
TagIndex,
|
|
19
19
|
createHonoCacheMiddleware
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-IVX6ABFX.js";
|
|
21
21
|
import {
|
|
22
22
|
PatternMatcher,
|
|
23
23
|
createStoredValueEnvelope,
|
|
@@ -157,6 +157,9 @@ function addMetricMap(base, delta) {
|
|
|
157
157
|
|
|
158
158
|
// src/CacheNamespace.ts
|
|
159
159
|
var CacheNamespace = class _CacheNamespace {
|
|
160
|
+
/**
|
|
161
|
+
* Creates a namespace backed by an existing cache stack.
|
|
162
|
+
*/
|
|
160
163
|
constructor(cache, prefix) {
|
|
161
164
|
this.cache = cache;
|
|
162
165
|
this.prefix = prefix;
|
|
@@ -166,9 +169,16 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
166
169
|
prefix;
|
|
167
170
|
static metricsMutexes = /* @__PURE__ */ new WeakMap();
|
|
168
171
|
metrics = createEmptyNamespaceMetrics();
|
|
172
|
+
/**
|
|
173
|
+
* Reads a key inside this namespace and optionally runs a read-through fetcher
|
|
174
|
+
* on miss or refresh.
|
|
175
|
+
*/
|
|
169
176
|
async get(key, fetcher, options) {
|
|
170
177
|
return this.trackMetrics(() => this.cache.get(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
171
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Alias for `get(key, fetcher, options)` that makes the get-or-set behavior explicit.
|
|
181
|
+
*/
|
|
172
182
|
async getOrSet(key, fetcher, options) {
|
|
173
183
|
return this.trackMetrics(() => this.cache.getOrSet(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
174
184
|
}
|
|
@@ -178,24 +188,69 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
178
188
|
async getOrThrow(key, fetcher, options) {
|
|
179
189
|
return this.trackMetrics(() => this.cache.getOrThrow(this.qualify(key), fetcher, this.qualifyGetOptions(options)));
|
|
180
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Returns true when the namespaced key exists and has not expired in any layer.
|
|
193
|
+
*/
|
|
181
194
|
async has(key) {
|
|
182
195
|
return this.trackMetrics(() => this.cache.has(this.qualify(key)));
|
|
183
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Returns the remaining TTL in milliseconds for the namespaced key.
|
|
199
|
+
*/
|
|
184
200
|
async ttl(key) {
|
|
185
201
|
return this.trackMetrics(() => this.cache.ttl(this.qualify(key)));
|
|
186
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Stores a value under a namespaced key.
|
|
205
|
+
*/
|
|
187
206
|
async set(key, value, options) {
|
|
188
207
|
await this.trackMetrics(() => this.cache.set(this.qualify(key), value, this.qualifyWriteOptions(options)));
|
|
189
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Deletes a namespaced key from all layers.
|
|
211
|
+
*/
|
|
190
212
|
async delete(key) {
|
|
191
213
|
await this.trackMetrics(() => this.cache.delete(this.qualify(key)));
|
|
192
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Deletes multiple namespaced keys from all layers.
|
|
217
|
+
*/
|
|
193
218
|
async mdelete(keys) {
|
|
194
219
|
await this.trackMetrics(() => this.cache.mdelete(keys.map((k) => this.qualify(k))));
|
|
195
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Alias for `delete(key)` scoped to this namespace.
|
|
223
|
+
*/
|
|
224
|
+
async invalidateByKey(key) {
|
|
225
|
+
await this.trackMetrics(() => this.cache.invalidateByKey(this.qualify(key)));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Alias for `mdelete(keys)` scoped to this namespace.
|
|
229
|
+
*/
|
|
230
|
+
async invalidateByKeys(keys) {
|
|
231
|
+
await this.trackMetrics(() => this.cache.invalidateByKeys(keys.map((k) => this.qualify(k))));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Marks one exact namespaced key expired without deleting its stale value.
|
|
235
|
+
*/
|
|
236
|
+
async expireByKey(key) {
|
|
237
|
+
await this.trackMetrics(() => this.cache.expireByKey(this.qualify(key)));
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Marks multiple exact namespaced keys expired without deleting their stale values.
|
|
241
|
+
*/
|
|
242
|
+
async expireByKeys(keys) {
|
|
243
|
+
await this.trackMetrics(() => this.cache.expireByKeys(keys.map((k) => this.qualify(k))));
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Clears all keys in this namespace by invalidating the namespace prefix.
|
|
247
|
+
*/
|
|
196
248
|
async clear() {
|
|
197
249
|
await this.trackMetrics(() => this.cache.invalidateByPrefix(this.prefix));
|
|
198
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Reads many namespaced keys concurrently.
|
|
253
|
+
*/
|
|
199
254
|
async mget(entries) {
|
|
200
255
|
return this.trackMetrics(
|
|
201
256
|
() => this.cache.mget(
|
|
@@ -207,6 +262,9 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
207
262
|
)
|
|
208
263
|
);
|
|
209
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Writes many namespaced entries concurrently.
|
|
267
|
+
*/
|
|
210
268
|
async mset(entries) {
|
|
211
269
|
await this.trackMetrics(
|
|
212
270
|
() => this.cache.mset(
|
|
@@ -218,12 +276,21 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
218
276
|
)
|
|
219
277
|
);
|
|
220
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Deletes keys associated with a tag scoped to this namespace.
|
|
281
|
+
*/
|
|
221
282
|
async invalidateByTag(tag) {
|
|
222
283
|
await this.trackMetrics(() => this.cache.invalidateByTag(this.qualifyTag(tag)));
|
|
223
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Expires keys associated with a tag scoped to this namespace while preserving stale windows.
|
|
287
|
+
*/
|
|
224
288
|
async expireByTag(tag) {
|
|
225
289
|
await this.trackMetrics(() => this.cache.expireByTag(this.qualifyTag(tag)));
|
|
226
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Deletes keys associated with any or all namespace-scoped tags.
|
|
293
|
+
*/
|
|
227
294
|
async invalidateByTags(tags, mode = "any") {
|
|
228
295
|
await this.trackMetrics(
|
|
229
296
|
() => this.cache.invalidateByTags(
|
|
@@ -232,6 +299,9 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
232
299
|
)
|
|
233
300
|
);
|
|
234
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Expires keys associated with any or all namespace-scoped tags while preserving stale windows.
|
|
304
|
+
*/
|
|
235
305
|
async expireByTags(tags, mode = "any") {
|
|
236
306
|
await this.trackMetrics(
|
|
237
307
|
() => this.cache.expireByTags(
|
|
@@ -240,15 +310,27 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
240
310
|
)
|
|
241
311
|
);
|
|
242
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Deletes namespaced keys matching a wildcard pattern.
|
|
315
|
+
*/
|
|
243
316
|
async invalidateByPattern(pattern) {
|
|
244
317
|
await this.trackMetrics(() => this.cache.invalidateByPattern(this.qualify(pattern)));
|
|
245
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Expires namespaced keys matching a wildcard pattern while preserving stale windows.
|
|
321
|
+
*/
|
|
246
322
|
async expireByPattern(pattern) {
|
|
247
323
|
await this.trackMetrics(() => this.cache.expireByPattern(this.qualify(pattern)));
|
|
248
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Deletes namespaced keys with the provided prefix.
|
|
327
|
+
*/
|
|
249
328
|
async invalidateByPrefix(prefix) {
|
|
250
329
|
await this.trackMetrics(() => this.cache.invalidateByPrefix(this.qualify(prefix)));
|
|
251
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Expires namespaced keys with the provided prefix while preserving stale windows.
|
|
333
|
+
*/
|
|
252
334
|
async expireByPrefix(prefix) {
|
|
253
335
|
await this.trackMetrics(() => this.cache.expireByPrefix(this.qualify(prefix)));
|
|
254
336
|
}
|
|
@@ -265,9 +347,15 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
265
347
|
tags: result.tags.filter((tag) => tag.startsWith(`${this.prefix}:`)).map((tag) => tag.slice(this.prefix.length + 1))
|
|
266
348
|
};
|
|
267
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Returns a cached wrapper whose generated keys are scoped to this namespace.
|
|
352
|
+
*/
|
|
268
353
|
wrap(keyPrefix, fetcher, options) {
|
|
269
354
|
return this.cache.wrap(`${this.prefix}:${keyPrefix}`, fetcher, this.qualifyWrapOptions(options));
|
|
270
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Warms entries after qualifying each key and tag with this namespace prefix.
|
|
358
|
+
*/
|
|
271
359
|
warm(entries, options) {
|
|
272
360
|
return this.cache.warm(
|
|
273
361
|
entries.map((entry) => ({
|
|
@@ -278,9 +366,15 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
278
366
|
options
|
|
279
367
|
);
|
|
280
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Returns metrics accumulated by operations performed through this namespace.
|
|
371
|
+
*/
|
|
281
372
|
getMetrics() {
|
|
282
373
|
return cloneNamespaceMetrics(this.metrics);
|
|
283
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Returns hit-rate statistics for operations performed through this namespace.
|
|
377
|
+
*/
|
|
284
378
|
getHitRate() {
|
|
285
379
|
return computeNamespaceHitRate(this.metrics);
|
|
286
380
|
}
|
|
@@ -297,6 +391,9 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
297
391
|
validateNamespaceKey(childPrefix);
|
|
298
392
|
return new _CacheNamespace(this.cache, `${this.prefix}:${childPrefix}`);
|
|
299
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Qualifies a raw key with this namespace prefix.
|
|
396
|
+
*/
|
|
300
397
|
qualify(key) {
|
|
301
398
|
return `${this.prefix}:${key}`;
|
|
302
399
|
}
|
|
@@ -2167,9 +2264,15 @@ var TtlResolver = class {
|
|
|
2167
2264
|
|
|
2168
2265
|
// src/serialization/JsonSerializer.ts
|
|
2169
2266
|
var JsonSerializer = class {
|
|
2267
|
+
/**
|
|
2268
|
+
* Serializes a value to JSON.
|
|
2269
|
+
*/
|
|
2170
2270
|
serialize(value) {
|
|
2171
2271
|
return JSON.stringify(value);
|
|
2172
2272
|
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Parses JSON and sanitizes the result before returning it.
|
|
2275
|
+
*/
|
|
2173
2276
|
deserialize(payload) {
|
|
2174
2277
|
const normalized = Buffer.isBuffer(payload) ? payload.toString("utf8") : payload;
|
|
2175
2278
|
let parsed;
|
|
@@ -2196,6 +2299,9 @@ var StampedeGuard = class {
|
|
|
2196
2299
|
this.maxInFlight = options.maxInFlight ?? 1e4;
|
|
2197
2300
|
this.entryTimeoutMs = options.entryTimeoutMs;
|
|
2198
2301
|
}
|
|
2302
|
+
/**
|
|
2303
|
+
* Deduplicates concurrent work for the same key in this process.
|
|
2304
|
+
*/
|
|
2199
2305
|
async execute(key, task) {
|
|
2200
2306
|
const existing = this.inFlight.get(key);
|
|
2201
2307
|
if (existing) {
|
|
@@ -2295,6 +2401,9 @@ var DebugLogger = class {
|
|
|
2295
2401
|
}
|
|
2296
2402
|
};
|
|
2297
2403
|
var CacheStack = class extends EventEmitter {
|
|
2404
|
+
/**
|
|
2405
|
+
* Creates a cache stack from ordered layers and optional global behavior settings.
|
|
2406
|
+
*/
|
|
2298
2407
|
constructor(layers, options = {}) {
|
|
2299
2408
|
super();
|
|
2300
2409
|
this.layers = layers;
|
|
@@ -2560,6 +2669,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2560
2669
|
});
|
|
2561
2670
|
});
|
|
2562
2671
|
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Clears every configured layer, removes tag metadata, resets internal TTL
|
|
2674
|
+
* profiles, and broadcasts a clear invalidation message.
|
|
2675
|
+
*/
|
|
2563
2676
|
async clear() {
|
|
2564
2677
|
await this.awaitStartup("clear");
|
|
2565
2678
|
this.maintenance.beginClearEpoch();
|
|
@@ -2589,6 +2702,58 @@ var CacheStack = class extends EventEmitter {
|
|
|
2589
2702
|
operation: "delete"
|
|
2590
2703
|
});
|
|
2591
2704
|
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Alias for `delete(key)` that matches the `invalidateBy*` API family.
|
|
2707
|
+
*/
|
|
2708
|
+
async invalidateByKey(key) {
|
|
2709
|
+
await this.delete(key);
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Alias for `mdelete(keys)` that matches the `invalidateBy*` API family.
|
|
2713
|
+
*/
|
|
2714
|
+
async invalidateByKeys(keys) {
|
|
2715
|
+
await this.mdelete(keys);
|
|
2716
|
+
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Marks one exact key expired without deleting its stale value.
|
|
2719
|
+
*/
|
|
2720
|
+
async expireByKey(key) {
|
|
2721
|
+
await this.observeOperation("layercache.expire_by_key", { "layercache.key": String(key ?? "") }, async () => {
|
|
2722
|
+
const normalizedKey = this.qualifyKey(validateCacheKey(key));
|
|
2723
|
+
await this.awaitStartup("expireByKey");
|
|
2724
|
+
await this.expireKeys([normalizedKey]);
|
|
2725
|
+
await this.publishInvalidation({
|
|
2726
|
+
scope: "key",
|
|
2727
|
+
keys: [normalizedKey],
|
|
2728
|
+
sourceId: this.instanceId,
|
|
2729
|
+
operation: "expire"
|
|
2730
|
+
});
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
/**
|
|
2734
|
+
* Marks multiple exact keys expired without deleting their stale values.
|
|
2735
|
+
*/
|
|
2736
|
+
async expireByKeys(keys) {
|
|
2737
|
+
await this.observeOperation("layercache.expire_by_keys", void 0, async () => {
|
|
2738
|
+
if (keys.length === 0) {
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
const normalizedKeys = keys.map((k) => validateCacheKey(k));
|
|
2742
|
+
const cacheKeys = normalizedKeys.map((key) => this.qualifyKey(key));
|
|
2743
|
+
await this.awaitStartup("expireByKeys");
|
|
2744
|
+
await this.expireKeys(cacheKeys);
|
|
2745
|
+
await this.publishInvalidation({
|
|
2746
|
+
scope: "keys",
|
|
2747
|
+
keys: cacheKeys,
|
|
2748
|
+
sourceId: this.instanceId,
|
|
2749
|
+
operation: "expire"
|
|
2750
|
+
});
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Reads many keys concurrently. Simple reads use layer-level bulk fast paths;
|
|
2755
|
+
* entries with fetchers or options fall back to per-entry read-through logic.
|
|
2756
|
+
*/
|
|
2592
2757
|
async mget(entries) {
|
|
2593
2758
|
return this.observeOperation("layercache.mget", void 0, async () => {
|
|
2594
2759
|
this.assertActive("mget");
|
|
@@ -2676,6 +2841,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2676
2841
|
return normalizedEntries.map((entry) => resultsByKey.get(entry.key) ?? null);
|
|
2677
2842
|
});
|
|
2678
2843
|
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Writes many entries concurrently using each layer's bulk write fast path
|
|
2846
|
+
* when available.
|
|
2847
|
+
*/
|
|
2679
2848
|
async mset(entries) {
|
|
2680
2849
|
await this.observeOperation("layercache.mset", void 0, async () => {
|
|
2681
2850
|
this.assertActive("mset");
|
|
@@ -2688,6 +2857,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2688
2857
|
await this.writeBatch(normalizedEntries);
|
|
2689
2858
|
});
|
|
2690
2859
|
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Pre-populates cache entries by running their fetchers with bounded
|
|
2862
|
+
* concurrency. Higher-priority entries run first.
|
|
2863
|
+
*/
|
|
2691
2864
|
async warm(entries, options = {}) {
|
|
2692
2865
|
this.assertActive("warm");
|
|
2693
2866
|
const concurrency = Math.max(1, options.concurrency ?? 4);
|
|
@@ -2738,6 +2911,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2738
2911
|
validateNamespaceKey(prefix);
|
|
2739
2912
|
return new CacheNamespace(this, prefix);
|
|
2740
2913
|
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Deletes every key currently associated with `tag` and broadcasts an
|
|
2916
|
+
* invalidation message.
|
|
2917
|
+
*/
|
|
2741
2918
|
async invalidateByTag(tag) {
|
|
2742
2919
|
await this.observeOperation("layercache.invalidate_by_tag", void 0, async () => {
|
|
2743
2920
|
validateTag(tag);
|
|
@@ -2747,6 +2924,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2747
2924
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
2748
2925
|
});
|
|
2749
2926
|
}
|
|
2927
|
+
/**
|
|
2928
|
+
* Marks every key associated with `tag` as expired while preserving stale
|
|
2929
|
+
* windows for stale serving.
|
|
2930
|
+
*/
|
|
2750
2931
|
async expireByTag(tag) {
|
|
2751
2932
|
await this.observeOperation("layercache.expire_by_tag", void 0, async () => {
|
|
2752
2933
|
validateTag(tag);
|
|
@@ -2756,6 +2937,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2756
2937
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
|
|
2757
2938
|
});
|
|
2758
2939
|
}
|
|
2940
|
+
/**
|
|
2941
|
+
* Deletes keys associated with any or all of the provided tags and broadcasts
|
|
2942
|
+
* an invalidation message.
|
|
2943
|
+
*/
|
|
2759
2944
|
async invalidateByTags(tags, mode = "any") {
|
|
2760
2945
|
await this.observeOperation("layercache.invalidate_by_tags", void 0, async () => {
|
|
2761
2946
|
if (tags.length === 0) {
|
|
@@ -2772,6 +2957,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2772
2957
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
2773
2958
|
});
|
|
2774
2959
|
}
|
|
2960
|
+
/**
|
|
2961
|
+
* Marks keys associated with any or all of the provided tags as expired while
|
|
2962
|
+
* preserving stale windows for stale serving.
|
|
2963
|
+
*/
|
|
2775
2964
|
async expireByTags(tags, mode = "any") {
|
|
2776
2965
|
await this.observeOperation("layercache.expire_by_tags", void 0, async () => {
|
|
2777
2966
|
if (tags.length === 0) {
|
|
@@ -2788,6 +2977,9 @@ var CacheStack = class extends EventEmitter {
|
|
|
2788
2977
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
|
|
2789
2978
|
});
|
|
2790
2979
|
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Deletes keys matching a wildcard pattern such as `user:*`.
|
|
2982
|
+
*/
|
|
2791
2983
|
async invalidateByPattern(pattern) {
|
|
2792
2984
|
await this.observeOperation("layercache.invalidate_by_pattern", void 0, async () => {
|
|
2793
2985
|
validatePattern(pattern);
|
|
@@ -2800,6 +2992,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2800
2992
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
2801
2993
|
});
|
|
2802
2994
|
}
|
|
2995
|
+
/**
|
|
2996
|
+
* Marks keys matching a wildcard pattern as expired while preserving stale
|
|
2997
|
+
* windows for stale serving.
|
|
2998
|
+
*/
|
|
2803
2999
|
async expireByPattern(pattern) {
|
|
2804
3000
|
await this.observeOperation("layercache.expire_by_pattern", void 0, async () => {
|
|
2805
3001
|
validatePattern(pattern);
|
|
@@ -2812,6 +3008,9 @@ var CacheStack = class extends EventEmitter {
|
|
|
2812
3008
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
|
|
2813
3009
|
});
|
|
2814
3010
|
}
|
|
3011
|
+
/**
|
|
3012
|
+
* Deletes keys that start with the provided prefix.
|
|
3013
|
+
*/
|
|
2815
3014
|
async invalidateByPrefix(prefix) {
|
|
2816
3015
|
await this.observeOperation("layercache.invalidate_by_prefix", void 0, async () => {
|
|
2817
3016
|
await this.awaitStartup("invalidateByPrefix");
|
|
@@ -2821,6 +3020,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2821
3020
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
2822
3021
|
});
|
|
2823
3022
|
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Marks keys that start with the provided prefix as expired while preserving
|
|
3025
|
+
* stale windows for stale serving.
|
|
3026
|
+
*/
|
|
2824
3027
|
async expireByPrefix(prefix) {
|
|
2825
3028
|
await this.observeOperation("layercache.expire_by_prefix", void 0, async () => {
|
|
2826
3029
|
await this.awaitStartup("expireByPrefix");
|
|
@@ -2830,9 +3033,15 @@ var CacheStack = class extends EventEmitter {
|
|
|
2830
3033
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "expire" });
|
|
2831
3034
|
});
|
|
2832
3035
|
}
|
|
3036
|
+
/**
|
|
3037
|
+
* Returns cumulative cache metrics since startup or the last `resetMetrics()`.
|
|
3038
|
+
*/
|
|
2833
3039
|
getMetrics() {
|
|
2834
3040
|
return this.metricsCollector.snapshot;
|
|
2835
3041
|
}
|
|
3042
|
+
/**
|
|
3043
|
+
* Returns metrics plus layer degradation state and active background refresh count.
|
|
3044
|
+
*/
|
|
2836
3045
|
getStats() {
|
|
2837
3046
|
return {
|
|
2838
3047
|
metrics: this.getMetrics(),
|
|
@@ -2844,6 +3053,9 @@ var CacheStack = class extends EventEmitter {
|
|
|
2844
3053
|
backgroundRefreshes: this.reader.activeRefreshCount
|
|
2845
3054
|
};
|
|
2846
3055
|
}
|
|
3056
|
+
/**
|
|
3057
|
+
* Resets cumulative metrics counters.
|
|
3058
|
+
*/
|
|
2847
3059
|
resetMetrics() {
|
|
2848
3060
|
this.metricsCollector.reset();
|
|
2849
3061
|
}
|
|
@@ -2853,6 +3065,10 @@ var CacheStack = class extends EventEmitter {
|
|
|
2853
3065
|
getHitRate() {
|
|
2854
3066
|
return this.metricsCollector.hitRate();
|
|
2855
3067
|
}
|
|
3068
|
+
/**
|
|
3069
|
+
* Runs each layer's `ping()` hook when available and returns per-layer health
|
|
3070
|
+
* and latency information.
|
|
3071
|
+
*/
|
|
2856
3072
|
async healthCheck() {
|
|
2857
3073
|
await this.startup;
|
|
2858
3074
|
return Promise.all(
|
|
@@ -2937,22 +3153,38 @@ var CacheStack = class extends EventEmitter {
|
|
|
2937
3153
|
const tags = await this.getTagsForKey(normalizedKey);
|
|
2938
3154
|
return { key: userKey, foundInLayers, freshTtlMs, staleTtlMs, errorTtlMs, isStale, tags };
|
|
2939
3155
|
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Exports cache entries from configured layers for process-local snapshots.
|
|
3158
|
+
*/
|
|
2940
3159
|
async exportState() {
|
|
2941
3160
|
await this.awaitStartup("exportState");
|
|
2942
3161
|
return this.snapshots.exportState(this.snapshotMaxEntries());
|
|
2943
3162
|
}
|
|
3163
|
+
/**
|
|
3164
|
+
* Imports entries produced by `exportState()` into the configured layers.
|
|
3165
|
+
*/
|
|
2944
3166
|
async importState(entries) {
|
|
2945
3167
|
await this.awaitStartup("importState");
|
|
2946
3168
|
await this.snapshots.importState(entries);
|
|
2947
3169
|
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Writes a snapshot file containing current cache entries.
|
|
3172
|
+
*/
|
|
2948
3173
|
async persistToFile(filePath) {
|
|
2949
3174
|
this.assertActive("persistToFile");
|
|
2950
3175
|
await this.snapshots.persistToFile(filePath, this.options.snapshotBaseDir, this.snapshotMaxEntries());
|
|
2951
3176
|
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Restores cache entries from a snapshot file.
|
|
3179
|
+
*/
|
|
2952
3180
|
async restoreFromFile(filePath) {
|
|
2953
3181
|
this.assertActive("restoreFromFile");
|
|
2954
3182
|
await this.snapshots.restoreFromFile(filePath, this.options.snapshotBaseDir, this.snapshotMaxBytes());
|
|
2955
3183
|
}
|
|
3184
|
+
/**
|
|
3185
|
+
* Flushes background work, unsubscribes from buses, disposes timers, and then
|
|
3186
|
+
* disposes each layer that provides `dispose()`.
|
|
3187
|
+
*/
|
|
2956
3188
|
async disconnect() {
|
|
2957
3189
|
if (!this.disconnectPromise) {
|
|
2958
3190
|
this.isDisconnecting = true;
|
|
@@ -3452,6 +3684,9 @@ var RedisInvalidationBus = class {
|
|
|
3452
3684
|
this.channel = options.channel ?? "layercache:invalidation";
|
|
3453
3685
|
this.logger = options.logger;
|
|
3454
3686
|
}
|
|
3687
|
+
/**
|
|
3688
|
+
* Subscribes to invalidation messages and returns an unsubscribe function.
|
|
3689
|
+
*/
|
|
3455
3690
|
async subscribe(handler) {
|
|
3456
3691
|
const previousPromise = this.subscribePromise;
|
|
3457
3692
|
let resolveThis;
|
|
@@ -3483,6 +3718,9 @@ var RedisInvalidationBus = class {
|
|
|
3483
3718
|
}
|
|
3484
3719
|
};
|
|
3485
3720
|
}
|
|
3721
|
+
/**
|
|
3722
|
+
* Publishes an invalidation message to other subscribers.
|
|
3723
|
+
*/
|
|
3486
3724
|
async publish(message) {
|
|
3487
3725
|
await this.publisher.publish(this.channel, JSON.stringify(message));
|
|
3488
3726
|
}
|
|
@@ -3783,6 +4021,9 @@ var RedisLayer = class {
|
|
|
3783
4021
|
decompressionMaxBytes;
|
|
3784
4022
|
commandTimeoutMs;
|
|
3785
4023
|
disconnectOnDispose;
|
|
4024
|
+
/**
|
|
4025
|
+
* Creates a Redis cache layer using an existing ioredis client.
|
|
4026
|
+
*/
|
|
3786
4027
|
constructor(options) {
|
|
3787
4028
|
this.client = options.client;
|
|
3788
4029
|
this.defaultTtl = options.ttl;
|
|
@@ -3797,10 +4038,16 @@ var RedisLayer = class {
|
|
|
3797
4038
|
this.commandTimeoutMs = this.normalizeCommandTimeoutMs(options.commandTimeoutMs);
|
|
3798
4039
|
this.disconnectOnDispose = options.disconnectOnDispose ?? false;
|
|
3799
4040
|
}
|
|
4041
|
+
/**
|
|
4042
|
+
* Reads and unwraps a fresh value from Redis.
|
|
4043
|
+
*/
|
|
3800
4044
|
async get(key) {
|
|
3801
4045
|
const payload = await this.getEntry(key);
|
|
3802
4046
|
return unwrapStoredValue(payload);
|
|
3803
4047
|
}
|
|
4048
|
+
/**
|
|
4049
|
+
* Reads the raw stored value or envelope from Redis.
|
|
4050
|
+
*/
|
|
3804
4051
|
async getEntry(key) {
|
|
3805
4052
|
this.validateKey(key);
|
|
3806
4053
|
const payload = await this.runCommand(
|
|
@@ -3812,6 +4059,9 @@ var RedisLayer = class {
|
|
|
3812
4059
|
}
|
|
3813
4060
|
return this.deserializeOrDelete(key, payload);
|
|
3814
4061
|
}
|
|
4062
|
+
/**
|
|
4063
|
+
* Reads many raw entries from Redis using a pipeline.
|
|
4064
|
+
*/
|
|
3815
4065
|
async getMany(keys) {
|
|
3816
4066
|
if (keys.length === 0) {
|
|
3817
4067
|
return [];
|
|
@@ -3837,6 +4087,9 @@ var RedisLayer = class {
|
|
|
3837
4087
|
})
|
|
3838
4088
|
);
|
|
3839
4089
|
}
|
|
4090
|
+
/**
|
|
4091
|
+
* Writes many entries to Redis using a pipeline.
|
|
4092
|
+
*/
|
|
3840
4093
|
async setMany(entries) {
|
|
3841
4094
|
if (entries.length === 0) {
|
|
3842
4095
|
return;
|
|
@@ -3857,6 +4110,9 @@ var RedisLayer = class {
|
|
|
3857
4110
|
}
|
|
3858
4111
|
await this.runCommand(`mset(${entries.length})`, () => pipeline.exec());
|
|
3859
4112
|
}
|
|
4113
|
+
/**
|
|
4114
|
+
* Stores a value in Redis using the provided TTL or layer default TTL.
|
|
4115
|
+
*/
|
|
3860
4116
|
async set(key, value, ttl = this.defaultTtl) {
|
|
3861
4117
|
this.validateKey(key);
|
|
3862
4118
|
const serialized = this.primarySerializer().serialize(value);
|
|
@@ -3871,10 +4127,16 @@ var RedisLayer = class {
|
|
|
3871
4127
|
}
|
|
3872
4128
|
await this.runCommand(`set(${this.displayKey(key)})`, () => this.client.set(normalizedKey, payload));
|
|
3873
4129
|
}
|
|
4130
|
+
/**
|
|
4131
|
+
* Deletes a key from Redis.
|
|
4132
|
+
*/
|
|
3874
4133
|
async delete(key) {
|
|
3875
4134
|
this.validateKey(key);
|
|
3876
4135
|
await this.runCommand(`delete(${this.displayKey(key)})`, () => this.client.del(this.withPrefix(key)));
|
|
3877
4136
|
}
|
|
4137
|
+
/**
|
|
4138
|
+
* Deletes multiple keys from Redis in batches.
|
|
4139
|
+
*/
|
|
3878
4140
|
async deleteMany(keys) {
|
|
3879
4141
|
if (keys.length === 0) {
|
|
3880
4142
|
return;
|
|
@@ -3887,11 +4149,17 @@ var RedisLayer = class {
|
|
|
3887
4149
|
() => this.client.del(...keys.map((key) => this.withPrefix(key)))
|
|
3888
4150
|
);
|
|
3889
4151
|
}
|
|
4152
|
+
/**
|
|
4153
|
+
* Returns true when the key exists in Redis.
|
|
4154
|
+
*/
|
|
3890
4155
|
async has(key) {
|
|
3891
4156
|
this.validateKey(key);
|
|
3892
4157
|
const exists = await this.runCommand(`has(${this.displayKey(key)})`, () => this.client.exists(this.withPrefix(key)));
|
|
3893
4158
|
return exists > 0;
|
|
3894
4159
|
}
|
|
4160
|
+
/**
|
|
4161
|
+
* Returns remaining Redis TTL in milliseconds, or null when absent or non-expiring.
|
|
4162
|
+
*/
|
|
3895
4163
|
async ttl(key) {
|
|
3896
4164
|
this.validateKey(key);
|
|
3897
4165
|
const remaining = await this.runCommand(
|
|
@@ -3903,6 +4171,9 @@ var RedisLayer = class {
|
|
|
3903
4171
|
}
|
|
3904
4172
|
return remaining;
|
|
3905
4173
|
}
|
|
4174
|
+
/**
|
|
4175
|
+
* Returns the number of keys under this layer's prefix.
|
|
4176
|
+
*/
|
|
3906
4177
|
async size() {
|
|
3907
4178
|
if (!this.prefix) {
|
|
3908
4179
|
return this.runCommand("dbsize()", () => this.client.dbsize());
|
|
@@ -3920,6 +4191,9 @@ var RedisLayer = class {
|
|
|
3920
4191
|
} while (cursor !== "0");
|
|
3921
4192
|
return count;
|
|
3922
4193
|
}
|
|
4194
|
+
/**
|
|
4195
|
+
* Runs a Redis ping command.
|
|
4196
|
+
*/
|
|
3923
4197
|
async ping() {
|
|
3924
4198
|
try {
|
|
3925
4199
|
return await this.runCommand("ping()", () => this.client.ping()) === "PONG";
|
|
@@ -3927,6 +4201,9 @@ var RedisLayer = class {
|
|
|
3927
4201
|
return false;
|
|
3928
4202
|
}
|
|
3929
4203
|
}
|
|
4204
|
+
/**
|
|
4205
|
+
* Disconnects the Redis client when `disconnectOnDispose` is enabled.
|
|
4206
|
+
*/
|
|
3930
4207
|
async dispose() {
|
|
3931
4208
|
if (this.disconnectOnDispose) {
|
|
3932
4209
|
this.client.disconnect();
|
|
@@ -3959,6 +4236,9 @@ var RedisLayer = class {
|
|
|
3959
4236
|
}
|
|
3960
4237
|
} while (cursor !== "0");
|
|
3961
4238
|
}
|
|
4239
|
+
/**
|
|
4240
|
+
* Returns keys under this layer's prefix without the prefix included.
|
|
4241
|
+
*/
|
|
3962
4242
|
async keys() {
|
|
3963
4243
|
const pattern = `${this.prefix}*`;
|
|
3964
4244
|
const keys = await this.scanKeys(pattern);
|
|
@@ -3967,6 +4247,9 @@ var RedisLayer = class {
|
|
|
3967
4247
|
}
|
|
3968
4248
|
return keys.map((key) => key.slice(this.prefix.length));
|
|
3969
4249
|
}
|
|
4250
|
+
/**
|
|
4251
|
+
* Visits keys under this layer's prefix without materializing all results.
|
|
4252
|
+
*/
|
|
3970
4253
|
async forEachKey(visitor) {
|
|
3971
4254
|
const pattern = `${this.prefix}*`;
|
|
3972
4255
|
let cursor = "0";
|
|
@@ -4324,6 +4607,9 @@ var DiskLayer = class {
|
|
|
4324
4607
|
maxEntryBytes;
|
|
4325
4608
|
protection;
|
|
4326
4609
|
writeQueue = Promise.resolve();
|
|
4610
|
+
/**
|
|
4611
|
+
* Creates a disk-backed cache layer.
|
|
4612
|
+
*/
|
|
4327
4613
|
constructor(options) {
|
|
4328
4614
|
this.directory = this.resolveDirectory(options.directory);
|
|
4329
4615
|
this.defaultTtl = options.ttl;
|
|
@@ -4336,9 +4622,15 @@ var DiskLayer = class {
|
|
|
4336
4622
|
signingKey: options.signingKey
|
|
4337
4623
|
});
|
|
4338
4624
|
}
|
|
4625
|
+
/**
|
|
4626
|
+
* Reads and unwraps a fresh value from disk.
|
|
4627
|
+
*/
|
|
4339
4628
|
async get(key) {
|
|
4340
4629
|
return unwrapStoredValue(await this.getEntry(key));
|
|
4341
4630
|
}
|
|
4631
|
+
/**
|
|
4632
|
+
* Reads the raw stored value or envelope from disk.
|
|
4633
|
+
*/
|
|
4342
4634
|
async getEntry(key) {
|
|
4343
4635
|
const filePath = this.keyToPath(key);
|
|
4344
4636
|
const raw = await this.readEntryFile(filePath);
|
|
@@ -4358,6 +4650,9 @@ var DiskLayer = class {
|
|
|
4358
4650
|
}
|
|
4359
4651
|
return entry.value;
|
|
4360
4652
|
}
|
|
4653
|
+
/**
|
|
4654
|
+
* Stores a value on disk using the provided TTL or layer default TTL.
|
|
4655
|
+
*/
|
|
4361
4656
|
async set(key, value, ttl = this.defaultTtl) {
|
|
4362
4657
|
await this.enqueueWrite(async () => {
|
|
4363
4658
|
await fs2.mkdir(this.directory, { recursive: true });
|
|
@@ -4383,16 +4678,28 @@ var DiskLayer = class {
|
|
|
4383
4678
|
}
|
|
4384
4679
|
});
|
|
4385
4680
|
}
|
|
4681
|
+
/**
|
|
4682
|
+
* Reads many raw entries from disk.
|
|
4683
|
+
*/
|
|
4386
4684
|
async getMany(keys) {
|
|
4387
4685
|
return Promise.all(keys.map((key) => this.getEntry(key)));
|
|
4388
4686
|
}
|
|
4687
|
+
/**
|
|
4688
|
+
* Writes many entries to disk.
|
|
4689
|
+
*/
|
|
4389
4690
|
async setMany(entries) {
|
|
4390
4691
|
await Promise.all(entries.map((entry) => this.set(entry.key, entry.value, entry.ttl)));
|
|
4391
4692
|
}
|
|
4693
|
+
/**
|
|
4694
|
+
* Returns true when the key exists and has not expired.
|
|
4695
|
+
*/
|
|
4392
4696
|
async has(key) {
|
|
4393
4697
|
const value = await this.getEntry(key);
|
|
4394
4698
|
return value !== null;
|
|
4395
4699
|
}
|
|
4700
|
+
/**
|
|
4701
|
+
* Returns remaining TTL in milliseconds, or null when absent or non-expiring.
|
|
4702
|
+
*/
|
|
4396
4703
|
async ttl(key) {
|
|
4397
4704
|
const filePath = this.keyToPath(key);
|
|
4398
4705
|
const raw = await this.readEntryFile(filePath);
|
|
@@ -4415,14 +4722,23 @@ var DiskLayer = class {
|
|
|
4415
4722
|
}
|
|
4416
4723
|
return remaining;
|
|
4417
4724
|
}
|
|
4725
|
+
/**
|
|
4726
|
+
* Deletes a key from disk.
|
|
4727
|
+
*/
|
|
4418
4728
|
async delete(key) {
|
|
4419
4729
|
await this.enqueueWrite(() => this.safeDelete(this.keyToPath(key)));
|
|
4420
4730
|
}
|
|
4731
|
+
/**
|
|
4732
|
+
* Deletes multiple keys from disk.
|
|
4733
|
+
*/
|
|
4421
4734
|
async deleteMany(keys) {
|
|
4422
4735
|
await this.enqueueWrite(async () => {
|
|
4423
4736
|
await this.deletePathsWithConcurrency(keys.map((key) => this.keyToPath(key)));
|
|
4424
4737
|
});
|
|
4425
4738
|
}
|
|
4739
|
+
/**
|
|
4740
|
+
* Removes all cache entry files from this layer's directory.
|
|
4741
|
+
*/
|
|
4426
4742
|
async clear() {
|
|
4427
4743
|
await this.enqueueWrite(async () => {
|
|
4428
4744
|
let entries;
|
|
@@ -4447,11 +4763,17 @@ var DiskLayer = class {
|
|
|
4447
4763
|
});
|
|
4448
4764
|
return keys;
|
|
4449
4765
|
}
|
|
4766
|
+
/**
|
|
4767
|
+
* Visits all non-expired keys stored on disk.
|
|
4768
|
+
*/
|
|
4450
4769
|
async forEachKey(visitor) {
|
|
4451
4770
|
await this.scanEntries(async (entry) => {
|
|
4452
4771
|
await visitor(entry.key);
|
|
4453
4772
|
});
|
|
4454
4773
|
}
|
|
4774
|
+
/**
|
|
4775
|
+
* Returns the number of non-expired entries stored on disk.
|
|
4776
|
+
*/
|
|
4455
4777
|
async size() {
|
|
4456
4778
|
let count = 0;
|
|
4457
4779
|
await this.scanEntries(async () => {
|
|
@@ -4459,6 +4781,9 @@ var DiskLayer = class {
|
|
|
4459
4781
|
});
|
|
4460
4782
|
return count;
|
|
4461
4783
|
}
|
|
4784
|
+
/**
|
|
4785
|
+
* Verifies the cache directory can be created.
|
|
4786
|
+
*/
|
|
4462
4787
|
async ping() {
|
|
4463
4788
|
try {
|
|
4464
4789
|
await fs2.mkdir(this.directory, { recursive: true });
|
|
@@ -4467,6 +4792,9 @@ var DiskLayer = class {
|
|
|
4467
4792
|
return false;
|
|
4468
4793
|
}
|
|
4469
4794
|
}
|
|
4795
|
+
/**
|
|
4796
|
+
* Reserved for interface compatibility; DiskLayer does not hold persistent handles.
|
|
4797
|
+
*/
|
|
4470
4798
|
async dispose() {
|
|
4471
4799
|
}
|
|
4472
4800
|
keyToPath(key) {
|
|
@@ -4670,6 +4998,9 @@ var MemcachedLayer = class {
|
|
|
4670
4998
|
client;
|
|
4671
4999
|
keyPrefix;
|
|
4672
5000
|
serializer;
|
|
5001
|
+
/**
|
|
5002
|
+
* Creates a Memcached cache layer using a compatible client.
|
|
5003
|
+
*/
|
|
4673
5004
|
constructor(options) {
|
|
4674
5005
|
this.client = options.client;
|
|
4675
5006
|
this.defaultTtl = options.ttl;
|
|
@@ -4677,9 +5008,15 @@ var MemcachedLayer = class {
|
|
|
4677
5008
|
this.keyPrefix = options.keyPrefix ?? "";
|
|
4678
5009
|
this.serializer = options.serializer ?? new JsonSerializer();
|
|
4679
5010
|
}
|
|
5011
|
+
/**
|
|
5012
|
+
* Reads and unwraps a fresh value from Memcached.
|
|
5013
|
+
*/
|
|
4680
5014
|
async get(key) {
|
|
4681
5015
|
return unwrapStoredValue(await this.getEntry(key));
|
|
4682
5016
|
}
|
|
5017
|
+
/**
|
|
5018
|
+
* Reads the raw stored value or envelope from Memcached.
|
|
5019
|
+
*/
|
|
4683
5020
|
async getEntry(key) {
|
|
4684
5021
|
this.validateKey(key);
|
|
4685
5022
|
const result = await this.client.get(this.withPrefix(key));
|
|
@@ -4692,9 +5029,15 @@ var MemcachedLayer = class {
|
|
|
4692
5029
|
return null;
|
|
4693
5030
|
}
|
|
4694
5031
|
}
|
|
5032
|
+
/**
|
|
5033
|
+
* Reads many raw entries from Memcached.
|
|
5034
|
+
*/
|
|
4695
5035
|
async getMany(keys) {
|
|
4696
5036
|
return Promise.all(keys.map((key) => this.getEntry(key)));
|
|
4697
5037
|
}
|
|
5038
|
+
/**
|
|
5039
|
+
* Stores a value in Memcached using the provided TTL or layer default TTL.
|
|
5040
|
+
*/
|
|
4698
5041
|
async set(key, value, ttl = this.defaultTtl) {
|
|
4699
5042
|
this.validateKey(key);
|
|
4700
5043
|
const payload = this.serializer.serialize(value);
|
|
@@ -4702,18 +5045,30 @@ var MemcachedLayer = class {
|
|
|
4702
5045
|
expires: ttl && ttl > 0 ? Math.ceil(ttl / 1e3) : void 0
|
|
4703
5046
|
});
|
|
4704
5047
|
}
|
|
5048
|
+
/**
|
|
5049
|
+
* Returns true when the key exists in Memcached.
|
|
5050
|
+
*/
|
|
4705
5051
|
async has(key) {
|
|
4706
5052
|
this.validateKey(key);
|
|
4707
5053
|
const result = await this.client.get(this.withPrefix(key));
|
|
4708
5054
|
return result !== null && result.value !== null;
|
|
4709
5055
|
}
|
|
5056
|
+
/**
|
|
5057
|
+
* Deletes a key from Memcached.
|
|
5058
|
+
*/
|
|
4710
5059
|
async delete(key) {
|
|
4711
5060
|
this.validateKey(key);
|
|
4712
5061
|
await this.client.delete(this.withPrefix(key));
|
|
4713
5062
|
}
|
|
5063
|
+
/**
|
|
5064
|
+
* Deletes multiple keys from Memcached.
|
|
5065
|
+
*/
|
|
4714
5066
|
async deleteMany(keys) {
|
|
4715
5067
|
await Promise.all(keys.map((key) => this.delete(key)));
|
|
4716
5068
|
}
|
|
5069
|
+
/**
|
|
5070
|
+
* Always throws because Memcached has no safe prefix clear primitive.
|
|
5071
|
+
*/
|
|
4717
5072
|
async clear() {
|
|
4718
5073
|
throw new Error(
|
|
4719
5074
|
"MemcachedLayer.clear() is not supported. Use a key prefix and rotate it to effectively invalidate all keys."
|
|
@@ -4741,9 +5096,15 @@ var MemcachedLayer = class {
|
|
|
4741
5096
|
// src/serialization/MsgpackSerializer.ts
|
|
4742
5097
|
import { decode, encode } from "@msgpack/msgpack";
|
|
4743
5098
|
var MsgpackSerializer = class {
|
|
5099
|
+
/**
|
|
5100
|
+
* Serializes a value to MessagePack bytes.
|
|
5101
|
+
*/
|
|
4744
5102
|
serialize(value) {
|
|
4745
5103
|
return Buffer.from(encode(value));
|
|
4746
5104
|
}
|
|
5105
|
+
/**
|
|
5106
|
+
* Decodes MessagePack bytes and sanitizes the result before returning it.
|
|
5107
|
+
*/
|
|
4747
5108
|
deserialize(payload) {
|
|
4748
5109
|
const normalized = Buffer.isBuffer(payload) ? payload : Buffer.from(payload, "latin1");
|
|
4749
5110
|
return sanitizeStructuredData(decode(normalized), {
|
|
@@ -4777,6 +5138,10 @@ var RedisSingleFlightCoordinator = class {
|
|
|
4777
5138
|
this.prefix = options.prefix ?? "layercache:singleflight";
|
|
4778
5139
|
this.commandTimeoutMs = this.normalizeCommandTimeoutMs(options.commandTimeoutMs);
|
|
4779
5140
|
}
|
|
5141
|
+
/**
|
|
5142
|
+
* Executes `worker` when this process acquires the Redis lock; otherwise runs
|
|
5143
|
+
* `waiter` while another process owns the work.
|
|
5144
|
+
*/
|
|
4780
5145
|
async execute(key, options, worker, waiter) {
|
|
4781
5146
|
const lockKey = `${this.prefix}:${encodeURIComponent(key)}`;
|
|
4782
5147
|
const token = randomUUID();
|