@hazeljs/distributed-lock 0.4.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.
@@ -0,0 +1,3 @@
1
+ import 'reflect-metadata';
2
+ import { DistributedLockOptions } from './interfaces';
3
+ export declare function DistributedLock(options: DistributedLockOptions): (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DistributedLock = DistributedLock;
4
+ require("reflect-metadata");
5
+ const lock_manager_1 = require("./lock-manager");
6
+ function DistributedLock(options) {
7
+ return function (target, propertyKey, descriptor) {
8
+ const originalMethod = descriptor.value;
9
+ descriptor.value = async function (...args) {
10
+ const lockManager = lock_manager_1.LockManager.getInstance();
11
+ let resolvedKey = options.key;
12
+ const paramNames = getParamNames(originalMethod);
13
+ paramNames.forEach((name, index) => {
14
+ const value = args[index];
15
+ const placeholder = `{${name}}`;
16
+ if (resolvedKey.includes(placeholder)) {
17
+ resolvedKey = resolvedKey.replace(placeholder, String(value));
18
+ }
19
+ });
20
+ paramNames.forEach((name, index) => {
21
+ const obj = args[index];
22
+ if (obj && typeof obj === 'object') {
23
+ for (const [prop, val] of Object.entries(obj)) {
24
+ const placeholder = `{${name}.${prop}}`;
25
+ if (resolvedKey.includes(placeholder)) {
26
+ resolvedKey = resolvedKey.replace(placeholder, String(val));
27
+ }
28
+ }
29
+ }
30
+ });
31
+ const lock = await lockManager.acquire({
32
+ ...options,
33
+ key: resolvedKey,
34
+ });
35
+ if (!lock) {
36
+ throw new Error(`Could not acquire distributed lock for key: ${resolvedKey}`);
37
+ }
38
+ try {
39
+ return await originalMethod.apply(this, args);
40
+ }
41
+ finally {
42
+ await lock.release();
43
+ }
44
+ };
45
+ return descriptor;
46
+ };
47
+ }
48
+ function getParamNames(fn) {
49
+ const fnStr = fn.toString();
50
+ const result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(/([^\s,]+)/g);
51
+ return result === null ? [] : result.map((p) => p.split('=')[0]);
52
+ }
53
+ //# sourceMappingURL=distributed-lock.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distributed-lock.decorator.js","sourceRoot":"","sources":["../src/distributed-lock.decorator.ts"],"names":[],"mappings":";;AASA,0CAuDC;AAhED,4BAA0B;AAE1B,iDAA6C;AAO7C,SAAgB,eAAe,CAAC,OAA+B;IAC7D,OAAO,UACL,MAAc,EACd,WAAmB,EACnB,UAA8B;QAE9B,MAAM,cAAc,GAAG,UAAU,CAAC,KAAK,CAAC;QAExC,UAAU,CAAC,KAAK,GAAG,KAAK,WAA0B,GAAG,IAAe;YAClE,MAAM,WAAW,GAAG,0BAAW,CAAC,WAAW,EAAE,CAAC;YAG9C,IAAI,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;YAC9B,MAAM,UAAU,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;YAGjD,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC1B,MAAM,WAAW,GAAG,IAAI,IAAI,GAAG,CAAC;gBAChC,IAAI,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;oBACtC,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAChE,CAAC;YACH,CAAC,CAAC,CAAC;YAGH,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBACjC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;gBACxB,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;oBACnC,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;wBAC9C,MAAM,WAAW,GAAG,IAAI,IAAI,IAAI,IAAI,GAAG,CAAC;wBACxC,IAAI,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;4BACtC,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;wBAC9D,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC;gBACrC,GAAG,OAAO;gBACV,GAAG,EAAE,WAAW;aACjB,CAAC,CAAC;YAEH,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,+CAA+C,WAAW,EAAE,CAAC,CAAC;YAChF,CAAC;YAED,IAAI,CAAC;gBACH,OAAO,MAAM,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAChD,CAAC;oBAAS,CAAC;gBACT,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACvB,CAAC;QACH,CAAC,CAAC;QAEF,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC;AACJ,CAAC;AAKD,SAAS,aAAa,CAAC,EAAmC;IACxD,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3F,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1 @@
1
+ import 'reflect-metadata';
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ require("reflect-metadata");
13
+ const distributed_lock_decorator_1 = require("./distributed-lock.decorator");
14
+ const lock_manager_1 = require("./lock-manager");
15
+ class TestService {
16
+ async doSomething(id) {
17
+ return `done-${id}`;
18
+ }
19
+ async doNested(data) {
20
+ return `done-${data.userId}`;
21
+ }
22
+ async doNoParams() {
23
+ return 'done';
24
+ }
25
+ }
26
+ __decorate([
27
+ (0, distributed_lock_decorator_1.DistributedLock)({ key: 'test-lock-{id}', ttl: 5000 }),
28
+ __metadata("design:type", Function),
29
+ __metadata("design:paramtypes", [Number]),
30
+ __metadata("design:returntype", Promise)
31
+ ], TestService.prototype, "doSomething", null);
32
+ __decorate([
33
+ (0, distributed_lock_decorator_1.DistributedLock)({ key: 'nested-lock-{data.userId}', ttl: 5000 }),
34
+ __metadata("design:type", Function),
35
+ __metadata("design:paramtypes", [Object]),
36
+ __metadata("design:returntype", Promise)
37
+ ], TestService.prototype, "doNested", null);
38
+ __decorate([
39
+ (0, distributed_lock_decorator_1.DistributedLock)({ key: 'no-params-lock', ttl: 5000 }),
40
+ __metadata("design:type", Function),
41
+ __metadata("design:paramtypes", []),
42
+ __metadata("design:returntype", Promise)
43
+ ], TestService.prototype, "doNoParams", null);
44
+ describe('DistributedLock Decorator', () => {
45
+ let lockManager;
46
+ let service;
47
+ beforeEach(() => {
48
+ lockManager = lock_manager_1.LockManager.getInstance();
49
+ service = new TestService();
50
+ });
51
+ afterEach(async () => {
52
+ await lockManager.close();
53
+ });
54
+ it('should acquire and release lock for simple param', async () => {
55
+ const acquireSpy = jest.spyOn(lockManager, 'acquire');
56
+ const result = await service.doSomething(123);
57
+ expect(result).toBe('done-123');
58
+ expect(acquireSpy).toHaveBeenCalledWith(expect.objectContaining({
59
+ key: 'test-lock-123',
60
+ }));
61
+ });
62
+ it('should acquire and release lock for nested param', async () => {
63
+ const acquireSpy = jest.spyOn(lockManager, 'acquire');
64
+ const result = await service.doNested({ userId: 'u1', name: 'John' });
65
+ expect(result).toBe('done-u1');
66
+ expect(acquireSpy).toHaveBeenCalledWith(expect.objectContaining({
67
+ key: 'nested-lock-u1',
68
+ }));
69
+ });
70
+ it('should block concurrent calls for same key', async () => {
71
+ await lockManager.acquire({ key: 'no-params-lock', ttl: 5000 });
72
+ await expect(service.doNoParams()).rejects.toThrow('Could not acquire distributed lock for key: no-params-lock');
73
+ });
74
+ it('should handle complex key resolution from object', async () => {
75
+ const acquireSpy = jest.spyOn(lockManager, 'acquire');
76
+ class TestClass {
77
+ async execute(user, session) {
78
+ return 'executed';
79
+ }
80
+ }
81
+ __decorate([
82
+ (0, distributed_lock_decorator_1.DistributedLock)({ key: 'user-{user.id}-session-{session.token}' }),
83
+ __metadata("design:type", Function),
84
+ __metadata("design:paramtypes", [Object, Object]),
85
+ __metadata("design:returntype", Promise)
86
+ ], TestClass.prototype, "execute", null);
87
+ const instance = new TestClass();
88
+ const result = await instance.execute({ id: '1' }, { token: 'abc' });
89
+ expect(result).toBe('executed');
90
+ expect(acquireSpy).toHaveBeenCalledWith(expect.objectContaining({
91
+ key: 'user-1-session-abc',
92
+ }));
93
+ });
94
+ it('should handle missing keys gracefully', async () => {
95
+ const acquireSpy = jest.spyOn(lockManager, 'acquire');
96
+ class TestClass {
97
+ async execute(data) {
98
+ return 'executed';
99
+ }
100
+ }
101
+ __decorate([
102
+ (0, distributed_lock_decorator_1.DistributedLock)({ key: 'test-{missing}' }),
103
+ __metadata("design:type", Function),
104
+ __metadata("design:paramtypes", [Object]),
105
+ __metadata("design:returntype", Promise)
106
+ ], TestClass.prototype, "execute", null);
107
+ const instance = new TestClass();
108
+ await instance.execute({ id: '1' });
109
+ expect(acquireSpy).toHaveBeenCalledWith(expect.objectContaining({
110
+ key: 'test-{missing}',
111
+ }));
112
+ });
113
+ });
114
+ //# sourceMappingURL=distributed-lock.decorator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distributed-lock.decorator.test.js","sourceRoot":"","sources":["../src/distributed-lock.decorator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from './interfaces';
2
+ export * from './lock-manager';
3
+ export * from './distributed-lock.decorator';
4
+ export * from './backends';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./interfaces"), exports);
18
+ __exportStar(require("./lock-manager"), exports);
19
+ __exportStar(require("./distributed-lock.decorator"), exports);
20
+ __exportStar(require("./backends"), exports);
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,iDAA+B;AAC/B,+DAA6C;AAC7C,6CAA2B"}
@@ -0,0 +1,20 @@
1
+ export interface LockOptions {
2
+ key: string;
3
+ ttl?: number;
4
+ retry?: {
5
+ attempts: number;
6
+ delay: number;
7
+ };
8
+ backend?: 'redis' | 'memory' | string;
9
+ }
10
+ export interface ILock {
11
+ release(): Promise<void>;
12
+ extend(ttl: number): Promise<boolean>;
13
+ }
14
+ export interface ILockBackend {
15
+ acquire(options: LockOptions): Promise<ILock | null>;
16
+ release(key: string, identifier: string): Promise<void>;
17
+ extend(key: string, identifier: string, ttl: number): Promise<boolean>;
18
+ close?(): Promise<void>;
19
+ }
20
+ export type DistributedLockOptions = LockOptions;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import { ILock, ILockBackend, LockOptions } from './interfaces';
2
+ export declare class LockManager {
3
+ private static instance;
4
+ private readonly backends;
5
+ private defaultBackendName;
6
+ private constructor();
7
+ static getInstance(): LockManager;
8
+ registerBackend(name: string, backend: ILockBackend): void;
9
+ setDefaultBackend(name: string): void;
10
+ acquire(options: LockOptions): Promise<ILock | null>;
11
+ setupRedis(redisOptions: unknown, name?: string): void;
12
+ close(): Promise<void>;
13
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LockManager = void 0;
4
+ const memory_backend_1 = require("./backends/memory-backend");
5
+ const redis_backend_1 = require("./backends/redis-backend");
6
+ class LockManager {
7
+ constructor() {
8
+ this.backends = new Map();
9
+ this.defaultBackendName = 'memory';
10
+ this.registerBackend('memory', new memory_backend_1.MemoryLockBackend());
11
+ }
12
+ static getInstance() {
13
+ if (!LockManager.instance) {
14
+ LockManager.instance = new LockManager();
15
+ }
16
+ return LockManager.instance;
17
+ }
18
+ registerBackend(name, backend) {
19
+ this.backends.set(name, backend);
20
+ }
21
+ setDefaultBackend(name) {
22
+ if (!this.backends.has(name)) {
23
+ throw new Error(`Backend '${name}' is not registered.`);
24
+ }
25
+ this.defaultBackendName = name;
26
+ }
27
+ async acquire(options) {
28
+ const backendName = options.backend || this.defaultBackendName;
29
+ const backend = this.backends.get(backendName);
30
+ if (!backend) {
31
+ throw new Error(`Lock backend '${backendName}' not found`);
32
+ }
33
+ return backend.acquire(options);
34
+ }
35
+ setupRedis(redisOptions, name = 'redis') {
36
+ const backend = new redis_backend_1.RedisLockBackend(redisOptions);
37
+ this.registerBackend(name, backend);
38
+ if (this.defaultBackendName === 'memory') {
39
+ this.defaultBackendName = name;
40
+ }
41
+ }
42
+ async close() {
43
+ for (const backend of this.backends.values()) {
44
+ if (backend.close) {
45
+ await backend.close();
46
+ }
47
+ }
48
+ this.backends.clear();
49
+ this.registerBackend('memory', new memory_backend_1.MemoryLockBackend());
50
+ }
51
+ }
52
+ exports.LockManager = LockManager;
53
+ //# sourceMappingURL=lock-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock-manager.js","sourceRoot":"","sources":["../src/lock-manager.ts"],"names":[],"mappings":";;;AACA,8DAA8D;AAC9D,4DAA4D;AAE5D,MAAa,WAAW;IAKtB;QAHiB,aAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;QACpD,uBAAkB,GAAW,QAAQ,CAAC;QAG5C,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,IAAI,kCAAiB,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;YAC1B,WAAW,CAAC,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC;QAC3C,CAAC;QACD,OAAO,WAAW,CAAC,QAAQ,CAAC;IAC9B,CAAC;IAED,eAAe,CAAC,IAAY,EAAE,OAAqB;QACjD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,iBAAiB,CAAC,IAAY;QAC5B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,sBAAsB,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAoB;QAChC,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,kBAAkB,CAAC;QAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAE/C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,iBAAiB,WAAW,aAAa,CAAC,CAAC;QAC7D,CAAC;QAED,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAGD,UAAU,CAAC,YAAqB,EAAE,OAAe,OAAO;QACtD,MAAM,OAAO,GAAG,IAAI,gCAAgB,CAAC,YAAY,CAAC,CAAC;QACnD,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,kBAAkB,KAAK,QAAQ,EAAE,CAAC;YACzC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QACjC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;gBAClB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,IAAI,kCAAiB,EAAE,CAAC,CAAC;IAC1D,CAAC;CACF;AAxDD,kCAwDC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const lock_manager_1 = require("./lock-manager");
4
+ describe('LockManager (Memory)', () => {
5
+ let lockManager;
6
+ beforeEach(() => {
7
+ lockManager = lock_manager_1.LockManager.getInstance();
8
+ });
9
+ afterEach(async () => {
10
+ await lockManager.close();
11
+ });
12
+ it('should acquire and release a lock', async () => {
13
+ const lock = await lockManager.acquire({ key: 'test-lock', ttl: 1000 });
14
+ expect(lock).toBeDefined();
15
+ expect(lock).not.toBeNull();
16
+ if (lock) {
17
+ const secondLock = await lockManager.acquire({ key: 'test-lock', ttl: 1000 });
18
+ expect(secondLock).toBeNull();
19
+ await lock.release();
20
+ const thirdLock = await lockManager.acquire({ key: 'test-lock', ttl: 1000 });
21
+ expect(thirdLock).not.toBeNull();
22
+ await thirdLock?.release();
23
+ }
24
+ });
25
+ it('should retry to acquire a lock', async () => {
26
+ const lock = await lockManager.acquire({ key: 'retry-lock', ttl: 500 });
27
+ expect(lock).not.toBeNull();
28
+ setTimeout(() => lock?.release(), 200);
29
+ const secondLock = await lockManager.acquire({
30
+ key: 'retry-lock',
31
+ ttl: 500,
32
+ retry: { attempts: 3, delay: 100 },
33
+ });
34
+ expect(secondLock).not.toBeNull();
35
+ await secondLock?.release();
36
+ });
37
+ it('should fail after max retries', async () => {
38
+ const lock = await lockManager.acquire({ key: 'fail-lock', ttl: 1000 });
39
+ expect(lock).not.toBeNull();
40
+ const startTime = Date.now();
41
+ const secondLock = await lockManager.acquire({
42
+ key: 'fail-lock',
43
+ ttl: 500,
44
+ retry: { attempts: 2, delay: 100 },
45
+ });
46
+ const duration = Date.now() - startTime;
47
+ expect(secondLock).toBeNull();
48
+ expect(duration).toBeGreaterThanOrEqual(190);
49
+ await lock?.release();
50
+ });
51
+ it('should register a new backend', async () => {
52
+ const mockBackend = {
53
+ acquire: jest.fn().mockResolvedValue({ release: jest.fn() }),
54
+ release: jest.fn(),
55
+ extend: jest.fn(),
56
+ };
57
+ lockManager.registerBackend('mock', mockBackend);
58
+ await lockManager.acquire({ key: 'test', backend: 'mock' });
59
+ expect(mockBackend.acquire).toHaveBeenCalled();
60
+ });
61
+ it('should throw error if backend is not found', async () => {
62
+ await expect(lockManager.acquire({ key: 'test', backend: 'non-existent' })).rejects.toThrow("Lock backend 'non-existent' not found");
63
+ });
64
+ it('should change default backend', async () => {
65
+ const mockBackend = {
66
+ acquire: jest.fn().mockResolvedValue({ release: jest.fn() }),
67
+ release: jest.fn(),
68
+ extend: jest.fn(),
69
+ };
70
+ lockManager.registerBackend('mock-default', mockBackend);
71
+ lockManager.setDefaultBackend('mock-default');
72
+ await lockManager.acquire({ key: 'test' });
73
+ expect(mockBackend.acquire).toHaveBeenCalled();
74
+ lockManager.setDefaultBackend('memory');
75
+ });
76
+ it('should throw error if setting non-existent default backend', () => {
77
+ expect(() => lockManager.setDefaultBackend('ghost')).toThrow("Backend 'ghost' is not registered.");
78
+ });
79
+ it('should setup redis backend', () => {
80
+ lockManager.setupRedis('redis://localhost:6379');
81
+ expect(lockManager.backends.has('redis')).toBe(true);
82
+ });
83
+ });
84
+ //# sourceMappingURL=lock-manager.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock-manager.test.js","sourceRoot":"","sources":["../src/lock-manager.test.ts"],"names":[],"mappings":";;AAAA,iDAA6C;AAE7C,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,IAAI,WAAwB,CAAC;IAE7B,UAAU,CAAC,GAAG,EAAE;QACd,WAAW,GAAG,0BAAW,CAAC,WAAW,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,MAAM,WAAW,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAE5B,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9E,MAAM,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC;YAE9B,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAErB,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7E,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YACjC,MAAM,SAAS,EAAE,OAAO,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAG5B,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;QAEvC,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC;YAC3C,GAAG,EAAE,YAAY;YACjB,GAAG,EAAE,GAAG;YACR,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE;SACnC,CAAC,CAAC;QAEH,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAClC,MAAM,UAAU,EAAE,OAAO,EAAE,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC;YAC3C,GAAG,EAAE,WAAW;YAChB,GAAG,EAAE,GAAG;YACR,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE;SACnC,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,MAAM,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,QAAQ,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;QAE7C,MAAM,IAAI,EAAE,OAAO,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,WAAW,GAAG;YAClB,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;YAC5D,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE;YAClB,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE;SAClB,CAAC;QACF,WAAW,CAAC,eAAe,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAEjD,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5D,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,gBAAgB,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CACzF,uCAAuC,CACxC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,WAAW,GAAG;YAClB,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;YAC5D,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE;YAClB,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE;SAClB,CAAC;QACF,WAAW,CAAC,eAAe,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;QACzD,WAAW,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;QAE9C,MAAM,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,gBAAgB,EAAE,CAAC;QAG/C,WAAW,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAC1D,oCAAoC,CACrC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QAEpC,WAAW,CAAC,UAAU,CAAC,wBAAwB,CAAC,CAAC;QAEjD,MAAM,CAAE,WAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}