layercache 1.2.6 → 1.2.8

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 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-328_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" alt="Coveralls"></a>
18
+ <img src="https://img.shields.io/badge/tests-397_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=20260409" 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** | Distributed tracing support |
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)` - distributed tracing spans |
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 ?? "*");
@@ -528,6 +528,17 @@ function summarizeInspectableValue(value) {
528
528
  }
529
529
  return value;
530
530
  }
531
+ function maskRedisUrl(url) {
532
+ try {
533
+ const parsed = new URL(url);
534
+ if (parsed.password) {
535
+ parsed.password = "***";
536
+ }
537
+ return parsed.toString();
538
+ } catch {
539
+ return url.replace(/:([^@/]+)@/, ":***@");
540
+ }
541
+ }
531
542
  if (process.argv[1]?.includes("cli.")) {
532
543
  void main();
533
544
  }
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 ?? "*");
@@ -186,6 +186,17 @@ function summarizeInspectableValue(value) {
186
186
  }
187
187
  return value;
188
188
  }
189
+ function maskRedisUrl(url) {
190
+ try {
191
+ const parsed = new URL(url);
192
+ if (parsed.password) {
193
+ parsed.password = "***";
194
+ }
195
+ return parsed.toString();
196
+ } catch {
197
+ return url.replace(/:([^@/]+)@/, ":***@");
198
+ }
199
+ }
189
200
  if (process.argv[1]?.includes("cli.")) {
190
201
  void main();
191
202
  }
@@ -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,17 +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;
541
559
  private readonly layerDegradedUntil;
542
- private readonly keyEpochs;
560
+ private readonly maintenance;
543
561
  private readonly ttlResolver;
544
562
  private readonly circuitBreakerManager;
563
+ private nextOperationId;
545
564
  private currentGeneration?;
546
- private readonly writeBehindQueue;
547
- private writeBehindTimer?;
548
- private writeBehindFlushPromise?;
549
- private generationCleanupPromise?;
550
- private clearEpoch;
551
565
  private isDisconnecting;
552
566
  private disconnectPromise?;
553
567
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
@@ -643,8 +657,6 @@ declare class CacheStack extends EventEmitter {
643
657
  private readFromLayers;
644
658
  private readLayerEntry;
645
659
  private backfill;
646
- private writeAcrossLayers;
647
- private executeLayerOperations;
648
660
  private resolveFreshTtl;
649
661
  private resolveLayerSeconds;
650
662
  private shouldNegativeCache;
@@ -659,25 +671,17 @@ declare class CacheStack extends EventEmitter {
659
671
  private sleep;
660
672
  private withTimeout;
661
673
  private shouldBroadcastL1Invalidation;
662
- private shouldCleanupGenerations;
663
- private generationCleanupBatchSize;
674
+ private observeOperation;
664
675
  private scheduleGenerationCleanup;
665
676
  private cleanupGeneration;
666
677
  private initializeWriteBehind;
667
678
  private shouldWriteBehind;
668
- private beginClearEpoch;
669
- private currentKeyEpoch;
670
- private bumpKeyEpochs;
671
- private isWriteOutdated;
672
679
  private enqueueWriteBehind;
673
680
  private flushWriteBehindQueue;
674
- private buildLayerSetEntry;
675
- private intersectKeys;
681
+ private runWriteBehindBatch;
676
682
  private qualifyKey;
677
683
  private qualifyPattern;
678
684
  private stripQualifiedKey;
679
- private generationPrefix;
680
- private deleteKeysFromLayers;
681
685
  private validateConfiguration;
682
686
  private validateWriteOptions;
683
687
  private assertActive;
@@ -690,14 +694,9 @@ declare class CacheStack extends EventEmitter {
690
694
  private recordCircuitFailure;
691
695
  private isNegativeStoredValue;
692
696
  private emitError;
693
- private isCacheSnapshotEntries;
694
- private sanitizeSnapshotValue;
695
697
  private snapshotMaxBytes;
696
698
  private snapshotMaxEntries;
697
699
  private invalidationMaxKeys;
698
- private collectKeysForTag;
699
- private assertWithinInvalidationKeyLimit;
700
- private visitExportEntries;
701
700
  }
702
701
 
703
702
  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,17 +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;
541
559
  private readonly layerDegradedUntil;
542
- private readonly keyEpochs;
560
+ private readonly maintenance;
543
561
  private readonly ttlResolver;
544
562
  private readonly circuitBreakerManager;
563
+ private nextOperationId;
545
564
  private currentGeneration?;
546
- private readonly writeBehindQueue;
547
- private writeBehindTimer?;
548
- private writeBehindFlushPromise?;
549
- private generationCleanupPromise?;
550
- private clearEpoch;
551
565
  private isDisconnecting;
552
566
  private disconnectPromise?;
553
567
  constructor(layers: CacheLayer[], options?: CacheStackOptions);
@@ -643,8 +657,6 @@ declare class CacheStack extends EventEmitter {
643
657
  private readFromLayers;
644
658
  private readLayerEntry;
645
659
  private backfill;
646
- private writeAcrossLayers;
647
- private executeLayerOperations;
648
660
  private resolveFreshTtl;
649
661
  private resolveLayerSeconds;
650
662
  private shouldNegativeCache;
@@ -659,25 +671,17 @@ declare class CacheStack extends EventEmitter {
659
671
  private sleep;
660
672
  private withTimeout;
661
673
  private shouldBroadcastL1Invalidation;
662
- private shouldCleanupGenerations;
663
- private generationCleanupBatchSize;
674
+ private observeOperation;
664
675
  private scheduleGenerationCleanup;
665
676
  private cleanupGeneration;
666
677
  private initializeWriteBehind;
667
678
  private shouldWriteBehind;
668
- private beginClearEpoch;
669
- private currentKeyEpoch;
670
- private bumpKeyEpochs;
671
- private isWriteOutdated;
672
679
  private enqueueWriteBehind;
673
680
  private flushWriteBehindQueue;
674
- private buildLayerSetEntry;
675
- private intersectKeys;
681
+ private runWriteBehindBatch;
676
682
  private qualifyKey;
677
683
  private qualifyPattern;
678
684
  private stripQualifiedKey;
679
- private generationPrefix;
680
- private deleteKeysFromLayers;
681
685
  private validateConfiguration;
682
686
  private validateWriteOptions;
683
687
  private assertActive;
@@ -690,14 +694,9 @@ declare class CacheStack extends EventEmitter {
690
694
  private recordCircuitFailure;
691
695
  private isNegativeStoredValue;
692
696
  private emitError;
693
- private isCacheSnapshotEntries;
694
- private sanitizeSnapshotValue;
695
697
  private snapshotMaxBytes;
696
698
  private snapshotMaxEntries;
697
699
  private invalidationMaxKeys;
698
- private collectKeysForTag;
699
- private assertWithinInvalidationKeyLimit;
700
- private visitExportEntries;
701
700
  }
702
701
 
703
702
  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-DLstcDMn.cjs';
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-DBs8Ko5W.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-DLstcDMn.js';
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-DBs8Ko5W.js';
2
2
  import 'node:events';