@lakutata/mutex 0.0.0-stage → 3.0.0-beta.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 +23 -0
- package/README.md +212 -2
- package/dist/cjs/exports/Mutex.d.ts +18 -0
- package/dist/cjs/exports/Mutex.js +37 -0
- package/dist/cjs/mutex/Mutex.d.ts +265 -0
- package/dist/cjs/mutex/Mutex.js +403 -0
- package/dist/cjs/mutex/MutexLock.d.ts +91 -0
- package/dist/cjs/mutex/MutexLock.js +113 -0
- package/dist/cjs/mutex/Synchronized.d.ts +90 -0
- package/dist/cjs/mutex/Synchronized.js +93 -0
- package/dist/cjs/mutex/drivers/FileMutexDriver.d.ts +115 -0
- package/dist/cjs/mutex/drivers/FileMutexDriver.js +230 -0
- package/dist/cjs/mutex/drivers/MemoryMutexDriver.d.ts +60 -0
- package/dist/cjs/mutex/drivers/MemoryMutexDriver.js +95 -0
- package/dist/cjs/mutex/drivers/MutexDriver.d.ts +82 -0
- package/dist/cjs/mutex/drivers/MutexDriver.js +38 -0
- package/dist/cjs/mutex/drivers/MysqlMutexDriver.d.ts +135 -0
- package/dist/cjs/mutex/drivers/MysqlMutexDriver.js +244 -0
- package/dist/cjs/mutex/drivers/PostgresMutexDriver.d.ts +136 -0
- package/dist/cjs/mutex/drivers/PostgresMutexDriver.js +230 -0
- package/dist/cjs/mutex/drivers/RedisMutexDriver.d.ts +129 -0
- package/dist/cjs/mutex/drivers/RedisMutexDriver.js +180 -0
- package/dist/cjs/mutex/exceptions/MutexDriverException.d.ts +12 -0
- package/dist/cjs/mutex/exceptions/MutexDriverException.js +19 -0
- package/dist/cjs/mutex/exceptions/MutexDriverNotFoundException.d.ts +12 -0
- package/dist/cjs/mutex/exceptions/MutexDriverNotFoundException.js +19 -0
- package/dist/cjs/mutex/exceptions/MutexLostException.d.ts +13 -0
- package/dist/cjs/mutex/exceptions/MutexLostException.js +20 -0
- package/dist/cjs/mutex/exceptions/MutexTimeoutException.d.ts +16 -0
- package/dist/cjs/mutex/exceptions/MutexTimeoutException.js +23 -0
- package/dist/cjs/mutex/lib/LoadDriver.d.ts +6 -0
- package/dist/cjs/mutex/lib/LoadDriver.js +64 -0
- package/dist/cjs/mutex/lib/Names.d.ts +14 -0
- package/dist/cjs/mutex/lib/Names.js +26 -0
- package/dist/cjs/mutex/options/FileMutexOptions.d.ts +33 -0
- package/dist/cjs/mutex/options/FileMutexOptions.js +45 -0
- package/dist/cjs/mutex/options/MemoryMutexOptions.d.ts +27 -0
- package/dist/cjs/mutex/options/MemoryMutexOptions.js +41 -0
- package/dist/cjs/mutex/options/MysqlMutexOptions.d.ts +70 -0
- package/dist/cjs/mutex/options/MysqlMutexOptions.js +77 -0
- package/dist/cjs/mutex/options/PostgresMutexOptions.d.ts +83 -0
- package/dist/cjs/mutex/options/PostgresMutexOptions.js +89 -0
- package/dist/cjs/mutex/options/RedisMutexOptions.d.ts +71 -0
- package/dist/cjs/mutex/options/RedisMutexOptions.js +77 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/exports/Mutex.js +18 -0
- package/dist/esm/mutex/Mutex.js +397 -0
- package/dist/esm/mutex/MutexLock.js +109 -0
- package/dist/esm/mutex/Synchronized.js +89 -0
- package/dist/esm/mutex/drivers/FileMutexDriver.js +223 -0
- package/dist/esm/mutex/drivers/MemoryMutexDriver.js +91 -0
- package/dist/esm/mutex/drivers/MutexDriver.js +34 -0
- package/dist/esm/mutex/drivers/MysqlMutexDriver.js +240 -0
- package/dist/esm/mutex/drivers/PostgresMutexDriver.js +226 -0
- package/dist/esm/mutex/drivers/RedisMutexDriver.js +176 -0
- package/dist/esm/mutex/exceptions/MutexDriverException.js +15 -0
- package/dist/esm/mutex/exceptions/MutexDriverNotFoundException.js +15 -0
- package/dist/esm/mutex/exceptions/MutexLostException.js +16 -0
- package/dist/esm/mutex/exceptions/MutexTimeoutException.js +19 -0
- package/dist/esm/mutex/lib/LoadDriver.js +28 -0
- package/dist/esm/mutex/lib/Names.js +21 -0
- package/dist/esm/mutex/options/FileMutexOptions.js +41 -0
- package/dist/esm/mutex/options/MemoryMutexOptions.js +37 -0
- package/dist/esm/mutex/options/MysqlMutexOptions.js +73 -0
- package/dist/esm/mutex/options/PostgresMutexOptions.js +85 -0
- package/dist/esm/mutex/options/RedisMutexOptions.js +73 -0
- package/dist/types/exports/Mutex.d.ts +18 -0
- package/dist/types/mutex/Mutex.d.ts +265 -0
- package/dist/types/mutex/MutexLock.d.ts +91 -0
- package/dist/types/mutex/Synchronized.d.ts +90 -0
- package/dist/types/mutex/drivers/FileMutexDriver.d.ts +115 -0
- package/dist/types/mutex/drivers/MemoryMutexDriver.d.ts +60 -0
- package/dist/types/mutex/drivers/MutexDriver.d.ts +82 -0
- package/dist/types/mutex/drivers/MysqlMutexDriver.d.ts +135 -0
- package/dist/types/mutex/drivers/PostgresMutexDriver.d.ts +136 -0
- package/dist/types/mutex/drivers/RedisMutexDriver.d.ts +129 -0
- package/dist/types/mutex/exceptions/MutexDriverException.d.ts +12 -0
- package/dist/types/mutex/exceptions/MutexDriverNotFoundException.d.ts +12 -0
- package/dist/types/mutex/exceptions/MutexLostException.d.ts +13 -0
- package/dist/types/mutex/exceptions/MutexTimeoutException.d.ts +16 -0
- package/dist/types/mutex/lib/LoadDriver.d.ts +6 -0
- package/dist/types/mutex/lib/Names.d.ts +14 -0
- package/dist/types/mutex/options/FileMutexOptions.d.ts +33 -0
- package/dist/types/mutex/options/MemoryMutexOptions.d.ts +27 -0
- package/dist/types/mutex/options/MysqlMutexOptions.d.ts +70 -0
- package/dist/types/mutex/options/PostgresMutexOptions.d.ts +83 -0
- package/dist/types/mutex/options/RedisMutexOptions.d.ts +71 -0
- package/package.json +57 -4
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
exports.PostgresMutexOptions = void 0;
|
|
13
|
+
const core_1 = require("@lakutata/core");
|
|
14
|
+
const dto_1 = require("@lakutata/core/decorator/dto");
|
|
15
|
+
/**
|
|
16
|
+
* The options of the PostgreSQL driver ({@link PostgresMutexDriver}): the locks are shared by every instance connected
|
|
17
|
+
* to the same database. It needs the optional peer dependency `pg` (`npm install pg`, 8): without it, the first
|
|
18
|
+
* acquisition fails with {@link MutexDriverNotFoundException}. The driver connects at the first acquisition, not when
|
|
19
|
+
* the component is created.
|
|
20
|
+
*
|
|
21
|
+
* In the `'advisory'` mode (the default), the locks are session advisory locks of one connection: they do not expire,
|
|
22
|
+
* PostgreSQL releases them when the connection closes (a crashed process frees them at once), and they are all lost
|
|
23
|
+
* when it closes or fails. They have no fencing token, and do not work through a pooler in transaction mode
|
|
24
|
+
* (PgBouncer). In the `'table'` mode, the locks are rows of a table created at the first use: they expire after their
|
|
25
|
+
* `ttl` unless renewed, work through the poolers, and carry fencing tokens.
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import {Application} from 'lakutata'
|
|
29
|
+
* import {Mutex} from 'lakutata/com/mutex'
|
|
30
|
+
*
|
|
31
|
+
* Application.run(() => ({
|
|
32
|
+
* id: 'postgres-mutex.app',
|
|
33
|
+
* name: 'PostgresMutex',
|
|
34
|
+
* components: {
|
|
35
|
+
* mutex: {
|
|
36
|
+
* class: Mutex,
|
|
37
|
+
* driver: {
|
|
38
|
+
* type: 'postgres',
|
|
39
|
+
* host: 'db',
|
|
40
|
+
* database: 'app',
|
|
41
|
+
* username: 'app',
|
|
42
|
+
* password: process.env.DB_PASSWORD ?? '',
|
|
43
|
+
* //Through PgBouncer: rows of a table, with fencing tokens
|
|
44
|
+
* mode: 'table'
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
* }))
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
class PostgresMutexOptions extends core_1.DTO {
|
|
52
|
+
}
|
|
53
|
+
exports.PostgresMutexOptions = PostgresMutexOptions;
|
|
54
|
+
__decorate([
|
|
55
|
+
(0, dto_1.Expect)(core_1.DTO.String().valid('postgres').required()),
|
|
56
|
+
__metadata("design:type", String)
|
|
57
|
+
], PostgresMutexOptions.prototype, "type", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
(0, dto_1.Expect)(core_1.DTO.String().required()),
|
|
60
|
+
__metadata("design:type", String)
|
|
61
|
+
], PostgresMutexOptions.prototype, "host", void 0);
|
|
62
|
+
__decorate([
|
|
63
|
+
(0, dto_1.Expect)(core_1.DTO.Number().port().optional().default(5432)),
|
|
64
|
+
__metadata("design:type", Number)
|
|
65
|
+
], PostgresMutexOptions.prototype, "port", void 0);
|
|
66
|
+
__decorate([
|
|
67
|
+
(0, dto_1.Expect)(core_1.DTO.String().required()),
|
|
68
|
+
__metadata("design:type", String)
|
|
69
|
+
], PostgresMutexOptions.prototype, "database", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
(0, dto_1.Expect)(core_1.DTO.String().required()),
|
|
72
|
+
__metadata("design:type", String)
|
|
73
|
+
], PostgresMutexOptions.prototype, "username", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
(0, dto_1.Expect)(core_1.DTO.String().allow('').required()),
|
|
76
|
+
__metadata("design:type", String)
|
|
77
|
+
], PostgresMutexOptions.prototype, "password", void 0);
|
|
78
|
+
__decorate([
|
|
79
|
+
(0, dto_1.Expect)(core_1.DTO.Boolean().optional()),
|
|
80
|
+
__metadata("design:type", Boolean)
|
|
81
|
+
], PostgresMutexOptions.prototype, "ssl", void 0);
|
|
82
|
+
__decorate([
|
|
83
|
+
(0, dto_1.Expect)(core_1.DTO.String().valid('advisory', 'table').optional().default('advisory')),
|
|
84
|
+
__metadata("design:type", String)
|
|
85
|
+
], PostgresMutexOptions.prototype, "mode", void 0);
|
|
86
|
+
__decorate([
|
|
87
|
+
(0, dto_1.Expect)(core_1.DTO.String().pattern(/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)?$/).optional().default('lakutata_mutex')),
|
|
88
|
+
__metadata("design:type", String)
|
|
89
|
+
], PostgresMutexOptions.prototype, "table", void 0);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { DTO } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* The options of the Redis driver ({@link RedisMutexDriver}): the locks are Redis keys shared by every instance
|
|
4
|
+
* connected to the same server or cluster. It needs the optional peer dependency `redis` (`npm install redis`, 4.6 or
|
|
5
|
+
* 5): without it, the first acquisition fails with {@link MutexDriverNotFoundException}. The keys expire after their
|
|
6
|
+
* `ttl` unless renewed, and each acquisition gets a fencing token. Configure the server with
|
|
7
|
+
* `maxmemory-policy noeviction`: an evicted key is a lock lost.
|
|
8
|
+
*
|
|
9
|
+
* The client connects at the first acquisition, not when the component is created. While the server cannot be reached,
|
|
10
|
+
* the calls wait for it instead of failing.
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import {Application} from 'lakutata'
|
|
14
|
+
* import {Mutex} from 'lakutata/com/mutex'
|
|
15
|
+
*
|
|
16
|
+
* Application.run(() => ({
|
|
17
|
+
* id: 'redis-mutex.app',
|
|
18
|
+
* name: 'RedisMutex',
|
|
19
|
+
* components: {
|
|
20
|
+
* mutex: {
|
|
21
|
+
* class: Mutex,
|
|
22
|
+
* driver: {type: 'redis', host: '127.0.0.1', port: 6379, password: process.env.REDIS_PASSWORD},
|
|
23
|
+
* //The applications sharing this Redis server keep their locks apart
|
|
24
|
+
* prefix: 'billing:mutex:'
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* }))
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare class RedisMutexOptions extends DTO {
|
|
31
|
+
/**
|
|
32
|
+
* The driver type: `'redis'`.
|
|
33
|
+
*/
|
|
34
|
+
type: 'redis';
|
|
35
|
+
/**
|
|
36
|
+
* The server URL (`redis://user:password@host:6379/0`, `rediss://` for TLS). When set, `host`, `port` and `tls` are
|
|
37
|
+
* ignored; `username`, `password` and `database` apply when the URL does not give them.
|
|
38
|
+
* @default none
|
|
39
|
+
*/
|
|
40
|
+
url?: string;
|
|
41
|
+
/**
|
|
42
|
+
* The host name or IP address of the server (without a `url`).
|
|
43
|
+
* @default '127.0.0.1'
|
|
44
|
+
*/
|
|
45
|
+
host?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The port of the server (without a `url`).
|
|
48
|
+
* @default 6379
|
|
49
|
+
*/
|
|
50
|
+
port?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The index of the Redis database.
|
|
53
|
+
* @default 0
|
|
54
|
+
*/
|
|
55
|
+
database?: number;
|
|
56
|
+
/**
|
|
57
|
+
* The user name (Redis 6 ACL).
|
|
58
|
+
* @default none (the default user)
|
|
59
|
+
*/
|
|
60
|
+
username?: string;
|
|
61
|
+
/**
|
|
62
|
+
* The password.
|
|
63
|
+
* @default none
|
|
64
|
+
*/
|
|
65
|
+
password?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Whether to connect with TLS (without a `url`).
|
|
68
|
+
* @default false
|
|
69
|
+
*/
|
|
70
|
+
tls?: boolean;
|
|
71
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
exports.RedisMutexOptions = void 0;
|
|
13
|
+
const core_1 = require("@lakutata/core");
|
|
14
|
+
const dto_1 = require("@lakutata/core/decorator/dto");
|
|
15
|
+
/**
|
|
16
|
+
* The options of the Redis driver ({@link RedisMutexDriver}): the locks are Redis keys shared by every instance
|
|
17
|
+
* connected to the same server or cluster. It needs the optional peer dependency `redis` (`npm install redis`, 4.6 or
|
|
18
|
+
* 5): without it, the first acquisition fails with {@link MutexDriverNotFoundException}. The keys expire after their
|
|
19
|
+
* `ttl` unless renewed, and each acquisition gets a fencing token. Configure the server with
|
|
20
|
+
* `maxmemory-policy noeviction`: an evicted key is a lock lost.
|
|
21
|
+
*
|
|
22
|
+
* The client connects at the first acquisition, not when the component is created. While the server cannot be reached,
|
|
23
|
+
* the calls wait for it instead of failing.
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import {Application} from 'lakutata'
|
|
27
|
+
* import {Mutex} from 'lakutata/com/mutex'
|
|
28
|
+
*
|
|
29
|
+
* Application.run(() => ({
|
|
30
|
+
* id: 'redis-mutex.app',
|
|
31
|
+
* name: 'RedisMutex',
|
|
32
|
+
* components: {
|
|
33
|
+
* mutex: {
|
|
34
|
+
* class: Mutex,
|
|
35
|
+
* driver: {type: 'redis', host: '127.0.0.1', port: 6379, password: process.env.REDIS_PASSWORD},
|
|
36
|
+
* //The applications sharing this Redis server keep their locks apart
|
|
37
|
+
* prefix: 'billing:mutex:'
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* }))
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
class RedisMutexOptions extends core_1.DTO {
|
|
44
|
+
}
|
|
45
|
+
exports.RedisMutexOptions = RedisMutexOptions;
|
|
46
|
+
__decorate([
|
|
47
|
+
(0, dto_1.Expect)(core_1.DTO.String().valid('redis').required()),
|
|
48
|
+
__metadata("design:type", String)
|
|
49
|
+
], RedisMutexOptions.prototype, "type", void 0);
|
|
50
|
+
__decorate([
|
|
51
|
+
(0, dto_1.Expect)(core_1.DTO.String().optional()),
|
|
52
|
+
__metadata("design:type", String)
|
|
53
|
+
], RedisMutexOptions.prototype, "url", void 0);
|
|
54
|
+
__decorate([
|
|
55
|
+
(0, dto_1.Expect)(core_1.DTO.String().optional()),
|
|
56
|
+
__metadata("design:type", String)
|
|
57
|
+
], RedisMutexOptions.prototype, "host", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
(0, dto_1.Expect)(core_1.DTO.Number().port().optional()),
|
|
60
|
+
__metadata("design:type", Number)
|
|
61
|
+
], RedisMutexOptions.prototype, "port", void 0);
|
|
62
|
+
__decorate([
|
|
63
|
+
(0, dto_1.Expect)(core_1.DTO.Number().integer().min(0).optional()),
|
|
64
|
+
__metadata("design:type", Number)
|
|
65
|
+
], RedisMutexOptions.prototype, "database", void 0);
|
|
66
|
+
__decorate([
|
|
67
|
+
(0, dto_1.Expect)(core_1.DTO.String().optional()),
|
|
68
|
+
__metadata("design:type", String)
|
|
69
|
+
], RedisMutexOptions.prototype, "username", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
(0, dto_1.Expect)(core_1.DTO.String().optional()),
|
|
72
|
+
__metadata("design:type", String)
|
|
73
|
+
], RedisMutexOptions.prototype, "password", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
(0, dto_1.Expect)(core_1.DTO.Boolean().optional()),
|
|
76
|
+
__metadata("design:type", Boolean)
|
|
77
|
+
], RedisMutexOptions.prototype, "tls", void 0);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from '../mutex/Mutex.js';
|
|
2
|
+
export * from '../mutex/MutexLock.js';
|
|
3
|
+
export * from '../mutex/drivers/MutexDriver.js';
|
|
4
|
+
export * from '../mutex/drivers/MemoryMutexDriver.js';
|
|
5
|
+
export * from '../mutex/drivers/FileMutexDriver.js';
|
|
6
|
+
export * from '../mutex/drivers/RedisMutexDriver.js';
|
|
7
|
+
export * from '../mutex/drivers/PostgresMutexDriver.js';
|
|
8
|
+
export * from '../mutex/drivers/MysqlMutexDriver.js';
|
|
9
|
+
export * from '../mutex/options/MemoryMutexOptions.js';
|
|
10
|
+
export * from '../mutex/options/FileMutexOptions.js';
|
|
11
|
+
export * from '../mutex/options/RedisMutexOptions.js';
|
|
12
|
+
export * from '../mutex/options/PostgresMutexOptions.js';
|
|
13
|
+
export * from '../mutex/options/MysqlMutexOptions.js';
|
|
14
|
+
export * from '../mutex/exceptions/MutexTimeoutException.js';
|
|
15
|
+
export * from '../mutex/exceptions/MutexLostException.js';
|
|
16
|
+
export * from '../mutex/exceptions/MutexDriverException.js';
|
|
17
|
+
export * from '../mutex/exceptions/MutexDriverNotFoundException.js';
|
|
18
|
+
export * from '../mutex/Synchronized.js';
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
13
|
+
import { Component, DTO } from '@lakutata/core';
|
|
14
|
+
import { Configurable, Singleton } from '@lakutata/core/decorator/di';
|
|
15
|
+
import { MutexLock } from './MutexLock.js';
|
|
16
|
+
import { MemoryMutexDriver } from './drivers/MemoryMutexDriver.js';
|
|
17
|
+
import { FileMutexDriver } from './drivers/FileMutexDriver.js';
|
|
18
|
+
import { RedisMutexDriver } from './drivers/RedisMutexDriver.js';
|
|
19
|
+
import { PostgresMutexDriver } from './drivers/PostgresMutexDriver.js';
|
|
20
|
+
import { MysqlMutexDriver } from './drivers/MysqlMutexDriver.js';
|
|
21
|
+
import { MemoryMutexOptions } from './options/MemoryMutexOptions.js';
|
|
22
|
+
import { FileMutexOptions } from './options/FileMutexOptions.js';
|
|
23
|
+
import { RedisMutexOptions } from './options/RedisMutexOptions.js';
|
|
24
|
+
import { PostgresMutexOptions } from './options/PostgresMutexOptions.js';
|
|
25
|
+
import { MysqlMutexOptions } from './options/MysqlMutexOptions.js';
|
|
26
|
+
import { MutexTimeoutException } from './exceptions/MutexTimeoutException.js';
|
|
27
|
+
import { MutexLostException } from './exceptions/MutexLostException.js';
|
|
28
|
+
import { MutexDriverException } from './exceptions/MutexDriverException.js';
|
|
29
|
+
/**
|
|
30
|
+
* The time an attempt sent to the store is given to answer, past the deadline of the wait too: a timeout of 0 tries
|
|
31
|
+
* once (a round trip of the store is not cut short), and a store which does not answer still ends the wait
|
|
32
|
+
*/
|
|
33
|
+
const ATTEMPT_GRACE = 1000;
|
|
34
|
+
/**
|
|
35
|
+
* The lock component: named locks giving a work to one holder at a time, between the instances and the processes of an
|
|
36
|
+
* application as its driver shares them (see {@link MutexDriverOptions}). Register it in the `components` of an
|
|
37
|
+
* application (`{class: Mutex, driver: {...}}`, named `mutex` for `@Synchronized` and the `exclusive` schedules) and
|
|
38
|
+
* get it with `getObject()` or `@Inject`; it is a singleton. Without a `driver`, the locks are in memory.
|
|
39
|
+
*
|
|
40
|
+
* A lock is acquired with a random token: only its holder ({@link MutexLock}) releases or renews it. The expiring
|
|
41
|
+
* drivers (memory, file, Redis, the `table` mode of PostgreSQL and MySQL) hold it for its `ttl` and the component renews
|
|
42
|
+
* it every third of that time, so a crashed holder keeps it at most one `ttl`. The advisory locks of PostgreSQL and
|
|
43
|
+
* MySQL do not expire: the database releases them when the component's connection closes.
|
|
44
|
+
*
|
|
45
|
+
* A lock is lost when a renewal finds it expired or taken, when a renewal fails (the store unreachable), or when the
|
|
46
|
+
* connection holding an advisory lock closes: its {@link MutexLock.signal} is then aborted with a
|
|
47
|
+
* {@link MutexLostException}, and the work it protects should stop. The locks are not reentrant: acquiring a lock this
|
|
48
|
+
* component already holds waits for its release like any other holder.
|
|
49
|
+
*
|
|
50
|
+
* The driver connects at its first use, not when the component is created: a store which cannot be reached, or whose
|
|
51
|
+
* client package is missing, fails the first acquisition. The component releases the locks it holds and closes the
|
|
52
|
+
* driver when it is destroyed (the application stopping).
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* import {Application} from 'lakutata'
|
|
56
|
+
* import {Mutex, type MutexLock} from 'lakutata/com/mutex'
|
|
57
|
+
*
|
|
58
|
+
* Application
|
|
59
|
+
* .run(() => ({
|
|
60
|
+
* id: 'mutex.app',
|
|
61
|
+
* name: 'Mutex',
|
|
62
|
+
* components: {
|
|
63
|
+
* //The locks shared by every instance connected to this Redis server
|
|
64
|
+
* mutex: {class: Mutex, driver: {type: 'redis', url: 'redis://127.0.0.1:6379'}, ttl: 30000, timeout: 10000}
|
|
65
|
+
* }
|
|
66
|
+
* }))
|
|
67
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
68
|
+
* const mutex: Mutex = await app.getObject('mutex')
|
|
69
|
+
* //Acquired before the function, released after it, even when it throws
|
|
70
|
+
* const total: number = await mutex.synchronized('report:daily', async (lock: MutexLock): Promise<number> => {
|
|
71
|
+
* lock.signal.throwIfAborted()
|
|
72
|
+
* return 42
|
|
73
|
+
* })
|
|
74
|
+
* console.log(total)
|
|
75
|
+
* })
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
let Mutex = class Mutex extends Component {
|
|
79
|
+
#driver;
|
|
80
|
+
#held = new Map();
|
|
81
|
+
/**
|
|
82
|
+
* Create the driver chosen by the `driver` option; it connects at its first use.
|
|
83
|
+
* @throws {Error} When the `table` option of a PostgreSQL or MySQL driver is not a valid table name.
|
|
84
|
+
* @protected
|
|
85
|
+
*/
|
|
86
|
+
async init() {
|
|
87
|
+
const options = this.driver;
|
|
88
|
+
switch (options.type) {
|
|
89
|
+
case 'file':
|
|
90
|
+
this.#driver = new FileMutexDriver(options.directory ?? path.join(tmpdir(), 'lakutata-mutex'), this.prefix);
|
|
91
|
+
break;
|
|
92
|
+
case 'redis':
|
|
93
|
+
this.#driver = new RedisMutexDriver(options, this.prefix);
|
|
94
|
+
break;
|
|
95
|
+
case 'postgres':
|
|
96
|
+
this.#driver = new PostgresMutexDriver(options, this.prefix, options.mode, options.table);
|
|
97
|
+
break;
|
|
98
|
+
case 'mysql':
|
|
99
|
+
this.#driver = new MysqlMutexDriver(options, this.prefix, options.mode, options.table);
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
this.#driver = new MemoryMutexDriver(this.prefix);
|
|
103
|
+
}
|
|
104
|
+
//The locks of a connection closed
|
|
105
|
+
this.#driver.onLost = (names) => {
|
|
106
|
+
for (const lock of [...this.#held.keys()])
|
|
107
|
+
if (names.includes(lock.name))
|
|
108
|
+
this.lost(lock, 'The connection holding the lock is closed');
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Release the locks this component holds (their failures ignored), then close the driver's connections.
|
|
113
|
+
* @protected
|
|
114
|
+
*/
|
|
115
|
+
async destroy() {
|
|
116
|
+
await Promise.allSettled([...this.#held.keys()].map((lock) => lock.release()));
|
|
117
|
+
await this.#driver?.close();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Mark a lock as lost: stop its renewal, forget it, and abort its signal with a {@link MutexLostException}. The lock
|
|
121
|
+
* is not released in the store: it stays there until it expires (or its connection closes).
|
|
122
|
+
* @param lock The lock lost.
|
|
123
|
+
* @param message Why it is lost, added to the exception's message.
|
|
124
|
+
* @protected
|
|
125
|
+
*/
|
|
126
|
+
lost(lock, message) {
|
|
127
|
+
const held = this.#held.get(lock);
|
|
128
|
+
clearTimeout(held?.timer);
|
|
129
|
+
this.#held.delete(lock);
|
|
130
|
+
lock.lose(new MutexLostException(`The lock ${lock.name} is lost: ${message}`));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Call the driver, turning its errors into a {@link MutexDriverException} (a {@link MutexDriverNotFoundException} is
|
|
134
|
+
* rethrown as it is).
|
|
135
|
+
* @param call The driver call.
|
|
136
|
+
* @returns What the driver call returns.
|
|
137
|
+
* @throws {MutexDriverException} When the driver call fails.
|
|
138
|
+
* @throws {MutexDriverNotFoundException} When the driver's client package is not installed.
|
|
139
|
+
* @protected
|
|
140
|
+
*/
|
|
141
|
+
async call(call) {
|
|
142
|
+
try {
|
|
143
|
+
return await call();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error instanceof MutexDriverException || error?.errno === 'E_MUTEX_DRIVER_NOT_FOUND')
|
|
147
|
+
throw error;
|
|
148
|
+
throw new MutexDriverException(`The driver ${this.driver.type} of the locks failed: ${error?.message ?? error}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Try to acquire a lock once, without waiting. The lock is renewed automatically while it is held (expiring
|
|
153
|
+
* drivers), until it is released or lost.
|
|
154
|
+
* @param name The lock name, a non-empty string; the component's `prefix` is added to it in the store.
|
|
155
|
+
* @param options `ttl`: how long the lock stays held without a renewal, in milliseconds (the component's `ttl` by
|
|
156
|
+
* default).
|
|
157
|
+
* @returns The lock, or `undefined` when another holder has it (this component included: the locks are not
|
|
158
|
+
* reentrant).
|
|
159
|
+
* @throws {TypeError} When the name is not a non-empty string.
|
|
160
|
+
* @throws {MutexDriverException} When the store fails (unreachable, a failed query).
|
|
161
|
+
* @throws {MutexDriverNotFoundException} When the driver's client package (`redis`, `pg`, `mysql2`) is not installed.
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* import type {Mutex, MutexLock} from 'lakutata/com/mutex'
|
|
165
|
+
*
|
|
166
|
+
* declare const mutex: Mutex
|
|
167
|
+
*
|
|
168
|
+
* //A cleanup run by whichever instance gets there first, skipped by the others
|
|
169
|
+
* const lock: MutexLock | undefined = await mutex.tryAcquire('cleanup')
|
|
170
|
+
* if (lock) {
|
|
171
|
+
* try {
|
|
172
|
+
* console.log('cleaning up')
|
|
173
|
+
* } finally {
|
|
174
|
+
* await lock.release()
|
|
175
|
+
* }
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
async tryAcquire(name, options = {}) {
|
|
180
|
+
if (typeof name !== 'string' || !name)
|
|
181
|
+
throw new TypeError('The name of a lock is a string which is not empty');
|
|
182
|
+
const ttl = options.ttl ?? this.ttl;
|
|
183
|
+
const startedAt = Date.now();
|
|
184
|
+
const acquired = await this.call(() => this.#driver.acquire(name, ttl));
|
|
185
|
+
if (!acquired)
|
|
186
|
+
return undefined;
|
|
187
|
+
const lock = new MutexLock(name, acquired.token, acquired.fence, {
|
|
188
|
+
release: async (target) => {
|
|
189
|
+
const held = this.#held.get(target);
|
|
190
|
+
clearTimeout(held?.timer);
|
|
191
|
+
this.#held.delete(target);
|
|
192
|
+
return await this.call(() => this.#driver.release(name, acquired));
|
|
193
|
+
},
|
|
194
|
+
extend: async (target, next) => {
|
|
195
|
+
const held = this.#held.get(target);
|
|
196
|
+
if (!held)
|
|
197
|
+
return false;
|
|
198
|
+
if (next !== undefined)
|
|
199
|
+
held.ttl = next;
|
|
200
|
+
const renewedAt = Date.now();
|
|
201
|
+
const extended = await this.call(() => this.#driver.extend(name, acquired, held.ttl));
|
|
202
|
+
if (!extended) {
|
|
203
|
+
this.lost(target, 'it expired before its renewal');
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
held.expiresAt = renewedAt + held.ttl;
|
|
207
|
+
//The next renewal follows its new time to live
|
|
208
|
+
this.renew(target);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
this.#held.set(lock, { ttl: ttl, lock: acquired, expiresAt: startedAt + ttl });
|
|
213
|
+
this.renew(lock);
|
|
214
|
+
return lock;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Plan the next renewal of a lock of an expiring driver: a third of its time to live (renewed twice before it
|
|
218
|
+
* expires). A renewal failing (the store unreachable) is tried again until the lock expires, then the lock is lost.
|
|
219
|
+
* @param lock The lock held by the component; a lock released or lost is not renewed.
|
|
220
|
+
* @protected
|
|
221
|
+
*/
|
|
222
|
+
renew(lock) {
|
|
223
|
+
const held = this.#held.get(lock);
|
|
224
|
+
if (!held || !this.#driver.expiring)
|
|
225
|
+
return;
|
|
226
|
+
clearTimeout(held.timer);
|
|
227
|
+
held.timer = setTimeout(() => {
|
|
228
|
+
lock.extend().catch((error) => {
|
|
229
|
+
if (!this.#held.has(lock))
|
|
230
|
+
return;
|
|
231
|
+
if (Date.now() >= held.expiresAt)
|
|
232
|
+
return this.lost(lock, error.message);
|
|
233
|
+
//Tried again before it expires
|
|
234
|
+
this.renew(lock);
|
|
235
|
+
});
|
|
236
|
+
}, Math.max(10, Math.floor(held.ttl / 3)));
|
|
237
|
+
held.timer.unref();
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* An attempt of {@link Mutex.acquire}, bounded by its deadline but given at least a second to answer (the round trip
|
|
241
|
+
* of a store is not cut short, a timeout of 0 tries once): an attempt hanging (the store unreachable) fails then, the
|
|
242
|
+
* lock it acquires later released.
|
|
243
|
+
* @param name The lock name.
|
|
244
|
+
* @param options The options of the acquisition (its `ttl`).
|
|
245
|
+
* @param deadline The time the wait ends, in milliseconds since the epoch.
|
|
246
|
+
* @param timeout The timeout of the wait, in milliseconds, for the message of its exception.
|
|
247
|
+
* @returns The lock acquired, or `undefined` when another holder has it.
|
|
248
|
+
* @throws {MutexTimeoutException} When the attempt does not answer before the deadline and its second of grace.
|
|
249
|
+
* @protected
|
|
250
|
+
*/
|
|
251
|
+
async attempt(name, options, deadline, timeout) {
|
|
252
|
+
const attempt = this.tryAcquire(name, options);
|
|
253
|
+
let timer;
|
|
254
|
+
//Given the time left, and at least ATTEMPT_GRACE: the deadline ends the wait, not an attempt answering
|
|
255
|
+
const expired = new Promise((resolve) => {
|
|
256
|
+
timer = setTimeout(() => resolve('expired'), Math.max(deadline - Date.now(), ATTEMPT_GRACE));
|
|
257
|
+
});
|
|
258
|
+
try {
|
|
259
|
+
const result = await Promise.race([attempt, expired]);
|
|
260
|
+
if (result !== 'expired')
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
clearTimeout(timer);
|
|
265
|
+
}
|
|
266
|
+
//The store did not answer: a lock it acquires later is released
|
|
267
|
+
attempt.then((late) => late?.release(), () => undefined);
|
|
268
|
+
throw new MutexTimeoutException(`The lock ${name} is not acquired in ${timeout} ms`);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Acquire a lock, waiting up to the timeout for its holder to release it (or for it to expire). The store is polled:
|
|
272
|
+
* the first retry comes after `retryDelay` milliseconds, the delay doubling up to one second. The lock is renewed
|
|
273
|
+
* automatically while it is held, until it is released or lost; release it with {@link MutexLock.release}, or
|
|
274
|
+
* declare it with `await using`.
|
|
275
|
+
* @param name The lock name, a non-empty string; the component's `prefix` is added to it in the store.
|
|
276
|
+
* @param options The lock's `ttl`, the `timeout` of the wait and an abort `signal` (the component's options by
|
|
277
|
+
* default).
|
|
278
|
+
* @returns The lock acquired.
|
|
279
|
+
* @throws {MutexTimeoutException} When the lock is not acquired before the timeout (a `timeout` of `0` after one
|
|
280
|
+
* attempt).
|
|
281
|
+
* @throws {TypeError} When the name is not a non-empty string.
|
|
282
|
+
* @throws {MutexDriverException} When the store fails; the wait stops at the first failure.
|
|
283
|
+
* @throws {MutexDriverNotFoundException} When the driver's client package is not installed.
|
|
284
|
+
* @throws The signal's reason (an `AbortError` `DOMException` by default) when the signal is aborted before the lock
|
|
285
|
+
* is acquired.
|
|
286
|
+
* @example
|
|
287
|
+
* ```typescript
|
|
288
|
+
* import type {Mutex, MutexLock} from 'lakutata/com/mutex'
|
|
289
|
+
*
|
|
290
|
+
* declare const mutex: Mutex
|
|
291
|
+
*
|
|
292
|
+
* //Waits up to 3 seconds, then rejects with MutexTimeoutException
|
|
293
|
+
* const lock: MutexLock = await mutex.acquire('order:42', {timeout: 3000})
|
|
294
|
+
* try {
|
|
295
|
+
* console.log(`order 42 is ours, fencing token ${lock.fence}`)
|
|
296
|
+
* } finally {
|
|
297
|
+
* await lock.release()
|
|
298
|
+
* }
|
|
299
|
+
*
|
|
300
|
+
* //Released at the end of the block, even when it throws
|
|
301
|
+
* {
|
|
302
|
+
* await using scoped: MutexLock = await mutex.acquire('order:43', {signal: AbortSignal.timeout(5000)})
|
|
303
|
+
* console.log(scoped.name)
|
|
304
|
+
* }
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
async acquire(name, options = {}) {
|
|
308
|
+
const timeout = options.timeout ?? this.timeout;
|
|
309
|
+
const deadline = Date.now() + timeout;
|
|
310
|
+
let delay = this.retryDelay;
|
|
311
|
+
for (;;) {
|
|
312
|
+
options.signal?.throwIfAborted();
|
|
313
|
+
const lock = await this.attempt(name, options, deadline, timeout);
|
|
314
|
+
if (lock)
|
|
315
|
+
return lock;
|
|
316
|
+
const remaining = deadline - Date.now();
|
|
317
|
+
if (remaining <= 0)
|
|
318
|
+
throw new MutexTimeoutException(`The lock ${name} is not acquired in ${timeout} ms`);
|
|
319
|
+
//Backed off up to a second, spread between the waiters
|
|
320
|
+
try {
|
|
321
|
+
await sleep(Math.min(remaining, delay * (0.5 + Math.random() / 2)), undefined, { signal: options.signal });
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
//The reason of its abort
|
|
325
|
+
throw options.signal?.aborted ? options.signal.reason : error;
|
|
326
|
+
}
|
|
327
|
+
delay = Math.min(delay * 2, 1000);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Run a function holding a lock: acquire it as {@link Mutex.acquire} does, run the function, then release the lock
|
|
332
|
+
* whether the function returned or threw (a failure of the release is ignored). When the lock is lost while the
|
|
333
|
+
* function runs, the function is not interrupted: its `lock.signal` is aborted, and it should check it.
|
|
334
|
+
* @param name The lock name, a non-empty string; the component's `prefix` is added to it in the store.
|
|
335
|
+
* @param fn The function to run, given the lock.
|
|
336
|
+
* @param options The lock's `ttl`, the `timeout` of the wait and an abort `signal` (the component's options by
|
|
337
|
+
* default).
|
|
338
|
+
* @returns What the function returns (awaited).
|
|
339
|
+
* @throws {MutexTimeoutException} When the lock is not acquired before the timeout; the function does not run.
|
|
340
|
+
* @throws {MutexDriverException} When the store fails while acquiring the lock.
|
|
341
|
+
* @throws {MutexDriverNotFoundException} When the driver's client package is not installed.
|
|
342
|
+
* @throws The function's error, after the lock is released.
|
|
343
|
+
* @example
|
|
344
|
+
* ```typescript
|
|
345
|
+
* import type {Mutex, MutexLock} from 'lakutata/com/mutex'
|
|
346
|
+
*
|
|
347
|
+
* declare const mutex: Mutex
|
|
348
|
+
* declare function chargeOrder(id: number, signal: AbortSignal): Promise<string>
|
|
349
|
+
*
|
|
350
|
+
* //One payment of order 42 at a time, in every instance sharing the store
|
|
351
|
+
* const receipt: string = await mutex.synchronized('order:42', (lock: MutexLock): Promise<string> => {
|
|
352
|
+
* //Aborted with a MutexLostException when the lock is lost
|
|
353
|
+
* return chargeOrder(42, lock.signal)
|
|
354
|
+
* }, {timeout: 5000})
|
|
355
|
+
* console.log(receipt)
|
|
356
|
+
* ```
|
|
357
|
+
*/
|
|
358
|
+
async synchronized(name, fn, options = {}) {
|
|
359
|
+
const lock = await this.acquire(name, options);
|
|
360
|
+
try {
|
|
361
|
+
return await fn(lock);
|
|
362
|
+
}
|
|
363
|
+
finally {
|
|
364
|
+
await lock.release().catch(() => undefined);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The locks this component holds now (not released, not lost), in their acquisition order; a new array on each read.
|
|
369
|
+
*/
|
|
370
|
+
get heldLocks() {
|
|
371
|
+
return [...this.#held.keys()];
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
__decorate([
|
|
375
|
+
Configurable(DTO.Alternatives(MemoryMutexOptions.required(), FileMutexOptions.required(), RedisMutexOptions.required(), PostgresMutexOptions.required(), MysqlMutexOptions.required()).optional().default({ type: 'memory' })),
|
|
376
|
+
__metadata("design:type", Object)
|
|
377
|
+
], Mutex.prototype, "driver", void 0);
|
|
378
|
+
__decorate([
|
|
379
|
+
Configurable(DTO.String().allow('').optional().default('lakutata:mutex:')),
|
|
380
|
+
__metadata("design:type", String)
|
|
381
|
+
], Mutex.prototype, "prefix", void 0);
|
|
382
|
+
__decorate([
|
|
383
|
+
Configurable(DTO.Number().integer().positive().optional().default(30000)),
|
|
384
|
+
__metadata("design:type", Number)
|
|
385
|
+
], Mutex.prototype, "ttl", void 0);
|
|
386
|
+
__decorate([
|
|
387
|
+
Configurable(DTO.Number().min(0).optional().default(10000)),
|
|
388
|
+
__metadata("design:type", Number)
|
|
389
|
+
], Mutex.prototype, "timeout", void 0);
|
|
390
|
+
__decorate([
|
|
391
|
+
Configurable(DTO.Number().integer().positive().optional().default(50)),
|
|
392
|
+
__metadata("design:type", Number)
|
|
393
|
+
], Mutex.prototype, "retryDelay", void 0);
|
|
394
|
+
Mutex = __decorate([
|
|
395
|
+
Singleton()
|
|
396
|
+
], Mutex);
|
|
397
|
+
export { Mutex };
|