@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.
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +635 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +625 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lock/api/decorators/with-lock.decorator.d.ts +105 -0
- package/dist/lock/api/decorators/with-lock.decorator.d.ts.map +1 -0
- package/dist/lock/application/ports/lock-service.port.d.ts +120 -0
- package/dist/lock/application/ports/lock-service.port.d.ts.map +1 -0
- package/dist/lock/application/ports/lock-store.port.d.ts +63 -0
- package/dist/lock/application/ports/lock-store.port.d.ts.map +1 -0
- package/dist/lock/application/services/lock-decorator-initializer.service.d.ts +19 -0
- package/dist/lock/application/services/lock-decorator-initializer.service.d.ts.map +1 -0
- package/dist/lock/application/services/lock.service.d.ts +90 -0
- package/dist/lock/application/services/lock.service.d.ts.map +1 -0
- package/dist/lock/domain/entities/lock.entity.d.ts +130 -0
- package/dist/lock/domain/entities/lock.entity.d.ts.map +1 -0
- package/dist/lock/infrastructure/adapters/redis-lock-store.adapter.d.ts +45 -0
- package/dist/lock/infrastructure/adapters/redis-lock-store.adapter.d.ts.map +1 -0
- package/dist/lock/infrastructure/scripts/lua-scripts.d.ts +24 -0
- package/dist/lock/infrastructure/scripts/lua-scripts.d.ts.map +1 -0
- package/dist/locks.plugin.d.ts +41 -0
- package/dist/locks.plugin.d.ts.map +1 -0
- package/dist/shared/constants/index.d.ts +13 -0
- package/dist/shared/constants/index.d.ts.map +1 -0
- package/dist/shared/errors/index.d.ts +64 -0
- package/dist/shared/errors/index.d.ts.map +1 -0
- package/dist/shared/types/index.d.ts +97 -0
- package/dist/shared/types/index.d.ts.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { ILockStore } from '../../application/ports/lock-store.port';
|
|
2
|
+
/**
|
|
3
|
+
* Represents an acquired distributed lock.
|
|
4
|
+
*/
|
|
5
|
+
export interface ILock {
|
|
6
|
+
/** Lock key */
|
|
7
|
+
readonly key: string;
|
|
8
|
+
/** Unique token identifying lock ownership */
|
|
9
|
+
readonly token: string;
|
|
10
|
+
/** Lock TTL in milliseconds */
|
|
11
|
+
readonly ttl: number;
|
|
12
|
+
/** Timestamp when lock was acquired */
|
|
13
|
+
readonly acquiredAt: Date;
|
|
14
|
+
/** Timestamp when lock will expire */
|
|
15
|
+
readonly expiresAt: Date;
|
|
16
|
+
/** Whether auto-renewal is active */
|
|
17
|
+
readonly isAutoRenewing: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Releases the lock.
|
|
20
|
+
*
|
|
21
|
+
* @throws {LockNotOwnedError} If lock is not owned by this token
|
|
22
|
+
*/
|
|
23
|
+
release(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Extends lock TTL.
|
|
26
|
+
*
|
|
27
|
+
* @param ttl - New TTL in milliseconds
|
|
28
|
+
* @throws {LockNotOwnedError} If lock was already released
|
|
29
|
+
* @throws {LockExtensionError} If extension fails
|
|
30
|
+
*/
|
|
31
|
+
extend(ttl: number): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Checks if lock is still held.
|
|
34
|
+
*
|
|
35
|
+
* @returns True if lock is held by this token
|
|
36
|
+
*/
|
|
37
|
+
isHeld(): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Stops automatic lock renewal.
|
|
40
|
+
*/
|
|
41
|
+
stopAutoRenew(): void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Lock entity implementation.
|
|
45
|
+
*
|
|
46
|
+
* Represents a distributed lock with automatic renewal capability.
|
|
47
|
+
* The lock maintains ownership through a unique token and can be
|
|
48
|
+
* extended or released explicitly.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const lock = new Lock('my-resource', 'token-123', 30000, store);
|
|
53
|
+
*
|
|
54
|
+
* // Start auto-renewal every 15 seconds
|
|
55
|
+
* lock.startAutoRenew(15000);
|
|
56
|
+
*
|
|
57
|
+
* try {
|
|
58
|
+
* // Do work...
|
|
59
|
+
* await performOperation();
|
|
60
|
+
* } finally {
|
|
61
|
+
* await lock.release();
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare class Lock implements ILock {
|
|
66
|
+
private readonly store;
|
|
67
|
+
readonly key: string;
|
|
68
|
+
readonly token: string;
|
|
69
|
+
readonly ttl: number;
|
|
70
|
+
readonly acquiredAt: Date;
|
|
71
|
+
private _expiresAt;
|
|
72
|
+
private autoRenewTimer;
|
|
73
|
+
private released;
|
|
74
|
+
/**
|
|
75
|
+
* Creates a new Lock instance.
|
|
76
|
+
*
|
|
77
|
+
* @param key - Lock key in Redis
|
|
78
|
+
* @param token - Unique ownership token
|
|
79
|
+
* @param ttl - Time-to-live in milliseconds
|
|
80
|
+
* @param store - Lock store for persistence operations
|
|
81
|
+
*/
|
|
82
|
+
constructor(key: string, token: string, ttl: number, store: ILockStore);
|
|
83
|
+
/**
|
|
84
|
+
* Gets the expiration timestamp.
|
|
85
|
+
*/
|
|
86
|
+
get expiresAt(): Date;
|
|
87
|
+
/**
|
|
88
|
+
* Checks if auto-renewal is active.
|
|
89
|
+
*/
|
|
90
|
+
get isAutoRenewing(): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Releases the lock.
|
|
93
|
+
*
|
|
94
|
+
* Stops auto-renewal and removes the lock from Redis.
|
|
95
|
+
* Idempotent - can be called multiple times safely.
|
|
96
|
+
*
|
|
97
|
+
* @throws {LockNotOwnedError} If lock is not owned by this token
|
|
98
|
+
*/
|
|
99
|
+
release(): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Extends the lock TTL.
|
|
102
|
+
*
|
|
103
|
+
* @param ttl - New TTL in milliseconds
|
|
104
|
+
* @throws {LockNotOwnedError} If lock was already released
|
|
105
|
+
* @throws {LockExtensionError} If extension fails (lock expired or not owned)
|
|
106
|
+
*/
|
|
107
|
+
extend(ttl: number): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Checks if lock is still held.
|
|
110
|
+
*
|
|
111
|
+
* @returns False if released, otherwise checks Redis
|
|
112
|
+
*/
|
|
113
|
+
isHeld(): Promise<boolean>;
|
|
114
|
+
/**
|
|
115
|
+
* Starts automatic lock renewal.
|
|
116
|
+
*
|
|
117
|
+
* The lock will be extended at the specified interval
|
|
118
|
+
* until stopAutoRenew() is called or extension fails.
|
|
119
|
+
*
|
|
120
|
+
* @param intervalMs - Renewal interval in milliseconds
|
|
121
|
+
*/
|
|
122
|
+
startAutoRenew(intervalMs: number): void;
|
|
123
|
+
/**
|
|
124
|
+
* Stops automatic lock renewal.
|
|
125
|
+
*
|
|
126
|
+
* Safe to call multiple times.
|
|
127
|
+
*/
|
|
128
|
+
stopAutoRenew(): void;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=lock.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.entity.d.ts","sourceRoot":"","sources":["../../../../src/lock/domain/entities/lock.entity.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,KAAK;IACpB,eAAe;IACf,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,+BAA+B;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB,uCAAuC;IACvC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAE1B,sCAAsC;IACtC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IAEzB,qCAAqC;IACrC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IAEjC;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB;;;;;;OAMG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;;;OAIG;IACH,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3B;;OAEG;IACH,aAAa,IAAI,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,IAAK,YAAW,KAAK;IAsB9B,OAAO,CAAC,QAAQ,CAAC,KAAK;IArBxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAE1B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;;;;OAOG;gBAED,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACM,KAAK,EAAE,UAAU;IASpC;;OAEG;IACH,IAAI,SAAS,IAAI,IAAI,CAEpB;IAED;;OAEG;IACH,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED;;;;;;;OAOG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAe9B;;;;;;OAMG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxC;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAOhC;;;;;;;OAOG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAqBxC;;;;OAIG;IACH,aAAa,IAAI,IAAI;CAMtB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { IRedisDriver } from '@nestjs-redisx/core';
|
|
3
|
+
import { ILockStore } from '../../application/ports/lock-store.port';
|
|
4
|
+
/**
|
|
5
|
+
* Redis-based lock store implementation.
|
|
6
|
+
*
|
|
7
|
+
* Uses atomic Redis operations for lock management:
|
|
8
|
+
* - SET NX PX for acquiring locks
|
|
9
|
+
* - Lua scripts for safe release and extension
|
|
10
|
+
*/
|
|
11
|
+
export declare class RedisLockStoreAdapter implements ILockStore, OnModuleInit {
|
|
12
|
+
private readonly driver;
|
|
13
|
+
private releaseSha;
|
|
14
|
+
private extendSha;
|
|
15
|
+
constructor(driver: IRedisDriver);
|
|
16
|
+
/**
|
|
17
|
+
* Lifecycle hook: loads Lua scripts into Redis on initialization.
|
|
18
|
+
*/
|
|
19
|
+
onModuleInit(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Acquires lock using SET NX PX.
|
|
22
|
+
*/
|
|
23
|
+
acquire(key: string, token: string, ttlMs: number): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Releases lock if owned by token (Lua script).
|
|
26
|
+
*/
|
|
27
|
+
release(key: string, token: string): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Extends lock TTL if owned by token (Lua script).
|
|
30
|
+
*/
|
|
31
|
+
extend(key: string, token: string, ttlMs: number): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Checks if lock key exists.
|
|
34
|
+
*/
|
|
35
|
+
exists(key: string): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Checks if lock is held by specific token.
|
|
38
|
+
*/
|
|
39
|
+
isHeldBy(key: string, token: string): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Force removes lock without ownership check.
|
|
42
|
+
*/
|
|
43
|
+
forceRelease(key: string): Promise<boolean>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=redis-lock-store.adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-lock-store.adapter.d.ts","sourceRoot":"","sources":["../../../../src/lock/infrastructure/adapters/redis-lock-store.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAgB,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,UAAU,EAAE,MAAM,yCAAyC,CAAC;AAGrE;;;;;;GAMG;AACH,qBACa,qBAAsB,YAAW,UAAU,EAAE,YAAY;IAIlC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHzD,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,SAAS,CAAuB;gBAEW,MAAM,EAAE,YAAY;IAEvE;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAMnC;;OAEG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ1E;;OAEG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU3D;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUzE;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3C;;OAEG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5D;;OAEG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAIlD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline Lua scripts for lock operations.
|
|
3
|
+
*
|
|
4
|
+
* Scripts are stored as inline strings to avoid issues with file reading
|
|
5
|
+
* after build (dist directory doesn't contain .lua files).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Release lock if owned by token.
|
|
9
|
+
*
|
|
10
|
+
* KEYS[1] = lock key
|
|
11
|
+
* ARGV[1] = owner token
|
|
12
|
+
* Returns: 1 if released, 0 if not owned or doesn't exist
|
|
13
|
+
*/
|
|
14
|
+
export declare const RELEASE_LOCK_SCRIPT: string;
|
|
15
|
+
/**
|
|
16
|
+
* Extend lock TTL if owned by token.
|
|
17
|
+
*
|
|
18
|
+
* KEYS[1] = lock key
|
|
19
|
+
* ARGV[1] = owner token
|
|
20
|
+
* ARGV[2] = TTL in milliseconds
|
|
21
|
+
* Returns: 1 if extended, 0 if not owned or doesn't exist
|
|
22
|
+
*/
|
|
23
|
+
export declare const EXTEND_LOCK_SCRIPT: string;
|
|
24
|
+
//# sourceMappingURL=lua-scripts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lua-scripts.d.ts","sourceRoot":"","sources":["../../../../src/lock/infrastructure/scripts/lua-scripts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,QAMxB,CAAC;AAET;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,QAMvB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locks plugin for NestJS RedisX.
|
|
3
|
+
* Provides distributed locking with auto-renewal and retry strategies.
|
|
4
|
+
*/
|
|
5
|
+
import { Provider } from '@nestjs/common';
|
|
6
|
+
import { IRedisXPlugin } from '@nestjs-redisx/core';
|
|
7
|
+
import { ILocksPluginOptions } from './shared/types';
|
|
8
|
+
/**
|
|
9
|
+
* Distributed locks plugin for NestJS RedisX.
|
|
10
|
+
*
|
|
11
|
+
* Provides distributed locking with auto-renewal and retry strategies.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Module({
|
|
16
|
+
* imports: [
|
|
17
|
+
* RedisModule.forRoot({
|
|
18
|
+
* clients: { host: 'localhost', port: 6379 },
|
|
19
|
+
* plugins: [
|
|
20
|
+
* new LocksPlugin({
|
|
21
|
+
* defaultTtl: 30000,
|
|
22
|
+
* keyPrefix: '_lock:',
|
|
23
|
+
* autoRenew: { enabled: true },
|
|
24
|
+
* }),
|
|
25
|
+
* ],
|
|
26
|
+
* }),
|
|
27
|
+
* ],
|
|
28
|
+
* })
|
|
29
|
+
* export class AppModule {}
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class LocksPlugin implements IRedisXPlugin {
|
|
33
|
+
private readonly options;
|
|
34
|
+
readonly name = "locks";
|
|
35
|
+
readonly version = "0.1.0";
|
|
36
|
+
readonly description = "Distributed locking with auto-renewal and retry strategies";
|
|
37
|
+
constructor(options?: ILocksPluginOptions);
|
|
38
|
+
getProviders(): Provider[];
|
|
39
|
+
getExports(): Array<string | symbol | Provider>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=locks.plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.plugin.d.ts","sourceRoot":"","sources":["../src/locks.plugin.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAqBrD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,WAAY,YAAW,aAAa;IAKnC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,QAAQ,CAAC,IAAI,WAAW;IACxB,QAAQ,CAAC,OAAO,WAAW;IAC3B,QAAQ,CAAC,WAAW,gEAAgE;gBAEvD,OAAO,GAAE,mBAAwB;IAE9D,YAAY,IAAI,QAAQ,EAAE;IA2C1B,UAAU,IAAI,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;CAGhD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injection token for locks plugin options
|
|
3
|
+
*/
|
|
4
|
+
export declare const LOCKS_PLUGIN_OPTIONS: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* Injection token for lock service
|
|
7
|
+
*/
|
|
8
|
+
export declare const LOCK_SERVICE: unique symbol;
|
|
9
|
+
/**
|
|
10
|
+
* Injection token for lock store
|
|
11
|
+
*/
|
|
12
|
+
export declare const LOCK_STORE: unique symbol;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/constants/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,oBAAoB,eAAqC,CAAC;AAEvE;;GAEG;AACH,eAAO,MAAM,YAAY,eAA6B,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,UAAU,eAA2B,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { RedisXError, ErrorCode } from '@nestjs-redisx/core';
|
|
2
|
+
/**
|
|
3
|
+
* Base error class for all lock-related errors.
|
|
4
|
+
*/
|
|
5
|
+
export declare class LockError extends RedisXError {
|
|
6
|
+
readonly lockKey: string;
|
|
7
|
+
constructor(message: string, code: ErrorCode, lockKey: string, cause?: Error);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Error thrown when lock acquisition fails.
|
|
11
|
+
*/
|
|
12
|
+
export declare class LockAcquisitionError extends LockError {
|
|
13
|
+
readonly reason: 'timeout' | 'held' | 'error';
|
|
14
|
+
constructor(key: string, reason: 'timeout' | 'held' | 'error', cause?: Error);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown when attempting to release or extend a lock not owned by the caller.
|
|
18
|
+
*/
|
|
19
|
+
export declare class LockNotOwnedError extends LockError {
|
|
20
|
+
readonly token: string;
|
|
21
|
+
constructor(key: string, token: string);
|
|
22
|
+
toJSON(): {
|
|
23
|
+
token: string;
|
|
24
|
+
name: string;
|
|
25
|
+
message: string;
|
|
26
|
+
code: string;
|
|
27
|
+
timestamp: string;
|
|
28
|
+
context?: Record<string, unknown>;
|
|
29
|
+
stack?: string;
|
|
30
|
+
cause?: {
|
|
31
|
+
name: string;
|
|
32
|
+
message: string;
|
|
33
|
+
stack?: string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Error thrown when lock extension fails.
|
|
39
|
+
*/
|
|
40
|
+
export declare class LockExtensionError extends LockError {
|
|
41
|
+
readonly token: string;
|
|
42
|
+
constructor(key: string, token: string, cause?: Error);
|
|
43
|
+
toJSON(): {
|
|
44
|
+
token: string;
|
|
45
|
+
name: string;
|
|
46
|
+
message: string;
|
|
47
|
+
code: string;
|
|
48
|
+
timestamp: string;
|
|
49
|
+
context?: Record<string, unknown>;
|
|
50
|
+
stack?: string;
|
|
51
|
+
cause?: {
|
|
52
|
+
name: string;
|
|
53
|
+
message: string;
|
|
54
|
+
stack?: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Error thrown when a lock has expired.
|
|
60
|
+
*/
|
|
61
|
+
export declare class LockExpiredError extends LockError {
|
|
62
|
+
constructor(key: string);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/errors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAE7D;;GAEG;AACH,qBAAa,SAAU,SAAQ,WAAW;aAItB,OAAO,EAAE,MAAM;gBAF/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,EACC,OAAO,EAAE,MAAM,EAC/B,KAAK,CAAC,EAAE,KAAK;CAIhB;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAS;aAG/B,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO;gBADpD,GAAG,EAAE,MAAM,EACK,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,EACpD,KAAK,CAAC,EAAE,KAAK;CAIhB;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,SAAS;aAG5B,KAAK,EAAE,MAAM;gBAD7B,GAAG,EAAE,MAAM,EACK,KAAK,EAAE,MAAM;IAKtB,MAAM;;;;;;;;;;;iBAoCijpH,CAAC;;;CA9BlkpH;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,SAAS;aAG7B,KAAK,EAAE,MAAM;gBAD7B,GAAG,EAAE,MAAM,EACK,KAAK,EAAE,MAAM,EAC7B,KAAK,CAAC,EAAE,KAAK;IAKN,MAAM;;;;;;;;;;;iBAgBijpH,CAAC;;;CAVlkpH;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,SAAS;gBACjC,GAAG,EAAE,MAAM;CAGxB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the Locks plugin
|
|
3
|
+
* @public
|
|
4
|
+
*/
|
|
5
|
+
export interface ILocksPluginOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Default TTL for locks in milliseconds
|
|
8
|
+
* @default 30000
|
|
9
|
+
*/
|
|
10
|
+
defaultTtl?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Maximum TTL for locks in milliseconds
|
|
13
|
+
* @default 300000
|
|
14
|
+
*/
|
|
15
|
+
maxTtl?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Key prefix for lock keys
|
|
18
|
+
* @default '_lock:'
|
|
19
|
+
*/
|
|
20
|
+
keyPrefix?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Retry configuration for lock acquisition
|
|
23
|
+
*/
|
|
24
|
+
retry?: {
|
|
25
|
+
/**
|
|
26
|
+
* Maximum number of retries
|
|
27
|
+
* @default 3
|
|
28
|
+
*/
|
|
29
|
+
maxRetries?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Initial delay between retries in milliseconds
|
|
32
|
+
* @default 100
|
|
33
|
+
*/
|
|
34
|
+
initialDelay?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Maximum delay between retries in milliseconds
|
|
37
|
+
* @default 3000
|
|
38
|
+
*/
|
|
39
|
+
maxDelay?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Multiplier for exponential backoff
|
|
42
|
+
* @default 2
|
|
43
|
+
*/
|
|
44
|
+
multiplier?: number;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Auto-renewal configuration
|
|
48
|
+
*/
|
|
49
|
+
autoRenew?: {
|
|
50
|
+
/**
|
|
51
|
+
* Enable auto-renewal
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
enabled?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Fraction of TTL to use as renewal interval
|
|
57
|
+
* @default 0.5
|
|
58
|
+
*/
|
|
59
|
+
intervalFraction?: number;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Options for lock acquisition
|
|
64
|
+
*/
|
|
65
|
+
export interface ILockOptions {
|
|
66
|
+
/**
|
|
67
|
+
* Lock TTL in milliseconds
|
|
68
|
+
*/
|
|
69
|
+
ttl?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Maximum time to wait for lock acquisition in milliseconds
|
|
72
|
+
*/
|
|
73
|
+
waitTimeout?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Enable auto-renewal for this lock
|
|
76
|
+
*/
|
|
77
|
+
autoRenew?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Retry configuration for this specific lock
|
|
80
|
+
*/
|
|
81
|
+
retry?: {
|
|
82
|
+
/**
|
|
83
|
+
* Maximum number of retries
|
|
84
|
+
*/
|
|
85
|
+
maxRetries?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Initial delay between retries in milliseconds
|
|
88
|
+
*/
|
|
89
|
+
initialDelay?: number;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Alias for plugin options (for consistency with plugin naming)
|
|
94
|
+
* @public
|
|
95
|
+
*/
|
|
96
|
+
export type LocksPluginOptions = ILocksPluginOptions;
|
|
97
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,KAAK,CAAC,EAAE;QACN;;;WAGG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;QAEpB;;;WAGG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;QAEtB;;;WAGG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC;QAElB;;;WAGG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IAEF;;OAEG;IACH,SAAS,CAAC,EAAE;QACV;;;WAGG;QACH,OAAO,CAAC,EAAE,OAAO,CAAC;QAElB;;;WAGG;QACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,KAAK,CAAC,EAAE;QACN;;WAEG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;QAEpB;;WAEG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nestjs-redisx/locks",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Distributed locks plugin for NestJS RedisX with auto-renewal and retry strategies",
|
|
5
|
+
"author": "NestJS RedisX Team",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"test": "SKIP_INTEGRATION=true vitest run",
|
|
25
|
+
"test:watch": "SKIP_INTEGRATION=true vitest",
|
|
26
|
+
"test:cov": "SKIP_INTEGRATION=true vitest run --coverage",
|
|
27
|
+
"test:integration": "vitest run test/integration/locks.integration.spec.ts",
|
|
28
|
+
"test:integration:watch": "vitest watch test/integration/locks.integration.spec.ts",
|
|
29
|
+
"docker:up": "docker-compose up -d",
|
|
30
|
+
"docker:down": "docker-compose down",
|
|
31
|
+
"docker:logs": "docker-compose logs -f redis",
|
|
32
|
+
"test:docker": "npm run docker:up && sleep 3 && npm run test:integration; TEST_EXIT=$?; npm run docker:down; exit $TEST_EXIT",
|
|
33
|
+
"test:all": "npm test && npm run test:integration",
|
|
34
|
+
"lint": "eslint \"{src,test}/**/*.ts\"",
|
|
35
|
+
"format": "prettier --write \"{src,test}/**/*.ts\""
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"nestjs",
|
|
39
|
+
"redis",
|
|
40
|
+
"locks",
|
|
41
|
+
"distributed",
|
|
42
|
+
"mutex",
|
|
43
|
+
"semaphore",
|
|
44
|
+
"auto-renewal",
|
|
45
|
+
"retry"
|
|
46
|
+
],
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/nestjs-redisx/nestjs-redisx.git",
|
|
50
|
+
"directory": "packages/locks"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://nestjs-redisx.dev/en/reference/locks/",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/nestjs-redisx/nestjs-redisx/issues"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@nestjs-redisx/core": "^1.0.0",
|
|
58
|
+
"@nestjs/common": "^10.0.0",
|
|
59
|
+
"@nestjs/core": "^10.0.0",
|
|
60
|
+
"reflect-metadata": "^0.2.0",
|
|
61
|
+
"rxjs": "^7.8.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@nestjs/testing": "^10.0.0",
|
|
65
|
+
"@types/node": "^20.0.0",
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
67
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
68
|
+
"eslint": "^8.0.0",
|
|
69
|
+
"prettier": "^3.0.0",
|
|
70
|
+
"tsup": "^8.0.0",
|
|
71
|
+
"typescript": "^5.3.0",
|
|
72
|
+
"vitest": "^1.6.0",
|
|
73
|
+
"@vitest/coverage-v8": "^1.6.0"
|
|
74
|
+
},
|
|
75
|
+
"publishConfig": {
|
|
76
|
+
"access": "public"
|
|
77
|
+
}
|
|
78
|
+
}
|