@nestjs-redisx/locks 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 (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/index.d.ts +9 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +635 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +625 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/lock/api/decorators/with-lock.decorator.d.ts +105 -0
  10. package/dist/lock/api/decorators/with-lock.decorator.d.ts.map +1 -0
  11. package/dist/lock/application/ports/lock-service.port.d.ts +120 -0
  12. package/dist/lock/application/ports/lock-service.port.d.ts.map +1 -0
  13. package/dist/lock/application/ports/lock-store.port.d.ts +63 -0
  14. package/dist/lock/application/ports/lock-store.port.d.ts.map +1 -0
  15. package/dist/lock/application/services/lock-decorator-initializer.service.d.ts +19 -0
  16. package/dist/lock/application/services/lock-decorator-initializer.service.d.ts.map +1 -0
  17. package/dist/lock/application/services/lock.service.d.ts +90 -0
  18. package/dist/lock/application/services/lock.service.d.ts.map +1 -0
  19. package/dist/lock/domain/entities/lock.entity.d.ts +130 -0
  20. package/dist/lock/domain/entities/lock.entity.d.ts.map +1 -0
  21. package/dist/lock/infrastructure/adapters/redis-lock-store.adapter.d.ts +45 -0
  22. package/dist/lock/infrastructure/adapters/redis-lock-store.adapter.d.ts.map +1 -0
  23. package/dist/lock/infrastructure/scripts/lua-scripts.d.ts +24 -0
  24. package/dist/lock/infrastructure/scripts/lua-scripts.d.ts.map +1 -0
  25. package/dist/locks.plugin.d.ts +41 -0
  26. package/dist/locks.plugin.d.ts.map +1 -0
  27. package/dist/shared/constants/index.d.ts +13 -0
  28. package/dist/shared/constants/index.d.ts.map +1 -0
  29. package/dist/shared/errors/index.d.ts +64 -0
  30. package/dist/shared/errors/index.d.ts.map +1 -0
  31. package/dist/shared/types/index.d.ts +97 -0
  32. package/dist/shared/types/index.d.ts.map +1 -0
  33. package/package.json +78 -0
package/dist/index.js ADDED
@@ -0,0 +1,635 @@
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
+
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __decorateClass = (decorators, target, key, kind) => {
10
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
11
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
12
+ if (decorator = decorators[i])
13
+ result = (decorator(result)) || result;
14
+ return result;
15
+ };
16
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
17
+
18
+ // src/shared/constants/index.ts
19
+ var LOCKS_PLUGIN_OPTIONS = /* @__PURE__ */ Symbol.for("LOCKS_PLUGIN_OPTIONS");
20
+ var LOCK_SERVICE = /* @__PURE__ */ Symbol.for("LOCK_SERVICE");
21
+ var LOCK_STORE = /* @__PURE__ */ Symbol.for("LOCK_STORE");
22
+ var LockError = class extends core.RedisXError {
23
+ constructor(message, code, lockKey, cause) {
24
+ super(message, code, cause, { lockKey });
25
+ this.lockKey = lockKey;
26
+ }
27
+ };
28
+ var LockAcquisitionError = class extends LockError {
29
+ constructor(key, reason, cause) {
30
+ super(`Failed to acquire lock "${key}": ${reason}`, reason === "timeout" ? core.ErrorCode.LOCK_ACQUISITION_TIMEOUT : core.ErrorCode.LOCK_ACQUISITION_FAILED, key, cause);
31
+ this.reason = reason;
32
+ }
33
+ };
34
+ var LockNotOwnedError = class extends LockError {
35
+ constructor(key, token) {
36
+ super(`Lock "${key}" not owned by token "${token}"`, core.ErrorCode.LOCK_NOT_OWNED, key);
37
+ this.token = token;
38
+ }
39
+ toJSON() {
40
+ return {
41
+ ...super.toJSON(),
42
+ token: this.token
43
+ };
44
+ }
45
+ };
46
+ var LockExtensionError = class extends LockError {
47
+ constructor(key, token, cause) {
48
+ super(`Failed to extend lock "${key}"`, core.ErrorCode.LOCK_EXTENSION_FAILED, key, cause);
49
+ this.token = token;
50
+ }
51
+ toJSON() {
52
+ return {
53
+ ...super.toJSON(),
54
+ token: this.token
55
+ };
56
+ }
57
+ };
58
+ var LockExpiredError = class extends LockError {
59
+ constructor(key) {
60
+ super(`Lock "${key}" expired`, core.ErrorCode.LOCK_EXPIRED, key);
61
+ }
62
+ };
63
+
64
+ // src/lock/api/decorators/with-lock.decorator.ts
65
+ var logger = new common.Logger("WithLock");
66
+ var WITH_LOCK_OPTIONS = /* @__PURE__ */ Symbol.for("WITH_LOCK_OPTIONS");
67
+ var globalLockServiceGetter = null;
68
+ function registerLockServiceGetter(getter) {
69
+ globalLockServiceGetter = getter;
70
+ }
71
+ function WithLock(options) {
72
+ return (target, propertyKey, descriptor) => {
73
+ const originalMethod = descriptor.value;
74
+ descriptor.value = async function(...args) {
75
+ if (!globalLockServiceGetter) {
76
+ logger.warn(`@WithLock: LockService not yet available, executing method without lock`);
77
+ return originalMethod.apply(this, args);
78
+ }
79
+ const lockService = globalLockServiceGetter();
80
+ if (!lockService) {
81
+ logger.warn(`@WithLock: LockService getter returned null, executing method without lock`);
82
+ return originalMethod.apply(this, args);
83
+ }
84
+ const key = buildLockKey(args, options);
85
+ let lock = null;
86
+ try {
87
+ lock = await lockService.acquire(key, {
88
+ ttl: options.ttl,
89
+ waitTimeout: options.waitTimeout,
90
+ autoRenew: options.autoRenew
91
+ });
92
+ const result = await originalMethod.apply(this, args);
93
+ return result;
94
+ } catch (error) {
95
+ if (error instanceof LockAcquisitionError) {
96
+ return handleLockFailed(key, options, error);
97
+ }
98
+ throw error;
99
+ } finally {
100
+ if (lock) {
101
+ await lock.release().catch((err) => {
102
+ logger.error(`@WithLock: Failed to release lock ${key}:`, err);
103
+ });
104
+ }
105
+ }
106
+ };
107
+ Object.defineProperty(descriptor.value, "name", {
108
+ value: originalMethod.name,
109
+ writable: false
110
+ });
111
+ Reflect.defineMetadata(WITH_LOCK_OPTIONS, options, descriptor.value);
112
+ return descriptor;
113
+ };
114
+ }
115
+ function buildLockKey(args, options) {
116
+ if (typeof options.key === "function") {
117
+ return options.key(...args);
118
+ }
119
+ return interpolateKey(options.key, args);
120
+ }
121
+ function interpolateKey(template, args) {
122
+ return template.replace(/\{(\d+)(?:\.(\w+))?\}/g, (_, index, prop) => {
123
+ const arg = args[Number(index)];
124
+ if (prop && typeof arg === "object" && arg !== null) {
125
+ return String(arg[prop]);
126
+ }
127
+ return String(arg);
128
+ });
129
+ }
130
+ function handleLockFailed(key, options, error) {
131
+ const handler = options.onLockFailed ?? "throw";
132
+ if (handler === "throw") {
133
+ throw error;
134
+ }
135
+ if (handler === "skip") {
136
+ return void 0;
137
+ }
138
+ if (typeof handler === "function") {
139
+ throw handler(key);
140
+ }
141
+ throw error;
142
+ }
143
+
144
+ // src/lock/application/services/lock-decorator-initializer.service.ts
145
+ var LockDecoratorInitializerService = class {
146
+ constructor(lockService) {
147
+ this.lockService = lockService;
148
+ }
149
+ logger = new common.Logger(LockDecoratorInitializerService.name);
150
+ /**
151
+ * Called after all modules are initialized.
152
+ * Registers lock service getter for @WithLock decorator.
153
+ */
154
+ // eslint-disable-next-line @typescript-eslint/require-await
155
+ async onModuleInit() {
156
+ this.logger.debug("Registering LockService getter for @WithLock decorator");
157
+ registerLockServiceGetter(() => this.lockService);
158
+ this.logger.log("@WithLock decorator initialized and ready to use");
159
+ }
160
+ };
161
+ LockDecoratorInitializerService = __decorateClass([
162
+ common.Injectable(),
163
+ __decorateParam(0, common.Inject(LOCK_SERVICE))
164
+ ], LockDecoratorInitializerService);
165
+
166
+ // src/lock/domain/entities/lock.entity.ts
167
+ var Lock = class {
168
+ /**
169
+ * Creates a new Lock instance.
170
+ *
171
+ * @param key - Lock key in Redis
172
+ * @param token - Unique ownership token
173
+ * @param ttl - Time-to-live in milliseconds
174
+ * @param store - Lock store for persistence operations
175
+ */
176
+ constructor(key, token, ttl, store) {
177
+ this.store = store;
178
+ this.key = key;
179
+ this.token = token;
180
+ this.ttl = ttl;
181
+ this.acquiredAt = /* @__PURE__ */ new Date();
182
+ this._expiresAt = new Date(Date.now() + ttl);
183
+ }
184
+ key;
185
+ token;
186
+ ttl;
187
+ acquiredAt;
188
+ _expiresAt;
189
+ autoRenewTimer = null;
190
+ released = false;
191
+ /**
192
+ * Gets the expiration timestamp.
193
+ */
194
+ get expiresAt() {
195
+ return this._expiresAt;
196
+ }
197
+ /**
198
+ * Checks if auto-renewal is active.
199
+ */
200
+ get isAutoRenewing() {
201
+ return this.autoRenewTimer !== null;
202
+ }
203
+ /**
204
+ * Releases the lock.
205
+ *
206
+ * Stops auto-renewal and removes the lock from Redis.
207
+ * Idempotent - can be called multiple times safely.
208
+ *
209
+ * @throws {LockNotOwnedError} If lock is not owned by this token
210
+ */
211
+ async release() {
212
+ if (this.released) {
213
+ return;
214
+ }
215
+ this.stopAutoRenew();
216
+ const success = await this.store.release(this.key, this.token);
217
+ if (!success) {
218
+ throw new LockNotOwnedError(this.key, this.token);
219
+ }
220
+ this.released = true;
221
+ }
222
+ /**
223
+ * Extends the lock TTL.
224
+ *
225
+ * @param ttl - New TTL in milliseconds
226
+ * @throws {LockNotOwnedError} If lock was already released
227
+ * @throws {LockExtensionError} If extension fails (lock expired or not owned)
228
+ */
229
+ async extend(ttl) {
230
+ if (this.released) {
231
+ throw new LockNotOwnedError(this.key, this.token);
232
+ }
233
+ const success = await this.store.extend(this.key, this.token, ttl);
234
+ if (!success) {
235
+ throw new LockExtensionError(this.key, this.token);
236
+ }
237
+ this._expiresAt = new Date(Date.now() + ttl);
238
+ }
239
+ /**
240
+ * Checks if lock is still held.
241
+ *
242
+ * @returns False if released, otherwise checks Redis
243
+ */
244
+ async isHeld() {
245
+ if (this.released) {
246
+ return false;
247
+ }
248
+ return this.store.isHeldBy(this.key, this.token);
249
+ }
250
+ /**
251
+ * Starts automatic lock renewal.
252
+ *
253
+ * The lock will be extended at the specified interval
254
+ * until stopAutoRenew() is called or extension fails.
255
+ *
256
+ * @param intervalMs - Renewal interval in milliseconds
257
+ */
258
+ startAutoRenew(intervalMs) {
259
+ if (this.autoRenewTimer) {
260
+ return;
261
+ }
262
+ this.autoRenewTimer = setInterval(async () => {
263
+ try {
264
+ await this.extend(this.ttl);
265
+ } catch {
266
+ this.stopAutoRenew();
267
+ }
268
+ }, intervalMs);
269
+ if (this.autoRenewTimer.unref) {
270
+ this.autoRenewTimer.unref();
271
+ }
272
+ }
273
+ /**
274
+ * Stops automatic lock renewal.
275
+ *
276
+ * Safe to call multiple times.
277
+ */
278
+ stopAutoRenew() {
279
+ if (this.autoRenewTimer) {
280
+ clearInterval(this.autoRenewTimer);
281
+ this.autoRenewTimer = null;
282
+ }
283
+ }
284
+ };
285
+
286
+ // src/lock/application/services/lock.service.ts
287
+ var METRICS_SERVICE = /* @__PURE__ */ Symbol.for("METRICS_SERVICE");
288
+ var TRACING_SERVICE = /* @__PURE__ */ Symbol.for("TRACING_SERVICE");
289
+ exports.LockService = class LockService {
290
+ constructor(config, store, metrics, tracing) {
291
+ this.config = config;
292
+ this.store = store;
293
+ this.metrics = metrics;
294
+ this.tracing = tracing;
295
+ }
296
+ logger = new common.Logger(exports.LockService.name);
297
+ activeLocks = /* @__PURE__ */ new Set();
298
+ /**
299
+ * Lifecycle hook: releases all active locks on shutdown.
300
+ */
301
+ async onModuleDestroy() {
302
+ const releasePromises = Array.from(this.activeLocks).map(
303
+ (lock) => lock.release().catch((error) => {
304
+ this.logger.error(`Failed to release lock ${lock.key} during shutdown:`, error);
305
+ })
306
+ );
307
+ await Promise.all(releasePromises);
308
+ this.activeLocks.clear();
309
+ }
310
+ /**
311
+ * Acquires lock with exponential backoff retry.
312
+ */
313
+ async acquire(key, options = {}) {
314
+ const span = this.tracing?.startSpan("lock.acquire", {
315
+ kind: "INTERNAL",
316
+ attributes: { "lock.key": key, "lock.ttl": options.ttl }
317
+ });
318
+ const fullKey = this.buildKey(key);
319
+ const ttl = this.resolveTtl(options.ttl);
320
+ const token = this.generateToken();
321
+ const retry = this.resolveRetryConfig(options);
322
+ const startTime = Date.now();
323
+ let delay = retry.initialDelay;
324
+ try {
325
+ for (let attempt = 0; attempt <= retry.maxRetries; attempt++) {
326
+ const acquired = await this.store.acquire(fullKey, token, ttl);
327
+ if (acquired) {
328
+ const waitDuration = (Date.now() - startTime) / 1e3;
329
+ this.metrics?.observeHistogram("redisx_lock_wait_duration_seconds", waitDuration);
330
+ this.metrics?.incrementCounter("redisx_lock_acquisitions_total", { status: "acquired" });
331
+ this.metrics?.incrementGauge("redisx_locks_active");
332
+ span?.setAttribute("lock.acquired", true);
333
+ span?.setAttribute("lock.attempts", attempt + 1);
334
+ span?.setStatus("OK");
335
+ const lock = this.createLock(fullKey, token, ttl, options);
336
+ this.activeLocks.add(lock);
337
+ return lock;
338
+ }
339
+ if (attempt === retry.maxRetries) {
340
+ this.metrics?.incrementCounter("redisx_lock_acquisitions_total", { status: "failed" });
341
+ span?.setAttribute("lock.acquired", false);
342
+ span?.setAttribute("lock.attempts", attempt + 1);
343
+ span?.setStatus("ERROR");
344
+ throw new LockAcquisitionError(key, "timeout");
345
+ }
346
+ await this.sleep(delay);
347
+ delay = Math.min(delay * retry.multiplier, retry.maxDelay);
348
+ }
349
+ this.metrics?.incrementCounter("redisx_lock_acquisitions_total", { status: "failed" });
350
+ throw new LockAcquisitionError(key, "timeout");
351
+ } catch (error) {
352
+ span?.recordException(error);
353
+ span?.setStatus("ERROR");
354
+ throw error;
355
+ } finally {
356
+ span?.end();
357
+ }
358
+ }
359
+ /**
360
+ * Tries to acquire lock once without retry.
361
+ */
362
+ async tryAcquire(key, options = {}) {
363
+ const fullKey = this.buildKey(key);
364
+ const ttl = this.resolveTtl(options.ttl);
365
+ const token = this.generateToken();
366
+ const acquired = await this.store.acquire(fullKey, token, ttl);
367
+ if (!acquired) {
368
+ this.metrics?.incrementCounter("redisx_lock_acquisitions_total", { status: "failed" });
369
+ return null;
370
+ }
371
+ this.metrics?.incrementCounter("redisx_lock_acquisitions_total", { status: "acquired" });
372
+ this.metrics?.incrementGauge("redisx_locks_active");
373
+ const lock = this.createLock(fullKey, token, ttl, options);
374
+ this.activeLocks.add(lock);
375
+ return lock;
376
+ }
377
+ /**
378
+ * Executes function with automatic lock management.
379
+ */
380
+ async withLock(key, fn, options = {}) {
381
+ const lock = await this.acquire(key, options);
382
+ const holdStart = Date.now();
383
+ try {
384
+ return await fn();
385
+ } finally {
386
+ const holdDuration = (Date.now() - holdStart) / 1e3;
387
+ this.metrics?.observeHistogram("redisx_lock_hold_duration_seconds", holdDuration);
388
+ this.metrics?.decrementGauge("redisx_locks_active");
389
+ await lock.release().catch((error) => {
390
+ this.logger.error(`Lock release failed for ${key}:`, error);
391
+ }).finally(() => {
392
+ this.activeLocks.delete(lock);
393
+ });
394
+ }
395
+ }
396
+ /**
397
+ * Checks if key is locked.
398
+ */
399
+ async isLocked(key) {
400
+ const fullKey = this.buildKey(key);
401
+ return this.store.exists(fullKey);
402
+ }
403
+ /**
404
+ * Force releases lock without ownership check.
405
+ */
406
+ async forceRelease(key) {
407
+ const fullKey = this.buildKey(key);
408
+ return this.store.forceRelease(fullKey);
409
+ }
410
+ /**
411
+ * Creates lock instance with optional auto-renewal.
412
+ */
413
+ createLock(fullKey, token, ttl, options) {
414
+ const lock = new Lock(fullKey, token, ttl, this.store);
415
+ const autoRenewEnabled = options.autoRenew ?? this.config.autoRenew?.enabled ?? true;
416
+ if (autoRenewEnabled) {
417
+ const intervalFraction = this.config.autoRenew?.intervalFraction ?? 0.5;
418
+ const interval = ttl * intervalFraction;
419
+ lock.startAutoRenew(interval);
420
+ }
421
+ return lock;
422
+ }
423
+ /**
424
+ * Builds full lock key with prefix.
425
+ */
426
+ buildKey(key) {
427
+ const prefix = this.config.keyPrefix ?? "_lock:";
428
+ return `${prefix}${key}`;
429
+ }
430
+ /**
431
+ * Generates unique lock token.
432
+ */
433
+ generateToken() {
434
+ return `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
435
+ }
436
+ /**
437
+ * Resolves TTL with defaults and limits.
438
+ */
439
+ resolveTtl(ttl) {
440
+ const resolvedTtl = ttl ?? this.config.defaultTtl ?? 3e4;
441
+ const maxTtl = this.config.maxTtl ?? 3e5;
442
+ return Math.min(resolvedTtl, maxTtl);
443
+ }
444
+ /**
445
+ * Resolves retry configuration.
446
+ */
447
+ resolveRetryConfig(options) {
448
+ return {
449
+ maxRetries: options.retry?.maxRetries ?? this.config.retry?.maxRetries ?? 3,
450
+ initialDelay: options.retry?.initialDelay ?? this.config.retry?.initialDelay ?? 100,
451
+ maxDelay: this.config.retry?.maxDelay ?? 3e3,
452
+ multiplier: this.config.retry?.multiplier ?? 2
453
+ };
454
+ }
455
+ /**
456
+ * Sleeps for specified milliseconds.
457
+ */
458
+ sleep(ms) {
459
+ return new Promise((resolve) => setTimeout(resolve, ms));
460
+ }
461
+ };
462
+ exports.LockService = __decorateClass([
463
+ common.Injectable(),
464
+ __decorateParam(0, common.Inject(LOCKS_PLUGIN_OPTIONS)),
465
+ __decorateParam(1, common.Inject(LOCK_STORE)),
466
+ __decorateParam(2, common.Optional()),
467
+ __decorateParam(2, common.Inject(METRICS_SERVICE)),
468
+ __decorateParam(3, common.Optional()),
469
+ __decorateParam(3, common.Inject(TRACING_SERVICE))
470
+ ], exports.LockService);
471
+
472
+ // src/lock/infrastructure/scripts/lua-scripts.ts
473
+ var RELEASE_LOCK_SCRIPT = `
474
+ if redis.call("get", KEYS[1]) == ARGV[1] then
475
+ return redis.call("del", KEYS[1])
476
+ else
477
+ return 0
478
+ end
479
+ `.trim();
480
+ var EXTEND_LOCK_SCRIPT = `
481
+ if redis.call("get", KEYS[1]) == ARGV[1] then
482
+ return redis.call("pexpire", KEYS[1], ARGV[2])
483
+ else
484
+ return 0
485
+ end
486
+ `.trim();
487
+
488
+ // src/lock/infrastructure/adapters/redis-lock-store.adapter.ts
489
+ var RedisLockStoreAdapter = class {
490
+ constructor(driver) {
491
+ this.driver = driver;
492
+ }
493
+ releaseSha = null;
494
+ extendSha = null;
495
+ /**
496
+ * Lifecycle hook: loads Lua scripts into Redis on initialization.
497
+ */
498
+ async onModuleInit() {
499
+ this.releaseSha = await this.driver.scriptLoad(RELEASE_LOCK_SCRIPT);
500
+ this.extendSha = await this.driver.scriptLoad(EXTEND_LOCK_SCRIPT);
501
+ }
502
+ /**
503
+ * Acquires lock using SET NX PX.
504
+ */
505
+ async acquire(key, token, ttlMs) {
506
+ const result = await this.driver.set(key, token, {
507
+ nx: true,
508
+ // Only set if key doesn't exist
509
+ px: ttlMs
510
+ // TTL in milliseconds
511
+ });
512
+ return result === "OK";
513
+ }
514
+ /**
515
+ * Releases lock if owned by token (Lua script).
516
+ */
517
+ async release(key, token) {
518
+ if (!this.releaseSha) {
519
+ this.releaseSha = await this.driver.scriptLoad(RELEASE_LOCK_SCRIPT);
520
+ }
521
+ const result = await this.driver.evalsha(this.releaseSha, [key], [token]);
522
+ return result === 1;
523
+ }
524
+ /**
525
+ * Extends lock TTL if owned by token (Lua script).
526
+ */
527
+ async extend(key, token, ttlMs) {
528
+ if (!this.extendSha) {
529
+ this.extendSha = await this.driver.scriptLoad(EXTEND_LOCK_SCRIPT);
530
+ }
531
+ const result = await this.driver.evalsha(this.extendSha, [key], [token, ttlMs]);
532
+ return result === 1;
533
+ }
534
+ /**
535
+ * Checks if lock key exists.
536
+ */
537
+ async exists(key) {
538
+ const count = await this.driver.exists(key);
539
+ return count > 0;
540
+ }
541
+ /**
542
+ * Checks if lock is held by specific token.
543
+ */
544
+ async isHeldBy(key, token) {
545
+ const value = await this.driver.get(key);
546
+ return value === token;
547
+ }
548
+ /**
549
+ * Force removes lock without ownership check.
550
+ */
551
+ async forceRelease(key) {
552
+ const count = await this.driver.del(key);
553
+ return count > 0;
554
+ }
555
+ };
556
+ RedisLockStoreAdapter = __decorateClass([
557
+ common.Injectable(),
558
+ __decorateParam(0, common.Inject(core.REDIS_DRIVER))
559
+ ], RedisLockStoreAdapter);
560
+
561
+ // src/locks.plugin.ts
562
+ var DEFAULT_LOCKS_CONFIG = {
563
+ defaultTtl: 3e4,
564
+ maxTtl: 3e5,
565
+ keyPrefix: "_lock:",
566
+ retry: {
567
+ maxRetries: 3,
568
+ initialDelay: 100,
569
+ maxDelay: 3e3,
570
+ multiplier: 2
571
+ },
572
+ autoRenew: {
573
+ enabled: true,
574
+ intervalFraction: 0.5
575
+ }
576
+ };
577
+ var LocksPlugin = class {
578
+ constructor(options = {}) {
579
+ this.options = options;
580
+ }
581
+ name = "locks";
582
+ version = "0.1.0";
583
+ description = "Distributed locking with auto-renewal and retry strategies";
584
+ getProviders() {
585
+ const config = {
586
+ defaultTtl: this.options.defaultTtl ?? DEFAULT_LOCKS_CONFIG.defaultTtl,
587
+ maxTtl: this.options.maxTtl ?? DEFAULT_LOCKS_CONFIG.maxTtl,
588
+ keyPrefix: this.options.keyPrefix ?? DEFAULT_LOCKS_CONFIG.keyPrefix,
589
+ retry: {
590
+ ...DEFAULT_LOCKS_CONFIG.retry,
591
+ ...this.options.retry
592
+ },
593
+ autoRenew: {
594
+ ...DEFAULT_LOCKS_CONFIG.autoRenew,
595
+ ...this.options.autoRenew
596
+ }
597
+ };
598
+ return [
599
+ // Configuration
600
+ {
601
+ provide: LOCKS_PLUGIN_OPTIONS,
602
+ useValue: config
603
+ },
604
+ // Store adapter
605
+ {
606
+ provide: LOCK_STORE,
607
+ useClass: RedisLockStoreAdapter
608
+ },
609
+ // Application service
610
+ {
611
+ provide: LOCK_SERVICE,
612
+ useClass: exports.LockService
613
+ },
614
+ // @WithLock decorator initialization (proxy-based)
615
+ LockDecoratorInitializerService,
616
+ // Reflector is needed for decorator metadata
617
+ core$1.Reflector
618
+ ];
619
+ }
620
+ getExports() {
621
+ return [LOCK_SERVICE];
622
+ }
623
+ };
624
+
625
+ exports.LOCKS_PLUGIN_OPTIONS = LOCKS_PLUGIN_OPTIONS;
626
+ exports.LOCK_SERVICE = LOCK_SERVICE;
627
+ exports.LockAcquisitionError = LockAcquisitionError;
628
+ exports.LockError = LockError;
629
+ exports.LockExpiredError = LockExpiredError;
630
+ exports.LockExtensionError = LockExtensionError;
631
+ exports.LockNotOwnedError = LockNotOwnedError;
632
+ exports.LocksPlugin = LocksPlugin;
633
+ exports.WithLock = WithLock;
634
+ //# sourceMappingURL=index.js.map
635
+ //# sourceMappingURL=index.js.map