@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,16 @@
|
|
|
1
|
+
import { Exception } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* The abort reason of {@link MutexLock.signal} when a lock is lost: a renewal found it expired or taken by another
|
|
4
|
+
* holder, a renewal failed, or the connection holding it closed. It is not thrown by the component: the work holding the
|
|
5
|
+
* lock meets it through the signal (`signal.throwIfAborted()`, an `AbortSignal`-aware API). Its `errno` is
|
|
6
|
+
* `'E_MUTEX_LOST'`.
|
|
7
|
+
*/
|
|
8
|
+
export class MutexLostException extends Exception {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
/**
|
|
12
|
+
* `'E_MUTEX_LOST'`.
|
|
13
|
+
*/
|
|
14
|
+
this.errno = 'E_MUTEX_LOST';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Exception } from '@lakutata/core';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by {@link Mutex.acquire} and {@link Mutex.synchronized} when the lock is not acquired before the timeout
|
|
4
|
+
* (another holder kept it), and by a {@link Synchronized} action whose lock is held by another request (HTTP 409
|
|
5
|
+
* Conflict). Its `errno` is `'E_MUTEX_TIMEOUT'`.
|
|
6
|
+
*/
|
|
7
|
+
export class MutexTimeoutException extends Exception {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
/**
|
|
11
|
+
* `'E_MUTEX_TIMEOUT'`.
|
|
12
|
+
*/
|
|
13
|
+
this.errno = 'E_MUTEX_TIMEOUT';
|
|
14
|
+
/**
|
|
15
|
+
* The HTTP status the transports answer with: 409 (Conflict).
|
|
16
|
+
*/
|
|
17
|
+
this.statusCode = 409;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { MutexDriverNotFoundException } from '../exceptions/MutexDriverNotFoundException.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether an error reports the package itself as missing
|
|
4
|
+
* @param error
|
|
5
|
+
* @param packageName
|
|
6
|
+
*/
|
|
7
|
+
function isPackageMissing(error, packageName) {
|
|
8
|
+
const { code, message } = error;
|
|
9
|
+
if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND')
|
|
10
|
+
return false;
|
|
11
|
+
return !!message && (message.includes(`'${packageName}'`) || message.includes(`"${packageName}"`));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Load the client package of a driver (an optional dependency)
|
|
15
|
+
* @param member a member of its API, to find it in the ESM namespace or the CommonJS exports
|
|
16
|
+
* @param packageName
|
|
17
|
+
*/
|
|
18
|
+
export async function loadDriver(member, packageName) {
|
|
19
|
+
try {
|
|
20
|
+
const driver = await import(packageName);
|
|
21
|
+
return (driver[member] === undefined && driver.default ? driver.default : driver);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
if (!isPackageMissing(e, packageName))
|
|
25
|
+
throw e;
|
|
26
|
+
}
|
|
27
|
+
throw new MutexDriverNotFoundException('Package "{packageName}" is required for this driver of the locks. Run "npm install {packageName}".', { packageName: packageName });
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* The token of a lock acquired (its holder checked by it)
|
|
4
|
+
*/
|
|
5
|
+
export function lockToken() {
|
|
6
|
+
return randomBytes(16).toString('hex');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The digest of the name of a lock (hexadecimal)
|
|
10
|
+
* @param name
|
|
11
|
+
*/
|
|
12
|
+
export function nameDigest(name) {
|
|
13
|
+
return createHash('sha256').update(name).digest('hex');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The key of 64 bits of the name of a lock (the advisory locks of PostgreSQL), a signed bigint as a string
|
|
17
|
+
* @param name
|
|
18
|
+
*/
|
|
19
|
+
export function nameKey64(name) {
|
|
20
|
+
return createHash('sha256').update(name).digest().readBigInt64BE(0).toString();
|
|
21
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 { DTO } from '@lakutata/core';
|
|
11
|
+
import { Expect } from '@lakutata/core/decorator/dto';
|
|
12
|
+
/**
|
|
13
|
+
* The options of the file driver ({@link FileMutexDriver}): one lock file per lock in a directory, shared by the
|
|
14
|
+
* processes of one host using the same directory (several workers or applications of a machine). It needs no package.
|
|
15
|
+
* The locks expire after their `ttl` unless renewed, a lock whose process is gone is taken over at once, and there is no
|
|
16
|
+
* fencing token.
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import {Application} from 'lakutata'
|
|
20
|
+
* import {Mutex} from 'lakutata/com/mutex'
|
|
21
|
+
*
|
|
22
|
+
* Application.run(() => ({
|
|
23
|
+
* id: 'file-mutex.app',
|
|
24
|
+
* name: 'FileMutex',
|
|
25
|
+
* components: {
|
|
26
|
+
* //Every worker of this machine uses the same directory
|
|
27
|
+
* mutex: {class: Mutex, driver: {type: 'file', directory: '/var/lock/my-app'}}
|
|
28
|
+
* }
|
|
29
|
+
* }))
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class FileMutexOptions extends DTO {
|
|
33
|
+
}
|
|
34
|
+
__decorate([
|
|
35
|
+
Expect(DTO.String().valid('file').required()),
|
|
36
|
+
__metadata("design:type", String)
|
|
37
|
+
], FileMutexOptions.prototype, "type", void 0);
|
|
38
|
+
__decorate([
|
|
39
|
+
Expect(DTO.String().optional()),
|
|
40
|
+
__metadata("design:type", String)
|
|
41
|
+
], FileMutexOptions.prototype, "directory", void 0);
|
|
@@ -0,0 +1,37 @@
|
|
|
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 { DTO } from '@lakutata/core';
|
|
11
|
+
import { Expect } from '@lakutata/core/decorator/dto';
|
|
12
|
+
/**
|
|
13
|
+
* The options of the memory driver ({@link MemoryMutexDriver}), the default of a {@link Mutex}: the locks live in the
|
|
14
|
+
* process memory, shared by the components of the process and invisible to the other processes. For development, tests
|
|
15
|
+
* and an application running as a single process; it needs no package. The locks expire after their `ttl` unless
|
|
16
|
+
* renewed, and carry fencing tokens counted by the process.
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import {Application} from 'lakutata'
|
|
20
|
+
* import {Mutex} from 'lakutata/com/mutex'
|
|
21
|
+
*
|
|
22
|
+
* Application.run(() => ({
|
|
23
|
+
* id: 'memory-mutex.app',
|
|
24
|
+
* name: 'MemoryMutex',
|
|
25
|
+
* components: {
|
|
26
|
+
* //The same as {class: Mutex}
|
|
27
|
+
* mutex: {class: Mutex, driver: {type: 'memory'}}
|
|
28
|
+
* }
|
|
29
|
+
* }))
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class MemoryMutexOptions extends DTO {
|
|
33
|
+
}
|
|
34
|
+
__decorate([
|
|
35
|
+
Expect(DTO.String().valid('memory').required()),
|
|
36
|
+
__metadata("design:type", String)
|
|
37
|
+
], MemoryMutexOptions.prototype, "type", void 0);
|
|
@@ -0,0 +1,73 @@
|
|
|
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 { DTO } from '@lakutata/core';
|
|
11
|
+
import { Expect } from '@lakutata/core/decorator/dto';
|
|
12
|
+
/**
|
|
13
|
+
* The options of the MySQL driver ({@link MysqlMutexDriver}), for MySQL or MariaDB: the locks are shared by every
|
|
14
|
+
* instance connected to the same server. It needs the optional peer dependency `mysql2` (`npm install mysql2`, 3):
|
|
15
|
+
* without it, the first acquisition fails with {@link MutexDriverNotFoundException}. The driver connects at the first
|
|
16
|
+
* acquisition, not when the component is created.
|
|
17
|
+
*
|
|
18
|
+
* In the `'advisory'` mode (the default), the locks are named locks (`GET_LOCK`) of one connection: they do not expire,
|
|
19
|
+
* MySQL releases them when the connection closes (a crashed process frees them at once), and they are all lost when it
|
|
20
|
+
* closes or fails. They have no fencing token, and are not replicated by the clusters (Galera, Group Replication): every
|
|
21
|
+
* holder must connect to the same server. In the `'table'` mode, the locks are rows of a table created at the first use:
|
|
22
|
+
* they expire after their `ttl` unless renewed, work through the poolers and the clusters, and carry fencing tokens.
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import {Application} from 'lakutata'
|
|
26
|
+
* import {Mutex} from 'lakutata/com/mutex'
|
|
27
|
+
*
|
|
28
|
+
* Application.run(() => ({
|
|
29
|
+
* id: 'mysql-mutex.app',
|
|
30
|
+
* name: 'MysqlMutex',
|
|
31
|
+
* components: {
|
|
32
|
+
* mutex: {
|
|
33
|
+
* class: Mutex,
|
|
34
|
+
* driver: {type: 'mysql', host: 'db', database: 'app', username: 'app', password: process.env.DB_PASSWORD ?? ''}
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* }))
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export class MysqlMutexOptions extends DTO {
|
|
41
|
+
}
|
|
42
|
+
__decorate([
|
|
43
|
+
Expect(DTO.String().valid('mysql').required()),
|
|
44
|
+
__metadata("design:type", String)
|
|
45
|
+
], MysqlMutexOptions.prototype, "type", void 0);
|
|
46
|
+
__decorate([
|
|
47
|
+
Expect(DTO.String().required()),
|
|
48
|
+
__metadata("design:type", String)
|
|
49
|
+
], MysqlMutexOptions.prototype, "host", void 0);
|
|
50
|
+
__decorate([
|
|
51
|
+
Expect(DTO.Number().port().optional().default(3306)),
|
|
52
|
+
__metadata("design:type", Number)
|
|
53
|
+
], MysqlMutexOptions.prototype, "port", void 0);
|
|
54
|
+
__decorate([
|
|
55
|
+
Expect(DTO.String().required()),
|
|
56
|
+
__metadata("design:type", String)
|
|
57
|
+
], MysqlMutexOptions.prototype, "database", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
Expect(DTO.String().required()),
|
|
60
|
+
__metadata("design:type", String)
|
|
61
|
+
], MysqlMutexOptions.prototype, "username", void 0);
|
|
62
|
+
__decorate([
|
|
63
|
+
Expect(DTO.String().allow('').required()),
|
|
64
|
+
__metadata("design:type", String)
|
|
65
|
+
], MysqlMutexOptions.prototype, "password", void 0);
|
|
66
|
+
__decorate([
|
|
67
|
+
Expect(DTO.String().valid('advisory', 'table').optional().default('advisory')),
|
|
68
|
+
__metadata("design:type", String)
|
|
69
|
+
], MysqlMutexOptions.prototype, "mode", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
Expect(DTO.String().pattern(/^[a-zA-Z_][a-zA-Z0-9_]*$/).optional().default('lakutata_mutex')),
|
|
72
|
+
__metadata("design:type", String)
|
|
73
|
+
], MysqlMutexOptions.prototype, "table", void 0);
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { DTO } from '@lakutata/core';
|
|
11
|
+
import { Expect } from '@lakutata/core/decorator/dto';
|
|
12
|
+
/**
|
|
13
|
+
* The options of the PostgreSQL driver ({@link PostgresMutexDriver}): the locks are shared by every instance connected
|
|
14
|
+
* to the same database. It needs the optional peer dependency `pg` (`npm install pg`, 8): without it, the first
|
|
15
|
+
* acquisition fails with {@link MutexDriverNotFoundException}. The driver connects at the first acquisition, not when
|
|
16
|
+
* the component is created.
|
|
17
|
+
*
|
|
18
|
+
* In the `'advisory'` mode (the default), the locks are session advisory locks of one connection: they do not expire,
|
|
19
|
+
* PostgreSQL releases them when the connection closes (a crashed process frees them at once), and they are all lost
|
|
20
|
+
* when it closes or fails. They have no fencing token, and do not work through a pooler in transaction mode
|
|
21
|
+
* (PgBouncer). In the `'table'` mode, the locks are rows of a table created at the first use: they expire after their
|
|
22
|
+
* `ttl` unless renewed, work through the poolers, and carry fencing tokens.
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import {Application} from 'lakutata'
|
|
26
|
+
* import {Mutex} from 'lakutata/com/mutex'
|
|
27
|
+
*
|
|
28
|
+
* Application.run(() => ({
|
|
29
|
+
* id: 'postgres-mutex.app',
|
|
30
|
+
* name: 'PostgresMutex',
|
|
31
|
+
* components: {
|
|
32
|
+
* mutex: {
|
|
33
|
+
* class: Mutex,
|
|
34
|
+
* driver: {
|
|
35
|
+
* type: 'postgres',
|
|
36
|
+
* host: 'db',
|
|
37
|
+
* database: 'app',
|
|
38
|
+
* username: 'app',
|
|
39
|
+
* password: process.env.DB_PASSWORD ?? '',
|
|
40
|
+
* //Through PgBouncer: rows of a table, with fencing tokens
|
|
41
|
+
* mode: 'table'
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* }))
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export class PostgresMutexOptions extends DTO {
|
|
49
|
+
}
|
|
50
|
+
__decorate([
|
|
51
|
+
Expect(DTO.String().valid('postgres').required()),
|
|
52
|
+
__metadata("design:type", String)
|
|
53
|
+
], PostgresMutexOptions.prototype, "type", void 0);
|
|
54
|
+
__decorate([
|
|
55
|
+
Expect(DTO.String().required()),
|
|
56
|
+
__metadata("design:type", String)
|
|
57
|
+
], PostgresMutexOptions.prototype, "host", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
Expect(DTO.Number().port().optional().default(5432)),
|
|
60
|
+
__metadata("design:type", Number)
|
|
61
|
+
], PostgresMutexOptions.prototype, "port", void 0);
|
|
62
|
+
__decorate([
|
|
63
|
+
Expect(DTO.String().required()),
|
|
64
|
+
__metadata("design:type", String)
|
|
65
|
+
], PostgresMutexOptions.prototype, "database", void 0);
|
|
66
|
+
__decorate([
|
|
67
|
+
Expect(DTO.String().required()),
|
|
68
|
+
__metadata("design:type", String)
|
|
69
|
+
], PostgresMutexOptions.prototype, "username", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
Expect(DTO.String().allow('').required()),
|
|
72
|
+
__metadata("design:type", String)
|
|
73
|
+
], PostgresMutexOptions.prototype, "password", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
Expect(DTO.Boolean().optional()),
|
|
76
|
+
__metadata("design:type", Boolean)
|
|
77
|
+
], PostgresMutexOptions.prototype, "ssl", void 0);
|
|
78
|
+
__decorate([
|
|
79
|
+
Expect(DTO.String().valid('advisory', 'table').optional().default('advisory')),
|
|
80
|
+
__metadata("design:type", String)
|
|
81
|
+
], PostgresMutexOptions.prototype, "mode", void 0);
|
|
82
|
+
__decorate([
|
|
83
|
+
Expect(DTO.String().pattern(/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)?$/).optional().default('lakutata_mutex')),
|
|
84
|
+
__metadata("design:type", String)
|
|
85
|
+
], PostgresMutexOptions.prototype, "table", void 0);
|
|
@@ -0,0 +1,73 @@
|
|
|
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 { DTO } from '@lakutata/core';
|
|
11
|
+
import { Expect } from '@lakutata/core/decorator/dto';
|
|
12
|
+
/**
|
|
13
|
+
* The options of the Redis driver ({@link RedisMutexDriver}): the locks are Redis keys shared by every instance
|
|
14
|
+
* connected to the same server or cluster. It needs the optional peer dependency `redis` (`npm install redis`, 4.6 or
|
|
15
|
+
* 5): without it, the first acquisition fails with {@link MutexDriverNotFoundException}. The keys expire after their
|
|
16
|
+
* `ttl` unless renewed, and each acquisition gets a fencing token. Configure the server with
|
|
17
|
+
* `maxmemory-policy noeviction`: an evicted key is a lock lost.
|
|
18
|
+
*
|
|
19
|
+
* The client connects at the first acquisition, not when the component is created. While the server cannot be reached,
|
|
20
|
+
* the calls wait for it instead of failing.
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import {Application} from 'lakutata'
|
|
24
|
+
* import {Mutex} from 'lakutata/com/mutex'
|
|
25
|
+
*
|
|
26
|
+
* Application.run(() => ({
|
|
27
|
+
* id: 'redis-mutex.app',
|
|
28
|
+
* name: 'RedisMutex',
|
|
29
|
+
* components: {
|
|
30
|
+
* mutex: {
|
|
31
|
+
* class: Mutex,
|
|
32
|
+
* driver: {type: 'redis', host: '127.0.0.1', port: 6379, password: process.env.REDIS_PASSWORD},
|
|
33
|
+
* //The applications sharing this Redis server keep their locks apart
|
|
34
|
+
* prefix: 'billing:mutex:'
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* }))
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export class RedisMutexOptions extends DTO {
|
|
41
|
+
}
|
|
42
|
+
__decorate([
|
|
43
|
+
Expect(DTO.String().valid('redis').required()),
|
|
44
|
+
__metadata("design:type", String)
|
|
45
|
+
], RedisMutexOptions.prototype, "type", void 0);
|
|
46
|
+
__decorate([
|
|
47
|
+
Expect(DTO.String().optional()),
|
|
48
|
+
__metadata("design:type", String)
|
|
49
|
+
], RedisMutexOptions.prototype, "url", void 0);
|
|
50
|
+
__decorate([
|
|
51
|
+
Expect(DTO.String().optional()),
|
|
52
|
+
__metadata("design:type", String)
|
|
53
|
+
], RedisMutexOptions.prototype, "host", void 0);
|
|
54
|
+
__decorate([
|
|
55
|
+
Expect(DTO.Number().port().optional()),
|
|
56
|
+
__metadata("design:type", Number)
|
|
57
|
+
], RedisMutexOptions.prototype, "port", void 0);
|
|
58
|
+
__decorate([
|
|
59
|
+
Expect(DTO.Number().integer().min(0).optional()),
|
|
60
|
+
__metadata("design:type", Number)
|
|
61
|
+
], RedisMutexOptions.prototype, "database", void 0);
|
|
62
|
+
__decorate([
|
|
63
|
+
Expect(DTO.String().optional()),
|
|
64
|
+
__metadata("design:type", String)
|
|
65
|
+
], RedisMutexOptions.prototype, "username", void 0);
|
|
66
|
+
__decorate([
|
|
67
|
+
Expect(DTO.String().optional()),
|
|
68
|
+
__metadata("design:type", String)
|
|
69
|
+
], RedisMutexOptions.prototype, "password", void 0);
|
|
70
|
+
__decorate([
|
|
71
|
+
Expect(DTO.Boolean().optional()),
|
|
72
|
+
__metadata("design:type", Boolean)
|
|
73
|
+
], RedisMutexOptions.prototype, "tls", void 0);
|
|
@@ -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';
|