@nestjs-redisx/cache 1.0.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 (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/cache/api/decorators/cached.decorator.d.ts +152 -0
  4. package/dist/cache/api/decorators/cached.decorator.d.ts.map +1 -0
  5. package/dist/cache/api/decorators/invalidate-tags.decorator.d.ts +44 -0
  6. package/dist/cache/api/decorators/invalidate-tags.decorator.d.ts.map +1 -0
  7. package/dist/cache/application/ports/cache-service.port.d.ts +120 -0
  8. package/dist/cache/application/ports/cache-service.port.d.ts.map +1 -0
  9. package/dist/cache/application/ports/l1-cache-store.port.d.ts +56 -0
  10. package/dist/cache/application/ports/l1-cache-store.port.d.ts.map +1 -0
  11. package/dist/cache/application/ports/l2-cache-store.port.d.ts +98 -0
  12. package/dist/cache/application/ports/l2-cache-store.port.d.ts.map +1 -0
  13. package/dist/cache/application/services/cache-decorator-initializer.service.d.ts +25 -0
  14. package/dist/cache/application/services/cache-decorator-initializer.service.d.ts.map +1 -0
  15. package/dist/cache/application/services/cache.service.d.ts +106 -0
  16. package/dist/cache/application/services/cache.service.d.ts.map +1 -0
  17. package/dist/cache/application/services/warmup.service.d.ts +25 -0
  18. package/dist/cache/application/services/warmup.service.d.ts.map +1 -0
  19. package/dist/cache/domain/services/serializer.service.d.ts +29 -0
  20. package/dist/cache/domain/services/serializer.service.d.ts.map +1 -0
  21. package/dist/cache/domain/value-objects/cache-entry.vo.d.ts +69 -0
  22. package/dist/cache/domain/value-objects/cache-entry.vo.d.ts.map +1 -0
  23. package/dist/cache/domain/value-objects/cache-key.vo.d.ts +45 -0
  24. package/dist/cache/domain/value-objects/cache-key.vo.d.ts.map +1 -0
  25. package/dist/cache/domain/value-objects/tag.vo.d.ts +36 -0
  26. package/dist/cache/domain/value-objects/tag.vo.d.ts.map +1 -0
  27. package/dist/cache/domain/value-objects/tags.vo.d.ts +57 -0
  28. package/dist/cache/domain/value-objects/tags.vo.d.ts.map +1 -0
  29. package/dist/cache/domain/value-objects/ttl.vo.d.ts +58 -0
  30. package/dist/cache/domain/value-objects/ttl.vo.d.ts.map +1 -0
  31. package/dist/cache/infrastructure/adapters/l1-memory-store.adapter.d.ts +36 -0
  32. package/dist/cache/infrastructure/adapters/l1-memory-store.adapter.d.ts.map +1 -0
  33. package/dist/cache/infrastructure/adapters/l2-redis-store.adapter.d.ts +41 -0
  34. package/dist/cache/infrastructure/adapters/l2-redis-store.adapter.d.ts.map +1 -0
  35. package/dist/cache.plugin.d.ts +17 -0
  36. package/dist/cache.plugin.d.ts.map +1 -0
  37. package/dist/cache.service.d.ts +234 -0
  38. package/dist/cache.service.d.ts.map +1 -0
  39. package/dist/decorators/cache-evict.decorator.d.ts +97 -0
  40. package/dist/decorators/cache-evict.decorator.d.ts.map +1 -0
  41. package/dist/decorators/cache-put.decorator.d.ts +95 -0
  42. package/dist/decorators/cache-put.decorator.d.ts.map +1 -0
  43. package/dist/decorators/cache.interceptor.d.ts +63 -0
  44. package/dist/decorators/cache.interceptor.d.ts.map +1 -0
  45. package/dist/decorators/cacheable.decorator.d.ts +88 -0
  46. package/dist/decorators/cacheable.decorator.d.ts.map +1 -0
  47. package/dist/decorators/key-generator.util.d.ts +69 -0
  48. package/dist/decorators/key-generator.util.d.ts.map +1 -0
  49. package/dist/index.d.ts +39 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +4199 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/index.mjs +4152 -0
  54. package/dist/index.mjs.map +1 -0
  55. package/dist/invalidation/application/ports/event-invalidation.port.d.ts +28 -0
  56. package/dist/invalidation/application/ports/event-invalidation.port.d.ts.map +1 -0
  57. package/dist/invalidation/application/ports/invalidation-registry.port.d.ts +36 -0
  58. package/dist/invalidation/application/ports/invalidation-registry.port.d.ts.map +1 -0
  59. package/dist/invalidation/application/services/event-invalidation.service.d.ts +30 -0
  60. package/dist/invalidation/application/services/event-invalidation.service.d.ts.map +1 -0
  61. package/dist/invalidation/application/services/invalidation-registry.service.d.ts +17 -0
  62. package/dist/invalidation/application/services/invalidation-registry.service.d.ts.map +1 -0
  63. package/dist/invalidation/domain/entities/invalidation-rule.entity.d.ts +60 -0
  64. package/dist/invalidation/domain/entities/invalidation-rule.entity.d.ts.map +1 -0
  65. package/dist/invalidation/domain/value-objects/event-pattern.vo.d.ts +27 -0
  66. package/dist/invalidation/domain/value-objects/event-pattern.vo.d.ts.map +1 -0
  67. package/dist/invalidation/domain/value-objects/tag-template.vo.d.ts +34 -0
  68. package/dist/invalidation/domain/value-objects/tag-template.vo.d.ts.map +1 -0
  69. package/dist/invalidation/infrastructure/adapters/amqp-event-source.adapter.d.ts +51 -0
  70. package/dist/invalidation/infrastructure/adapters/amqp-event-source.adapter.d.ts.map +1 -0
  71. package/dist/invalidation/infrastructure/decorators/invalidate-on.decorator.d.ts +69 -0
  72. package/dist/invalidation/infrastructure/decorators/invalidate-on.decorator.d.ts.map +1 -0
  73. package/dist/key-builder.d.ts +199 -0
  74. package/dist/key-builder.d.ts.map +1 -0
  75. package/dist/serializers/index.d.ts +19 -0
  76. package/dist/serializers/index.d.ts.map +1 -0
  77. package/dist/serializers/json.serializer.d.ts +51 -0
  78. package/dist/serializers/json.serializer.d.ts.map +1 -0
  79. package/dist/serializers/msgpack.serializer.d.ts +67 -0
  80. package/dist/serializers/msgpack.serializer.d.ts.map +1 -0
  81. package/dist/serializers/serializer.interface.d.ts +36 -0
  82. package/dist/serializers/serializer.interface.d.ts.map +1 -0
  83. package/dist/shared/constants/index.d.ts +73 -0
  84. package/dist/shared/constants/index.d.ts.map +1 -0
  85. package/dist/shared/errors/index.d.ts +46 -0
  86. package/dist/shared/errors/index.d.ts.map +1 -0
  87. package/dist/shared/types/context-provider.interface.d.ts +58 -0
  88. package/dist/shared/types/context-provider.interface.d.ts.map +1 -0
  89. package/dist/shared/types/index.d.ts +259 -0
  90. package/dist/shared/types/index.d.ts.map +1 -0
  91. package/dist/stampede/application/ports/stampede-protection.port.d.ts +33 -0
  92. package/dist/stampede/application/ports/stampede-protection.port.d.ts.map +1 -0
  93. package/dist/stampede/infrastructure/stampede-protection.service.d.ts +30 -0
  94. package/dist/stampede/infrastructure/stampede-protection.service.d.ts.map +1 -0
  95. package/dist/strategies/eviction-strategy.interface.d.ts +39 -0
  96. package/dist/strategies/eviction-strategy.interface.d.ts.map +1 -0
  97. package/dist/strategies/fifo.strategy.d.ts +86 -0
  98. package/dist/strategies/fifo.strategy.d.ts.map +1 -0
  99. package/dist/strategies/index.d.ts +19 -0
  100. package/dist/strategies/index.d.ts.map +1 -0
  101. package/dist/strategies/lfu.strategy.d.ts +87 -0
  102. package/dist/strategies/lfu.strategy.d.ts.map +1 -0
  103. package/dist/strategies/lru.strategy.d.ts +78 -0
  104. package/dist/strategies/lru.strategy.d.ts.map +1 -0
  105. package/dist/swr/application/ports/swr-manager.port.d.ts +83 -0
  106. package/dist/swr/application/ports/swr-manager.port.d.ts.map +1 -0
  107. package/dist/swr/infrastructure/swr-manager.service.d.ts +30 -0
  108. package/dist/swr/infrastructure/swr-manager.service.d.ts.map +1 -0
  109. package/dist/tags/application/ports/tag-index.port.d.ts +55 -0
  110. package/dist/tags/application/ports/tag-index.port.d.ts.map +1 -0
  111. package/dist/tags/infrastructure/repositories/tag-index.repository.d.ts +37 -0
  112. package/dist/tags/infrastructure/repositories/tag-index.repository.d.ts.map +1 -0
  113. package/dist/tags/infrastructure/scripts/lua-scripts.d.ts +25 -0
  114. package/dist/tags/infrastructure/scripts/lua-scripts.d.ts.map +1 -0
  115. package/dist/tags/infrastructure/services/lua-script-loader.service.d.ts +44 -0
  116. package/dist/tags/infrastructure/services/lua-script-loader.service.d.ts.map +1 -0
  117. package/package.json +79 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 NestJS RedisX Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/nestjs-redisx/nestjs-redisx/main/website/public/images/logo.png" alt="NestJS RedisX" />
3
+ </p>
4
+
5
+ # @nestjs-redisx/cache
6
+
7
+ [![npm](https://img.shields.io/npm/v/@nestjs-redisx/cache)](https://www.npmjs.com/package/@nestjs-redisx/cache)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@nestjs-redisx/cache)](https://www.npmjs.com/package/@nestjs-redisx/cache)
9
+ [![license](https://img.shields.io/npm/l/@nestjs-redisx/cache)](https://opensource.org/licenses/MIT)
10
+
11
+ Two-tier caching plugin for NestJS RedisX. L1 in-memory + L2 Redis with anti-stampede protection, stale-while-revalidate (SWR), tag-based invalidation, and declarative `@Cached` decorator.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @nestjs-redisx/core @nestjs-redisx/cache ioredis
17
+ ```
18
+
19
+ ## Quick Example
20
+
21
+ ```typescript
22
+ import { RedisModule } from '@nestjs-redisx/core';
23
+ import { CachePlugin, Cached } from '@nestjs-redisx/cache';
24
+
25
+ @Module({
26
+ imports: [
27
+ RedisModule.forRoot({
28
+ clients: { host: 'localhost', port: 6379 },
29
+ plugins: [new CachePlugin({ l1: { maxSize: 1000 }, l2: { defaultTtl: 3600 } })],
30
+ }),
31
+ ],
32
+ })
33
+ export class AppModule {}
34
+
35
+ @Injectable()
36
+ export class UserService {
37
+ @Cached({ key: 'user:{0}', ttl: 300, tags: ['users'] })
38
+ async getUser(id: string) {
39
+ return this.repo.findById(id);
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## Documentation
45
+
46
+ Full documentation: [nestjs-redisx.dev/en/reference/cache/](https://nestjs-redisx.dev/en/reference/cache/)
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @Cached decorator for method-level caching.
3
+ *
4
+ * Uses immediate proxy-based wrapping (not deferred to module init).
5
+ * Works on ANY Injectable class methods (services, repositories, etc).
6
+ */
7
+ import 'reflect-metadata';
8
+ /**
9
+ * Cache service interface for decorator use.
10
+ * Minimal subset of ICacheService needed by decorators.
11
+ */
12
+ interface IDecoratorCacheService {
13
+ get<T>(key: string): Promise<T | null>;
14
+ set<T>(key: string, value: T, options?: {
15
+ ttl?: number;
16
+ tags?: string[];
17
+ strategy?: 'l1-only' | 'l2-only' | 'l1-l2';
18
+ }): Promise<void>;
19
+ getOrSet<T>(key: string, loader: () => Promise<T>, options?: {
20
+ ttl?: number;
21
+ tags?: string[];
22
+ strategy?: 'l1-only' | 'l2-only' | 'l1-l2';
23
+ swr?: {
24
+ enabled?: boolean;
25
+ staleTime?: number;
26
+ };
27
+ }): Promise<T>;
28
+ invalidateTags(tags: string[]): Promise<number>;
29
+ deleteMany(keys: string[]): Promise<number>;
30
+ }
31
+ /**
32
+ * Context provider interface for decorator use.
33
+ */
34
+ interface IDecoratorContextProvider {
35
+ get<T = unknown>(key: string): T | undefined;
36
+ }
37
+ /**
38
+ * Plugin options subset needed by decorators for context enrichment.
39
+ */
40
+ interface IDecoratorPluginOptions {
41
+ contextProvider?: IDecoratorContextProvider;
42
+ contextKeys?: string[];
43
+ keys?: {
44
+ separator?: string;
45
+ };
46
+ }
47
+ /**
48
+ * Register cache service getter for lazy injection.
49
+ * Called by CacheDecoratorInitializerService during initialization.
50
+ */
51
+ export declare function registerCacheServiceGetter(getter: () => IDecoratorCacheService): void;
52
+ /**
53
+ * Register plugin options for context enrichment in decorators.
54
+ * Called by CacheDecoratorInitializerService during initialization.
55
+ */
56
+ export declare function registerCachePluginOptions(options: IDecoratorPluginOptions): void;
57
+ /**
58
+ * Get the registered cache service.
59
+ * Used by other cache decorators (@InvalidateTags, etc.)
60
+ */
61
+ export declare function getCacheService(): IDecoratorCacheService | null;
62
+ export interface ICachedOptions {
63
+ /**
64
+ * Cache key template. Use {0}, {1}, etc. for method arguments.
65
+ * Example: 'user:{0}' for first argument.
66
+ *
67
+ * If omitted, key is auto-generated as `ClassName:methodName:args`.
68
+ */
69
+ key?: string;
70
+ /**
71
+ * TTL in seconds. Defaults to plugin's defaultTtl.
72
+ */
73
+ ttl?: number;
74
+ /**
75
+ * Tags for invalidation. Can be static array or function of args.
76
+ */
77
+ tags?: string[] | ((...args: unknown[]) => string[]);
78
+ /**
79
+ * Cache strategy: where to store the cached value.
80
+ * - 'l1-only': Only in-memory cache
81
+ * - 'l2-only': Only Redis cache
82
+ * - 'l1-l2': Both layers (default)
83
+ */
84
+ strategy?: 'l1-only' | 'l2-only' | 'l1-l2';
85
+ /**
86
+ * Condition to check BEFORE method execution.
87
+ * If returns false, skip caching and execute method.
88
+ */
89
+ condition?: (...args: unknown[]) => boolean;
90
+ /**
91
+ * Condition to check AFTER method execution.
92
+ * If returns true, don't cache the result.
93
+ */
94
+ unless?: (result: unknown, ...args: unknown[]) => boolean;
95
+ /**
96
+ * Additional context keys to vary cache by.
97
+ * Values are resolved from contextProvider at call time.
98
+ * Adds to (not replaces) global contextKeys.
99
+ *
100
+ * Works on any Injectable — values come from contextProvider (CLS, AsyncLocalStorage, etc.),
101
+ * not from HTTP headers. Ignored if contextProvider is not configured.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * @Cached({
106
+ * key: 'products',
107
+ * varyBy: ['locale', 'currency'] // resolved from contextProvider
108
+ * })
109
+ * ```
110
+ */
111
+ varyBy?: string[];
112
+ /**
113
+ * Stale-while-revalidate configuration.
114
+ * If enabled, serves stale data while revalidating in background.
115
+ */
116
+ swr?: {
117
+ enabled?: boolean;
118
+ staleTime?: number;
119
+ };
120
+ /**
121
+ * Context keys to include in cache key (from contextProvider).
122
+ * Overrides global contextKeys for this method.
123
+ */
124
+ contextKeys?: string[];
125
+ /**
126
+ * Disable context enrichment for this method.
127
+ * Set to true to prevent automatic context keys from being added.
128
+ *
129
+ * @default false
130
+ */
131
+ skipContext?: boolean;
132
+ }
133
+ /**
134
+ * Caches method return value using immediate proxy-based wrapping.
135
+ *
136
+ * Works on any Injectable class method, not just controllers.
137
+ * Wrapping happens immediately when decorator is applied.
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * @Injectable()
142
+ * class UserService {
143
+ * @Cached({ key: 'user:{0}', ttl: 3600, tags: ['users'] })
144
+ * async getUser(id: string): Promise<User> {
145
+ * return this.userRepository.findById(id);
146
+ * }
147
+ * }
148
+ * ```
149
+ */
150
+ export declare function Cached(options?: ICachedOptions): MethodDecorator;
151
+ export {};
152
+ //# sourceMappingURL=cached.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cached.decorator.d.ts","sourceRoot":"","sources":["../../../../src/cache/api/decorators/cached.decorator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @InvalidateTags decorator for automatic tag invalidation.
3
+ *
4
+ * Uses immediate proxy-based wrapping (not deferred to interceptor).
5
+ * Works on ANY Injectable class methods (services, repositories, etc).
6
+ */
7
+ import 'reflect-metadata';
8
+ export interface IInvalidateTagsOptions {
9
+ /**
10
+ * Tags to invalidate. Can be static array or function of method args.
11
+ */
12
+ tags: string[] | ((...args: unknown[]) => string[]);
13
+ /**
14
+ * When to invalidate: 'before' or 'after' method execution.
15
+ * Default: 'after'
16
+ */
17
+ when?: 'before' | 'after';
18
+ }
19
+ /**
20
+ * Invalidates cache tags when method is called.
21
+ *
22
+ * Works on any Injectable class method, not just controllers.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * @Injectable()
27
+ * class UserService {
28
+ * @InvalidateTags({
29
+ * tags: (id: string) => [`user:${id}`, 'users'],
30
+ * when: 'after',
31
+ * })
32
+ * async updateUser(id: string, data: UpdateUserDto): Promise<User> {
33
+ * return this.userRepository.update(id, data);
34
+ * }
35
+ *
36
+ * @InvalidateTags({ tags: ['users'] })
37
+ * async deleteUser(id: string): Promise<void> {
38
+ * await this.userRepository.delete(id);
39
+ * }
40
+ * }
41
+ * ```
42
+ */
43
+ export declare function InvalidateTags(options: IInvalidateTagsOptions): MethodDecorator;
44
+ //# sourceMappingURL=invalidate-tags.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invalidate-tags.decorator.d.ts","sourceRoot":"","sources":["../../../../src/cache/api/decorators/invalidate-tags.decorator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,kBAAkB,CAAC;AAM1B,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC,CAAC;IAEpD;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CA+C/E"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Main cache service interface (port).
3
+ */
4
+ import { CacheSetOptions, CacheGetOrSetOptions, CacheStats } from '../../../shared/types';
5
+ /**
6
+ * Main cache service interface.
7
+ */
8
+ export interface ICacheService {
9
+ /**
10
+ * Gets value from cache (L1 -> L2).
11
+ *
12
+ * @param key - Cache key
13
+ * @returns Cached value or null if not found
14
+ */
15
+ get<T>(key: string): Promise<T | null>;
16
+ /**
17
+ * Sets value in cache.
18
+ *
19
+ * @param key - Cache key
20
+ * @param value - Value to cache
21
+ * @param options - Cache options
22
+ */
23
+ set<T>(key: string, value: T, options?: CacheSetOptions): Promise<void>;
24
+ /**
25
+ * Deletes key from both caches.
26
+ *
27
+ * @param key - Cache key
28
+ * @returns true if deleted, false if not found
29
+ */
30
+ delete(key: string): Promise<boolean>;
31
+ /**
32
+ * Checks if key exists in cache.
33
+ *
34
+ * @param key - Cache key
35
+ * @returns true if exists, false otherwise
36
+ */
37
+ has(key: string): Promise<boolean>;
38
+ /**
39
+ * Gets value or loads and caches it.
40
+ * Implements stampede protection.
41
+ *
42
+ * @param key - Cache key
43
+ * @param loader - Function to load value if cache miss
44
+ * @param options - Cache options
45
+ * @returns Cached or loaded value
46
+ */
47
+ getOrSet<T>(key: string, loader: () => Promise<T>, options?: CacheGetOrSetOptions): Promise<T>;
48
+ /**
49
+ * Gets multiple values from cache.
50
+ *
51
+ * @param keys - Array of cache keys
52
+ * @returns Array of values (null if not found)
53
+ */
54
+ getMany<T>(keys: string[]): Promise<Array<T | null>>;
55
+ /**
56
+ * Sets multiple values in cache.
57
+ *
58
+ * @param entries - Array of entries with key, value, and optional options
59
+ */
60
+ setMany<T>(entries: Array<{
61
+ key: string;
62
+ value: T;
63
+ ttl?: number;
64
+ tags?: string[];
65
+ }>): Promise<void>;
66
+ /**
67
+ * Deletes multiple keys from cache.
68
+ *
69
+ * @param keys - Array of cache keys
70
+ * @returns Number of keys deleted
71
+ */
72
+ deleteMany(keys: string[]): Promise<number>;
73
+ /**
74
+ * Invalidates all keys with given tag.
75
+ *
76
+ * @param tag - Tag name
77
+ * @returns Number of keys invalidated
78
+ */
79
+ invalidateTag(tag: string): Promise<number>;
80
+ /**
81
+ * Invalidates all keys with any of given tags.
82
+ *
83
+ * @param tags - Array of tag names
84
+ * @returns Number of keys invalidated
85
+ */
86
+ invalidateTags(tags: string[]): Promise<number>;
87
+ /**
88
+ * Gets all keys associated with a tag.
89
+ *
90
+ * @param tag - Tag name
91
+ * @returns Array of cache keys
92
+ */
93
+ getKeysByTag(tag: string): Promise<string[]>;
94
+ /**
95
+ * Gets TTL for a key in seconds.
96
+ *
97
+ * @param key - Cache key
98
+ * @returns TTL in seconds, -1 if no TTL, -2 if key doesn't exist
99
+ */
100
+ ttl(key: string): Promise<number>;
101
+ /**
102
+ * Clears all caches (L1 + L2).
103
+ */
104
+ clear(): Promise<void>;
105
+ /**
106
+ * Gets cache statistics.
107
+ *
108
+ * @returns Cache stats
109
+ */
110
+ getStats(): Promise<CacheStats>;
111
+ /**
112
+ * Invalidates cache keys matching a pattern.
113
+ * Uses Redis SCAN for safe iteration.
114
+ *
115
+ * @param pattern - Redis pattern (supports * and ?)
116
+ * @returns Number of keys deleted
117
+ */
118
+ invalidateByPattern(pattern: string): Promise<number>;
119
+ }
120
+ //# sourceMappingURL=cache-service.port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-service.port.d.ts","sourceRoot":"","sources":["../../../../src/cache/application/ports/cache-service.port.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAE1F;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEvC;;;;;;OAMG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExE;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtC;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnC;;;;;;;;OAQG;IACH,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE/F;;;;;OAKG;IACH,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAErD;;;;OAIG;IACH,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpG;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5C;;;;;OAKG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5C;;;;;OAKG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE7C;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAElC;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;;OAIG;IACH,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAEhC;;;;;;OAMG;IACH,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACvD"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * L1 (in-memory) cache store interface.
3
+ */
4
+ import { CacheEntry } from '../../domain/value-objects/cache-entry.vo';
5
+ export interface IL1CacheStore {
6
+ /**
7
+ * Gets value from L1 cache.
8
+ *
9
+ * @param key - Cache key
10
+ * @returns CacheEntry or null if not found/expired
11
+ */
12
+ get<T>(key: string): Promise<CacheEntry<T> | null>;
13
+ /**
14
+ * Sets value in L1 cache.
15
+ *
16
+ * @param key - Cache key
17
+ * @param entry - CacheEntry to store
18
+ * @param ttl - TTL in milliseconds (optional, uses default if not provided)
19
+ */
20
+ set<T>(key: string, entry: CacheEntry<T>, ttl?: number): Promise<void>;
21
+ /**
22
+ * Deletes value from L1 cache.
23
+ *
24
+ * @param key - Cache key
25
+ * @returns true if existed, false otherwise
26
+ */
27
+ delete(key: string): Promise<boolean>;
28
+ /**
29
+ * Checks if key exists in L1 cache.
30
+ *
31
+ * @param key - Cache key
32
+ * @returns true if exists and not expired
33
+ */
34
+ has(key: string): Promise<boolean>;
35
+ /**
36
+ * Gets current size of L1 cache.
37
+ *
38
+ * @returns Number of items in cache
39
+ */
40
+ size(): Promise<number>;
41
+ /**
42
+ * Clears all items from L1 cache.
43
+ */
44
+ clear(): Promise<void>;
45
+ /**
46
+ * Gets L1 cache statistics.
47
+ *
48
+ * @returns Cache statistics (hits, misses, size)
49
+ */
50
+ getStats(): {
51
+ hits: number;
52
+ misses: number;
53
+ size: number;
54
+ };
55
+ }
56
+ //# sourceMappingURL=l1-cache-store.port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l1-cache-store.port.d.ts","sourceRoot":"","sources":["../../../../src/cache/application/ports/l1-cache-store.port.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AAEvE,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEnD;;;;;;OAMG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvE;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtC;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnC;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAExB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;;OAIG;IACH,QAAQ,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * L2 (Redis) cache store interface.
3
+ */
4
+ import { SwrEntry } from '../../../shared/types';
5
+ import { CacheEntry } from '../../domain/value-objects/cache-entry.vo';
6
+ export interface IL2CacheStore {
7
+ /**
8
+ * Gets cache entry from L2 cache.
9
+ *
10
+ * @param key - Cache key
11
+ * @returns CacheEntry or null if not found
12
+ */
13
+ get<T>(key: string): Promise<CacheEntry<T> | null>;
14
+ /**
15
+ * Sets cache entry in L2 cache.
16
+ *
17
+ * @param key - Cache key
18
+ * @param entry - Cache entry to store
19
+ * @param ttlSeconds - TTL in seconds (optional)
20
+ */
21
+ set<T>(key: string, entry: CacheEntry<T>, ttlSeconds?: number): Promise<void>;
22
+ /**
23
+ * Deletes value from L2 cache.
24
+ *
25
+ * @param key - Cache key
26
+ * @returns true if deleted, false if not found
27
+ */
28
+ delete(key: string): Promise<boolean>;
29
+ /**
30
+ * Checks if key exists in L2 cache.
31
+ *
32
+ * @param key - Cache key
33
+ * @returns true if exists
34
+ */
35
+ has(key: string): Promise<boolean>;
36
+ /**
37
+ * Clears all cache entries.
38
+ * WARNING: This is a destructive operation.
39
+ */
40
+ clear(): Promise<void>;
41
+ /**
42
+ * Gets multiple cache entries from L2 cache.
43
+ *
44
+ * @param keys - Array of cache keys
45
+ * @returns Map of key -> CacheEntry (null if not found)
46
+ */
47
+ getMany<T>(keys: string[]): Promise<Array<CacheEntry<T> | null>>;
48
+ /**
49
+ * Sets multiple cache entries in L2 cache.
50
+ *
51
+ * @param entries - Array of entries with key, entry, and optional ttl
52
+ */
53
+ setMany<T>(entries: Array<{
54
+ key: string;
55
+ entry: CacheEntry<T>;
56
+ ttl?: number;
57
+ }>): Promise<void>;
58
+ /**
59
+ * Gets TTL for a key.
60
+ *
61
+ * @param key - Cache key
62
+ * @returns TTL in seconds, -1 if no TTL, -2 if key doesn't exist
63
+ */
64
+ ttl(key: string): Promise<number>;
65
+ /**
66
+ * Gets L2 cache statistics.
67
+ */
68
+ getStats(): Promise<{
69
+ hits: number;
70
+ misses: number;
71
+ }>;
72
+ /**
73
+ * Gets SWR entry from L2 cache with metadata.
74
+ *
75
+ * @param key - Cache key
76
+ * @returns SwrEntry with timestamps or null if not found
77
+ */
78
+ getSwr<T>(key: string): Promise<SwrEntry<T> | null>;
79
+ /**
80
+ * Sets SWR entry in L2 cache with metadata.
81
+ *
82
+ * @param key - Cache key
83
+ * @param swrEntry - SWR entry with value and timestamps
84
+ */
85
+ setSwr<T>(key: string, swrEntry: SwrEntry<T>): Promise<void>;
86
+ /**
87
+ * Scans keys matching a pattern using Redis SCAN.
88
+ *
89
+ * @param pattern - Redis pattern (supports * and ?)
90
+ * @param count - Hint for number of elements per SCAN iteration
91
+ * @returns Scan result with matching keys and cursor
92
+ */
93
+ scan(pattern: string, count?: number): Promise<{
94
+ keys: string[];
95
+ cursor: string;
96
+ }>;
97
+ }
98
+ //# sourceMappingURL=l2-cache-store.port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l2-cache-store.port.d.ts","sourceRoot":"","sources":["../../../../src/cache/application/ports/l2-cache-store.port.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AAEvE,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEnD;;;;;;OAMG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9E;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtC;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnC;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;;;OAKG;IACH,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAEjE;;;;OAIG;IACH,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/F;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAElC;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEtD;;;;;OAKG;IACH,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEpD;;;;;OAKG;IACH,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7D;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpF"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Service for initializing @Cached decorator with lazy cache service injection.
3
+ *
4
+ * Runs on module initialization and registers a getter function that provides
5
+ * access to CacheService for the @Cached decorator's proxy logic.
6
+ */
7
+ import { OnModuleInit } from '@nestjs/common';
8
+ import { ModuleRef } from '@nestjs/core';
9
+ import { IEventInvalidationService } from '../../../invalidation/application/ports/event-invalidation.port';
10
+ import { ICachePluginOptions } from '../../../shared/types';
11
+ import { ICacheService } from '../ports/cache-service.port';
12
+ export declare class CacheDecoratorInitializerService implements OnModuleInit {
13
+ private readonly moduleRef;
14
+ private readonly cacheService;
15
+ private readonly pluginOptions;
16
+ private readonly eventInvalidationService?;
17
+ private readonly logger;
18
+ constructor(moduleRef: ModuleRef, cacheService: ICacheService, pluginOptions: ICachePluginOptions, eventInvalidationService?: IEventInvalidationService | undefined);
19
+ /**
20
+ * Called after all modules are initialized.
21
+ * Registers cache service getter and plugin options for @Cached decorator.
22
+ */
23
+ onModuleInit(): Promise<void>;
24
+ }
25
+ //# sourceMappingURL=cache-decorator-initializer.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-decorator-initializer.service.d.ts","sourceRoot":"","sources":["../../../../src/cache/application/services/cache-decorator-initializer.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAc,YAAY,EAA4B,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,iEAAiE,CAAC;AAG5G,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,qBACa,gCAAiC,YAAW,YAAY;IAIjE,OAAO,CAAC,QAAQ,CAAC,SAAS;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAC;IAN5F,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqD;gBAGzD,SAAS,EAAE,SAAS,EACG,YAAY,EAAE,aAAa,EACpB,aAAa,EAAE,mBAAmB,EAChB,wBAAwB,CAAC,EAAE,yBAAyB,YAAA;IAGvH;;;OAGG;IAEG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAkBpC"}