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
|
@@ -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,118 @@ 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
|
+
}
|
|
518
|
+
|
|
519
|
+
// ../../src/invalidation/PatternMatcher.ts
|
|
520
|
+
var PatternMatcher = class _PatternMatcher {
|
|
521
|
+
/**
|
|
522
|
+
* Tests whether a glob-style pattern matches a value.
|
|
523
|
+
* Supports `*` (any sequence of characters) and `?` (any single character).
|
|
524
|
+
* Uses a two-pointer algorithm to avoid ReDoS vulnerabilities and
|
|
525
|
+
* quadratic memory usage on long patterns/keys.
|
|
526
|
+
*/
|
|
527
|
+
static matches(pattern, value) {
|
|
528
|
+
return _PatternMatcher.matchLinear(pattern, value);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Linear-time glob matching with O(1) extra memory.
|
|
532
|
+
*/
|
|
533
|
+
static matchLinear(pattern, value) {
|
|
534
|
+
let patternIndex = 0;
|
|
535
|
+
let valueIndex = 0;
|
|
536
|
+
let starIndex = -1;
|
|
537
|
+
let backtrackValueIndex = 0;
|
|
538
|
+
while (valueIndex < value.length) {
|
|
539
|
+
const patternChar = pattern[patternIndex];
|
|
540
|
+
const valueChar = value[valueIndex];
|
|
541
|
+
if (patternChar === "*" && patternIndex < pattern.length) {
|
|
542
|
+
starIndex = patternIndex;
|
|
543
|
+
patternIndex += 1;
|
|
544
|
+
backtrackValueIndex = valueIndex;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (patternChar === "?" || patternChar === valueChar) {
|
|
548
|
+
patternIndex += 1;
|
|
549
|
+
valueIndex += 1;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (starIndex !== -1) {
|
|
553
|
+
patternIndex = starIndex + 1;
|
|
554
|
+
backtrackValueIndex += 1;
|
|
555
|
+
valueIndex = backtrackValueIndex;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
while (patternIndex < pattern.length && pattern[patternIndex] === "*") {
|
|
561
|
+
patternIndex += 1;
|
|
562
|
+
}
|
|
563
|
+
return patternIndex === pattern.length;
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
// ../../src/internal/CacheKeyDiscovery.ts
|
|
568
|
+
var CacheKeyDiscovery = class {
|
|
569
|
+
constructor(options) {
|
|
570
|
+
this.options = options;
|
|
571
|
+
}
|
|
572
|
+
options;
|
|
573
|
+
async collectKeysWithPrefix(prefix) {
|
|
574
|
+
const { tagIndex } = this.options;
|
|
575
|
+
const matches = new Set(
|
|
576
|
+
tagIndex.keysForPrefix ? await tagIndex.keysForPrefix(prefix) : await tagIndex.matchPattern(`${prefix}*`)
|
|
577
|
+
);
|
|
578
|
+
await Promise.all(
|
|
579
|
+
this.options.layers.map(async (layer) => {
|
|
580
|
+
if (!layer.keys || this.options.shouldSkipLayer(layer)) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
try {
|
|
584
|
+
const keys = await layer.keys();
|
|
585
|
+
for (const key of keys) {
|
|
586
|
+
if (key.startsWith(prefix)) {
|
|
587
|
+
matches.add(key);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} catch (error) {
|
|
591
|
+
await this.options.handleLayerFailure(layer, "invalidate-prefix-scan", error);
|
|
592
|
+
}
|
|
593
|
+
})
|
|
594
|
+
);
|
|
595
|
+
return [...matches];
|
|
596
|
+
}
|
|
597
|
+
async collectKeysMatchingPattern(pattern) {
|
|
598
|
+
const matches = new Set(await this.options.tagIndex.matchPattern(pattern));
|
|
599
|
+
await Promise.all(
|
|
600
|
+
this.options.layers.map(async (layer) => {
|
|
601
|
+
if (!layer.keys || this.options.shouldSkipLayer(layer)) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
const keys = await layer.keys();
|
|
606
|
+
for (const key of keys) {
|
|
607
|
+
if (PatternMatcher.matches(pattern, key)) {
|
|
608
|
+
matches.add(key);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
} catch (error) {
|
|
612
|
+
await this.options.handleLayerFailure(layer, "invalidate-pattern-scan", error);
|
|
613
|
+
}
|
|
614
|
+
})
|
|
615
|
+
);
|
|
616
|
+
return [...matches];
|
|
617
|
+
}
|
|
618
|
+
};
|
|
506
619
|
|
|
507
620
|
// ../../src/internal/CircuitBreakerManager.ts
|
|
508
621
|
var CircuitBreakerManager = class {
|
|
@@ -522,9 +635,7 @@ var CircuitBreakerManager = class {
|
|
|
522
635
|
}
|
|
523
636
|
const now = Date.now();
|
|
524
637
|
if (state.openUntil <= now) {
|
|
525
|
-
|
|
526
|
-
state.failures = 0;
|
|
527
|
-
this.breakers.set(key, state);
|
|
638
|
+
this.breakers.delete(key);
|
|
528
639
|
return;
|
|
529
640
|
}
|
|
530
641
|
const remainingMs = state.openUntil - now;
|
|
@@ -535,15 +646,15 @@ var CircuitBreakerManager = class {
|
|
|
535
646
|
if (!options) {
|
|
536
647
|
return;
|
|
537
648
|
}
|
|
649
|
+
this.pruneIfNeeded();
|
|
538
650
|
const failureThreshold = options.failureThreshold ?? 3;
|
|
539
651
|
const cooldownMs = options.cooldownMs ?? 3e4;
|
|
540
|
-
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null };
|
|
652
|
+
const state = this.breakers.get(key) ?? { failures: 0, openUntil: null, createdAt: Date.now() };
|
|
541
653
|
state.failures += 1;
|
|
542
654
|
if (state.failures >= failureThreshold) {
|
|
543
655
|
state.openUntil = Date.now() + cooldownMs;
|
|
544
656
|
}
|
|
545
657
|
this.breakers.set(key, state);
|
|
546
|
-
this.pruneIfNeeded();
|
|
547
658
|
}
|
|
548
659
|
recordSuccess(key) {
|
|
549
660
|
this.breakers.delete(key);
|
|
@@ -554,8 +665,7 @@ var CircuitBreakerManager = class {
|
|
|
554
665
|
return false;
|
|
555
666
|
}
|
|
556
667
|
if (state.openUntil <= Date.now()) {
|
|
557
|
-
|
|
558
|
-
state.failures = 0;
|
|
668
|
+
this.breakers.delete(key);
|
|
559
669
|
return false;
|
|
560
670
|
}
|
|
561
671
|
return true;
|
|
@@ -579,15 +689,20 @@ var CircuitBreakerManager = class {
|
|
|
579
689
|
if (this.breakers.size <= this.maxEntries) {
|
|
580
690
|
return;
|
|
581
691
|
}
|
|
692
|
+
const now = Date.now();
|
|
582
693
|
for (const [key, state] of this.breakers.entries()) {
|
|
583
694
|
if (this.breakers.size <= this.maxEntries) {
|
|
584
|
-
|
|
695
|
+
return;
|
|
585
696
|
}
|
|
586
|
-
if (!state.openUntil || state.openUntil <=
|
|
697
|
+
if (!state.openUntil || state.openUntil <= now) {
|
|
587
698
|
this.breakers.delete(key);
|
|
588
699
|
}
|
|
589
700
|
}
|
|
590
|
-
|
|
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) {
|
|
591
706
|
if (this.breakers.size <= this.maxEntries) {
|
|
592
707
|
break;
|
|
593
708
|
}
|
|
@@ -597,6 +712,7 @@ var CircuitBreakerManager = class {
|
|
|
597
712
|
};
|
|
598
713
|
|
|
599
714
|
// ../../src/internal/FetchRateLimiter.ts
|
|
715
|
+
var MAX_BUCKETS = 1e4;
|
|
600
716
|
var FetchRateLimiter = class {
|
|
601
717
|
buckets = /* @__PURE__ */ new Map();
|
|
602
718
|
queuesByBucket = /* @__PURE__ */ new Map();
|
|
@@ -762,10 +878,25 @@ var FetchRateLimiter = class {
|
|
|
762
878
|
if (existing) {
|
|
763
879
|
return existing;
|
|
764
880
|
}
|
|
881
|
+
if (this.buckets.size >= MAX_BUCKETS) {
|
|
882
|
+
this.evictIdleBuckets();
|
|
883
|
+
}
|
|
765
884
|
const bucket = { active: 0, startedAt: [] };
|
|
766
885
|
this.buckets.set(bucketKey, bucket);
|
|
767
886
|
return bucket;
|
|
768
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
|
+
}
|
|
769
900
|
cleanupBucket(bucketKey, bucket, intervalMs) {
|
|
770
901
|
const queued = this.queuesByBucket.get(bucketKey)?.length ?? 0;
|
|
771
902
|
if (queued === 0 && bucket.active === 0 && bucket.startedAt.length === 0) {
|
|
@@ -1092,66 +1223,18 @@ var TtlResolver = class {
|
|
|
1092
1223
|
return;
|
|
1093
1224
|
}
|
|
1094
1225
|
const toRemove = Math.ceil(this.maxProfileEntries * 0.1);
|
|
1095
|
-
|
|
1096
|
-
for (
|
|
1097
|
-
|
|
1098
|
-
|
|
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]);
|
|
1099
1231
|
}
|
|
1100
|
-
this.accessProfiles.delete(key);
|
|
1101
|
-
removed += 1;
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
};
|
|
1105
|
-
|
|
1106
|
-
// ../../src/invalidation/PatternMatcher.ts
|
|
1107
|
-
var PatternMatcher = class _PatternMatcher {
|
|
1108
|
-
/**
|
|
1109
|
-
* Tests whether a glob-style pattern matches a value.
|
|
1110
|
-
* Supports `*` (any sequence of characters) and `?` (any single character).
|
|
1111
|
-
* Uses a two-pointer algorithm to avoid ReDoS vulnerabilities and
|
|
1112
|
-
* quadratic memory usage on long patterns/keys.
|
|
1113
|
-
*/
|
|
1114
|
-
static matches(pattern, value) {
|
|
1115
|
-
return _PatternMatcher.matchLinear(pattern, value);
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Linear-time glob matching with O(1) extra memory.
|
|
1119
|
-
*/
|
|
1120
|
-
static matchLinear(pattern, value) {
|
|
1121
|
-
let patternIndex = 0;
|
|
1122
|
-
let valueIndex = 0;
|
|
1123
|
-
let starIndex = -1;
|
|
1124
|
-
let backtrackValueIndex = 0;
|
|
1125
|
-
while (valueIndex < value.length) {
|
|
1126
|
-
const patternChar = pattern[patternIndex];
|
|
1127
|
-
const valueChar = value[valueIndex];
|
|
1128
|
-
if (patternChar === "*" && patternIndex < pattern.length) {
|
|
1129
|
-
starIndex = patternIndex;
|
|
1130
|
-
patternIndex += 1;
|
|
1131
|
-
backtrackValueIndex = valueIndex;
|
|
1132
|
-
continue;
|
|
1133
|
-
}
|
|
1134
|
-
if (patternChar === "?" || patternChar === valueChar) {
|
|
1135
|
-
patternIndex += 1;
|
|
1136
|
-
valueIndex += 1;
|
|
1137
|
-
continue;
|
|
1138
|
-
}
|
|
1139
|
-
if (starIndex !== -1) {
|
|
1140
|
-
patternIndex = starIndex + 1;
|
|
1141
|
-
backtrackValueIndex += 1;
|
|
1142
|
-
valueIndex = backtrackValueIndex;
|
|
1143
|
-
continue;
|
|
1144
|
-
}
|
|
1145
|
-
return false;
|
|
1146
|
-
}
|
|
1147
|
-
while (patternIndex < pattern.length && pattern[patternIndex] === "*") {
|
|
1148
|
-
patternIndex += 1;
|
|
1149
1232
|
}
|
|
1150
|
-
return patternIndex === pattern.length;
|
|
1151
1233
|
}
|
|
1152
1234
|
};
|
|
1153
1235
|
|
|
1154
1236
|
// ../../src/invalidation/TagIndex.ts
|
|
1237
|
+
var MAX_PATTERN_RECURSION_DEPTH = 500;
|
|
1155
1238
|
var TagIndex = class {
|
|
1156
1239
|
tagToKeys = /* @__PURE__ */ new Map();
|
|
1157
1240
|
keyToTags = /* @__PURE__ */ new Map();
|
|
@@ -1206,7 +1289,7 @@ var TagIndex = class {
|
|
|
1206
1289
|
}
|
|
1207
1290
|
async matchPattern(pattern) {
|
|
1208
1291
|
const matches = /* @__PURE__ */ new Set();
|
|
1209
|
-
this.collectPatternMatches(this.root, "", pattern, 0, matches, /* @__PURE__ */ new Set());
|
|
1292
|
+
this.collectPatternMatches(this.root, "", pattern, 0, matches, /* @__PURE__ */ new Set(), 0);
|
|
1210
1293
|
return [...matches];
|
|
1211
1294
|
}
|
|
1212
1295
|
async clear() {
|
|
@@ -1258,7 +1341,10 @@ var TagIndex = class {
|
|
|
1258
1341
|
this.collectFromNode(child, `${prefix}${character}`, matches);
|
|
1259
1342
|
}
|
|
1260
1343
|
}
|
|
1261
|
-
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
|
+
}
|
|
1262
1348
|
const stateKey = `${node.id}:${patternIndex}`;
|
|
1263
1349
|
if (visited.has(stateKey)) {
|
|
1264
1350
|
return;
|
|
@@ -1275,21 +1361,37 @@ var TagIndex = class {
|
|
|
1275
1361
|
return;
|
|
1276
1362
|
}
|
|
1277
1363
|
if (patternChar === "*") {
|
|
1278
|
-
this.collectPatternMatches(node, prefix, pattern, patternIndex + 1, matches, visited);
|
|
1364
|
+
this.collectPatternMatches(node, prefix, pattern, patternIndex + 1, matches, visited, depth + 1);
|
|
1279
1365
|
for (const [character, child2] of node.children) {
|
|
1280
|
-
this.collectPatternMatches(child2, `${prefix}${character}`, pattern, patternIndex, matches, visited);
|
|
1366
|
+
this.collectPatternMatches(child2, `${prefix}${character}`, pattern, patternIndex, matches, visited, depth + 1);
|
|
1281
1367
|
}
|
|
1282
1368
|
return;
|
|
1283
1369
|
}
|
|
1284
1370
|
if (patternChar === "?") {
|
|
1285
1371
|
for (const [character, child2] of node.children) {
|
|
1286
|
-
this.collectPatternMatches(
|
|
1372
|
+
this.collectPatternMatches(
|
|
1373
|
+
child2,
|
|
1374
|
+
`${prefix}${character}`,
|
|
1375
|
+
pattern,
|
|
1376
|
+
patternIndex + 1,
|
|
1377
|
+
matches,
|
|
1378
|
+
visited,
|
|
1379
|
+
depth + 1
|
|
1380
|
+
);
|
|
1287
1381
|
}
|
|
1288
1382
|
return;
|
|
1289
1383
|
}
|
|
1290
1384
|
const child = node.children.get(patternChar);
|
|
1291
1385
|
if (child) {
|
|
1292
|
-
this.collectPatternMatches(
|
|
1386
|
+
this.collectPatternMatches(
|
|
1387
|
+
child,
|
|
1388
|
+
`${prefix}${patternChar}`,
|
|
1389
|
+
pattern,
|
|
1390
|
+
patternIndex + 1,
|
|
1391
|
+
matches,
|
|
1392
|
+
visited,
|
|
1393
|
+
depth + 1
|
|
1394
|
+
);
|
|
1293
1395
|
}
|
|
1294
1396
|
}
|
|
1295
1397
|
pruneKnownKeysIfNeeded() {
|
|
@@ -1431,6 +1533,7 @@ var DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS = 5e3;
|
|
|
1431
1533
|
var DEFAULT_SINGLE_FLIGHT_POLL_MS = 50;
|
|
1432
1534
|
var DEFAULT_BACKGROUND_REFRESH_TIMEOUT_MS = 3e4;
|
|
1433
1535
|
var MAX_CACHE_KEY_LENGTH = 1024;
|
|
1536
|
+
var MAX_PATTERN_LENGTH = 1024;
|
|
1434
1537
|
var DEFAULT_MAX_PROFILE_ENTRIES = 1e5;
|
|
1435
1538
|
var DANGEROUS_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
1436
1539
|
var DebugLogger = class {
|
|
@@ -1479,6 +1582,14 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1479
1582
|
const debugEnv = process.env.DEBUG?.split(",").includes("layercache:debug") ?? false;
|
|
1480
1583
|
this.logger = typeof options.logger === "object" ? options.logger : new DebugLogger(Boolean(options.logger) || debugEnv);
|
|
1481
1584
|
this.tagIndex = options.tagIndex ?? new TagIndex();
|
|
1585
|
+
this.keyDiscovery = new CacheKeyDiscovery({
|
|
1586
|
+
layers: this.layers,
|
|
1587
|
+
tagIndex: this.tagIndex,
|
|
1588
|
+
shouldSkipLayer: (layer) => this.shouldSkipLayer(layer),
|
|
1589
|
+
handleLayerFailure: async (layer, operation, error) => {
|
|
1590
|
+
await this.handleLayerFailure(layer, operation, error);
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1482
1593
|
if (!options.tagIndex && layers.some((layer) => layer.isLocal === false)) {
|
|
1483
1594
|
this.logger.warn?.(
|
|
1484
1595
|
"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."
|
|
@@ -1506,6 +1617,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1506
1617
|
unsubscribeInvalidation;
|
|
1507
1618
|
logger;
|
|
1508
1619
|
tagIndex;
|
|
1620
|
+
keyDiscovery;
|
|
1509
1621
|
fetchRateLimiter = new FetchRateLimiter();
|
|
1510
1622
|
snapshotSerializer = new JsonSerializer();
|
|
1511
1623
|
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
@@ -1855,15 +1967,16 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1855
1967
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1856
1968
|
}
|
|
1857
1969
|
async invalidateByPattern(pattern) {
|
|
1970
|
+
this.validatePattern(pattern);
|
|
1858
1971
|
await this.awaitStartup("invalidateByPattern");
|
|
1859
|
-
const keys = await this.collectKeysMatchingPattern(this.qualifyPattern(pattern));
|
|
1972
|
+
const keys = await this.keyDiscovery.collectKeysMatchingPattern(this.qualifyPattern(pattern));
|
|
1860
1973
|
await this.deleteKeys(keys);
|
|
1861
1974
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1862
1975
|
}
|
|
1863
1976
|
async invalidateByPrefix(prefix) {
|
|
1864
1977
|
await this.awaitStartup("invalidateByPrefix");
|
|
1865
1978
|
const qualifiedPrefix = this.qualifyKey(this.validateCacheKey(prefix));
|
|
1866
|
-
const keys = await this.collectKeysWithPrefix(qualifiedPrefix);
|
|
1979
|
+
const keys = await this.keyDiscovery.collectKeysWithPrefix(qualifiedPrefix);
|
|
1867
1980
|
await this.deleteKeys(keys);
|
|
1868
1981
|
await this.publishInvalidation({ scope: "keys", keys, sourceId: this.instanceId, operation: "invalidate" });
|
|
1869
1982
|
}
|
|
@@ -2476,50 +2589,6 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2476
2589
|
shouldBroadcastL1Invalidation() {
|
|
2477
2590
|
return this.options.broadcastL1Invalidation ?? this.options.publishSetInvalidation ?? false;
|
|
2478
2591
|
}
|
|
2479
|
-
async collectKeysWithPrefix(prefix) {
|
|
2480
|
-
const matches = new Set(
|
|
2481
|
-
this.tagIndex.keysForPrefix ? await this.tagIndex.keysForPrefix(prefix) : await this.tagIndex.matchPattern(`${prefix}*`)
|
|
2482
|
-
);
|
|
2483
|
-
await Promise.all(
|
|
2484
|
-
this.layers.map(async (layer) => {
|
|
2485
|
-
if (!layer.keys || this.shouldSkipLayer(layer)) {
|
|
2486
|
-
return;
|
|
2487
|
-
}
|
|
2488
|
-
try {
|
|
2489
|
-
const keys = await layer.keys();
|
|
2490
|
-
for (const key of keys) {
|
|
2491
|
-
if (key.startsWith(prefix)) {
|
|
2492
|
-
matches.add(key);
|
|
2493
|
-
}
|
|
2494
|
-
}
|
|
2495
|
-
} catch (error) {
|
|
2496
|
-
await this.handleLayerFailure(layer, "invalidate-prefix-scan", error);
|
|
2497
|
-
}
|
|
2498
|
-
})
|
|
2499
|
-
);
|
|
2500
|
-
return [...matches];
|
|
2501
|
-
}
|
|
2502
|
-
async collectKeysMatchingPattern(pattern) {
|
|
2503
|
-
const matches = new Set(await this.tagIndex.matchPattern(pattern));
|
|
2504
|
-
await Promise.all(
|
|
2505
|
-
this.layers.map(async (layer) => {
|
|
2506
|
-
if (!layer.keys || this.shouldSkipLayer(layer)) {
|
|
2507
|
-
return;
|
|
2508
|
-
}
|
|
2509
|
-
try {
|
|
2510
|
-
const keys = await layer.keys();
|
|
2511
|
-
for (const key of keys) {
|
|
2512
|
-
if (PatternMatcher.matches(pattern, key)) {
|
|
2513
|
-
matches.add(key);
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
} catch (error) {
|
|
2517
|
-
await this.handleLayerFailure(layer, "invalidate-pattern-scan", error);
|
|
2518
|
-
}
|
|
2519
|
-
})
|
|
2520
|
-
);
|
|
2521
|
-
return [...matches];
|
|
2522
|
-
}
|
|
2523
2592
|
shouldCleanupGenerations() {
|
|
2524
2593
|
return Boolean(this.options.generationCleanup);
|
|
2525
2594
|
}
|
|
@@ -2542,7 +2611,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2542
2611
|
}
|
|
2543
2612
|
async cleanupGeneration(generation) {
|
|
2544
2613
|
const prefix = `v${generation}:`;
|
|
2545
|
-
const keys = await this.collectKeysWithPrefix(prefix);
|
|
2614
|
+
const keys = await this.keyDiscovery.collectKeysWithPrefix(prefix);
|
|
2546
2615
|
if (keys.length === 0) {
|
|
2547
2616
|
return;
|
|
2548
2617
|
}
|
|
@@ -2792,6 +2861,17 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2792
2861
|
}
|
|
2793
2862
|
return key;
|
|
2794
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
|
+
}
|
|
2795
2875
|
validateTtlPolicy(name, policy) {
|
|
2796
2876
|
if (!policy || typeof policy === "function" || policy === "until-midnight" || policy === "next-hour") {
|
|
2797
2877
|
return;
|
|
@@ -2956,7 +3036,18 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2956
3036
|
}
|
|
2957
3037
|
};
|
|
2958
3038
|
function createInstanceId() {
|
|
2959
|
-
|
|
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("")}`;
|
|
2960
3051
|
}
|
|
2961
3052
|
|
|
2962
3053
|
// src/module.ts
|
|
@@ -410,6 +410,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
410
410
|
private unsubscribeInvalidation?;
|
|
411
411
|
private readonly logger;
|
|
412
412
|
private readonly tagIndex;
|
|
413
|
+
private readonly keyDiscovery;
|
|
413
414
|
private readonly fetchRateLimiter;
|
|
414
415
|
private readonly snapshotSerializer;
|
|
415
416
|
private readonly backgroundRefreshes;
|
|
@@ -532,8 +533,6 @@ declare class CacheStack extends EventEmitter {
|
|
|
532
533
|
private sleep;
|
|
533
534
|
private withTimeout;
|
|
534
535
|
private shouldBroadcastL1Invalidation;
|
|
535
|
-
private collectKeysWithPrefix;
|
|
536
|
-
private collectKeysMatchingPattern;
|
|
537
536
|
private shouldCleanupGenerations;
|
|
538
537
|
private generationCleanupBatchSize;
|
|
539
538
|
private scheduleGenerationCleanup;
|
|
@@ -556,6 +555,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
556
555
|
private validateRateLimitOptions;
|
|
557
556
|
private validateNonNegativeNumber;
|
|
558
557
|
private validateCacheKey;
|
|
558
|
+
private validatePattern;
|
|
559
559
|
private validateTtlPolicy;
|
|
560
560
|
private assertActive;
|
|
561
561
|
private awaitStartup;
|
|
@@ -410,6 +410,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
410
410
|
private unsubscribeInvalidation?;
|
|
411
411
|
private readonly logger;
|
|
412
412
|
private readonly tagIndex;
|
|
413
|
+
private readonly keyDiscovery;
|
|
413
414
|
private readonly fetchRateLimiter;
|
|
414
415
|
private readonly snapshotSerializer;
|
|
415
416
|
private readonly backgroundRefreshes;
|
|
@@ -532,8 +533,6 @@ declare class CacheStack extends EventEmitter {
|
|
|
532
533
|
private sleep;
|
|
533
534
|
private withTimeout;
|
|
534
535
|
private shouldBroadcastL1Invalidation;
|
|
535
|
-
private collectKeysWithPrefix;
|
|
536
|
-
private collectKeysMatchingPattern;
|
|
537
536
|
private shouldCleanupGenerations;
|
|
538
537
|
private generationCleanupBatchSize;
|
|
539
538
|
private scheduleGenerationCleanup;
|
|
@@ -556,6 +555,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
556
555
|
private validateRateLimitOptions;
|
|
557
556
|
private validateNonNegativeNumber;
|
|
558
557
|
private validateCacheKey;
|
|
558
|
+
private validatePattern;
|
|
559
559
|
private validateTtlPolicy;
|
|
560
560
|
private assertActive;
|
|
561
561
|
private awaitStartup;
|