layercache 1.2.7 → 1.2.9
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 +4 -4
- package/dist/cli.cjs +26 -2
- package/dist/cli.js +26 -2
- package/dist/{edge-BMmPVqaD.d.cts → edge-BXWTKlI1.d.cts} +21 -10
- package/dist/{edge-BMmPVqaD.d.ts → edge-BXWTKlI1.d.ts} +21 -10
- package/dist/edge.d.cts +1 -1
- package/dist/edge.d.ts +1 -1
- package/dist/index.cjs +1016 -834
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +919 -737
- package/package.json +1 -1
- package/packages/nestjs/dist/index.cjs +942 -715
- package/packages/nestjs/dist/index.d.cts +21 -10
- package/packages/nestjs/dist/index.d.ts +21 -10
- package/packages/nestjs/dist/index.js +942 -715
package/README.md
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache_2.0-green" alt="license"></a>
|
|
16
16
|
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-first-3178C6?logo=typescript&logoColor=white" alt="TypeScript"></a>
|
|
17
17
|
<img src="https://img.shields.io/badge/Node.js-%E2%89%A5_20-339933?logo=nodedotjs&logoColor=white" alt="Node.js >= 20">
|
|
18
|
-
<img src="https://img.shields.io/badge/tests-
|
|
19
|
-
<a href="https://coveralls.io/github/flyingsquirrel0419/layercache?branch=main"><img src="https://coveralls.io/repos/github/flyingsquirrel0419/layercache/badge.svg?branch=main" alt="Coveralls"></a>
|
|
18
|
+
<img src="https://img.shields.io/badge/tests-411_passing-brightgreen" alt="tests">
|
|
19
|
+
<a href="https://coveralls.io/github/flyingsquirrel0419/layercache?branch=main"><img src="https://coveralls.io/repos/github/flyingsquirrel0419/layercache/badge.svg?branch=main&t=20260410" alt="Coveralls"></a>
|
|
20
20
|
</p>
|
|
21
21
|
|
|
22
22
|
<p align="center">
|
|
@@ -163,7 +163,7 @@ const cache = new CacheStack([
|
|
|
163
163
|
| **Per-layer latency** | Avg, max, and sample count using Welford's algorithm |
|
|
164
164
|
| **Health checks** | Async health endpoint per layer with latency measurement |
|
|
165
165
|
| **Event hooks** | `hit`, `miss`, `set`, `delete`, `stale-serve`, `stampede-dedupe`, `backfill`, `warm`, `error` |
|
|
166
|
-
| **OpenTelemetry** |
|
|
166
|
+
| **OpenTelemetry** | Hook-based distributed tracing support without method monkey-patching |
|
|
167
167
|
| **Prometheus exporter** | Metrics export including latency gauges |
|
|
168
168
|
| **HTTP stats handler** | JSON endpoint for dashboards |
|
|
169
169
|
| **Admin CLI** | `npx layercache stats\|keys\|invalidate` for Redis-backed caches |
|
|
@@ -183,7 +183,7 @@ layercache plugs into the frameworks you already use:
|
|
|
183
183
|
| **GraphQL** | `cacheGraphqlResolver(cache, prefix, resolver, opts)` - field resolver wrapper |
|
|
184
184
|
| **NestJS** | `@cachestack/nestjs` - `CacheStackModule.forRoot()`, `@Cacheable()` decorator |
|
|
185
185
|
| **Next.js** | Works natively with App Router and API routes |
|
|
186
|
-
| **OpenTelemetry** | `createOpenTelemetryPlugin(cache, tracer)` -
|
|
186
|
+
| **OpenTelemetry** | `createOpenTelemetryPlugin(cache, tracer)` - event-driven tracing spans without monkey-patching |
|
|
187
187
|
|
|
188
188
|
<details>
|
|
189
189
|
<summary><b>Express example</b></summary>
|
package/dist/cli.cjs
CHANGED
|
@@ -373,7 +373,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
373
373
|
try {
|
|
374
374
|
await redis.connect().catch((error) => {
|
|
375
375
|
const message = error instanceof Error ? error.message : String(error);
|
|
376
|
-
throw new Error(`Failed to connect to Redis: ${message}`);
|
|
376
|
+
throw new Error(`Failed to connect to Redis at ${maskRedisUrl(redisUrl)}: ${message}`);
|
|
377
377
|
});
|
|
378
378
|
if (args.command === "stats") {
|
|
379
379
|
const keys = await scanKeys(redis, args.pattern ?? "*");
|
|
@@ -465,6 +465,11 @@ function parseArgs(argv) {
|
|
|
465
465
|
const token = rest[index];
|
|
466
466
|
const value = rest[index + 1];
|
|
467
467
|
if (token === "--redis") {
|
|
468
|
+
if (!value || value.startsWith("--")) {
|
|
469
|
+
process.stderr.write("Error: --redis requires a value (e.g. redis://localhost:6379)\n");
|
|
470
|
+
process.exitCode = 1;
|
|
471
|
+
return parsed;
|
|
472
|
+
}
|
|
468
473
|
parsed.redisUrl = value;
|
|
469
474
|
index += 1;
|
|
470
475
|
} else if (token === "--pattern") {
|
|
@@ -484,6 +489,7 @@ function parseArgs(argv) {
|
|
|
484
489
|
return parsed;
|
|
485
490
|
}
|
|
486
491
|
var BATCH_DELETE_SIZE = 500;
|
|
492
|
+
var SCAN_MAX_KEYS = 1e6;
|
|
487
493
|
async function batchDelete(redis, keys) {
|
|
488
494
|
for (let i = 0; i < keys.length; i += BATCH_DELETE_SIZE) {
|
|
489
495
|
const batch = keys.slice(i, i + BATCH_DELETE_SIZE);
|
|
@@ -497,6 +503,13 @@ async function scanKeys(redis, pattern) {
|
|
|
497
503
|
const [nextCursor, batch] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
498
504
|
cursor = nextCursor;
|
|
499
505
|
keys.push(...batch);
|
|
506
|
+
if (keys.length >= SCAN_MAX_KEYS) {
|
|
507
|
+
process.stderr.write(
|
|
508
|
+
`Warning: stopped scanning after ${SCAN_MAX_KEYS} keys. Use a more specific --pattern to narrow results.
|
|
509
|
+
`
|
|
510
|
+
);
|
|
511
|
+
return keys;
|
|
512
|
+
}
|
|
500
513
|
} while (cursor !== "0");
|
|
501
514
|
return keys;
|
|
502
515
|
}
|
|
@@ -528,7 +541,18 @@ function summarizeInspectableValue(value) {
|
|
|
528
541
|
}
|
|
529
542
|
return value;
|
|
530
543
|
}
|
|
531
|
-
|
|
544
|
+
function maskRedisUrl(url) {
|
|
545
|
+
try {
|
|
546
|
+
const parsed = new URL(url);
|
|
547
|
+
if (parsed.password) {
|
|
548
|
+
parsed.password = "***";
|
|
549
|
+
}
|
|
550
|
+
return parsed.toString();
|
|
551
|
+
} catch {
|
|
552
|
+
return url.replace(/:([^@/]+)@/, ":***@");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (process.argv[1]?.endsWith("cli.cjs") || process.argv[1]?.endsWith("cli.js")) {
|
|
532
556
|
void main();
|
|
533
557
|
}
|
|
534
558
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
31
31
|
try {
|
|
32
32
|
await redis.connect().catch((error) => {
|
|
33
33
|
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
-
throw new Error(`Failed to connect to Redis: ${message}`);
|
|
34
|
+
throw new Error(`Failed to connect to Redis at ${maskRedisUrl(redisUrl)}: ${message}`);
|
|
35
35
|
});
|
|
36
36
|
if (args.command === "stats") {
|
|
37
37
|
const keys = await scanKeys(redis, args.pattern ?? "*");
|
|
@@ -123,6 +123,11 @@ function parseArgs(argv) {
|
|
|
123
123
|
const token = rest[index];
|
|
124
124
|
const value = rest[index + 1];
|
|
125
125
|
if (token === "--redis") {
|
|
126
|
+
if (!value || value.startsWith("--")) {
|
|
127
|
+
process.stderr.write("Error: --redis requires a value (e.g. redis://localhost:6379)\n");
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
return parsed;
|
|
130
|
+
}
|
|
126
131
|
parsed.redisUrl = value;
|
|
127
132
|
index += 1;
|
|
128
133
|
} else if (token === "--pattern") {
|
|
@@ -142,6 +147,7 @@ function parseArgs(argv) {
|
|
|
142
147
|
return parsed;
|
|
143
148
|
}
|
|
144
149
|
var BATCH_DELETE_SIZE = 500;
|
|
150
|
+
var SCAN_MAX_KEYS = 1e6;
|
|
145
151
|
async function batchDelete(redis, keys) {
|
|
146
152
|
for (let i = 0; i < keys.length; i += BATCH_DELETE_SIZE) {
|
|
147
153
|
const batch = keys.slice(i, i + BATCH_DELETE_SIZE);
|
|
@@ -155,6 +161,13 @@ async function scanKeys(redis, pattern) {
|
|
|
155
161
|
const [nextCursor, batch] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
156
162
|
cursor = nextCursor;
|
|
157
163
|
keys.push(...batch);
|
|
164
|
+
if (keys.length >= SCAN_MAX_KEYS) {
|
|
165
|
+
process.stderr.write(
|
|
166
|
+
`Warning: stopped scanning after ${SCAN_MAX_KEYS} keys. Use a more specific --pattern to narrow results.
|
|
167
|
+
`
|
|
168
|
+
);
|
|
169
|
+
return keys;
|
|
170
|
+
}
|
|
158
171
|
} while (cursor !== "0");
|
|
159
172
|
return keys;
|
|
160
173
|
}
|
|
@@ -186,7 +199,18 @@ function summarizeInspectableValue(value) {
|
|
|
186
199
|
}
|
|
187
200
|
return value;
|
|
188
201
|
}
|
|
189
|
-
|
|
202
|
+
function maskRedisUrl(url) {
|
|
203
|
+
try {
|
|
204
|
+
const parsed = new URL(url);
|
|
205
|
+
if (parsed.password) {
|
|
206
|
+
parsed.password = "***";
|
|
207
|
+
}
|
|
208
|
+
return parsed.toString();
|
|
209
|
+
} catch {
|
|
210
|
+
return url.replace(/:([^@/]+)@/, ":***@");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (process.argv[1]?.endsWith("cli.cjs") || process.argv[1]?.endsWith("cli.js")) {
|
|
190
214
|
void main();
|
|
191
215
|
}
|
|
192
216
|
export {
|
|
@@ -352,6 +352,21 @@ interface CacheStackEvents {
|
|
|
352
352
|
warm: {
|
|
353
353
|
key: string;
|
|
354
354
|
};
|
|
355
|
+
/** Fired immediately before a high-level cache operation begins. */
|
|
356
|
+
'operation-start': {
|
|
357
|
+
id: number;
|
|
358
|
+
name: string;
|
|
359
|
+
attributes?: Record<string, unknown>;
|
|
360
|
+
};
|
|
361
|
+
/** Fired after a high-level cache operation finishes. */
|
|
362
|
+
'operation-end': {
|
|
363
|
+
id: number;
|
|
364
|
+
name: string;
|
|
365
|
+
attributes?: Record<string, unknown>;
|
|
366
|
+
success: boolean;
|
|
367
|
+
result?: 'null';
|
|
368
|
+
error?: unknown;
|
|
369
|
+
};
|
|
355
370
|
/** Fired when an error occurs (layer failure, circuit breaker, etc.). */
|
|
356
371
|
error: {
|
|
357
372
|
operation: string;
|
|
@@ -537,11 +552,16 @@ declare class CacheStack extends EventEmitter {
|
|
|
537
552
|
private readonly keyDiscovery;
|
|
538
553
|
private readonly fetchRateLimiter;
|
|
539
554
|
private readonly snapshotSerializer;
|
|
555
|
+
private readonly invalidation;
|
|
556
|
+
private readonly layerWriter;
|
|
557
|
+
private readonly snapshots;
|
|
540
558
|
private readonly backgroundRefreshes;
|
|
559
|
+
private readonly backgroundRefreshAbort;
|
|
541
560
|
private readonly layerDegradedUntil;
|
|
542
561
|
private readonly maintenance;
|
|
543
562
|
private readonly ttlResolver;
|
|
544
563
|
private readonly circuitBreakerManager;
|
|
564
|
+
private nextOperationId;
|
|
545
565
|
private currentGeneration?;
|
|
546
566
|
private isDisconnecting;
|
|
547
567
|
private disconnectPromise?;
|
|
@@ -638,8 +658,6 @@ declare class CacheStack extends EventEmitter {
|
|
|
638
658
|
private readFromLayers;
|
|
639
659
|
private readLayerEntry;
|
|
640
660
|
private backfill;
|
|
641
|
-
private writeAcrossLayers;
|
|
642
|
-
private executeLayerOperations;
|
|
643
661
|
private resolveFreshTtl;
|
|
644
662
|
private resolveLayerSeconds;
|
|
645
663
|
private shouldNegativeCache;
|
|
@@ -654,6 +672,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
654
672
|
private sleep;
|
|
655
673
|
private withTimeout;
|
|
656
674
|
private shouldBroadcastL1Invalidation;
|
|
675
|
+
private observeOperation;
|
|
657
676
|
private scheduleGenerationCleanup;
|
|
658
677
|
private cleanupGeneration;
|
|
659
678
|
private initializeWriteBehind;
|
|
@@ -661,12 +680,9 @@ declare class CacheStack extends EventEmitter {
|
|
|
661
680
|
private enqueueWriteBehind;
|
|
662
681
|
private flushWriteBehindQueue;
|
|
663
682
|
private runWriteBehindBatch;
|
|
664
|
-
private buildLayerSetEntry;
|
|
665
|
-
private intersectKeys;
|
|
666
683
|
private qualifyKey;
|
|
667
684
|
private qualifyPattern;
|
|
668
685
|
private stripQualifiedKey;
|
|
669
|
-
private deleteKeysFromLayers;
|
|
670
686
|
private validateConfiguration;
|
|
671
687
|
private validateWriteOptions;
|
|
672
688
|
private assertActive;
|
|
@@ -679,14 +695,9 @@ declare class CacheStack extends EventEmitter {
|
|
|
679
695
|
private recordCircuitFailure;
|
|
680
696
|
private isNegativeStoredValue;
|
|
681
697
|
private emitError;
|
|
682
|
-
private isCacheSnapshotEntries;
|
|
683
|
-
private sanitizeSnapshotValue;
|
|
684
698
|
private snapshotMaxBytes;
|
|
685
699
|
private snapshotMaxEntries;
|
|
686
700
|
private invalidationMaxKeys;
|
|
687
|
-
private collectKeysForTag;
|
|
688
|
-
private assertWithinInvalidationKeyLimit;
|
|
689
|
-
private visitExportEntries;
|
|
690
701
|
}
|
|
691
702
|
|
|
692
703
|
interface HonoLikeRequest {
|
|
@@ -352,6 +352,21 @@ interface CacheStackEvents {
|
|
|
352
352
|
warm: {
|
|
353
353
|
key: string;
|
|
354
354
|
};
|
|
355
|
+
/** Fired immediately before a high-level cache operation begins. */
|
|
356
|
+
'operation-start': {
|
|
357
|
+
id: number;
|
|
358
|
+
name: string;
|
|
359
|
+
attributes?: Record<string, unknown>;
|
|
360
|
+
};
|
|
361
|
+
/** Fired after a high-level cache operation finishes. */
|
|
362
|
+
'operation-end': {
|
|
363
|
+
id: number;
|
|
364
|
+
name: string;
|
|
365
|
+
attributes?: Record<string, unknown>;
|
|
366
|
+
success: boolean;
|
|
367
|
+
result?: 'null';
|
|
368
|
+
error?: unknown;
|
|
369
|
+
};
|
|
355
370
|
/** Fired when an error occurs (layer failure, circuit breaker, etc.). */
|
|
356
371
|
error: {
|
|
357
372
|
operation: string;
|
|
@@ -537,11 +552,16 @@ declare class CacheStack extends EventEmitter {
|
|
|
537
552
|
private readonly keyDiscovery;
|
|
538
553
|
private readonly fetchRateLimiter;
|
|
539
554
|
private readonly snapshotSerializer;
|
|
555
|
+
private readonly invalidation;
|
|
556
|
+
private readonly layerWriter;
|
|
557
|
+
private readonly snapshots;
|
|
540
558
|
private readonly backgroundRefreshes;
|
|
559
|
+
private readonly backgroundRefreshAbort;
|
|
541
560
|
private readonly layerDegradedUntil;
|
|
542
561
|
private readonly maintenance;
|
|
543
562
|
private readonly ttlResolver;
|
|
544
563
|
private readonly circuitBreakerManager;
|
|
564
|
+
private nextOperationId;
|
|
545
565
|
private currentGeneration?;
|
|
546
566
|
private isDisconnecting;
|
|
547
567
|
private disconnectPromise?;
|
|
@@ -638,8 +658,6 @@ declare class CacheStack extends EventEmitter {
|
|
|
638
658
|
private readFromLayers;
|
|
639
659
|
private readLayerEntry;
|
|
640
660
|
private backfill;
|
|
641
|
-
private writeAcrossLayers;
|
|
642
|
-
private executeLayerOperations;
|
|
643
661
|
private resolveFreshTtl;
|
|
644
662
|
private resolveLayerSeconds;
|
|
645
663
|
private shouldNegativeCache;
|
|
@@ -654,6 +672,7 @@ declare class CacheStack extends EventEmitter {
|
|
|
654
672
|
private sleep;
|
|
655
673
|
private withTimeout;
|
|
656
674
|
private shouldBroadcastL1Invalidation;
|
|
675
|
+
private observeOperation;
|
|
657
676
|
private scheduleGenerationCleanup;
|
|
658
677
|
private cleanupGeneration;
|
|
659
678
|
private initializeWriteBehind;
|
|
@@ -661,12 +680,9 @@ declare class CacheStack extends EventEmitter {
|
|
|
661
680
|
private enqueueWriteBehind;
|
|
662
681
|
private flushWriteBehindQueue;
|
|
663
682
|
private runWriteBehindBatch;
|
|
664
|
-
private buildLayerSetEntry;
|
|
665
|
-
private intersectKeys;
|
|
666
683
|
private qualifyKey;
|
|
667
684
|
private qualifyPattern;
|
|
668
685
|
private stripQualifiedKey;
|
|
669
|
-
private deleteKeysFromLayers;
|
|
670
686
|
private validateConfiguration;
|
|
671
687
|
private validateWriteOptions;
|
|
672
688
|
private assertActive;
|
|
@@ -679,14 +695,9 @@ declare class CacheStack extends EventEmitter {
|
|
|
679
695
|
private recordCircuitFailure;
|
|
680
696
|
private isNegativeStoredValue;
|
|
681
697
|
private emitError;
|
|
682
|
-
private isCacheSnapshotEntries;
|
|
683
|
-
private sanitizeSnapshotValue;
|
|
684
698
|
private snapshotMaxBytes;
|
|
685
699
|
private snapshotMaxEntries;
|
|
686
700
|
private invalidationMaxKeys;
|
|
687
|
-
private collectKeysForTag;
|
|
688
|
-
private assertWithinInvalidationKeyLimit;
|
|
689
|
-
private visitExportEntries;
|
|
690
701
|
}
|
|
691
702
|
|
|
692
703
|
interface HonoLikeRequest {
|
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-BXWTKlI1.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-BXWTKlI1.js';
|
|
2
2
|
import 'node:events';
|