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