@nestjs-pipeline/cache 0.2.0

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 (44) hide show
  1. package/COMMERCIAL_LICENSE.txt +34 -0
  2. package/LICENSE +661 -0
  3. package/README.md +572 -0
  4. package/dist/adapters/cache-manager.adapter.d.ts +21 -0
  5. package/dist/adapters/cache-manager.adapter.d.ts.map +1 -0
  6. package/dist/adapters/cache-manager.adapter.js +56 -0
  7. package/dist/adapters/cache-manager.adapter.js.map +1 -0
  8. package/dist/cache.behavior.d.ts +84 -0
  9. package/dist/cache.behavior.d.ts.map +1 -0
  10. package/dist/cache.behavior.js +239 -0
  11. package/dist/cache.behavior.js.map +1 -0
  12. package/dist/cache.module.d.ts +85 -0
  13. package/dist/cache.module.d.ts.map +1 -0
  14. package/dist/cache.module.js +189 -0
  15. package/dist/cache.module.js.map +1 -0
  16. package/dist/constants/tokens.d.ts +13 -0
  17. package/dist/constants/tokens.d.ts.map +1 -0
  18. package/dist/constants/tokens.js +17 -0
  19. package/dist/constants/tokens.js.map +1 -0
  20. package/dist/errors/missing-partition.error.d.ts +18 -0
  21. package/dist/errors/missing-partition.error.d.ts.map +1 -0
  22. package/dist/errors/missing-partition.error.js +23 -0
  23. package/dist/errors/missing-partition.error.js.map +1 -0
  24. package/dist/helpers/cache-factory.d.ts +39 -0
  25. package/dist/helpers/cache-factory.d.ts.map +1 -0
  26. package/dist/helpers/cache-factory.js +152 -0
  27. package/dist/helpers/cache-factory.js.map +1 -0
  28. package/dist/helpers/cache-key.d.ts +79 -0
  29. package/dist/helpers/cache-key.d.ts.map +1 -0
  30. package/dist/helpers/cache-key.js +92 -0
  31. package/dist/helpers/cache-key.js.map +1 -0
  32. package/dist/helpers/cache.intent.d.ts +26 -0
  33. package/dist/helpers/cache.intent.d.ts.map +1 -0
  34. package/dist/helpers/cache.intent.js +24 -0
  35. package/dist/helpers/cache.intent.js.map +1 -0
  36. package/dist/index.d.ts +10 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +27 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/interfaces/cache-options.interface.d.ts +120 -0
  41. package/dist/interfaces/cache-options.interface.d.ts.map +1 -0
  42. package/dist/interfaces/cache-options.interface.js +4 -0
  43. package/dist/interfaces/cache-options.interface.js.map +1 -0
  44. package/package.json +86 -0
