layercache 1.2.2 → 1.2.4
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 +119 -89
- package/dist/{chunk-ZMDB5KOK.js → chunk-7V7XAB74.js} +24 -1
- package/dist/{chunk-46UH7LNM.js → chunk-KOYGHLVP.js} +142 -18
- package/dist/{chunk-IXCMHVHP.js → chunk-QHWG7QS5.js} +1 -1
- package/dist/cli.cjs +37 -3
- package/dist/cli.js +15 -4
- package/dist/{edge-DLpdQN0W.d.ts → edge-Dw97n89L.d.cts} +33 -1
- package/dist/{edge-DLpdQN0W.d.cts → edge-Dw97n89L.d.ts} +33 -1
- package/dist/edge.cjs +165 -17
- package/dist/edge.d.cts +1 -1
- package/dist/edge.d.ts +1 -1
- package/dist/edge.js +2 -2
- package/dist/index.cjs +657 -146
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +447 -84
- package/package.json +4 -4
- package/packages/nestjs/dist/index.cjs +558 -91
- package/packages/nestjs/dist/index.d.cts +24 -0
- package/packages/nestjs/dist/index.d.ts +24 -0
- package/packages/nestjs/dist/index.js +558 -91
package/dist/cli.cjs
CHANGED
|
@@ -38,7 +38,30 @@ var import_ioredis = __toESM(require("ioredis"), 1);
|
|
|
38
38
|
|
|
39
39
|
// src/internal/StoredValue.ts
|
|
40
40
|
function isStoredValueEnvelope(value) {
|
|
41
|
-
|
|
41
|
+
if (typeof value !== "object" || value === null) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const v = value;
|
|
45
|
+
if (v.__layercache !== 1) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (v.kind !== "value" && v.kind !== "empty") {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (v.freshUntil !== null && typeof v.freshUntil !== "number") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (v.staleUntil !== null && typeof v.staleUntil !== "number") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (v.errorUntil !== null && typeof v.errorUntil !== "number") {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
61
|
+
if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
42
65
|
}
|
|
43
66
|
function resolveStoredValue(stored, now = Date.now()) {
|
|
44
67
|
if (!isStoredValueEnvelope(stored)) {
|
|
@@ -259,7 +282,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
259
282
|
const redisUrl = validateRedisUrl(args.redisUrl);
|
|
260
283
|
if (!redisUrl) {
|
|
261
284
|
process.stderr.write(
|
|
262
|
-
`Error: invalid Redis URL "${args.redisUrl}". Expected format: redis://[user:password@]host[:port][/db]
|
|
285
|
+
`Error: invalid Redis URL "${maskRedisUrl(args.redisUrl)}". Expected format: redis://[user:password@]host[:port][/db]
|
|
263
286
|
`
|
|
264
287
|
);
|
|
265
288
|
process.exitCode = 1;
|
|
@@ -273,7 +296,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
273
296
|
try {
|
|
274
297
|
await redis.connect().catch((error) => {
|
|
275
298
|
const message = error instanceof Error ? error.message : String(error);
|
|
276
|
-
throw new Error(`Failed to connect to Redis at "${redisUrl}": ${message}`);
|
|
299
|
+
throw new Error(`Failed to connect to Redis at "${maskRedisUrl(redisUrl)}": ${message}`);
|
|
277
300
|
});
|
|
278
301
|
if (args.command === "stats") {
|
|
279
302
|
const keys = await scanKeys(redis, args.pattern ?? "*");
|
|
@@ -428,6 +451,17 @@ function summarizeInspectableValue(value) {
|
|
|
428
451
|
}
|
|
429
452
|
return value;
|
|
430
453
|
}
|
|
454
|
+
function maskRedisUrl(url) {
|
|
455
|
+
try {
|
|
456
|
+
const parsed = new URL(url);
|
|
457
|
+
if (parsed.password) {
|
|
458
|
+
parsed.password = "***";
|
|
459
|
+
}
|
|
460
|
+
return parsed.toString();
|
|
461
|
+
} catch {
|
|
462
|
+
return url.replace(/:([^@/]+)@/, ":***@");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
431
465
|
if (process.argv[1]?.includes("cli.")) {
|
|
432
466
|
void main();
|
|
433
467
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
RedisTagIndex
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QHWG7QS5.js";
|
|
5
5
|
import {
|
|
6
6
|
isStoredValueEnvelope,
|
|
7
7
|
resolveStoredValue
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7V7XAB74.js";
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
11
|
import Redis from "ioredis";
|
|
@@ -20,7 +20,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
20
20
|
const redisUrl = validateRedisUrl(args.redisUrl);
|
|
21
21
|
if (!redisUrl) {
|
|
22
22
|
process.stderr.write(
|
|
23
|
-
`Error: invalid Redis URL "${args.redisUrl}". Expected format: redis://[user:password@]host[:port][/db]
|
|
23
|
+
`Error: invalid Redis URL "${maskRedisUrl(args.redisUrl)}". Expected format: redis://[user:password@]host[:port][/db]
|
|
24
24
|
`
|
|
25
25
|
);
|
|
26
26
|
process.exitCode = 1;
|
|
@@ -34,7 +34,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
34
34
|
try {
|
|
35
35
|
await redis.connect().catch((error) => {
|
|
36
36
|
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
-
throw new Error(`Failed to connect to Redis at "${redisUrl}": ${message}`);
|
|
37
|
+
throw new Error(`Failed to connect to Redis at "${maskRedisUrl(redisUrl)}": ${message}`);
|
|
38
38
|
});
|
|
39
39
|
if (args.command === "stats") {
|
|
40
40
|
const keys = await scanKeys(redis, args.pattern ?? "*");
|
|
@@ -189,6 +189,17 @@ function summarizeInspectableValue(value) {
|
|
|
189
189
|
}
|
|
190
190
|
return value;
|
|
191
191
|
}
|
|
192
|
+
function maskRedisUrl(url) {
|
|
193
|
+
try {
|
|
194
|
+
const parsed = new URL(url);
|
|
195
|
+
if (parsed.password) {
|
|
196
|
+
parsed.password = "***";
|
|
197
|
+
}
|
|
198
|
+
return parsed.toString();
|
|
199
|
+
} catch {
|
|
200
|
+
return url.replace(/:([^@/]+)@/, ":***@");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
192
203
|
if (process.argv[1]?.includes("cli.")) {
|
|
193
204
|
void main();
|
|
194
205
|
}
|
|
@@ -177,6 +177,7 @@ interface CacheStackOptions {
|
|
|
177
177
|
invalidationBus?: InvalidationBus;
|
|
178
178
|
tagIndex?: CacheTagIndex;
|
|
179
179
|
generation?: number;
|
|
180
|
+
generationCleanup?: boolean | CacheGenerationCleanupOptions;
|
|
180
181
|
broadcastL1Invalidation?: boolean;
|
|
181
182
|
/**
|
|
182
183
|
* @deprecated Use `broadcastL1Invalidation` instead.
|
|
@@ -195,11 +196,13 @@ interface CacheStackOptions {
|
|
|
195
196
|
writeStrategy?: 'write-through' | 'write-behind';
|
|
196
197
|
writeBehind?: CacheWriteBehindOptions;
|
|
197
198
|
fetcherRateLimit?: CacheRateLimitOptions;
|
|
199
|
+
backgroundRefreshTimeoutMs?: number;
|
|
198
200
|
singleFlightCoordinator?: CacheSingleFlightCoordinator;
|
|
199
201
|
singleFlightLeaseMs?: number;
|
|
200
202
|
singleFlightTimeoutMs?: number;
|
|
201
203
|
singleFlightPollMs?: number;
|
|
202
204
|
singleFlightRenewIntervalMs?: number;
|
|
205
|
+
snapshotBaseDir?: string | false;
|
|
203
206
|
/**
|
|
204
207
|
* Maximum number of entries in `accessProfiles` and `circuitBreakers` maps
|
|
205
208
|
* before the oldest entries are pruned. Prevents unbounded memory growth.
|
|
@@ -212,6 +215,9 @@ interface CacheAdaptiveTtlOptions {
|
|
|
212
215
|
step?: number | LayerTtlMap;
|
|
213
216
|
maxTtl?: number | LayerTtlMap;
|
|
214
217
|
}
|
|
218
|
+
interface CacheGenerationCleanupOptions {
|
|
219
|
+
batchSize?: number;
|
|
220
|
+
}
|
|
215
221
|
type CacheTtlPolicy = 'until-midnight' | 'next-hour' | {
|
|
216
222
|
alignTo: number;
|
|
217
223
|
} | ((context: CacheTtlPolicyContext) => number | undefined);
|
|
@@ -415,7 +421,7 @@ interface TagIndexOptions {
|
|
|
415
421
|
/**
|
|
416
422
|
* Maximum number of keys tracked in `knownKeys`. When exceeded, the oldest
|
|
417
423
|
* 10 % of keys are pruned to keep memory bounded.
|
|
418
|
-
* Defaults to
|
|
424
|
+
* Defaults to 100,000.
|
|
419
425
|
*/
|
|
420
426
|
maxKnownKeys?: number;
|
|
421
427
|
}
|
|
@@ -424,6 +430,8 @@ declare class TagIndex implements CacheTagIndex {
|
|
|
424
430
|
private readonly keyToTags;
|
|
425
431
|
private readonly knownKeys;
|
|
426
432
|
private readonly maxKnownKeys;
|
|
433
|
+
private nextNodeId;
|
|
434
|
+
private readonly root;
|
|
427
435
|
constructor(options?: TagIndexOptions);
|
|
428
436
|
touch(key: string): Promise<void>;
|
|
429
437
|
track(key: string, tags: string[]): Promise<void>;
|
|
@@ -433,8 +441,14 @@ declare class TagIndex implements CacheTagIndex {
|
|
|
433
441
|
tagsForKey(key: string): Promise<string[]>;
|
|
434
442
|
matchPattern(pattern: string): Promise<string[]>;
|
|
435
443
|
clear(): Promise<void>;
|
|
444
|
+
private createTrieNode;
|
|
445
|
+
private insertKnownKey;
|
|
446
|
+
private findNode;
|
|
447
|
+
private collectFromNode;
|
|
448
|
+
private collectPatternMatches;
|
|
436
449
|
private pruneKnownKeysIfNeeded;
|
|
437
450
|
private removeKey;
|
|
451
|
+
private removeKnownKey;
|
|
438
452
|
}
|
|
439
453
|
|
|
440
454
|
declare class CacheNamespace {
|
|
@@ -504,7 +518,9 @@ declare class CacheStack extends EventEmitter {
|
|
|
504
518
|
private unsubscribeInvalidation?;
|
|
505
519
|
private readonly logger;
|
|
506
520
|
private readonly tagIndex;
|
|
521
|
+
private readonly keyDiscovery;
|
|
507
522
|
private readonly fetchRateLimiter;
|
|
523
|
+
private readonly snapshotSerializer;
|
|
508
524
|
private readonly backgroundRefreshes;
|
|
509
525
|
private readonly layerDegradedUntil;
|
|
510
526
|
private readonly ttlResolver;
|
|
@@ -513,6 +529,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
513
529
|
private readonly writeBehindQueue;
|
|
514
530
|
private writeBehindTimer?;
|
|
515
531
|
private writeBehindFlushPromise?;
|
|
532
|
+
private generationCleanupPromise?;
|
|
516
533
|
private isDisconnecting;
|
|
517
534
|
private disconnectPromise?;
|
|
518
535
|
constructor(layers: CacheLayer[], options?: CacheStackOptions);
|
|
@@ -523,6 +540,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
523
540
|
* and no `fetcher` is provided.
|
|
524
541
|
*/
|
|
525
542
|
get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
|
|
543
|
+
private getPrepared;
|
|
526
544
|
/**
|
|
527
545
|
* Alias for `get(key, fetcher, options)` — explicit get-or-set pattern.
|
|
528
546
|
* Fetches and caches the value if not already present.
|
|
@@ -581,6 +599,11 @@ declare class CacheStack extends EventEmitter {
|
|
|
581
599
|
*/
|
|
582
600
|
getHitRate(): CacheHitRateSnapshot;
|
|
583
601
|
healthCheck(): Promise<CacheHealthCheckResult[]>;
|
|
602
|
+
/**
|
|
603
|
+
* Rotates the active generation prefix used for all future cache keys.
|
|
604
|
+
* Previous-generation keys remain in the underlying layers until they expire,
|
|
605
|
+
* unless `generationCleanup` is enabled to prune them in the background.
|
|
606
|
+
*/
|
|
584
607
|
bumpGeneration(nextGeneration?: number): number;
|
|
585
608
|
/**
|
|
586
609
|
* Returns detailed metadata about a single cache key: which layers contain it,
|
|
@@ -608,6 +631,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
608
631
|
private resolveLayerSeconds;
|
|
609
632
|
private shouldNegativeCache;
|
|
610
633
|
private scheduleBackgroundRefresh;
|
|
634
|
+
private runBackgroundRefresh;
|
|
611
635
|
private resolveSingleFlightOptions;
|
|
612
636
|
private deleteKeys;
|
|
613
637
|
private publishInvalidation;
|
|
@@ -615,7 +639,12 @@ declare class CacheStack extends EventEmitter {
|
|
|
615
639
|
private getTagsForKey;
|
|
616
640
|
private formatError;
|
|
617
641
|
private sleep;
|
|
642
|
+
private withTimeout;
|
|
618
643
|
private shouldBroadcastL1Invalidation;
|
|
644
|
+
private shouldCleanupGenerations;
|
|
645
|
+
private generationCleanupBatchSize;
|
|
646
|
+
private scheduleGenerationCleanup;
|
|
647
|
+
private cleanupGeneration;
|
|
619
648
|
private initializeWriteBehind;
|
|
620
649
|
private shouldWriteBehind;
|
|
621
650
|
private enqueueWriteBehind;
|
|
@@ -643,12 +672,15 @@ declare class CacheStack extends EventEmitter {
|
|
|
643
672
|
private applyFreshReadPolicies;
|
|
644
673
|
private shouldSkipLayer;
|
|
645
674
|
private handleLayerFailure;
|
|
675
|
+
private reportRecoverableLayerFailure;
|
|
646
676
|
private isGracefulDegradationEnabled;
|
|
647
677
|
private recordCircuitFailure;
|
|
648
678
|
private isNegativeStoredValue;
|
|
649
679
|
private emitError;
|
|
650
680
|
private serializeKeyPart;
|
|
651
681
|
private isCacheSnapshotEntries;
|
|
682
|
+
private sanitizeSnapshotValue;
|
|
683
|
+
private validateSnapshotFilePath;
|
|
652
684
|
private normalizeForSerialization;
|
|
653
685
|
}
|
|
654
686
|
|
|
@@ -177,6 +177,7 @@ interface CacheStackOptions {
|
|
|
177
177
|
invalidationBus?: InvalidationBus;
|
|
178
178
|
tagIndex?: CacheTagIndex;
|
|
179
179
|
generation?: number;
|
|
180
|
+
generationCleanup?: boolean | CacheGenerationCleanupOptions;
|
|
180
181
|
broadcastL1Invalidation?: boolean;
|
|
181
182
|
/**
|
|
182
183
|
* @deprecated Use `broadcastL1Invalidation` instead.
|
|
@@ -195,11 +196,13 @@ interface CacheStackOptions {
|
|
|
195
196
|
writeStrategy?: 'write-through' | 'write-behind';
|
|
196
197
|
writeBehind?: CacheWriteBehindOptions;
|
|
197
198
|
fetcherRateLimit?: CacheRateLimitOptions;
|
|
199
|
+
backgroundRefreshTimeoutMs?: number;
|
|
198
200
|
singleFlightCoordinator?: CacheSingleFlightCoordinator;
|
|
199
201
|
singleFlightLeaseMs?: number;
|
|
200
202
|
singleFlightTimeoutMs?: number;
|
|
201
203
|
singleFlightPollMs?: number;
|
|
202
204
|
singleFlightRenewIntervalMs?: number;
|
|
205
|
+
snapshotBaseDir?: string | false;
|
|
203
206
|
/**
|
|
204
207
|
* Maximum number of entries in `accessProfiles` and `circuitBreakers` maps
|
|
205
208
|
* before the oldest entries are pruned. Prevents unbounded memory growth.
|
|
@@ -212,6 +215,9 @@ interface CacheAdaptiveTtlOptions {
|
|
|
212
215
|
step?: number | LayerTtlMap;
|
|
213
216
|
maxTtl?: number | LayerTtlMap;
|
|
214
217
|
}
|
|
218
|
+
interface CacheGenerationCleanupOptions {
|
|
219
|
+
batchSize?: number;
|
|
220
|
+
}
|
|
215
221
|
type CacheTtlPolicy = 'until-midnight' | 'next-hour' | {
|
|
216
222
|
alignTo: number;
|
|
217
223
|
} | ((context: CacheTtlPolicyContext) => number | undefined);
|
|
@@ -415,7 +421,7 @@ interface TagIndexOptions {
|
|
|
415
421
|
/**
|
|
416
422
|
* Maximum number of keys tracked in `knownKeys`. When exceeded, the oldest
|
|
417
423
|
* 10 % of keys are pruned to keep memory bounded.
|
|
418
|
-
* Defaults to
|
|
424
|
+
* Defaults to 100,000.
|
|
419
425
|
*/
|
|
420
426
|
maxKnownKeys?: number;
|
|
421
427
|
}
|
|
@@ -424,6 +430,8 @@ declare class TagIndex implements CacheTagIndex {
|
|
|
424
430
|
private readonly keyToTags;
|
|
425
431
|
private readonly knownKeys;
|
|
426
432
|
private readonly maxKnownKeys;
|
|
433
|
+
private nextNodeId;
|
|
434
|
+
private readonly root;
|
|
427
435
|
constructor(options?: TagIndexOptions);
|
|
428
436
|
touch(key: string): Promise<void>;
|
|
429
437
|
track(key: string, tags: string[]): Promise<void>;
|
|
@@ -433,8 +441,14 @@ declare class TagIndex implements CacheTagIndex {
|
|
|
433
441
|
tagsForKey(key: string): Promise<string[]>;
|
|
434
442
|
matchPattern(pattern: string): Promise<string[]>;
|
|
435
443
|
clear(): Promise<void>;
|
|
444
|
+
private createTrieNode;
|
|
445
|
+
private insertKnownKey;
|
|
446
|
+
private findNode;
|
|
447
|
+
private collectFromNode;
|
|
448
|
+
private collectPatternMatches;
|
|
436
449
|
private pruneKnownKeysIfNeeded;
|
|
437
450
|
private removeKey;
|
|
451
|
+
private removeKnownKey;
|
|
438
452
|
}
|
|
439
453
|
|
|
440
454
|
declare class CacheNamespace {
|
|
@@ -504,7 +518,9 @@ declare class CacheStack extends EventEmitter {
|
|
|
504
518
|
private unsubscribeInvalidation?;
|
|
505
519
|
private readonly logger;
|
|
506
520
|
private readonly tagIndex;
|
|
521
|
+
private readonly keyDiscovery;
|
|
507
522
|
private readonly fetchRateLimiter;
|
|
523
|
+
private readonly snapshotSerializer;
|
|
508
524
|
private readonly backgroundRefreshes;
|
|
509
525
|
private readonly layerDegradedUntil;
|
|
510
526
|
private readonly ttlResolver;
|
|
@@ -513,6 +529,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
513
529
|
private readonly writeBehindQueue;
|
|
514
530
|
private writeBehindTimer?;
|
|
515
531
|
private writeBehindFlushPromise?;
|
|
532
|
+
private generationCleanupPromise?;
|
|
516
533
|
private isDisconnecting;
|
|
517
534
|
private disconnectPromise?;
|
|
518
535
|
constructor(layers: CacheLayer[], options?: CacheStackOptions);
|
|
@@ -523,6 +540,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
523
540
|
* and no `fetcher` is provided.
|
|
524
541
|
*/
|
|
525
542
|
get<T>(key: string, fetcher?: () => Promise<T>, options?: CacheGetOptions): Promise<T | null>;
|
|
543
|
+
private getPrepared;
|
|
526
544
|
/**
|
|
527
545
|
* Alias for `get(key, fetcher, options)` — explicit get-or-set pattern.
|
|
528
546
|
* Fetches and caches the value if not already present.
|
|
@@ -581,6 +599,11 @@ declare class CacheStack extends EventEmitter {
|
|
|
581
599
|
*/
|
|
582
600
|
getHitRate(): CacheHitRateSnapshot;
|
|
583
601
|
healthCheck(): Promise<CacheHealthCheckResult[]>;
|
|
602
|
+
/**
|
|
603
|
+
* Rotates the active generation prefix used for all future cache keys.
|
|
604
|
+
* Previous-generation keys remain in the underlying layers until they expire,
|
|
605
|
+
* unless `generationCleanup` is enabled to prune them in the background.
|
|
606
|
+
*/
|
|
584
607
|
bumpGeneration(nextGeneration?: number): number;
|
|
585
608
|
/**
|
|
586
609
|
* Returns detailed metadata about a single cache key: which layers contain it,
|
|
@@ -608,6 +631,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
608
631
|
private resolveLayerSeconds;
|
|
609
632
|
private shouldNegativeCache;
|
|
610
633
|
private scheduleBackgroundRefresh;
|
|
634
|
+
private runBackgroundRefresh;
|
|
611
635
|
private resolveSingleFlightOptions;
|
|
612
636
|
private deleteKeys;
|
|
613
637
|
private publishInvalidation;
|
|
@@ -615,7 +639,12 @@ declare class CacheStack extends EventEmitter {
|
|
|
615
639
|
private getTagsForKey;
|
|
616
640
|
private formatError;
|
|
617
641
|
private sleep;
|
|
642
|
+
private withTimeout;
|
|
618
643
|
private shouldBroadcastL1Invalidation;
|
|
644
|
+
private shouldCleanupGenerations;
|
|
645
|
+
private generationCleanupBatchSize;
|
|
646
|
+
private scheduleGenerationCleanup;
|
|
647
|
+
private cleanupGeneration;
|
|
619
648
|
private initializeWriteBehind;
|
|
620
649
|
private shouldWriteBehind;
|
|
621
650
|
private enqueueWriteBehind;
|
|
@@ -643,12 +672,15 @@ declare class CacheStack extends EventEmitter {
|
|
|
643
672
|
private applyFreshReadPolicies;
|
|
644
673
|
private shouldSkipLayer;
|
|
645
674
|
private handleLayerFailure;
|
|
675
|
+
private reportRecoverableLayerFailure;
|
|
646
676
|
private isGracefulDegradationEnabled;
|
|
647
677
|
private recordCircuitFailure;
|
|
648
678
|
private isNegativeStoredValue;
|
|
649
679
|
private emitError;
|
|
650
680
|
private serializeKeyPart;
|
|
651
681
|
private isCacheSnapshotEntries;
|
|
682
|
+
private sanitizeSnapshotValue;
|
|
683
|
+
private validateSnapshotFilePath;
|
|
652
684
|
private normalizeForSerialization;
|
|
653
685
|
}
|
|
654
686
|
|
package/dist/edge.cjs
CHANGED
|
@@ -29,7 +29,30 @@ module.exports = __toCommonJS(edge_exports);
|
|
|
29
29
|
|
|
30
30
|
// src/internal/StoredValue.ts
|
|
31
31
|
function isStoredValueEnvelope(value) {
|
|
32
|
-
|
|
32
|
+
if (typeof value !== "object" || value === null) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const v = value;
|
|
36
|
+
if (v.__layercache !== 1) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (v.kind !== "value" && v.kind !== "empty") {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (v.freshUntil !== null && typeof v.freshUntil !== "number") {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (v.staleUntil !== null && typeof v.staleUntil !== "number") {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (v.errorUntil !== null && typeof v.errorUntil !== "number") {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const maxTimestamp = Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
52
|
+
if (typeof v.freshUntil === "number" && v.freshUntil > maxTimestamp) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
33
56
|
}
|
|
34
57
|
function unwrapStoredValue(stored) {
|
|
35
58
|
if (!isStoredValueEnvelope(stored)) {
|
|
@@ -87,16 +110,10 @@ var MemoryLayer = class {
|
|
|
87
110
|
return entry.value;
|
|
88
111
|
}
|
|
89
112
|
async getMany(keys) {
|
|
90
|
-
|
|
91
|
-
for (const key of keys) {
|
|
92
|
-
values.push(await this.getEntry(key));
|
|
93
|
-
}
|
|
94
|
-
return values;
|
|
113
|
+
return Promise.all(keys.map((key) => this.getEntry(key)));
|
|
95
114
|
}
|
|
96
115
|
async setMany(entries) {
|
|
97
|
-
|
|
98
|
-
await this.set(entry.key, entry.value, entry.ttl);
|
|
99
|
-
}
|
|
116
|
+
await Promise.all(entries.map((entry) => this.set(entry.key, entry.value, entry.ttl)));
|
|
100
117
|
}
|
|
101
118
|
async set(key, value, ttl = this.defaultTtl) {
|
|
102
119
|
this.entries.delete(key);
|
|
@@ -283,15 +300,17 @@ var TagIndex = class {
|
|
|
283
300
|
keyToTags = /* @__PURE__ */ new Map();
|
|
284
301
|
knownKeys = /* @__PURE__ */ new Set();
|
|
285
302
|
maxKnownKeys;
|
|
303
|
+
nextNodeId = 1;
|
|
304
|
+
root = this.createTrieNode();
|
|
286
305
|
constructor(options = {}) {
|
|
287
|
-
this.maxKnownKeys = options.maxKnownKeys;
|
|
306
|
+
this.maxKnownKeys = options.maxKnownKeys ?? 1e5;
|
|
288
307
|
}
|
|
289
308
|
async touch(key) {
|
|
290
|
-
this.
|
|
309
|
+
this.insertKnownKey(key);
|
|
291
310
|
this.pruneKnownKeysIfNeeded();
|
|
292
311
|
}
|
|
293
312
|
async track(key, tags) {
|
|
294
|
-
this.
|
|
313
|
+
this.insertKnownKey(key);
|
|
295
314
|
this.pruneKnownKeysIfNeeded();
|
|
296
315
|
if (tags.length === 0) {
|
|
297
316
|
return;
|
|
@@ -317,18 +336,104 @@ var TagIndex = class {
|
|
|
317
336
|
return [...this.tagToKeys.get(tag) ?? /* @__PURE__ */ new Set()];
|
|
318
337
|
}
|
|
319
338
|
async keysForPrefix(prefix) {
|
|
320
|
-
|
|
339
|
+
const node = this.findNode(prefix);
|
|
340
|
+
if (!node) {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
const matches = [];
|
|
344
|
+
this.collectFromNode(node, prefix, matches);
|
|
345
|
+
return matches;
|
|
321
346
|
}
|
|
322
347
|
async tagsForKey(key) {
|
|
323
348
|
return [...this.keyToTags.get(key) ?? /* @__PURE__ */ new Set()];
|
|
324
349
|
}
|
|
325
350
|
async matchPattern(pattern) {
|
|
326
|
-
|
|
351
|
+
const matches = /* @__PURE__ */ new Set();
|
|
352
|
+
this.collectPatternMatches(this.root, "", pattern, 0, matches, /* @__PURE__ */ new Set());
|
|
353
|
+
return [...matches];
|
|
327
354
|
}
|
|
328
355
|
async clear() {
|
|
329
356
|
this.tagToKeys.clear();
|
|
330
357
|
this.keyToTags.clear();
|
|
331
358
|
this.knownKeys.clear();
|
|
359
|
+
this.root.children.clear();
|
|
360
|
+
this.root.terminal = false;
|
|
361
|
+
this.nextNodeId = this.root.id + 1;
|
|
362
|
+
}
|
|
363
|
+
createTrieNode() {
|
|
364
|
+
return {
|
|
365
|
+
id: this.nextNodeId++,
|
|
366
|
+
terminal: false,
|
|
367
|
+
children: /* @__PURE__ */ new Map()
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
insertKnownKey(key) {
|
|
371
|
+
if (this.knownKeys.has(key)) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
this.knownKeys.add(key);
|
|
375
|
+
let node = this.root;
|
|
376
|
+
for (const character of key) {
|
|
377
|
+
let child = node.children.get(character);
|
|
378
|
+
if (!child) {
|
|
379
|
+
child = this.createTrieNode();
|
|
380
|
+
node.children.set(character, child);
|
|
381
|
+
}
|
|
382
|
+
node = child;
|
|
383
|
+
}
|
|
384
|
+
node.terminal = true;
|
|
385
|
+
}
|
|
386
|
+
findNode(prefix) {
|
|
387
|
+
let node = this.root;
|
|
388
|
+
for (const character of prefix) {
|
|
389
|
+
node = node.children.get(character);
|
|
390
|
+
if (!node) {
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return node;
|
|
395
|
+
}
|
|
396
|
+
collectFromNode(node, prefix, matches) {
|
|
397
|
+
if (node.terminal) {
|
|
398
|
+
matches.push(prefix);
|
|
399
|
+
}
|
|
400
|
+
for (const [character, child] of node.children) {
|
|
401
|
+
this.collectFromNode(child, `${prefix}${character}`, matches);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
collectPatternMatches(node, prefix, pattern, patternIndex, matches, visited) {
|
|
405
|
+
const stateKey = `${node.id}:${patternIndex}`;
|
|
406
|
+
if (visited.has(stateKey)) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
visited.add(stateKey);
|
|
410
|
+
if (patternIndex === pattern.length) {
|
|
411
|
+
if (node.terminal) {
|
|
412
|
+
matches.add(prefix);
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const patternChar = pattern[patternIndex];
|
|
417
|
+
if (patternChar === void 0) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (patternChar === "*") {
|
|
421
|
+
this.collectPatternMatches(node, prefix, pattern, patternIndex + 1, matches, visited);
|
|
422
|
+
for (const [character, child2] of node.children) {
|
|
423
|
+
this.collectPatternMatches(child2, `${prefix}${character}`, pattern, patternIndex, matches, visited);
|
|
424
|
+
}
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (patternChar === "?") {
|
|
428
|
+
for (const [character, child2] of node.children) {
|
|
429
|
+
this.collectPatternMatches(child2, `${prefix}${character}`, pattern, patternIndex + 1, matches, visited);
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const child = node.children.get(patternChar);
|
|
434
|
+
if (child) {
|
|
435
|
+
this.collectPatternMatches(child, `${prefix}${patternChar}`, pattern, patternIndex + 1, matches, visited);
|
|
436
|
+
}
|
|
332
437
|
}
|
|
333
438
|
pruneKnownKeysIfNeeded() {
|
|
334
439
|
if (this.maxKnownKeys === void 0 || this.knownKeys.size <= this.maxKnownKeys) {
|
|
@@ -345,7 +450,7 @@ var TagIndex = class {
|
|
|
345
450
|
}
|
|
346
451
|
}
|
|
347
452
|
removeKey(key) {
|
|
348
|
-
this.
|
|
453
|
+
this.removeKnownKey(key);
|
|
349
454
|
const tags = this.keyToTags.get(key);
|
|
350
455
|
if (!tags) {
|
|
351
456
|
return;
|
|
@@ -362,6 +467,34 @@ var TagIndex = class {
|
|
|
362
467
|
}
|
|
363
468
|
this.keyToTags.delete(key);
|
|
364
469
|
}
|
|
470
|
+
removeKnownKey(key) {
|
|
471
|
+
if (!this.knownKeys.delete(key)) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const path = [];
|
|
475
|
+
let node = this.root;
|
|
476
|
+
for (const character of key) {
|
|
477
|
+
const child = node.children.get(character);
|
|
478
|
+
if (!child) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
path.push([node, character]);
|
|
482
|
+
node = child;
|
|
483
|
+
}
|
|
484
|
+
node.terminal = false;
|
|
485
|
+
for (let index = path.length - 1; index >= 0; index -= 1) {
|
|
486
|
+
const entry = path[index];
|
|
487
|
+
if (!entry) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const [parent, character] = entry;
|
|
491
|
+
const child = parent.children.get(character);
|
|
492
|
+
if (!child || child.terminal || child.children.size > 0) {
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
parent.children.delete(character);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
365
498
|
};
|
|
366
499
|
|
|
367
500
|
// src/integrations/hono.ts
|
|
@@ -373,7 +506,8 @@ function createHonoCacheMiddleware(cache, options = {}) {
|
|
|
373
506
|
await next();
|
|
374
507
|
return;
|
|
375
508
|
}
|
|
376
|
-
const
|
|
509
|
+
const rawPath = context.req.path ?? context.req.url ?? "/";
|
|
510
|
+
const key = options.keyResolver ? options.keyResolver(context.req) : `${method}:${normalizeUrl(rawPath)}`;
|
|
377
511
|
const cached = await cache.get(key, void 0, options);
|
|
378
512
|
if (cached !== null) {
|
|
379
513
|
context.header?.("x-cache", "HIT");
|
|
@@ -384,12 +518,26 @@ function createHonoCacheMiddleware(cache, options = {}) {
|
|
|
384
518
|
const originalJson = context.json.bind(context);
|
|
385
519
|
context.json = (body, status) => {
|
|
386
520
|
context.header?.("x-cache", "MISS");
|
|
387
|
-
|
|
521
|
+
cache.set(key, body, options).catch((err) => {
|
|
522
|
+
cache.emit("error", {
|
|
523
|
+
operation: "set",
|
|
524
|
+
error: err instanceof Error ? err.message : String(err)
|
|
525
|
+
});
|
|
526
|
+
});
|
|
388
527
|
return originalJson(body, status);
|
|
389
528
|
};
|
|
390
529
|
await next();
|
|
391
530
|
};
|
|
392
531
|
}
|
|
532
|
+
function normalizeUrl(url) {
|
|
533
|
+
try {
|
|
534
|
+
const parsed = new URL(url, "http://localhost");
|
|
535
|
+
parsed.searchParams.sort();
|
|
536
|
+
return decodeURIComponent(parsed.pathname) + parsed.search;
|
|
537
|
+
} catch {
|
|
538
|
+
return url;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
393
541
|
// Annotate the CommonJS export names for ESM import in node:
|
|
394
542
|
0 && (module.exports = {
|
|
395
543
|
MemoryLayer,
|
package/dist/edge.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { e as CacheGetOptions, f as CacheLayer, h as CacheLayerSetManyEntry, t as CacheMetricsSnapshot, w as CacheRateLimitOptions, B as CacheTtlPolicy, D as CacheTtlPolicyContext, J as CacheWriteOptions, K as EvictionPolicy, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-
|
|
1
|
+
export { e as CacheGetOptions, f as CacheLayer, h as CacheLayerSetManyEntry, t as CacheMetricsSnapshot, w as CacheRateLimitOptions, B as CacheTtlPolicy, D as CacheTtlPolicyContext, J as CacheWriteOptions, K as EvictionPolicy, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-Dw97n89L.cjs';
|
|
2
2
|
import 'node:events';
|
package/dist/edge.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { e as CacheGetOptions, f as CacheLayer, h as CacheLayerSetManyEntry, t as CacheMetricsSnapshot, w as CacheRateLimitOptions, B as CacheTtlPolicy, D as CacheTtlPolicyContext, J as CacheWriteOptions, K as EvictionPolicy, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-
|
|
1
|
+
export { e as CacheGetOptions, f as CacheLayer, h as CacheLayerSetManyEntry, t as CacheMetricsSnapshot, w as CacheRateLimitOptions, B as CacheTtlPolicy, D as CacheTtlPolicyContext, J as CacheWriteOptions, K as EvictionPolicy, M as MemoryLayer, N as MemoryLayerOptions, O as MemoryLayerSnapshotEntry, P as PatternMatcher, T as TagIndex, Q as createHonoCacheMiddleware } from './edge-Dw97n89L.js';
|
|
2
2
|
import 'node:events';
|
package/dist/edge.js
CHANGED
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
MemoryLayer,
|
|
3
3
|
TagIndex,
|
|
4
4
|
createHonoCacheMiddleware
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KOYGHLVP.js";
|
|
6
6
|
import {
|
|
7
7
|
PatternMatcher
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7V7XAB74.js";
|
|
9
9
|
export {
|
|
10
10
|
MemoryLayer,
|
|
11
11
|
PatternMatcher,
|