velocious 1.0.699 → 1.0.701
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/README.md +1 -2
- package/build/configuration-types.js +12 -1
- package/build/configuration.js +174 -18
- package/build/database/advisory-lock-backend-policy.js +204 -0
- package/build/database/advisory-lock-file-backend.js +348 -0
- package/build/database/advisory-lock-runner.js +239 -16
- package/build/environment-handlers/base.js +13 -0
- package/build/environment-handlers/node.js +11 -0
- package/build/http-server/server-lock.js +21 -115
- package/build/src/configuration-types.d.ts +33 -2
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +12 -2
- package/build/src/configuration.d.ts +99 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +161 -18
- package/build/src/database/advisory-lock-backend-policy.d.ts +145 -0
- package/build/src/database/advisory-lock-backend-policy.d.ts.map +1 -0
- package/build/src/database/advisory-lock-backend-policy.js +184 -0
- package/build/src/database/advisory-lock-file-backend.d.ts +194 -0
- package/build/src/database/advisory-lock-file-backend.d.ts.map +1 -0
- package/build/src/database/advisory-lock-file-backend.js +313 -0
- package/build/src/database/advisory-lock-runner.d.ts +116 -15
- package/build/src/database/advisory-lock-runner.d.ts.map +1 -1
- package/build/src/database/advisory-lock-runner.js +190 -17
- package/build/src/environment-handlers/base.d.ts +14 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +13 -1
- package/build/src/environment-handlers/node.d.ts +11 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +11 -1
- package/build/src/http-server/server-lock.d.ts +13 -37
- package/build/src/http-server/server-lock.d.ts.map +1 -1
- package/build/src/http-server/server-lock.js +22 -109
- package/package.json +1 -1
- package/src/configuration-types.js +12 -1
- package/src/configuration.js +174 -18
- package/src/database/advisory-lock-backend-policy.js +204 -0
- package/src/database/advisory-lock-file-backend.js +348 -0
- package/src/database/advisory-lock-runner.js +239 -16
- package/src/environment-handlers/base.js +13 -0
- package/src/environment-handlers/node.js +11 -0
- package/src/http-server/server-lock.js +21 -115
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import AdvisoryLockFileBackend from "../database/advisory-lock-file-backend.js";
|
|
1
2
|
/**
|
|
2
|
-
* Directory-based lock that allows only one HTTP server process per app
|
|
3
|
+
* Directory-based lock that allows only one HTTP server process per app
|
|
4
|
+
* directory. Thin wrapper over the shared file-lock backend
|
|
5
|
+
* (`src/database/advisory-lock-file-backend.js` — the STC-210 `file` tier
|
|
6
|
+
* primitive generalized from this lock's original hand-rolled directory
|
|
7
|
+
* protocol), so the server lock and the advisory-lock file tier share one
|
|
8
|
+
* acquisition, owner-metadata, lease-renewal, and staleness implementation.
|
|
3
9
|
*/
|
|
4
10
|
export default class VelociousHttpServerLock {
|
|
5
11
|
configuration: import("../configuration.js").default;
|
|
6
12
|
host: string;
|
|
7
13
|
port: number;
|
|
8
14
|
lockPath: string;
|
|
15
|
+
backend: AdvisoryLockFileBackend;
|
|
9
16
|
acquired: boolean;
|
|
10
17
|
/**
|
|
11
18
|
* Build a lock for the configured application directory and server endpoint.
|
|
@@ -20,53 +27,22 @@ export default class VelociousHttpServerLock {
|
|
|
20
27
|
port: number;
|
|
21
28
|
});
|
|
22
29
|
/**
|
|
23
|
-
* Acquires the app-directory HTTP server lock before startup side effects
|
|
30
|
+
* Acquires the app-directory HTTP server lock before startup side effects
|
|
31
|
+
* run. A stale lock (dead local owner or expired lease) is reclaimed by the
|
|
32
|
+
* backend; a live foreign owner fails loudly with the owner details.
|
|
24
33
|
* @returns {Promise<void>} - Resolves after the lock has been acquired.
|
|
25
34
|
*/
|
|
26
35
|
acquire(): Promise<void>;
|
|
27
|
-
/**
|
|
28
|
-
* Tries to create the lock directory and write owner metadata.
|
|
29
|
-
* @returns {Promise<boolean>} - Whether the lock was acquired.
|
|
30
|
-
*/
|
|
31
|
-
tryAcquire(): Promise<boolean>;
|
|
32
36
|
/**
|
|
33
37
|
* Releases the held HTTP server lock directory.
|
|
34
38
|
* @returns {Promise<void>} - Resolves after best-effort lock release.
|
|
35
39
|
*/
|
|
36
40
|
release(): Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* Writes metadata used to explain or reclaim an existing server lock.
|
|
39
|
-
* @returns {Promise<void>} - Resolves after owner metadata has been written.
|
|
40
|
-
*/
|
|
41
|
-
writeOwnerMetadata(): Promise<void>;
|
|
42
|
-
/**
|
|
43
|
-
* Checks whether the current lock owner is a dead process on this host.
|
|
44
|
-
* @returns {Promise<boolean>} - Whether the existing lock belongs to a dead process.
|
|
45
|
-
*/
|
|
46
|
-
isStale(): Promise<boolean>;
|
|
47
|
-
/**
|
|
48
|
-
* Runs is local process owner.
|
|
49
|
-
* @param {Record<string, ReturnType<typeof JSON.parse>> | null} owner - Existing lock owner metadata.
|
|
50
|
-
* @returns {boolean} - Whether owner metadata names a local process.
|
|
51
|
-
*/
|
|
52
|
-
isLocalProcessOwner(owner: Record<string, ReturnType<typeof JSON.parse>> | null): boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Runs owner hostname matches.
|
|
55
|
-
* @param {Record<string, ReturnType<typeof JSON.parse>>} owner - Existing lock owner metadata.
|
|
56
|
-
* @returns {boolean} - Whether the owner hostname is local or absent.
|
|
57
|
-
*/
|
|
58
|
-
ownerHostnameMatches(owner: Record<string, ReturnType<typeof JSON.parse>>): boolean;
|
|
59
|
-
/**
|
|
60
|
-
* Runs process is dead.
|
|
61
|
-
* @param {number} pid - Process id.
|
|
62
|
-
* @returns {boolean} - Whether the process no longer exists.
|
|
63
|
-
*/
|
|
64
|
-
processIsDead(pid: number): boolean;
|
|
65
41
|
/**
|
|
66
42
|
* Reads owner metadata from an existing server lock directory.
|
|
67
|
-
* @returns {Promise<
|
|
43
|
+
* @returns {Promise<import("../database/advisory-lock-file-backend.js").AdvisoryLockFileOwner | null>} - Parsed owner metadata, when readable.
|
|
68
44
|
*/
|
|
69
|
-
readOwnerMetadata(): Promise<
|
|
45
|
+
readOwnerMetadata(): Promise<import("../database/advisory-lock-file-backend.js").AdvisoryLockFileOwner | null>;
|
|
70
46
|
/**
|
|
71
47
|
* Builds a duplicate-server error message with owner details when available.
|
|
72
48
|
* @returns {Promise<string>} - Error message explaining which server owns the lock.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-lock.d.ts","sourceRoot":"","sources":["../../../src/http-server/server-lock.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server-lock.d.ts","sourceRoot":"","sources":["../../../src/http-server/server-lock.js"],"names":[],"mappings":"AAEA,OAAO,uBAAuB,MAAM,2CAA2C,CAAA;AAG/E;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,OAAO,uBAAuB;IASnC,aAAa;IACb,IAAI;IACJ,IAAI;IACJ,QAAQ;IACR,OAAO;IAKP,QAAQ;IAjBf;;;;;;OAMG;IACH,YAAY,EAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAC,EAJpC;QAAoD,aAAa,EAAzD,OAAO,qBAAqB,EAAE,OAAO,CAC7C;QAAqB,IAAI,EAAjB,MAAM,CACd;QAAqB,IAAI,EAAjB,MAAM,CAChB;KACsC,EAWtC;IAED;;;;;OAKG;IACG,OAAO,IAFA,OAAO,CAAC,IAAI,CAAC,CAQzB;IAED;;;OAGG;IACG,OAAO,IAFA,OAAO,CAAC,IAAI,CAAC,CAOzB;IAED;;;OAGG;IACG,iBAAiB,IAFV,OAAO,CAAC,OAAO,2CAA2C,EAAE,qBAAqB,GAAG,IAAI,CAAC,CAIrG;IAED;;;OAGG;IACG,eAAe,IAFR,OAAO,CAAC,MAAM,CAAC,CAS3B;CACF"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
import
|
|
3
|
-
import os from "node:os";
|
|
2
|
+
import AdvisoryLockFileBackend from "../database/advisory-lock-file-backend.js";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
/**
|
|
6
|
-
* Directory-based lock that allows only one HTTP server process per app
|
|
5
|
+
* Directory-based lock that allows only one HTTP server process per app
|
|
6
|
+
* directory. Thin wrapper over the shared file-lock backend
|
|
7
|
+
* (`src/database/advisory-lock-file-backend.js` — the STC-210 `file` tier
|
|
8
|
+
* primitive generalized from this lock's original hand-rolled directory
|
|
9
|
+
* protocol), so the server lock and the advisory-lock file tier share one
|
|
10
|
+
* acquisition, owner-metadata, lease-renewal, and staleness implementation.
|
|
7
11
|
*/
|
|
8
12
|
export default class VelociousHttpServerLock {
|
|
9
13
|
/**
|
|
@@ -18,48 +22,24 @@ export default class VelociousHttpServerLock {
|
|
|
18
22
|
this.host = host;
|
|
19
23
|
this.port = port;
|
|
20
24
|
this.lockPath = path.join(configuration.getDirectory(), "tmp", "server.lock");
|
|
25
|
+
this.backend = new AdvisoryLockFileBackend({
|
|
26
|
+
directory: path.join(configuration.getDirectory(), "tmp"),
|
|
27
|
+
extraOwnerFields: { host: this.host, port: this.port },
|
|
28
|
+
pathStyle: "plain"
|
|
29
|
+
});
|
|
21
30
|
this.acquired = false;
|
|
22
31
|
}
|
|
23
32
|
/**
|
|
24
|
-
* Acquires the app-directory HTTP server lock before startup side effects
|
|
33
|
+
* Acquires the app-directory HTTP server lock before startup side effects
|
|
34
|
+
* run. A stale lock (dead local owner or expired lease) is reclaimed by the
|
|
35
|
+
* backend; a live foreign owner fails loudly with the owner details.
|
|
25
36
|
* @returns {Promise<void>} - Resolves after the lock has been acquired.
|
|
26
37
|
*/
|
|
27
38
|
async acquire() {
|
|
28
|
-
await
|
|
29
|
-
|
|
30
|
-
const acquired = await this.tryAcquire();
|
|
31
|
-
if (acquired) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (await this.isStale()) {
|
|
35
|
-
await fs.rm(this.lockPath, { recursive: true, force: true });
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
39
|
+
const acquired = await this.backend.acquire("server", { timeoutMs: 0 });
|
|
40
|
+
if (!acquired)
|
|
38
41
|
throw new Error(await this.lockHeldMessage());
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Tries to create the lock directory and write owner metadata.
|
|
43
|
-
* @returns {Promise<boolean>} - Whether the lock was acquired.
|
|
44
|
-
*/
|
|
45
|
-
async tryAcquire() {
|
|
46
|
-
try {
|
|
47
|
-
await fs.mkdir(this.lockPath);
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
if ( /** @type {{code?: string}} */(error).code === "EEXIST")
|
|
51
|
-
return false;
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
await this.writeOwnerMetadata();
|
|
56
|
-
this.acquired = true;
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
await fs.rm(this.lockPath, { recursive: true, force: true });
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
42
|
+
this.acquired = true;
|
|
63
43
|
}
|
|
64
44
|
/**
|
|
65
45
|
* Releases the held HTTP server lock directory.
|
|
@@ -69,81 +49,14 @@ export default class VelociousHttpServerLock {
|
|
|
69
49
|
if (!this.acquired)
|
|
70
50
|
return;
|
|
71
51
|
this.acquired = false;
|
|
72
|
-
await
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Writes metadata used to explain or reclaim an existing server lock.
|
|
76
|
-
* @returns {Promise<void>} - Resolves after owner metadata has been written.
|
|
77
|
-
*/
|
|
78
|
-
async writeOwnerMetadata() {
|
|
79
|
-
await fs.writeFile(path.join(this.lockPath, "owner.json"), JSON.stringify({
|
|
80
|
-
acquiredAt: new Date().toISOString(),
|
|
81
|
-
host: this.host,
|
|
82
|
-
hostname: os.hostname(),
|
|
83
|
-
pid: process.pid,
|
|
84
|
-
port: this.port
|
|
85
|
-
}));
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Checks whether the current lock owner is a dead process on this host.
|
|
89
|
-
* @returns {Promise<boolean>} - Whether the existing lock belongs to a dead process.
|
|
90
|
-
*/
|
|
91
|
-
async isStale() {
|
|
92
|
-
const owner = await this.readOwnerMetadata();
|
|
93
|
-
if (!this.isLocalProcessOwner(owner))
|
|
94
|
-
return false;
|
|
95
|
-
return this.processIsDead(/** @type {{pid: number}} */ (owner).pid);
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Runs is local process owner.
|
|
99
|
-
* @param {Record<string, ReturnType<typeof JSON.parse>> | null} owner - Existing lock owner metadata.
|
|
100
|
-
* @returns {boolean} - Whether owner metadata names a local process.
|
|
101
|
-
*/
|
|
102
|
-
isLocalProcessOwner(owner) {
|
|
103
|
-
if (!owner)
|
|
104
|
-
return false;
|
|
105
|
-
if (typeof owner.pid !== "number")
|
|
106
|
-
return false;
|
|
107
|
-
return this.ownerHostnameMatches(owner);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Runs owner hostname matches.
|
|
111
|
-
* @param {Record<string, ReturnType<typeof JSON.parse>>} owner - Existing lock owner metadata.
|
|
112
|
-
* @returns {boolean} - Whether the owner hostname is local or absent.
|
|
113
|
-
*/
|
|
114
|
-
ownerHostnameMatches(owner) {
|
|
115
|
-
if (!owner.hostname)
|
|
116
|
-
return true;
|
|
117
|
-
if (owner.hostname === os.hostname())
|
|
118
|
-
return true;
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Runs process is dead.
|
|
123
|
-
* @param {number} pid - Process id.
|
|
124
|
-
* @returns {boolean} - Whether the process no longer exists.
|
|
125
|
-
*/
|
|
126
|
-
processIsDead(pid) {
|
|
127
|
-
try {
|
|
128
|
-
process.kill(pid, 0);
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
return /** @type {{code?: string}} */ (error).code === "ESRCH";
|
|
133
|
-
}
|
|
52
|
+
await this.backend.release("server");
|
|
134
53
|
}
|
|
135
54
|
/**
|
|
136
55
|
* Reads owner metadata from an existing server lock directory.
|
|
137
|
-
* @returns {Promise<
|
|
56
|
+
* @returns {Promise<import("../database/advisory-lock-file-backend.js").AdvisoryLockFileOwner | null>} - Parsed owner metadata, when readable.
|
|
138
57
|
*/
|
|
139
58
|
async readOwnerMetadata() {
|
|
140
|
-
|
|
141
|
-
const rawOwner = await fs.readFile(path.join(this.lockPath, "owner.json"), "utf8");
|
|
142
|
-
return /** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (JSON.parse(rawOwner));
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
59
|
+
return await this.backend.holderInfo("server");
|
|
147
60
|
}
|
|
148
61
|
/**
|
|
149
62
|
* Builds a duplicate-server error message with owner details when available.
|
|
@@ -157,4 +70,4 @@ export default class VelociousHttpServerLock {
|
|
|
157
70
|
return `A Velocious HTTP server is already running for this application (${details}). Remove ${this.lockPath} if the server is no longer running.`;
|
|
158
71
|
}
|
|
159
72
|
}
|
|
160
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLWxvY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaHR0cC1zZXJ2ZXIvc2VydmVyLWxvY2suanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8sRUFBRSxNQUFNLGtCQUFrQixDQUFBO0FBQ2pDLE9BQU8sRUFBRSxNQUFNLFNBQVMsQ0FBQTtBQUN4QixPQUFPLElBQUksTUFBTSxXQUFXLENBQUE7QUFFNUI7O0dBRUc7QUFDSCxNQUFNLENBQUMsT0FBTyxPQUFPLHVCQUF1QjtJQUMxQzs7Ozs7O09BTUc7SUFDSCxZQUFZLEVBQUMsYUFBYSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUM7UUFDckMsSUFBSSxDQUFDLGFBQWEsR0FBRyxhQUFhLENBQUE7UUFDbEMsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUE7UUFDaEIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUE7UUFDaEIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxZQUFZLEVBQUUsRUFBRSxLQUFLLEVBQUUsYUFBYSxDQUFDLENBQUE7UUFDN0UsSUFBSSxDQUFDLFFBQVEsR0FBRyxLQUFLLENBQUE7SUFDdkIsQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxPQUFPO1FBQ1gsTUFBTSxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLEVBQUMsU0FBUyxFQUFFLElBQUksRUFBQyxDQUFDLENBQUE7UUFFOUQsT0FBTyxJQUFJLEVBQUUsQ0FBQztZQUNaLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFBO1lBRXhDLElBQUksUUFBUSxFQUFFLENBQUM7Z0JBQ2IsT0FBTTtZQUNSLENBQUM7WUFFRCxJQUFJLE1BQU0sSUFBSSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUM7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Q0FBQyxJQUFJLE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQUc7WUFDdkcsQ0FBQyxDQUFDLGtCQUFrQixJQUFJLENBQUMsUUFBUSxFQUFFLENBQUE7UUFFckMsT0FBTyxvRUFBb0UsT0FBTyxhQUFhLElBQUksQ0FBQyxRQUFRLHNDQUFzQyxDQUFBO0lBQ3BKLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5pbXBvcnQgZnMgZnJvbSBcIm5vZGU6ZnMvcHJvbWlzZXNcIlxuaW1wb3J0IG9zIGZyb20gXCJub2RlOm9zXCJcbmltcG9ydCBwYXRoIGZyb20gXCJub2RlOnBhdGhcIlxuXG4vKipcbiAqIERpcmVjdG9yeS1iYXNlZCBsb2NrIHRoYXQgYWxsb3dzIG9ubHkgb25lIEhUVFAgc2VydmVyIHByb2Nlc3MgcGVyIGFwcCBkaXJlY3RvcnkuXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFZlbG9jaW91c0h0dHBTZXJ2ZXJMb2NrIHtcbiAgLyoqXG4gICAqIEJ1aWxkIGEgbG9jayBmb3IgdGhlIGNvbmZpZ3VyZWQgYXBwbGljYXRpb24gZGlyZWN0b3J5IGFuZCBzZXJ2ZXIgZW5kcG9pbnQuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3B0aW9ucyBvYmplY3QuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBhcmdzLmNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uIHRoYXQgb3ducyB0aGUgYXBwbGljYXRpb24gZGlyZWN0b3J5LlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5ob3N0IC0gQ29uZmlndXJlZCBIVFRQIGhvc3QuXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBhcmdzLnBvcnQgLSBDb25maWd1cmVkIEhUVFAgcG9ydC5cbiAgICovXG4gIGNvbnN0cnVjdG9yKHtjb25maWd1cmF0aW9uLCBob3N0LCBwb3J0fSkge1xuICAgIHRoaXMuY29uZmlndXJhdGlvbiA9IGNvbmZpZ3VyYXRpb25cbiAgICB0aGlzLmhvc3QgPSBob3N0XG4gICAgdGhpcy5wb3J0ID0gcG9ydFxuICAgIHRoaXMubG9ja1BhdGggPSBwYXRoLmpvaW4oY29uZmlndXJhdGlvbi5nZXREaXJlY3RvcnkoKSwgXCJ0bXBcIiwgXCJzZXJ2ZXIubG9ja1wiKVxuICAgIHRoaXMuYWNxdWlyZWQgPSBmYWxzZVxuICB9XG5cbiAgLyoqXG4gICAqIEFjcXVpcmVzIHRoZSBhcHAtZGlyZWN0b3J5IEhUVFAgc2VydmVyIGxvY2sgYmVmb3JlIHN0YXJ0dXAgc2lkZSBlZmZlY3RzIHJ1bi5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgdGhlIGxvY2sgaGFzIGJlZW4gYWNxdWlyZWQuXG4gICAqL1xuICBhc3luYyBhY3F1aXJlKCkge1xuICAgIGF3YWl0IGZzLm1rZGlyKHBhdGguZGlybmFtZSh0aGlzLmxvY2tQYXRoKSwge3JlY3Vyc2l2ZTogdHJ1ZX0pXG5cbiAgICB3aGlsZSAodHJ1ZSkge1xuICAgICAgY29uc3QgYWNxdWlyZWQgPSBhd2FpdCB0aGlzLnRyeUFjcXVpcmUoKVxuXG4gICAgICBpZiAoYWNxdWlyZWQpIHtcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG5cbiAgICAgIGlmIChhd2FpdCB0aGlzLmlzU3RhbGUoKSkge1xuICAgICAgICBhd2FpdCBmcy5ybSh0aGlzLmxvY2tQYXRoLCB7cmVjdXJzaXZlOiB0cnVlLCBmb3JjZTogdHJ1ZX0pXG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB9XG5cbiAgICAgIHRocm93IG5ldyBFcnJvcihhd2FpdCB0aGlzLmxvY2tIZWxkTWVzc2FnZSgpKVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBUcmllcyB0byBjcmVhdGUgdGhlIGxvY2sgZGlyZWN0b3J5IGFuZCB3cml0ZSBvd25lciBtZXRhZGF0YS5cbiAgICogQHJldHVybnMge1Byb21pc2U8Ym9vbGVhbj59IC0gV2hldGhlciB0aGUgbG9jayB3YXMgYWNxdWlyZWQuXG4gICAqL1xuICBhc3luYyB0cnlBY3F1aXJlKCkge1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCBmcy5ta2Rpcih0aGlzLmxvY2tQYXRoKVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBpZiAoLyoqIEB0eXBlIHt7Y29kZT86IHN0cmluZ319ICovIChlcnJvcikuY29kZSA9PT0gXCJFRVhJU1RcIikgcmV0dXJuIGZhbHNlXG4gICAgICB0aHJvdyBlcnJvclxuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0aGlzLndyaXRlT3duZXJNZXRhZGF0YSgpXG4gICAgICB0aGlzLmFjcXVpcmVkID0gdHJ1ZVxuXG4gICAgICByZXR1cm4gdHJ1ZVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBhd2FpdCBmcy5ybSh0aGlzLmxvY2tQYXRoLCB7cmVjdXJzaXZlOiB0cnVlLCBmb3JjZTogdHJ1ZX0pXG4gICAgICB0aHJvdyBlcnJvclxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZWxlYXNlcyB0aGUgaGVsZCBIVFRQIHNlcnZlciBsb2NrIGRpcmVjdG9yeS5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgYmVzdC1lZmZvcnQgbG9jayByZWxlYXNlLlxuICAgKi9cbiAgYXN5bmMgcmVsZWFzZSgpIHtcbiAgICBpZiAoIXRoaXMuYWNxdWlyZWQpIHJldHVyblxuXG4gICAgdGhpcy5hY3F1aXJlZCA9IGZhbHNlXG4gICAgYXdhaXQgZnMucm0odGhpcy5sb2NrUGF0aCwge3JlY3Vyc2l2ZTogdHJ1ZSwgZm9yY2U6IHRydWV9KVxuICB9XG5cbiAgLyoqXG4gICAqIFdyaXRlcyBtZXRhZGF0YSB1c2VkIHRvIGV4cGxhaW4gb3IgcmVjbGFpbSBhbiBleGlzdGluZyBzZXJ2ZXIgbG9jay5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgb3duZXIgbWV0YWRhdGEgaGFzIGJlZW4gd3JpdHRlbi5cbiAgICovXG4gIGFzeW5jIHdyaXRlT3duZXJNZXRhZGF0YSgpIHtcbiAgICBhd2FpdCBmcy53cml0ZUZpbGUocGF0aC5qb2luKHRoaXMubG9ja1BhdGgsIFwib3duZXIuanNvblwiKSwgSlNPTi5zdHJpbmdpZnkoe1xuICAgICAgYWNxdWlyZWRBdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgaG9zdDogdGhpcy5ob3N0LFxuICAgICAgaG9zdG5hbWU6IG9zLmhvc3RuYW1lKCksXG4gICAgICBwaWQ6IHByb2Nlc3MucGlkLFxuICAgICAgcG9ydDogdGhpcy5wb3J0XG4gICAgfSkpXG4gIH1cblxuICAvKipcbiAgICogQ2hlY2tzIHdoZXRoZXIgdGhlIGN1cnJlbnQgbG9jayBvd25lciBpcyBhIGRlYWQgcHJvY2VzcyBvbiB0aGlzIGhvc3QuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPGJvb2xlYW4+fSAtIFdoZXRoZXIgdGhlIGV4aXN0aW5nIGxvY2sgYmVsb25ncyB0byBhIGRlYWQgcHJvY2Vzcy5cbiAgICovXG4gIGFzeW5jIGlzU3RhbGUoKSB7XG4gICAgY29uc3Qgb3duZXIgPSBhd2FpdCB0aGlzLnJlYWRPd25lck1ldGFkYXRhKClcblxuICAgIGlmICghdGhpcy5pc0xvY2FsUHJvY2Vzc093bmVyKG93bmVyKSkgcmV0dXJuIGZhbHNlXG5cbiAgICByZXR1cm4gdGhpcy5wcm9jZXNzSXNEZWFkKC8qKiBAdHlwZSB7e3BpZDogbnVtYmVyfX0gKi8gKG93bmVyKS5waWQpXG4gIH1cblxuICAvKipcbiAgICogUnVucyBpcyBsb2NhbCBwcm9jZXNzIG93bmVyLlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+PiB8IG51bGx9IG93bmVyIC0gRXhpc3RpbmcgbG9jayBvd25lciBtZXRhZGF0YS5cbiAgICogQHJldHVybnMge2Jvb2xlYW59IC0gV2hldGhlciBvd25lciBtZXRhZGF0YSBuYW1lcyBhIGxvY2FsIHByb2Nlc3MuXG4gICAqL1xuICBpc0xvY2FsUHJvY2Vzc093bmVyKG93bmVyKSB7XG4gICAgaWYgKCFvd25lcikgcmV0dXJuIGZhbHNlXG4gICAgaWYgKHR5cGVvZiBvd25lci5waWQgIT09IFwibnVtYmVyXCIpIHJldHVybiBmYWxzZVxuXG4gICAgcmV0dXJuIHRoaXMub3duZXJIb3N0bmFtZU1hdGNoZXMob3duZXIpXG4gIH1cblxuICAvKipcbiAgICogUnVucyBvd25lciBob3N0bmFtZSBtYXRjaGVzLlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+Pn0gb3duZXIgLSBFeGlzdGluZyBsb2NrIG93bmVyIG1ldGFkYXRhLlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gLSBXaGV0aGVyIHRoZSBvd25lciBob3N0bmFtZSBpcyBsb2NhbCBvciBhYnNlbnQuXG4gICAqL1xuICBvd25lckhvc3RuYW1lTWF0Y2hlcyhvd25lcikge1xuICAgIGlmICghb3duZXIuaG9zdG5hbWUpIHJldHVybiB0cnVlXG4gICAgaWYgKG93bmVyLmhvc3RuYW1lID09PSBvcy5ob3N0bmFtZSgpKSByZXR1cm4gdHJ1ZVxuXG4gICAgcmV0dXJuIGZhbHNlXG4gIH1cblxuICAvKipcbiAgICogUnVucyBwcm9jZXNzIGlzIGRlYWQuXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBwaWQgLSBQcm9jZXNzIGlkLlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gLSBXaGV0aGVyIHRoZSBwcm9jZXNzIG5vIGxvbmdlciBleGlzdHMuXG4gICAqL1xuICBwcm9jZXNzSXNEZWFkKHBpZCkge1xuICAgIHRyeSB7XG4gICAgICBwcm9jZXNzLmtpbGwocGlkLCAwKVxuXG4gICAgICByZXR1cm4gZmFsc2VcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgcmV0dXJuIC8qKiBAdHlwZSB7e2NvZGU/OiBzdHJpbmd9fSAqLyAoZXJyb3IpLmNvZGUgPT09IFwiRVNSQ0hcIlxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZWFkcyBvd25lciBtZXRhZGF0YSBmcm9tIGFuIGV4aXN0aW5nIHNlcnZlciBsb2NrIGRpcmVjdG9yeS5cbiAgICogQHJldHVybnMge1Byb21pc2U8UmVjb3JkPHN0cmluZywgUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4+IHwgbnVsbD59IC0gUGFyc2VkIG93bmVyIG1ldGFkYXRhLCB3aGVuIHJlYWRhYmxlLlxuICAgKi9cbiAgYXN5bmMgcmVhZE93bmVyTWV0YWRhdGEoKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJhd093bmVyID0gYXdhaXQgZnMucmVhZEZpbGUocGF0aC5qb2luKHRoaXMubG9ja1BhdGgsIFwib3duZXIuanNvblwiKSwgXCJ1dGY4XCIpXG5cbiAgICAgIHJldHVybiAvKiogQHR5cGUge1JlY29yZDxzdHJpbmcsIFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+Pn0gKi8gKEpTT04ucGFyc2UocmF3T3duZXIpKVxuICAgIH0gY2F0Y2gge1xuICAgICAgcmV0dXJuIG51bGxcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQnVpbGRzIGEgZHVwbGljYXRlLXNlcnZlciBlcnJvciBtZXNzYWdlIHdpdGggb3duZXIgZGV0YWlscyB3aGVuIGF2YWlsYWJsZS5cbiAgICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gLSBFcnJvciBtZXNzYWdlIGV4cGxhaW5pbmcgd2hpY2ggc2VydmVyIG93bnMgdGhlIGxvY2suXG4gICAqL1xuICBhc3luYyBsb2NrSGVsZE1lc3NhZ2UoKSB7XG4gICAgY29uc3Qgb3duZXIgPSBhd2FpdCB0aGlzLnJlYWRPd25lck1ldGFkYXRhKClcbiAgICBjb25zdCBkZXRhaWxzID0gb3duZXJcbiAgICAgID8gYFBJRCAke1N0cmluZyhvd25lci5waWQpfSBvbiAke1N0cmluZyhvd25lci5ob3N0bmFtZSl9ICgke1N0cmluZyhvd25lci5ob3N0KX06JHtTdHJpbmcob3duZXIucG9ydCl9KWBcbiAgICAgIDogYGxvY2sgZGlyZWN0b3J5ICR7dGhpcy5sb2NrUGF0aH1gXG5cbiAgICByZXR1cm4gYEEgVmVsb2Npb3VzIEhUVFAgc2VydmVyIGlzIGFscmVhZHkgcnVubmluZyBmb3IgdGhpcyBhcHBsaWNhdGlvbiAoJHtkZXRhaWxzfSkuIFJlbW92ZSAke3RoaXMubG9ja1BhdGh9IGlmIHRoZSBzZXJ2ZXIgaXMgbm8gbG9uZ2VyIHJ1bm5pbmcuYFxuICB9XG59XG4iXX0=
|
|
73
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLWxvY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaHR0cC1zZXJ2ZXIvc2VydmVyLWxvY2suanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE9BQU8sdUJBQXVCLE1BQU0sMkNBQTJDLENBQUE7QUFDL0UsT0FBTyxJQUFJLE1BQU0sV0FBVyxDQUFBO0FBRTVCOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLENBQUMsT0FBTyxPQUFPLHVCQUF1QjtJQUMxQzs7Ozs7O09BTUc7SUFDSCxZQUFZLEVBQUMsYUFBYSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUM7UUFDckMsSUFBSSxDQUFDLGFBQWEsR0FBRyxhQUFhLENBQUE7UUFDbEMsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUE7UUFDaEIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUE7UUFDaEIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxZQUFZLEVBQUUsRUFBRSxLQUFLLEVBQUUsYUFBYSxDQUFDLENBQUE7UUFDN0UsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLHVCQUF1QixDQUFDO1lBQ3pDLFNBQVMsRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxZQUFZLEVBQUUsRUFBRSxLQUFLLENBQUM7WUFDekQsZ0JBQWdCLEVBQUUsRUFBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksRUFBQztZQUNwRCxTQUFTLEVBQUUsT0FBTztTQUNuQixDQUFDLENBQUE7UUFDRixJQUFJLENBQUMsUUFBUSxHQUFHLEtBQUssQ0FBQTtJQUN2QixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSCxLQUFLLENBQUMsT0FBTztRQUNYLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLEVBQUMsU0FBUyxFQUFFLENBQUMsRUFBQyxDQUFDLENBQUE7UUFFckUsSUFBSSxDQUFDLFFBQVE7WUFBRSxNQUFNLElBQUksS0FBSyxDQUFDLE1BQU0sSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDLENBQUE7UUFFNUQsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUE7SUFDdEIsQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxPQUFPO1FBQ1gsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRO1lBQUUsT0FBTTtRQUUxQixJQUFJLENBQUMsUUFBUSxHQUFHLEtBQUssQ0FBQTtRQUNyQixNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFBO0lBQ3RDLENBQUM7SUFFRDs7O09BR0c7SUFDSCxLQUFLLENBQUMsaUJBQWlCO1FBQ3JCLE9BQU8sTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUNoRCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLGVBQWU7UUFDbkIsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQTtRQUM1QyxNQUFNLE9BQU8sR0FBRyxLQUFLO1lBQ25CLENBQUMsQ0FBQyxPQUFPLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsS0FBSyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQUc7WUFDdkcsQ0FBQyxDQUFDLGtCQUFrQixJQUFJLENBQUMsUUFBUSxFQUFFLENBQUE7UUFFckMsT0FBTyxvRUFBb0UsT0FBTyxhQUFhLElBQUksQ0FBQyxRQUFRLHNDQUFzQyxDQUFBO0lBQ3BKLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIEB0cy1jaGVja1xuXG5pbXBvcnQgQWR2aXNvcnlMb2NrRmlsZUJhY2tlbmQgZnJvbSBcIi4uL2RhdGFiYXNlL2Fkdmlzb3J5LWxvY2stZmlsZS1iYWNrZW5kLmpzXCJcbmltcG9ydCBwYXRoIGZyb20gXCJub2RlOnBhdGhcIlxuXG4vKipcbiAqIERpcmVjdG9yeS1iYXNlZCBsb2NrIHRoYXQgYWxsb3dzIG9ubHkgb25lIEhUVFAgc2VydmVyIHByb2Nlc3MgcGVyIGFwcFxuICogZGlyZWN0b3J5LiBUaGluIHdyYXBwZXIgb3ZlciB0aGUgc2hhcmVkIGZpbGUtbG9jayBiYWNrZW5kXG4gKiAoYHNyYy9kYXRhYmFzZS9hZHZpc29yeS1sb2NrLWZpbGUtYmFja2VuZC5qc2Ag4oCUIHRoZSBTVEMtMjEwIGBmaWxlYCB0aWVyXG4gKiBwcmltaXRpdmUgZ2VuZXJhbGl6ZWQgZnJvbSB0aGlzIGxvY2sncyBvcmlnaW5hbCBoYW5kLXJvbGxlZCBkaXJlY3RvcnlcbiAqIHByb3RvY29sKSwgc28gdGhlIHNlcnZlciBsb2NrIGFuZCB0aGUgYWR2aXNvcnktbG9jayBmaWxlIHRpZXIgc2hhcmUgb25lXG4gKiBhY3F1aXNpdGlvbiwgb3duZXItbWV0YWRhdGEsIGxlYXNlLXJlbmV3YWwsIGFuZCBzdGFsZW5lc3MgaW1wbGVtZW50YXRpb24uXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFZlbG9jaW91c0h0dHBTZXJ2ZXJMb2NrIHtcbiAgLyoqXG4gICAqIEJ1aWxkIGEgbG9jayBmb3IgdGhlIGNvbmZpZ3VyZWQgYXBwbGljYXRpb24gZGlyZWN0b3J5IGFuZCBzZXJ2ZXIgZW5kcG9pbnQuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3B0aW9ucyBvYmplY3QuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBhcmdzLmNvbmZpZ3VyYXRpb24gLSBDb25maWd1cmF0aW9uIHRoYXQgb3ducyB0aGUgYXBwbGljYXRpb24gZGlyZWN0b3J5LlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5ob3N0IC0gQ29uZmlndXJlZCBIVFRQIGhvc3QuXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBhcmdzLnBvcnQgLSBDb25maWd1cmVkIEhUVFAgcG9ydC5cbiAgICovXG4gIGNvbnN0cnVjdG9yKHtjb25maWd1cmF0aW9uLCBob3N0LCBwb3J0fSkge1xuICAgIHRoaXMuY29uZmlndXJhdGlvbiA9IGNvbmZpZ3VyYXRpb25cbiAgICB0aGlzLmhvc3QgPSBob3N0XG4gICAgdGhpcy5wb3J0ID0gcG9ydFxuICAgIHRoaXMubG9ja1BhdGggPSBwYXRoLmpvaW4oY29uZmlndXJhdGlvbi5nZXREaXJlY3RvcnkoKSwgXCJ0bXBcIiwgXCJzZXJ2ZXIubG9ja1wiKVxuICAgIHRoaXMuYmFja2VuZCA9IG5ldyBBZHZpc29yeUxvY2tGaWxlQmFja2VuZCh7XG4gICAgICBkaXJlY3Rvcnk6IHBhdGguam9pbihjb25maWd1cmF0aW9uLmdldERpcmVjdG9yeSgpLCBcInRtcFwiKSxcbiAgICAgIGV4dHJhT3duZXJGaWVsZHM6IHtob3N0OiB0aGlzLmhvc3QsIHBvcnQ6IHRoaXMucG9ydH0sXG4gICAgICBwYXRoU3R5bGU6IFwicGxhaW5cIlxuICAgIH0pXG4gICAgdGhpcy5hY3F1aXJlZCA9IGZhbHNlXG4gIH1cblxuICAvKipcbiAgICogQWNxdWlyZXMgdGhlIGFwcC1kaXJlY3RvcnkgSFRUUCBzZXJ2ZXIgbG9jayBiZWZvcmUgc3RhcnR1cCBzaWRlIGVmZmVjdHNcbiAgICogcnVuLiBBIHN0YWxlIGxvY2sgKGRlYWQgbG9jYWwgb3duZXIgb3IgZXhwaXJlZCBsZWFzZSkgaXMgcmVjbGFpbWVkIGJ5IHRoZVxuICAgKiBiYWNrZW5kOyBhIGxpdmUgZm9yZWlnbiBvd25lciBmYWlscyBsb3VkbHkgd2l0aCB0aGUgb3duZXIgZGV0YWlscy5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgdGhlIGxvY2sgaGFzIGJlZW4gYWNxdWlyZWQuXG4gICAqL1xuICBhc3luYyBhY3F1aXJlKCkge1xuICAgIGNvbnN0IGFjcXVpcmVkID0gYXdhaXQgdGhpcy5iYWNrZW5kLmFjcXVpcmUoXCJzZXJ2ZXJcIiwge3RpbWVvdXRNczogMH0pXG5cbiAgICBpZiAoIWFjcXVpcmVkKSB0aHJvdyBuZXcgRXJyb3IoYXdhaXQgdGhpcy5sb2NrSGVsZE1lc3NhZ2UoKSlcblxuICAgIHRoaXMuYWNxdWlyZWQgPSB0cnVlXG4gIH1cblxuICAvKipcbiAgICogUmVsZWFzZXMgdGhlIGhlbGQgSFRUUCBzZXJ2ZXIgbG9jayBkaXJlY3RvcnkuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIGFmdGVyIGJlc3QtZWZmb3J0IGxvY2sgcmVsZWFzZS5cbiAgICovXG4gIGFzeW5jIHJlbGVhc2UoKSB7XG4gICAgaWYgKCF0aGlzLmFjcXVpcmVkKSByZXR1cm5cblxuICAgIHRoaXMuYWNxdWlyZWQgPSBmYWxzZVxuICAgIGF3YWl0IHRoaXMuYmFja2VuZC5yZWxlYXNlKFwic2VydmVyXCIpXG4gIH1cblxuICAvKipcbiAgICogUmVhZHMgb3duZXIgbWV0YWRhdGEgZnJvbSBhbiBleGlzdGluZyBzZXJ2ZXIgbG9jayBkaXJlY3RvcnkuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPGltcG9ydChcIi4uL2RhdGFiYXNlL2Fkdmlzb3J5LWxvY2stZmlsZS1iYWNrZW5kLmpzXCIpLkFkdmlzb3J5TG9ja0ZpbGVPd25lciB8IG51bGw+fSAtIFBhcnNlZCBvd25lciBtZXRhZGF0YSwgd2hlbiByZWFkYWJsZS5cbiAgICovXG4gIGFzeW5jIHJlYWRPd25lck1ldGFkYXRhKCkge1xuICAgIHJldHVybiBhd2FpdCB0aGlzLmJhY2tlbmQuaG9sZGVySW5mbyhcInNlcnZlclwiKVxuICB9XG5cbiAgLyoqXG4gICAqIEJ1aWxkcyBhIGR1cGxpY2F0ZS1zZXJ2ZXIgZXJyb3IgbWVzc2FnZSB3aXRoIG93bmVyIGRldGFpbHMgd2hlbiBhdmFpbGFibGUuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHN0cmluZz59IC0gRXJyb3IgbWVzc2FnZSBleHBsYWluaW5nIHdoaWNoIHNlcnZlciBvd25zIHRoZSBsb2NrLlxuICAgKi9cbiAgYXN5bmMgbG9ja0hlbGRNZXNzYWdlKCkge1xuICAgIGNvbnN0IG93bmVyID0gYXdhaXQgdGhpcy5yZWFkT3duZXJNZXRhZGF0YSgpXG4gICAgY29uc3QgZGV0YWlscyA9IG93bmVyXG4gICAgICA/IGBQSUQgJHtTdHJpbmcob3duZXIucGlkKX0gb24gJHtTdHJpbmcob3duZXIuaG9zdG5hbWUpfSAoJHtTdHJpbmcob3duZXIuaG9zdCl9OiR7U3RyaW5nKG93bmVyLnBvcnQpfSlgXG4gICAgICA6IGBsb2NrIGRpcmVjdG9yeSAke3RoaXMubG9ja1BhdGh9YFxuXG4gICAgcmV0dXJuIGBBIFZlbG9jaW91cyBIVFRQIHNlcnZlciBpcyBhbHJlYWR5IHJ1bm5pbmcgZm9yIHRoaXMgYXBwbGljYXRpb24gKCR7ZGV0YWlsc30pLiBSZW1vdmUgJHt0aGlzLmxvY2tQYXRofSBpZiB0aGUgc2VydmVyIGlzIG5vIGxvbmdlciBydW5uaW5nLmBcbiAgfVxufVxuIl19
|
package/package.json
CHANGED
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
* @property {string} [host] - Hostname to bind the HTTP server to.
|
|
330
330
|
* @property {boolean} [inProcess] - Run HTTP handlers in the main thread instead of worker threads.
|
|
331
331
|
* @property {number} [maxBufferedResponseBodyBytes] - Maximum UTF-8/binary byte length accepted by buffered response bodies. Unbounded when absent; streamed file responses are unaffected.
|
|
332
|
-
* @property {number} [maxRequestBodyBytes] - Maximum decoded request-body bytes accepted before routing. Unbounded when absent.
|
|
332
|
+
* @property {number} [maxRequestBodyBytes] - Maximum decoded request-body bytes accepted before routing. Unbounded when absent, except the built-in MCP endpoint, which defaults to a 1 MiB cap that this option overrides.
|
|
333
333
|
* @property {HttpRequestBodyPolicyResolver} [requestBodyPolicyResolver] - Optional synchronous header-stage resolver for per-request limits and raw-body handling.
|
|
334
334
|
* @property {number} [maxWorkers] - Backward-compatible alias for workers.
|
|
335
335
|
* @property {number} [port] - Port to bind the HTTP server to.
|
|
@@ -780,6 +780,16 @@
|
|
|
780
780
|
* @property {(args: {configuration: import("./configuration.js").default, databaseConfiguration: DatabaseConfigurationType, identifier: string, migrationsApplied: number, tenant: ReturnType<typeof JSON.parse>}) => void | Promise<void>} [afterMigrateTenant] - Runs app-owned tenant work after generic migrations for one tenant. `migrationsApplied` is how many migrations actually ran (0 when the tenant was already up to date), so the app can skip expensive per-tenant work on no-op deploys.
|
|
781
781
|
*/
|
|
782
782
|
|
|
783
|
+
/**
|
|
784
|
+
* Advisory-lock backend chain configuration (STC-210 design §10, file tier
|
|
785
|
+
* implemented by STC-213). Everything defaults to today's driver-native-only
|
|
786
|
+
* behavior; the file backend is disabled by default and only selectable by
|
|
787
|
+
* explicit configuration.
|
|
788
|
+
* @typedef {object} AdvisoryLocksConfiguration
|
|
789
|
+
* @property {{file?: {directory?: string, enabled?: boolean, leaseMs?: number, renewIntervalMs?: number}}} [backends] - Backend enablement. `file.enabled` defaults to false; enabling it is the deployment's explicit single-host declaration.
|
|
790
|
+
* @property {import("./database/advisory-lock-backend-policy.js").AdvisoryLockBackendPolicyEntry[]} [policy] - Per-lock-name minimum/allowed/preferred tier rules (design §5).
|
|
791
|
+
*/
|
|
792
|
+
|
|
783
793
|
/**
|
|
784
794
|
* @typedef {object} ConfigurationArgsType
|
|
785
795
|
* @property {boolean} [autoload] - Globally enable auto-batch-preload of relationships on lazy access. Default true.
|
|
@@ -787,6 +797,7 @@
|
|
|
787
797
|
* @property {string} [cookieSecret] - Secret for encrypting cookies.
|
|
788
798
|
* @property {AbilityResourceClassType[]} [abilityResources] - Resource classes used to define abilities per model.
|
|
789
799
|
* @property {AbilityResolverType} [abilityResolver] - Resolver for creating request-scoped ability instances.
|
|
800
|
+
* @property {AdvisoryLocksConfiguration} [advisoryLocks] - Advisory-lock backend chain configuration (backends + per-lock-name tier policy). Defaults to today's driver-native-only behavior.
|
|
790
801
|
* @property {AttachmentsConfiguration} [attachments] - Attachment storage configuration.
|
|
791
802
|
* @property {BackendProjectConfiguration[]} [backendProjects] - Backend project definitions used for frontend model generation.
|
|
792
803
|
* @property {VelociousPackageConfiguration[]} [packages] - External Velocious packages that contribute models, frontend-model resources and migrations.
|
package/src/configuration.js
CHANGED
|
@@ -45,6 +45,7 @@ import VelociousPackage from "./packages/velocious-package.js"
|
|
|
45
45
|
import FrontendTenantSqliteLifecycle from "./tenants/frontend-tenant-sqlite-lifecycle.js"
|
|
46
46
|
import { resolveGenerationId, resolveInitialGenerationState, resolveLifecycleSocketPath } from "./background-jobs/generation-identity.js"
|
|
47
47
|
import { runShutdownSteps } from "./utils/shutdown-lifecycle.js"
|
|
48
|
+
import { resolveAdvisoryLockBackendPolicy } from "./database/advisory-lock-backend-policy.js"
|
|
48
49
|
|
|
49
50
|
export { CurrentConfigurationNotSetError }
|
|
50
51
|
|
|
@@ -228,6 +229,14 @@ function normalizeHttpCompression(value) {
|
|
|
228
229
|
}
|
|
229
230
|
}
|
|
230
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Default request-body limit for the built-in MCP endpoint. JSON-RPC messages are small,
|
|
234
|
+
* so the framework caps them instead of making every app that enables `mcp: true` restate
|
|
235
|
+
* a request-body policy. Explicit `httpServer.maxRequestBodyBytes` or a resolver limit wins.
|
|
236
|
+
* @type {number}
|
|
237
|
+
*/
|
|
238
|
+
const DEFAULT_MCP_MAX_REQUEST_BODY_BYTES = 1024 * 1024
|
|
239
|
+
|
|
231
240
|
export default class VelociousConfiguration {
|
|
232
241
|
/**
|
|
233
242
|
* Close database connections promise.
|
|
@@ -245,6 +254,20 @@ export default class VelociousConfiguration {
|
|
|
245
254
|
* @type {Set<import("./database/drivers/base.js").default>} */
|
|
246
255
|
_advisoryLockConnections = new Set()
|
|
247
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Lazily created file-tier advisory-lock backend (STC-213). Stays `undefined`
|
|
259
|
+
* unless `advisoryLocks.backends.file.enabled: true` is explicitly configured —
|
|
260
|
+
* the file tier is never auto-enabled, and a non-Node environment handler
|
|
261
|
+
* (browser/Expo, no `fs`) cannot create one at all.
|
|
262
|
+
* @type {import("./database/advisory-lock-file-backend.js").default | undefined} */
|
|
263
|
+
_advisoryLockFileBackend = undefined
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Most recent advisory-lock acquisition per lock name — the "which backend
|
|
267
|
+
* served this lock" surface (design §3).
|
|
268
|
+
* @type {Map<string, {backend: import("./database/advisory-lock-backend-policy.js").AdvisoryLockTier, fencingSupported: boolean, servedAt: string}>} */
|
|
269
|
+
_advisoryLockAcquisitions = new Map()
|
|
270
|
+
|
|
248
271
|
/** @type {Map<string, number>} */
|
|
249
272
|
_schemaCacheGenerationsByReuseKey = new Map()
|
|
250
273
|
|
|
@@ -260,11 +283,12 @@ export default class VelociousConfiguration {
|
|
|
260
283
|
* Runs constructor.
|
|
261
284
|
* @param {import("./configuration-types.js").ConfigurationArgsType} args - Configuration arguments.
|
|
262
285
|
*/
|
|
263
|
-
constructor({abilityResolver, abilityResources, attachments, autoload = true, backgroundJobs, backendProjects, beacon, cookieSecret, cors, database, debug = false, debugEndpoint = false, apiManifest = false, directory, enforceTenantDatabaseScopes = true, environment, environmentHandler, exposeInternalErrorsToClients, frontendTenantSqlite, httpServer, initializeModels, initializers, locale, localeFallbacks, locales, logging, mailerBackend, mcp = false, packages, requestTimeoutMs, routeResolverHooks, scheduledBackgroundJobs, secureFrontendModelErrors, structureSql, sync, tenantDatabaseProviders, tenantDatabaseResolver, tenantResolver, testTenantPools, testing, timeZone, timezoneOffsetMinutes, trustedProxies, websocketChannelResolver, websocketMessageHandlerResolver, ...restArgs}) {
|
|
286
|
+
constructor({abilityResolver, abilityResources, advisoryLocks, attachments, autoload = true, backgroundJobs, backendProjects, beacon, cookieSecret, cors, database, debug = false, debugEndpoint = false, apiManifest = false, directory, enforceTenantDatabaseScopes = true, environment, environmentHandler, exposeInternalErrorsToClients, frontendTenantSqlite, httpServer, initializeModels, initializers, locale, localeFallbacks, locales, logging, mailerBackend, mcp = false, packages, requestTimeoutMs, routeResolverHooks, scheduledBackgroundJobs, secureFrontendModelErrors, structureSql, sync, tenantDatabaseProviders, tenantDatabaseResolver, tenantResolver, testTenantPools, testing, timeZone, timezoneOffsetMinutes, trustedProxies, websocketChannelResolver, websocketMessageHandlerResolver, ...restArgs}) {
|
|
264
287
|
restArgsError(restArgs)
|
|
265
288
|
|
|
266
289
|
this._abilityResolver = abilityResolver
|
|
267
290
|
this._abilityResources = abilityResources || []
|
|
291
|
+
this._advisoryLocks = advisoryLocks
|
|
268
292
|
this._autoload = autoload
|
|
269
293
|
this._backgroundJobs = backgroundJobs
|
|
270
294
|
this._beacon = beacon
|
|
@@ -760,31 +784,58 @@ export default class VelociousConfiguration {
|
|
|
760
784
|
resolveHttpRequestBodyPolicy(args) {
|
|
761
785
|
const resolver = this.httpServer.requestBodyPolicyResolver
|
|
762
786
|
const configuredPolicy = resolver ? resolver(args) : undefined
|
|
787
|
+
/** @type {import("./configuration-types.js").ResolvedHttpRequestBodyPolicy} */
|
|
788
|
+
let resolvedPolicy
|
|
763
789
|
|
|
764
790
|
if (configuredPolicy === undefined) {
|
|
765
|
-
|
|
766
|
-
}
|
|
791
|
+
resolvedPolicy = {maxRequestBodyBytes: this.httpServer.maxRequestBodyBytes, mode: "parsed"}
|
|
792
|
+
} else {
|
|
793
|
+
if (!configuredPolicy || typeof configuredPolicy !== "object" || Array.isArray(configuredPolicy)) {
|
|
794
|
+
throw new TypeError("httpServer.requestBodyPolicyResolver must return an object or undefined")
|
|
795
|
+
}
|
|
767
796
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
797
|
+
const {maxRequestBodyBytes, mode = "parsed", ...restPolicy} = configuredPolicy
|
|
798
|
+
const unknownKeys = Object.keys(restPolicy)
|
|
771
799
|
|
|
772
|
-
|
|
773
|
-
|
|
800
|
+
if (unknownKeys.length > 0) {
|
|
801
|
+
throw new TypeError(`httpServer.requestBodyPolicyResolver returned unknown keys: ${unknownKeys.join(", ")} (supported: maxRequestBodyBytes, mode)`)
|
|
802
|
+
}
|
|
803
|
+
if (mode !== "parsed" && mode !== "raw") {
|
|
804
|
+
throw new TypeError(`httpServer.requestBodyPolicyResolver mode must be "parsed" or "raw", got: ${String(mode)}`)
|
|
805
|
+
}
|
|
774
806
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
807
|
+
resolvedPolicy = {
|
|
808
|
+
maxRequestBodyBytes: maxRequestBodyBytes === undefined
|
|
809
|
+
? this.httpServer.maxRequestBodyBytes
|
|
810
|
+
: optionalPositiveSafeInteger(maxRequestBodyBytes, "httpServer.requestBodyPolicyResolver maxRequestBodyBytes"),
|
|
811
|
+
mode
|
|
812
|
+
}
|
|
780
813
|
}
|
|
781
814
|
|
|
782
|
-
return {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
815
|
+
return this._applyMcpRequestBodyPolicyDefaults({args, policy: resolvedPolicy})
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Enforces the built-in MCP endpoint's body contract without per-app plumbing: the
|
|
820
|
+
* JSON-RPC envelope is read from parsed params, so raw mode is rejected with an
|
|
821
|
+
* actionable error, and a framework default byte cap applies when the app configured none.
|
|
822
|
+
* @param {{args: import("./configuration-types.js").HttpRequestBodyPolicyResolverArgs, policy: import("./configuration-types.js").ResolvedHttpRequestBodyPolicy}} options - Resolution inputs.
|
|
823
|
+
* @returns {import("./configuration-types.js").ResolvedHttpRequestBodyPolicy} - Effective request policy.
|
|
824
|
+
*/
|
|
825
|
+
_applyMcpRequestBodyPolicyDefaults({args, policy}) {
|
|
826
|
+
if (!this._mcp.enabled) return policy
|
|
827
|
+
if (args.httpMethod !== "POST") return policy
|
|
828
|
+
if (args.path.split("?")[0] !== this._mcp.path) return policy
|
|
829
|
+
|
|
830
|
+
if (policy.mode === "raw") {
|
|
831
|
+
throw new TypeError(
|
|
832
|
+
`The built-in MCP endpoint at ${this._mcp.path} requires parsed request bodies because its JSON-RPC envelope is read from params. Remove the raw-mode httpServer.requestBodyPolicyResolver entry for this path.`
|
|
833
|
+
)
|
|
787
834
|
}
|
|
835
|
+
|
|
836
|
+
if (policy.maxRequestBodyBytes !== undefined) return policy
|
|
837
|
+
|
|
838
|
+
return {maxRequestBodyBytes: DEFAULT_MCP_MAX_REQUEST_BODY_BYTES, mode: policy.mode}
|
|
788
839
|
}
|
|
789
840
|
|
|
790
841
|
/**
|
|
@@ -4076,6 +4127,102 @@ export default class VelociousConfiguration {
|
|
|
4076
4127
|
})
|
|
4077
4128
|
}
|
|
4078
4129
|
|
|
4130
|
+
/**
|
|
4131
|
+
* Resolves the `advisoryLocks` configuration block (STC-210 design §10).
|
|
4132
|
+
* Full backward compatibility: an app that configures nothing gets today's
|
|
4133
|
+
* driver-native-only behavior — the file backend is **disabled by default**
|
|
4134
|
+
* and only ever selectable by explicit configuration.
|
|
4135
|
+
* @returns {{backends: {file: {directory?: string, enabled: boolean, leaseMs: number, renewIntervalMs: number}}, policy: import("./database/advisory-lock-backend-policy.js").AdvisoryLockBackendPolicyEntry[]}} - Advisory-lock configuration with defaults applied.
|
|
4136
|
+
*/
|
|
4137
|
+
getAdvisoryLockConfig() {
|
|
4138
|
+
const configured = this._advisoryLocks || {}
|
|
4139
|
+
const fileConfigured = configured.backends?.file || {}
|
|
4140
|
+
|
|
4141
|
+
return {
|
|
4142
|
+
backends: {
|
|
4143
|
+
file: {
|
|
4144
|
+
directory: fileConfigured.directory,
|
|
4145
|
+
enabled: fileConfigured.enabled === true,
|
|
4146
|
+
leaseMs: typeof fileConfigured.leaseMs === "number" ? fileConfigured.leaseMs : 30_000,
|
|
4147
|
+
renewIntervalMs: typeof fileConfigured.renewIntervalMs === "number" ? fileConfigured.renewIntervalMs : 10_000
|
|
4148
|
+
}
|
|
4149
|
+
},
|
|
4150
|
+
policy: Array.isArray(configured.policy) ? configured.policy : []
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
/**
|
|
4155
|
+
* Runs get advisory lock policy.
|
|
4156
|
+
* @returns {import("./database/advisory-lock-backend-policy.js").AdvisoryLockBackendPolicyEntry[]} - Per-lock-name backend policy entries.
|
|
4157
|
+
*/
|
|
4158
|
+
getAdvisoryLockPolicy() {
|
|
4159
|
+
return this.getAdvisoryLockConfig().policy
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
/**
|
|
4163
|
+
* Resolves the effective backend policy for one lock name (design §5).
|
|
4164
|
+
* @param {string} lockName - Concrete lock name.
|
|
4165
|
+
* @returns {import("./database/advisory-lock-backend-policy.js").ResolvedAdvisoryLockBackendPolicy} - Resolved policy.
|
|
4166
|
+
*/
|
|
4167
|
+
resolveAdvisoryLockPolicy(lockName) {
|
|
4168
|
+
return resolveAdvisoryLockBackendPolicy(lockName, this.getAdvisoryLockPolicy())
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
/**
|
|
4172
|
+
* Returns the file-tier advisory-lock backend when this deployment has
|
|
4173
|
+
* explicitly enabled it (`advisoryLocks.backends.file.enabled: true`),
|
|
4174
|
+
* creating it on first use. Returns `undefined` when the tier is disabled
|
|
4175
|
+
* (the default) — callers must never fall back to it implicitly. Creation
|
|
4176
|
+
* goes through the environment handler so browser/Expo configurations
|
|
4177
|
+
* without `fs` fail loudly instead of half-working.
|
|
4178
|
+
* @returns {Promise<import("./database/advisory-lock-file-backend.js").default | undefined>} - Resolves with the enabled file backend, or undefined.
|
|
4179
|
+
*/
|
|
4180
|
+
async getAdvisoryLockFileBackend() {
|
|
4181
|
+
const config = this.getAdvisoryLockConfig()
|
|
4182
|
+
|
|
4183
|
+
if (!config.backends.file.enabled) return undefined
|
|
4184
|
+
if (this._advisoryLockFileBackend) return this._advisoryLockFileBackend
|
|
4185
|
+
|
|
4186
|
+
const directory = config.backends.file.directory || `${this.getDirectory()}/tmp/advisory-locks`
|
|
4187
|
+
|
|
4188
|
+
this._advisoryLockFileBackend = await this.getEnvironmentHandler().createAdvisoryLockFileBackend({
|
|
4189
|
+
directory,
|
|
4190
|
+
leaseMs: config.backends.file.leaseMs,
|
|
4191
|
+
renewIntervalMs: config.backends.file.renewIntervalMs
|
|
4192
|
+
})
|
|
4193
|
+
|
|
4194
|
+
return this._advisoryLockFileBackend
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4197
|
+
/**
|
|
4198
|
+
* Records which backend tier served one advisory-lock acquisition so
|
|
4199
|
+
* `getAdvisoryLockBackendStatus()` can answer "which backend served this
|
|
4200
|
+
* lock" (design §3).
|
|
4201
|
+
* @param {object} args - Acquisition record.
|
|
4202
|
+
* @param {string} args.lockName - Lock name.
|
|
4203
|
+
* @param {import("./database/advisory-lock-backend-policy.js").AdvisoryLockTier} args.backend - Tier that served the acquisition.
|
|
4204
|
+
* @param {boolean} args.fencingSupported - Whether the serving tier carries fencing tokens.
|
|
4205
|
+
* @returns {void}
|
|
4206
|
+
*/
|
|
4207
|
+
recordAdvisoryLockAcquisition({lockName, backend, fencingSupported}) {
|
|
4208
|
+
this._advisoryLockAcquisitions.set(lockName, {backend, fencingSupported, servedAt: new Date().toISOString()})
|
|
4209
|
+
}
|
|
4210
|
+
|
|
4211
|
+
/**
|
|
4212
|
+
* The "which backend served this lock" diagnostics surface (design §3):
|
|
4213
|
+
* per lock name, the tier of the most recent acquisition, whether that tier
|
|
4214
|
+
* carries fencing tokens, and the resolved policy (minimum/allowed/preferred
|
|
4215
|
+
* tiers) for the name.
|
|
4216
|
+
* @param {string} lockName - Lock name.
|
|
4217
|
+
* @returns {{acquisition: {backend: import("./database/advisory-lock-backend-policy.js").AdvisoryLockTier, fencingSupported: boolean, servedAt: string} | null, policy: import("./database/advisory-lock-backend-policy.js").ResolvedAdvisoryLockBackendPolicy}} - Backend status for the name.
|
|
4218
|
+
*/
|
|
4219
|
+
getAdvisoryLockBackendStatus(lockName) {
|
|
4220
|
+
return {
|
|
4221
|
+
acquisition: this._advisoryLockAcquisitions.get(lockName) ?? null,
|
|
4222
|
+
policy: this.resolveAdvisoryLockPolicy(lockName)
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4079
4226
|
/**
|
|
4080
4227
|
* Registers a dedicated connection that currently holds an advisory lock, so a
|
|
4081
4228
|
* shutdown can close it and release the lock. See `_advisoryLockConnections`.
|
|
@@ -4155,6 +4302,15 @@ export default class VelociousConfiguration {
|
|
|
4155
4302
|
// leave the rest of the connections open.
|
|
4156
4303
|
await this._closeAdvisoryLockConnections()
|
|
4157
4304
|
} finally {
|
|
4305
|
+
try {
|
|
4306
|
+
// Release file-tier advisory-lock holds explicitly (STC-213): the
|
|
4307
|
+
// lease would make them reclaimable anyway, but a clean shutdown
|
|
4308
|
+
// should not leave lock directories behind for the staleness path.
|
|
4309
|
+
await this._advisoryLockFileBackend?.releaseAllHeld()
|
|
4310
|
+
} catch (error) {
|
|
4311
|
+
closeErrors.push(error instanceof Error ? error : new Error(String(error)))
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4158
4314
|
for (const pool of Object.values(this.databasePools)) {
|
|
4159
4315
|
if (!pool) continue
|
|
4160
4316
|
|