@@ -0,0 +1,18 @@
1
+ import { MissingPartitionError } from '@nestjs-pipeline/core';
2
+ /** Which dimension of the cache key could not be resolved. */
3
+ export type CachePartitionDimension = 'tenant' | 'principal' | 'scope';
4
+ /**
5
+ * Raised before the cache is consulted when a dimension required by the
6
+ * configured key factory cannot be resolved.
7
+ *
8
+ * A cache hit returns without executing the handler,
9
+ * so it also skips whatever entity-level authorization and field filtering that
10
+ * handler performs. A key missing its tenant or principal segment therefore does
11
+ * not merely lose isolation — it can replay one caller's authorized response to
12
+ * another.
13
+ */
14
+ export declare class MissingCachePartitionError extends MissingPartitionError<CachePartitionDimension> {
15
+ readonly name = "MissingCachePartitionError";
16
+ constructor(requestName: string, dimension: CachePartitionDimension, remedy: string);
17
+ }
18
+ //# sourceMappingURL=missing-partition.error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-partition.error.d.ts","sourceRoot":"","sources":["../../src/errors/missing-partition.error.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,8DAA8D;AAC9D,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAEvE;;;;;;;;;GASG;AACH,qBAAa,0BAA2B,SAAQ,qBAAqB,CAAC,uBAAuB,CAAC;IAC5F,SAAkB,IAAI,gCAAgC;gBAGpD,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,uBAAuB,EAClC,MAAM,EAAE,MAAM;CAIjB"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.MissingCachePartitionError = void 0;
5
+ const core_1 = require("@nestjs-pipeline/core");
6
+ /**
7
+ * Raised before the cache is consulted when a dimension required by the
8
+ * configured key factory cannot be resolved.
9
+ *
10
+ * A cache hit returns without executing the handler,
11
+ * so it also skips whatever entity-level authorization and field filtering that
12
+ * handler performs. A key missing its tenant or principal segment therefore does
13
+ * not merely lose isolation — it can replay one caller's authorized response to
14
+ * another.
15
+ */
16
+ class MissingCachePartitionError extends core_1.MissingPartitionError {
17
+ name = 'MissingCachePartitionError';
18
+ constructor(requestName, dimension, remedy) {
19
+ super('Cache', requestName, dimension, remedy);
20
+ }
21
+ }
22
+ exports.MissingCachePartitionError = MissingCachePartitionError;
23
+ //# sourceMappingURL=missing-partition.error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-partition.error.js","sourceRoot":"","sources":["../../src/errors/missing-partition.error.ts"],"names":[],"mappings":";AAAA,sEAAsE;;;AAEtE,gDAA8D;AAK9D;;;;;;;;;GASG;AACH,MAAa,0BAA2B,SAAQ,4BAA8C;IAC1E,IAAI,GAAG,4BAA4B,CAAC;IAEtD,YACE,WAAmB,EACnB,SAAkC,EAClC,MAAc;QAEd,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IACjD,CAAC;CACF;AAVD,gEAUC"}
@@ -0,0 +1,39 @@
1
+ import { type Cache } from 'cache-manager';
2
+ import { Keyv } from 'keyv';
3
+ import type { CacheModuleOptions, CacheStoreConfig } from '../interfaces/cache-options.interface';
4
+ /**
5
+ * Build a single package-owned `Keyv` instance from a declarative store
6
+ * configuration. Package-owned stores enable `throwOnErrors` so
7
+ * {@link CacheBehavior} can apply its own `failOpen` / fail-closed policy rather
8
+ * than having Keyv silently consume backend failures first.
9
+ */
10
+ export declare function buildKeyv(config: CacheStoreConfig): Keyv;
11
+ /**
12
+ * Resolve the {@link CacheModuleOptions} into a ready-to-use `cache-manager`
13
+ * {@link Cache}. A pre-built `cache` wins, followed by pre-built `stores`,
14
+ * followed by declarative `store` configuration, falling back to an in-memory
15
+ * store when nothing is provided.
16
+ *
17
+ * Package-created stores use `throwOnErrors: true` so cache failures reach
18
+ * `CacheBehavior` and its configured failure policy. Caller-owned `cache` and
19
+ * `stores` are **not mutated**: if an application intentionally shares a Keyv
20
+ * instance with another subsystem, this package must not change that object's
21
+ * error semantics globally.
22
+ *
23
+ * @example Fully package-owned Redis cache
24
+ * ```ts
25
+ * CacheModule.forRoot({
26
+ * store: { type: 'redis', url: process.env.REDIS_URL! },
27
+ * defaults: { failOpen: true },
28
+ * });
29
+ * ```
30
+ *
31
+ * @example Caller-owned shared store — configuration remains caller-controlled
32
+ * ```ts
33
+ * const shared = new Keyv({ store: sharedRedis, throwOnErrors: false });
34
+ * CacheModule.forRoot({ stores: [shared] });
35
+ * // buildCache() will not mutate shared.throwOnErrors.
36
+ * ```
37
+ */
38
+ export declare function buildCache(options: CacheModuleOptions): Cache;
39
+ //# sourceMappingURL=cache-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-factory.d.ts","sourceRoot":"","sources":["../../src/helpers/cache-factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,KAAK,EAAe,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,IAAI,EAAyB,MAAM,MAAM,CAAC;AACnD,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAEjB,MAAM,uCAAuC,CAAC;AAmG/C;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAexD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,kBAAkB,GAAG,KAAK,CAsB7D"}
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.buildKeyv = buildKeyv;
5
+ exports.buildCache = buildCache;
6
+ const cache_manager_1 = require("cache-manager");
7
+ const keyv_1 = require("keyv");
8
+ /** Maps declarative store types to their optional `@keyv/*` adapter package. */
9
+ const ADAPTER_PACKAGES = {
10
+ redis: '@keyv/redis',
11
+ memcache: '@keyv/memcache',
12
+ sqlite: '@keyv/sqlite',
13
+ postgres: '@keyv/postgres',
14
+ };
15
+ /**
16
+ * Whether the requested adapter package itself could not be resolved.
17
+ *
18
+ * Only a resolution failure *for that exact package* counts. A `MODULE_NOT_FOUND`
19
+ * naming some other module means the adapter is installed but one of its own
20
+ * dependencies is not, and a native binding failure means it is installed but
21
+ * did not build.
22
+ */
23
+ function isRequestedModuleMissing(error, pkg) {
24
+ if (!(error instanceof Error))
25
+ return false;
26
+ if (error.code !== 'MODULE_NOT_FOUND') {
27
+ return false;
28
+ }
29
+ return (error.message.includes(`Cannot find module '${pkg}'`) ||
30
+ error.message.includes(`Cannot find module "${pkg}"`));
31
+ }
32
+ /** Whether the adapter is present but its native binary is missing or unusable. */
33
+ function isNativeBindingFailure(error) {
34
+ if (!(error instanceof Error))
35
+ return false;
36
+ return (error.message.includes('bindings file') ||
37
+ error.message.includes('was compiled against a different Node.js version') ||
38
+ error.message.includes('invalid ELF header'));
39
+ }
40
+ /**
41
+ * Lazily resolve an optional `@keyv/*` adapter. The adapters are declared as
42
+ * optional peer dependencies, so they are only required when the matching store
43
+ * type is actually requested.
44
+ *
45
+ * Each failure keeps its own diagnosis, and every wrapper preserves `cause`:
46
+ *
47
+ * - the package cannot be resolved → install it;
48
+ * - the package loaded but its native binary did not → rebuild it;
49
+ * - anything else → rethrown untouched, so the real root cause survives.
50
+ */
51
+ function requireAdapter(pkg) {
52
+ let mod;
53
+ try {
54
+ mod = require(pkg);
55
+ }
56
+ catch (error) {
57
+ if (isRequestedModuleMissing(error, pkg)) {
58
+ throw new Error(`[pipeline-cache] The optional '${pkg}' package is required for this store type. Install it with: pnpm add ${pkg}`, { cause: error });
59
+ }
60
+ if (isNativeBindingFailure(error)) {
61
+ throw new Error(`[pipeline-cache] '${pkg}' is installed but its native binding could not be loaded. ` +
62
+ `Rebuild it for this Node.js version (for example: pnpm rebuild ${pkg}); reinstalling the package alone will not help.`, { cause: error });
63
+ }
64
+ // An installed adapter that failed for any other reason — a missing
65
+ // transitive dependency, a broken export, an initialization error. Its own
66
+ // message is the accurate one.
67
+ throw error;
68
+ }
69
+ return (mod.default ??
70
+ mod);
71
+ }
72
+ /** Construct the backing `Keyv` store adapter for a declarative config. */
73
+ function createAdapterStore(config) {
74
+ const { type, url, options } = config;
75
+ const Adapter = requireAdapter(ADAPTER_PACKAGES[type]);
76
+ if (type === 'postgres') {
77
+ return new Adapter({ uri: url, ...options });
78
+ }
79
+ return new Adapter(url, options);
80
+ }
81
+ /**
82
+ * Build a single package-owned `Keyv` instance from a declarative store
83
+ * configuration. Package-owned stores enable `throwOnErrors` so
84
+ * {@link CacheBehavior} can apply its own `failOpen` / fail-closed policy rather
85
+ * than having Keyv silently consume backend failures first.
86
+ */
87
+ function buildKeyv(config) {
88
+ if (config.type === 'memory') {
89
+ return new keyv_1.Keyv({
90
+ namespace: config.namespace,
91
+ ttl: config.ttl,
92
+ throwOnErrors: true,
93
+ });
94
+ }
95
+ return new keyv_1.Keyv({
96
+ store: createAdapterStore(config),
97
+ namespace: config.namespace,
98
+ ttl: config.ttl,
99
+ throwOnErrors: true,
100
+ });
101
+ }
102
+ /**
103
+ * Resolve the {@link CacheModuleOptions} into a ready-to-use `cache-manager`
104
+ * {@link Cache}. A pre-built `cache` wins, followed by pre-built `stores`,
105
+ * followed by declarative `store` configuration, falling back to an in-memory
106
+ * store when nothing is provided.
107
+ *
108
+ * Package-created stores use `throwOnErrors: true` so cache failures reach
109
+ * `CacheBehavior` and its configured failure policy. Caller-owned `cache` and
110
+ * `stores` are **not mutated**: if an application intentionally shares a Keyv
111
+ * instance with another subsystem, this package must not change that object's
112
+ * error semantics globally.
113
+ *
114
+ * @example Fully package-owned Redis cache
115
+ * ```ts
116
+ * CacheModule.forRoot({
117
+ * store: { type: 'redis', url: process.env.REDIS_URL! },
118
+ * defaults: { failOpen: true },
119
+ * });
120
+ * ```
121
+ *
122
+ * @example Caller-owned shared store — configuration remains caller-controlled
123
+ * ```ts
124
+ * const shared = new Keyv({ store: sharedRedis, throwOnErrors: false });
125
+ * CacheModule.forRoot({ stores: [shared] });
126
+ * // buildCache() will not mutate shared.throwOnErrors.
127
+ * ```
128
+ */
129
+ function buildCache(options) {
130
+ if (options.cache) {
131
+ return options.cache;
132
+ }
133
+ let stores;
134
+ if (options.stores && options.stores.length > 0) {
135
+ stores = options.stores;
136
+ }
137
+ else if (options.store) {
138
+ const configs = Array.isArray(options.store)
139
+ ? options.store
140
+ : [options.store];
141
+ stores = configs.map(buildKeyv);
142
+ }
143
+ else {
144
+ stores = [new keyv_1.Keyv({ throwOnErrors: true })];
145
+ }
146
+ return (0, cache_manager_1.createCache)({
147
+ stores,
148
+ ttl: options.ttl,
149
+ nonBlocking: options.nonBlocking,
150
+ });
151
+ }
152
+ //# sourceMappingURL=cache-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-factory.js","sourceRoot":"","sources":["../../src/helpers/cache-factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { type IPipelineContext, type TenantPartitionOptions } from '@nestjs-pipeline/core';
2
+ import type { CacheKeyFactory } from '../interfaces/cache-options.interface';
3
+ /** Options for {@link createPartitionedCacheKeyFactory}. */
4
+ export interface PartitionedCacheKeyOptions extends TenantPartitionOptions {
5
+ /**
6
+ * Resolves the principal the response is scoped to.
7
+ *
8
+ * Required whenever the handler performs entity-level authorization or field
9
+ * filtering, because a cache hit skips the handler entirely — and therefore
10
+ * skips those checks.
11
+ */
12
+ principal: (context: IPipelineContext) => string | undefined;
13
+ /**
14
+ * Resolves a fingerprint of the caller's permission scope — a role-set hash or
15
+ * a capability version.
16
+ *
17
+ * Without it, a principal whose roles change keeps reading responses computed
18
+ * under the old permissions until the entry expires.
19
+ */
20
+ scope?: (context: IPipelineContext) => string | undefined;
21
+ /**
22
+ * Whether a missing principal is an error.
23
+ *
24
+ * Set to `false` only for genuinely public, identical-for-everyone responses.
25
+ *
26
+ * @default true
27
+ */
28
+ requirePrincipal?: boolean;
29
+ /**
30
+ * Whether a missing authorization scope is an error rather than an omitted segment.
31
+ *
32
+ * Set to `false` only when responses do not depend on the caller's permissions;
33
+ * `scope` may then be omitted.
34
+ *
35
+ * @default true
36
+ */
37
+ requireScope?: boolean;
38
+ }
39
+ /**
40
+ * Builds a cache key that partitions every dimension capable of changing an
41
+ * authorized response.
42
+ *
43
+ * The key includes tenant, principal, optional permission scope, request type,
44
+ * and a SHA-256 digest of the request payload. This keeps raw request data out
45
+ * of cache key listings while preventing authorized responses from being shared
46
+ * across callers with different security context.
47
+ *
48
+ * Use this helper when a cache hit can bypass authorization or response
49
+ * filtering performed inside the handler.
50
+ *
51
+ * @param options - Resolvers and fail-closed requirements for key partitioning.
52
+ * @returns A `CacheKeyFactory` suitable for `CacheBehaviorOptions.key`.
53
+ * @throws {TypeError} When `requireScope` is not `false` and no `scope` resolver is given.
54
+ * @throws {MissingCachePartitionError} (from the returned factory) When a required
55
+ * tenant, principal or scope is absent.
56
+ *
57
+ * @example Per principal, tenant-aware, invalidated when roles change
58
+ * ```ts
59
+ * @UsePipeline([CacheBehavior, {
60
+ * key: createPartitionedCacheKeyFactory({
61
+ * principal: (ctx) => ctx.items.get('currentUserId') as string | undefined,
62
+ * scope: (ctx) => ctx.items.get('capabilityVersion') as string | undefined,
63
+ * }),
64
+ * }])
65
+ * export class GetUsersHandler {}
66
+ * ```
67
+ *
68
+ * @example Public reference data, identical for every caller
69
+ * ```ts
70
+ * createPartitionedCacheKeyFactory({
71
+ * principal: () => 'public',
72
+ * requirePrincipal: false,
73
+ * requireTenant: false,
74
+ * requireScope: false,
75
+ * });
76
+ * ```
77
+ */
78
+ export declare function createPartitionedCacheKeyFactory(options: PartitionedCacheKeyOptions): CacheKeyFactory;
79
+ //# sourceMappingURL=cache-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-key.d.ts","sourceRoot":"","sources":["../../src/helpers/cache-key.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAE5B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAK7E,4DAA4D;AAC5D,MAAM,WAAW,0BAA2B,SAAQ,sBAAsB;IACxE;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,MAAM,GAAG,SAAS,CAAC;IAE7D;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,MAAM,GAAG,SAAS,CAAC;IAE1D;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,0BAA0B,GAClC,eAAe,CAuDjB"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createPartitionedCacheKeyFactory = createPartitionedCacheKeyFactory;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const safe_stringify_1 = require("@cqrs-ddd/safe-stringify");
7
+ const core_1 = require("@nestjs-pipeline/core");
8
+ const missing_partition_error_1 = require("../errors/missing-partition.error");
9
+ /** Key format version. Bump to make a format change produce a cold cache. */
10
+ const KEY_VERSION = 'v3';
11
+ /** Deterministic digest of the request payload, so secrets stay out of key listings. */
12
+ function digestRequest(request) {
13
+ return (0, node_crypto_1.createHash)('sha256').update((0, safe_stringify_1.stableStringify)(request)).digest('hex');
14
+ }
15
+ /**
16
+ * Builds a cache key that partitions every dimension capable of changing an
17
+ * authorized response.
18
+ *
19
+ * The key includes tenant, principal, optional permission scope, request type,
20
+ * and a SHA-256 digest of the request payload. This keeps raw request data out
21
+ * of cache key listings while preventing authorized responses from being shared
22
+ * across callers with different security context.
23
+ *
24
+ * Use this helper when a cache hit can bypass authorization or response
25
+ * filtering performed inside the handler.
26
+ *
27
+ * @param options - Resolvers and fail-closed requirements for key partitioning.
28
+ * @returns A `CacheKeyFactory` suitable for `CacheBehaviorOptions.key`.
29
+ * @throws {TypeError} When `requireScope` is not `false` and no `scope` resolver is given.
30
+ * @throws {MissingCachePartitionError} (from the returned factory) When a required
31
+ * tenant, principal or scope is absent.
32
+ *
33
+ * @example Per principal, tenant-aware, invalidated when roles change
34
+ * ```ts
35
+ * @UsePipeline([CacheBehavior, {
36
+ * key: createPartitionedCacheKeyFactory({
37
+ * principal: (ctx) => ctx.items.get('currentUserId') as string | undefined,
38
+ * scope: (ctx) => ctx.items.get('capabilityVersion') as string | undefined,
39
+ * }),
40
+ * }])
41
+ * export class GetUsersHandler {}
42
+ * ```
43
+ *
44
+ * @example Public reference data, identical for every caller
45
+ * ```ts
46
+ * createPartitionedCacheKeyFactory({
47
+ * principal: () => 'public',
48
+ * requirePrincipal: false,
49
+ * requireTenant: false,
50
+ * requireScope: false,
51
+ * });
52
+ * ```
53
+ */
54
+ function createPartitionedCacheKeyFactory(options) {
55
+ const requirePrincipal = options.requirePrincipal ?? true;
56
+ const requireScope = options.requireScope ?? true;
57
+ if (requireScope && !options.scope) {
58
+ throw new TypeError('createPartitionedCacheKeyFactory requires a `scope` resolver: a cache hit ' +
59
+ 'skips the handler, so a response computed under revoked permissions would ' +
60
+ 'keep being served. Pass requireScope: false when responses do not depend ' +
61
+ "on the caller's permissions.");
62
+ }
63
+ return (context) => {
64
+ const tenant = (0, core_1.tenantSegments)(context, options, missing_partition_error_1.MissingCachePartitionError);
65
+ const resolvedPrincipal = options.principal(context);
66
+ const principal = typeof resolvedPrincipal === 'string' ? resolvedPrincipal.trim() : '';
67
+ if (requirePrincipal && !principal) {
68
+ throw new missing_partition_error_1.MissingCachePartitionError(context.requestName, 'principal', 'A cache hit skips the handler and therefore its entity-level ' +
69
+ 'authorization. Return the authenticated principal, or pass ' +
70
+ 'requirePrincipal: false for a genuinely public response.');
71
+ }
72
+ const resolvedScope = options.scope?.(context);
73
+ const scope = typeof resolvedScope === 'string' && resolvedScope.trim()
74
+ ? resolvedScope.trim()
75
+ : undefined;
76
+ if (requireScope && !scope) {
77
+ throw new missing_partition_error_1.MissingCachePartitionError(context.requestName, 'scope', 'A cache hit skips the handler and therefore its entity-level ' +
78
+ 'authorization. Return the authorization scope, or pass ' +
79
+ 'requireScope: false when responses are scope-independent.');
80
+ }
81
+ return (0, safe_stringify_1.joinKeySegments)([
82
+ 'cache',
83
+ KEY_VERSION,
84
+ ...tenant,
85
+ principal || undefined,
86
+ scope,
87
+ context.requestName,
88
+ digestRequest(context.request),
89
+ ]);
90
+ };
91
+ }
92
+ //# sourceMappingURL=cache-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-key.js","sourceRoot":"","sources":["../../src/helpers/cache-key.ts"],"names":[],"mappings":";AAAA,sEAAsE;;AAmGtE,4EAyDC;AA1JD,6CAAyC;AACzC,6DAA4E;AAC5E,gDAI+B;AAC/B,+EAA+E;AAG/E,6EAA6E;AAC7E,MAAM,WAAW,GAAG,IAAI,CAAC;AA0CzB,wFAAwF;AACxF,SAAS,aAAa,CAAC,OAAgB;IACrC,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAA,gCAAe,EAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,SAAgB,gCAAgC,CAC9C,OAAmC;IAEnC,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,IAAI,CAAC;IAC1D,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC;IAClD,IAAI,YAAY,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CACjB,4EAA4E;YAC1E,4EAA4E;YAC5E,2EAA2E;YAC3E,8BAA8B,CACjC,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,OAAO,EAAE,EAAE;QACjB,MAAM,MAAM,GAAG,IAAA,qBAAc,EAAC,OAAO,EAAE,OAAO,EAAE,oDAA0B,CAAC,CAAC;QAE5E,MAAM,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACrD,MAAM,SAAS,GACb,OAAO,iBAAiB,KAAK,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAExE,IAAI,gBAAgB,IAAI,CAAC,SAAS,EAAE,CAAC;YACnC,MAAM,IAAI,oDAA0B,CAClC,OAAO,CAAC,WAAW,EACnB,WAAW,EACX,+DAA+D;gBAC7D,6DAA6D;gBAC7D,0DAA0D,CAC7D,CAAC;QACJ,CAAC;QAED,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,KAAK,GACT,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,CAAC,IAAI,EAAE;YACvD,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE;YACtB,CAAC,CAAC,SAAS,CAAC;QAEhB,IAAI,YAAY,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,MAAM,IAAI,oDAA0B,CAClC,OAAO,CAAC,WAAW,EACnB,OAAO,EACP,+DAA+D;gBAC7D,yDAAyD;gBACzD,2DAA2D,CAC9D,CAAC;QACJ,CAAC;QAED,OAAO,IAAA,gCAAe,EAAC;YACrB,OAAO;YACP,WAAW;YACX,GAAG,MAAM;YACT,SAAS,IAAI,SAAS;YACtB,KAAK;YACL,OAAO,CAAC,WAAW;YACnB,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC;SAC/B,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { PipelineBehaviorTuple } from '@nestjs-pipeline/core';
2
+ import { CacheBehavior } from '../cache.behavior';
3
+ import type { CacheBehaviorOptions } from '../interfaces/cache-options.interface';
4
+ export type CacheIntentOptions = Omit<CacheBehaviorOptions, 'key'> & ({
5
+ key: NonNullable<CacheBehaviorOptions['key']>;
6
+ inheritModuleKey?: never;
7
+ } | {
8
+ inheritModuleKey: true;
9
+ key?: never;
10
+ });
11
+ /**
12
+ * Returns a cache behavior entry for `@UsePipeline`.
13
+ * @param options A key factory and cache options, or `inheritModuleKey: true`
14
+ * when the module supplies the key factory. The inheritance marker is not
15
+ * forwarded to the behavior and does not validate module configuration.
16
+ * @returns The behavior class and options tuple.
17
+ * Keys for authorized responses must separate tenant, principal and permission
18
+ * scope; prefer `createPartitionedCacheKeyFactory` for those responses.
19
+ * @example
20
+ * ```ts
21
+ * @UsePipeline(cache({ key: userCacheKey, ttl: 30_000 }))
22
+ * @UsePipeline(cache({ inheritModuleKey: true, ttl: 30_000 }))
23
+ * ```
24
+ */
25
+ export declare function cache(options: CacheIntentOptions): PipelineBehaviorTuple<CacheBehavior, CacheBehaviorOptions>;
26
+ //# sourceMappingURL=cache.intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.intent.d.ts","sourceRoot":"","sources":["../../src/helpers/cache.intent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,oBAAoB,EAAE,KAAK,CAAC,GAChE,CACI;IACE,GAAG,EAAE,WAAW,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,KAAK,CAAC;CAC1B,GACD;IAAE,gBAAgB,EAAE,IAAI,CAAC;IAAC,GAAG,CAAC,EAAE,KAAK,CAAA;CAAE,CAC1C,CAAC;AAEJ;;;;;;;;;;;;;GAaG;AACH,wBAAgB,KAAK,CACnB,OAAO,EAAE,kBAAkB,GAC1B,qBAAqB,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAG5D"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.cache = cache;
5
+ const cache_behavior_1 = require("../cache.behavior");
6
+ /**
7
+ * Returns a cache behavior entry for `@UsePipeline`.
8
+ * @param options A key factory and cache options, or `inheritModuleKey: true`
9
+ * when the module supplies the key factory. The inheritance marker is not
10
+ * forwarded to the behavior and does not validate module configuration.
11
+ * @returns The behavior class and options tuple.
12
+ * Keys for authorized responses must separate tenant, principal and permission
13
+ * scope; prefer `createPartitionedCacheKeyFactory` for those responses.
14
+ * @example
15
+ * ```ts
16
+ * @UsePipeline(cache({ key: userCacheKey, ttl: 30_000 }))
17
+ * @UsePipeline(cache({ inheritModuleKey: true, ttl: 30_000 }))
18
+ * ```
19
+ */
20
+ function cache(options) {
21
+ const { inheritModuleKey: _inheritModuleKey, ...behaviorOptions } = options;
22
+ return [cache_behavior_1.CacheBehavior, behaviorOptions];
23
+ }
24
+ //# sourceMappingURL=cache.intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.intent.js","sourceRoot":"","sources":["../../src/helpers/cache.intent.ts"],"names":[],"mappings":";AAAA,sEAAsE;;AA6BtE,sBAKC;AA/BD,sDAAkD;AAYlD;;;;;;;;;;;;;GAaG;AACH,SAAgB,KAAK,CACnB,OAA2B;IAE3B,MAAM,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC;IAC5E,OAAO,CAAC,8BAAa,EAAE,eAAe,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,10 @@
1
+ export { CacheManagerAdapter, type IPipelineCache, } from './adapters/cache-manager.adapter';
2
+ export { CACHE_HIT_ITEM, CACHE_HIT_ITEM_TOKEN, CACHE_KEY_ITEM, CACHE_KEY_ITEM_TOKEN, CacheBehavior, } from './cache.behavior';
3
+ export { CacheModule } from './cache.module';
4
+ export { CACHE_DEFAULT_OPTIONS, PIPELINE_CACHE } from './constants/tokens';
5
+ export { type CachePartitionDimension, MissingCachePartitionError, } from './errors/missing-partition.error';
6
+ export { type CacheIntentOptions, cache, } from './helpers/cache.intent';
7
+ export { buildCache, buildKeyv } from './helpers/cache-factory';
8
+ export { createPartitionedCacheKeyFactory, type PartitionedCacheKeyOptions, } from './helpers/cache-key';
9
+ export type { CacheBehaviorOptions, CacheCondition, CacheKeyFactory, CacheModuleAsyncOptions, CacheModuleOptions, CacheStoreConfig, CacheStoreType, } from './interfaces/cache-options.interface';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,KAAK,cAAc,GACpB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACpB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACL,KAAK,uBAAuB,EAC5B,0BAA0B,GAC3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,GACN,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EACL,gCAAgC,EAChC,KAAK,0BAA0B,GAChC,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,GACf,MAAM,sCAAsC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createPartitionedCacheKeyFactory = exports.buildKeyv = exports.buildCache = exports.cache = exports.MissingCachePartitionError = exports.PIPELINE_CACHE = exports.CACHE_DEFAULT_OPTIONS = exports.CacheModule = exports.CacheBehavior = exports.CACHE_KEY_ITEM_TOKEN = exports.CACHE_KEY_ITEM = exports.CACHE_HIT_ITEM_TOKEN = exports.CACHE_HIT_ITEM = exports.CacheManagerAdapter = void 0;
5
+ var cache_manager_adapter_1 = require("./adapters/cache-manager.adapter");
6
+ Object.defineProperty(exports, "CacheManagerAdapter", { enumerable: true, get: function () { return cache_manager_adapter_1.CacheManagerAdapter; } });
7
+ var cache_behavior_1 = require("./cache.behavior");
8
+ Object.defineProperty(exports, "CACHE_HIT_ITEM", { enumerable: true, get: function () { return cache_behavior_1.CACHE_HIT_ITEM; } });
9
+ Object.defineProperty(exports, "CACHE_HIT_ITEM_TOKEN", { enumerable: true, get: function () { return cache_behavior_1.CACHE_HIT_ITEM_TOKEN; } });
10
+ Object.defineProperty(exports, "CACHE_KEY_ITEM", { enumerable: true, get: function () { return cache_behavior_1.CACHE_KEY_ITEM; } });
11
+ Object.defineProperty(exports, "CACHE_KEY_ITEM_TOKEN", { enumerable: true, get: function () { return cache_behavior_1.CACHE_KEY_ITEM_TOKEN; } });
12
+ Object.defineProperty(exports, "CacheBehavior", { enumerable: true, get: function () { return cache_behavior_1.CacheBehavior; } });
13
+ var cache_module_1 = require("./cache.module");
14
+ Object.defineProperty(exports, "CacheModule", { enumerable: true, get: function () { return cache_module_1.CacheModule; } });
15
+ var tokens_1 = require("./constants/tokens");
16
+ Object.defineProperty(exports, "CACHE_DEFAULT_OPTIONS", { enumerable: true, get: function () { return tokens_1.CACHE_DEFAULT_OPTIONS; } });
17
+ Object.defineProperty(exports, "PIPELINE_CACHE", { enumerable: true, get: function () { return tokens_1.PIPELINE_CACHE; } });
18
+ var missing_partition_error_1 = require("./errors/missing-partition.error");
19
+ Object.defineProperty(exports, "MissingCachePartitionError", { enumerable: true, get: function () { return missing_partition_error_1.MissingCachePartitionError; } });
20
+ var cache_intent_1 = require("./helpers/cache.intent");
21
+ Object.defineProperty(exports, "cache", { enumerable: true, get: function () { return cache_intent_1.cache; } });
22
+ var cache_factory_1 = require("./helpers/cache-factory");
23
+ Object.defineProperty(exports, "buildCache", { enumerable: true, get: function () { return cache_factory_1.buildCache; } });
24
+ Object.defineProperty(exports, "buildKeyv", { enumerable: true, get: function () { return cache_factory_1.buildKeyv; } });
25
+ var cache_key_1 = require("./helpers/cache-key");
26
+ Object.defineProperty(exports, "createPartitionedCacheKeyFactory", { enumerable: true, get: function () { return cache_key_1.createPartitionedCacheKeyFactory; } });
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,sEAAsE;;;AAEtE,0EAG0C;AAFxC,4HAAA,mBAAmB,OAAA;AAGrB,mDAM0B;AALxB,gHAAA,cAAc,OAAA;AACd,sHAAA,oBAAoB,OAAA;AACpB,gHAAA,cAAc,OAAA;AACd,sHAAA,oBAAoB,OAAA;AACpB,+GAAA,aAAa,OAAA;AAEf,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AACpB,6CAA2E;AAAlE,+GAAA,qBAAqB,OAAA;AAAE,wGAAA,cAAc,OAAA;AAC9C,4EAG0C;AADxC,qIAAA,0BAA0B,OAAA;AAE5B,uDAGgC;AAD9B,qGAAA,KAAK,OAAA;AAEP,yDAAgE;AAAvD,2GAAA,UAAU,OAAA;AAAE,0GAAA,SAAS,OAAA;AAC9B,iDAG6B;AAF3B,6HAAA,gCAAgC,OAAA"}