@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/dist/index.mjs ADDED
@@ -0,0 +1,4152 @@
1
+ import { Reflector } from '@nestjs/core';
2
+ import { Logger, Injectable, Inject, Optional, SetMetadata } from '@nestjs/common';
3
+ import 'reflect-metadata';
4
+ import { REDIS_DRIVER, RedisXError, ErrorCode } from '@nestjs-redisx/core';
5
+ import { createHash } from 'crypto';
6
+ import { EventEmitter } from 'events';
7
+ import { from, of } from 'rxjs';
8
+ import { switchMap, tap } from 'rxjs/operators';
9
+
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
12
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
13
+ }) : x)(function(x) {
14
+ if (typeof require !== "undefined") return require.apply(this, arguments);
15
+ throw Error('Dynamic require of "' + x + '" is not supported');
16
+ });
17
+ var __decorateClass = (decorators, target, key, kind) => {
18
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
19
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
20
+ if (decorator = decorators[i])
21
+ result = (decorator(result)) || result;
22
+ return result;
23
+ };
24
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
25
+
26
+ // src/shared/constants/index.ts
27
+ var CACHE_PLUGIN_OPTIONS = /* @__PURE__ */ Symbol.for("CACHE_PLUGIN_OPTIONS");
28
+ var CACHE_SERVICE = /* @__PURE__ */ Symbol.for("CACHE_SERVICE");
29
+ var L1_CACHE_STORE = /* @__PURE__ */ Symbol.for("L1_CACHE_STORE");
30
+ var L2_CACHE_STORE = /* @__PURE__ */ Symbol.for("L2_CACHE_STORE");
31
+ var STAMPEDE_PROTECTION = /* @__PURE__ */ Symbol.for("STAMPEDE_PROTECTION");
32
+ var TAG_INDEX = /* @__PURE__ */ Symbol.for("TAG_INDEX");
33
+ var SWR_MANAGER = /* @__PURE__ */ Symbol.for("SWR_MANAGER");
34
+ var SERIALIZER = /* @__PURE__ */ Symbol.for("SERIALIZER");
35
+ var INVALIDATION_REGISTRY = /* @__PURE__ */ Symbol.for("INVALIDATION_REGISTRY");
36
+ var EVENT_INVALIDATION_SERVICE = /* @__PURE__ */ Symbol.for("EVENT_INVALIDATION_SERVICE");
37
+ var LUA_SCRIPT_LOADER = /* @__PURE__ */ Symbol.for("LUA_SCRIPT_LOADER");
38
+ var INVALIDATION_RULES_INIT = /* @__PURE__ */ Symbol.for("INVALIDATION_RULES_INIT");
39
+ var AMQP_CONNECTION = /* @__PURE__ */ Symbol.for("AMQP_CONNECTION");
40
+ var CACHE_OPTIONS_KEY = "cache:options";
41
+ var INVALIDATE_TAGS_KEY = "cache:invalidate:tags";
42
+ var DEFAULT_CACHE_CONFIG = {
43
+ l1: {
44
+ enabled: true,
45
+ maxSize: 1e3,
46
+ ttl: 60,
47
+ evictionPolicy: "lru"
48
+ },
49
+ l2: {
50
+ enabled: true,
51
+ defaultTtl: 3600,
52
+ maxTtl: 86400,
53
+ keyPrefix: "cache:",
54
+ clientName: "default"
55
+ },
56
+ stampede: {
57
+ enabled: true,
58
+ lockTimeout: 5e3,
59
+ waitTimeout: 1e4,
60
+ fallback: "load"
61
+ },
62
+ swr: {
63
+ enabled: false,
64
+ defaultStaleTime: 60
65
+ },
66
+ tags: {
67
+ enabled: true,
68
+ indexPrefix: "_tag:",
69
+ maxTagsPerKey: 10
70
+ },
71
+ warmup: {
72
+ enabled: false,
73
+ concurrency: 10
74
+ },
75
+ keys: {
76
+ maxLength: 1024,
77
+ version: "v1",
78
+ separator: ":"
79
+ },
80
+ invalidation: {
81
+ enabled: true,
82
+ source: "internal",
83
+ deduplicationTtl: 60
84
+ }
85
+ };
86
+
87
+ // src/cache/api/decorators/cached.decorator.ts
88
+ var logger = new Logger("Cached");
89
+ var globalCacheServiceGetter = null;
90
+ var globalPluginOptions = null;
91
+ function registerCacheServiceGetter(getter) {
92
+ globalCacheServiceGetter = getter;
93
+ }
94
+ function registerCachePluginOptions(options) {
95
+ globalPluginOptions = options;
96
+ }
97
+ function getCacheService() {
98
+ return globalCacheServiceGetter ? globalCacheServiceGetter() : null;
99
+ }
100
+ function Cached(options = {}) {
101
+ return (target, propertyKey, descriptor) => {
102
+ const originalMethod = descriptor.value;
103
+ descriptor.value = async function(...args) {
104
+ if (!globalCacheServiceGetter) {
105
+ logger.warn(`@Cached: CacheService not yet available, executing method without cache`);
106
+ return originalMethod.apply(this, args);
107
+ }
108
+ const cacheService = globalCacheServiceGetter();
109
+ if (!cacheService) {
110
+ logger.warn(`@Cached: CacheService getter returned null, executing method without cache`);
111
+ return originalMethod.apply(this, args);
112
+ }
113
+ if (options.condition && !options.condition(...args)) {
114
+ return originalMethod.apply(this, args);
115
+ }
116
+ const key = buildCacheKey(this, propertyKey.toString(), args, options);
117
+ try {
118
+ const cached = await cacheService.get(key);
119
+ if (cached !== null) {
120
+ return cached;
121
+ }
122
+ } catch (error) {
123
+ logger.error(`@Cached: Cache get error for key ${key}:`, error);
124
+ }
125
+ const result = await originalMethod.apply(this, args);
126
+ if (options.unless?.(result, ...args)) {
127
+ return result;
128
+ }
129
+ await cacheResult(cacheService, key, result, options, args);
130
+ return result;
131
+ };
132
+ Object.defineProperty(descriptor.value, "name", {
133
+ value: originalMethod.name,
134
+ writable: false
135
+ });
136
+ Reflect.defineMetadata(CACHE_OPTIONS_KEY, options, descriptor.value);
137
+ return descriptor;
138
+ };
139
+ }
140
+ function buildCacheKey(instance, methodName, args, options) {
141
+ const className = instance.constructor.name;
142
+ let baseKey;
143
+ if (options.key) {
144
+ baseKey = interpolateKey(options.key, args);
145
+ } else {
146
+ const argKeys = args.map((arg) => serializeArg(arg)).join(":");
147
+ baseKey = `${className}:${methodName}:${argKeys}`;
148
+ }
149
+ return enrichWithContext(baseKey, options);
150
+ }
151
+ function enrichWithContext(key, options) {
152
+ if (options.skipContext) return key;
153
+ const pluginOpts = globalPluginOptions;
154
+ if (!pluginOpts?.contextProvider) return key;
155
+ const separator = pluginOpts.keys?.separator ?? ":";
156
+ const marker = `${separator}_ctx_${separator}`;
157
+ const contextMap = /* @__PURE__ */ new Map();
158
+ const contextKeys = options.contextKeys ?? pluginOpts.contextKeys ?? [];
159
+ for (const ctxKey of contextKeys) {
160
+ const value = pluginOpts.contextProvider.get(ctxKey);
161
+ if (value !== void 0 && value !== null) {
162
+ contextMap.set(ctxKey, String(value));
163
+ }
164
+ }
165
+ if (options.varyBy) {
166
+ for (const name of options.varyBy) {
167
+ if (!contextMap.has(name)) {
168
+ const value = pluginOpts.contextProvider.get(name);
169
+ if (value !== void 0 && value !== null) {
170
+ contextMap.set(name, String(value));
171
+ }
172
+ }
173
+ }
174
+ }
175
+ if (contextMap.size === 0) return key;
176
+ const sortedEntries = [...contextMap.entries()].sort(([a], [b]) => a.localeCompare(b));
177
+ const suffix = sortedEntries.map(([k, v]) => `${sanitizeForKey(k)}.${sanitizeForKey(v)}`).join(separator);
178
+ return `${key}${marker}${suffix}`;
179
+ }
180
+ function sanitizeForKey(value) {
181
+ return String(value).replace(/[^a-zA-Z0-9\-_]/g, "_");
182
+ }
183
+ function interpolateKey(template, args) {
184
+ return template.replace(/\{(\d+)\}/g, (match, index) => {
185
+ const argIndex = parseInt(index, 10);
186
+ if (argIndex < args.length) {
187
+ return serializeArg(args[argIndex]);
188
+ }
189
+ return match;
190
+ });
191
+ }
192
+ function serializeArg(arg) {
193
+ if (arg === null || arg === void 0) {
194
+ return "null";
195
+ }
196
+ if (typeof arg === "string" || typeof arg === "number" || typeof arg === "boolean") {
197
+ return String(arg);
198
+ }
199
+ if (typeof arg === "object") {
200
+ try {
201
+ return JSON.stringify(arg);
202
+ } catch {
203
+ return "object";
204
+ }
205
+ }
206
+ return "unknown";
207
+ }
208
+ async function cacheResult(cacheService, key, value, options, args) {
209
+ try {
210
+ const tags = typeof options.tags === "function" ? options.tags(...args) : options.tags;
211
+ if (options.swr?.enabled) {
212
+ await cacheService.getOrSet(
213
+ key,
214
+ // eslint-disable-next-line @typescript-eslint/require-await
215
+ async () => value,
216
+ {
217
+ ttl: options.ttl,
218
+ tags,
219
+ strategy: options.strategy,
220
+ swr: options.swr
221
+ }
222
+ );
223
+ } else {
224
+ await cacheService.set(key, value, {
225
+ ttl: options.ttl,
226
+ tags,
227
+ strategy: options.strategy
228
+ });
229
+ }
230
+ } catch (error) {
231
+ logger.error(`@Cached: Failed to cache result for key ${key}:`, error);
232
+ }
233
+ }
234
+
235
+ // src/invalidation/infrastructure/decorators/invalidate-on.decorator.ts
236
+ var INVALIDATE_ON_OPTIONS = /* @__PURE__ */ Symbol.for("INVALIDATE_ON_OPTIONS");
237
+ var globalEventInvalidationServiceGetter = null;
238
+ function registerEventInvalidationServiceGetter(getter) {
239
+ globalEventInvalidationServiceGetter = getter;
240
+ }
241
+ function getEventInvalidationService() {
242
+ return globalEventInvalidationServiceGetter ? globalEventInvalidationServiceGetter() : null;
243
+ }
244
+ function InvalidateOn(options) {
245
+ return (target, propertyKey, descriptor) => {
246
+ const originalMethod = descriptor.value;
247
+ descriptor.value = async function(...args) {
248
+ const result = await originalMethod.apply(this, args);
249
+ const cacheService = getCacheService();
250
+ if (cacheService) {
251
+ try {
252
+ if (options.condition && !options.condition(result, args)) {
253
+ return result;
254
+ }
255
+ const tags = resolveTags(options.tags, result, args);
256
+ const keys = resolveKeys(options.keys, result, args);
257
+ if (tags.length > 0) {
258
+ await cacheService.invalidateTags(tags);
259
+ }
260
+ if (keys.length > 0) {
261
+ await cacheService.deleteMany(keys);
262
+ }
263
+ if (options.publish) {
264
+ const eventInvalidationService = getEventInvalidationService();
265
+ if (eventInvalidationService) {
266
+ for (const event of options.events) {
267
+ await eventInvalidationService.emit(event, {
268
+ result,
269
+ args,
270
+ tags,
271
+ keys,
272
+ timestamp: Date.now()
273
+ });
274
+ }
275
+ }
276
+ }
277
+ } catch (error) {
278
+ console.error("@InvalidateOn: Invalidation failed:", error);
279
+ }
280
+ }
281
+ return result;
282
+ };
283
+ Object.defineProperty(descriptor.value, "name", {
284
+ value: originalMethod.name,
285
+ writable: false
286
+ });
287
+ Reflect.defineMetadata(INVALIDATE_ON_OPTIONS, options, descriptor.value);
288
+ return descriptor;
289
+ };
290
+ }
291
+ function resolveTags(tags, result, args) {
292
+ if (!tags) {
293
+ return [];
294
+ }
295
+ if (typeof tags === "function") {
296
+ return tags(result, args);
297
+ }
298
+ return tags;
299
+ }
300
+ function resolveKeys(keys, result, args) {
301
+ if (!keys) {
302
+ return [];
303
+ }
304
+ if (typeof keys === "function") {
305
+ return keys(result, args);
306
+ }
307
+ return keys;
308
+ }
309
+
310
+ // src/cache/application/services/cache-decorator-initializer.service.ts
311
+ var CacheDecoratorInitializerService = class {
312
+ constructor(moduleRef, cacheService, pluginOptions, eventInvalidationService) {
313
+ this.moduleRef = moduleRef;
314
+ this.cacheService = cacheService;
315
+ this.pluginOptions = pluginOptions;
316
+ this.eventInvalidationService = eventInvalidationService;
317
+ }
318
+ logger = new Logger(CacheDecoratorInitializerService.name);
319
+ /**
320
+ * Called after all modules are initialized.
321
+ * Registers cache service getter and plugin options for @Cached decorator.
322
+ */
323
+ // eslint-disable-next-line @typescript-eslint/require-await
324
+ async onModuleInit() {
325
+ this.logger.debug("Registering CacheService getter for @Cached decorator");
326
+ registerCacheServiceGetter(() => this.cacheService);
327
+ registerCachePluginOptions(this.pluginOptions);
328
+ this.logger.log("@Cached decorator initialized and ready to use");
329
+ if (this.eventInvalidationService) {
330
+ this.logger.debug("Registering EventInvalidationService getter for @InvalidateOn decorator");
331
+ registerEventInvalidationServiceGetter(() => this.eventInvalidationService);
332
+ this.logger.log("@InvalidateOn decorator event publishing initialized");
333
+ }
334
+ }
335
+ };
336
+ CacheDecoratorInitializerService = __decorateClass([
337
+ Injectable(),
338
+ __decorateParam(1, Inject(CACHE_SERVICE)),
339
+ __decorateParam(2, Inject(CACHE_PLUGIN_OPTIONS)),
340
+ __decorateParam(3, Optional()),
341
+ __decorateParam(3, Inject(EVENT_INVALIDATION_SERVICE))
342
+ ], CacheDecoratorInitializerService);
343
+ var CacheError = class extends RedisXError {
344
+ constructor(message, code = ErrorCode.OPERATION_FAILED, cause) {
345
+ super(message, code, cause);
346
+ this.name = "CacheError";
347
+ }
348
+ };
349
+ var CacheKeyError = class extends CacheError {
350
+ constructor(key, message) {
351
+ super(`Invalid cache key "${key}": ${message}`, ErrorCode.CACHE_KEY_INVALID);
352
+ this.key = key;
353
+ this.name = "CacheKeyError";
354
+ }
355
+ };
356
+ var SerializationError = class extends CacheError {
357
+ constructor(message, cause) {
358
+ super(`Serialization error: ${message}`, ErrorCode.SERIALIZATION_FAILED, cause);
359
+ this.name = "SerializationError";
360
+ }
361
+ };
362
+ var LoaderError = class extends CacheError {
363
+ constructor(key, cause) {
364
+ super(`Loader failed for key "${key}": ${cause.message}`, ErrorCode.OPERATION_FAILED, cause);
365
+ this.key = key;
366
+ this.name = "LoaderError";
367
+ }
368
+ };
369
+ var StampedeError = class extends CacheError {
370
+ constructor(key, timeout) {
371
+ super(`Stampede protection timeout for key "${key}" after ${timeout}ms`, ErrorCode.OPERATION_TIMEOUT);
372
+ this.key = key;
373
+ this.timeout = timeout;
374
+ this.name = "StampedeError";
375
+ }
376
+ };
377
+ var TagInvalidationError = class extends CacheError {
378
+ constructor(tag, message, cause) {
379
+ super(`Tag invalidation failed for "${tag}": ${message}`, ErrorCode.OPERATION_FAILED, cause);
380
+ this.tag = tag;
381
+ this.name = "TagInvalidationError";
382
+ }
383
+ };
384
+
385
+ // src/cache/domain/value-objects/cache-entry.vo.ts
386
+ var CacheEntry = class _CacheEntry {
387
+ constructor(value, cachedAt, ttl, tags) {
388
+ this.value = value;
389
+ this.cachedAt = cachedAt;
390
+ this.ttl = ttl;
391
+ this.tags = tags;
392
+ }
393
+ /**
394
+ * Creates a cache entry.
395
+ *
396
+ * @param value - Value to cache
397
+ * @param ttl - TTL in seconds
398
+ * @param tags - Optional tags
399
+ * @returns CacheEntry instance
400
+ */
401
+ static create(value, ttl, tags) {
402
+ return new _CacheEntry(value, Date.now(), ttl, tags);
403
+ }
404
+ /**
405
+ * Checks if entry is expired.
406
+ *
407
+ * @returns true if expired, false otherwise
408
+ */
409
+ isExpired() {
410
+ const expiresAt = this.cachedAt + this.ttl * 1e3;
411
+ return Date.now() > expiresAt;
412
+ }
413
+ /**
414
+ * Gets time until expiration in milliseconds.
415
+ *
416
+ * @returns Milliseconds until expiration (0 if expired)
417
+ */
418
+ getTimeToLive() {
419
+ const expiresAt = this.cachedAt + this.ttl * 1e3;
420
+ const remaining = expiresAt - Date.now();
421
+ return Math.max(0, remaining);
422
+ }
423
+ /**
424
+ * Gets age of entry in milliseconds.
425
+ *
426
+ * @returns Age in milliseconds
427
+ */
428
+ getAge() {
429
+ return Date.now() - this.cachedAt;
430
+ }
431
+ /**
432
+ * Checks if entry has a specific tag.
433
+ *
434
+ * @param tag - Tag to check
435
+ * @returns true if entry has the tag
436
+ */
437
+ hasTag(tag) {
438
+ return this.tags?.includes(tag) ?? false;
439
+ }
440
+ /**
441
+ * Serializes entry to JSON.
442
+ *
443
+ * @returns JSON representation
444
+ */
445
+ toJSON() {
446
+ return {
447
+ value: this.value,
448
+ cachedAt: this.cachedAt,
449
+ ttl: this.ttl,
450
+ tags: this.tags
451
+ };
452
+ }
453
+ /**
454
+ * Deserializes entry from JSON.
455
+ *
456
+ * @param json - JSON representation
457
+ * @returns CacheEntry instance
458
+ */
459
+ static fromJSON(json) {
460
+ return new _CacheEntry(json.value, json.cachedAt, json.ttl, json.tags);
461
+ }
462
+ };
463
+
464
+ // src/cache/domain/value-objects/cache-key.vo.ts
465
+ var DEFAULT_OPTIONS = {
466
+ maxLength: 512,
467
+ prefix: "",
468
+ version: "",
469
+ separator: ":"
470
+ };
471
+ var CacheKey = class _CacheKey {
472
+ constructor(rawKey, options) {
473
+ this.rawKey = rawKey;
474
+ this.options = options;
475
+ }
476
+ /**
477
+ * Creates a validated cache key.
478
+ *
479
+ * @param key - Raw key
480
+ * @param options - Key options
481
+ * @returns CacheKey instance
482
+ * @throws CacheKeyError if validation fails
483
+ */
484
+ static create(key, options = {}) {
485
+ const opts = {
486
+ ...DEFAULT_OPTIONS,
487
+ ...options
488
+ };
489
+ const normalizedKey = key.trim();
490
+ if (!normalizedKey || normalizedKey.length === 0) {
491
+ throw new CacheKeyError(key, "Key cannot be empty");
492
+ }
493
+ if (/\s/.test(normalizedKey)) {
494
+ throw new CacheKeyError(key, "Key cannot contain whitespace");
495
+ }
496
+ if (!/^[a-zA-Z0-9\-_:.]+$/.test(normalizedKey)) {
497
+ throw new CacheKeyError(key, "Invalid characters in key. Only alphanumeric, hyphens, underscores, colons, and dots allowed");
498
+ }
499
+ const fullKey = opts.prefix + opts.version + (opts.version ? opts.separator : "") + normalizedKey;
500
+ if (fullKey.length > opts.maxLength) {
501
+ throw new CacheKeyError(normalizedKey, `Key exceeds maximum length (${fullKey.length} > ${opts.maxLength})`);
502
+ }
503
+ return new _CacheKey(normalizedKey, opts);
504
+ }
505
+ /**
506
+ * Returns the full cache key with prefix and version.
507
+ */
508
+ toString() {
509
+ return this.options.prefix + this.options.version + (this.options.version ? this.options.separator : "") + this.rawKey;
510
+ }
511
+ /**
512
+ * Returns the raw key without prefix/version.
513
+ */
514
+ getRaw() {
515
+ return this.rawKey;
516
+ }
517
+ /**
518
+ * Returns the prefix.
519
+ */
520
+ getPrefix() {
521
+ return this.options.prefix;
522
+ }
523
+ /**
524
+ * Returns the version.
525
+ */
526
+ getVersion() {
527
+ return this.options.version;
528
+ }
529
+ /**
530
+ * Checks equality with another CacheKey.
531
+ */
532
+ equals(other) {
533
+ return this.toString() === other.toString();
534
+ }
535
+ };
536
+ var DEFAULT_MAX_LENGTH = 128;
537
+ var Tag = class _Tag {
538
+ constructor(value) {
539
+ this.value = value;
540
+ }
541
+ /**
542
+ * Creates a validated tag.
543
+ *
544
+ * @param value - Tag value
545
+ * @param maxLength - Maximum tag length (default: 128)
546
+ * @returns Tag instance
547
+ * @throws CacheError if validation fails
548
+ *
549
+ * @example
550
+ * ```typescript
551
+ * const tag = Tag.create('users');
552
+ * const productTag = Tag.create('product:123');
553
+ * ```
554
+ */
555
+ static create(value, maxLength = DEFAULT_MAX_LENGTH) {
556
+ if (!value || value.length === 0) {
557
+ throw new CacheError("Tag cannot be empty", ErrorCode.CACHE_KEY_INVALID);
558
+ }
559
+ const normalized = value.trim().toLowerCase();
560
+ if (normalized.length === 0) {
561
+ throw new CacheError("Tag cannot be empty after normalization", ErrorCode.CACHE_KEY_INVALID);
562
+ }
563
+ if (/\s/.test(normalized)) {
564
+ throw new CacheError("Tag cannot contain whitespace", ErrorCode.CACHE_KEY_INVALID);
565
+ }
566
+ if (!/^[a-z0-9\-_:.]+$/.test(normalized)) {
567
+ throw new CacheError("Invalid tag characters. Only lowercase alphanumeric, hyphens, underscores, colons, and dots allowed", ErrorCode.CACHE_KEY_INVALID);
568
+ }
569
+ if (normalized.length > maxLength) {
570
+ throw new CacheError(`Tag exceeds maximum length (${normalized.length} > ${maxLength})`, ErrorCode.CACHE_KEY_INVALID);
571
+ }
572
+ return new _Tag(normalized);
573
+ }
574
+ /**
575
+ * Returns the tag value.
576
+ */
577
+ toString() {
578
+ return this.value;
579
+ }
580
+ /**
581
+ * Returns the raw tag value.
582
+ */
583
+ getRaw() {
584
+ return this.value;
585
+ }
586
+ /**
587
+ * Checks equality with another tag.
588
+ */
589
+ equals(other) {
590
+ return this.value === other.value;
591
+ }
592
+ };
593
+ var DEFAULT_MAX_TAGS = 10;
594
+ var Tags = class _Tags {
595
+ constructor(tags) {
596
+ this.tags = tags;
597
+ }
598
+ /**
599
+ * Creates a validated tags collection.
600
+ *
601
+ * @param values - Array of tag values
602
+ * @param maxTags - Maximum number of tags (default: 10)
603
+ * @returns Tags instance
604
+ * @throws CacheError if validation fails
605
+ *
606
+ * @example
607
+ * ```typescript
608
+ * const tags = Tags.create(['users', 'product:123']);
609
+ * const singleTag = Tags.create(['cache']);
610
+ * ```
611
+ */
612
+ static create(values, maxTags = DEFAULT_MAX_TAGS) {
613
+ if (values.length > maxTags) {
614
+ throw new CacheError(`Too many tags (${values.length} > ${maxTags})`, ErrorCode.CACHE_KEY_INVALID);
615
+ }
616
+ const tagObjects = values.map((v) => Tag.create(v));
617
+ const uniqueValues = Array.from(new Set(tagObjects.map((t) => t.toString())));
618
+ const uniqueTags = uniqueValues.map((v) => Tag.create(v));
619
+ return new _Tags(uniqueTags);
620
+ }
621
+ /**
622
+ * Creates empty tags collection.
623
+ */
624
+ static empty() {
625
+ return new _Tags([]);
626
+ }
627
+ /**
628
+ * Returns array of tag strings.
629
+ */
630
+ toStrings() {
631
+ return this.tags.map((t) => t.toString());
632
+ }
633
+ /**
634
+ * Returns array of Tag objects.
635
+ */
636
+ toArray() {
637
+ return [...this.tags];
638
+ }
639
+ /**
640
+ * Returns number of tags.
641
+ */
642
+ size() {
643
+ return this.tags.length;
644
+ }
645
+ /**
646
+ * Checks if collection contains a specific tag.
647
+ */
648
+ has(tag) {
649
+ return this.tags.some((t) => t.equals(tag));
650
+ }
651
+ /**
652
+ * Checks if collection is empty.
653
+ */
654
+ isEmpty() {
655
+ return this.tags.length === 0;
656
+ }
657
+ /**
658
+ * Iterates over tags.
659
+ */
660
+ forEach(callback) {
661
+ this.tags.forEach(callback);
662
+ }
663
+ /**
664
+ * Maps over tags.
665
+ */
666
+ map(callback) {
667
+ return this.tags.map(callback);
668
+ }
669
+ };
670
+ var DEFAULT_MAX_TTL_SECONDS = 86400;
671
+ var TTL = class _TTL {
672
+ constructor(seconds) {
673
+ this.seconds = seconds;
674
+ }
675
+ /**
676
+ * Creates a TTL value object.
677
+ *
678
+ * @param seconds - TTL in seconds
679
+ * @param maxTtl - Maximum allowed TTL
680
+ * @returns TTL instance
681
+ * @throws CacheError if validation fails
682
+ */
683
+ static create(seconds, maxTtl = DEFAULT_MAX_TTL_SECONDS) {
684
+ if (seconds <= 0) {
685
+ throw new CacheError(`TTL must be positive (got ${seconds})`, ErrorCode.CACHE_KEY_INVALID);
686
+ }
687
+ if (seconds > maxTtl) {
688
+ throw new CacheError(`TTL exceeds maximum (${seconds} > ${maxTtl})`, ErrorCode.CACHE_KEY_INVALID);
689
+ }
690
+ const rounded = Math.round(seconds);
691
+ return new _TTL(rounded);
692
+ }
693
+ /**
694
+ * Creates TTL from milliseconds.
695
+ *
696
+ * @param milliseconds - TTL in milliseconds
697
+ * @param maxTtl - Maximum allowed TTL in seconds
698
+ * @returns TTL instance
699
+ */
700
+ static fromMilliseconds(milliseconds, maxTtl = DEFAULT_MAX_TTL_SECONDS) {
701
+ return _TTL.create(Math.ceil(milliseconds / 1e3), maxTtl);
702
+ }
703
+ /**
704
+ * Returns TTL in seconds.
705
+ */
706
+ toSeconds() {
707
+ return this.seconds;
708
+ }
709
+ /**
710
+ * Returns TTL in milliseconds.
711
+ */
712
+ toMilliseconds() {
713
+ return this.seconds * 1e3;
714
+ }
715
+ /**
716
+ * Checks if TTL is less than another TTL.
717
+ */
718
+ isLessThan(other) {
719
+ return this.seconds < other.seconds;
720
+ }
721
+ /**
722
+ * Checks if TTL is greater than another TTL.
723
+ */
724
+ isGreaterThan(other) {
725
+ return this.seconds > other.seconds;
726
+ }
727
+ /**
728
+ * Returns the minimum of two TTLs.
729
+ */
730
+ static min(a, b) {
731
+ return a.isLessThan(b) ? a : b;
732
+ }
733
+ /**
734
+ * Returns the maximum of two TTLs.
735
+ */
736
+ static max(a, b) {
737
+ return a.isGreaterThan(b) ? a : b;
738
+ }
739
+ /**
740
+ * Checks equality with another TTL.
741
+ */
742
+ equals(other) {
743
+ return this.seconds === other.seconds;
744
+ }
745
+ /**
746
+ * Returns string representation.
747
+ */
748
+ toString() {
749
+ return `${this.seconds}s`;
750
+ }
751
+ };
752
+
753
+ // src/cache/application/services/cache.service.ts
754
+ var METRICS_SERVICE = /* @__PURE__ */ Symbol.for("METRICS_SERVICE");
755
+ var TRACING_SERVICE = /* @__PURE__ */ Symbol.for("TRACING_SERVICE");
756
+ var CacheService = class {
757
+ constructor(driver, l1Store, l2Store, stampede, tagIndex, swrManager, options, metrics, tracing) {
758
+ this.driver = driver;
759
+ this.l1Store = l1Store;
760
+ this.l2Store = l2Store;
761
+ this.stampede = stampede;
762
+ this.tagIndex = tagIndex;
763
+ this.swrManager = swrManager;
764
+ this.options = options;
765
+ this.metrics = metrics;
766
+ this.tracing = tracing;
767
+ this.l1Enabled = options.l1?.enabled ?? true;
768
+ this.l2Enabled = options.l2?.enabled ?? true;
769
+ this.keyPrefix = options.l2?.keyPrefix ?? "cache:";
770
+ this.stampedeEnabled = options.stampede?.enabled ?? true;
771
+ this.swrEnabled = options.swr?.enabled ?? false;
772
+ this.tagsEnabled = options.tags?.enabled ?? true;
773
+ }
774
+ logger = new Logger(CacheService.name);
775
+ l1Enabled;
776
+ l2Enabled;
777
+ stampedeEnabled;
778
+ swrEnabled;
779
+ tagsEnabled;
780
+ keyPrefix;
781
+ async get(key) {
782
+ const span = this.tracing?.startSpan("cache.get", {
783
+ kind: "INTERNAL",
784
+ attributes: { "cache.key": key }
785
+ });
786
+ try {
787
+ const normalizedKey = this.validateAndNormalizeKey(key);
788
+ const enrichedKey = this.enrichKeyWithContext(normalizedKey);
789
+ if (this.l1Enabled) {
790
+ const l1Entry = await this.l1Store.get(enrichedKey);
791
+ if (l1Entry) {
792
+ this.logger.debug(`L1 hit for key: ${key}`);
793
+ this.metrics?.incrementCounter("redisx_cache_hits_total", { layer: "l1" });
794
+ span?.setAttribute("cache.hit", true);
795
+ span?.setAttribute("cache.layer", "l1");
796
+ span?.setStatus("OK");
797
+ return l1Entry.value;
798
+ }
799
+ this.metrics?.incrementCounter("redisx_cache_misses_total", { layer: "l1" });
800
+ }
801
+ if (this.l2Enabled) {
802
+ const l2Entry = await this.l2Store.get(enrichedKey);
803
+ if (l2Entry) {
804
+ this.logger.debug(`L2 hit for key: ${key}`);
805
+ this.metrics?.incrementCounter("redisx_cache_hits_total", { layer: "l2" });
806
+ span?.setAttribute("cache.hit", true);
807
+ span?.setAttribute("cache.layer", "l2");
808
+ span?.setStatus("OK");
809
+ if (this.l1Enabled) {
810
+ await this.l1Store.set(enrichedKey, l2Entry, this.options.l1?.ttl);
811
+ }
812
+ return l2Entry.value;
813
+ }
814
+ this.metrics?.incrementCounter("redisx_cache_misses_total", { layer: "l2" });
815
+ }
816
+ span?.setAttribute("cache.hit", false);
817
+ span?.setStatus("OK");
818
+ return null;
819
+ } catch (error) {
820
+ if (error instanceof CacheKeyError) {
821
+ this.logger.warn(`Invalid cache key "${key}": ${error.message}`);
822
+ } else {
823
+ this.logger.error(`Cache get failed for key ${key}:`, error);
824
+ }
825
+ span?.recordException(error);
826
+ span?.setStatus("ERROR");
827
+ return null;
828
+ } finally {
829
+ span?.end();
830
+ }
831
+ }
832
+ async set(key, value, options = {}) {
833
+ const span = this.tracing?.startSpan("cache.set", {
834
+ kind: "INTERNAL",
835
+ attributes: { "cache.key": key, "cache.ttl": options.ttl }
836
+ });
837
+ try {
838
+ const normalizedKey = this.validateAndNormalizeKey(key);
839
+ const enrichedKey = this.enrichKeyWithContext(normalizedKey, options.varyBy);
840
+ const ttlSeconds = options.ttl ?? this.options.l2?.defaultTtl ?? 3600;
841
+ const maxTtl = this.options.l2?.maxTtl ?? 86400;
842
+ const ttl = TTL.create(ttlSeconds, maxTtl);
843
+ const entry = CacheEntry.create(value, ttl.toSeconds());
844
+ const strategy = options.strategy ?? "l1-l2";
845
+ span?.setAttribute("cache.strategy", strategy);
846
+ if (this.l2Enabled && strategy !== "l1-only") {
847
+ await this.l2Store.set(enrichedKey, entry, ttl.toSeconds());
848
+ }
849
+ if (this.l1Enabled && strategy !== "l2-only") {
850
+ const l1MaxTtl = TTL.create(this.options.l1?.ttl ?? 60, maxTtl);
851
+ const l1Ttl = TTL.min(ttl, l1MaxTtl);
852
+ await this.l1Store.set(enrichedKey, entry, l1Ttl.toSeconds());
853
+ }
854
+ if (this.tagsEnabled && options.tags && options.tags.length > 0 && strategy !== "l1-only") {
855
+ const fullKey = `${this.keyPrefix}${enrichedKey}`;
856
+ await this.tagIndex.addKeyToTags(fullKey, options.tags);
857
+ span?.setAttribute("cache.tags", options.tags.join(","));
858
+ }
859
+ span?.setStatus("OK");
860
+ } catch (error) {
861
+ span?.recordException(error);
862
+ span?.setStatus("ERROR");
863
+ if (error instanceof CacheKeyError || error instanceof CacheError) {
864
+ throw error;
865
+ }
866
+ throw new CacheError(`Failed to set cache for key "${key}": ${error.message}`, ErrorCode.CACHE_SET_FAILED, error);
867
+ } finally {
868
+ span?.end();
869
+ }
870
+ }
871
+ async getOrSet(key, loader, options = {}) {
872
+ const normalizedKey = this.validateAndNormalizeKey(key);
873
+ const enrichedKey = this.enrichKeyWithContext(normalizedKey, options.varyBy);
874
+ const swrEnabled = this.swrEnabled && (options.swr?.enabled ?? true);
875
+ if (swrEnabled) {
876
+ const swrEntry = await this.l2Store.getSwr(enrichedKey);
877
+ if (swrEntry) {
878
+ const isExpired = this.swrManager.isExpired(swrEntry);
879
+ if (!isExpired) {
880
+ const isStale = this.swrManager.isStale(swrEntry);
881
+ if (isStale && this.swrManager.shouldRevalidate(enrichedKey)) {
882
+ void this.swrManager.scheduleRevalidation(
883
+ enrichedKey,
884
+ loader,
885
+ async (freshValue) => {
886
+ await this.set(enrichedKey, freshValue, {
887
+ ttl: options.ttl,
888
+ tags: options.tags,
889
+ strategy: options.strategy
890
+ });
891
+ },
892
+ (error) => {
893
+ this.logger.error(`SWR revalidation failed for key ${enrichedKey}:`, error);
894
+ }
895
+ );
896
+ }
897
+ return swrEntry.value;
898
+ }
899
+ }
900
+ const value = await this.loadWithStampede(enrichedKey, loader, options);
901
+ const staleTime = options.swr?.staleTime ?? this.options.swr?.defaultStaleTime ?? 60;
902
+ const ttl = options.ttl ?? this.options.l2?.defaultTtl ?? 3600;
903
+ const swrEntryNew = this.swrManager.createSwrEntry(value, ttl, staleTime);
904
+ await this.l2Store.setSwr(enrichedKey, swrEntryNew);
905
+ return value;
906
+ }
907
+ const cached = await this.get(enrichedKey);
908
+ if (cached !== null) {
909
+ return cached;
910
+ }
911
+ return this.loadWithStampede(enrichedKey, loader, options);
912
+ }
913
+ /**
914
+ * Loads value with stampede protection if enabled.
915
+ *
916
+ * @param key - Normalized cache key
917
+ * @param loader - Function to load value
918
+ * @param options - Cache options
919
+ * @returns Loaded value
920
+ * @private
921
+ */
922
+ async loadWithStampede(key, loader, options) {
923
+ if (this.stampedeEnabled && !options.skipStampede) {
924
+ const result = await this.stampede.protect(key, loader);
925
+ if (result.cached) {
926
+ this.metrics?.incrementCounter("redisx_cache_stampede_prevented_total");
927
+ return result.value;
928
+ }
929
+ await this.set(key, result.value, {
930
+ ttl: options.ttl,
931
+ tags: options.tags,
932
+ strategy: options.strategy
933
+ });
934
+ return result.value;
935
+ }
936
+ const value = await loader();
937
+ await this.set(key, value, {
938
+ ttl: options.ttl,
939
+ tags: options.tags,
940
+ strategy: options.strategy
941
+ });
942
+ return value;
943
+ }
944
+ async delete(key) {
945
+ try {
946
+ const normalizedKey = this.validateAndNormalizeKey(key);
947
+ const enrichedKey = this.enrichKeyWithContext(normalizedKey);
948
+ let deleted = false;
949
+ if (this.l1Enabled) {
950
+ const l1Deleted = await this.l1Store.delete(enrichedKey);
951
+ deleted = deleted || l1Deleted;
952
+ }
953
+ if (this.l2Enabled) {
954
+ const l2Deleted = await this.l2Store.delete(enrichedKey);
955
+ deleted = deleted || l2Deleted;
956
+ }
957
+ return deleted;
958
+ } catch (error) {
959
+ if (error instanceof CacheKeyError) {
960
+ throw error;
961
+ }
962
+ throw new CacheError(`Failed to delete cache for key "${key}": ${error.message}`, ErrorCode.CACHE_DELETE_FAILED, error);
963
+ }
964
+ }
965
+ async deleteMany(keys) {
966
+ if (keys.length === 0) {
967
+ return 0;
968
+ }
969
+ try {
970
+ const normalizedKeys = keys.map((key) => {
971
+ try {
972
+ const normalized = this.validateAndNormalizeKey(key);
973
+ return this.enrichKeyWithContext(normalized);
974
+ } catch (error) {
975
+ if (error instanceof CacheKeyError) {
976
+ this.logger.warn(`Invalid key in deleteMany "${key}": ${error.message}`);
977
+ }
978
+ return null;
979
+ }
980
+ }).filter((k) => k !== null);
981
+ if (normalizedKeys.length === 0) {
982
+ return 0;
983
+ }
984
+ let deletedCount = 0;
985
+ if (this.l1Enabled) {
986
+ for (const key of normalizedKeys) {
987
+ const deleted = await this.l1Store.delete(key);
988
+ if (deleted) deletedCount++;
989
+ }
990
+ }
991
+ if (this.l2Enabled && normalizedKeys.length > 0) {
992
+ const fullKeys = normalizedKeys.map((k) => `${this.keyPrefix}${k}`);
993
+ const pipeline = this.driver.pipeline();
994
+ for (const fullKey of fullKeys) {
995
+ pipeline.del(fullKey);
996
+ }
997
+ const results = await pipeline.exec();
998
+ let l2Count = 0;
999
+ if (results) {
1000
+ for (const [error, result] of results) {
1001
+ if (!error && typeof result === "number" && result > 0) {
1002
+ l2Count++;
1003
+ }
1004
+ }
1005
+ }
1006
+ deletedCount = Math.max(deletedCount, l2Count);
1007
+ }
1008
+ return deletedCount;
1009
+ } catch (error) {
1010
+ throw new CacheError(`Failed to delete multiple keys: ${error.message}`, ErrorCode.CACHE_DELETE_FAILED, error);
1011
+ }
1012
+ }
1013
+ async clear() {
1014
+ try {
1015
+ if (this.l1Enabled) {
1016
+ await this.l1Store.clear();
1017
+ }
1018
+ if (this.l2Enabled) {
1019
+ await this.l2Store.clear();
1020
+ }
1021
+ if (this.tagsEnabled) {
1022
+ await this.tagIndex.clearAllTags();
1023
+ }
1024
+ } catch (error) {
1025
+ throw new CacheError(`Failed to clear cache: ${error.message}`, ErrorCode.CACHE_CLEAR_FAILED, error);
1026
+ }
1027
+ }
1028
+ async has(key) {
1029
+ try {
1030
+ const normalizedKey = this.validateAndNormalizeKey(key);
1031
+ const enrichedKey = this.enrichKeyWithContext(normalizedKey);
1032
+ if (this.l1Enabled) {
1033
+ const l1Has = await this.l1Store.has(enrichedKey);
1034
+ if (l1Has) return true;
1035
+ }
1036
+ if (this.l2Enabled) {
1037
+ return await this.l2Store.has(enrichedKey);
1038
+ }
1039
+ return false;
1040
+ } catch (error) {
1041
+ if (error instanceof CacheKeyError) {
1042
+ this.logger.warn(`Invalid cache key "${key}": ${error.message}`);
1043
+ }
1044
+ return false;
1045
+ }
1046
+ }
1047
+ async invalidateTag(tag) {
1048
+ if (!this.tagsEnabled) {
1049
+ return 0;
1050
+ }
1051
+ try {
1052
+ const keysWithPrefix = await this.tagIndex.getKeysByTag(tag);
1053
+ if (this.l1Enabled) {
1054
+ const keysWithoutPrefix = keysWithPrefix.map((key) => key.startsWith(this.keyPrefix) ? key.slice(this.keyPrefix.length) : key);
1055
+ await Promise.all(keysWithoutPrefix.map((key) => this.l1Store.delete(key)));
1056
+ }
1057
+ return await this.tagIndex.invalidateTag(tag);
1058
+ } catch (error) {
1059
+ throw new CacheError(`Failed to invalidate tag "${tag}": ${error.message}`, ErrorCode.CACHE_TAG_INVALIDATION_FAILED, error);
1060
+ }
1061
+ }
1062
+ async invalidateTags(tags) {
1063
+ if (!this.tagsEnabled || tags.length === 0) {
1064
+ return 0;
1065
+ }
1066
+ try {
1067
+ let total = 0;
1068
+ for (const tag of tags) {
1069
+ const count = await this.invalidateTag(tag);
1070
+ total += count;
1071
+ }
1072
+ return total;
1073
+ } catch (error) {
1074
+ throw new CacheError(`Failed to invalidate tags: ${error.message}`, ErrorCode.CACHE_TAG_INVALIDATION_FAILED, error);
1075
+ }
1076
+ }
1077
+ async getKeysByTag(tag) {
1078
+ if (!this.tagsEnabled) {
1079
+ return [];
1080
+ }
1081
+ try {
1082
+ const validTag = Tag.create(tag);
1083
+ const keysWithPrefix = await this.tagIndex.getKeysByTag(validTag.toString());
1084
+ return keysWithPrefix.map((key) => key.startsWith(this.keyPrefix) ? key.slice(this.keyPrefix.length) : key);
1085
+ } catch (error) {
1086
+ this.logger.error(`Failed to get keys for tag ${tag}:`, error);
1087
+ return [];
1088
+ }
1089
+ }
1090
+ async getMany(keys) {
1091
+ if (keys.length === 0) {
1092
+ return [];
1093
+ }
1094
+ try {
1095
+ const normalizedKeys = keys.map((key) => {
1096
+ try {
1097
+ const normalized = this.validateAndNormalizeKey(key);
1098
+ return this.enrichKeyWithContext(normalized);
1099
+ } catch (error) {
1100
+ if (error instanceof CacheKeyError) {
1101
+ this.logger.warn(`Invalid cache key in getMany "${key}": ${error.message}`);
1102
+ }
1103
+ return null;
1104
+ }
1105
+ });
1106
+ if (!this.l2Enabled) {
1107
+ return keys.map(() => null);
1108
+ }
1109
+ const validKeys = [];
1110
+ const indexMap = /* @__PURE__ */ new Map();
1111
+ normalizedKeys.forEach((key, index) => {
1112
+ if (key !== null) {
1113
+ indexMap.set(validKeys.length, index);
1114
+ validKeys.push(key);
1115
+ }
1116
+ });
1117
+ if (validKeys.length === 0) {
1118
+ return keys.map(() => null);
1119
+ }
1120
+ const entries = await this.l2Store.getMany(validKeys);
1121
+ const result = keys.map(() => null);
1122
+ entries.forEach((entry, validIndex) => {
1123
+ const originalIndex = indexMap.get(validIndex);
1124
+ if (originalIndex !== void 0) {
1125
+ result[originalIndex] = entry ? entry.value : null;
1126
+ }
1127
+ });
1128
+ return result;
1129
+ } catch (error) {
1130
+ this.logger.error("Failed to getMany:", error);
1131
+ return keys.map(() => null);
1132
+ }
1133
+ }
1134
+ async setMany(entries) {
1135
+ if (entries.length === 0) {
1136
+ return;
1137
+ }
1138
+ try {
1139
+ if (!this.l2Enabled) {
1140
+ return;
1141
+ }
1142
+ const maxTtl = this.options.l2?.maxTtl ?? 86400;
1143
+ const defaultTtl = this.options.l2?.defaultTtl ?? 3600;
1144
+ const cacheEntries = entries.map(({ key, value, ttl }) => {
1145
+ const entryTtl = ttl ?? defaultTtl;
1146
+ return {
1147
+ key: this.enrichKeyWithContext(this.validateAndNormalizeKey(key)),
1148
+ entry: CacheEntry.create(value, Math.min(entryTtl, maxTtl)),
1149
+ ttl: entryTtl
1150
+ };
1151
+ });
1152
+ await this.l2Store.setMany(cacheEntries);
1153
+ if (this.tagsEnabled) {
1154
+ for (let i = 0; i < entries.length; i++) {
1155
+ const entry = entries[i];
1156
+ const { tags } = entry;
1157
+ if (tags && tags.length > 0) {
1158
+ const enrichedKey = cacheEntries[i].key;
1159
+ const fullKey = `${this.keyPrefix}${enrichedKey}`;
1160
+ const validatedTags = Tags.create(tags).toStrings();
1161
+ await this.tagIndex.addKeyToTags(fullKey, validatedTags);
1162
+ }
1163
+ }
1164
+ }
1165
+ } catch (error) {
1166
+ if (error instanceof CacheKeyError) {
1167
+ throw error;
1168
+ }
1169
+ throw new CacheError(`Failed to setMany: ${error.message}`, ErrorCode.CACHE_SET_FAILED, error);
1170
+ }
1171
+ }
1172
+ async ttl(key) {
1173
+ if (!this.l2Enabled) {
1174
+ return -1;
1175
+ }
1176
+ try {
1177
+ const normalizedKey = this.validateAndNormalizeKey(key);
1178
+ const enrichedKey = this.enrichKeyWithContext(normalizedKey);
1179
+ return await this.l2Store.ttl(enrichedKey);
1180
+ } catch (error) {
1181
+ if (error instanceof CacheKeyError) {
1182
+ this.logger.warn(`Invalid cache key "${key}": ${error.message}`);
1183
+ }
1184
+ return -1;
1185
+ }
1186
+ }
1187
+ async getStats() {
1188
+ const l1Stats = this.l1Enabled ? this.l1Store.getStats() : { hits: 0, misses: 0, size: 0 };
1189
+ const l2Stats = this.l2Enabled ? await this.l2Store.getStats() : { hits: 0, misses: 0 };
1190
+ const stampedeStats = this.stampedeEnabled ? this.stampede.getStats() : { prevented: 0 };
1191
+ return {
1192
+ l1: l1Stats,
1193
+ l2: l2Stats,
1194
+ stampedePrevented: stampedeStats.prevented
1195
+ };
1196
+ }
1197
+ async invalidateByPattern(pattern) {
1198
+ if (!this.l2Enabled) {
1199
+ return 0;
1200
+ }
1201
+ try {
1202
+ const result = await this.l2Store.scan(pattern);
1203
+ const keys = result.keys;
1204
+ if (keys.length === 0) {
1205
+ return 0;
1206
+ }
1207
+ if (this.l1Enabled) {
1208
+ const keysWithoutPrefix = keys.map((key) => key.startsWith(this.keyPrefix) ? key.slice(this.keyPrefix.length) : key);
1209
+ await Promise.all(keysWithoutPrefix.map((key) => this.l1Store.delete(key)));
1210
+ }
1211
+ let deleted = 0;
1212
+ for (const key of keys) {
1213
+ const wasDeleted = await this.l2Store.delete(key);
1214
+ if (wasDeleted) {
1215
+ deleted++;
1216
+ }
1217
+ }
1218
+ return deleted;
1219
+ } catch (error) {
1220
+ throw new CacheError(`Failed to invalidate by pattern "${pattern}": ${error.message}`, ErrorCode.CACHE_DELETE_FAILED, error);
1221
+ }
1222
+ }
1223
+ /**
1224
+ * Validates and normalizes cache key using CacheKey value object.
1225
+ *
1226
+ * @param rawKey - Raw key string
1227
+ * @returns Normalized key string (without prefix - prefix added by L2 store)
1228
+ * @throws CacheKeyError if validation fails
1229
+ *
1230
+ * @private
1231
+ */
1232
+ validateAndNormalizeKey(rawKey) {
1233
+ try {
1234
+ const keyOptions = {
1235
+ maxLength: this.options.keys?.maxLength ?? 1024,
1236
+ version: this.options.keys?.version,
1237
+ separator: this.options.keys?.separator ?? ":",
1238
+ // Don't include prefix here - it's added by L2 store
1239
+ prefix: ""
1240
+ };
1241
+ const cacheKey = CacheKey.create(rawKey, keyOptions);
1242
+ return cacheKey.getRaw();
1243
+ } catch (error) {
1244
+ if (error instanceof CacheKeyError) {
1245
+ throw error;
1246
+ }
1247
+ throw new CacheKeyError(rawKey, `Invalid cache key: ${error.message}`);
1248
+ }
1249
+ }
1250
+ /**
1251
+ * Enriches a normalized key with context values.
1252
+ * Appends global context keys and per-call varyBy values as key suffix.
1253
+ * Uses a marker (_ctx_) to prevent double-enrichment in internal call chains.
1254
+ *
1255
+ * @param normalizedKey - Already validated and normalized cache key
1256
+ * @param varyBy - Optional per-call context overrides
1257
+ * @returns Enriched key with context suffix, or original key if no context
1258
+ * @private
1259
+ */
1260
+ enrichKeyWithContext(normalizedKey, varyBy) {
1261
+ const separator = this.options.keys?.separator ?? ":";
1262
+ const marker = `${separator}_ctx_${separator}`;
1263
+ if (normalizedKey.includes(marker)) {
1264
+ return normalizedKey;
1265
+ }
1266
+ const contextProvider = this.options.contextProvider;
1267
+ const contextKeys = this.options.contextKeys;
1268
+ const contextMap = /* @__PURE__ */ new Map();
1269
+ if (contextProvider && contextKeys && contextKeys.length > 0) {
1270
+ for (const ctxKey of contextKeys) {
1271
+ const value = contextProvider.get(ctxKey);
1272
+ if (value !== void 0 && value !== null) {
1273
+ contextMap.set(ctxKey, String(value));
1274
+ }
1275
+ }
1276
+ }
1277
+ if (varyBy) {
1278
+ for (const [k, v] of Object.entries(varyBy)) {
1279
+ contextMap.set(k, v);
1280
+ }
1281
+ }
1282
+ if (contextMap.size === 0) return normalizedKey;
1283
+ const sortedEntries = [...contextMap.entries()].sort(([a], [b]) => a.localeCompare(b));
1284
+ const suffix = sortedEntries.map(([k, v]) => `${this.sanitizeForKey(k)}.${this.sanitizeForKey(v)}`).join(separator);
1285
+ return `${normalizedKey}${marker}${suffix}`;
1286
+ }
1287
+ /**
1288
+ * Sanitizes a value for use in cache key (removes non-allowed characters).
1289
+ * @private
1290
+ */
1291
+ sanitizeForKey(value) {
1292
+ return String(value).replace(/[^a-zA-Z0-9\-_]/g, "_");
1293
+ }
1294
+ };
1295
+ CacheService = __decorateClass([
1296
+ Injectable(),
1297
+ __decorateParam(0, Inject(REDIS_DRIVER)),
1298
+ __decorateParam(1, Inject(L1_CACHE_STORE)),
1299
+ __decorateParam(2, Inject(L2_CACHE_STORE)),
1300
+ __decorateParam(3, Inject(STAMPEDE_PROTECTION)),
1301
+ __decorateParam(4, Inject(TAG_INDEX)),
1302
+ __decorateParam(5, Inject(SWR_MANAGER)),
1303
+ __decorateParam(6, Inject(CACHE_PLUGIN_OPTIONS)),
1304
+ __decorateParam(7, Optional()),
1305
+ __decorateParam(7, Inject(METRICS_SERVICE)),
1306
+ __decorateParam(8, Optional()),
1307
+ __decorateParam(8, Inject(TRACING_SERVICE))
1308
+ ], CacheService);
1309
+ var WarmupService = class {
1310
+ constructor(cacheService, options) {
1311
+ this.cacheService = cacheService;
1312
+ this.options = options;
1313
+ this.enabled = options.warmup?.enabled ?? false;
1314
+ this.keys = options.warmup?.keys ?? [];
1315
+ this.concurrency = options.warmup?.concurrency ?? 10;
1316
+ }
1317
+ logger = new Logger(WarmupService.name);
1318
+ enabled;
1319
+ keys;
1320
+ concurrency;
1321
+ async onModuleInit() {
1322
+ if (!this.enabled || this.keys.length === 0) {
1323
+ return;
1324
+ }
1325
+ this.logger.log(`Starting cache warmup for ${this.keys.length} keys...`);
1326
+ const startTime = Date.now();
1327
+ const chunks = [];
1328
+ for (let i = 0; i < this.keys.length; i += this.concurrency) {
1329
+ chunks.push(this.keys.slice(i, i + this.concurrency));
1330
+ }
1331
+ let succeeded = 0;
1332
+ let failed = 0;
1333
+ for (const chunk of chunks) {
1334
+ const results = await Promise.allSettled(chunk.map((warmupKey) => this.warmupKey(warmupKey)));
1335
+ succeeded += results.filter((r) => r.status === "fulfilled").length;
1336
+ failed += results.filter((r) => r.status === "rejected").length;
1337
+ }
1338
+ const duration = Date.now() - startTime;
1339
+ this.logger.log(`Cache warmup completed: ${succeeded} succeeded, ${failed} failed (${duration}ms)`);
1340
+ }
1341
+ /**
1342
+ * Warms up a single cache key.
1343
+ *
1344
+ * @param warmupKey - Warmup key configuration
1345
+ * @private
1346
+ */
1347
+ async warmupKey(warmupKey) {
1348
+ try {
1349
+ this.logger.debug(`Warming up key: ${warmupKey.key}`);
1350
+ await this.cacheService.getOrSet(warmupKey.key, warmupKey.loader, {
1351
+ ttl: warmupKey.ttl,
1352
+ tags: warmupKey.tags
1353
+ });
1354
+ this.logger.debug(`Successfully warmed up key: ${warmupKey.key}`);
1355
+ } catch (error) {
1356
+ this.logger.error(`Failed to warm up key ${warmupKey.key}:`, error);
1357
+ throw error;
1358
+ }
1359
+ }
1360
+ };
1361
+ WarmupService = __decorateClass([
1362
+ Injectable(),
1363
+ __decorateParam(0, Inject(CACHE_SERVICE)),
1364
+ __decorateParam(1, Inject(CACHE_PLUGIN_OPTIONS))
1365
+ ], WarmupService);
1366
+ var Serializer = class {
1367
+ /**
1368
+ * Serializes value to string.
1369
+ *
1370
+ * @param value - Value to serialize
1371
+ * @returns Serialized string
1372
+ * @throws SerializationError if serialization fails
1373
+ */
1374
+ serialize(value) {
1375
+ try {
1376
+ return JSON.stringify(value);
1377
+ } catch (error) {
1378
+ throw new SerializationError(`Failed to serialize value: ${error.message}`, error);
1379
+ }
1380
+ }
1381
+ /**
1382
+ * Deserializes string to value.
1383
+ *
1384
+ * @param serialized - Serialized string
1385
+ * @returns Deserialized value
1386
+ * @throws SerializationError if deserialization fails
1387
+ */
1388
+ deserialize(serialized) {
1389
+ try {
1390
+ return JSON.parse(serialized);
1391
+ } catch (error) {
1392
+ throw new SerializationError(`Failed to deserialize value: ${error.message}`, error);
1393
+ }
1394
+ }
1395
+ /**
1396
+ * Safely tries to deserialize, returns null on error.
1397
+ *
1398
+ * @param serialized - Serialized string
1399
+ * @returns Deserialized value or null
1400
+ */
1401
+ tryDeserialize(serialized) {
1402
+ try {
1403
+ return this.deserialize(serialized);
1404
+ } catch {
1405
+ return null;
1406
+ }
1407
+ }
1408
+ };
1409
+ Serializer = __decorateClass([
1410
+ Injectable()
1411
+ ], Serializer);
1412
+ var L1MemoryStoreAdapter = class {
1413
+ constructor(options) {
1414
+ this.options = options;
1415
+ this.maxSize = options.l1?.maxSize ?? 1e3;
1416
+ this.defaultTtl = (options.l1?.ttl ?? 60) * 1e3;
1417
+ this.evictionPolicy = options.l1?.evictionPolicy ?? "lru";
1418
+ }
1419
+ cache = /* @__PURE__ */ new Map();
1420
+ head = null;
1421
+ tail = null;
1422
+ maxSize;
1423
+ defaultTtl;
1424
+ evictionPolicy;
1425
+ hits = 0;
1426
+ misses = 0;
1427
+ // eslint-disable-next-line @typescript-eslint/require-await
1428
+ async get(key) {
1429
+ const node = this.cache.get(key);
1430
+ if (!node) {
1431
+ this.misses++;
1432
+ return null;
1433
+ }
1434
+ if (Date.now() > node.expiresAt) {
1435
+ void this.delete(key);
1436
+ this.misses++;
1437
+ return null;
1438
+ }
1439
+ if (this.evictionPolicy === "lru") {
1440
+ this.moveToFront(node);
1441
+ } else {
1442
+ node.frequency++;
1443
+ }
1444
+ this.hits++;
1445
+ return node.entry;
1446
+ }
1447
+ // eslint-disable-next-line @typescript-eslint/require-await
1448
+ async set(key, entry, ttl) {
1449
+ const existingNode = this.cache.get(key);
1450
+ if (existingNode) {
1451
+ existingNode.entry = entry;
1452
+ existingNode.expiresAt = Date.now() + (ttl ?? this.defaultTtl);
1453
+ if (this.evictionPolicy === "lru") {
1454
+ this.moveToFront(existingNode);
1455
+ } else {
1456
+ existingNode.frequency++;
1457
+ }
1458
+ } else {
1459
+ if (this.cache.size >= this.maxSize) {
1460
+ this.evict();
1461
+ }
1462
+ const node = {
1463
+ key,
1464
+ entry,
1465
+ prev: null,
1466
+ next: this.head,
1467
+ expiresAt: Date.now() + (ttl ?? this.defaultTtl),
1468
+ frequency: 1
1469
+ };
1470
+ if (this.head) {
1471
+ this.head.prev = node;
1472
+ }
1473
+ this.head = node;
1474
+ if (!this.tail) {
1475
+ this.tail = node;
1476
+ }
1477
+ this.cache.set(key, node);
1478
+ }
1479
+ }
1480
+ // eslint-disable-next-line @typescript-eslint/require-await
1481
+ async delete(key) {
1482
+ const node = this.cache.get(key);
1483
+ if (!node) {
1484
+ return false;
1485
+ }
1486
+ this.removeNode(node);
1487
+ this.cache.delete(key);
1488
+ return true;
1489
+ }
1490
+ // eslint-disable-next-line @typescript-eslint/require-await
1491
+ async clear() {
1492
+ this.cache.clear();
1493
+ this.head = null;
1494
+ this.tail = null;
1495
+ }
1496
+ // eslint-disable-next-line @typescript-eslint/require-await
1497
+ async has(key) {
1498
+ const node = this.cache.get(key);
1499
+ if (!node) {
1500
+ return false;
1501
+ }
1502
+ if (Date.now() > node.expiresAt) {
1503
+ void this.delete(key);
1504
+ return false;
1505
+ }
1506
+ return true;
1507
+ }
1508
+ // eslint-disable-next-line @typescript-eslint/require-await
1509
+ async size() {
1510
+ const now = Date.now();
1511
+ for (const [key, node] of this.cache.entries()) {
1512
+ if (now > node.expiresAt) {
1513
+ void this.delete(key);
1514
+ }
1515
+ }
1516
+ return this.cache.size;
1517
+ }
1518
+ moveToFront(node) {
1519
+ if (node === this.head) {
1520
+ return;
1521
+ }
1522
+ this.removeNode(node);
1523
+ node.prev = null;
1524
+ node.next = this.head;
1525
+ if (this.head) {
1526
+ this.head.prev = node;
1527
+ }
1528
+ this.head = node;
1529
+ if (!this.tail) {
1530
+ this.tail = node;
1531
+ }
1532
+ }
1533
+ removeNode(node) {
1534
+ if (node.prev) {
1535
+ node.prev.next = node.next;
1536
+ } else {
1537
+ this.head = node.next;
1538
+ }
1539
+ if (node.next) {
1540
+ node.next.prev = node.prev;
1541
+ } else {
1542
+ this.tail = node.prev;
1543
+ }
1544
+ }
1545
+ evict() {
1546
+ if (this.evictionPolicy === "lfu") {
1547
+ this.evictLFU();
1548
+ } else {
1549
+ this.evictLRU();
1550
+ }
1551
+ }
1552
+ evictLRU() {
1553
+ if (!this.tail) {
1554
+ return;
1555
+ }
1556
+ const key = this.tail.key;
1557
+ this.removeNode(this.tail);
1558
+ this.cache.delete(key);
1559
+ }
1560
+ evictLFU() {
1561
+ if (this.cache.size === 0) {
1562
+ return;
1563
+ }
1564
+ let victim = null;
1565
+ let current = this.tail;
1566
+ while (current) {
1567
+ if (!victim || current.frequency < victim.frequency) {
1568
+ victim = current;
1569
+ }
1570
+ current = current.prev;
1571
+ }
1572
+ if (victim) {
1573
+ this.removeNode(victim);
1574
+ this.cache.delete(victim.key);
1575
+ }
1576
+ }
1577
+ getStats() {
1578
+ return {
1579
+ hits: this.hits,
1580
+ misses: this.misses,
1581
+ size: this.cache.size
1582
+ };
1583
+ }
1584
+ };
1585
+ L1MemoryStoreAdapter = __decorateClass([
1586
+ Injectable(),
1587
+ __decorateParam(0, Inject(CACHE_PLUGIN_OPTIONS))
1588
+ ], L1MemoryStoreAdapter);
1589
+ var DEFAULT_BATCH_SIZE = 100;
1590
+ var L2RedisStoreAdapter = class {
1591
+ constructor(driver, options, serializer) {
1592
+ this.driver = driver;
1593
+ this.options = options;
1594
+ this.serializer = serializer;
1595
+ this.keyPrefix = options.l2?.keyPrefix ?? "cache:";
1596
+ this.defaultTtl = options.l2?.defaultTtl ?? 3600;
1597
+ }
1598
+ keyPrefix;
1599
+ defaultTtl;
1600
+ hits = 0;
1601
+ misses = 0;
1602
+ async get(key) {
1603
+ try {
1604
+ const fullKey = this.buildKey(key);
1605
+ const value = await this.driver.get(fullKey);
1606
+ if (!value) {
1607
+ this.misses++;
1608
+ return null;
1609
+ }
1610
+ this.hits++;
1611
+ return this.serializer.deserialize(value);
1612
+ } catch {
1613
+ this.misses++;
1614
+ return null;
1615
+ }
1616
+ }
1617
+ async set(key, entry, ttl) {
1618
+ try {
1619
+ const fullKey = this.buildKey(key);
1620
+ const serialized = this.serializer.serialize(entry);
1621
+ const ttlSeconds = ttl ?? this.defaultTtl;
1622
+ await this.driver.setex(fullKey, ttlSeconds, serialized);
1623
+ } catch (error) {
1624
+ throw new CacheError(`Failed to set cache entry for key "${key}": ${error.message}`, ErrorCode.CACHE_SET_FAILED, error);
1625
+ }
1626
+ }
1627
+ async delete(key) {
1628
+ try {
1629
+ const fullKey = this.buildKey(key);
1630
+ const result = await this.driver.del(fullKey);
1631
+ return result > 0;
1632
+ } catch (error) {
1633
+ throw new CacheError(`Failed to delete cache entry for key "${key}": ${error.message}`, ErrorCode.CACHE_DELETE_FAILED, error);
1634
+ }
1635
+ }
1636
+ async clear() {
1637
+ try {
1638
+ const pattern = `${this.keyPrefix}*`;
1639
+ const keys = await this.scanKeys(pattern);
1640
+ if (keys.length === 0) {
1641
+ return;
1642
+ }
1643
+ const batchSize = DEFAULT_BATCH_SIZE;
1644
+ for (let i = 0; i < keys.length; i += batchSize) {
1645
+ const batch = keys.slice(i, i + batchSize);
1646
+ await Promise.all(batch.map((key) => this.driver.del(key)));
1647
+ }
1648
+ } catch (error) {
1649
+ throw new CacheError(`Failed to clear cache: ${error.message}`, ErrorCode.CACHE_CLEAR_FAILED, error);
1650
+ }
1651
+ }
1652
+ async has(key) {
1653
+ try {
1654
+ const fullKey = this.buildKey(key);
1655
+ const exists = await this.driver.exists(fullKey);
1656
+ return exists > 0;
1657
+ } catch {
1658
+ return false;
1659
+ }
1660
+ }
1661
+ async ttl(key) {
1662
+ try {
1663
+ const fullKey = this.buildKey(key);
1664
+ const ttl = await this.driver.ttl(fullKey);
1665
+ return ttl;
1666
+ } catch {
1667
+ return -1;
1668
+ }
1669
+ }
1670
+ async expire(key, ttl) {
1671
+ try {
1672
+ const fullKey = this.buildKey(key);
1673
+ const result = await this.driver.expire(fullKey, ttl);
1674
+ return result === 1;
1675
+ } catch (error) {
1676
+ throw new CacheError(`Failed to set expiration for key "${key}": ${error.message}`, ErrorCode.CACHE_OPERATION_FAILED, error);
1677
+ }
1678
+ }
1679
+ async scan(pattern, count = DEFAULT_BATCH_SIZE) {
1680
+ try {
1681
+ const fullPattern = `${this.keyPrefix}${pattern}`;
1682
+ const keys = await this.scanKeys(fullPattern, count);
1683
+ const strippedKeys = keys.map((key) => key.startsWith(this.keyPrefix) ? key.slice(this.keyPrefix.length) : key);
1684
+ return {
1685
+ keys: strippedKeys,
1686
+ cursor: "0"
1687
+ // Simplified: full scan completed
1688
+ };
1689
+ } catch (error) {
1690
+ throw new CacheError(`Failed to scan keys with pattern "${pattern}": ${error.message}`, ErrorCode.CACHE_OPERATION_FAILED, error);
1691
+ }
1692
+ }
1693
+ async getMany(keys) {
1694
+ try {
1695
+ if (keys.length === 0) {
1696
+ return [];
1697
+ }
1698
+ const fullKeys = keys.map((key) => this.buildKey(key));
1699
+ const values = await this.driver.mget(...fullKeys);
1700
+ return values.map((value) => {
1701
+ if (!value) {
1702
+ this.misses++;
1703
+ return null;
1704
+ }
1705
+ this.hits++;
1706
+ return this.serializer.tryDeserialize(value);
1707
+ });
1708
+ } catch {
1709
+ return keys.map(() => null);
1710
+ }
1711
+ }
1712
+ async setMany(entries) {
1713
+ try {
1714
+ if (entries.length === 0) {
1715
+ return;
1716
+ }
1717
+ await Promise.all(
1718
+ entries.map(async ({ key, entry, ttl }) => {
1719
+ const fullKey = this.buildKey(key);
1720
+ const serialized = this.serializer.serialize(entry);
1721
+ const ttlSeconds = ttl ?? this.defaultTtl;
1722
+ await this.driver.setex(fullKey, ttlSeconds, serialized);
1723
+ })
1724
+ );
1725
+ } catch (error) {
1726
+ throw new CacheError(`Failed to set multiple cache entries: ${error.message}`, ErrorCode.CACHE_SET_FAILED, error);
1727
+ }
1728
+ }
1729
+ buildKey(key) {
1730
+ return `${this.keyPrefix}${key}`;
1731
+ }
1732
+ async scanKeys(pattern, count = DEFAULT_BATCH_SIZE) {
1733
+ const keys = [];
1734
+ let cursor = 0;
1735
+ do {
1736
+ const result = await this.driver.scan(cursor, {
1737
+ match: pattern,
1738
+ count
1739
+ });
1740
+ cursor = parseInt(result[0], 10);
1741
+ keys.push(...result[1]);
1742
+ } while (cursor !== 0);
1743
+ return keys;
1744
+ }
1745
+ // eslint-disable-next-line @typescript-eslint/require-await
1746
+ async getStats() {
1747
+ return {
1748
+ hits: this.hits,
1749
+ misses: this.misses
1750
+ };
1751
+ }
1752
+ async getSwr(key) {
1753
+ try {
1754
+ const fullKey = this.buildKey(key);
1755
+ const value = await this.driver.get(fullKey);
1756
+ if (!value) {
1757
+ this.misses++;
1758
+ return null;
1759
+ }
1760
+ this.hits++;
1761
+ return this.serializer.deserialize(value);
1762
+ } catch {
1763
+ this.misses++;
1764
+ return null;
1765
+ }
1766
+ }
1767
+ async setSwr(key, swrEntry) {
1768
+ try {
1769
+ const fullKey = this.buildKey(key);
1770
+ const serialized = this.serializer.serialize(swrEntry);
1771
+ const now = Date.now();
1772
+ const ttlMs = swrEntry.expiresAt - now;
1773
+ if (ttlMs <= 0) {
1774
+ return;
1775
+ }
1776
+ const ttlSeconds = Math.max(1, Math.ceil(ttlMs / 1e3));
1777
+ await this.driver.setex(fullKey, ttlSeconds, serialized);
1778
+ } catch (error) {
1779
+ throw new CacheError(`Failed to set SWR entry for key "${key}": ${error.message}`, ErrorCode.CACHE_SET_FAILED, error);
1780
+ }
1781
+ }
1782
+ };
1783
+ L2RedisStoreAdapter = __decorateClass([
1784
+ Injectable(),
1785
+ __decorateParam(0, Inject(REDIS_DRIVER)),
1786
+ __decorateParam(1, Inject(CACHE_PLUGIN_OPTIONS)),
1787
+ __decorateParam(2, Inject(SERIALIZER))
1788
+ ], L2RedisStoreAdapter);
1789
+ var CacheService2 = class {
1790
+ constructor(internalCache) {
1791
+ this.internalCache = internalCache;
1792
+ }
1793
+ /**
1794
+ * Gets value from cache.
1795
+ *
1796
+ * @param key - Cache key
1797
+ * @returns Cached value or null if not found
1798
+ *
1799
+ * @example
1800
+ * ```typescript
1801
+ * const user = await cacheService.get<User>('user:123');
1802
+ * ```
1803
+ */
1804
+ async get(key) {
1805
+ return this.internalCache.get(key);
1806
+ }
1807
+ /**
1808
+ * Sets value in cache with optional TTL and tags.
1809
+ *
1810
+ * @param key - Cache key
1811
+ * @param value - Value to cache
1812
+ * @param options - Cache options (ttl, tags, strategy)
1813
+ *
1814
+ * @example
1815
+ * ```typescript
1816
+ * await cacheService.set('user:123', user, {
1817
+ * ttl: 3600,
1818
+ * tags: ['users', 'user:123']
1819
+ * });
1820
+ * ```
1821
+ */
1822
+ async set(key, value, options) {
1823
+ return this.internalCache.set(key, value, options);
1824
+ }
1825
+ /**
1826
+ * Deletes key from cache.
1827
+ *
1828
+ * @param key - Cache key
1829
+ * @returns True if key was deleted, false if not found
1830
+ *
1831
+ * @example
1832
+ * ```typescript
1833
+ * const deleted = await cacheService.del('user:123');
1834
+ * ```
1835
+ */
1836
+ async del(key) {
1837
+ return this.internalCache.delete(key);
1838
+ }
1839
+ /**
1840
+ * Gets multiple values from cache.
1841
+ *
1842
+ * @param keys - Array of cache keys
1843
+ * @returns Array of values (null for missing keys)
1844
+ *
1845
+ * @example
1846
+ * ```typescript
1847
+ * const users = await cacheService.getMany<User>(['user:1', 'user:2', 'user:3']);
1848
+ * ```
1849
+ */
1850
+ async getMany(keys) {
1851
+ return this.internalCache.getMany(keys);
1852
+ }
1853
+ /**
1854
+ * Sets multiple values in cache.
1855
+ *
1856
+ * @param entries - Array of key-value-ttl tuples
1857
+ *
1858
+ * @example
1859
+ * ```typescript
1860
+ * await cacheService.setMany([
1861
+ * { key: 'user:1', value: user1, ttl: 3600 },
1862
+ * { key: 'user:2', value: user2, ttl: 3600 }
1863
+ * ]);
1864
+ * ```
1865
+ */
1866
+ async setMany(entries) {
1867
+ return this.internalCache.setMany(entries);
1868
+ }
1869
+ /**
1870
+ * Gets value from cache or loads it using the provided loader function.
1871
+ * Implements cache-aside pattern with anti-stampede protection.
1872
+ *
1873
+ * @param key - Cache key
1874
+ * @param loader - Function to load value if not cached
1875
+ * @param options - Cache options
1876
+ * @returns Cached or loaded value
1877
+ *
1878
+ * @example
1879
+ * ```typescript
1880
+ * const user = await cacheService.getOrSet(
1881
+ * 'user:123',
1882
+ * async () => {
1883
+ * return await userRepository.findById('123');
1884
+ * },
1885
+ * { ttl: 3600, tags: ['users'] }
1886
+ * );
1887
+ * ```
1888
+ */
1889
+ async getOrSet(key, loader, options) {
1890
+ return this.internalCache.getOrSet(key, loader, options);
1891
+ }
1892
+ /**
1893
+ * Wraps a function with caching logic.
1894
+ * Helper for creating cached functions.
1895
+ *
1896
+ * @param fn - Function to wrap
1897
+ * @param options - Cache options or key builder function
1898
+ * @returns Wrapped function with caching
1899
+ *
1900
+ * @example
1901
+ * ```typescript
1902
+ * const getCachedUser = cacheService.wrap(
1903
+ * async (id: string) => userRepository.findById(id),
1904
+ * {
1905
+ * key: (id: string) => `user:${id}`,
1906
+ * ttl: 3600,
1907
+ * tags: (id: string) => [`user:${id}`, 'users']
1908
+ * }
1909
+ * );
1910
+ *
1911
+ * const user = await getCachedUser('123');
1912
+ * ```
1913
+ */
1914
+ wrap(fn, options) {
1915
+ return async (...args) => {
1916
+ const key = options.key(...args);
1917
+ const tags = typeof options.tags === "function" ? options.tags(...args) : options.tags;
1918
+ return this.getOrSet(key, () => fn(...args), {
1919
+ ttl: options.ttl,
1920
+ tags
1921
+ });
1922
+ };
1923
+ }
1924
+ /**
1925
+ * Deletes multiple keys from cache.
1926
+ *
1927
+ * @param keys - Array of cache keys
1928
+ * @returns Number of keys deleted
1929
+ *
1930
+ * @example
1931
+ * ```typescript
1932
+ * const count = await cacheService.deleteMany(['user:1', 'user:2', 'user:3']);
1933
+ * console.log(`Deleted ${count} keys`);
1934
+ * ```
1935
+ */
1936
+ async deleteMany(keys) {
1937
+ return this.internalCache.deleteMany(keys);
1938
+ }
1939
+ /**
1940
+ * Gets all cache keys associated with a tag.
1941
+ *
1942
+ * @param tag - Tag name
1943
+ * @returns Array of cache keys
1944
+ *
1945
+ * @example
1946
+ * ```typescript
1947
+ * const keys = await cacheService.getKeysByTag('users');
1948
+ * console.log(`Found ${keys.length} cached user keys`);
1949
+ * ```
1950
+ */
1951
+ async getKeysByTag(tag) {
1952
+ return this.internalCache.getKeysByTag(tag);
1953
+ }
1954
+ /**
1955
+ * Invalidates cache by tag.
1956
+ *
1957
+ * @param tag - Tag to invalidate
1958
+ * @returns Number of keys invalidated
1959
+ *
1960
+ * @example
1961
+ * ```typescript
1962
+ * // Invalidate all user caches
1963
+ * const count = await cacheService.invalidate('users');
1964
+ * console.log(`Invalidated ${count} keys`);
1965
+ * ```
1966
+ */
1967
+ async invalidate(tag) {
1968
+ return this.internalCache.invalidateTag(tag);
1969
+ }
1970
+ /**
1971
+ * Invalidates multiple tags.
1972
+ *
1973
+ * @param tags - Array of tags to invalidate
1974
+ * @returns Total number of keys invalidated
1975
+ *
1976
+ * @example
1977
+ * ```typescript
1978
+ * const count = await cacheService.invalidate(['users', 'products']);
1979
+ * ```
1980
+ */
1981
+ async invalidateTags(tags) {
1982
+ return this.internalCache.invalidateTags(tags);
1983
+ }
1984
+ /**
1985
+ * Invalidates cache keys matching a pattern.
1986
+ * Uses Redis SCAN for safe iteration.
1987
+ *
1988
+ * @param pattern - Redis pattern (supports * and ?)
1989
+ * @returns Number of keys deleted
1990
+ *
1991
+ * @example
1992
+ * ```typescript
1993
+ * // Delete all user-related caches
1994
+ * await cacheService.invalidateByPattern('user:*');
1995
+ *
1996
+ * // Delete specific locale caches
1997
+ * await cacheService.invalidateByPattern('*:en_US');
1998
+ * ```
1999
+ */
2000
+ async invalidateByPattern(pattern) {
2001
+ return this.internalCache.invalidateByPattern(pattern);
2002
+ }
2003
+ /**
2004
+ * Checks if key exists in cache.
2005
+ *
2006
+ * @param key - Cache key
2007
+ * @returns True if key exists
2008
+ */
2009
+ async has(key) {
2010
+ return this.internalCache.has(key);
2011
+ }
2012
+ /**
2013
+ * Gets TTL for a cached key.
2014
+ *
2015
+ * @param key - Cache key
2016
+ * @returns TTL in seconds, -1 if no TTL, -2 if key doesn't exist
2017
+ */
2018
+ async ttl(key) {
2019
+ return this.internalCache.ttl(key);
2020
+ }
2021
+ /**
2022
+ * Clears all cache entries.
2023
+ * Use with caution in production.
2024
+ *
2025
+ * @example
2026
+ * ```typescript
2027
+ * await cacheService.clear();
2028
+ * ```
2029
+ */
2030
+ async clear() {
2031
+ return this.internalCache.clear();
2032
+ }
2033
+ /**
2034
+ * Gets cache statistics.
2035
+ *
2036
+ * @returns Cache stats (hits, misses, size)
2037
+ *
2038
+ * @example
2039
+ * ```typescript
2040
+ * const stats = await cacheService.getStats();
2041
+ * console.log(`L1 Hit Rate: ${stats.l1.hits / (stats.l1.hits + stats.l1.misses) * 100}%`);
2042
+ * ```
2043
+ */
2044
+ async getStats() {
2045
+ return this.internalCache.getStats();
2046
+ }
2047
+ };
2048
+ CacheService2 = __decorateClass([
2049
+ Injectable(),
2050
+ __decorateParam(0, Inject(CACHE_SERVICE))
2051
+ ], CacheService2);
2052
+ var EventInvalidationService = class {
2053
+ constructor(registry, cacheService, driver, config) {
2054
+ this.registry = registry;
2055
+ this.cacheService = cacheService;
2056
+ this.driver = driver;
2057
+ this.config = config;
2058
+ }
2059
+ logger = new Logger(EventInvalidationService.name);
2060
+ handlers = /* @__PURE__ */ new Set();
2061
+ eventEmitter = new EventEmitter();
2062
+ // eslint-disable-next-line @typescript-eslint/require-await
2063
+ async onModuleInit() {
2064
+ const source = this.config.invalidation?.source ?? "internal";
2065
+ if (source === "internal") {
2066
+ this.setupInternalSource();
2067
+ this.logger.log("Event invalidation initialized with internal source");
2068
+ }
2069
+ }
2070
+ async processEvent(event, payload) {
2071
+ const startTime = Date.now();
2072
+ try {
2073
+ const eventId = this.generateEventId(event, payload);
2074
+ const isDuplicate = await this.checkDuplicate(eventId);
2075
+ if (isDuplicate) {
2076
+ this.logger.debug(`Skipping duplicate event "${event}"`);
2077
+ return {
2078
+ event,
2079
+ tagsInvalidated: [],
2080
+ keysInvalidated: [],
2081
+ totalKeysDeleted: 0,
2082
+ duration: Date.now() - startTime,
2083
+ skipped: true,
2084
+ skipReason: "duplicate"
2085
+ };
2086
+ }
2087
+ const resolved = this.registry.resolve(event, payload);
2088
+ if (resolved.tags.length === 0 && resolved.keys.length === 0) {
2089
+ this.logger.debug(`No matching rules for event "${event}"`);
2090
+ return {
2091
+ event,
2092
+ tagsInvalidated: [],
2093
+ keysInvalidated: [],
2094
+ totalKeysDeleted: 0,
2095
+ duration: Date.now() - startTime,
2096
+ skipped: true,
2097
+ skipReason: "no_matching_rules"
2098
+ };
2099
+ }
2100
+ let totalDeleted = 0;
2101
+ if (resolved.tags.length > 0) {
2102
+ this.logger.debug(`Invalidating tags: ${resolved.tags.join(", ")} for event "${event}"`);
2103
+ totalDeleted += await this.cacheService.invalidateTags(resolved.tags);
2104
+ }
2105
+ if (resolved.keys.length > 0) {
2106
+ this.logger.debug(`Invalidating keys: ${resolved.keys.join(", ")} for event "${event}"`);
2107
+ totalDeleted += await this.cacheService.deleteMany(resolved.keys);
2108
+ }
2109
+ await this.markProcessed(eventId);
2110
+ const result = {
2111
+ event,
2112
+ tagsInvalidated: resolved.tags,
2113
+ keysInvalidated: resolved.keys,
2114
+ totalKeysDeleted: totalDeleted,
2115
+ duration: Date.now() - startTime,
2116
+ skipped: false
2117
+ };
2118
+ this.logger.log(`Processed event "${event}": invalidated ${resolved.tags.length} tags, ${resolved.keys.length} keys, deleted ${totalDeleted} cache entries (${result.duration}ms)`);
2119
+ await this.notifyHandlers(event, payload, result);
2120
+ return result;
2121
+ } catch (error) {
2122
+ this.logger.error(`Failed to process event "${event}":`, error);
2123
+ throw error;
2124
+ }
2125
+ }
2126
+ // eslint-disable-next-line @typescript-eslint/require-await
2127
+ async emit(event, payload) {
2128
+ this.eventEmitter.emit("invalidation", { event, payload });
2129
+ }
2130
+ subscribe(handler) {
2131
+ this.handlers.add(handler);
2132
+ return () => this.handlers.delete(handler);
2133
+ }
2134
+ setupInternalSource() {
2135
+ this.eventEmitter.on("invalidation", async ({ event, payload }) => {
2136
+ try {
2137
+ await this.processEvent(event, payload);
2138
+ } catch (error) {
2139
+ this.logger.error(`Internal event processing failed for "${event}":`, error);
2140
+ }
2141
+ });
2142
+ }
2143
+ async checkDuplicate(eventId) {
2144
+ try {
2145
+ const key = `_invalidation:processed:${eventId}`;
2146
+ const exists = await this.driver.exists(key);
2147
+ return exists > 0;
2148
+ } catch (error) {
2149
+ this.logger.warn("Deduplication check failed:", error);
2150
+ return false;
2151
+ }
2152
+ }
2153
+ async markProcessed(eventId) {
2154
+ try {
2155
+ const key = `_invalidation:processed:${eventId}`;
2156
+ const ttl = this.config.invalidation?.deduplicationTtl ?? 60;
2157
+ await this.driver.setex(key, ttl, "1");
2158
+ } catch (error) {
2159
+ this.logger.warn("Failed to mark event as processed:", error);
2160
+ }
2161
+ }
2162
+ generateEventId(event, payload) {
2163
+ const hash = createHash("sha256").update(event).update(JSON.stringify(payload ?? {})).digest("hex").slice(0, 16);
2164
+ return hash;
2165
+ }
2166
+ async notifyHandlers(event, payload, result) {
2167
+ for (const handler of this.handlers) {
2168
+ try {
2169
+ await handler(event, payload, result);
2170
+ } catch (error) {
2171
+ this.logger.error("Invalidation handler error:", error);
2172
+ }
2173
+ }
2174
+ }
2175
+ };
2176
+ EventInvalidationService = __decorateClass([
2177
+ Injectable(),
2178
+ __decorateParam(0, Inject(INVALIDATION_REGISTRY)),
2179
+ __decorateParam(1, Inject(CACHE_SERVICE)),
2180
+ __decorateParam(2, Inject(REDIS_DRIVER)),
2181
+ __decorateParam(3, Inject(CACHE_PLUGIN_OPTIONS))
2182
+ ], EventInvalidationService);
2183
+ var InvalidationRegistryService = class {
2184
+ logger = new Logger(InvalidationRegistryService.name);
2185
+ rules = [];
2186
+ register(rule) {
2187
+ this.rules.push(rule);
2188
+ this.rules.sort((a, b) => b.getPriority() - a.getPriority());
2189
+ this.logger.debug(`Registered invalidation rule for event "${rule.getEventPattern()}" with priority ${rule.getPriority()}`);
2190
+ }
2191
+ registerMany(rules) {
2192
+ for (const rule of rules) {
2193
+ this.register(rule);
2194
+ }
2195
+ }
2196
+ unregister(event) {
2197
+ const initialLength = this.rules.length;
2198
+ this.rules = this.rules.filter((r) => r.getEventPattern() !== event);
2199
+ const removed = initialLength - this.rules.length;
2200
+ if (removed > 0) {
2201
+ this.logger.debug(`Unregistered ${removed} rule(s) for event "${event}"`);
2202
+ }
2203
+ }
2204
+ findRules(event) {
2205
+ return this.rules.filter((rule) => rule.matches(event));
2206
+ }
2207
+ resolve(event, payload) {
2208
+ const matchedRules = this.findRules(event);
2209
+ const tagsSet = /* @__PURE__ */ new Set();
2210
+ const keysSet = /* @__PURE__ */ new Set();
2211
+ const applicableRules = [];
2212
+ for (const rule of matchedRules) {
2213
+ if (!rule.testCondition(payload)) {
2214
+ this.logger.debug(`Rule for event "${rule.getEventPattern()}" skipped - condition not met`);
2215
+ continue;
2216
+ }
2217
+ applicableRules.push(rule);
2218
+ if (rule.hasTags()) {
2219
+ const tags = rule.resolveTags(payload);
2220
+ for (const tag of tags) {
2221
+ if (!tag.includes("{")) {
2222
+ tagsSet.add(tag);
2223
+ } else {
2224
+ this.logger.warn(`Tag template "${tag}" has unresolved placeholders for event "${event}"`);
2225
+ }
2226
+ }
2227
+ }
2228
+ if (rule.hasKeys()) {
2229
+ const keys = rule.resolveKeys(payload);
2230
+ for (const key of keys) {
2231
+ if (!key.includes("{")) {
2232
+ keysSet.add(key);
2233
+ } else {
2234
+ this.logger.warn(`Key template "${key}" has unresolved placeholders for event "${event}"`);
2235
+ }
2236
+ }
2237
+ }
2238
+ }
2239
+ return {
2240
+ tags: Array.from(tagsSet),
2241
+ keys: Array.from(keysSet),
2242
+ matchedRules: applicableRules
2243
+ };
2244
+ }
2245
+ getRules() {
2246
+ return [...this.rules];
2247
+ }
2248
+ };
2249
+ InvalidationRegistryService = __decorateClass([
2250
+ Injectable()
2251
+ ], InvalidationRegistryService);
2252
+ var EventPattern = class _EventPattern {
2253
+ pattern;
2254
+ regex;
2255
+ constructor(pattern, regex) {
2256
+ this.pattern = pattern;
2257
+ this.regex = regex;
2258
+ }
2259
+ /**
2260
+ * Creates EventPattern from string pattern.
2261
+ * @param pattern - AMQP-style pattern (e.g., 'user.*', 'order.#', 'product.updated')
2262
+ */
2263
+ static create(pattern) {
2264
+ if (!pattern || pattern.trim().length === 0) {
2265
+ throw new CacheError("Event pattern cannot be empty", ErrorCode.VALIDATION_FAILED);
2266
+ }
2267
+ const normalized = pattern.trim();
2268
+ if (!/^[a-z0-9*#._-]+$/i.test(normalized)) {
2269
+ throw new CacheError(`Invalid event pattern "${normalized}". Only alphanumeric, dots, dashes, underscores, *, and # are allowed`, ErrorCode.VALIDATION_FAILED);
2270
+ }
2271
+ let regexStr = normalized.replace(/\./g, "\\.").replace(/\*/g, "[^.]+").replace(/#/g, ".*");
2272
+ regexStr = regexStr.replace(/\\\.\.\*$/, "(?:\\..*)?");
2273
+ const regex = new RegExp(`^${regexStr}$`);
2274
+ return new _EventPattern(normalized, regex);
2275
+ }
2276
+ /**
2277
+ * Tests if this pattern matches the given event.
2278
+ */
2279
+ matches(event) {
2280
+ return this.regex.test(event);
2281
+ }
2282
+ /**
2283
+ * Returns the raw pattern string.
2284
+ */
2285
+ toString() {
2286
+ return this.pattern;
2287
+ }
2288
+ /**
2289
+ * Checks equality with another pattern.
2290
+ */
2291
+ equals(other) {
2292
+ return this.pattern === other.pattern;
2293
+ }
2294
+ };
2295
+ var TagTemplate = class _TagTemplate {
2296
+ template;
2297
+ placeholders;
2298
+ constructor(template, placeholders) {
2299
+ this.template = template;
2300
+ this.placeholders = placeholders;
2301
+ }
2302
+ /**
2303
+ * Creates TagTemplate from template string.
2304
+ * @param template - Template string with placeholders (e.g., 'user:{userId}')
2305
+ */
2306
+ static create(template) {
2307
+ if (!template || template.trim().length === 0) {
2308
+ throw new CacheError("Tag template cannot be empty", ErrorCode.VALIDATION_FAILED);
2309
+ }
2310
+ const normalized = template.trim();
2311
+ const placeholders = [];
2312
+ const placeholderRegex = /\{([^}]+)\}/g;
2313
+ let match;
2314
+ while ((match = placeholderRegex.exec(normalized)) !== null) {
2315
+ placeholders.push(match[1]);
2316
+ }
2317
+ return new _TagTemplate(normalized, placeholders);
2318
+ }
2319
+ /**
2320
+ * Resolves template with given payload.
2321
+ * @param payload - Data object to resolve placeholders from
2322
+ * @returns Resolved tag string
2323
+ */
2324
+ resolve(payload) {
2325
+ return this.template.replace(/\{([^}]+)\}/g, (_, path) => {
2326
+ const value = this.getNestedValue(payload, path);
2327
+ if (value === void 0 || value === null) {
2328
+ return `{${path}}`;
2329
+ }
2330
+ return String(value);
2331
+ });
2332
+ }
2333
+ /**
2334
+ * Returns the raw template string.
2335
+ */
2336
+ toString() {
2337
+ return this.template;
2338
+ }
2339
+ /**
2340
+ * Returns all placeholders in the template.
2341
+ */
2342
+ getPlaceholders() {
2343
+ return [...this.placeholders];
2344
+ }
2345
+ /**
2346
+ * Checks if template has any placeholders.
2347
+ */
2348
+ hasPlaceholders() {
2349
+ return this.placeholders.length > 0;
2350
+ }
2351
+ getNestedValue(obj, path) {
2352
+ if (obj === null || obj === void 0) {
2353
+ return void 0;
2354
+ }
2355
+ const parts = path.split(".");
2356
+ let current = obj;
2357
+ for (const part of parts) {
2358
+ if (current === null || current === void 0) {
2359
+ return void 0;
2360
+ }
2361
+ if (typeof current !== "object") {
2362
+ return void 0;
2363
+ }
2364
+ current = current[part];
2365
+ }
2366
+ return current;
2367
+ }
2368
+ };
2369
+
2370
+ // src/invalidation/domain/entities/invalidation-rule.entity.ts
2371
+ var InvalidationRule = class _InvalidationRule {
2372
+ eventPattern;
2373
+ tagTemplates;
2374
+ keyTemplates;
2375
+ condition;
2376
+ priority;
2377
+ constructor(eventPattern, tagTemplates, keyTemplates, condition, priority) {
2378
+ this.eventPattern = eventPattern;
2379
+ this.tagTemplates = tagTemplates;
2380
+ this.keyTemplates = keyTemplates;
2381
+ this.condition = condition;
2382
+ this.priority = priority;
2383
+ }
2384
+ /**
2385
+ * Creates InvalidationRule from props.
2386
+ */
2387
+ static create(props) {
2388
+ const eventPattern = EventPattern.create(props.event);
2389
+ const tagTemplates = (props.tags ?? []).map((tag) => TagTemplate.create(tag));
2390
+ const keyTemplates = (props.keys ?? []).map((key) => TagTemplate.create(key));
2391
+ const priority = props.priority ?? 0;
2392
+ return new _InvalidationRule(eventPattern, tagTemplates, keyTemplates, props.condition, priority);
2393
+ }
2394
+ /**
2395
+ * Tests if this rule matches the given event.
2396
+ */
2397
+ matches(event) {
2398
+ return this.eventPattern.matches(event);
2399
+ }
2400
+ /**
2401
+ * Tests if condition passes for the given payload.
2402
+ */
2403
+ testCondition(payload) {
2404
+ if (!this.condition) {
2405
+ return true;
2406
+ }
2407
+ try {
2408
+ return this.condition(payload);
2409
+ } catch {
2410
+ return false;
2411
+ }
2412
+ }
2413
+ /**
2414
+ * Resolves tags for the given payload.
2415
+ */
2416
+ resolveTags(payload) {
2417
+ return this.tagTemplates.map((template) => template.resolve(payload));
2418
+ }
2419
+ /**
2420
+ * Resolves keys for the given payload.
2421
+ */
2422
+ resolveKeys(payload) {
2423
+ return this.keyTemplates.map((template) => template.resolve(payload));
2424
+ }
2425
+ /**
2426
+ * Gets rule priority (higher = processed first).
2427
+ */
2428
+ getPriority() {
2429
+ return this.priority;
2430
+ }
2431
+ /**
2432
+ * Gets the event pattern string.
2433
+ */
2434
+ getEventPattern() {
2435
+ return this.eventPattern.toString();
2436
+ }
2437
+ /**
2438
+ * Checks if rule has any tags.
2439
+ */
2440
+ hasTags() {
2441
+ return this.tagTemplates.length > 0;
2442
+ }
2443
+ /**
2444
+ * Checks if rule has any keys.
2445
+ */
2446
+ hasKeys() {
2447
+ return this.keyTemplates.length > 0;
2448
+ }
2449
+ /**
2450
+ * Checks if rule has a condition.
2451
+ */
2452
+ hasCondition() {
2453
+ return this.condition !== void 0;
2454
+ }
2455
+ };
2456
+ var AMQPEventSourceAdapter = class {
2457
+ constructor(invalidationService, config, amqpConnection) {
2458
+ this.invalidationService = invalidationService;
2459
+ this.config = config;
2460
+ this.amqpConnection = amqpConnection;
2461
+ }
2462
+ logger = new Logger(AMQPEventSourceAdapter.name);
2463
+ amqpConnection;
2464
+ async onModuleInit() {
2465
+ const source = this.config.invalidation?.source;
2466
+ if (source !== "amqp") {
2467
+ return;
2468
+ }
2469
+ if (!this.amqpConnection) {
2470
+ this.logger.warn("AMQP source configured but @golevelup/nestjs-rabbitmq is not available. Install it with: npm install @golevelup/nestjs-rabbitmq");
2471
+ return;
2472
+ }
2473
+ const amqpConfig = this.config.invalidation?.amqp;
2474
+ if (!amqpConfig) {
2475
+ this.logger.warn("AMQP source configured but amqp config is missing");
2476
+ return;
2477
+ }
2478
+ const exchange = amqpConfig.exchange ?? "cache.invalidation";
2479
+ const queue = amqpConfig.queue ?? `${this.getServiceName()}.cache.invalidation`;
2480
+ const routingKeys = amqpConfig.routingKeys ?? ["#"];
2481
+ try {
2482
+ await this.amqpConnection.createSubscriber(
2483
+ async (msg, rawMsg) => {
2484
+ const routingKey = rawMsg.fields.routingKey;
2485
+ this.logger.debug(`Received AMQP invalidation event: ${routingKey}`);
2486
+ await this.invalidationService.processEvent(routingKey, msg.payload);
2487
+ },
2488
+ {
2489
+ exchange,
2490
+ queue,
2491
+ routingKey: routingKeys,
2492
+ queueOptions: {
2493
+ durable: true
2494
+ }
2495
+ }
2496
+ );
2497
+ this.logger.log(`AMQP event source initialized: exchange=${exchange}, queue=${queue}, routingKeys=${routingKeys.join(",")}`);
2498
+ } catch (error) {
2499
+ this.logger.error("Failed to setup AMQP event source:", error);
2500
+ throw error;
2501
+ }
2502
+ }
2503
+ getServiceName() {
2504
+ return process.env.SERVICE_NAME ?? "app";
2505
+ }
2506
+ };
2507
+ AMQPEventSourceAdapter = __decorateClass([
2508
+ Injectable(),
2509
+ __decorateParam(0, Inject(EVENT_INVALIDATION_SERVICE)),
2510
+ __decorateParam(1, Inject(CACHE_PLUGIN_OPTIONS)),
2511
+ __decorateParam(2, Optional()),
2512
+ __decorateParam(2, Inject(AMQP_CONNECTION))
2513
+ ], AMQPEventSourceAdapter);
2514
+ var RELEASE_LOCK_SCRIPT = `
2515
+ if redis.call("get", KEYS[1]) == ARGV[1] then
2516
+ return redis.call("del", KEYS[1])
2517
+ else
2518
+ return 0
2519
+ end
2520
+ `.trim();
2521
+ var LOCK_PREFIX = "_stampede:";
2522
+ var StampedeProtectionService = class {
2523
+ constructor(options, driver) {
2524
+ this.options = options;
2525
+ this.driver = driver;
2526
+ this.lockTimeout = options.stampede?.lockTimeout ?? 5e3;
2527
+ this.waitTimeout = options.stampede?.waitTimeout ?? 1e4;
2528
+ }
2529
+ logger = new Logger(StampedeProtectionService.name);
2530
+ flights = /* @__PURE__ */ new Map();
2531
+ lockTimeout;
2532
+ waitTimeout;
2533
+ prevented = 0;
2534
+ async protect(key, loader) {
2535
+ const existingFlight = this.flights.get(key);
2536
+ if (existingFlight) {
2537
+ existingFlight.waiters++;
2538
+ this.prevented++;
2539
+ const value = await this.waitForFlight(existingFlight, key);
2540
+ return { value, cached: true, waited: true };
2541
+ }
2542
+ let resolveFunc;
2543
+ let rejectFunc;
2544
+ const promise = new Promise((resolve, reject) => {
2545
+ resolveFunc = resolve;
2546
+ rejectFunc = reject;
2547
+ });
2548
+ const flight = {
2549
+ promise,
2550
+ resolve: resolveFunc,
2551
+ reject: rejectFunc,
2552
+ waiters: 0,
2553
+ timestamp: Date.now()
2554
+ };
2555
+ this.flights.set(key, flight);
2556
+ let lock;
2557
+ try {
2558
+ const lockKey = `${LOCK_PREFIX}${key}`;
2559
+ const lockValue = this.generateLockValue();
2560
+ const lockTtlSeconds = Math.ceil(this.lockTimeout / 1e3);
2561
+ const acquired = await this.tryAcquireLock(lockKey, lockValue, lockTtlSeconds);
2562
+ if (acquired) {
2563
+ lock = { lockKey, lockValue };
2564
+ }
2565
+ } catch {
2566
+ }
2567
+ try {
2568
+ const value = await this.executeLoader(loader, key);
2569
+ flight.resolve(value);
2570
+ return { value, cached: false, waited: false };
2571
+ } catch (error) {
2572
+ if (flight.waiters > 0) {
2573
+ flight.reject(error);
2574
+ }
2575
+ throw error;
2576
+ } finally {
2577
+ setTimeout(() => {
2578
+ this.flights.delete(key);
2579
+ }, 100);
2580
+ if (lock) {
2581
+ this.releaseLock(lock.lockKey, lock.lockValue).catch((err) => {
2582
+ this.logger.warn(`Failed to release lock for "${key}": ${err.message}`);
2583
+ });
2584
+ }
2585
+ }
2586
+ }
2587
+ // eslint-disable-next-line @typescript-eslint/require-await
2588
+ async clearKey(key) {
2589
+ const flight = this.flights.get(key);
2590
+ if (flight) {
2591
+ flight.reject(new Error("Flight cancelled"));
2592
+ this.flights.delete(key);
2593
+ }
2594
+ }
2595
+ // eslint-disable-next-line @typescript-eslint/require-await
2596
+ async clearAll() {
2597
+ for (const [, flight] of this.flights.entries()) {
2598
+ flight.reject(new Error("All flights cancelled"));
2599
+ }
2600
+ this.flights.clear();
2601
+ }
2602
+ getStats() {
2603
+ const stats = {
2604
+ activeFlights: this.flights.size,
2605
+ totalWaiters: 0,
2606
+ oldestFlight: 0,
2607
+ prevented: this.prevented
2608
+ };
2609
+ const now = Date.now();
2610
+ let oldestTimestamp = now;
2611
+ for (const flight of this.flights.values()) {
2612
+ stats.totalWaiters += flight.waiters;
2613
+ if (flight.timestamp < oldestTimestamp) {
2614
+ oldestTimestamp = flight.timestamp;
2615
+ }
2616
+ }
2617
+ stats.oldestFlight = stats.activeFlights > 0 ? now - oldestTimestamp : 0;
2618
+ return stats;
2619
+ }
2620
+ async waitForFlight(flight, key) {
2621
+ const age = Date.now() - flight.timestamp;
2622
+ if (age > this.lockTimeout) {
2623
+ throw new StampedeError(key, age);
2624
+ }
2625
+ let timeoutId;
2626
+ let timeoutCancelled = false;
2627
+ const timeoutPromise = new Promise((_, reject) => {
2628
+ timeoutId = setTimeout(() => {
2629
+ if (!timeoutCancelled) {
2630
+ reject(new StampedeError(key, this.waitTimeout));
2631
+ }
2632
+ }, this.waitTimeout);
2633
+ });
2634
+ try {
2635
+ const result = await Promise.race([flight.promise, timeoutPromise]);
2636
+ timeoutCancelled = true;
2637
+ if (timeoutId) clearTimeout(timeoutId);
2638
+ return result;
2639
+ } catch (error) {
2640
+ timeoutCancelled = true;
2641
+ if (timeoutId) clearTimeout(timeoutId);
2642
+ throw error;
2643
+ }
2644
+ }
2645
+ async executeLoader(loader, key) {
2646
+ let timeoutId;
2647
+ let timeoutCancelled = false;
2648
+ const timeoutPromise = new Promise((_, reject) => {
2649
+ timeoutId = setTimeout(() => {
2650
+ if (!timeoutCancelled) {
2651
+ reject(new StampedeError(key, this.lockTimeout));
2652
+ }
2653
+ }, this.lockTimeout);
2654
+ });
2655
+ try {
2656
+ const result = await Promise.race([loader(), timeoutPromise]);
2657
+ timeoutCancelled = true;
2658
+ if (timeoutId) clearTimeout(timeoutId);
2659
+ return result;
2660
+ } catch (error) {
2661
+ timeoutCancelled = true;
2662
+ if (timeoutId) clearTimeout(timeoutId);
2663
+ if (error instanceof StampedeError) {
2664
+ throw error;
2665
+ }
2666
+ throw new LoaderError(key, error);
2667
+ }
2668
+ }
2669
+ async tryAcquireLock(lockKey, lockValue, ttlSeconds) {
2670
+ try {
2671
+ const result = await this.driver.set(lockKey, lockValue, { ex: ttlSeconds, nx: true });
2672
+ return result === "OK";
2673
+ } catch (error) {
2674
+ this.logger.warn(`Failed to acquire distributed lock: ${error.message}`);
2675
+ return false;
2676
+ }
2677
+ }
2678
+ async releaseLock(lockKey, lockValue) {
2679
+ try {
2680
+ await this.driver.eval(RELEASE_LOCK_SCRIPT, [lockKey], [lockValue]);
2681
+ } catch (error) {
2682
+ this.logger.warn(`Failed to release distributed lock: ${error.message}`);
2683
+ }
2684
+ }
2685
+ generateLockValue() {
2686
+ return `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
2687
+ }
2688
+ };
2689
+ StampedeProtectionService = __decorateClass([
2690
+ Injectable(),
2691
+ __decorateParam(0, Inject(CACHE_PLUGIN_OPTIONS)),
2692
+ __decorateParam(1, Inject(REDIS_DRIVER))
2693
+ ], StampedeProtectionService);
2694
+ var SwrManagerService = class {
2695
+ constructor(options) {
2696
+ this.options = options;
2697
+ this.enabled = options.swr?.enabled ?? false;
2698
+ this.staleTtl = options.swr?.defaultStaleTime ?? 60;
2699
+ }
2700
+ logger = new Logger(SwrManagerService.name);
2701
+ jobs = /* @__PURE__ */ new Map();
2702
+ staleTtl;
2703
+ enabled;
2704
+ // eslint-disable-next-line @typescript-eslint/require-await
2705
+ async get(_key) {
2706
+ return null;
2707
+ }
2708
+ async set(_key, _value, _staleTimeSeconds) {
2709
+ }
2710
+ async delete(_key) {
2711
+ }
2712
+ isStale(entry) {
2713
+ if (!this.enabled) {
2714
+ return false;
2715
+ }
2716
+ const now = Date.now();
2717
+ return now > entry.staleAt;
2718
+ }
2719
+ isExpired(entry) {
2720
+ const now = Date.now();
2721
+ return now > entry.expiresAt;
2722
+ }
2723
+ shouldRevalidate(key) {
2724
+ return !this.jobs.has(key);
2725
+ }
2726
+ // eslint-disable-next-line @typescript-eslint/require-await
2727
+ async scheduleRevalidation(key, loader, onSuccess, onError) {
2728
+ if (this.jobs.has(key)) {
2729
+ this.logger.debug(`Revalidation already scheduled for key: ${key}`);
2730
+ return;
2731
+ }
2732
+ const job = {
2733
+ key,
2734
+ loader,
2735
+ onSuccess,
2736
+ onError: onError ?? ((error) => this.logger.error(`Revalidation failed for ${key}:`, error)),
2737
+ timestamp: Date.now()
2738
+ };
2739
+ this.jobs.set(key, job);
2740
+ setImmediate(() => {
2741
+ void this.executeRevalidation(job);
2742
+ });
2743
+ }
2744
+ createSwrEntry(value, freshTtl, staleTtl) {
2745
+ const now = Date.now();
2746
+ const freshTtlMs = freshTtl * 1e3;
2747
+ const staleTtlMs = (staleTtl ?? this.staleTtl) * 1e3;
2748
+ return {
2749
+ value,
2750
+ cachedAt: now,
2751
+ staleAt: now + freshTtlMs,
2752
+ expiresAt: now + freshTtlMs + staleTtlMs
2753
+ };
2754
+ }
2755
+ getStats() {
2756
+ return {
2757
+ activeRevalidations: this.jobs.size,
2758
+ enabled: this.enabled,
2759
+ staleTtl: this.staleTtl
2760
+ };
2761
+ }
2762
+ // eslint-disable-next-line @typescript-eslint/require-await
2763
+ async clearRevalidations() {
2764
+ this.jobs.clear();
2765
+ }
2766
+ async executeRevalidation(job) {
2767
+ try {
2768
+ this.logger.debug(`Starting revalidation for key: ${job.key}`);
2769
+ const value = await job.loader();
2770
+ this.logger.debug(`Revalidation successful for key: ${job.key}`);
2771
+ await job.onSuccess(value);
2772
+ } catch (error) {
2773
+ this.logger.error(`Revalidation failed for key: ${job.key}`, error);
2774
+ job.onError(error);
2775
+ } finally {
2776
+ this.jobs.delete(job.key);
2777
+ }
2778
+ }
2779
+ };
2780
+ SwrManagerService = __decorateClass([
2781
+ Injectable(),
2782
+ __decorateParam(0, Inject(CACHE_PLUGIN_OPTIONS))
2783
+ ], SwrManagerService);
2784
+ var TAG_BATCH_SIZE = 100;
2785
+ var TagIndexRepository = class {
2786
+ constructor(driver, options, luaLoader) {
2787
+ this.driver = driver;
2788
+ this.options = options;
2789
+ this.luaLoader = luaLoader;
2790
+ const l2Prefix = options.l2?.keyPrefix ?? "cache:";
2791
+ const tagIndexPrefix = options.tags?.indexPrefix ?? "_tag:";
2792
+ this.tagPrefix = `${l2Prefix}${tagIndexPrefix}`;
2793
+ }
2794
+ tagPrefix;
2795
+ async addKeyToTags(key, tags) {
2796
+ if (tags.length === 0) {
2797
+ return;
2798
+ }
2799
+ try {
2800
+ const validatedTags = this.validateTags(tags);
2801
+ const tagTtl = this.options.tags?.ttl ?? this.options.l2?.maxTtl ?? 86400;
2802
+ const operations = validatedTags.map(async (tag) => {
2803
+ const tagKey = this.buildTagKey(tag);
2804
+ await this.driver.sadd(tagKey, key);
2805
+ await this.driver.expire(tagKey, tagTtl);
2806
+ });
2807
+ await Promise.all(operations);
2808
+ } catch (error) {
2809
+ throw new TagInvalidationError(tags[0] ?? "unknown", `Failed to add key "${key}" to tags [${tags.join(", ")}]: ${error.message}`, error);
2810
+ }
2811
+ }
2812
+ async removeKeyFromTags(key, tags) {
2813
+ if (tags.length === 0) {
2814
+ return;
2815
+ }
2816
+ try {
2817
+ const validatedTags = this.validateTags(tags);
2818
+ await Promise.all(
2819
+ validatedTags.map(async (tag) => {
2820
+ const tagKey = this.buildTagKey(tag);
2821
+ await this.driver.srem(tagKey, key);
2822
+ })
2823
+ );
2824
+ } catch (error) {
2825
+ throw new TagInvalidationError(tags[0] ?? "unknown", `Failed to remove key "${key}" from tags [${tags.join(", ")}]: ${error.message}`, error);
2826
+ }
2827
+ }
2828
+ async getKeysByTag(tag) {
2829
+ try {
2830
+ const validTag = Tag.create(tag);
2831
+ const tagKey = this.buildTagKey(validTag.toString());
2832
+ const keys = await this.driver.smembers(tagKey);
2833
+ return keys;
2834
+ } catch (error) {
2835
+ throw new TagInvalidationError(tag, `Failed to get keys for tag: ${error.message}`, error);
2836
+ }
2837
+ }
2838
+ async invalidateTag(tag) {
2839
+ try {
2840
+ const validTag = Tag.create(tag);
2841
+ const tagKey = this.buildTagKey(validTag.toString());
2842
+ const cacheKeys = await this.driver.smembers(tagKey);
2843
+ if (cacheKeys.length === 0) {
2844
+ await this.driver.del(tagKey);
2845
+ return 0;
2846
+ }
2847
+ let deletedCount = 0;
2848
+ const batchSize = TAG_BATCH_SIZE;
2849
+ for (let i = 0; i < cacheKeys.length; i += batchSize) {
2850
+ const batch = cacheKeys.slice(i, i + batchSize);
2851
+ const results = await Promise.all(batch.map((key) => this.driver.del(key)));
2852
+ deletedCount += results.reduce((sum, result) => sum + result, 0);
2853
+ }
2854
+ await this.driver.del(tagKey);
2855
+ return deletedCount;
2856
+ } catch (error) {
2857
+ throw new TagInvalidationError(tag, `Failed to invalidate tag: ${error.message}`, error);
2858
+ }
2859
+ }
2860
+ async invalidateTags(tags) {
2861
+ if (tags.length === 0) {
2862
+ return 0;
2863
+ }
2864
+ try {
2865
+ const validatedTags = this.validateTags(tags);
2866
+ let totalInvalidated = 0;
2867
+ for (const tag of validatedTags) {
2868
+ const count = await this.invalidateTag(tag);
2869
+ totalInvalidated += count;
2870
+ }
2871
+ return totalInvalidated;
2872
+ } catch (error) {
2873
+ throw new TagInvalidationError(tags[0] ?? "unknown", `Failed to invalidate tags [${tags.join(", ")}]: ${error.message}`, error);
2874
+ }
2875
+ }
2876
+ async clearAllTags() {
2877
+ try {
2878
+ const pattern = `${this.tagPrefix}*`;
2879
+ const keys = await this.scanKeys(pattern);
2880
+ if (keys.length === 0) {
2881
+ return;
2882
+ }
2883
+ const batchSize = TAG_BATCH_SIZE;
2884
+ for (let i = 0; i < keys.length; i += batchSize) {
2885
+ const batch = keys.slice(i, i + batchSize);
2886
+ await Promise.all(batch.map((key) => this.driver.del(key)));
2887
+ }
2888
+ } catch (error) {
2889
+ throw new TagInvalidationError("_all", `Failed to clear all tags: ${error.message}`, error);
2890
+ }
2891
+ }
2892
+ async getTagStats(tag) {
2893
+ try {
2894
+ const validTag = Tag.create(tag);
2895
+ const tagKey = this.buildTagKey(validTag.toString());
2896
+ const exists = await this.driver.exists(tagKey) > 0;
2897
+ const keyCount = exists ? await this.driver.scard(tagKey) : 0;
2898
+ return { keyCount, exists };
2899
+ } catch {
2900
+ return { keyCount: 0, exists: false };
2901
+ }
2902
+ }
2903
+ /**
2904
+ * Validates tags using Tags value object.
2905
+ *
2906
+ * @param tags - Array of tag strings
2907
+ * @returns Array of validated tag strings
2908
+ * @throws CacheError if validation fails
2909
+ * @private
2910
+ */
2911
+ validateTags(tags) {
2912
+ const maxTags = this.options.tags?.maxTagsPerKey ?? 10;
2913
+ const tagsVo = Tags.create(tags, maxTags);
2914
+ return tagsVo.toStrings();
2915
+ }
2916
+ buildTagKey(tag) {
2917
+ return `${this.tagPrefix}${tag}`;
2918
+ }
2919
+ async scanKeys(pattern) {
2920
+ const keys = [];
2921
+ let cursor = 0;
2922
+ do {
2923
+ const result = await this.driver.scan(cursor, {
2924
+ match: pattern,
2925
+ count: TAG_BATCH_SIZE
2926
+ });
2927
+ cursor = parseInt(result[0], 10);
2928
+ keys.push(...result[1]);
2929
+ } while (cursor !== 0);
2930
+ return keys;
2931
+ }
2932
+ };
2933
+ TagIndexRepository = __decorateClass([
2934
+ Injectable(),
2935
+ __decorateParam(0, Inject(REDIS_DRIVER)),
2936
+ __decorateParam(1, Inject(CACHE_PLUGIN_OPTIONS)),
2937
+ __decorateParam(2, Inject(LUA_SCRIPT_LOADER))
2938
+ ], TagIndexRepository);
2939
+
2940
+ // src/tags/infrastructure/scripts/lua-scripts.ts
2941
+ var ADD_KEY_TO_TAGS_SCRIPT = `
2942
+ local cache_key = ARGV[1]
2943
+ local tag_ttl = tonumber(ARGV[2])
2944
+
2945
+ for i = 1, #KEYS do
2946
+ local tag_key = KEYS[i]
2947
+ redis.call('SADD', tag_key, cache_key)
2948
+ redis.call('EXPIRE', tag_key, tag_ttl)
2949
+ end
2950
+
2951
+ return #KEYS
2952
+ `.trim();
2953
+ var INVALIDATE_TAG_SCRIPT = `
2954
+ local tag_key = KEYS[1]
2955
+
2956
+ -- Get all cache keys for this tag
2957
+ local cache_keys = redis.call('SMEMBERS', tag_key)
2958
+
2959
+ if #cache_keys == 0 then
2960
+ -- Delete empty tag set and return 0
2961
+ redis.call('DEL', tag_key)
2962
+ return 0
2963
+ end
2964
+
2965
+ -- Delete all cache keys
2966
+ local deleted = 0
2967
+ for i = 1, #cache_keys do
2968
+ local result = redis.call('DEL', cache_keys[i])
2969
+ deleted = deleted + result
2970
+ end
2971
+
2972
+ -- Delete the tag set itself
2973
+ redis.call('DEL', tag_key)
2974
+
2975
+ return deleted
2976
+ `.trim();
2977
+
2978
+ // src/tags/infrastructure/services/lua-script-loader.service.ts
2979
+ var SCRIPTS = {
2980
+ "invalidate-tag": INVALIDATE_TAG_SCRIPT,
2981
+ "add-key-to-tags": ADD_KEY_TO_TAGS_SCRIPT
2982
+ };
2983
+ var LuaScriptLoader = class {
2984
+ constructor(driver) {
2985
+ this.driver = driver;
2986
+ }
2987
+ logger = new Logger(LuaScriptLoader.name);
2988
+ scriptShas = /* @__PURE__ */ new Map();
2989
+ async onModuleInit() {
2990
+ await this.loadScripts();
2991
+ }
2992
+ /**
2993
+ * Loads all Lua scripts into Redis.
2994
+ */
2995
+ async loadScripts() {
2996
+ for (const [scriptName, scriptContent] of Object.entries(SCRIPTS)) {
2997
+ try {
2998
+ const sha = await this.driver.scriptLoad(scriptContent);
2999
+ this.scriptShas.set(scriptName, sha);
3000
+ this.logger.debug(`Loaded Lua script: ${scriptName} (SHA: ${sha})`);
3001
+ } catch (error) {
3002
+ this.logger.error(`Failed to load script ${scriptName}:`, error);
3003
+ throw error;
3004
+ }
3005
+ }
3006
+ this.logger.log(`Successfully loaded ${this.scriptShas.size} Lua scripts`);
3007
+ }
3008
+ /**
3009
+ * Executes a Lua script by name using EVALSHA.
3010
+ *
3011
+ * @param scriptName - Name of the script
3012
+ * @param keys - Redis keys to pass to the script
3013
+ * @param args - Arguments to pass to the script
3014
+ * @returns Script execution result
3015
+ */
3016
+ async evalSha(scriptName, keys, args) {
3017
+ const sha = this.scriptShas.get(scriptName);
3018
+ if (!sha) {
3019
+ throw new Error(`Lua script not loaded: ${scriptName}`);
3020
+ }
3021
+ return this.driver.evalsha(sha, keys, args);
3022
+ }
3023
+ /**
3024
+ * Gets the SHA hash of a loaded script.
3025
+ *
3026
+ * @param scriptName - Name of the script
3027
+ * @returns SHA hash or undefined if not loaded
3028
+ */
3029
+ getSha(scriptName) {
3030
+ return this.scriptShas.get(scriptName);
3031
+ }
3032
+ /**
3033
+ * Checks if a script is loaded.
3034
+ *
3035
+ * @param scriptName - Name of the script
3036
+ * @returns True if script is loaded
3037
+ */
3038
+ isLoaded(scriptName) {
3039
+ return this.scriptShas.has(scriptName);
3040
+ }
3041
+ };
3042
+ LuaScriptLoader = __decorateClass([
3043
+ Injectable(),
3044
+ __decorateParam(0, Inject(REDIS_DRIVER))
3045
+ ], LuaScriptLoader);
3046
+
3047
+ // src/cache.plugin.ts
3048
+ var CachePlugin = class {
3049
+ constructor(options = {}) {
3050
+ this.options = options;
3051
+ }
3052
+ name = "cache";
3053
+ version = "0.1.0";
3054
+ description = "Advanced caching with L1+L2, anti-stampede, SWR, and tag invalidation";
3055
+ getProviders() {
3056
+ const config = {
3057
+ l1: { ...DEFAULT_CACHE_CONFIG.l1, ...this.options.l1 },
3058
+ l2: { ...DEFAULT_CACHE_CONFIG.l2, ...this.options.l2 },
3059
+ stampede: { ...DEFAULT_CACHE_CONFIG.stampede, ...this.options.stampede },
3060
+ swr: { ...DEFAULT_CACHE_CONFIG.swr, ...this.options.swr },
3061
+ tags: { ...DEFAULT_CACHE_CONFIG.tags, ...this.options.tags },
3062
+ warmup: { ...DEFAULT_CACHE_CONFIG.warmup, ...this.options.warmup },
3063
+ keys: { ...DEFAULT_CACHE_CONFIG.keys, ...this.options.keys },
3064
+ invalidation: {
3065
+ ...DEFAULT_CACHE_CONFIG.invalidation,
3066
+ ...this.options.invalidation
3067
+ }
3068
+ };
3069
+ return [
3070
+ // Configuration
3071
+ {
3072
+ provide: CACHE_PLUGIN_OPTIONS,
3073
+ useValue: config
3074
+ },
3075
+ // Domain services
3076
+ {
3077
+ provide: SERIALIZER,
3078
+ useClass: Serializer
3079
+ },
3080
+ // Infrastructure adapters
3081
+ {
3082
+ provide: L1_CACHE_STORE,
3083
+ useClass: L1MemoryStoreAdapter
3084
+ },
3085
+ {
3086
+ provide: L2_CACHE_STORE,
3087
+ useClass: L2RedisStoreAdapter
3088
+ },
3089
+ // Application services
3090
+ {
3091
+ provide: CACHE_SERVICE,
3092
+ useClass: CacheService
3093
+ },
3094
+ {
3095
+ provide: STAMPEDE_PROTECTION,
3096
+ useClass: StampedeProtectionService
3097
+ },
3098
+ {
3099
+ provide: TAG_INDEX,
3100
+ useClass: TagIndexRepository
3101
+ },
3102
+ {
3103
+ provide: SWR_MANAGER,
3104
+ useClass: SwrManagerService
3105
+ },
3106
+ {
3107
+ provide: LUA_SCRIPT_LOADER,
3108
+ useClass: LuaScriptLoader
3109
+ },
3110
+ // Invalidation services
3111
+ {
3112
+ provide: INVALIDATION_REGISTRY,
3113
+ useClass: InvalidationRegistryService
3114
+ },
3115
+ {
3116
+ provide: EVENT_INVALIDATION_SERVICE,
3117
+ useClass: EventInvalidationService
3118
+ },
3119
+ // Invalidation adapters (optional)
3120
+ AMQPEventSourceAdapter,
3121
+ // Public API
3122
+ CacheService2,
3123
+ // @Cached decorator initialization
3124
+ CacheDecoratorInitializerService,
3125
+ // Cache warmup (runs on OnModuleInit if enabled)
3126
+ WarmupService,
3127
+ // Reflector is needed for decorator metadata
3128
+ Reflector,
3129
+ // Factory for registering static invalidation rules
3130
+ {
3131
+ provide: INVALIDATION_RULES_INIT,
3132
+ useFactory: (registry, config2) => {
3133
+ if (config2.invalidation?.rules && config2.invalidation.rules.length > 0) {
3134
+ const rules = config2.invalidation.rules.map((ruleProps) => InvalidationRule.create(ruleProps));
3135
+ registry.registerMany(rules);
3136
+ }
3137
+ return true;
3138
+ },
3139
+ inject: [INVALIDATION_REGISTRY, CACHE_PLUGIN_OPTIONS]
3140
+ }
3141
+ ];
3142
+ }
3143
+ getExports() {
3144
+ return [CACHE_PLUGIN_OPTIONS, CACHE_SERVICE, CacheService2, INVALIDATION_REGISTRY, EVENT_INVALIDATION_SERVICE];
3145
+ }
3146
+ };
3147
+ var logger2 = new Logger("InvalidateTags");
3148
+ function InvalidateTags(options) {
3149
+ return (target, propertyKey, descriptor) => {
3150
+ const originalMethod = descriptor.value;
3151
+ const when = options.when ?? "after";
3152
+ descriptor.value = async function(...args) {
3153
+ const cacheService = getCacheService();
3154
+ const tags = typeof options.tags === "function" ? options.tags(...args) : options.tags;
3155
+ if (when === "before" && cacheService && tags.length > 0) {
3156
+ try {
3157
+ await cacheService.invalidateTags(tags);
3158
+ } catch (error) {
3159
+ logger2.error(`@InvalidateTags: Failed to invalidate tags before method:`, error);
3160
+ }
3161
+ }
3162
+ const result = await originalMethod.apply(this, args);
3163
+ if (when === "after" && cacheService && tags.length > 0) {
3164
+ try {
3165
+ await cacheService.invalidateTags(tags);
3166
+ } catch (error) {
3167
+ logger2.error(`@InvalidateTags: Failed to invalidate tags after method:`, error);
3168
+ }
3169
+ }
3170
+ return result;
3171
+ };
3172
+ Object.defineProperty(descriptor.value, "name", {
3173
+ value: originalMethod.name,
3174
+ writable: false
3175
+ });
3176
+ Reflect.defineMetadata(INVALIDATE_TAGS_KEY, options, descriptor.value);
3177
+ return descriptor;
3178
+ };
3179
+ }
3180
+ var CACHEABLE_METADATA_KEY = "cache:cacheable";
3181
+ function Cacheable(options) {
3182
+ return SetMetadata(CACHEABLE_METADATA_KEY, options);
3183
+ }
3184
+ var CACHE_PUT_METADATA_KEY = "cache:put";
3185
+ function CachePut(options) {
3186
+ return SetMetadata(CACHE_PUT_METADATA_KEY, options);
3187
+ }
3188
+ var CACHE_EVICT_METADATA_KEY = "cache:evict";
3189
+ function CacheEvict(options = {}) {
3190
+ return SetMetadata(CACHE_EVICT_METADATA_KEY, options);
3191
+ }
3192
+
3193
+ // src/decorators/key-generator.util.ts
3194
+ function getParameterNames(method) {
3195
+ const fnStr = method.toString().replace(/\/\*[\s\S]*?\*\//g, "");
3196
+ const match = fnStr.match(/\(([^)]*)\)/);
3197
+ if (!match?.[1]) {
3198
+ return [];
3199
+ }
3200
+ const params = match[1];
3201
+ return params.split(",").map((param) => param.trim().split("=")[0]?.trim() ?? "").filter((param) => param && param !== "");
3202
+ }
3203
+ function getNestedValue(obj, path) {
3204
+ if (!obj || typeof obj !== "object") {
3205
+ return void 0;
3206
+ }
3207
+ const keys = path.split(".");
3208
+ let value = obj;
3209
+ for (const key of keys) {
3210
+ if (value === null || value === void 0) {
3211
+ return void 0;
3212
+ }
3213
+ value = value[key];
3214
+ }
3215
+ return value;
3216
+ }
3217
+ function generateKey(template, method, args, namespace) {
3218
+ const paramNames = getParameterNames(method);
3219
+ const paramMap = /* @__PURE__ */ new Map();
3220
+ paramNames.forEach((name, index) => {
3221
+ if (index < args.length) {
3222
+ paramMap.set(name, args[index]);
3223
+ }
3224
+ });
3225
+ let key = template;
3226
+ const placeholderRegex = /\{([^}]+)\}/g;
3227
+ const matches = Array.from(template.matchAll(placeholderRegex));
3228
+ if (matches.length === 0) {
3229
+ return namespace ? `${namespace}:${template}` : template;
3230
+ }
3231
+ for (const match of matches) {
3232
+ const placeholder = match[0];
3233
+ const path = match[1];
3234
+ if (!path) {
3235
+ continue;
3236
+ }
3237
+ let value;
3238
+ if (path.includes(".")) {
3239
+ const [rootParam, ...nestedPath] = path.split(".");
3240
+ if (!rootParam) {
3241
+ throw new CacheKeyError(template, "Invalid nested property path");
3242
+ }
3243
+ const rootValue = paramMap.get(rootParam);
3244
+ if (rootValue === void 0) {
3245
+ throw new CacheKeyError(template, `Parameter '${rootParam}' not found in method signature`);
3246
+ }
3247
+ value = getNestedValue(rootValue, nestedPath.join("."));
3248
+ if (value === void 0) {
3249
+ throw new CacheKeyError(template, `Nested property '${path}' not found or is undefined`);
3250
+ }
3251
+ } else {
3252
+ if (!paramMap.has(path)) {
3253
+ throw new CacheKeyError(template, `Parameter '${path}' not found in method signature`);
3254
+ }
3255
+ value = paramMap.get(path);
3256
+ if (value === void 0 || value === null) {
3257
+ throw new CacheKeyError(template, `Parameter '${path}' is null or undefined`);
3258
+ }
3259
+ }
3260
+ const stringValue = String(value);
3261
+ if (stringValue.includes(":") || stringValue.includes("{") || stringValue.includes("}")) {
3262
+ throw new CacheKeyError(template, `Parameter value '${stringValue}' contains invalid characters (:, {, })`);
3263
+ }
3264
+ key = key.replace(placeholder, stringValue);
3265
+ }
3266
+ if (namespace) {
3267
+ key = `${namespace}:${key}`;
3268
+ }
3269
+ return key;
3270
+ }
3271
+ function generateKeys(templates, method, args, namespace) {
3272
+ return templates.map((template) => generateKey(template, method, args, namespace));
3273
+ }
3274
+ function evaluateTags(tags, args) {
3275
+ if (!tags) {
3276
+ return [];
3277
+ }
3278
+ if (typeof tags === "function") {
3279
+ return tags(...args);
3280
+ }
3281
+ return tags;
3282
+ }
3283
+ function evaluateCondition(condition, args) {
3284
+ if (!condition) {
3285
+ return true;
3286
+ }
3287
+ return condition(...args);
3288
+ }
3289
+ var CacheInterceptor = class {
3290
+ constructor(cacheService, reflector) {
3291
+ this.cacheService = cacheService;
3292
+ this.reflector = reflector;
3293
+ }
3294
+ logger = new Logger(CacheInterceptor.name);
3295
+ intercept(context, next) {
3296
+ const handler = context.getHandler();
3297
+ context.getClass();
3298
+ const cacheableOptions = this.reflector.get(CACHEABLE_METADATA_KEY, handler);
3299
+ const cachePutOptions = this.reflector.get(CACHE_PUT_METADATA_KEY, handler);
3300
+ const cacheEvictOptions = this.reflector.get(CACHE_EVICT_METADATA_KEY, handler);
3301
+ const args = context.getArgByIndex(context.getArgs().length - 1) ? context.getArgs() : [];
3302
+ const method = handler;
3303
+ if (cacheableOptions) {
3304
+ return this.handleCacheable(cacheableOptions, method, args, next);
3305
+ }
3306
+ if (cachePutOptions) {
3307
+ return this.handleCachePut(cachePutOptions, method, args, next);
3308
+ }
3309
+ if (cacheEvictOptions) {
3310
+ return this.handleCacheEvict(cacheEvictOptions, method, args, next);
3311
+ }
3312
+ return next.handle();
3313
+ }
3314
+ /**
3315
+ * Handles @Cacheable decorator.
3316
+ * Returns cached value or executes method and caches result.
3317
+ */
3318
+ handleCacheable(options, method, args, next) {
3319
+ if (!evaluateCondition(options.condition, args)) {
3320
+ this.logger.debug("Cacheable condition not met, executing method");
3321
+ return next.handle();
3322
+ }
3323
+ try {
3324
+ const key = options.keyGenerator ? options.keyGenerator(...args) : generateKey(options.key, method, args, options.namespace);
3325
+ this.logger.debug(`Cacheable: checking cache for key: ${key}`);
3326
+ return from(this.cacheService.get(key)).pipe(
3327
+ switchMap((cachedValue) => {
3328
+ if (cachedValue !== null) {
3329
+ this.logger.debug(`Cacheable: cache hit for key: ${key}`);
3330
+ return of(cachedValue);
3331
+ }
3332
+ this.logger.debug(`Cacheable: cache miss for key: ${key}, executing method`);
3333
+ return next.handle().pipe(
3334
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
3335
+ tap(async (result) => {
3336
+ const tags = evaluateTags(options.tags, args);
3337
+ const ttl = options.ttl ?? 3600;
3338
+ try {
3339
+ await this.cacheService.set(key, result, { ttl, tags });
3340
+ this.logger.debug(`Cacheable: cached result for key: ${key}`);
3341
+ } catch (error) {
3342
+ this.logger.error(`Cacheable: failed to cache result for key ${key}: ${error.message}`);
3343
+ }
3344
+ })
3345
+ );
3346
+ })
3347
+ );
3348
+ } catch (error) {
3349
+ this.logger.error(`Cacheable: error processing cache: ${error.message}`);
3350
+ return next.handle();
3351
+ }
3352
+ }
3353
+ /**
3354
+ * Handles @CachePut decorator.
3355
+ * Always executes method and caches the result.
3356
+ */
3357
+ handleCachePut(options, method, args, next) {
3358
+ if (!evaluateCondition(options.condition, args)) {
3359
+ this.logger.debug("CachePut condition not met, executing method without caching");
3360
+ return next.handle();
3361
+ }
3362
+ try {
3363
+ const key = options.keyGenerator ? options.keyGenerator(...args) : generateKey(options.key, method, args, options.namespace);
3364
+ this.logger.debug(`CachePut: executing method for key: ${key}`);
3365
+ return next.handle().pipe(
3366
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
3367
+ tap(async (result) => {
3368
+ if ((result === null || result === void 0) && !options.cacheNullValues) {
3369
+ this.logger.debug(`CachePut: skipping null/undefined result for key: ${key}`);
3370
+ return;
3371
+ }
3372
+ const tags = evaluateTags(options.tags, args);
3373
+ const ttl = options.ttl ?? 3600;
3374
+ try {
3375
+ await this.cacheService.set(key, result, { ttl, tags });
3376
+ this.logger.debug(`CachePut: cached result for key: ${key}`);
3377
+ } catch (error) {
3378
+ this.logger.error(`CachePut: failed to cache result for key ${key}: ${error.message}`);
3379
+ }
3380
+ })
3381
+ );
3382
+ } catch (error) {
3383
+ this.logger.error(`CachePut: error processing cache: ${error.message}`);
3384
+ return next.handle();
3385
+ }
3386
+ }
3387
+ /**
3388
+ * Handles @CacheEvict decorator.
3389
+ * Evicts cache entries before or after method execution.
3390
+ */
3391
+ handleCacheEvict(options, method, args, next) {
3392
+ if (!evaluateCondition(options.condition, args)) {
3393
+ this.logger.debug("CacheEvict condition not met, executing method without eviction");
3394
+ return next.handle();
3395
+ }
3396
+ const evictFn = async () => {
3397
+ try {
3398
+ if (options.allEntries) {
3399
+ this.logger.debug("CacheEvict: clearing all cache entries");
3400
+ await this.cacheService.clear();
3401
+ return;
3402
+ }
3403
+ if (options.tags && options.tags.length > 0) {
3404
+ this.logger.debug(`CacheEvict: invalidating tags: ${options.tags.join(", ")}`);
3405
+ await this.cacheService.invalidateTags(options.tags);
3406
+ }
3407
+ if (options.keys && options.keys.length > 0) {
3408
+ const keys = options.keyGenerator ? options.keyGenerator(...args) : generateKeys(options.keys, method, args, options.namespace);
3409
+ this.logger.debug(`CacheEvict: evicting keys: ${keys.join(", ")}`);
3410
+ for (const key of keys) {
3411
+ if (key.includes("*")) {
3412
+ this.logger.warn(`CacheEvict: wildcard keys not supported: ${key}. Use tags instead.`);
3413
+ } else {
3414
+ await this.cacheService.del(key);
3415
+ }
3416
+ }
3417
+ }
3418
+ } catch (error) {
3419
+ this.logger.error(`CacheEvict: error evicting cache: ${error.message}`);
3420
+ }
3421
+ };
3422
+ if (options.beforeInvocation) {
3423
+ return from(evictFn()).pipe(switchMap(() => next.handle()));
3424
+ }
3425
+ return next.handle().pipe(
3426
+ tap(() => {
3427
+ void evictFn();
3428
+ })
3429
+ );
3430
+ }
3431
+ };
3432
+ CacheInterceptor = __decorateClass([
3433
+ Injectable()
3434
+ ], CacheInterceptor);
3435
+
3436
+ // src/strategies/lru.strategy.ts
3437
+ var LruStrategy = class {
3438
+ /**
3439
+ * Map maintaining keys in LRU order.
3440
+ * JavaScript Map maintains insertion order, so we use delete+set to move to end.
3441
+ */
3442
+ keyOrder;
3443
+ /**
3444
+ * Timestamp counter for tracking access order
3445
+ */
3446
+ timestamp;
3447
+ constructor() {
3448
+ this.keyOrder = /* @__PURE__ */ new Map();
3449
+ this.timestamp = 0;
3450
+ }
3451
+ /**
3452
+ * Records access to a key, moving it to most recently used position.
3453
+ *
3454
+ * @param key - Key that was accessed
3455
+ */
3456
+ recordAccess(key) {
3457
+ this.timestamp++;
3458
+ this.keyOrder.set(key, this.timestamp);
3459
+ }
3460
+ /**
3461
+ * Records insertion of a new key.
3462
+ *
3463
+ * @param key - Key that was inserted
3464
+ */
3465
+ recordInsert(key) {
3466
+ this.timestamp++;
3467
+ this.keyOrder.set(key, this.timestamp);
3468
+ }
3469
+ /**
3470
+ * Records deletion of a key.
3471
+ *
3472
+ * @param key - Key that was deleted
3473
+ */
3474
+ recordDelete(key) {
3475
+ this.keyOrder.delete(key);
3476
+ }
3477
+ /**
3478
+ * Selects the least recently used key for eviction.
3479
+ *
3480
+ * @returns Least recently used key, or undefined if empty
3481
+ */
3482
+ selectVictim() {
3483
+ if (this.keyOrder.size === 0) {
3484
+ return void 0;
3485
+ }
3486
+ let oldestKey;
3487
+ let oldestTimestamp = Infinity;
3488
+ for (const [key, timestamp] of this.keyOrder.entries()) {
3489
+ if (timestamp < oldestTimestamp) {
3490
+ oldestTimestamp = timestamp;
3491
+ oldestKey = key;
3492
+ }
3493
+ }
3494
+ return oldestKey;
3495
+ }
3496
+ /**
3497
+ * Clears all tracking data.
3498
+ */
3499
+ clear() {
3500
+ this.keyOrder.clear();
3501
+ this.timestamp = 0;
3502
+ }
3503
+ /**
3504
+ * Gets current number of tracked keys.
3505
+ *
3506
+ * @returns Number of keys
3507
+ */
3508
+ size() {
3509
+ return this.keyOrder.size;
3510
+ }
3511
+ /**
3512
+ * Gets all keys in LRU order (oldest to newest).
3513
+ *
3514
+ * @returns Array of keys sorted by access time
3515
+ */
3516
+ getKeys() {
3517
+ return Array.from(this.keyOrder.entries()).sort(([, a], [, b]) => a - b).map(([key]) => key);
3518
+ }
3519
+ /**
3520
+ * Gets keys that should be evicted to reach target size.
3521
+ *
3522
+ * @param targetSize - Desired size after eviction
3523
+ * @returns Array of keys to evict
3524
+ */
3525
+ getVictims(targetSize) {
3526
+ const currentSize = this.keyOrder.size;
3527
+ if (currentSize <= targetSize) {
3528
+ return [];
3529
+ }
3530
+ const numToEvict = currentSize - targetSize;
3531
+ const sortedKeys = this.getKeys();
3532
+ return sortedKeys.slice(0, numToEvict);
3533
+ }
3534
+ };
3535
+
3536
+ // src/strategies/fifo.strategy.ts
3537
+ var FifoStrategy = class {
3538
+ /**
3539
+ * Queue maintaining keys in insertion order.
3540
+ * First element is oldest (next to evict).
3541
+ */
3542
+ queue;
3543
+ /**
3544
+ * Set for O(1) existence checks
3545
+ */
3546
+ keySet;
3547
+ constructor() {
3548
+ this.queue = [];
3549
+ this.keySet = /* @__PURE__ */ new Set();
3550
+ }
3551
+ /**
3552
+ * Records access to a key.
3553
+ * In FIFO, access doesn't affect eviction order, so this is a no-op.
3554
+ *
3555
+ * @param _key - Key that was accessed
3556
+ */
3557
+ recordAccess(_key) {
3558
+ }
3559
+ /**
3560
+ * Records insertion of a new key.
3561
+ *
3562
+ * @param key - Key that was inserted
3563
+ */
3564
+ recordInsert(key) {
3565
+ if (!this.keySet.has(key)) {
3566
+ this.queue.push(key);
3567
+ this.keySet.add(key);
3568
+ }
3569
+ }
3570
+ /**
3571
+ * Records deletion of a key.
3572
+ *
3573
+ * @param key - Key that was deleted
3574
+ */
3575
+ recordDelete(key) {
3576
+ if (this.keySet.has(key)) {
3577
+ const index = this.queue.indexOf(key);
3578
+ if (index !== -1) {
3579
+ this.queue.splice(index, 1);
3580
+ }
3581
+ this.keySet.delete(key);
3582
+ }
3583
+ }
3584
+ /**
3585
+ * Selects the oldest inserted key for eviction.
3586
+ *
3587
+ * @returns Oldest key (first in queue), or undefined if empty
3588
+ */
3589
+ selectVictim() {
3590
+ return this.queue[0];
3591
+ }
3592
+ /**
3593
+ * Clears all tracking data.
3594
+ */
3595
+ clear() {
3596
+ this.queue.length = 0;
3597
+ this.keySet.clear();
3598
+ }
3599
+ /**
3600
+ * Gets current number of tracked keys.
3601
+ *
3602
+ * @returns Number of keys
3603
+ */
3604
+ size() {
3605
+ return this.queue.length;
3606
+ }
3607
+ /**
3608
+ * Gets all keys in FIFO order (oldest to newest).
3609
+ *
3610
+ * @returns Array of keys in insertion order
3611
+ */
3612
+ getKeys() {
3613
+ return [...this.queue];
3614
+ }
3615
+ /**
3616
+ * Gets keys that should be evicted to reach target size.
3617
+ *
3618
+ * @param targetSize - Desired size after eviction
3619
+ * @returns Array of keys to evict (oldest first)
3620
+ */
3621
+ getVictims(targetSize) {
3622
+ const currentSize = this.queue.length;
3623
+ if (currentSize <= targetSize) {
3624
+ return [];
3625
+ }
3626
+ const numToEvict = currentSize - targetSize;
3627
+ return this.queue.slice(0, numToEvict);
3628
+ }
3629
+ /**
3630
+ * Checks if a key is tracked.
3631
+ *
3632
+ * @param key - Key to check
3633
+ * @returns True if key is tracked
3634
+ */
3635
+ has(key) {
3636
+ return this.keySet.has(key);
3637
+ }
3638
+ };
3639
+
3640
+ // src/strategies/lfu.strategy.ts
3641
+ var LfuStrategy = class {
3642
+ entries;
3643
+ insertCounter;
3644
+ constructor() {
3645
+ this.entries = /* @__PURE__ */ new Map();
3646
+ this.insertCounter = 0;
3647
+ }
3648
+ /**
3649
+ * Records access to a key, incrementing its frequency counter.
3650
+ *
3651
+ * @param key - Key that was accessed
3652
+ */
3653
+ recordAccess(key) {
3654
+ const entry = this.entries.get(key);
3655
+ if (entry) {
3656
+ entry.frequency++;
3657
+ }
3658
+ }
3659
+ /**
3660
+ * Records insertion of a new key with initial frequency of 1.
3661
+ *
3662
+ * @param key - Key that was inserted
3663
+ */
3664
+ recordInsert(key) {
3665
+ if (!this.entries.has(key)) {
3666
+ this.insertCounter++;
3667
+ this.entries.set(key, {
3668
+ key,
3669
+ frequency: 1,
3670
+ insertOrder: this.insertCounter
3671
+ });
3672
+ }
3673
+ }
3674
+ /**
3675
+ * Records deletion of a key.
3676
+ *
3677
+ * @param key - Key that was deleted
3678
+ */
3679
+ recordDelete(key) {
3680
+ this.entries.delete(key);
3681
+ }
3682
+ /**
3683
+ * Selects the least frequently used key for eviction.
3684
+ * When frequencies are equal, the oldest inserted key is selected.
3685
+ *
3686
+ * @returns Least frequently used key, or undefined if empty
3687
+ */
3688
+ selectVictim() {
3689
+ if (this.entries.size === 0) {
3690
+ return void 0;
3691
+ }
3692
+ let victim;
3693
+ for (const entry of this.entries.values()) {
3694
+ if (!victim || entry.frequency < victim.frequency || entry.frequency === victim.frequency && entry.insertOrder < victim.insertOrder) {
3695
+ victim = entry;
3696
+ }
3697
+ }
3698
+ return victim?.key;
3699
+ }
3700
+ /**
3701
+ * Clears all tracking data.
3702
+ */
3703
+ clear() {
3704
+ this.entries.clear();
3705
+ this.insertCounter = 0;
3706
+ }
3707
+ /**
3708
+ * Gets current number of tracked keys.
3709
+ *
3710
+ * @returns Number of keys
3711
+ */
3712
+ size() {
3713
+ return this.entries.size;
3714
+ }
3715
+ /**
3716
+ * Gets all keys sorted by frequency (lowest first), then by insertion order.
3717
+ *
3718
+ * @returns Array of keys sorted by eviction priority
3719
+ */
3720
+ getKeys() {
3721
+ return Array.from(this.entries.values()).sort((a, b) => a.frequency - b.frequency || a.insertOrder - b.insertOrder).map((entry) => entry.key);
3722
+ }
3723
+ /**
3724
+ * Gets keys that should be evicted to reach target size.
3725
+ *
3726
+ * @param targetSize - Desired size after eviction
3727
+ * @returns Array of keys to evict
3728
+ */
3729
+ getVictims(targetSize) {
3730
+ const currentSize = this.entries.size;
3731
+ if (currentSize <= targetSize) {
3732
+ return [];
3733
+ }
3734
+ const numToEvict = currentSize - targetSize;
3735
+ const sortedKeys = this.getKeys();
3736
+ return sortedKeys.slice(0, numToEvict);
3737
+ }
3738
+ /**
3739
+ * Checks if a key is tracked.
3740
+ *
3741
+ * @param key - Key to check
3742
+ * @returns True if key is tracked
3743
+ */
3744
+ has(key) {
3745
+ return this.entries.has(key);
3746
+ }
3747
+ /**
3748
+ * Gets the frequency count for a key.
3749
+ *
3750
+ * @param key - Key to check
3751
+ * @returns Frequency count, or 0 if key not tracked
3752
+ */
3753
+ getFrequency(key) {
3754
+ return this.entries.get(key)?.frequency ?? 0;
3755
+ }
3756
+ };
3757
+
3758
+ // src/serializers/json.serializer.ts
3759
+ var JsonSerializer = class {
3760
+ /**
3761
+ * Serializes value to JSON string.
3762
+ *
3763
+ * @param value - Value to serialize
3764
+ * @returns JSON string
3765
+ * @throws SerializationError if serialization fails
3766
+ */
3767
+ serialize(value) {
3768
+ try {
3769
+ return JSON.stringify(value);
3770
+ } catch (error) {
3771
+ throw new SerializationError(`JSON serialization failed: ${error.message}`, error);
3772
+ }
3773
+ }
3774
+ /**
3775
+ * Deserializes JSON string back to value.
3776
+ *
3777
+ * @param data - JSON string or buffer
3778
+ * @returns Deserialized value
3779
+ * @throws SerializationError if deserialization fails
3780
+ */
3781
+ deserialize(data) {
3782
+ try {
3783
+ const str = Buffer.isBuffer(data) ? data.toString("utf8") : data;
3784
+ return JSON.parse(str);
3785
+ } catch (error) {
3786
+ throw new SerializationError(`JSON deserialization failed: ${error.message}`, error);
3787
+ }
3788
+ }
3789
+ /**
3790
+ * Safely tries to deserialize, returns null on error.
3791
+ *
3792
+ * @param data - JSON string or buffer
3793
+ * @returns Deserialized value or null
3794
+ */
3795
+ tryDeserialize(data) {
3796
+ try {
3797
+ return this.deserialize(data);
3798
+ } catch {
3799
+ return null;
3800
+ }
3801
+ }
3802
+ /**
3803
+ * Gets content type for JSON.
3804
+ *
3805
+ * @returns Content type string
3806
+ */
3807
+ getContentType() {
3808
+ return "application/json";
3809
+ }
3810
+ };
3811
+
3812
+ // src/serializers/msgpack.serializer.ts
3813
+ var MsgpackSerializer = class {
3814
+ encoder;
3815
+ decoder;
3816
+ constructor() {
3817
+ try {
3818
+ const msgpackr = __require("msgpackr");
3819
+ this.encoder = new msgpackr.Encoder({
3820
+ useRecords: false,
3821
+ // Don't use msgpack extension records
3822
+ structuredClone: true
3823
+ // Deep clone objects
3824
+ });
3825
+ this.decoder = new msgpackr.Decoder({
3826
+ useRecords: false
3827
+ });
3828
+ } catch {
3829
+ throw new Error("msgpackr package is required for MsgpackSerializer. Install with: npm install msgpackr");
3830
+ }
3831
+ }
3832
+ /**
3833
+ * Serializes value to MessagePack buffer.
3834
+ *
3835
+ * @param value - Value to serialize
3836
+ * @returns MessagePack buffer
3837
+ * @throws SerializationError if serialization fails
3838
+ */
3839
+ serialize(value) {
3840
+ try {
3841
+ return this.encoder.encode(value);
3842
+ } catch (error) {
3843
+ throw new SerializationError(`MessagePack serialization failed: ${error.message}`, error);
3844
+ }
3845
+ }
3846
+ /**
3847
+ * Deserializes MessagePack buffer back to value.
3848
+ *
3849
+ * @param data - MessagePack buffer or string
3850
+ * @returns Deserialized value
3851
+ * @throws SerializationError if deserialization fails
3852
+ */
3853
+ deserialize(data) {
3854
+ try {
3855
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, "binary");
3856
+ return this.decoder.decode(buffer);
3857
+ } catch (error) {
3858
+ throw new SerializationError(`MessagePack deserialization failed: ${error.message}`, error);
3859
+ }
3860
+ }
3861
+ /**
3862
+ * Safely tries to deserialize, returns null on error.
3863
+ *
3864
+ * @param data - MessagePack buffer or string
3865
+ * @returns Deserialized value or null
3866
+ */
3867
+ tryDeserialize(data) {
3868
+ try {
3869
+ return this.deserialize(data);
3870
+ } catch {
3871
+ return null;
3872
+ }
3873
+ }
3874
+ /**
3875
+ * Gets content type for MessagePack.
3876
+ *
3877
+ * @returns Content type string
3878
+ */
3879
+ getContentType() {
3880
+ return "application/msgpack";
3881
+ }
3882
+ /**
3883
+ * Compares serialized size with JSON.
3884
+ *
3885
+ * @param value - Value to compare
3886
+ * @returns Object with sizes and compression ratio
3887
+ */
3888
+ compareWithJson(value) {
3889
+ const jsonSize = Buffer.from(JSON.stringify(value), "utf8").length;
3890
+ const msgpackSize = this.serialize(value).length;
3891
+ return {
3892
+ jsonSize,
3893
+ msgpackSize,
3894
+ compressionRatio: jsonSize / msgpackSize
3895
+ };
3896
+ }
3897
+ };
3898
+
3899
+ // src/key-builder.ts
3900
+ var KeyBuilder = class _KeyBuilder {
3901
+ keySegments = [];
3902
+ options;
3903
+ constructor(options = {}) {
3904
+ this.options = {
3905
+ separator: options.separator ?? ":",
3906
+ maxLength: options.maxLength ?? 512,
3907
+ validate: options.validate ?? true,
3908
+ lowercase: options.lowercase ?? false
3909
+ };
3910
+ }
3911
+ /**
3912
+ * Creates a new KeyBuilder instance.
3913
+ *
3914
+ * @param options - Builder options
3915
+ * @returns New KeyBuilder
3916
+ */
3917
+ static create(options) {
3918
+ return new _KeyBuilder(options);
3919
+ }
3920
+ /**
3921
+ * Creates a key from a template string with placeholders.
3922
+ *
3923
+ * @param template - Template string (e.g., 'user:{id}:post:{postId}')
3924
+ * @param params - Parameter values
3925
+ * @param options - Builder options
3926
+ * @returns Generated key
3927
+ *
3928
+ * @example
3929
+ * ```typescript
3930
+ * KeyBuilder.fromTemplate('user:{id}', { id: '123' })
3931
+ * // 'user:123'
3932
+ *
3933
+ * KeyBuilder.fromTemplate('user:{userId}:post:{postId}', {
3934
+ * userId: '123',
3935
+ * postId: '456',
3936
+ * })
3937
+ * // 'user:123:post:456'
3938
+ * ```
3939
+ */
3940
+ static fromTemplate(template, params, options) {
3941
+ let result = template;
3942
+ const placeholderRegex = /\{([^}]+)\}/g;
3943
+ const matches = Array.from(template.matchAll(placeholderRegex));
3944
+ for (const match of matches) {
3945
+ const placeholder = match[0];
3946
+ const paramName = match[1];
3947
+ if (!paramName) {
3948
+ continue;
3949
+ }
3950
+ if (!(paramName in params)) {
3951
+ throw new CacheKeyError(template, `Parameter '${paramName}' not found in params`);
3952
+ }
3953
+ const value = params[paramName];
3954
+ if (value === null || value === void 0) {
3955
+ throw new CacheKeyError(template, `Parameter '${paramName}' is null or undefined`);
3956
+ }
3957
+ result = result.replace(placeholder, String(value));
3958
+ }
3959
+ const builder = new _KeyBuilder(options);
3960
+ builder.validateKey(result);
3961
+ return result;
3962
+ }
3963
+ /**
3964
+ * Creates a key from an array of segments.
3965
+ *
3966
+ * @param segments - Array of key segments
3967
+ * @param options - Builder options
3968
+ * @returns Generated key
3969
+ */
3970
+ static fromSegments(segments, options) {
3971
+ const builder = new _KeyBuilder(options);
3972
+ segments.forEach((segment) => builder.segment(segment));
3973
+ return builder.build();
3974
+ }
3975
+ /**
3976
+ * Adds a namespace segment (first segment).
3977
+ *
3978
+ * @param ns - Namespace value
3979
+ * @returns This builder for chaining
3980
+ */
3981
+ namespace(ns) {
3982
+ this.keySegments.unshift(this.normalizeSegment(ns));
3983
+ return this;
3984
+ }
3985
+ /**
3986
+ * Adds a prefix segment.
3987
+ *
3988
+ * @param prefix - Prefix value
3989
+ * @returns This builder for chaining
3990
+ */
3991
+ prefix(prefix) {
3992
+ return this.segment(prefix);
3993
+ }
3994
+ /**
3995
+ * Adds a version segment.
3996
+ *
3997
+ * @param version - Version value (e.g., 'v1', 'v2')
3998
+ * @returns This builder for chaining
3999
+ */
4000
+ version(version) {
4001
+ return this.segment(version);
4002
+ }
4003
+ /**
4004
+ * Adds a segment to the key.
4005
+ *
4006
+ * @param value - Segment value
4007
+ * @returns This builder for chaining
4008
+ */
4009
+ segment(value) {
4010
+ this.keySegments.push(this.normalizeSegment(String(value)));
4011
+ return this;
4012
+ }
4013
+ /**
4014
+ * Adds multiple segments at once.
4015
+ *
4016
+ * @param values - Array of segment values
4017
+ * @returns This builder for chaining
4018
+ */
4019
+ segments(...values) {
4020
+ values.forEach((value) => this.segment(value));
4021
+ return this;
4022
+ }
4023
+ /**
4024
+ * Adds a tag segment (useful for grouping).
4025
+ *
4026
+ * @param tag - Tag value
4027
+ * @returns This builder for chaining
4028
+ */
4029
+ tag(tag) {
4030
+ this.segment("tag");
4031
+ this.segment(tag);
4032
+ return this;
4033
+ }
4034
+ /**
4035
+ * Adds timestamp segment.
4036
+ *
4037
+ * @param timestamp - Unix timestamp (default: now)
4038
+ * @returns This builder for chaining
4039
+ */
4040
+ timestamp(timestamp) {
4041
+ return this.segment(timestamp ?? Date.now());
4042
+ }
4043
+ /**
4044
+ * Adds a hash segment from an object.
4045
+ * Useful for cache keys based on complex objects.
4046
+ *
4047
+ * @param obj - Object to hash
4048
+ * @returns This builder for chaining
4049
+ */
4050
+ hash(obj) {
4051
+ const hash = this.simpleHash(JSON.stringify(obj));
4052
+ return this.segment(hash);
4053
+ }
4054
+ /**
4055
+ * Builds and returns the final key.
4056
+ *
4057
+ * @returns Generated cache key
4058
+ * @throws CacheKeyError if key is invalid
4059
+ */
4060
+ build() {
4061
+ if (this.keySegments.length === 0) {
4062
+ throw new CacheKeyError("", "Cannot build key with no segments");
4063
+ }
4064
+ const key = this.keySegments.join(this.options.separator);
4065
+ this.validateKey(key);
4066
+ return key;
4067
+ }
4068
+ /**
4069
+ * Resets the builder to initial state.
4070
+ *
4071
+ * @returns This builder for chaining
4072
+ */
4073
+ reset() {
4074
+ this.keySegments = [];
4075
+ return this;
4076
+ }
4077
+ /**
4078
+ * Gets current segments.
4079
+ *
4080
+ * @returns Array of segments
4081
+ */
4082
+ getSegments() {
4083
+ return [...this.keySegments];
4084
+ }
4085
+ /**
4086
+ * Normalizes a segment value.
4087
+ *
4088
+ * @param value - Segment value
4089
+ * @returns Normalized value
4090
+ */
4091
+ normalizeSegment(value) {
4092
+ let normalized = value.trim();
4093
+ if (this.options.lowercase) {
4094
+ normalized = normalized.toLowerCase();
4095
+ }
4096
+ if (this.options.validate) {
4097
+ const invalidChars = [this.options.separator, "{", "}", " ", "\n", "\r", " "];
4098
+ for (const char of invalidChars) {
4099
+ if (normalized.includes(char)) {
4100
+ throw new CacheKeyError(normalized, `Segment contains invalid character: '${char}'`);
4101
+ }
4102
+ }
4103
+ if (normalized.length === 0) {
4104
+ throw new CacheKeyError(normalized, "Segment cannot be empty");
4105
+ }
4106
+ }
4107
+ return normalized;
4108
+ }
4109
+ /**
4110
+ * Validates a complete key.
4111
+ *
4112
+ * @param key - Key to validate
4113
+ * @throws CacheKeyError if key is invalid
4114
+ */
4115
+ validateKey(key) {
4116
+ if (!this.options.validate) {
4117
+ return;
4118
+ }
4119
+ if (key.length === 0) {
4120
+ throw new CacheKeyError(key, "Key cannot be empty");
4121
+ }
4122
+ if (key.length > this.options.maxLength) {
4123
+ throw new CacheKeyError(key, `Key length ${key.length} exceeds maximum ${this.options.maxLength}`);
4124
+ }
4125
+ if (key.startsWith(this.options.separator) || key.endsWith(this.options.separator)) {
4126
+ throw new CacheKeyError(key, "Key cannot start or end with separator");
4127
+ }
4128
+ const consecutiveSeparators = this.options.separator + this.options.separator;
4129
+ if (key.includes(consecutiveSeparators)) {
4130
+ throw new CacheKeyError(key, "Key cannot contain consecutive separators");
4131
+ }
4132
+ }
4133
+ /**
4134
+ * Simple hash function for objects.
4135
+ *
4136
+ * @param str - String to hash
4137
+ * @returns Hash string
4138
+ */
4139
+ simpleHash(str) {
4140
+ let hash = 0;
4141
+ for (let i = 0; i < str.length; i++) {
4142
+ const char = str.charCodeAt(i);
4143
+ hash = (hash << 5) - hash + char;
4144
+ hash = hash & hash;
4145
+ }
4146
+ return Math.abs(hash).toString(36);
4147
+ }
4148
+ };
4149
+
4150
+ export { AMQP_CONNECTION, CACHE_OPTIONS_KEY, CACHE_PLUGIN_OPTIONS, CACHE_SERVICE, CacheEntry, CacheError, CacheEvict, CacheKey, CacheKeyError, CachePlugin, CachePut, CacheService2 as CacheService, Cacheable, Cached, DEFAULT_CACHE_CONFIG, CacheInterceptor as DeclarativeCacheInterceptor, EVENT_INVALIDATION_SERVICE, EventInvalidationService, EventPattern, FifoStrategy, INVALIDATE_TAGS_KEY, INVALIDATION_REGISTRY, InvalidateOn, InvalidateTags, InvalidationRegistryService, InvalidationRule, JsonSerializer, KeyBuilder, L1_CACHE_STORE, L2_CACHE_STORE, LUA_SCRIPT_LOADER, LfuStrategy, LoaderError, LruStrategy, MsgpackSerializer, SERIALIZER, STAMPEDE_PROTECTION, SWR_MANAGER, SerializationError, StampedeError, TAG_INDEX, TTL, TagInvalidationError, TagTemplate, evaluateCondition, evaluateTags, generateKey, generateKeys, getNestedValue, getParameterNames };
4151
+ //# sourceMappingURL=index.mjs.map
4152
+ //# sourceMappingURL=index.mjs.map