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
|
@@ -156,6 +156,7 @@ interface CacheSingleFlightExecutionOptions {
|
|
|
156
156
|
leaseMs: number;
|
|
157
157
|
waitTimeoutMs: number;
|
|
158
158
|
pollIntervalMs: number;
|
|
159
|
+
renewIntervalMs?: number;
|
|
159
160
|
}
|
|
160
161
|
interface CacheSingleFlightCoordinator {
|
|
161
162
|
execute<T>(key: string, options: CacheSingleFlightExecutionOptions, worker: () => Promise<T>, waiter: () => Promise<T>): Promise<T>;
|
|
@@ -189,6 +190,7 @@ interface CacheStackOptions {
|
|
|
189
190
|
singleFlightLeaseMs?: number;
|
|
190
191
|
singleFlightTimeoutMs?: number;
|
|
191
192
|
singleFlightPollMs?: number;
|
|
193
|
+
singleFlightRenewIntervalMs?: number;
|
|
192
194
|
/**
|
|
193
195
|
* Maximum number of entries in `accessProfiles` and `circuitBreakers` maps
|
|
194
196
|
* before the oldest entries are pruned. Prevents unbounded memory growth.
|
|
@@ -219,6 +221,8 @@ interface CacheRateLimitOptions {
|
|
|
219
221
|
maxConcurrent?: number;
|
|
220
222
|
intervalMs?: number;
|
|
221
223
|
maxPerInterval?: number;
|
|
224
|
+
scope?: 'global' | 'key' | 'fetcher';
|
|
225
|
+
bucketKey?: string;
|
|
222
226
|
}
|
|
223
227
|
interface CacheWriteBehindOptions {
|
|
224
228
|
flushIntervalMs?: number;
|
|
@@ -527,6 +531,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
527
531
|
private validateWriteOptions;
|
|
528
532
|
private validateLayerNumberOption;
|
|
529
533
|
private validatePositiveNumber;
|
|
534
|
+
private validateRateLimitOptions;
|
|
530
535
|
private validateNonNegativeNumber;
|
|
531
536
|
private validateCacheKey;
|
|
532
537
|
private validateTtlPolicy;
|
|
@@ -562,11 +562,12 @@ var CircuitBreakerManager = class {
|
|
|
562
562
|
|
|
563
563
|
// ../../src/internal/FetchRateLimiter.ts
|
|
564
564
|
var FetchRateLimiter = class {
|
|
565
|
-
active = 0;
|
|
566
565
|
queue = [];
|
|
567
|
-
|
|
566
|
+
buckets = /* @__PURE__ */ new Map();
|
|
567
|
+
fetcherBuckets = /* @__PURE__ */ new WeakMap();
|
|
568
|
+
nextFetcherBucketId = 0;
|
|
568
569
|
drainTimer;
|
|
569
|
-
async schedule(options, task) {
|
|
570
|
+
async schedule(options, context, task) {
|
|
570
571
|
if (!options) {
|
|
571
572
|
return task();
|
|
572
573
|
}
|
|
@@ -575,7 +576,13 @@ var FetchRateLimiter = class {
|
|
|
575
576
|
return task();
|
|
576
577
|
}
|
|
577
578
|
return new Promise((resolve, reject) => {
|
|
578
|
-
this.queue.push({
|
|
579
|
+
this.queue.push({
|
|
580
|
+
bucketKey: this.resolveBucketKey(normalized, context),
|
|
581
|
+
options: normalized,
|
|
582
|
+
task,
|
|
583
|
+
resolve,
|
|
584
|
+
reject
|
|
585
|
+
});
|
|
579
586
|
this.drain();
|
|
580
587
|
});
|
|
581
588
|
}
|
|
@@ -589,63 +596,109 @@ var FetchRateLimiter = class {
|
|
|
589
596
|
return {
|
|
590
597
|
maxConcurrent,
|
|
591
598
|
intervalMs,
|
|
592
|
-
maxPerInterval
|
|
599
|
+
maxPerInterval,
|
|
600
|
+
scope: options.scope ?? "global",
|
|
601
|
+
bucketKey: options.bucketKey
|
|
593
602
|
};
|
|
594
603
|
}
|
|
604
|
+
resolveBucketKey(options, context) {
|
|
605
|
+
if (options.bucketKey) {
|
|
606
|
+
return `custom:${options.bucketKey}`;
|
|
607
|
+
}
|
|
608
|
+
if (options.scope === "key") {
|
|
609
|
+
return `key:${context.key}`;
|
|
610
|
+
}
|
|
611
|
+
if (options.scope === "fetcher") {
|
|
612
|
+
const existing = this.fetcherBuckets.get(context.fetcher);
|
|
613
|
+
if (existing) {
|
|
614
|
+
return existing;
|
|
615
|
+
}
|
|
616
|
+
const bucket = `fetcher:${this.nextFetcherBucketId}`;
|
|
617
|
+
this.nextFetcherBucketId += 1;
|
|
618
|
+
this.fetcherBuckets.set(context.fetcher, bucket);
|
|
619
|
+
return bucket;
|
|
620
|
+
}
|
|
621
|
+
return "global";
|
|
622
|
+
}
|
|
595
623
|
drain() {
|
|
596
624
|
if (this.drainTimer) {
|
|
597
625
|
clearTimeout(this.drainTimer);
|
|
598
626
|
this.drainTimer = void 0;
|
|
599
627
|
}
|
|
600
628
|
while (this.queue.length > 0) {
|
|
601
|
-
|
|
602
|
-
|
|
629
|
+
let nextIndex = -1;
|
|
630
|
+
let nextWaitMs = Number.POSITIVE_INFINITY;
|
|
631
|
+
for (let index = 0; index < this.queue.length; index += 1) {
|
|
632
|
+
const next2 = this.queue[index];
|
|
633
|
+
if (!next2) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const waitMs = this.waitTime(next2.bucketKey, next2.options);
|
|
637
|
+
if (waitMs <= 0) {
|
|
638
|
+
nextIndex = index;
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
nextWaitMs = Math.min(nextWaitMs, waitMs);
|
|
642
|
+
}
|
|
643
|
+
if (nextIndex < 0) {
|
|
644
|
+
if (Number.isFinite(nextWaitMs)) {
|
|
645
|
+
this.drainTimer = setTimeout(() => {
|
|
646
|
+
this.drainTimer = void 0;
|
|
647
|
+
this.drain();
|
|
648
|
+
}, nextWaitMs);
|
|
649
|
+
this.drainTimer.unref?.();
|
|
650
|
+
}
|
|
603
651
|
return;
|
|
604
652
|
}
|
|
605
|
-
const
|
|
606
|
-
if (
|
|
607
|
-
this.drainTimer = setTimeout(() => {
|
|
608
|
-
this.drainTimer = void 0;
|
|
609
|
-
this.drain();
|
|
610
|
-
}, waitMs);
|
|
611
|
-
this.drainTimer.unref?.();
|
|
653
|
+
const next = this.queue.splice(nextIndex, 1)[0];
|
|
654
|
+
if (!next) {
|
|
612
655
|
return;
|
|
613
656
|
}
|
|
614
|
-
this.
|
|
615
|
-
|
|
616
|
-
|
|
657
|
+
const bucket = this.bucketState(next.bucketKey);
|
|
658
|
+
bucket.active += 1;
|
|
659
|
+
bucket.startedAt.push(Date.now());
|
|
617
660
|
void next.task().then(next.resolve, next.reject).finally(() => {
|
|
618
|
-
|
|
661
|
+
bucket.active -= 1;
|
|
619
662
|
this.drain();
|
|
620
663
|
});
|
|
621
664
|
}
|
|
622
665
|
}
|
|
623
|
-
waitTime(options) {
|
|
666
|
+
waitTime(bucketKey, options) {
|
|
667
|
+
const bucket = this.bucketState(bucketKey);
|
|
624
668
|
const now = Date.now();
|
|
625
|
-
if (options.maxConcurrent &&
|
|
669
|
+
if (options.maxConcurrent && bucket.active >= options.maxConcurrent) {
|
|
626
670
|
return 1;
|
|
627
671
|
}
|
|
628
672
|
if (!options.intervalMs || !options.maxPerInterval) {
|
|
629
673
|
return 0;
|
|
630
674
|
}
|
|
631
|
-
this.prune(now, options.intervalMs);
|
|
632
|
-
if (
|
|
675
|
+
this.prune(bucket, now, options.intervalMs);
|
|
676
|
+
if (bucket.startedAt.length < options.maxPerInterval) {
|
|
633
677
|
return 0;
|
|
634
678
|
}
|
|
635
|
-
const oldest =
|
|
679
|
+
const oldest = bucket.startedAt[0];
|
|
636
680
|
if (!oldest) {
|
|
637
681
|
return 0;
|
|
638
682
|
}
|
|
639
683
|
return Math.max(1, options.intervalMs - (now - oldest));
|
|
640
684
|
}
|
|
641
|
-
prune(now, intervalMs) {
|
|
642
|
-
while (
|
|
643
|
-
const startedAt =
|
|
685
|
+
prune(bucket, now, intervalMs) {
|
|
686
|
+
while (bucket.startedAt.length > 0) {
|
|
687
|
+
const startedAt = bucket.startedAt[0];
|
|
644
688
|
if (startedAt === void 0 || now - startedAt < intervalMs) {
|
|
645
689
|
break;
|
|
646
690
|
}
|
|
647
|
-
|
|
691
|
+
bucket.startedAt.shift();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
bucketState(bucketKey) {
|
|
695
|
+
const existing = this.buckets.get(bucketKey);
|
|
696
|
+
if (existing) {
|
|
697
|
+
return existing;
|
|
648
698
|
}
|
|
699
|
+
const bucket = { active: 0, startedAt: [] };
|
|
700
|
+
this.buckets.set(bucketKey, bucket);
|
|
701
|
+
return bucket;
|
|
649
702
|
}
|
|
650
703
|
};
|
|
651
704
|
|
|
@@ -1751,6 +1804,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
1751
1804
|
try {
|
|
1752
1805
|
fetched = await this.fetchRateLimiter.schedule(
|
|
1753
1806
|
options?.fetcherRateLimit ?? this.options.fetcherRateLimit,
|
|
1807
|
+
{ key, fetcher },
|
|
1754
1808
|
fetcher
|
|
1755
1809
|
);
|
|
1756
1810
|
this.circuitBreakerManager.recordSuccess(key);
|
|
@@ -2008,7 +2062,8 @@ var CacheStack = class extends EventEmitter {
|
|
|
2008
2062
|
return {
|
|
2009
2063
|
leaseMs: this.options.singleFlightLeaseMs ?? DEFAULT_SINGLE_FLIGHT_LEASE_MS,
|
|
2010
2064
|
waitTimeoutMs: this.options.singleFlightTimeoutMs ?? DEFAULT_SINGLE_FLIGHT_TIMEOUT_MS,
|
|
2011
|
-
pollIntervalMs: this.options.singleFlightPollMs ?? DEFAULT_SINGLE_FLIGHT_POLL_MS
|
|
2065
|
+
pollIntervalMs: this.options.singleFlightPollMs ?? DEFAULT_SINGLE_FLIGHT_POLL_MS,
|
|
2066
|
+
renewIntervalMs: this.options.singleFlightRenewIntervalMs
|
|
2012
2067
|
};
|
|
2013
2068
|
}
|
|
2014
2069
|
async deleteKeys(keys) {
|
|
@@ -2213,6 +2268,8 @@ var CacheStack = class extends EventEmitter {
|
|
|
2213
2268
|
this.validatePositiveNumber("singleFlightLeaseMs", this.options.singleFlightLeaseMs);
|
|
2214
2269
|
this.validatePositiveNumber("singleFlightTimeoutMs", this.options.singleFlightTimeoutMs);
|
|
2215
2270
|
this.validatePositiveNumber("singleFlightPollMs", this.options.singleFlightPollMs);
|
|
2271
|
+
this.validatePositiveNumber("singleFlightRenewIntervalMs", this.options.singleFlightRenewIntervalMs);
|
|
2272
|
+
this.validateRateLimitOptions("fetcherRateLimit", this.options.fetcherRateLimit);
|
|
2216
2273
|
this.validateAdaptiveTtlOptions(this.options.adaptiveTtl);
|
|
2217
2274
|
this.validateCircuitBreakerOptions(this.options.circuitBreaker);
|
|
2218
2275
|
if (this.options.generation !== void 0) {
|
|
@@ -2232,6 +2289,7 @@ var CacheStack = class extends EventEmitter {
|
|
|
2232
2289
|
this.validateTtlPolicy("options.ttlPolicy", options.ttlPolicy);
|
|
2233
2290
|
this.validateAdaptiveTtlOptions(options.adaptiveTtl);
|
|
2234
2291
|
this.validateCircuitBreakerOptions(options.circuitBreaker);
|
|
2292
|
+
this.validateRateLimitOptions("options.fetcherRateLimit", options.fetcherRateLimit);
|
|
2235
2293
|
}
|
|
2236
2294
|
validateLayerNumberOption(name, value) {
|
|
2237
2295
|
if (value === void 0) {
|
|
@@ -2256,6 +2314,20 @@ var CacheStack = class extends EventEmitter {
|
|
|
2256
2314
|
throw new Error(`${name} must be a positive finite number.`);
|
|
2257
2315
|
}
|
|
2258
2316
|
}
|
|
2317
|
+
validateRateLimitOptions(name, options) {
|
|
2318
|
+
if (!options) {
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
this.validatePositiveNumber(`${name}.maxConcurrent`, options.maxConcurrent);
|
|
2322
|
+
this.validatePositiveNumber(`${name}.intervalMs`, options.intervalMs);
|
|
2323
|
+
this.validatePositiveNumber(`${name}.maxPerInterval`, options.maxPerInterval);
|
|
2324
|
+
if (options.scope && !["global", "key", "fetcher"].includes(options.scope)) {
|
|
2325
|
+
throw new Error(`${name}.scope must be one of "global", "key", or "fetcher".`);
|
|
2326
|
+
}
|
|
2327
|
+
if (options.bucketKey !== void 0 && options.bucketKey.length === 0) {
|
|
2328
|
+
throw new Error(`${name}.bucketKey must not be empty.`);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2259
2331
|
validateNonNegativeNumber(name, value) {
|
|
2260
2332
|
if (!Number.isFinite(value) || value < 0) {
|
|
2261
2333
|
throw new Error(`${name} must be a non-negative finite number.`);
|