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/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
- startedAt = [];
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((resolve, reject) => {
416
- this.queue.push({ options: normalized, task, resolve, reject });
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
- const next = this.queue[0];
440
- if (!next) {
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 waitMs = this.waitTime(next.options);
444
- if (waitMs > 0) {
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.queue.shift();
453
- this.active += 1;
454
- this.startedAt.push(Date.now());
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
- this.active -= 1;
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 && this.active >= 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 (this.startedAt.length < options.maxPerInterval) {
513
+ this.prune(bucket, now, options.intervalMs);
514
+ if (bucket.startedAt.length < options.maxPerInterval) {
471
515
  return 0;
472
516
  }
473
- const oldest = this.startedAt[0];
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 (this.startedAt.length > 0) {
481
- const startedAt = this.startedAt[0];
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
- this.startedAt.shift();
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((resolve) => setTimeout(resolve, ms));
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.knownKeysKey(), key);
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.knownKeysKey(), key);
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.knownKeysKey(), key);
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
- let cursor = "0";
2368
- do {
2369
- const [nextCursor, keys] = await this.client.sscan(this.knownKeysKey(), cursor, "COUNT", this.scanCount);
2370
- cursor = nextCursor;
2371
- matches.push(...keys.filter((key) => key.startsWith(prefix)));
2372
- } while (cursor !== "0");
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
- let cursor = "0";
2381
- do {
2382
- const [nextCursor, keys] = await this.client.sscan(
2383
- this.knownKeysKey(),
2384
- cursor,
2385
- "MATCH",
2386
- pattern,
2387
- "COUNT",
2388
- this.scanCount
2389
- );
2390
- cursor = nextCursor;
2391
- matches.push(...keys.filter((key) => PatternMatcher.matches(pattern, key)));
2392
- } while (cursor !== "0");
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
- knownKeysKey() {
2414
- return `${this.prefix}:keys`;
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.serializer.deserialize(raw);
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.serializer.deserialize(raw);
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.serializer.deserialize(raw);
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-C1sBhTfv.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-C1sBhTfv.cjs';
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 knownKeysKey;
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-C1sBhTfv.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-C1sBhTfv.js';
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 knownKeysKey;
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 {