layercache 1.2.4 → 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 +6 -0
- 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-Dw97n89L.d.cts → edge-P07GCO2Y.d.cts} +1 -0
- package/dist/{edge-Dw97n89L.d.ts → edge-P07GCO2Y.d.ts} +1 -0
- 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 +122 -30
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +96 -24
- package/package.json +1 -1
- package/packages/nestjs/dist/index.cjs +96 -23
- package/packages/nestjs/dist/index.d.cts +1 -0
- package/packages/nestjs/dist/index.d.ts +1 -0
- package/packages/nestjs/dist/index.js +96 -23
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,17 @@ 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
|
+
}
|
|
268
280
|
|
|
269
281
|
// src/internal/CacheKeyDiscovery.ts
|
|
270
282
|
var CacheKeyDiscovery = class {
|
|
@@ -337,9 +349,7 @@ var CircuitBreakerManager = class {
|
|
|
337
349
|
}
|
|
338
350
|
const now = Date.now();
|
|
339
351
|
if (state.openUntil <= now) {
|
|
340
|
-
|
|
341
|
-
state.failures = 0;
|
|
342
|
-
this.breakers.set(key, state);
|
|
352
|
+
this.breakers.delete(key);
|
|
343
353
|
return;
|
|
344
354
|
}
|
|
345
355
|
const remainingMs = state.openUntil - now;
|
|
@@ -350,15 +360,15 @@ var CircuitBreakerManager = class {
|
|
|
350
360
|
if (!options) {
|
|
351
361
|
return;
|
|
352
362
|
}
|
|
363
|
+
this.pruneIfNeeded();
|
|
353
364
|
const failureThreshold = options.failureThreshold ?? 3;
|
|
354
365
|
const cooldownMs = options.cooldownMs ?? 3e4;
|
|
355
|
-
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null };
|
|
366
|
+
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null, createdAt: Date.now() };
|
|
356
367
|
state.failures += 1;
|
|
357
368
|
if (state.failures >= failureThreshold) {
|
|
358
369
|
state.openUntil = Date.now() + cooldownMs;
|
|
359
370
|
}
|
|
360
371
|
this.breakers.set(key, state);
|
|
361
|
-
this.pruneIfNeeded();
|
|
362
372
|
}
|
|
363
373
|
recordSuccess(key) {
|
|
364
374
|
this.breakers.delete(key);
|
|
@@ -369,8 +379,7 @@ var CircuitBreakerManager = class {
|
|
|
369
379
|
return false;
|
|
370
380
|
}
|
|
371
381
|
if (state.openUntil <= Date.now()) {
|
|
372
|
-
|
|
373
|
-
state.failures = 0;
|
|
382
|
+
this.breakers.delete(key);
|
|
374
383
|
return false;
|
|
375
384
|
}
|
|
376
385
|
return true;
|
|
@@ -394,15 +403,20 @@ var CircuitBreakerManager = class {
|
|
|
394
403
|
if (this.breakers.size <= this.maxEntries) {
|
|
395
404
|
return;
|
|
396
405
|
}
|
|
406
|
+
const now = Date.now();
|
|
397
407
|
for (const [key, state] of this.breakers.entries()) {
|
|
398
408
|
if (this.breakers.size <= this.maxEntries) {
|
|
399
|
-
|
|
409
|
+
return;
|
|
400
410
|
}
|
|
401
|
-
if (!state.openUntil || state.openUntil <=
|
|
411
|
+
if (!state.openUntil || state.openUntil <= now) {
|
|
402
412
|
this.breakers.delete(key);
|
|
403
413
|
}
|
|
404
414
|
}
|
|
405
|
-
|
|
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) {
|
|
406
420
|
if (this.breakers.size <= this.maxEntries) {
|
|
407
421
|
break;
|
|
408
422
|
}
|
|
@@ -412,6 +426,7 @@ var CircuitBreakerManager = class {
|
|
|
412
426
|
};
|
|
413
427
|
|
|
414
428
|
// src/internal/FetchRateLimiter.ts
|
|
429
|
+
var MAX_BUCKETS = 1e4;
|
|
415
430
|
var FetchRateLimiter = class {
|
|
416
431
|
buckets = /* @__PURE__ */ new Map();
|
|
417
432
|
queuesByBucket = /* @__PURE__ */ new Map();
|
|
@@ -577,10 +592,25 @@ var FetchRateLimiter = class {
|
|
|
577
592
|
if (existing) {
|
|
578
593
|
return existing;
|
|
579
594
|
}
|
|
595
|
+
if (this.buckets.size >= MAX_BUCKETS) {
|
|
596
|
+
this.evictIdleBuckets();
|
|
597
|
+
}
|
|
580
598
|
const bucket = { active: 0, startedAt: [] };
|
|
581
599
|
this.buckets.set(bucketKey, bucket);
|
|
582
600
|
return bucket;
|
|
583
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
|
+
}
|
|
584
614
|
cleanupBucket(bucketKey, bucket, intervalMs) {
|
|
585
615
|
const queued = this.queuesByBucket.get(bucketKey)?.length ?? 0;
|
|
586
616
|
if (queued === 0 && bucket.active === 0 && bucket.startedAt.length === 0) {
|
|
@@ -783,13 +813,12 @@ var TtlResolver = class {
|
|
|
783
813
|
return;
|
|
784
814
|
}
|
|
785
815
|
const toRemove = Math.ceil(this.maxProfileEntries * 0.1);
|
|
786
|
-
|
|
787
|
-
for (
|
|
788
|
-
|
|
789
|
-
|
|
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]);
|
|
790
821
|
}
|
|
791
|
-
this.accessProfiles.delete(key);
|
|
792
|
-
removed += 1;
|
|
793
822
|
}
|
|
794
823
|
}
|
|
795
824
|
};
|
|
@@ -872,6 +901,7 @@ var DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS = 5e3;
|
|
|
872
901
|
var DEFAULT_SINGLE_FLIGHT_POLL_MS = 50;
|
|
873
902
|
var DEFAULT_BACKGROUND_REFRESH_TIMEOUT_MS = 3e4;
|
|
874
903
|
var MAX_CACHE_KEY_LENGTH = 1024;
|
|
904
|
+
var MAX_PATTERN_LENGTH = 1024;
|
|
875
905
|
var DEFAULT_MAX_PROFILE_ENTRIES = 1e5;
|
|
876
906
|
var DANGEROUS_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
877
907
|
var DebugLogger = class {
|
|
@@ -1305,6 +1335,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
1305
1335
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1306
1336
|
}
|
|
1307
1337
|
async invalidateByPattern(pattern) {
|
|
1338
|
+
this.validatePattern(pattern);
|
|
1308
1339
|
await this.awaitStartup("invalidateByPattern");
|
|
1309
1340
|
const keys = await this.keyDiscovery.collectKeysMatchingPattern(this.qualifyPattern(pattern));
|
|
1310
1341
|
await this.deleteKeys(keys);
|
|
@@ -2198,6 +2229,17 @@ var CacheStack = class extends EventEmitter {
|
|
|
2198
2229
|
}
|
|
2199
2230
|
return key;
|
|
2200
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
|
+
}
|
|
2201
2243
|
validateTtlPolicy(name, policy) {
|
|
2202
2244
|
if (!policy || typeof policy === "function" || policy === "until-midnight" || policy === "next-hour") {
|
|
2203
2245
|
return;
|
|
@@ -2362,7 +2404,18 @@ var CacheStack = class extends EventEmitter {
|
|
|
2362
2404
|
}
|
|
2363
2405
|
};
|
|
2364
2406
|
function createInstanceId() {
|
|
2365
|
-
|
|
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("")}`;
|
|
2366
2419
|
}
|
|
2367
2420
|
|
|
2368
2421
|
// src/invalidation/RedisInvalidationBus.ts
|
|
@@ -2404,7 +2457,7 @@ var RedisInvalidationBus = class {
|
|
|
2404
2457
|
async dispatchToHandlers(payload) {
|
|
2405
2458
|
let message;
|
|
2406
2459
|
try {
|
|
2407
|
-
const parsed = JSON.parse(payload);
|
|
2460
|
+
const parsed = sanitizeJsonValue2(JSON.parse(payload));
|
|
2408
2461
|
if (!this.isInvalidationMessage(parsed)) {
|
|
2409
2462
|
throw new Error("Invalid invalidation payload shape.");
|
|
2410
2463
|
}
|
|
@@ -2441,12 +2494,30 @@ var RedisInvalidationBus = class {
|
|
|
2441
2494
|
console.error(`[layercache] ${message}`, error);
|
|
2442
2495
|
}
|
|
2443
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
|
+
}
|
|
2444
2513
|
|
|
2445
2514
|
// src/http/createCacheStatsHandler.ts
|
|
2446
2515
|
function createCacheStatsHandler(cache) {
|
|
2447
2516
|
return async (_request, response) => {
|
|
2448
2517
|
response.statusCode = 200;
|
|
2449
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");
|
|
2450
2521
|
response.end(JSON.stringify(cache.getStats(), null, 2));
|
|
2451
2522
|
};
|
|
2452
2523
|
}
|
|
@@ -2532,7 +2603,7 @@ function normalizeUrl(url) {
|
|
|
2532
2603
|
try {
|
|
2533
2604
|
const parsed = new URL(url, "http://localhost");
|
|
2534
2605
|
parsed.searchParams.sort();
|
|
2535
|
-
return
|
|
2606
|
+
return parsed.pathname + parsed.search;
|
|
2536
2607
|
} catch {
|
|
2537
2608
|
return url;
|
|
2538
2609
|
}
|
|
@@ -2822,8 +2893,9 @@ var RedisLayer = class {
|
|
|
2822
2893
|
}
|
|
2823
2894
|
}
|
|
2824
2895
|
try {
|
|
2825
|
-
await this.client.del(this.withPrefix(key))
|
|
2826
|
-
} catch {
|
|
2896
|
+
await this.client.del(this.withPrefix(key));
|
|
2897
|
+
} catch (deleteError) {
|
|
2898
|
+
console.warn(`[layercache] RedisLayer: failed to delete corrupted key "${key}"`, deleteError);
|
|
2827
2899
|
}
|
|
2828
2900
|
return null;
|
|
2829
2901
|
}
|
|
@@ -3223,7 +3295,7 @@ var MemcachedLayer = class {
|
|
|
3223
3295
|
|
|
3224
3296
|
// src/serialization/MsgpackSerializer.ts
|
|
3225
3297
|
import { decode, encode } from "@msgpack/msgpack";
|
|
3226
|
-
var
|
|
3298
|
+
var DANGEROUS_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3227
3299
|
var MsgpackSerializer = class {
|
|
3228
3300
|
serialize(value) {
|
|
3229
3301
|
return Buffer.from(encode(value));
|
|
@@ -3242,7 +3314,7 @@ function sanitizeMsgpackValue(value) {
|
|
|
3242
3314
|
}
|
|
3243
3315
|
const sanitized = {};
|
|
3244
3316
|
for (const [key, entry] of Object.entries(value)) {
|
|
3245
|
-
if (
|
|
3317
|
+
if (DANGEROUS_KEYS2.has(key)) {
|
|
3246
3318
|
continue;
|
|
3247
3319
|
}
|
|
3248
3320
|
sanitized[key] = sanitizeMsgpackValue(entry);
|
package/package.json
CHANGED
|
@@ -374,6 +374,7 @@ var CacheNamespace = class _CacheNamespace {
|
|
|
374
374
|
* ```
|
|
375
375
|
*/
|
|
376
376
|
namespace(childPrefix) {
|
|
377
|
+
validateNamespaceKey(childPrefix);
|
|
377
378
|
return new _CacheNamespace(this.cache, `${this.prefix}:${childPrefix}`);
|
|
378
379
|
}
|
|
379
380
|
qualify(key) {
|
|
@@ -503,6 +504,17 @@ function addMap(base, delta) {
|
|
|
503
504
|
}
|
|
504
505
|
return result;
|
|
505
506
|
}
|
|
507
|
+
function validateNamespaceKey(key) {
|
|
508
|
+
if (key.length === 0) {
|
|
509
|
+
throw new Error("Namespace prefix must not be empty.");
|
|
510
|
+
}
|
|
511
|
+
if (key.length > 256) {
|
|
512
|
+
throw new Error("Namespace prefix must be at most 256 characters.");
|
|
513
|
+
}
|
|
514
|
+
if (/[\u0000-\u001F\u007F]/.test(key)) {
|
|
515
|
+
throw new Error("Namespace prefix contains unsupported control characters.");
|
|
516
|
+
}
|
|
517
|
+
}
|
|
506
518
|
|
|
507
519
|
// ../../src/invalidation/PatternMatcher.ts
|
|
508
520
|
var PatternMatcher = class _PatternMatcher {
|
|
@@ -623,9 +635,7 @@ var CircuitBreakerManager = class {
|
|
|
623
635
|
}
|
|
624
636
|
const now = Date.now();
|
|
625
637
|
if (state.openUntil <= now) {
|
|
626
|
-
|
|
627
|
-
state.failures = 0;
|
|
628
|
-
this.breakers.set(key, state);
|
|
638
|
+
this.breakers.delete(key);
|
|
629
639
|
return;
|
|
630
640
|
}
|
|
631
641
|
const remainingMs = state.openUntil - now;
|
|
@@ -636,15 +646,15 @@ var CircuitBreakerManager = class {
|
|
|
636
646
|
if (!options) {
|
|
637
647
|
return;
|
|
638
648
|
}
|
|
649
|
+
this.pruneIfNeeded();
|
|
639
650
|
const failureThreshold = options.failureThreshold ?? 3;
|
|
640
651
|
const cooldownMs = options.cooldownMs ?? 3e4;
|
|
641
|
-
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null };
|
|
652
|
+
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null, createdAt: Date.now() };
|
|
642
653
|
state.failures += 1;
|
|
643
654
|
if (state.failures >= failureThreshold) {
|
|
644
655
|
state.openUntil = Date.now() + cooldownMs;
|
|
645
656
|
}
|
|
646
657
|
this.breakers.set(key, state);
|
|
647
|
-
this.pruneIfNeeded();
|
|
648
658
|
}
|
|
649
659
|
recordSuccess(key) {
|
|
650
660
|
this.breakers.delete(key);
|
|
@@ -655,8 +665,7 @@ var CircuitBreakerManager = class {
|
|
|
655
665
|
return false;
|
|
656
666
|
}
|
|
657
667
|
if (state.openUntil <= Date.now()) {
|
|
658
|
-
|
|
659
|
-
state.failures = 0;
|
|
668
|
+
this.breakers.delete(key);
|
|
660
669
|
return false;
|
|
661
670
|
}
|
|
662
671
|
return true;
|
|
@@ -680,15 +689,20 @@ var CircuitBreakerManager = class {
|
|
|
680
689
|
if (this.breakers.size <= this.maxEntries) {
|
|
681
690
|
return;
|
|
682
691
|
}
|
|
692
|
+
const now = Date.now();
|
|
683
693
|
for (const [key, state] of this.breakers.entries()) {
|
|
684
694
|
if (this.breakers.size <= this.maxEntries) {
|
|
685
|
-
|
|
695
|
+
return;
|
|
686
696
|
}
|
|
687
|
-
if (!state.openUntil || state.openUntil <=
|
|
697
|
+
if (!state.openUntil || state.openUntil <= now) {
|
|
688
698
|
this.breakers.delete(key);
|
|
689
699
|
}
|
|
690
700
|
}
|
|
691
|
-
|
|
701
|
+
if (this.breakers.size <= this.maxEntries) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const sorted = [...this.breakers.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
705
|
+
for (const [key] of sorted) {
|
|
692
706
|
if (this.breakers.size <= this.maxEntries) {
|
|
693
707
|
break;
|
|
694
708
|
}
|
|
@@ -698,6 +712,7 @@ var CircuitBreakerManager = class {
|
|
|
698
712
|
};
|
|
699
713
|
|
|
700
714
|
// ../../src/internal/FetchRateLimiter.ts
|
|
715
|
+
var MAX_BUCKETS = 1e4;
|
|
701
716
|
var FetchRateLimiter = class {
|
|
702
717
|
buckets = /* @__PURE__ */ new Map();
|
|
703
718
|
queuesByBucket = /* @__PURE__ */ new Map();
|
|
@@ -863,10 +878,25 @@ var FetchRateLimiter = class {
|
|
|
863
878
|
if (existing) {
|
|
864
879
|
return existing;
|
|
865
880
|
}
|
|
881
|
+
if (this.buckets.size >= MAX_BUCKETS) {
|
|
882
|
+
this.evictIdleBuckets();
|
|
883
|
+
}
|
|
866
884
|
const bucket = { active: 0, startedAt: [] };
|
|
867
885
|
this.buckets.set(bucketKey, bucket);
|
|
868
886
|
return bucket;
|
|
869
887
|
}
|
|
888
|
+
evictIdleBuckets() {
|
|
889
|
+
for (const [key, bucket] of this.buckets.entries()) {
|
|
890
|
+
if (this.buckets.size <= MAX_BUCKETS * 0.9) {
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
if (bucket.active === 0 && bucket.startedAt.length === 0 && !this.queuesByBucket.has(key)) {
|
|
894
|
+
this.buckets.delete(key);
|
|
895
|
+
this.queuesByBucket.delete(key);
|
|
896
|
+
this.pendingBuckets.delete(key);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
870
900
|
cleanupBucket(bucketKey, bucket, intervalMs) {
|
|
871
901
|
const queued = this.queuesByBucket.get(bucketKey)?.length ?? 0;
|
|
872
902
|
if (queued === 0 && bucket.active === 0 && bucket.startedAt.length === 0) {
|
|
@@ -1193,18 +1223,18 @@ var TtlResolver = class {
|
|
|
1193
1223
|
return;
|
|
1194
1224
|
}
|
|
1195
1225
|
const toRemove = Math.ceil(this.maxProfileEntries * 0.1);
|
|
1196
|
-
|
|
1197
|
-
for (
|
|
1198
|
-
|
|
1199
|
-
|
|
1226
|
+
const sorted = [...this.accessProfiles.entries()].sort((a, b) => a[1].lastAccessAt - b[1].lastAccessAt);
|
|
1227
|
+
for (let i = 0; i < toRemove && i < sorted.length; i++) {
|
|
1228
|
+
const entry = sorted[i];
|
|
1229
|
+
if (entry) {
|
|
1230
|
+
this.accessProfiles.delete(entry[0]);
|
|
1200
1231
|
}
|
|
1201
|
-
this.accessProfiles.delete(key);
|
|
1202
|
-
removed += 1;
|
|
1203
1232
|
}
|
|
1204
1233
|
}
|
|
1205
1234
|
};
|
|
1206
1235
|
|
|
1207
1236
|
// ../../src/invalidation/TagIndex.ts
|
|
1237
|
+
var MAX_PATTERN_RECURSION_DEPTH = 500;
|
|
1208
1238
|
var TagIndex = class {
|
|
1209
1239
|
tagToKeys = /* @__PURE__ */ new Map();
|
|
1210
1240
|
keyToTags = /* @__PURE__ */ new Map();
|
|
@@ -1259,7 +1289,7 @@ var TagIndex = class {
|
|
|
1259
1289
|
}
|
|
1260
1290
|
async matchPattern(pattern) {
|
|
1261
1291
|
const matches = /* @__PURE__ */ new Set();
|
|
1262
|
-
this.collectPatternMatches(this.root, "", pattern, 0, matches, /* @__PURE__ */ new Set());
|
|
1292
|
+
this.collectPatternMatches(this.root, "", pattern, 0, matches, /* @__PURE__ */ new Set(), 0);
|
|
1263
1293
|
return [...matches];
|
|
1264
1294
|
}
|
|
1265
1295
|
async clear() {
|
|
@@ -1311,7 +1341,10 @@ var TagIndex = class {
|
|
|
1311
1341
|
this.collectFromNode(child, `${prefix}${character}`, matches);
|
|
1312
1342
|
}
|
|
1313
1343
|
}
|
|
1314
|
-
collectPatternMatches(node, prefix, pattern, patternIndex, matches, visited) {
|
|
1344
|
+
collectPatternMatches(node, prefix, pattern, patternIndex, matches, visited, depth) {
|
|
1345
|
+
if (depth > MAX_PATTERN_RECURSION_DEPTH) {
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1315
1348
|
const stateKey = `${node.id}:${patternIndex}`;
|
|
1316
1349
|
if (visited.has(stateKey)) {
|
|
1317
1350
|
return;
|
|
@@ -1328,21 +1361,37 @@ var TagIndex = class {
|
|
|
1328
1361
|
return;
|
|
1329
1362
|
}
|
|
1330
1363
|
if (patternChar === "*") {
|
|
1331
|
-
this.collectPatternMatches(node, prefix, pattern, patternIndex + 1, matches, visited);
|
|
1364
|
+
this.collectPatternMatches(node, prefix, pattern, patternIndex + 1, matches, visited, depth + 1);
|
|
1332
1365
|
for (const [character, child2] of node.children) {
|
|
1333
|
-
this.collectPatternMatches(child2, `${prefix}${character}`, pattern, patternIndex, matches, visited);
|
|
1366
|
+
this.collectPatternMatches(child2, `${prefix}${character}`, pattern, patternIndex, matches, visited, depth + 1);
|
|
1334
1367
|
}
|
|
1335
1368
|
return;
|
|
1336
1369
|
}
|
|
1337
1370
|
if (patternChar === "?") {
|
|
1338
1371
|
for (const [character, child2] of node.children) {
|
|
1339
|
-
this.collectPatternMatches(
|
|
1372
|
+
this.collectPatternMatches(
|
|
1373
|
+
child2,
|
|
1374
|
+
`${prefix}${character}`,
|
|
1375
|
+
pattern,
|
|
1376
|
+
patternIndex + 1,
|
|
1377
|
+
matches,
|
|
1378
|
+
visited,
|
|
1379
|
+
depth + 1
|
|
1380
|
+
);
|
|
1340
1381
|
}
|
|
1341
1382
|
return;
|
|
1342
1383
|
}
|
|
1343
1384
|
const child = node.children.get(patternChar);
|
|
1344
1385
|
if (child) {
|
|
1345
|
-
this.collectPatternMatches(
|
|
1386
|
+
this.collectPatternMatches(
|
|
1387
|
+
child,
|
|
1388
|
+
`${prefix}${patternChar}`,
|
|
1389
|
+
pattern,
|
|
1390
|
+
patternIndex + 1,
|
|
1391
|
+
matches,
|
|
1392
|
+
visited,
|
|
1393
|
+
depth + 1
|
|
1394
|
+
);
|
|
1346
1395
|
}
|
|
1347
1396
|
}
|
|
1348
1397
|
pruneKnownKeysIfNeeded() {
|
|
@@ -1484,6 +1533,7 @@ var DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS = 5e3;
|
|
|
1484
1533
|
var DEFAULT_SINGLE_FLIGHT_POLL_MS = 50;
|
|
1485
1534
|
var DEFAULT_BACKGROUND_REFRESH_TIMEOUT_MS = 3e4;
|
|
1486
1535
|
var MAX_CACHE_KEY_LENGTH = 1024;
|
|
1536
|
+
var MAX_PATTERN_LENGTH = 1024;
|
|
1487
1537
|
var DEFAULT_MAX_PROFILE_ENTRIES = 1e5;
|
|
1488
1538
|
var DANGEROUS_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
1489
1539
|
var DebugLogger = class {
|
|
@@ -1917,6 +1967,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1917
1967
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1918
1968
|
}
|
|
1919
1969
|
async invalidateByPattern(pattern) {
|
|
1970
|
+
this.validatePattern(pattern);
|
|
1920
1971
|
await this.awaitStartup("invalidateByPattern");
|
|
1921
1972
|
const keys = await this.keyDiscovery.collectKeysMatchingPattern(this.qualifyPattern(pattern));
|
|
1922
1973
|
await this.deleteKeys(keys);
|
|
@@ -2810,6 +2861,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2810
2861
|
}
|
|
2811
2862
|
return key;
|
|
2812
2863
|
}
|
|
2864
|
+
validatePattern(pattern) {
|
|
2865
|
+
if (pattern.length === 0) {
|
|
2866
|
+
throw new Error("Pattern must not be empty.");
|
|
2867
|
+
}
|
|
2868
|
+
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
2869
|
+
throw new Error(`Pattern length must be at most ${MAX_PATTERN_LENGTH} characters.`);
|
|
2870
|
+
}
|
|
2871
|
+
if (/[\u0000-\u001F\u007F]/.test(pattern)) {
|
|
2872
|
+
throw new Error("Pattern contains unsupported control characters.");
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2813
2875
|
validateTtlPolicy(name, policy) {
|
|
2814
2876
|
if (!policy || typeof policy === "function" || policy === "until-midnight" || policy === "next-hour") {
|
|
2815
2877
|
return;
|
|
@@ -2974,7 +3036,18 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2974
3036
|
}
|
|
2975
3037
|
};
|
|
2976
3038
|
function createInstanceId() {
|
|
2977
|
-
|
|
3039
|
+
if (globalThis.crypto?.randomUUID) {
|
|
3040
|
+
return globalThis.crypto.randomUUID();
|
|
3041
|
+
}
|
|
3042
|
+
const bytes = new Uint8Array(16);
|
|
3043
|
+
if (globalThis.crypto?.getRandomValues) {
|
|
3044
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
3045
|
+
} else {
|
|
3046
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
3047
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
return `layercache-${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
2978
3051
|
}
|
|
2979
3052
|
|
|
2980
3053
|
// src/module.ts
|
|
@@ -555,6 +555,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
555
555
|
private validateRateLimitOptions;
|
|
556
556
|
private validateNonNegativeNumber;
|
|
557
557
|
private validateCacheKey;
|
|
558
|
+
private validatePattern;
|
|
558
559
|
private validateTtlPolicy;
|
|
559
560
|
private assertActive;
|
|
560
561
|
private awaitStartup;
|
|
@@ -555,6 +555,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
555
555
|
private validateRateLimitOptions;
|
|
556
556
|
private validateNonNegativeNumber;
|
|
557
557
|
private validateCacheKey;
|
|
558
|
+
private validatePattern;
|
|
558
559
|
private validateTtlPolicy;
|
|
559
560
|
private assertActive;
|
|
560
561
|
private awaitStartup;
|