layercache 1.3.0 → 1.3.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 CHANGED
@@ -15,7 +15,7 @@
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-431_passing-brightgreen" alt="tests">
18
+ <img src="https://img.shields.io/badge/tests-467_passing-brightgreen" alt="tests">
19
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
 
@@ -182,7 +182,6 @@ layercache plugs into the frameworks you already use:
182
182
  | **Hono** | `createHonoCacheMiddleware(cache, opts)` - edge-compatible middleware |
183
183
  | **tRPC** | `createTrpcCacheMiddleware(cache, prefix, opts)` - procedure middleware |
184
184
  | **GraphQL** | `cacheGraphqlResolver(cache, prefix, resolver, opts)` - field resolver wrapper |
185
- | **NestJS** | `@cachestack/nestjs` - `CacheStackModule.forRoot()`, `@Cacheable()` decorator |
186
185
  | **Next.js** | Works natively with App Router and API routes |
187
186
  | **OpenTelemetry** | `createOpenTelemetryPlugin(cache, tracer)` - event-driven tracing spans without monkey-patching |
188
187
 
@@ -205,42 +204,6 @@ app.get('/api/users', createExpressCacheMiddleware(cache, {
205
204
 
206
205
  </details>
207
206
 
208
- <details>
209
- <summary><b>NestJS example</b></summary>
210
-
211
- ```bash
212
- npm install @cachestack/nestjs
213
- ```
214
-
215
- ```ts
216
- // app.module.ts
217
- import { CacheStackModule } from '@cachestack/nestjs'
218
-
219
- @Module({
220
- imports: [
221
- CacheStackModule.forRoot({
222
- layers: [
223
- new MemoryLayer({ ttl: 20 }),
224
- new RedisLayer({ client: redis, ttl: 300 })
225
- ]
226
- })
227
- ]
228
- })
229
- export class AppModule {}
230
-
231
- // user.service.ts
232
- @Injectable()
233
- export class UserService {
234
- constructor(@InjectCacheStack() private readonly cache: CacheStack) {}
235
-
236
- async getUser(id: number) {
237
- return this.cache.get(`user:${id}`, () => this.db.findUser(id))
238
- }
239
- }
240
- ```
241
-
242
- </details>
243
-
244
207
  <details>
245
208
  <summary><b>Next.js App Router example</b></summary>
246
209
 
@@ -326,18 +289,7 @@ const cache = new CacheStack(
326
289
  └─────────────────────┴────────┘
327
290
  ```
328
291
 
329
- Run benchmarks locally:
330
-
331
- ```bash
332
- npm run bench:direct
333
- npm run bench:edge
334
- npm run bench:slow-redis
335
- npm run bench:queue-amplification
336
- npm run bench:http
337
- npm run bench:multi-process-fanout
338
- ```
339
-
340
- The benchmark harness defaults to `/root/cache-test/data/users.json` so the in-repo scripts stay aligned with the external reproduction workspace. Set `LAYERCACHE_BENCH_FIXTURE_PATH` if you want to point at a different workload fixture.
292
+ Benchmark commands, fixtures, and scenario notes live in [docs/benchmarking.md](./docs/benchmarking.md).
341
293
 
342
294
  ---
343
295
 
@@ -359,7 +311,6 @@ The benchmark harness defaults to `/root/cache-test/data/users.json` so the in-r
359
311
  | Persistence / snapshots | -- | -- | -- | **Yes** |
360
312
  | Compression | -- | -- | Yes | **Yes** |
361
313
  | Admin CLI | -- | -- | -- | **Yes** |
362
- | NestJS module | -- | -- | -- | **Yes** |
363
314
  | TypeScript-first | Partial | Yes | Yes | **Yes** |
364
315
  | Wrap / decorator API | Yes | -- | -- | **Yes** |
365
316
  | Namespaces | -- | Yes | Yes | **Yes** |
@@ -388,7 +339,6 @@ The benchmark harness defaults to `/root/cache-test/data/users.json` so the in-r
388
339
  The [`examples/`](./examples) directory contains ready-to-run projects:
389
340
 
390
341
  - [`express-api/`](./examples/express-api/) - Express REST API with layered caching
391
- - [`nestjs-module/`](./examples/nestjs-module/) - NestJS module integration
392
342
  - [`nextjs-api-routes/`](./examples/nextjs-api-routes/) - Next.js App Router with layercache
393
343
 
394
344
  ---
package/dist/cli.cjs CHANGED
@@ -365,6 +365,18 @@ async function main(argv = process.argv.slice(2)) {
365
365
  process.exitCode = 1;
366
366
  return;
367
367
  }
368
+ if (isPlaintextRedisUrl(redisUrl)) {
369
+ if (args.requireTls) {
370
+ process.stderr.write(
371
+ "Error: --require-tls is set but the URL uses redis:// (plaintext). Use rediss:// for TLS-encrypted connections.\n"
372
+ );
373
+ process.exitCode = 1;
374
+ return;
375
+ }
376
+ process.stderr.write(
377
+ "Warning: connecting to Redis without TLS (redis://). All data including cached values and credentials will be transmitted in plaintext. Use rediss:// in production environments, or set --require-tls.\n"
378
+ );
379
+ }
368
380
  const redis = new import_ioredis.default(redisUrl, {
369
381
  connectTimeout: CONNECT_TIMEOUT_MS,
370
382
  lazyConnect: true,
@@ -484,6 +496,8 @@ function parseArgs(argv) {
484
496
  } else if (token === "--tag-index-prefix") {
485
497
  parsed.tagIndexPrefix = value;
486
498
  index += 1;
499
+ } else if (token === "--require-tls") {
500
+ parsed.requireTls = true;
487
501
  }
488
502
  }
489
503
  return parsed;
@@ -515,7 +529,7 @@ async function scanKeys(redis, pattern) {
515
529
  }
516
530
  function printUsage() {
517
531
  process.stdout.write(
518
- "Usage:\n layercache stats --redis <url> [--pattern <glob>]\n layercache keys --redis <url> [--pattern <glob>]\n layercache inspect --redis <url> --key <key>\n layercache invalidate --redis <url> [--pattern <glob> | --tag <tag>] [--tag-index-prefix <prefix>]\n\nOptions:\n --redis <url> Redis connection URL (e.g. redis://localhost:6379)\n --pattern <glob> Glob pattern to filter keys (default: *)\n --key <key> Exact cache key to inspect\n --tag <tag> Invalidate by tag name\n --tag-index-prefix <prefix> Redis key prefix for tag index (default: layercache:tag-index)\n"
532
+ "Usage:\n layercache stats --redis <url> [--pattern <glob>]\n layercache keys --redis <url> [--pattern <glob>]\n layercache inspect --redis <url> --key <key>\n layercache invalidate --redis <url> [--pattern <glob> | --tag <tag>] [--tag-index-prefix <prefix>]\n\nOptions:\n --redis <url> Redis connection URL (e.g. redis://localhost:6379)\n --pattern <glob> Glob pattern to filter keys (default: *)\n --key <key> Exact cache key to inspect\n --tag <tag> Invalidate by tag name\n --tag-index-prefix <prefix> Redis key prefix for tag index (default: layercache:tag-index)\n --require-tls Reject non-TLS (redis://) connections\n"
519
533
  );
520
534
  }
521
535
  function decodeInspectablePayload(payload) {
@@ -541,6 +555,14 @@ function summarizeInspectableValue(value) {
541
555
  }
542
556
  return value;
543
557
  }
558
+ function isPlaintextRedisUrl(url) {
559
+ try {
560
+ const parsed = new URL(url);
561
+ return parsed.protocol === "redis:";
562
+ } catch {
563
+ return true;
564
+ }
565
+ }
544
566
  function maskRedisUrl(url) {
545
567
  try {
546
568
  const parsed = new URL(url);
package/dist/cli.js CHANGED
@@ -23,6 +23,18 @@ async function main(argv = process.argv.slice(2)) {
23
23
  process.exitCode = 1;
24
24
  return;
25
25
  }
26
+ if (isPlaintextRedisUrl(redisUrl)) {
27
+ if (args.requireTls) {
28
+ process.stderr.write(
29
+ "Error: --require-tls is set but the URL uses redis:// (plaintext). Use rediss:// for TLS-encrypted connections.\n"
30
+ );
31
+ process.exitCode = 1;
32
+ return;
33
+ }
34
+ process.stderr.write(
35
+ "Warning: connecting to Redis without TLS (redis://). All data including cached values and credentials will be transmitted in plaintext. Use rediss:// in production environments, or set --require-tls.\n"
36
+ );
37
+ }
26
38
  const redis = new Redis(redisUrl, {
27
39
  connectTimeout: CONNECT_TIMEOUT_MS,
28
40
  lazyConnect: true,
@@ -142,6 +154,8 @@ function parseArgs(argv) {
142
154
  } else if (token === "--tag-index-prefix") {
143
155
  parsed.tagIndexPrefix = value;
144
156
  index += 1;
157
+ } else if (token === "--require-tls") {
158
+ parsed.requireTls = true;
145
159
  }
146
160
  }
147
161
  return parsed;
@@ -173,7 +187,7 @@ async function scanKeys(redis, pattern) {
173
187
  }
174
188
  function printUsage() {
175
189
  process.stdout.write(
176
- "Usage:\n layercache stats --redis <url> [--pattern <glob>]\n layercache keys --redis <url> [--pattern <glob>]\n layercache inspect --redis <url> --key <key>\n layercache invalidate --redis <url> [--pattern <glob> | --tag <tag>] [--tag-index-prefix <prefix>]\n\nOptions:\n --redis <url> Redis connection URL (e.g. redis://localhost:6379)\n --pattern <glob> Glob pattern to filter keys (default: *)\n --key <key> Exact cache key to inspect\n --tag <tag> Invalidate by tag name\n --tag-index-prefix <prefix> Redis key prefix for tag index (default: layercache:tag-index)\n"
190
+ "Usage:\n layercache stats --redis <url> [--pattern <glob>]\n layercache keys --redis <url> [--pattern <glob>]\n layercache inspect --redis <url> --key <key>\n layercache invalidate --redis <url> [--pattern <glob> | --tag <tag>] [--tag-index-prefix <prefix>]\n\nOptions:\n --redis <url> Redis connection URL (e.g. redis://localhost:6379)\n --pattern <glob> Glob pattern to filter keys (default: *)\n --key <key> Exact cache key to inspect\n --tag <tag> Invalidate by tag name\n --tag-index-prefix <prefix> Redis key prefix for tag index (default: layercache:tag-index)\n --require-tls Reject non-TLS (redis://) connections\n"
177
191
  );
178
192
  }
179
193
  function decodeInspectablePayload(payload) {
@@ -199,6 +213,14 @@ function summarizeInspectableValue(value) {
199
213
  }
200
214
  return value;
201
215
  }
216
+ function isPlaintextRedisUrl(url) {
217
+ try {
218
+ const parsed = new URL(url);
219
+ return parsed.protocol === "redis:";
220
+ } catch {
221
+ return true;
222
+ }
223
+ }
202
224
  function maskRedisUrl(url) {
203
225
  try {
204
226
  const parsed = new URL(url);
@@ -178,6 +178,8 @@ interface CacheStackOptions {
178
178
  logger?: CacheLogger | boolean;
179
179
  metrics?: boolean;
180
180
  stampedePrevention?: boolean;
181
+ stampedeMaxInFlight?: number;
182
+ stampedeEntryTimeoutMs?: number;
181
183
  invalidationBus?: InvalidationBus;
182
184
  tagIndex?: CacheTagIndex;
183
185
  generation?: number;
@@ -178,6 +178,8 @@ interface CacheStackOptions {
178
178
  logger?: CacheLogger | boolean;
179
179
  metrics?: boolean;
180
180
  stampedePrevention?: boolean;
181
+ stampedeMaxInFlight?: number;
182
+ stampedeEntryTimeoutMs?: number;
181
183
  invalidationBus?: InvalidationBus;
182
184
  tagIndex?: CacheTagIndex;
183
185
  generation?: number;
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-BXWTKlI1.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-CUHTP9Bc.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-BXWTKlI1.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-CUHTP9Bc.js';
2
2
  import 'node:events';