layercache 1.2.3 → 1.2.5
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 +101 -84
- package/dist/{chunk-KOYGHLVP.js → chunk-JC26W3KK.js} +27 -7
- package/dist/cli.cjs +2 -16
- package/dist/cli.js +2 -16
- package/dist/{edge-B_rUqDy6.d.cts → edge-P07GCO2Y.d.cts} +2 -2
- package/dist/{edge-B_rUqDy6.d.ts → edge-P07GCO2Y.d.ts} +2 -2
- package/dist/edge.cjs +27 -7
- package/dist/edge.d.cts +1 -1
- package/dist/edge.d.ts +1 -1
- package/dist/edge.js +1 -1
- package/dist/index.cjs +235 -125
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +161 -71
- package/package.json +1 -1
- package/packages/nestjs/dist/index.cjs +209 -118
- package/packages/nestjs/dist/index.d.cts +2 -2
- package/packages/nestjs/dist/index.d.ts +2 -2
- package/packages/nestjs/dist/index.js +209 -118
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
MemoryLayer,
|
|
6
6
|
TagIndex,
|
|
7
7
|
createHonoCacheMiddleware
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-JC26W3KK.js";
|
|
9
9
|
import {
|
|
10
10
|
PatternMatcher,
|
|
11
11
|
createStoredValueEnvelope,
|
|
@@ -136,6 +136,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
136
136
|
* ```
|
|
137
137
|
*/
|
|
138
138
|
namespace(childPrefix) {
|
|
139
|
+
validateNamespaceKey(childPrefix);
|
|
139
140
|
return new _CacheNamespace(this.cache, `${this.prefix}:${childPrefix}`);
|
|
140
141
|
}
|
|
141
142
|
qualify(key) {
|
|
@@ -265,6 +266,70 @@ function addMap(base, delta) {
|
|
|
265
266
|
}
|
|
266
267
|
return result;
|
|
267
268
|
}
|
|
269
|
+
function validateNamespaceKey(key) {
|
|
270
|
+
if (key.length === 0) {
|
|
271
|
+
throw new Error("Namespace prefix must not be empty.");
|
|
272
|
+
}
|
|
273
|
+
if (key.length > 256) {
|
|
274
|
+
throw new Error("Namespace prefix must be at most 256 characters.");
|
|
275
|
+
}
|
|
276
|
+
if (/[\u0000-\u001F\u007F]/.test(key)) {
|
|
277
|
+
throw new Error("Namespace prefix contains unsupported control characters.");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/internal/CacheKeyDiscovery.ts
|
|
282
|
+
var CacheKeyDiscovery = class {
|
|
283
|
+
constructor(options) {
|
|
284
|
+
this.options = options;
|
|
285
|
+
}
|
|
286
|
+
options;
|
|
287
|
+
async collectKeysWithPrefix(prefix) {
|
|
288
|
+
const { tagIndex } = this.options;
|
|
289
|
+
const matches = new Set(
|
|
290
|
+
tagIndex.keysForPrefix ? await tagIndex.keysForPrefix(prefix) : await tagIndex.matchPattern(`${prefix}*`)
|
|
291
|
+
);
|
|
292
|
+
await Promise.all(
|
|
293
|
+
this.options.layers.map(async (layer) => {
|
|
294
|
+
if (!layer.keys || this.options.shouldSkipLayer(layer)) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const keys = await layer.keys();
|
|
299
|
+
for (const key of keys) {
|
|
300
|
+
if (key.startsWith(prefix)) {
|
|
301
|
+
matches.add(key);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
await this.options.handleLayerFailure(layer, "invalidate-prefix-scan", error);
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
);
|
|
309
|
+
return [...matches];
|
|
310
|
+
}
|
|
311
|
+
async collectKeysMatchingPattern(pattern) {
|
|
312
|
+
const matches = new Set(await this.options.tagIndex.matchPattern(pattern));
|
|
313
|
+
await Promise.all(
|
|
314
|
+
this.options.layers.map(async (layer) => {
|
|
315
|
+
if (!layer.keys || this.options.shouldSkipLayer(layer)) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const keys = await layer.keys();
|
|
320
|
+
for (const key of keys) {
|
|
321
|
+
if (PatternMatcher.matches(pattern, key)) {
|
|
322
|
+
matches.add(key);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} catch (error) {
|
|
326
|
+
await this.options.handleLayerFailure(layer, "invalidate-pattern-scan", error);
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
);
|
|
330
|
+
return [...matches];
|
|
331
|
+
}
|
|
332
|
+
};
|
|
268
333
|
|
|
269
334
|
// src/internal/CircuitBreakerManager.ts
|
|
270
335
|
var CircuitBreakerManager = class {
|
|
@@ -284,9 +349,7 @@ var CircuitBreakerManager = class {
|
|
|
284
349
|
}
|
|
285
350
|
const now = Date.now();
|
|
286
351
|
if (state.openUntil <= now) {
|
|
287
|
-
|
|
288
|
-
state.failures = 0;
|
|
289
|
-
this.breakers.set(key, state);
|
|
352
|
+
this.breakers.delete(key);
|
|
290
353
|
return;
|
|
291
354
|
}
|
|
292
355
|
const remainingMs = state.openUntil - now;
|
|
@@ -297,15 +360,15 @@ var CircuitBreakerManager = class {
|
|
|
297
360
|
if (!options) {
|
|
298
361
|
return;
|
|
299
362
|
}
|
|
363
|
+
this.pruneIfNeeded();
|
|
300
364
|
const failureThreshold = options.failureThreshold ?? 3;
|
|
301
365
|
const cooldownMs = options.cooldownMs ?? 3e4;
|
|
302
|
-
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null };
|
|
366
|
+
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null, createdAt: Date.now() };
|
|
303
367
|
state.failures += 1;
|
|
304
368
|
if (state.failures >= failureThreshold) {
|
|
305
369
|
state.openUntil = Date.now() + cooldownMs;
|
|
306
370
|
}
|
|
307
371
|
this.breakers.set(key, state);
|
|
308
|
-
this.pruneIfNeeded();
|
|
309
372
|
}
|
|
310
373
|
recordSuccess(key) {
|
|
311
374
|
this.breakers.delete(key);
|
|
@@ -316,8 +379,7 @@ var CircuitBreakerManager = class {
|
|
|
316
379
|
return false;
|
|
317
380
|
}
|
|
318
381
|
if (state.openUntil <= Date.now()) {
|
|
319
|
-
|
|
320
|
-
state.failures = 0;
|
|
382
|
+
this.breakers.delete(key);
|
|
321
383
|
return false;
|
|
322
384
|
}
|
|
323
385
|
return true;
|
|
@@ -341,15 +403,20 @@ var CircuitBreakerManager = class {
|
|
|
341
403
|
if (this.breakers.size <= this.maxEntries) {
|
|
342
404
|
return;
|
|
343
405
|
}
|
|
406
|
+
const now = Date.now();
|
|
344
407
|
for (const [key, state] of this.breakers.entries()) {
|
|
345
408
|
if (this.breakers.size <= this.maxEntries) {
|
|
346
|
-
|
|
409
|
+
return;
|
|
347
410
|
}
|
|
348
|
-
if (!state.openUntil || state.openUntil <=
|
|
411
|
+
if (!state.openUntil || state.openUntil <= now) {
|
|
349
412
|
this.breakers.delete(key);
|
|
350
413
|
}
|
|
351
414
|
}
|
|
352
|
-
|
|
415
|
+
if (this.breakers.size <= this.maxEntries) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const sorted = [...this.breakers.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
419
|
+
for (const [key] of sorted) {
|
|
353
420
|
if (this.breakers.size <= this.maxEntries) {
|
|
354
421
|
break;
|
|
355
422
|
}
|
|
@@ -359,6 +426,7 @@ var CircuitBreakerManager = class {
|
|
|
359
426
|
};
|
|
360
427
|
|
|
361
428
|
// src/internal/FetchRateLimiter.ts
|
|
429
|
+
var MAX_BUCKETS = 1e4;
|
|
362
430
|
var FetchRateLimiter = class {
|
|
363
431
|
buckets = /* @__PURE__ */ new Map();
|
|
364
432
|
queuesByBucket = /* @__PURE__ */ new Map();
|
|
@@ -524,10 +592,25 @@ var FetchRateLimiter = class {
|
|
|
524
592
|
if (existing) {
|
|
525
593
|
return existing;
|
|
526
594
|
}
|
|
595
|
+
if (this.buckets.size >= MAX_BUCKETS) {
|
|
596
|
+
this.evictIdleBuckets();
|
|
597
|
+
}
|
|
527
598
|
const bucket = { active: 0, startedAt: [] };
|
|
528
599
|
this.buckets.set(bucketKey, bucket);
|
|
529
600
|
return bucket;
|
|
530
601
|
}
|
|
602
|
+
evictIdleBuckets() {
|
|
603
|
+
for (const [key, bucket] of this.buckets.entries()) {
|
|
604
|
+
if (this.buckets.size <= MAX_BUCKETS * 0.9) {
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
if (bucket.active === 0 && bucket.startedAt.length === 0 && !this.queuesByBucket.has(key)) {
|
|
608
|
+
this.buckets.delete(key);
|
|
609
|
+
this.queuesByBucket.delete(key);
|
|
610
|
+
this.pendingBuckets.delete(key);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
531
614
|
cleanupBucket(bucketKey, bucket, intervalMs) {
|
|
532
615
|
const queued = this.queuesByBucket.get(bucketKey)?.length ?? 0;
|
|
533
616
|
if (queued === 0 && bucket.active === 0 && bucket.startedAt.length === 0) {
|
|
@@ -730,13 +813,12 @@ var TtlResolver = class {
|
|
|
730
813
|
return;
|
|
731
814
|
}
|
|
732
815
|
const toRemove = Math.ceil(this.maxProfileEntries * 0.1);
|
|
733
|
-
|
|
734
|
-
for (
|
|
735
|
-
|
|
736
|
-
|
|
816
|
+
const sorted = [...this.accessProfiles.entries()].sort((a, b) => a[1].lastAccessAt - b[1].lastAccessAt);
|
|
817
|
+
for (let i = 0; i < toRemove && i < sorted.length; i++) {
|
|
818
|
+
const entry = sorted[i];
|
|
819
|
+
if (entry) {
|
|
820
|
+
this.accessProfiles.delete(entry[0]);
|
|
737
821
|
}
|
|
738
|
-
this.accessProfiles.delete(key);
|
|
739
|
-
removed += 1;
|
|
740
822
|
}
|
|
741
823
|
}
|
|
742
824
|
};
|
|
@@ -819,6 +901,7 @@ var DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS = 5e3;
|
|
|
819
901
|
var DEFAULT_SINGLE_FLIGHT_POLL_MS = 50;
|
|
820
902
|
var DEFAULT_BACKGROUND_REFRESH_TIMEOUT_MS = 3e4;
|
|
821
903
|
var MAX_CACHE_KEY_LENGTH = 1024;
|
|
904
|
+
var MAX_PATTERN_LENGTH = 1024;
|
|
822
905
|
var DEFAULT_MAX_PROFILE_ENTRIES = 1e5;
|
|
823
906
|
var DANGEROUS_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
824
907
|
var DebugLogger = class {
|
|
@@ -867,6 +950,14 @@ var CacheStack = class extends EventEmitter {
|
|
|
867
950
|
const debugEnv = process.env.DEBUG?.split(",").includes("layercache:debug") ?? false;
|
|
868
951
|
this.logger = typeof options.logger === "object" ? options.logger : new DebugLogger(Boolean(options.logger) || debugEnv);
|
|
869
952
|
this.tagIndex = options.tagIndex ?? new TagIndex();
|
|
953
|
+
this.keyDiscovery = new CacheKeyDiscovery({
|
|
954
|
+
layers: this.layers,
|
|
955
|
+
tagIndex: this.tagIndex,
|
|
956
|
+
shouldSkipLayer: (layer) => this.shouldSkipLayer(layer),
|
|
957
|
+
handleLayerFailure: async (layer, operation, error) => {
|
|
958
|
+
await this.handleLayerFailure(layer, operation, error);
|
|
959
|
+
}
|
|
960
|
+
});
|
|
870
961
|
if (!options.tagIndex && layers.some((layer) => layer.isLocal === false)) {
|
|
871
962
|
this.logger.warn?.(
|
|
872
963
|
"Using the default in-memory TagIndex with a shared cache layer only tracks keys seen by this process. Use RedisTagIndex for cross-instance tag invalidation."
|
|
@@ -894,6 +985,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
894
985
|
unsubscribeInvalidation;
|
|
895
986
|
logger;
|
|
896
987
|
tagIndex;
|
|
988
|
+
keyDiscovery;
|
|
897
989
|
fetchRateLimiter = new FetchRateLimiter();
|
|
898
990
|
snapshotSerializer = new JsonSerializer();
|
|
899
991
|
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
@@ -1243,15 +1335,16 @@ var CacheStack = class extends EventEmitter {
|
|
|
1243
1335
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1244
1336
|
}
|
|
1245
1337
|
async invalidateByPattern(pattern) {
|
|
1338
|
+
this.validatePattern(pattern);
|
|
1246
1339
|
await this.awaitStartup("invalidateByPattern");
|
|
1247
|
-
const keys = await this.collectKeysMatchingPattern(this.qualifyPattern(pattern));
|
|
1340
|
+
const keys = await this.keyDiscovery.collectKeysMatchingPattern(this.qualifyPattern(pattern));
|
|
1248
1341
|
await this.deleteKeys(keys);
|
|
1249
1342
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1250
1343
|
}
|
|
1251
1344
|
async invalidateByPrefix(prefix) {
|
|
1252
1345
|
await this.awaitStartup("invalidateByPrefix");
|
|
1253
1346
|
const qualifiedPrefix = this.qualifyKey(this.validateCacheKey(prefix));
|
|
1254
|
-
const keys = await this.collectKeysWithPrefix(qualifiedPrefix);
|
|
1347
|
+
const keys = await this.keyDiscovery.collectKeysWithPrefix(qualifiedPrefix);
|
|
1255
1348
|
await this.deleteKeys(keys);
|
|
1256
1349
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1257
1350
|
}
|
|
@@ -1864,50 +1957,6 @@ var CacheStack = class extends EventEmitter {
|
|
|
1864
1957
|
shouldBroadcastL1Invalidation() {
|
|
1865
1958
|
return this.options.broadcastL1Invalidation ?? this.options.publishSetInvalidation ?? false;
|
|
1866
1959
|
}
|
|
1867
|
-
async collectKeysWithPrefix(prefix) {
|
|
1868
|
-
const matches = new Set(
|
|
1869
|
-
this.tagIndex.keysForPrefix ? await this.tagIndex.keysForPrefix(prefix) : await this.tagIndex.matchPattern(`${prefix}*`)
|
|
1870
|
-
);
|
|
1871
|
-
await Promise.all(
|
|
1872
|
-
this.layers.map(async (layer) => {
|
|
1873
|
-
if (!layer.keys || this.shouldSkipLayer(layer)) {
|
|
1874
|
-
return;
|
|
1875
|
-
}
|
|
1876
|
-
try {
|
|
1877
|
-
const keys = await layer.keys();
|
|
1878
|
-
for (const key of keys) {
|
|
1879
|
-
if (key.startsWith(prefix)) {
|
|
1880
|
-
matches.add(key);
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
} catch (error) {
|
|
1884
|
-
await this.handleLayerFailure(layer, "invalidate-prefix-scan", error);
|
|
1885
|
-
}
|
|
1886
|
-
})
|
|
1887
|
-
);
|
|
1888
|
-
return [...matches];
|
|
1889
|
-
}
|
|
1890
|
-
async collectKeysMatchingPattern(pattern) {
|
|
1891
|
-
const matches = new Set(await this.tagIndex.matchPattern(pattern));
|
|
1892
|
-
await Promise.all(
|
|
1893
|
-
this.layers.map(async (layer) => {
|
|
1894
|
-
if (!layer.keys || this.shouldSkipLayer(layer)) {
|
|
1895
|
-
return;
|
|
1896
|
-
}
|
|
1897
|
-
try {
|
|
1898
|
-
const keys = await layer.keys();
|
|
1899
|
-
for (const key of keys) {
|
|
1900
|
-
if (PatternMatcher.matches(pattern, key)) {
|
|
1901
|
-
matches.add(key);
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
} catch (error) {
|
|
1905
|
-
await this.handleLayerFailure(layer, "invalidate-pattern-scan", error);
|
|
1906
|
-
}
|
|
1907
|
-
})
|
|
1908
|
-
);
|
|
1909
|
-
return [...matches];
|
|
1910
|
-
}
|
|
1911
1960
|
shouldCleanupGenerations() {
|
|
1912
1961
|
return Boolean(this.options.generationCleanup);
|
|
1913
1962
|
}
|
|
@@ -1930,7 +1979,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
1930
1979
|
}
|
|
1931
1980
|
async cleanupGeneration(generation) {
|
|
1932
1981
|
const prefix = `v${generation}:`;
|
|
1933
|
-
const keys = await this.collectKeysWithPrefix(prefix);
|
|
1982
|
+
const keys = await this.keyDiscovery.collectKeysWithPrefix(prefix);
|
|
1934
1983
|
if (keys.length === 0) {
|
|
1935
1984
|
return;
|
|
1936
1985
|
}
|
|
@@ -2180,6 +2229,17 @@ var CacheStack = class extends EventEmitter {
|
|
|
2180
2229
|
}
|
|
2181
2230
|
return key;
|
|
2182
2231
|
}
|
|
2232
|
+
validatePattern(pattern) {
|
|
2233
|
+
if (pattern.length === 0) {
|
|
2234
|
+
throw new Error("Pattern must not be empty.");
|
|
2235
|
+
}
|
|
2236
|
+
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
2237
|
+
throw new Error(`Pattern length must be at most ${MAX_PATTERN_LENGTH} characters.`);
|
|
2238
|
+
}
|
|
2239
|
+
if (/[\u0000-\u001F\u007F]/.test(pattern)) {
|
|
2240
|
+
throw new Error("Pattern contains unsupported control characters.");
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2183
2243
|
validateTtlPolicy(name, policy) {
|
|
2184
2244
|
if (!policy || typeof policy === "function" || policy === "until-midnight" || policy === "next-hour") {
|
|
2185
2245
|
return;
|
|
@@ -2344,7 +2404,18 @@ var CacheStack = class extends EventEmitter {
|
|
|
2344
2404
|
}
|
|
2345
2405
|
};
|
|
2346
2406
|
function createInstanceId() {
|
|
2347
|
-
|
|
2407
|
+
if (globalThis.crypto?.randomUUID) {
|
|
2408
|
+
return globalThis.crypto.randomUUID();
|
|
2409
|
+
}
|
|
2410
|
+
const bytes = new Uint8Array(16);
|
|
2411
|
+
if (globalThis.crypto?.getRandomValues) {
|
|
2412
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2413
|
+
} else {
|
|
2414
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2415
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
return `layercache-${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
2348
2419
|
}
|
|
2349
2420
|
|
|
2350
2421
|
// src/invalidation/RedisInvalidationBus.ts
|
|
@@ -2386,7 +2457,7 @@ var RedisInvalidationBus = class {
|
|
|
2386
2457
|
async dispatchToHandlers(payload) {
|
|
2387
2458
|
let message;
|
|
2388
2459
|
try {
|
|
2389
|
-
const parsed = JSON.parse(payload);
|
|
2460
|
+
const parsed = sanitizeJsonValue2(JSON.parse(payload));
|
|
2390
2461
|
if (!this.isInvalidationMessage(parsed)) {
|
|
2391
2462
|
throw new Error("Invalid invalidation payload shape.");
|
|
2392
2463
|
}
|
|
@@ -2423,12 +2494,30 @@ var RedisInvalidationBus = class {
|
|
|
2423
2494
|
console.error(`[layercache] ${message}`, error);
|
|
2424
2495
|
}
|
|
2425
2496
|
};
|
|
2497
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
2498
|
+
function sanitizeJsonValue2(value) {
|
|
2499
|
+
if (Array.isArray(value)) {
|
|
2500
|
+
return value.map(sanitizeJsonValue2);
|
|
2501
|
+
}
|
|
2502
|
+
if (value && typeof value === "object") {
|
|
2503
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
2504
|
+
for (const key of Object.keys(value)) {
|
|
2505
|
+
if (!DANGEROUS_KEYS.has(key)) {
|
|
2506
|
+
result[key] = sanitizeJsonValue2(value[key]);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
return result;
|
|
2510
|
+
}
|
|
2511
|
+
return value;
|
|
2512
|
+
}
|
|
2426
2513
|
|
|
2427
2514
|
// src/http/createCacheStatsHandler.ts
|
|
2428
2515
|
function createCacheStatsHandler(cache) {
|
|
2429
2516
|
return async (_request, response) => {
|
|
2430
2517
|
response.statusCode = 200;
|
|
2431
2518
|
response.setHeader?.("content-type", "application/json; charset=utf-8");
|
|
2519
|
+
response.setHeader?.("cache-control", "no-store");
|
|
2520
|
+
response.setHeader?.("x-content-type-options", "nosniff");
|
|
2432
2521
|
response.end(JSON.stringify(cache.getStats(), null, 2));
|
|
2433
2522
|
};
|
|
2434
2523
|
}
|
|
@@ -2514,7 +2603,7 @@ function normalizeUrl(url) {
|
|
|
2514
2603
|
try {
|
|
2515
2604
|
const parsed = new URL(url, "http://localhost");
|
|
2516
2605
|
parsed.searchParams.sort();
|
|
2517
|
-
return
|
|
2606
|
+
return parsed.pathname + parsed.search;
|
|
2518
2607
|
} catch {
|
|
2519
2608
|
return url;
|
|
2520
2609
|
}
|
|
@@ -2804,8 +2893,9 @@ var RedisLayer = class {
|
|
|
2804
2893
|
}
|
|
2805
2894
|
}
|
|
2806
2895
|
try {
|
|
2807
|
-
await this.client.del(this.withPrefix(key))
|
|
2808
|
-
} catch {
|
|
2896
|
+
await this.client.del(this.withPrefix(key));
|
|
2897
|
+
} catch (deleteError) {
|
|
2898
|
+
console.warn(`[layercache] RedisLayer: failed to delete corrupted key "${key}"`, deleteError);
|
|
2809
2899
|
}
|
|
2810
2900
|
return null;
|
|
2811
2901
|
}
|
|
@@ -3205,7 +3295,7 @@ var MemcachedLayer = class {
|
|
|
3205
3295
|
|
|
3206
3296
|
// src/serialization/MsgpackSerializer.ts
|
|
3207
3297
|
import { decode, encode } from "@msgpack/msgpack";
|
|
3208
|
-
var
|
|
3298
|
+
var DANGEROUS_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3209
3299
|
var MsgpackSerializer = class {
|
|
3210
3300
|
serialize(value) {
|
|
3211
3301
|
return Buffer.from(encode(value));
|
|
@@ -3224,7 +3314,7 @@ function sanitizeMsgpackValue(value) {
|
|
|
3224
3314
|
}
|
|
3225
3315
|
const sanitized = {};
|
|
3226
3316
|
for (const [key, entry] of Object.entries(value)) {
|
|
3227
|
-
if (
|
|
3317
|
+
if (DANGEROUS_KEYS2.has(key)) {
|
|
3228
3318
|
continue;
|
|
3229
3319
|
}
|
|
3230
3320
|
sanitized[key] = sanitizeMsgpackValue(entry);
|
package/package.json
CHANGED