@nestjs-pipeline/cache 0.3.0 → 0.4.1

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.
Files changed (39) hide show
  1. package/README.md +4 -537
  2. package/dist/adapters/cache-manager.adapter.js +3 -7
  3. package/dist/adapters/cache-manager.adapter.js.map +1 -1
  4. package/dist/cache.behavior.d.ts +2 -2
  5. package/dist/cache.behavior.d.ts.map +1 -1
  6. package/dist/cache.behavior.js +23 -26
  7. package/dist/cache.behavior.js.map +1 -1
  8. package/dist/cache.module.d.ts +1 -1
  9. package/dist/cache.module.d.ts.map +1 -1
  10. package/dist/cache.module.js +20 -23
  11. package/dist/cache.module.js.map +1 -1
  12. package/dist/constants/tokens.d.ts +1 -1
  13. package/dist/constants/tokens.js +4 -7
  14. package/dist/constants/tokens.js.map +1 -1
  15. package/dist/errors/missing-partition.error.js +2 -6
  16. package/dist/errors/missing-partition.error.js.map +1 -1
  17. package/dist/helpers/build-attributes.d.ts +2 -1
  18. package/dist/helpers/build-attributes.d.ts.map +1 -1
  19. package/dist/helpers/build-attributes.js +7 -9
  20. package/dist/helpers/build-attributes.js.map +1 -1
  21. package/dist/helpers/cache-factory.d.ts +1 -1
  22. package/dist/helpers/cache-factory.d.ts.map +1 -1
  23. package/dist/helpers/cache-factory.js +11 -13
  24. package/dist/helpers/cache-factory.js.map +1 -1
  25. package/dist/helpers/cache-key.d.ts +1 -1
  26. package/dist/helpers/cache-key.d.ts.map +1 -1
  27. package/dist/helpers/cache-key.js +10 -13
  28. package/dist/helpers/cache-key.js.map +1 -1
  29. package/dist/helpers/cache.intent.d.ts +2 -2
  30. package/dist/helpers/cache.intent.d.ts.map +1 -1
  31. package/dist/helpers/cache.intent.js +3 -6
  32. package/dist/helpers/cache.intent.js.map +1 -1
  33. package/dist/index.d.ts +10 -10
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +9 -27
  36. package/dist/index.js.map +1 -1
  37. package/dist/interfaces/cache-options.interface.js +1 -2
  38. package/dist/interfaces/cache-options.interface.js.map +1 -1
  39. package/package.json +12 -4
package/README.md CHANGED
@@ -1,57 +1,11 @@
1
1
  # @nestjs-pipeline/cache
2
2
 
