layercache 1.2.1 → 1.2.2
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 +40 -4
- package/dist/{chunk-GF47Y3XR.js → chunk-IXCMHVHP.js} +55 -24
- package/dist/cli.cjs +55 -24
- package/dist/cli.js +1 -1
- package/dist/{edge-C1sBhTfv.d.cts → edge-DLpdQN0W.d.cts} +5 -0
- package/dist/{edge-C1sBhTfv.d.ts → edge-DLpdQN0W.d.ts} +5 -0
- package/dist/edge.d.cts +1 -1
- package/dist/edge.d.ts +1 -1
- package/dist/index.cjs +238 -60
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +185 -38
- package/package.json +2 -2
- package/packages/nestjs/dist/index.cjs +100 -28
- package/packages/nestjs/dist/index.d.cts +5 -0
- package/packages/nestjs/dist/index.d.ts +5 -0
- package/packages/nestjs/dist/index.js +100 -28
package/dist/index.cjs
CHANGED
|
@@ -400,11 +400,12 @@ var CircuitBreakerManager = class {
|
|
|
400
400
|
|
|
401
401
|
// src/internal/FetchRateLimiter.ts
|
|
402
402
|
var FetchRateLimiter = class {
|
|
403
|
-
active = 0;
|
|
404
403
|
queue = [];
|
|
405
|
-
|
|
404
|
+
buckets = /* @__PURE__ */ new Map();
|
|
405
|
+
fetcherBuckets = /* @__PURE__ */ new WeakMap();
|
|
406
|
+
nextFetcherBucketId = 0;
|
|
406
407
|
drainTimer;
|
|
407
|
-
async schedule(options, task) {
|
|
408
|
+
async schedule(options, context, task) {
|
|
408
409
|
if (!options) {
|
|
409
410
|
return task();
|
|
410
411
|
}
|
|
@@ -412,8 +413,14 @@ var FetchRateLimiter = class {
|
|
|
412
413
|
if (!normalized) {
|
|
413
414
|
return task();
|
|
414
415
|
}
|
|
415
|
-
return new Promise((
|
|
416
|
-
this.queue.push({
|
|
416
|
+
return new Promise((resolve2, reject) => {
|
|
417
|
+
this.queue.push({
|
|
418
|
+
bucketKey: this.resolveBucketKey(normalized, context),
|
|
419
|
+
options: normalized,
|
|
420
|
+
task,
|
|
421
|
+
resolve: resolve2,
|
|
422
|
+
reject
|
|
423
|
+
});
|
|
417
424
|
this.drain();
|
|
418
425
|
});
|
|
419
426
|
}
|
|
@@ -427,64 +434,110 @@ var FetchRateLimiter = class {
|
|
|
427
434
|
return {
|
|
428
435
|
maxConcurrent,
|
|
429
436
|
intervalMs,
|
|
430
|
-
maxPerInterval
|
|
437
|
+
maxPerInterval,
|
|
438
|
+
scope: options.scope ?? "global",
|
|
439
|
+
bucketKey: options.bucketKey
|
|
431
440
|
};
|
|
432
441
|
}
|
|
442
|
+
resolveBucketKey(options, context) {
|
|
443
|
+
if (options.bucketKey) {
|
|
444
|
+
return `custom:${options.bucketKey}`;
|
|
445
|
+
}
|
|
446
|
+
if (options.scope === "key") {
|
|
447
|
+
return `key:${context.key}`;
|
|
448
|
+
}
|
|
449
|
+
if (options.scope === "fetcher") {
|
|
450
|
+
const existing = this.fetcherBuckets.get(context.fetcher);
|
|
451
|
+
if (existing) {
|
|
452
|
+
return existing;
|
|
453
|
+
}
|
|
454
|
+
const bucket = `fetcher:${this.nextFetcherBucketId}`;
|
|
455
|
+
this.nextFetcherBucketId += 1;
|
|
456
|
+
this.fetcherBuckets.set(context.fetcher, bucket);
|
|
457
|
+
return bucket;
|
|
458
|
+
}
|
|
459
|
+
return "global";
|
|
460
|
+
}
|
|
433
461
|
drain() {
|
|
434
462
|
if (this.drainTimer) {
|
|
435
463
|
clearTimeout(this.drainTimer);
|
|
436
464
|
this.drainTimer = void 0;
|
|
437
465
|
}
|
|
438
466
|
while (this.queue.length > 0) {
|
|
439
|
-
|
|
440
|
-
|
|
467
|
+
let nextIndex = -1;
|
|
468
|
+
let nextWaitMs = Number.POSITIVE_INFINITY;
|
|
469
|
+
for (let index = 0; index < this.queue.length; index += 1) {
|
|
470
|
+
const next2 = this.queue[index];
|
|
471
|
+
if (!next2) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const waitMs = this.waitTime(next2.bucketKey, next2.options);
|
|
475
|
+
if (waitMs <= 0) {
|
|
476
|
+
nextIndex = index;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
nextWaitMs = Math.min(nextWaitMs, waitMs);
|
|
480
|
+
}
|
|
481
|
+
if (nextIndex < 0) {
|
|
482
|
+
if (Number.isFinite(nextWaitMs)) {
|
|
483
|
+
this.drainTimer = setTimeout(() => {
|
|
484
|
+
this.drainTimer = void 0;
|
|
485
|
+
this.drain();
|
|
486
|
+
}, nextWaitMs);
|
|
487
|
+
this.drainTimer.unref?.();
|
|
488
|
+
}
|
|
441
489
|
return;
|
|
442
490
|
}
|
|
443
|
-
const
|
|
444
|
-
if (
|
|
445
|
-
this.drainTimer = setTimeout(() => {
|
|
446
|
-
this.drainTimer = void 0;
|
|
447
|
-
this.drain();
|
|
448
|
-
}, waitMs);
|
|
449
|
-
this.drainTimer.unref?.();
|
|
491
|
+
const next = this.queue.splice(nextIndex, 1)[0];
|
|
492
|
+
if (!next) {
|
|
450
493
|
return;
|
|
451
494
|
}
|
|
452
|
-
this.
|
|
453
|
-
|
|
454
|
-
|
|
495
|
+
const bucket = this.bucketState(next.bucketKey);
|
|
496
|
+
bucket.active += 1;
|
|
497
|
+
bucket.startedAt.push(Date.now());
|
|
455
498
|
void next.task().then(next.resolve, next.reject).finally(() => {
|
|
456
|
-
|
|
499
|
+
bucket.active -= 1;
|
|
457
500
|
this.drain();
|
|
458
501
|
});
|
|
459
502
|
}
|
|
460
503
|
}
|
|
461
|
-
waitTime(options) {
|
|
504
|
+
waitTime(bucketKey, options) {
|
|
505
|
+
const bucket = this.bucketState(bucketKey);
|
|
462
506
|
const now = Date.now();
|
|
463
|
-
if (options.maxConcurrent &&
|
|
507
|
+
if (options.maxConcurrent && bucket.active >= options.maxConcurrent) {
|
|
464
508
|
return 1;
|
|
465
509
|
}
|
|
466
510
|
if (!options.intervalMs || !options.maxPerInterval) {
|
|
467
511
|
return 0;
|
|
468
512
|
}
|
|
469
|
-
this.prune(now, options.intervalMs);
|
|
470
|
-
if (
|
|
513
|
+
this.prune(bucket, now, options.intervalMs);
|
|
514
|
+
if (bucket.startedAt.length < options.maxPerInterval) {
|
|
471
515
|
return 0;
|
|
472
516
|
}
|
|
473
|
-
const oldest =
|
|
517
|
+
const oldest = bucket.startedAt[0];
|
|
474
518
|
if (!oldest) {
|
|
475
519
|
return 0;
|
|
476
520
|
}
|
|
477
521
|
return Math.max(1, options.intervalMs - (now - oldest));
|
|
478
522
|
}
|
|
479
|
-
prune(now, intervalMs) {
|
|
480
|
-
while (
|
|
481
|
-
const startedAt =
|
|
523
|
+
prune(bucket, now, intervalMs) {
|
|
524
|
+
while (bucket.startedAt.length > 0) {
|
|
525
|
+
const startedAt = bucket.startedAt[0];
|
|
482
526
|
if (startedAt === void 0 || now - startedAt < intervalMs) {
|
|
483
527
|
break;
|
|
484
528
|
}
|
|
485
|
-
|
|
529
|
+
bucket.startedAt.shift();
|
|
486
530
|
}
|
|
487
531
|
}
|
|
532
|
+
bucketState(bucketKey) {
|
|
533
|
+
const existing = this.buckets.get(bucketKey);
|
|
534
|
+
if (existing) {
|
|
535
|
+
return existing;
|
|
536
|
+
}
|
|
537
|
+
const bucket = { active: 0, startedAt: [] };
|
|
538
|
+
this.buckets.set(bucketKey, bucket);
|
|
539
|
+
return bucket;
|
|
540
|
+
}
|
|
488
541
|
};
|
|
489
542
|
|
|
490
543
|
// src/internal/MetricsCollector.ts
|
|
@@ -1590,6 +1643,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1590
1643
|
try {
|
|
1591
1644
|
fetched = await this.fetchRateLimiter.schedule(
|
|
1592
1645
|
options?.fetcherRateLimit ?? this.options.fetcherRateLimit,
|
|
1646
|
+
{ key, fetcher },
|
|
1593
1647
|
fetcher
|
|
1594
1648
|
);
|
|
1595
1649
|
this.circuitBreakerManager.recordSuccess(key);
|
|
@@ -1847,7 +1901,8 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1847
1901
|
return {
|
|
1848
1902
|
leaseMs: this.options.singleFlightLeaseMs ?? DEFAULT_SINGLE_FLIGHT_LEASE_MS,
|
|
1849
1903
|
waitTimeoutMs: this.options.singleFlightTimeoutMs ?? DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS,
|
|
1850
|
-
pollIntervalMs: this.options.singleFlightPollMs ?? DEFAULT_SINGLE_FLIGHT_POLL_MS
|
|
1904
|
+
pollIntervalMs: this.options.singleFlightPollMs ?? DEFAULT_SINGLE_FLIGHT_POLL_MS,
|
|
1905
|
+
renewIntervalMs: this.options.singleFlightRenewIntervalMs
|
|
1851
1906
|
};
|
|
1852
1907
|
}
|
|
1853
1908
|
async deleteKeys(keys) {
|
|
@@ -1907,7 +1962,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
1907
1962
|
return String(error);
|
|
1908
1963
|
}
|
|
1909
1964
|
sleep(ms) {
|
|
1910
|
-
return new Promise((
|
|
1965
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1911
1966
|
}
|
|
1912
1967
|
shouldBroadcastL1Invalidation() {
|
|
1913
1968
|
return this.options.broadcastL1Invalidation ?? this.options.publishSetInvalidation ?? true;
|
|
@@ -2052,6 +2107,8 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2052
2107
|
this.validatePositiveNumber("singleFlightLeaseMs", this.options.singleFlightLeaseMs);
|
|
2053
2108
|
this.validatePositiveNumber("singleFlightTimeoutMs", this.options.singleFlightTimeoutMs);
|
|
2054
2109
|
this.validatePositiveNumber("singleFlightPollMs", this.options.singleFlightPollMs);
|
|
2110
|
+
this.validatePositiveNumber("singleFlightRenewIntervalMs", this.options.singleFlightRenewIntervalMs);
|
|
2111
|
+
this.validateRateLimitOptions("fetcherRateLimit", this.options.fetcherRateLimit);
|
|
2055
2112
|
this.validateAdaptiveTtlOptions(this.options.adaptiveTtl);
|
|
2056
2113
|
this.validateCircuitBreakerOptions(this.options.circuitBreaker);
|
|
2057
2114
|
if (this.options.generation !== void 0) {
|
|
@@ -2071,6 +2128,7 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2071
2128
|
this.validateTtlPolicy("options.ttlPolicy", options.ttlPolicy);
|
|
2072
2129
|
this.validateAdaptiveTtlOptions(options.adaptiveTtl);
|
|
2073
2130
|
this.validateCircuitBreakerOptions(options.circuitBreaker);
|
|
2131
|
+
this.validateRateLimitOptions("options.fetcherRateLimit", options.fetcherRateLimit);
|
|
2074
2132
|
}
|
|
2075
2133
|
validateLayerNumberOption(name, value) {
|
|
2076
2134
|
if (value === void 0) {
|
|
@@ -2095,6 +2153,20 @@ var CacheStack = class extends import_node_events.EventEmitter {
|
|
|
2095
2153
|
throw new Error(`${name} must be a positive finite number.`);
|
|
2096
2154
|
}
|
|
2097
2155
|
}
|
|
2156
|
+
validateRateLimitOptions(name, options) {
|
|
2157
|
+
if (!options) {
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
this.validatePositiveNumber(`${name}.maxConcurrent`, options.maxConcurrent);
|
|
2161
|
+
this.validatePositiveNumber(`${name}.intervalMs`, options.intervalMs);
|
|
2162
|
+
this.validatePositiveNumber(`${name}.maxPerInterval`, options.maxPerInterval);
|
|
2163
|
+
if (options.scope && !["global", "key", "fetcher"].includes(options.scope)) {
|
|
2164
|
+
throw new Error(`${name}.scope must be one of "global", "key", or "fetcher".`);
|
|
2165
|
+
}
|
|
2166
|
+
if (options.bucketKey !== void 0 && options.bucketKey.length === 0) {
|
|
2167
|
+
throw new Error(`${name}.bucketKey must not be empty.`);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2098
2170
|
validateNonNegativeNumber(name, value) {
|
|
2099
2171
|
if (!Number.isFinite(value) || value < 0) {
|
|
2100
2172
|
throw new Error(`${name} must be a non-negative finite number.`);
|
|
@@ -2323,19 +2395,21 @@ var RedisTagIndex = class {
|
|
|
2323
2395
|
client;
|
|
2324
2396
|
prefix;
|
|
2325
2397
|
scanCount;
|
|
2398
|
+
knownKeysShards;
|
|
2326
2399
|
constructor(options) {
|
|
2327
2400
|
this.client = options.client;
|
|
2328
2401
|
this.prefix = options.prefix ?? "layercache:tag-index";
|
|
2329
2402
|
this.scanCount = options.scanCount ?? 100;
|
|
2403
|
+
this.knownKeysShards = normalizeKnownKeysShards(options.knownKeysShards);
|
|
2330
2404
|
}
|
|
2331
2405
|
async touch(key) {
|
|
2332
|
-
await this.client.sadd(this.
|
|
2406
|
+
await this.client.sadd(this.knownKeysKeyFor(key), key);
|
|
2333
2407
|
}
|
|
2334
2408
|
async track(key, tags) {
|
|
2335
2409
|
const keyTagsKey = this.keyTagsKey(key);
|
|
2336
2410
|
const existingTags = await this.client.smembers(keyTagsKey);
|
|
2337
2411
|
const pipeline = this.client.pipeline();
|
|
2338
|
-
pipeline.sadd(this.
|
|
2412
|
+
pipeline.sadd(this.knownKeysKeyFor(key), key);
|
|
2339
2413
|
for (const tag of existingTags) {
|
|
2340
2414
|
pipeline.srem(this.tagKeysKey(tag), key);
|
|
2341
2415
|
}
|
|
@@ -2352,7 +2426,7 @@ var RedisTagIndex = class {
|
|
|
2352
2426
|
const keyTagsKey = this.keyTagsKey(key);
|
|
2353
2427
|
const existingTags = await this.client.smembers(keyTagsKey);
|
|
2354
2428
|
const pipeline = this.client.pipeline();
|
|
2355
|
-
pipeline.srem(this.
|
|
2429
|
+
pipeline.srem(this.knownKeysKeyFor(key), key);
|
|
2356
2430
|
pipeline.del(keyTagsKey);
|
|
2357
2431
|
for (const tag of existingTags) {
|
|
2358
2432
|
pipeline.srem(this.tagKeysKey(tag), key);
|
|
@@ -2364,12 +2438,14 @@ var RedisTagIndex = class {
|
|
|
2364
2438
|
}
|
|
2365
2439
|
async keysForPrefix(prefix) {
|
|
2366
2440
|
const matches = [];
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2441
|
+
for (const knownKeysKey of this.knownKeysKeys()) {
|
|
2442
|
+
let cursor = "0";
|
|
2443
|
+
do {
|
|
2444
|
+
const [nextCursor, keys] = await this.client.sscan(knownKeysKey, cursor, "COUNT", this.scanCount);
|
|
2445
|
+
cursor = nextCursor;
|
|
2446
|
+
matches.push(...keys.filter((key) => key.startsWith(prefix)));
|
|
2447
|
+
} while (cursor !== "0");
|
|
2448
|
+
}
|
|
2373
2449
|
return matches;
|
|
2374
2450
|
}
|
|
2375
2451
|
async tagsForKey(key) {
|
|
@@ -2377,19 +2453,21 @@ var RedisTagIndex = class {
|
|
|
2377
2453
|
}
|
|
2378
2454
|
async matchPattern(pattern) {
|
|
2379
2455
|
const matches = [];
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
this.
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2456
|
+
for (const knownKeysKey of this.knownKeysKeys()) {
|
|
2457
|
+
let cursor = "0";
|
|
2458
|
+
do {
|
|
2459
|
+
const [nextCursor, keys] = await this.client.sscan(
|
|
2460
|
+
knownKeysKey,
|
|
2461
|
+
cursor,
|
|
2462
|
+
"MATCH",
|
|
2463
|
+
pattern,
|
|
2464
|
+
"COUNT",
|
|
2465
|
+
this.scanCount
|
|
2466
|
+
);
|
|
2467
|
+
cursor = nextCursor;
|
|
2468
|
+
matches.push(...keys.filter((key) => PatternMatcher.matches(pattern, key)));
|
|
2469
|
+
} while (cursor !== "0");
|
|
2470
|
+
}
|
|
2393
2471
|
return matches;
|
|
2394
2472
|
}
|
|
2395
2473
|
async clear() {
|
|
@@ -2410,8 +2488,17 @@ var RedisTagIndex = class {
|
|
|
2410
2488
|
} while (cursor !== "0");
|
|
2411
2489
|
return matches;
|
|
2412
2490
|
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2491
|
+
knownKeysKeyFor(key) {
|
|
2492
|
+
if (this.knownKeysShards === 1) {
|
|
2493
|
+
return `${this.prefix}:keys`;
|
|
2494
|
+
}
|
|
2495
|
+
return `${this.prefix}:keys:${simpleHash(key) % this.knownKeysShards}`;
|
|
2496
|
+
}
|
|
2497
|
+
knownKeysKeys() {
|
|
2498
|
+
if (this.knownKeysShards === 1) {
|
|
2499
|
+
return [`${this.prefix}:keys`];
|
|
2500
|
+
}
|
|
2501
|
+
return Array.from({ length: this.knownKeysShards }, (_, index) => `${this.prefix}:keys:${index}`);
|
|
2415
2502
|
}
|
|
2416
2503
|
keyTagsKey(key) {
|
|
2417
2504
|
return `${this.prefix}:key:${encodeURIComponent(key)}`;
|
|
@@ -2420,6 +2507,22 @@ var RedisTagIndex = class {
|
|
|
2420
2507
|
return `${this.prefix}:tag:${encodeURIComponent(tag)}`;
|
|
2421
2508
|
}
|
|
2422
2509
|
};
|
|
2510
|
+
function normalizeKnownKeysShards(value) {
|
|
2511
|
+
if (value === void 0) {
|
|
2512
|
+
return 1;
|
|
2513
|
+
}
|
|
2514
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
2515
|
+
throw new Error("RedisTagIndex.knownKeysShards must be a positive integer.");
|
|
2516
|
+
}
|
|
2517
|
+
return value;
|
|
2518
|
+
}
|
|
2519
|
+
function simpleHash(value) {
|
|
2520
|
+
let hash = 0;
|
|
2521
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2522
|
+
hash = hash * 31 + value.charCodeAt(index) >>> 0;
|
|
2523
|
+
}
|
|
2524
|
+
return hash;
|
|
2525
|
+
}
|
|
2423
2526
|
|
|
2424
2527
|
// src/http/createCacheStatsHandler.ts
|
|
2425
2528
|
function createCacheStatsHandler(cache) {
|
|
@@ -2818,15 +2921,35 @@ var import_node_util = require("util");
|
|
|
2818
2921
|
var import_node_zlib = require("zlib");
|
|
2819
2922
|
|
|
2820
2923
|
// src/serialization/JsonSerializer.ts
|
|
2924
|
+
var DANGEROUS_JSON_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
2821
2925
|
var JsonSerializer = class {
|
|
2822
2926
|
serialize(value) {
|
|
2823
2927
|
return JSON.stringify(value);
|
|
2824
2928
|
}
|
|
2825
2929
|
deserialize(payload) {
|
|
2826
2930
|
const normalized = Buffer.isBuffer(payload) ? payload.toString("utf8") : payload;
|
|
2827
|
-
return JSON.parse(normalized);
|
|
2931
|
+
return sanitizeJsonValue(JSON.parse(normalized));
|
|
2828
2932
|
}
|
|
2829
2933
|
};
|
|
2934
|
+
function sanitizeJsonValue(value) {
|
|
2935
|
+
if (Array.isArray(value)) {
|
|
2936
|
+
return value.map((entry) => sanitizeJsonValue(entry));
|
|
2937
|
+
}
|
|
2938
|
+
if (!isPlainObject(value)) {
|
|
2939
|
+
return value;
|
|
2940
|
+
}
|
|
2941
|
+
const sanitized = {};
|
|
2942
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2943
|
+
if (DANGEROUS_JSON_KEYS.has(key)) {
|
|
2944
|
+
continue;
|
|
2945
|
+
}
|
|
2946
|
+
sanitized[key] = sanitizeJsonValue(entry);
|
|
2947
|
+
}
|
|
2948
|
+
return sanitized;
|
|
2949
|
+
}
|
|
2950
|
+
function isPlainObject(value) {
|
|
2951
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
2952
|
+
}
|
|
2830
2953
|
|
|
2831
2954
|
// src/layers/RedisLayer.ts
|
|
2832
2955
|
var BATCH_DELETE_SIZE = 500;
|
|
@@ -3083,11 +3206,11 @@ var DiskLayer = class {
|
|
|
3083
3206
|
maxFiles;
|
|
3084
3207
|
writeQueue = Promise.resolve();
|
|
3085
3208
|
constructor(options) {
|
|
3086
|
-
this.directory = options.directory;
|
|
3209
|
+
this.directory = this.resolveDirectory(options.directory);
|
|
3087
3210
|
this.defaultTtl = options.ttl;
|
|
3088
3211
|
this.name = options.name ?? "disk";
|
|
3089
3212
|
this.serializer = options.serializer ?? new JsonSerializer();
|
|
3090
|
-
this.maxFiles = options.maxFiles;
|
|
3213
|
+
this.maxFiles = this.normalizeMaxFiles(options.maxFiles);
|
|
3091
3214
|
}
|
|
3092
3215
|
async get(key) {
|
|
3093
3216
|
return unwrapStoredValue(await this.getEntry(key));
|
|
@@ -3102,7 +3225,7 @@ var DiskLayer = class {
|
|
|
3102
3225
|
}
|
|
3103
3226
|
let entry;
|
|
3104
3227
|
try {
|
|
3105
|
-
entry = this.
|
|
3228
|
+
entry = this.deserializeEntry(raw);
|
|
3106
3229
|
} catch {
|
|
3107
3230
|
await this.safeDelete(filePath);
|
|
3108
3231
|
return null;
|
|
@@ -3153,8 +3276,9 @@ var DiskLayer = class {
|
|
|
3153
3276
|
}
|
|
3154
3277
|
let entry;
|
|
3155
3278
|
try {
|
|
3156
|
-
entry = this.
|
|
3279
|
+
entry = this.deserializeEntry(raw);
|
|
3157
3280
|
} catch {
|
|
3281
|
+
await this.safeDelete(filePath);
|
|
3158
3282
|
return null;
|
|
3159
3283
|
}
|
|
3160
3284
|
if (entry.expiresAt === null) {
|
|
@@ -3211,7 +3335,7 @@ var DiskLayer = class {
|
|
|
3211
3335
|
}
|
|
3212
3336
|
let entry;
|
|
3213
3337
|
try {
|
|
3214
|
-
entry = this.
|
|
3338
|
+
entry = this.deserializeEntry(raw);
|
|
3215
3339
|
} catch {
|
|
3216
3340
|
await this.safeDelete(filePath);
|
|
3217
3341
|
return;
|
|
@@ -3243,6 +3367,31 @@ var DiskLayer = class {
|
|
|
3243
3367
|
const hash = (0, import_node_crypto.createHash)("sha256").update(key).digest("hex");
|
|
3244
3368
|
return (0, import_node_path.join)(this.directory, `${hash}.lc`);
|
|
3245
3369
|
}
|
|
3370
|
+
resolveDirectory(directory) {
|
|
3371
|
+
if (typeof directory !== "string" || directory.trim().length === 0) {
|
|
3372
|
+
throw new Error("DiskLayer.directory must be a non-empty path.");
|
|
3373
|
+
}
|
|
3374
|
+
if (directory.includes("\0")) {
|
|
3375
|
+
throw new Error("DiskLayer.directory must not contain null bytes.");
|
|
3376
|
+
}
|
|
3377
|
+
return (0, import_node_path.resolve)(directory);
|
|
3378
|
+
}
|
|
3379
|
+
normalizeMaxFiles(maxFiles) {
|
|
3380
|
+
if (maxFiles === void 0) {
|
|
3381
|
+
return void 0;
|
|
3382
|
+
}
|
|
3383
|
+
if (!Number.isInteger(maxFiles) || maxFiles <= 0) {
|
|
3384
|
+
throw new Error("DiskLayer.maxFiles must be a positive integer.");
|
|
3385
|
+
}
|
|
3386
|
+
return maxFiles;
|
|
3387
|
+
}
|
|
3388
|
+
deserializeEntry(raw) {
|
|
3389
|
+
const entry = this.serializer.deserialize(raw);
|
|
3390
|
+
if (!isDiskEntry(entry)) {
|
|
3391
|
+
throw new Error("Invalid disk cache entry.");
|
|
3392
|
+
}
|
|
3393
|
+
return entry;
|
|
3394
|
+
}
|
|
3246
3395
|
async safeDelete(filePath) {
|
|
3247
3396
|
try {
|
|
3248
3397
|
await import_node_fs.promises.unlink(filePath);
|
|
@@ -3287,6 +3436,14 @@ var DiskLayer = class {
|
|
|
3287
3436
|
await Promise.all(toEvict.map(({ filePath }) => this.safeDelete(filePath)));
|
|
3288
3437
|
}
|
|
3289
3438
|
};
|
|
3439
|
+
function isDiskEntry(value) {
|
|
3440
|
+
if (!value || typeof value !== "object") {
|
|
3441
|
+
return false;
|
|
3442
|
+
}
|
|
3443
|
+
const candidate = value;
|
|
3444
|
+
const validExpiry = candidate.expiresAt === null || typeof candidate.expiresAt === "number";
|
|
3445
|
+
return typeof candidate.key === "string" && validExpiry && "value" in candidate;
|
|
3446
|
+
}
|
|
3290
3447
|
|
|
3291
3448
|
// src/layers/MemcachedLayer.ts
|
|
3292
3449
|
var MemcachedLayer = class {
|
|
@@ -3366,6 +3523,12 @@ if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
|
3366
3523
|
end
|
|
3367
3524
|
return 0
|
|
3368
3525
|
`;
|
|
3526
|
+
var RENEW_SCRIPT = `
|
|
3527
|
+
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
3528
|
+
return redis.call("pexpire", KEYS[1], ARGV[2])
|
|
3529
|
+
end
|
|
3530
|
+
return 0
|
|
3531
|
+
`;
|
|
3369
3532
|
var RedisSingleFlightCoordinator = class {
|
|
3370
3533
|
client;
|
|
3371
3534
|
prefix;
|
|
@@ -3378,14 +3541,29 @@ var RedisSingleFlightCoordinator = class {
|
|
|
3378
3541
|
const token = (0, import_node_crypto2.randomUUID)();
|
|
3379
3542
|
const acquired = await this.client.set(lockKey, token, "PX", options.leaseMs, "NX");
|
|
3380
3543
|
if (acquired === "OK") {
|
|
3544
|
+
const renewTimer = this.startLeaseRenewal(lockKey, token, options);
|
|
3381
3545
|
try {
|
|
3382
3546
|
return await worker();
|
|
3383
3547
|
} finally {
|
|
3548
|
+
if (renewTimer) {
|
|
3549
|
+
clearInterval(renewTimer);
|
|
3550
|
+
}
|
|
3384
3551
|
await this.client.eval(RELEASE_SCRIPT, 1, lockKey, token);
|
|
3385
3552
|
}
|
|
3386
3553
|
}
|
|
3387
3554
|
return waiter();
|
|
3388
3555
|
}
|
|
3556
|
+
startLeaseRenewal(lockKey, token, options) {
|
|
3557
|
+
const renewIntervalMs = options.renewIntervalMs ?? Math.max(100, Math.floor(options.leaseMs / 2));
|
|
3558
|
+
if (renewIntervalMs <= 0 || renewIntervalMs >= options.leaseMs) {
|
|
3559
|
+
return void 0;
|
|
3560
|
+
}
|
|
3561
|
+
const timer = setInterval(() => {
|
|
3562
|
+
void this.client.eval(RENEW_SCRIPT, 1, lockKey, token, String(options.leaseMs)).catch(() => void 0);
|
|
3563
|
+
}, renewIntervalMs);
|
|
3564
|
+
timer.unref?.();
|
|
3565
|
+
return timer;
|
|
3566
|
+
}
|
|
3389
3567
|
};
|
|
3390
3568
|
|
|
3391
3569
|
// src/metrics/PrometheusExporter.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as InvalidationBus, C as CacheLogger, a as InvalidationMessage, b as CacheTagIndex, c as CacheStack, d as CacheWrapOptions, e as CacheGetOptions, f as CacheLayer, g as CacheSerializer, h as CacheLayerSetManyEntry, i as CacheSingleFlightCoordinator, j as CacheSingleFlightExecutionOptions } from './edge-
|
|
2
|
-
export { k as CacheAdaptiveTtlOptions, l as CacheCircuitBreakerOptions, m as CacheDegradationOptions, n as CacheHealthCheckResult, o as CacheHitRateSnapshot, p as CacheInspectResult, q as CacheLayerLatency, r as CacheMGetEntry, s as CacheMSetEntry, t as CacheMetricsSnapshot, u as CacheMissError, v as CacheNamespace, w as CacheRateLimitOptions, x as CacheSnapshotEntry, y as CacheStackEvents, z as CacheStackOptions, A as CacheStatsSnapshot, B as CacheTtlPolicy, D as CacheTtlPolicyContext, E as CacheWarmEntry, F as CacheWarmOptions, G as CacheWarmProgress, H as CacheWriteBehindOptions, J as CacheWriteOptions, K as EvictionPolicy, L as LayerTtlMap, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-
|
|
1
|
+
import { I as InvalidationBus, C as CacheLogger, a as InvalidationMessage, b as CacheTagIndex, c as CacheStack, d as CacheWrapOptions, e as CacheGetOptions, f as CacheLayer, g as CacheSerializer, h as CacheLayerSetManyEntry, i as CacheSingleFlightCoordinator, j as CacheSingleFlightExecutionOptions } from './edge-DLpdQN0W.cjs';
|
|
2
|
+
export { k as CacheAdaptiveTtlOptions, l as CacheCircuitBreakerOptions, m as CacheDegradationOptions, n as CacheHealthCheckResult, o as CacheHitRateSnapshot, p as CacheInspectResult, q as CacheLayerLatency, r as CacheMGetEntry, s as CacheMSetEntry, t as CacheMetricsSnapshot, u as CacheMissError, v as CacheNamespace, w as CacheRateLimitOptions, x as CacheSnapshotEntry, y as CacheStackEvents, z as CacheStackOptions, A as CacheStatsSnapshot, B as CacheTtlPolicy, D as CacheTtlPolicyContext, E as CacheWarmEntry, F as CacheWarmOptions, G as CacheWarmProgress, H as CacheWriteBehindOptions, J as CacheWriteOptions, K as EvictionPolicy, L as LayerTtlMap, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-DLpdQN0W.cjs';
|
|
3
3
|
import Redis from 'ioredis';
|
|
4
4
|
import 'node:events';
|
|
5
5
|
|
|
@@ -35,11 +35,13 @@ interface RedisTagIndexOptions {
|
|
|
35
35
|
client: Redis;
|
|
36
36
|
prefix?: string;
|
|
37
37
|
scanCount?: number;
|
|
38
|
+
knownKeysShards?: number;
|
|
38
39
|
}
|
|
39
40
|
declare class RedisTagIndex implements CacheTagIndex {
|
|
40
41
|
private readonly client;
|
|
41
42
|
private readonly prefix;
|
|
42
43
|
private readonly scanCount;
|
|
44
|
+
private readonly knownKeysShards;
|
|
43
45
|
constructor(options: RedisTagIndexOptions);
|
|
44
46
|
touch(key: string): Promise<void>;
|
|
45
47
|
track(key: string, tags: string[]): Promise<void>;
|
|
@@ -50,7 +52,8 @@ declare class RedisTagIndex implements CacheTagIndex {
|
|
|
50
52
|
matchPattern(pattern: string): Promise<string[]>;
|
|
51
53
|
clear(): Promise<void>;
|
|
52
54
|
private scanIndexKeys;
|
|
53
|
-
private
|
|
55
|
+
private knownKeysKeyFor;
|
|
56
|
+
private knownKeysKeys;
|
|
54
57
|
private keyTagsKey;
|
|
55
58
|
private tagKeysKey;
|
|
56
59
|
}
|
|
@@ -274,6 +277,9 @@ declare class DiskLayer implements CacheLayer {
|
|
|
274
277
|
ping(): Promise<boolean>;
|
|
275
278
|
dispose(): Promise<void>;
|
|
276
279
|
private keyToPath;
|
|
280
|
+
private resolveDirectory;
|
|
281
|
+
private normalizeMaxFiles;
|
|
282
|
+
private deserializeEntry;
|
|
277
283
|
private safeDelete;
|
|
278
284
|
private enqueueWrite;
|
|
279
285
|
/**
|
|
@@ -362,6 +368,7 @@ declare class RedisSingleFlightCoordinator implements CacheSingleFlightCoordinat
|
|
|
362
368
|
private readonly prefix;
|
|
363
369
|
constructor(options: RedisSingleFlightCoordinatorOptions);
|
|
364
370
|
execute<T>(key: string, options: CacheSingleFlightExecutionOptions, worker: () => Promise<T>, waiter: () => Promise<T>): Promise<T>;
|
|
371
|
+
private startLeaseRenewal;
|
|
365
372
|
}
|
|
366
373
|
|
|
367
374
|
declare class StampedeGuard {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as InvalidationBus, C as CacheLogger, a as InvalidationMessage, b as CacheTagIndex, c as CacheStack, d as CacheWrapOptions, e as CacheGetOptions, f as CacheLayer, g as CacheSerializer, h as CacheLayerSetManyEntry, i as CacheSingleFlightCoordinator, j as CacheSingleFlightExecutionOptions } from './edge-
|
|
2
|
-
export { k as CacheAdaptiveTtlOptions, l as CacheCircuitBreakerOptions, m as CacheDegradationOptions, n as CacheHealthCheckResult, o as CacheHitRateSnapshot, p as CacheInspectResult, q as CacheLayerLatency, r as CacheMGetEntry, s as CacheMSetEntry, t as CacheMetricsSnapshot, u as CacheMissError, v as CacheNamespace, w as CacheRateLimitOptions, x as CacheSnapshotEntry, y as CacheStackEvents, z as CacheStackOptions, A as CacheStatsSnapshot, B as CacheTtlPolicy, D as CacheTtlPolicyContext, E as CacheWarmEntry, F as CacheWarmOptions, G as CacheWarmProgress, H as CacheWriteBehindOptions, J as CacheWriteOptions, K as EvictionPolicy, L as LayerTtlMap, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-
|
|
1
|
+
import { I as InvalidationBus, C as CacheLogger, a as InvalidationMessage, b as CacheTagIndex, c as CacheStack, d as CacheWrapOptions, e as CacheGetOptions, f as CacheLayer, g as CacheSerializer, h as CacheLayerSetManyEntry, i as CacheSingleFlightCoordinator, j as CacheSingleFlightExecutionOptions } from './edge-DLpdQN0W.js';
|
|
2
|
+
export { k as CacheAdaptiveTtlOptions, l as CacheCircuitBreakerOptions, m as CacheDegradationOptions, n as CacheHealthCheckResult, o as CacheHitRateSnapshot, p as CacheInspectResult, q as CacheLayerLatency, r as CacheMGetEntry, s as CacheMSetEntry, t as CacheMetricsSnapshot, u as CacheMissError, v as CacheNamespace, w as CacheRateLimitOptions, x as CacheSnapshotEntry, y as CacheStackEvents, z as CacheStackOptions, A as CacheStatsSnapshot, B as CacheTtlPolicy, D as CacheTtlPolicyContext, E as CacheWarmEntry, F as CacheWarmOptions, G as CacheWarmProgress, H as CacheWriteBehindOptions, J as CacheWriteOptions, K as EvictionPolicy, L as LayerTtlMap, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-DLpdQN0W.js';
|
|
3
3
|
import Redis from 'ioredis';
|
|
4
4
|
import 'node:events';
|
|
5
5
|
|
|
@@ -35,11 +35,13 @@ interface RedisTagIndexOptions {
|
|
|
35
35
|
client: Redis;
|
|
36
36
|
prefix?: string;
|
|
37
37
|
scanCount?: number;
|
|
38
|
+
knownKeysShards?: number;
|
|
38
39
|
}
|
|
39
40
|
declare class RedisTagIndex implements CacheTagIndex {
|
|
40
41
|
private readonly client;
|
|
41
42
|
private readonly prefix;
|
|
42
43
|
private readonly scanCount;
|
|
44
|
+
private readonly knownKeysShards;
|
|
43
45
|
constructor(options: RedisTagIndexOptions);
|
|
44
46
|
touch(key: string): Promise<void>;
|
|
45
47
|
track(key: string, tags: string[]): Promise<void>;
|
|
@@ -50,7 +52,8 @@ declare class RedisTagIndex implements CacheTagIndex {
|
|
|
50
52
|
matchPattern(pattern: string): Promise<string[]>;
|
|
51
53
|
clear(): Promise<void>;
|
|
52
54
|
private scanIndexKeys;
|
|
53
|
-
private
|
|
55
|
+
private knownKeysKeyFor;
|
|
56
|
+
private knownKeysKeys;
|
|
54
57
|
private keyTagsKey;
|
|
55
58
|
private tagKeysKey;
|
|
56
59
|
}
|
|
@@ -274,6 +277,9 @@ declare class DiskLayer implements CacheLayer {
|
|
|
274
277
|
ping(): Promise<boolean>;
|
|
275
278
|
dispose(): Promise<void>;
|
|
276
279
|
private keyToPath;
|
|
280
|
+
private resolveDirectory;
|
|
281
|
+
private normalizeMaxFiles;
|
|
282
|
+
private deserializeEntry;
|
|
277
283
|
private safeDelete;
|
|
278
284
|
private enqueueWrite;
|
|
279
285
|
/**
|
|
@@ -362,6 +368,7 @@ declare class RedisSingleFlightCoordinator implements CacheSingleFlightCoordinat
|
|
|
362
368
|
private readonly prefix;
|
|
363
369
|
constructor(options: RedisSingleFlightCoordinatorOptions);
|
|
364
370
|
execute<T>(key: string, options: CacheSingleFlightExecutionOptions, worker: () => Promise<T>, waiter: () => Promise<T>): Promise<T>;
|
|
371
|
+
private startLeaseRenewal;
|
|
365
372
|
}
|
|
366
373
|
|
|
367
374
|
declare class StampedeGuard {
|