3
- ## Architectural role
4
-
5
- This reusable package caches final application query results, including expensive
6
- aggregations, read models combining repositories and external-service composition.
7
- It complements repository snapshot/read-through caches; it is not replaced by
8
- them just because a particular example currently uses repository caching. Choose
9
- the layer that owns the result and define its dependencies, security scope and
10
- freshness. Using both layers is optional, and invalidation is not automatically
11
- shared.
12
-
13
3
  [![npm version](https://img.shields.io/npm/v/@nestjs-pipeline/cache.svg)](https://www.npmjs.com/package/@nestjs-pipeline/cache)
14
4
  [![License](https://img.shields.io/npm/l/@nestjs-pipeline/cache.svg)](https://www.npmjs.com/package/@nestjs-pipeline/cache)
15
5
 
16
6
  Caching behavior for `@nestjs-pipeline/core`, powered by [cache-manager](https://www.npmjs.com/package/cache-manager) v7 on top of [Keyv](https://keyv.org/). Transparently cache query results — declaratively, with zero changes to your handler code — and choose any backend: **memory** (default), **redis**, **memcache**, **sqlite**, or **postgres**.
17
7
 
18
- ---
19
-
20
- ## Table of Contents
21
-
22
- - [Why](#why)
23
- - [Installation](#installation)
24
- - [Quick Start](#quick-start)
25
- - [1. Register the module](#1-register-the-module)
26
- - [2. Attach the behavior](#2-attach-the-behavior)
27
- - [3. Configure per handler](#3-configure-per-handler)
28
- - [Choosing a Store](#choosing-a-store)
29
- - [Memory (default)](#memory-default)
30
- - [Redis](#redis)
31
- - [Memcache](#memcache)
32
- - [SQLite](#sqlite)
33
- - [Postgres](#postgres)
34
- - [Tiered (multi-layer) caches](#tiered-multi-layer-caches)
35
- - [Escape hatches](#escape-hatches)
36
- - [How It Works](#how-it-works)
37
- - [What gets cached](#what-gets-cached)
38
- - [Cache keys](#cache-keys)
39
- - [Tenant partitioning](#tenant-partitioning)
40
- - [Options resolution](#options-resolution)
41
- - [Context items](#context-items)
42
- - [Configuration](#configuration)
43
- - [Behavior Contract & Bootstrap Diagnostics](#behavior-contract--bootstrap-diagnostics)
44
- - [Custom Logger](#custom-logger)
45
- - [API Reference](#api-reference)
46
- - [License](#license)
47
-
48
- ---
49
-
50
- ## Why
51
-
52
- Read-heavy queries often hit the same data repeatedly. `@nestjs-pipeline/cache` adds a transparent caching layer to your CQRS pipeline without coupling the caching logic to your business code. It is a thin, type-safe behavior over [cache-manager](https://github.com/jaredwray/cacheable) v7 + [Keyv](https://keyv.org/), so you get tiered caches and a consistent interface across every supported backend.
53
-
54
- ---
8
+ **Documentation:** [guide](https://aristoteliss.github.io/nestjs-pipeline/packages/nestjs-pipeline/cache/) · [API reference](https://aristoteliss.github.io/nestjs-pipeline/api/nestjs-pipeline/cache/) · [all packages](https://aristoteliss.github.io/nestjs-pipeline/)
55
9
 
56
10
  ## Installation
57
11
 
@@ -65,9 +19,11 @@ pnpm add @nestjs-pipeline/cache cache-manager keyv
65
19
  pnpm add @nestjs-pipeline/core @nestjs/common reflect-metadata
66
20
  ```
67
21
 
68
- Requires Node.js 22.12 or later, `@nestjs/common` `^12.1.0`, `@nestjs-pipeline/core` `^0.3.0`,
22
+ Requires Node.js 22.12 or later, `@nestjs/common` `^12.1.0`, `@nestjs-pipeline/core` `^0.4.1`,
69
23
  `cache-manager` `^7.0.0` and `keyv` `^5.0.0`.
70
24
 
25
+ Published as an ES module; a CommonJS application loads it with `require()`. Coming from 0.3.x, see [Upgrading from 0.3.x](https://aristoteliss.github.io/nestjs-pipeline/upgrading/from-0-3/).
26
+
71
27
  **Optional store adapters** — install only the one(s) you use:
72
28
 
73
29
  ```bash
@@ -77,495 +33,6 @@ pnpm add @keyv/sqlite # type: 'sqlite'
77
33
  pnpm add @keyv/postgres # type: 'postgres'
78
34
  ```
79
35
 
80
- > The `memory` store needs no adapter — it ships with Keyv. The other backends are loaded lazily; you only need the matching `@keyv/*` package when you actually select that store type.
81
-
82
- ---
83
-
84
- ## Quick Start
85
-
86
- ### 1. Register the module
87
-
88
- ```ts
89
- import { Module } from '@nestjs/common';
90
- import { PipelineModule } from '@nestjs-pipeline/core';
91
- import { CacheModule, CacheBehavior } from '@nestjs-pipeline/cache';
92
-
93
- @Module({
94
- imports: [
95
- // In-memory cache with a 30s default TTL
96
- CacheModule.forRoot({ ttl: 30_000 }),
97
- // Make CacheBehavior available to @UsePipeline/globalBehaviors.
98
- PipelineModule.forRoot({ behaviors: [CacheBehavior] }),
99
- ],
100
- })
101
- export class AppModule {}
102
- ```
103
-
104
- The cache is built when the application instantiates its providers, once per
105
- application. To derive store settings from injected configuration, use
106
- `forRootAsync`:
107
-
108
- ```ts
109
- CacheModule.forRootAsync({
110
- inject: [ConfigService],
111
- useFactory: (config: ConfigService) => ({
112
- store: { type: 'redis', url: config.getOrThrow('REDIS_URL') },
113
- ttl: 30_000,
114
- }),
115
- });
116
- ```
117
-
118
- A cache built from `store` (or the default memory store) is disconnected on
119
- application shutdown (`app.close()` or Nest shutdown hooks). A supplied `cache` or
120
- `stores` belongs to the caller, who closes it.
121
-
122
- ### 2. Attach the behavior
123
-
124
- The `behaviors` option above registers `CacheBehavior` with Nest DI; it does not execute it globally. Attach it per handler with `@UsePipeline`, or put it in `globalBehaviors` if you want it to run for a global scope.
125
-
126
- ### 3. Configure per handler
127
-
128
- This protected-query fragment assumes the pipeline has a tenant (`context.tenantId`,
129
- see [Tenant partitioning](#tenant-partitioning)) and that an upstream behavior sets
130
- `currentUserId` and `capabilityVersion` on `context.items` before caching runs.
131
- The handler still performs entity and field authorization on cache misses.
132
-
133
- ```ts
134
- import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
135
- import { UsePipeline } from '@nestjs-pipeline/core';
136
- import { cache, createPartitionedCacheKeyFactory } from '@nestjs-pipeline/cache';
137
-
138
- @QueryHandler(GetUserQuery)
139
- @UsePipeline(cache({
140
- ttl: 60_000,
141
- key: createPartitionedCacheKeyFactory({
142
- principal: (ctx) => ctx.items.get('currentUserId') as string | undefined,
143
- scope: (ctx) => {
144
- const scope = ctx.items.get('capabilityVersion');
145
- if (typeof scope !== 'string' || !scope.trim()) {
146
- throw new Error('Missing capability version');
147
- }
148
- return scope;
149
- },
150
- }),
151
- }))
152
- export class GetUserHandler implements IQueryHandler<GetUserQuery> {
153
- async execute(query: GetUserQuery) {
154
- // ...expensive read; result cached for 60s
155
- }
156
- }
157
- ```
158
-
159
- > Use `cache({ inheritModuleKey: true })` only when the module supplies the key factory.
160
- > The raw tuple form `@UsePipeline([CacheBehavior, { ... }])` remains supported as an escape hatch.
161
-
162
- ---
163
-
164
- ## Choosing a Store
165
-
166
- The backend is selected once, when registering the module. Per-handler options
167
- (`ttl`, `key`, `condition`, `kinds`) are independent of the store you pick.
168
-
169
- ### Memory (default)
170
-
171
- ```ts
172
- CacheModule.forRoot({ ttl: 30_000 });
173
- // equivalent to:
174
- CacheModule.forRoot({ store: { type: 'memory' }, ttl: 30_000 });
175
- ```
176
-
177
- ### Redis
178
-
179
- ```ts
180
- CacheModule.forRoot({
181
- store: { type: 'redis', url: 'redis://localhost:6379' },
182
- ttl: 60_000,
183
- });
184
- ```
185
-
186
- ### Memcache
187
-
188
- ```ts
189
- CacheModule.forRoot({
190
- store: { type: 'memcache', url: 'localhost:11211' },
191
- });
192
- ```
193
-
194
- ### SQLite
195
-
196
- ```ts
197
- CacheModule.forRoot({
198
- store: { type: 'sqlite', url: 'sqlite://./cache.sqlite' },
199
- });
200
- ```
201
-
202
- ### Postgres
203
-
204
- ```ts
205
- CacheModule.forRoot({
206
- store: {
207
- type: 'postgres',
208
- url: 'postgresql://user:pass@localhost:5432/db',
209
- options: { table: 'cache' },
210
- },
211
- });
212
- ```
213
-
214
- ### Tiered (multi-layer) caches
215
-
216
- Provide an array of stores — they are checked in order (fastest first) and
217
- writes fan out to every layer:
218
-
219
- ```ts
220
- CacheModule.forRoot({
221
- store: [
222
- { type: 'memory', ttl: 5_000 }, // L1: in-process
223
- { type: 'redis', url: 'redis://localhost:6379' }, // L2: shared
224
- ],
225
- });
226
- ```
227
-
228
- ### Escape hatches
229
-
230
- For full control, pass a pre-built `cache-manager` instance or your own `Keyv`
231
- stores:
232
-
233
- ```ts
234
- import { createCache } from 'cache-manager';
235
- import { Keyv } from 'keyv';
236
- import KeyvRedis from '@keyv/redis';
237
-
238
- // Pre-built Keyv stores
239
- CacheModule.forRoot({
240
- stores: [new Keyv({ store: new KeyvRedis('redis://localhost:6379') })],
241
- });
242
-
243
- // Fully pre-built cache
244
- CacheModule.forRoot({ cache: createCache({ stores: [new Keyv()] }) });
245
- ```
246
-
247
- | Option | Precedence | Description |
248
- | ------ | ---------- | ----------- |
249
- | `cache` | 1 (highest) | A ready-made `cache-manager` instance. |
250
- | `stores` | 2 | Pre-built `Keyv[]` (tiered, highest priority first). |
251
- | `store` | 3 | Declarative config — a single store or an array. |
252
- | _(none)_ | 4 (fallback) | In-memory `Keyv`. |
253
-
254
- ---
255
-
256
- ## How It Works
257
-
258
- ### What gets cached
259
-
260
- Only **query** requests are cached by default — commands and events always pass
261
- through untouched. Override this with the `kinds` option. On a cache miss,
262
- `null` and `undefined` results are not written. A hit returns the value from
263
- that lookup directly. `CacheBehavior` does not use `cache-manager.wrap()` or
264
- background refresh because a refresh callback would re-run every behavior and
265
- side effect nested after the cache behavior.
266
-
267
- Cached results are JSON values. On a miss the behavior stores the JSON form of
268
- the handler result and returns that same form, so a miss and a later hit have
269
- the same shape: a `Date` is an ISO string and a class instance a plain object on
270
- both. Return plain JSON data (a snapshot or read model) from cached handlers. A
271
- result without a JSON form (a `bigint`, a cycle) is returned unchanged, not
272
- cached, and logged as a warning.
273
-
274
- ### Store errors
275
-
276
- Declaratively created stores use `throwOnErrors: true`. Pre-built `cache` and
277
- `stores` remain caller-owned and are passed through without changing their error
278
- settings. The behavior can handle only failures those implementations surface;
279
- it cannot detect backend errors they swallow.
280
-
281
- `CacheBehavior` owns a consistent failure policy independently of the injected
282
- `cache-manager` or custom cache implementation. By default, `failOpen: true`:
283
-
284
- - a thrown cache read is logged, recorded as `cache.hit = false`, and bypasses
285
- the cache write for that execution; the handler result still uses the same JSON conversion as a normal miss;
286
- - a thrown cache write is logged and the successful handler result is returned.
287
-
288
- Set `failOpen: false` to log and propagate either store error. This strict mode
289
- can turn a successful downstream handler execution into a rejected request when
290
- the subsequent cache write fails, so it is best suited to cases where cache
291
- availability is part of the operation's contract. Errors from the condition,
292
- key factory, or downstream handler are always propagated unchanged.
293
-
294
- ### Cache keys
295
-
296
- The helper emits `cache:v3:<tenant>:<principal>:<scope>:<requestName>:<sha256>`,
297
- with escaped segments and the absent-segment encoding supplied by core
298
- `joinKeySegments`. Treat the generated key as opaque. Changing from older
299
- formats causes a cold cache; let old entries expire or remove their namespace.
300
-
301
- `key` is **required**. There is deliberately no default.
302
-
303
- The previous default embedded `context.correlationId`, which is unique per
304
- request. That made the cache write an entry for every query and never read one
305
- back — two extra round-trips and unbounded store growth for a zero percent hit
306
- rate. It was not an authorization boundary either: a client can send its own
307
- correlation ID, and nested executions deliberately inherit one. Correlation metadata
308
- does not establish principal or permission isolation, as correlation IDs may be supplied
309
- or reused. Cache hits skip the handler, including entity/field checks. For protected
310
- results, provide an explicit `key` covering tenant, principal type/ID, effective
311
- permission scope and response dependencies; fail closed if required context is
312
- missing. Configure invalidation/freshness for that result separately. A type-level
313
- authorization behavior outside the cache does not reproduce every entity check.
314
-
315
- Use `createPartitionedCacheKeyFactory`. It partitions every dimension that can
316
- change an authorized response, escapes each segment so `a:b` + `c` cannot collide
317
- with `a` + `b:c`, and fails closed with `MissingCachePartitionError` when a
318
- required dimension is absent:
319
-
320
- ```typescript
321
- import { createPartitionedCacheKeyFactory } from '@nestjs-pipeline/cache';
322
-
323
- @UsePipeline([CacheBehavior, {
324
- key: createPartitionedCacheKeyFactory({
325
- principal: (ctx) => ctx.items.get('currentUserId') as string | undefined,
326
- // Include a role-set hash or capability version, otherwise a principal whose
327
- // permissions were revoked keeps reading the old response until it expires.
328
- scope: (ctx) => ctx.items.get('capabilityVersion') as string | undefined,
329
- }),
330
- }])
331
- export class GetUsersHandler {}
332
- ```
333
-
334
- For genuinely public responses that are identical for every caller:
335
-
336
- ```typescript
337
- createPartitionedCacheKeyFactory({
338
- principal: () => 'public',
339
- requirePrincipal: false,
340
- requireTenant: false,
341
- requireScope: false,
342
- });
343
- ```
344
-
345
- The permission scope is required by default: creating a factory without a
346
- `scope` resolver throws unless `requireScope: false` declares that responses do
347
- not depend on the caller's permissions, and a resolver that returns nothing
348
- throws `MissingCachePartitionError` at request time.
349
-
350
- The tenant options are the same for the cache, idempotency and rate-limit key factories
351
- (`TenantPartitionOptions` of `@nestjs-pipeline/core`): `includeTenant` (default `true`)
352
- puts the tenant in the key, and `requireTenant` (default: `includeTenant`) refuses a
353
- missing one. The three packages' partition errors extend `MissingPartitionError`.
354
-
355
- The request payload is included as a SHA-256 digest, so secrets and search terms
356
- stay out of Redis key listings. The digest is built with `stableStringify` from `@cqrs-ddd/safe-stringify` (not re-exported by this package), which
357
- sorts object keys recursively so structurally equal payloads map to the same
358
- entry. It accepts `null`, booleans, finite numbers, strings, arrays, record-like
359
- objects, and valid dates (converted to ISO strings). Lossy native JSON cases such
360
- as `Map`, `Set`, `RegExp`, `Error`, binary values, non-finite numbers,
361
- `undefined`, bigint, functions, symbols, and cycles are rejected instead of
362
- risking a collision.
363
-
364
- Because a cache hit returns before the handler runs, it also skips whatever
365
- entity-level authorization and field filtering the handler performs. That is why
366
- tenant, principal and scope are all required by the helper by default. Opt out
367
- of each explicitly with `requireTenant: false`, `requirePrincipal: false` or
368
- `requireScope: false`.
369
-
370
- A caught `MissingCachePartitionError` names the missing dimension:
371
-
372
- ```typescript
373
- import { MissingCachePartitionError } from '@nestjs-pipeline/cache';
374
-
375
- if (error instanceof MissingCachePartitionError) {
376
- // error.name === 'MissingCachePartitionError'; map it to 401/403 in your filter
377
- }
378
- ```
379
-
380
- ### Tenant partitioning
381
-
382
- The tenant segment of the key is `context.tenantId`, not a value on
383
- `context.items`. The pipeline takes it from the tenant source configured on
384
- `PipelineModule.forRoot()`; the cache package does not import
385
- `@nestjs-pipeline/tenant`:
386
-
387
- ```typescript
388
- import { Module } from '@nestjs/common';
389
- import { PipelineModule } from '@nestjs-pipeline/core';
390
- import { tenantSource } from '@nestjs-pipeline/tenant';
391
- import { CacheBehavior, CacheModule } from '@nestjs-pipeline/cache';
392
-
393
- @Module({
394
- imports: [
395
- CacheModule.forRoot({ ttl: 30_000 }),
396
- PipelineModule.forRoot({
397
- sources: { tenantId: tenantSource },
398
- behaviors: [CacheBehavior],
399
- }),
400
- ],
401
- })
402
- export class AppModule {}
403
- ```
404
-
405
- For a single-tenant deployment, leave the tenant out of the key:
406
-
407
- ```typescript
408
- createPartitionedCacheKeyFactory({
409
- includeTenant: false,
410
- principal: (ctx) => ctx.items.get('currentUserId') as string | undefined,
411
- scope: (ctx) => ctx.items.get('capabilityVersion') as string | undefined,
412
- });
413
- ```
414
-
415
- The correlation id is never part of the key (see above).
416
-
417
- ### Sharing a key factory through module defaults
418
-
419
- Set the factory once in `defaults` and opt in per handler with
420
- `inheritModuleKey: true`; `cache()` rejects a call with neither `key` nor
421
- `inheritModuleKey` at compile time:
422
-
423
- ```typescript
424
- CacheModule.forRoot({
425
- ttl: 30_000,
426
- defaults: {
427
- key: createPartitionedCacheKeyFactory({
428
- principal: (ctx) => ctx.items.get('currentUserId') as string | undefined,
429
- scope: (ctx) => ctx.items.get('capabilityVersion') as string | undefined,
430
- }),
431
- },
432
- });
433
-
434
- @QueryHandler(ListOrdersQuery)
435
- @UsePipeline(cache({ inheritModuleKey: true, ttl: 10_000 }))
436
- export class ListOrdersHandler {}
437
- ```
438
-
439
- ### Conditional caching
440
-
441
- `condition` returns `false` to bypass the cache for one request:
442
-
443
- ```typescript
444
- @UsePipeline(cache({
445
- inheritModuleKey: true,
446
- condition: (ctx) => (ctx.request as { fresh?: boolean }).fresh !== true,
447
- }))
448
- export class GetReportHandler {}
449
- ```
450
-
451
- ### Options resolution
452
-
453
- Effective options for a handler are resolved as:
454
-
455
- 1. Module-wide defaults bound via `CacheModule.forRoot({ ttl, defaults })`.
456
- 2. Per-handler options from `@UsePipeline([CacheBehavior, { ... }])`,
457
- shallow-merged on top (handler keys win).
458
-
459
- ### Context items
460
-
461
- The behavior records diagnostics on `context.items`:
462
-
463
- | Item Token | Type | Meaning |
464
- | ---------- | ---- | ------- |
465
- | `CACHE_HIT_ITEM` | `boolean` | Whether the request was served from cache. |
466
- | `CACHE_KEY_ITEM` | `string` | The resolved cache key. |
467
-
468
- Exported as unique `Symbol` constants (`CACHE_HIT_ITEM` and `CACHE_KEY_ITEM`) to prevent key collisions in `context.items`. `CACHE_HIT_ITEM_TOKEN` and `CACHE_KEY_ITEM_TOKEN` are typed tokens over the same keys for `getPipelineItem` / `requirePipelineItem` from `@nestjs-pipeline/core`.
469
-
470
- `buildCacheAttributes(context)` turns the hit flag into the attribute `cache.hit`. Use it for span attributes through `AttributesBehavior` of [`@nestjs-pipeline/opentelemetry`](https://github.com/aristoteliss/nestjs-pipeline/tree/master/packages/pipeline-opentelemetry#attributes-from-other-behaviors), for audit `metadata`, or on a log line; it needs no telemetry package. It returns `{}` when the behavior did not run, never a fabricated miss, and never includes the key.
471
-
472
-
473
- ---
474
-
475
- ## Configuration
476
-
477
- `CacheModuleOptions` (passed to `CacheModule.forRoot`):
478
-
479
- | Field | Type | Description |
480
- | ----- | ---- | ----------- |
481
- | `cache` | `Cache` | Pre-built `cache-manager` instance (escape hatch). |
482
- | `stores` | `Keyv[]` | Pre-built Keyv stores (tiered). |
483
- | `store` | `CacheStoreConfig \| CacheStoreConfig[]` | Declarative store(s). |
484
- | `ttl` | `number` | Default TTL (ms) for stores and handlers. |
485
- | `nonBlocking` | `boolean` | Optimize multi-store reads/writes. |
486
- | `defaults` | `CacheBehaviorOptions` | Default per-handler options. |
487
-
488
- `CacheBehaviorOptions` (per handler and/or module `defaults`):
489
-
490
- | Field | Type | Default | Description |
491
- | ----- | ---- | ------- | ----------- |
492
- | `kinds` | `Array<'command' \| 'query' \| 'event' \| 'unknown'>` | `['query']` | Request kinds eligible for caching. |
493
- | `ttl` | `number` | module `ttl` | TTL (ms) for entries written by this handler. |
494
- | `key` | `(context) => string` | Required | Explicit cache-key factory; use `createPartitionedCacheKeyFactory` for protected responses. |
495
- | `condition` | `(context) => boolean` | _always_ | Gate whether a request is cached. |
496
- | `failOpen` | `boolean` | `true` | Log and bypass thrown cache read/write errors; set `false` to propagate them. |
497
-
498
- `CacheStoreConfig` (declarative store):
499
-
500
- | Field | Type | Description |
501
- | ----- | ---- | ----------- |
502
- | `type` | `'memory' \| 'redis' \| 'memcache' \| 'sqlite' \| 'postgres'` | Backend to build. |
503
- | `url` | `string` | Connection string / URI (ignored for `memory`). |
504
- | `namespace` | `string` | Key prefix for this store. |
505
- | `ttl` | `number` | Default TTL (ms) for this store. |
506
- | `options` | `Record<string, unknown>` | Adapter-specific options passed through. |
507
-
508
- ---
509
-
510
- ## Behavior Contract & Bootstrap Diagnostics
511
-
512
- `CacheBehavior` implements `@nestjs-pipeline/core` behavior contract diagnostics:
513
-
514
- ### Ordering Constraints
515
-
516
- - **Execution order**: Cache lookup must execute **after** CASL authorization (`@nestjs-pipeline/casl:CaslBehavior`) for all active cache request kinds (`kinds: ['query']` by default). This guarantees unauthenticated or unauthorized callers never receive cached responses.
517
- - **Dynamic evaluation**: The ordering rule evaluates dynamically per handler. For inactive request kinds (e.g. a command handler where cache defaults to queries only), ordering constraints are not enforced.
518
-
519
- ### Validation Invariants
520
-
521
- - **Callable key factory**: Whenever caching is active for a handler's request kind, `key` must be a callable function (`typeof === 'function'`). Strings or non-callable values are rejected fast at bootstrap.
522
- - **Module defaults**: Application-wide defaults supplied to `CacheModule.forRoot({ defaults: { ... } })` are resolved by `CacheBehavior.resolveEffectiveOptions` and evaluated alongside handler options during bootstrap.
523
-
524
- ---
525
-
526
- ## Custom Logger
527
-
528
- Diagnostic logger failures do not replace handler results or cache-store errors.
529
-
530
- `CacheBehavior` emits `debug` cache hit/miss lines and `warn`/`error` store-failure lines through the logger injected with `LOGGING_BEHAVIOR_LOGGER`, falling back to a standard NestJS `Logger` when that token is not bound. `CacheModule` logs the store-initialization message through a NestJS `Logger` when the cache is built.
531
-
532
- ---
533
-
534
- ## API Reference
535
-
536
- ```ts
537
- import {
538
- CacheModule,
539
- CacheBehavior,
540
- cache,
541
- CACHE_DEFAULT_OPTIONS,
542
- PIPELINE_CACHE,
543
- CACHE_HIT_ITEM,
544
- CACHE_HIT_ITEM_TOKEN,
545
- CACHE_KEY_ITEM,
546
- CACHE_KEY_ITEM_TOKEN,
547
- CacheManagerAdapter,
548
- buildCache,
549
- buildCacheAttributes,
550
- buildKeyv,
551
- createPartitionedCacheKeyFactory,
552
- MissingCachePartitionError,
553
- type CachePartitionDimension,
554
- type IPipelineCache,
555
- type CacheModuleOptions,
556
- type CacheModuleAsyncOptions,
557
- type CacheBehaviorOptions,
558
- type CacheIntentOptions,
559
- type CacheStoreConfig,
560
- type CacheStoreType,
561
- type CacheKeyFactory,
562
- type CacheCondition,
563
- type PartitionedCacheKeyOptions,
564
- } from '@nestjs-pipeline/cache';
565
- ```
566
-
567
- ---
568
-
569
36
  ## License
570
37
 
571
38
  Distributed under a dual license: **AGPLv3** (open source) or a **Commercial
@@ -1,17 +1,14 @@
1
- "use strict";
2
1
  /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.CacheManagerAdapter = void 0;
5
- const node_async_hooks_1 = require("node:async_hooks");
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
6
3
  /**
7
4
  * Adapter wrapping a cache-manager Cache instance to reliably expose backend
8
5
  * store errors that cache-manager would otherwise swallow in get().
9
6
  */
10
- class CacheManagerAdapter {
7
+ export class CacheManagerAdapter {
11
8
  cache;
12
9
  // cache-manager emits errors in the originating operation's async context.
13
10
  // A key alone cannot identify concurrent reads, writes, or fallback lookups.
14
- operation = new node_async_hooks_1.AsyncLocalStorage();
11
+ operation = new AsyncLocalStorage();
15
12
  constructor(cache) {
16
13
  this.cache = cache;
17
14
  // A caller-supplied store may swallow backend errors, which then read as a
@@ -52,5 +49,4 @@ class CacheManagerAdapter {
52
49
  });
53
50
  }
54
51
  }
55
- exports.CacheManagerAdapter = CacheManagerAdapter;
56
52
  //# sourceMappingURL=cache-manager.adapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache-manager.adapter.js","sourceRoot":"","sources":["../../src/adapters/cache-manager.adapter.ts"],"names":[],"mappings":";AAAA,sEAAsE;;;AAEtE,uDAAqD;AAWrD;;;GAGG;AACH;IAS+B,KAAK;IARlC,2EAA2E;IAC3E,6EAA6E;IAC5D,SAAS,GAAG,IAAI,oCAAiB,EAI9C,CAAC;IAEL,YAA6B,KAAY;qBAAZ,KAAK;QAChC,2EAA2E;QAC3E,iDAAiD;QACjD,MAAM,OAAO,GAAG,KAEf,CAAC;QACF,OAAO,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;IACxE,CAAC;IAEO,YAAY,CAAC,IAA+B,EAAE,KAAc;QAClE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC5C,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO;QAC5D,IACE,KAAK;YACL,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,IAAI,KAAK;YACd,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,GAAG;YAC3B,OAAO,IAAI,KAAK,EAChB,CAAC;YACD,SAAS,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QAChC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;YAC/C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,KAAK,CAAC;YAC5D,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAc,EAAE,GAAY;QACjD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YACrD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;YAC/C,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,KAAK,CAAC;YACrC,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
1
+ {"version":3,"file":"cache-manager.adapter.js","sourceRoot":"","sources":["../../src/adapters/cache-manager.adapter.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAWrD;;;GAGG;AACH,MAAM,OAAO,mBAAmB;IASD,KAAK;IARlC,2EAA2E;IAC3E,6EAA6E;IAC5D,SAAS,GAAG,IAAI,iBAAiB,EAI9C,CAAC;IAEL,YAA6B,KAAY;qBAAZ,KAAK;QAChC,2EAA2E;QAC3E,iDAAiD;QACjD,MAAM,OAAO,GAAG,KAEf,CAAC;QACF,OAAO,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;IACxE,CAAC;IAEO,YAAY,CAAC,IAA+B,EAAE,KAAc;QAClE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC5C,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO;QAC5D,IACE,KAAK;YACL,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,IAAI,KAAK;YACd,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,GAAG;YAC3B,OAAO,IAAI,KAAK,EAChB,CAAC;YACD,SAAS,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QAChC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;YAC/C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,KAAK,CAAC;YAC5D,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAc,EAAE,GAAY;QACjD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YACrD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;YAC/C,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,KAAK,CAAC;YACrC,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -1,8 +1,8 @@
1
1
  import { type LoggerService } from '@nestjs/common';
2
2
  import { type IPipelineBehavior, type IPipelineBehaviorContract, type IPipelineBehaviorOptionsResolver, type IPipelineContext, type NextDelegate, PIPELINE_BEHAVIOR_CONTRACT, type PipelineItemToken } from '@nestjs-pipeline/core';
3
3
  import type { Cache } from 'cache-manager';
4
- import { type IPipelineCache } from './adapters/cache-manager.adapter';
5
- import type { CacheBehaviorOptions } from './interfaces/cache-options.interface';
4
+ import { type IPipelineCache } from './adapters/cache-manager.adapter.js';
5
+ import type { CacheBehaviorOptions } from './interfaces/cache-options.interface.js';
6
6
  /**
7
7
  * Unique symbol key set on `context.items` recording whether the request was served from cache (`true` on hit).
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"cache.behavior.d.ts","sourceRoot":"","sources":["../src/cache.behavior.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACrC,KAAK,gBAAgB,EAErB,KAAK,YAAY,EACjB,0BAA0B,EAI1B,KAAK,iBAAiB,EAEvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAEjF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,eAA2B,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,CAAC,OAAO,CACG,CAAC;AAEhE;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,eAA2B,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,CAAC,MAAM,CACG,CAAC;AAY/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBACa,aACX,YACE,iBAAiB,EACjB,gCAAgC,CAAC,oBAAoB,CAAC;IAExD,MAAM,CAAC,QAAQ,CAAC,CAAC,0BAA0B,CAAC,EAAE,yBAAyB,CAkDrE;IAEF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiB;IAE9C,YAEE,KAAK,EAAE,KAAK,GAAG,cAAc,EAG7B,QAAQ,CAAC,EAAE,oBAAoB,EAG/B,MAAM,CAAC,EAAE,aAAa,EASvB;IAEK,MAAM,CACV,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,OAAO,CAAC,CAyDlB;IAED,oEAAoE;IACpE,OAAO,CAAC,gBAAgB;IA0BxB,OAAO,CAAC,QAAQ;IAQhB,uEAAuE;IACvE,uBAAuB,CACrB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,oBAAoB,CAGtB;CACF"}
1
+ {"version":3,"file":"cache.behavior.d.ts","sourceRoot":"","sources":["../src/cache.behavior.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACrC,KAAK,gBAAgB,EAErB,KAAK,YAAY,EACjB,0BAA0B,EAI1B,KAAK,iBAAiB,EAEvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAEpF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,eAA2B,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,CAAC,OAAO,CACG,CAAC;AAEhE;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,eAA2B,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,CAAC,MAAM,CACG,CAAC;AAY/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBACa,aACX,YACE,iBAAiB,EACjB,gCAAgC,CAAC,oBAAoB,CAAC;IAExD,MAAM,CAAC,QAAQ,CAAC,CAAC,0BAA0B,CAAC,EAAE,yBAAyB,CAkDrE;IAEF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiB;IAE9C,YAEE,KAAK,EAAE,KAAK,GAAG,cAAc,EAG7B,QAAQ,CAAC,EAAE,oBAAoB,EAG/B,MAAM,CAAC,EAAE,aAAa,EASvB;IAEK,MAAM,CACV,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,OAAO,CAAC,CAyDlB;IAED,oEAAoE;IACpE,OAAO,CAAC,gBAAgB;IA0BxB,OAAO,CAAC,QAAQ;IAQhB,uEAAuE;IACvE,uBAAuB,CACrB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,oBAAoB,CAGtB;CACF"}