@dbx-tools/core 0.6.65 → 0.6.67

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 CHANGED
@@ -1,6 +1,7 @@
1
1
  # @dbx-tools/core
2
2
 
3
- Node-only core helpers for process execution and project discovery.
3
+ Node-only core helpers for binary installation, process execution, and project
4
+ discovery.
4
5
 
5
6
  Import this package when code needs `node:child_process`, `node:fs`, or
6
7
  `node:path`. Browser-safe utilities live in
@@ -16,6 +17,47 @@ Key features:
16
17
  the current working directory.
17
18
  - Safe filesystem stat and project naming helpers for CLIs and projen synth.
18
19
  - YAML/JSON brand-context discovery and loading with shared Zod validation.
20
+ - Idempotent executable downloads with zip/tar extraction and atomic installs.
21
+ - Keyed mutual exclusion across the main thread and its worker threads.
22
+
23
+ ## Install A Binary
24
+
25
+ ```ts
26
+ import { join } from "node:path";
27
+
28
+ import { bin } from "@dbx-tools/core";
29
+
30
+ const executable = await bin.ensure("tool", releaseUrl, {
31
+ autoUnpackage: true,
32
+ minVersion: "1.2",
33
+ selector: ({ source }) => join(source, "tool"),
34
+ });
35
+ ```
36
+
37
+ `bin.ensure()` installs to `$HOME/.<name>/bin/<name>` and returns its `root`,
38
+ `binDir`, and executable `path`. An existing executable returns immediately, so
39
+ a URL resolver is only called when installation is necessary. Concurrent
40
+ callers use `processLock` with a check-lock-check-load sequence, preventing
41
+ duplicate downloads across the main thread and wired workers. Direct downloads
42
+ are selected by default. Zip, tar, tar.gz, and tgz archives can be unpacked
43
+ automatically; a single-file archive needs no selector, while a selector can
44
+ choose a binary from a larger archive. The selected file is normalized to mode
45
+ `0755` and must report an acceptable version before it is atomically moved into
46
+ place; the final renamed path runs the same validation again before returning.
47
+
48
+ Every candidate runs with `--version` before it is accepted. Set
49
+ `versionArgument` for a different argument and `minVersion` to require a partial
50
+ or complete numeric floor such as `1`, `1.2`, or `1.3.5`. The default parser
51
+ searches stdout first, then stderr. It accepts one to three numeric components
52
+ with common npm/Python suffixes such as `1.2.3-rc.1`, `1.2.3rc1`, or
53
+ `1.2.3.patchdev`; candidates with more components rank first, then the highest
54
+ numeric candidate wins. Supply `versionParser({ stdout, stderr })` for another
55
+ output format.
56
+
57
+ Successful installs log an info event with the source URL and destination path;
58
+ credentials, query parameters, and fragments are removed from the URL. Set
59
+ `LOG_LEVEL=debug` to trace access checks, version decisions, locking, downloads,
60
+ archive extraction, and selection.
19
61
 
20
62
  ## Load Brand Context
21
63
 
@@ -80,6 +122,50 @@ const argv = exec.shlex('pnpm exec prettier --write "README.md"');
80
122
  `shlex()` is a small parser for command strings that need to become argv arrays.
81
123
  Prefer explicit argv arrays when possible.
82
124
 
125
+ ## Serialize Work Across Threads
126
+
127
+ ```ts
128
+ import { processLock } from "@dbx-tools/core";
129
+
130
+ await processLock.withProcessLock(["cache", name], async () => {
131
+ if (!(await exists(name))) await build(name);
132
+ });
133
+ ```
134
+
135
+ `withProcessLock()` runs the callback while holding the named lock, releasing it
136
+ when the callback settles. Callers sharing a key are serialized; distinct keys
137
+ run concurrently. The key is any value with a stable identity - a string, a
138
+ `["invoice", id]` tuple, a config object - canonicalized by `object.toStableKey`,
139
+ so structure counts: `["invoice", 7]` and `"invoice_7"` are different locks.
140
+
141
+ Use it instead of a module-level promise chain when worker threads are involved.
142
+ A promise chain only serializes the thread it lives on, so a worker pool runs one
143
+ callback per thread; the coordinator here is shared, so the key admits one
144
+ callback for the whole process.
145
+
146
+ Workers opt in when they are constructed:
147
+
148
+ ```ts
149
+ import { Worker } from "node:worker_threads";
150
+
151
+ new Worker(url, processLock.processLockWorkerOptions({ workerData: { tenant } }));
152
+ ```
153
+
154
+ That preserves your own `workerData` and `transferList`, and lets the worker lock
155
+ during module initialization. For a worker you did not construct, call
156
+ `attachProcessLock(worker)` and have the worker `await processLockAttached()`
157
+ first - the port arrives by message, so it is not available quite as early.
158
+
159
+ A thread that exits while holding a lock releases it, so a crashed worker cannot
160
+ wedge a key. Locks are held only as long as the callback runs, and an idle lock
161
+ never keeps the process from exiting.
162
+
163
+ The scope is the THREADS of one process - `withProcessLock` shares nothing with a
164
+ second `node` invocation or another replica. When the critical section spans a
165
+ deployment, use
166
+ [`@dbx-tools/postgres`](../postgres)'s `withAdvisoryLock`, which puts the arbiter
167
+ in PostgreSQL where every replica can see it.
168
+
83
169
  ## Discover Project Roots
84
170
 
85
171
  ```ts
@@ -97,6 +183,11 @@ basename. `project.stat()` returns `undefined` instead of throwing.
97
183
  ## Modules
98
184
 
99
185
  - `exec` - async/sync process spawning, stdio handling, abort wiring, and shlex.
186
+ - `bin` - executable download, optional archive extraction, selection, and
187
+ atomic installation.
100
188
  - `project` - root discovery, project naming, git-remote parsing, and safe
101
189
  filesystem stat.
102
190
  - `brand` - YAML/JSON discovery, parsing, validation, and asset path resolution.
191
+ - `processLock` - keyed mutual exclusion across the main thread and its workers,
192
+ with worker wiring (`processLockWorkerOptions`, `attachProcessLock`,
193
+ `processLockAttached`).
package/index.ts CHANGED
@@ -2,10 +2,13 @@
2
2
  // Regenerated from the exporting modules in ./src.
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
+ export * as bin from "./src/bin.ts";
5
6
  export * as brand from "./src/brand.ts";
6
7
  export * as exec from "./src/exec.ts";
7
8
  export * as file from "./src/file.ts";
9
+ export * as processLock from "./src/process-lock.ts";
8
10
  export * as project from "./src/project.ts";
11
+ export type { BinContext, BinSelectionContext, BinSelector, BinVersionOutput, BinVersionParser, BinOptions, BinUrl } from "./src/bin.ts";
9
12
  export { BrandContextSchema, defaultBrandContext, parseBrandContext, brandContextJsonSchema, brandContextPrompt } from "./src/brand.ts";
10
13
  export type { BrandContext, BrandContextInput } from "./src/brand.ts";
11
14
  export { COMMAND_NOT_FOUND_EXIT_CODE } from "./src/exec.ts";
package/lib/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
+ export * as bin from "./src/bin.ts";
1
2
  export * as brand from "./src/brand.ts";
2
3
  export * as exec from "./src/exec.ts";
3
4
  export * as file from "./src/file.ts";
5
+ export * as processLock from "./src/process-lock.ts";
4
6
  export * as project from "./src/project.ts";
7
+ export type { BinContext, BinSelectionContext, BinSelector, BinVersionOutput, BinVersionParser, BinOptions, BinUrl } from "./src/bin.ts";
5
8
  export { BrandContextSchema, defaultBrandContext, parseBrandContext, brandContextJsonSchema, brandContextPrompt } from "./src/brand.ts";
6
9
  export type { BrandContext, BrandContextInput } from "./src/brand.ts";
7
10
  export { COMMAND_NOT_FOUND_EXIT_CODE } from "./src/exec.ts";
package/lib/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  // GENERATED by projen watch - DO NOT EDIT.
2
2
  // Regenerated from the exporting modules in ./src.
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
+ export * as bin from "./src/bin.js";
4
5
  export * as brand from "./src/brand.js";
5
6
  export * as exec from "./src/exec.js";
6
7
  export * as file from "./src/file.js";
8
+ export * as processLock from "./src/process-lock.js";
7
9
  export * as project from "./src/project.js";
8
10
  export { BrandContextSchema, defaultBrandContext, parseBrandContext, brandContextJsonSchema, brandContextPrompt } from "./src/brand.js";
9
11
  export { COMMAND_NOT_FOUND_EXIT_CODE } from "./src/exec.js";
10
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLG1CQUFtQixFQUFFLGlCQUFpQixFQUFFLHNCQUFzQixFQUFFLGtCQUFrQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEksT0FBTyxFQUFFLDJCQUEyQixFQUFFLE1BQU0sZUFBZSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gR0VORVJBVEVEIGJ5IHByb2plbiB3YXRjaCAtIERPIE5PVCBFRElULlxuLy8gUmVnZW5lcmF0ZWQgZnJvbSB0aGUgZXhwb3J0aW5nIG1vZHVsZXMgaW4gLi9zcmMuXG4vLyBIYW5kIGVkaXRzIGFyZSBvdmVyd3JpdHRlbiBvbiB0aGUgbmV4dCB3YXRjaDsgdGhpcyBmaWxlIGlzIHJlYWQtb25seS5cblxuZXhwb3J0ICogYXMgYnJhbmQgZnJvbSBcIi4vc3JjL2JyYW5kLnRzXCI7XG5leHBvcnQgKiBhcyBleGVjIGZyb20gXCIuL3NyYy9leGVjLnRzXCI7XG5leHBvcnQgKiBhcyBmaWxlIGZyb20gXCIuL3NyYy9maWxlLnRzXCI7XG5leHBvcnQgKiBhcyBwcm9qZWN0IGZyb20gXCIuL3NyYy9wcm9qZWN0LnRzXCI7XG5leHBvcnQgeyBCcmFuZENvbnRleHRTY2hlbWEsIGRlZmF1bHRCcmFuZENvbnRleHQsIHBhcnNlQnJhbmRDb250ZXh0LCBicmFuZENvbnRleHRKc29uU2NoZW1hLCBicmFuZENvbnRleHRQcm9tcHQgfSBmcm9tIFwiLi9zcmMvYnJhbmQudHNcIjtcbmV4cG9ydCB0eXBlIHsgQnJhbmRDb250ZXh0LCBCcmFuZENvbnRleHRJbnB1dCB9IGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0IHsgQ09NTUFORF9OT1RfRk9VTkRfRVhJVF9DT0RFIH0gZnJvbSBcIi4vc3JjL2V4ZWMudHNcIjtcbmV4cG9ydCB0eXBlIHsgRXhlY1N0ZGlvLCBMaW5lSGFuZGxlciwgU3RkaW9PcHRpb24sIEV4ZWNSZXN1bHQsIENoaWxkUHJvY2Vzc1Jlc3VsdCwgRXhlY09wdGlvbnMsIFN5bmNFeGVjU3RkaW8sIFN5bmNFeGVjT3B0aW9ucywgU3Bhd25BcmdzIH0gZnJvbSBcIi4vc3JjL2V4ZWMudHNcIjtcbmV4cG9ydCB0eXBlIHsgUHJvamVjdENvbnRleHQgfSBmcm9tIFwiLi9zcmMvcHJvamVjdC50c1wiO1xuIl19
12
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssR0FBRyxNQUFNLGNBQWMsQ0FBQztBQUNwQyxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxXQUFXLE1BQU0sdUJBQXVCLENBQUM7QUFDckQsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUU1QyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsbUJBQW1CLEVBQUUsaUJBQWlCLEVBQUUsc0JBQXNCLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV4SSxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsTUFBTSxlQUFlLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBHRU5FUkFURUQgYnkgcHJvamVuIHdhdGNoIC0gRE8gTk9UIEVESVQuXG4vLyBSZWdlbmVyYXRlZCBmcm9tIHRoZSBleHBvcnRpbmcgbW9kdWxlcyBpbiAuL3NyYy5cbi8vIEhhbmQgZWRpdHMgYXJlIG92ZXJ3cml0dGVuIG9uIHRoZSBuZXh0IHdhdGNoOyB0aGlzIGZpbGUgaXMgcmVhZC1vbmx5LlxuXG5leHBvcnQgKiBhcyBiaW4gZnJvbSBcIi4vc3JjL2Jpbi50c1wiO1xuZXhwb3J0ICogYXMgYnJhbmQgZnJvbSBcIi4vc3JjL2JyYW5kLnRzXCI7XG5leHBvcnQgKiBhcyBleGVjIGZyb20gXCIuL3NyYy9leGVjLnRzXCI7XG5leHBvcnQgKiBhcyBmaWxlIGZyb20gXCIuL3NyYy9maWxlLnRzXCI7XG5leHBvcnQgKiBhcyBwcm9jZXNzTG9jayBmcm9tIFwiLi9zcmMvcHJvY2Vzcy1sb2NrLnRzXCI7XG5leHBvcnQgKiBhcyBwcm9qZWN0IGZyb20gXCIuL3NyYy9wcm9qZWN0LnRzXCI7XG5leHBvcnQgdHlwZSB7IEJpbkNvbnRleHQsIEJpblNlbGVjdGlvbkNvbnRleHQsIEJpblNlbGVjdG9yLCBCaW5WZXJzaW9uT3V0cHV0LCBCaW5WZXJzaW9uUGFyc2VyLCBCaW5PcHRpb25zLCBCaW5VcmwgfSBmcm9tIFwiLi9zcmMvYmluLnRzXCI7XG5leHBvcnQgeyBCcmFuZENvbnRleHRTY2hlbWEsIGRlZmF1bHRCcmFuZENvbnRleHQsIHBhcnNlQnJhbmRDb250ZXh0LCBicmFuZENvbnRleHRKc29uU2NoZW1hLCBicmFuZENvbnRleHRQcm9tcHQgfSBmcm9tIFwiLi9zcmMvYnJhbmQudHNcIjtcbmV4cG9ydCB0eXBlIHsgQnJhbmRDb250ZXh0LCBCcmFuZENvbnRleHRJbnB1dCB9IGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0IHsgQ09NTUFORF9OT1RfRk9VTkRfRVhJVF9DT0RFIH0gZnJvbSBcIi4vc3JjL2V4ZWMudHNcIjtcbmV4cG9ydCB0eXBlIHsgRXhlY1N0ZGlvLCBMaW5lSGFuZGxlciwgU3RkaW9PcHRpb24sIEV4ZWNSZXN1bHQsIENoaWxkUHJvY2Vzc1Jlc3VsdCwgRXhlY09wdGlvbnMsIFN5bmNFeGVjU3RkaW8sIFN5bmNFeGVjT3B0aW9ucywgU3Bhd25BcmdzIH0gZnJvbSBcIi4vc3JjL2V4ZWMudHNcIjtcbmV4cG9ydCB0eXBlIHsgUHJvamVjdENvbnRleHQgfSBmcm9tIFwiLi9zcmMvcHJvamVjdC50c1wiO1xuIl19
@@ -0,0 +1,52 @@
1
+ /** Stable paths for an installed binary. */
2
+ export interface BinContext {
3
+ root: string;
4
+ binDir: string;
5
+ path: string;
6
+ }
7
+ /** Temporary download and extraction paths supplied to a custom selector. */
8
+ export interface BinSelectionContext {
9
+ destination: BinContext;
10
+ downloadPath: string;
11
+ source: string;
12
+ }
13
+ /**
14
+ * Select the executable from a download or unpacked archive. A selector may
15
+ * also prepare the file, such as applying its executable mode.
16
+ */
17
+ export type BinSelector = (context: BinSelectionContext) => string | Promise<string>;
18
+ /** Captured output passed to a custom binary version parser. */
19
+ export interface BinVersionOutput {
20
+ stdout: string;
21
+ stderr: string;
22
+ }
23
+ /** Extract a version string from a successful version-command result. */
24
+ export type BinVersionParser = (output: BinVersionOutput) => string | undefined;
25
+ /** Options for {@link ensure}. */
26
+ export interface BinOptions {
27
+ autoUnpackage?: boolean;
28
+ selector?: BinSelector;
29
+ homeDir?: string;
30
+ /** Minimum accepted numeric version, with one to three components. */
31
+ minVersion?: string;
32
+ /** Argument passed to the binary for version detection. Defaults to `--version`. */
33
+ versionArgument?: string;
34
+ /** Version output parser. Defaults to {@link parseVersion}. */
35
+ versionParser?: BinVersionParser;
36
+ }
37
+ /** A URL resolved only when the executable is not already installed. */
38
+ export type BinUrl = string | (() => string | Promise<string>);
39
+ /**
40
+ * Parse the deepest, highest version from stdout, falling back to stderr only
41
+ * when stdout contains no version. Supports one to three numeric components
42
+ * and common suffixes such as `rc1`, `.post1`, and `-dev.2`.
43
+ */
44
+ export declare function parseVersion({ stdout, stderr }: BinVersionOutput): string | undefined;
45
+ /**
46
+ * Return an existing executable or install it atomically under
47
+ * `$HOME/.<name>/bin/<name>`. Installation uses a check-lock-check-load
48
+ * sequence so concurrent callers resolve and download the binary only once.
49
+ * The downloaded candidate and final renamed executable must both pass the
50
+ * same executable and version checks.
51
+ */
52
+ export declare function ensure(name: string, url: BinUrl, options?: BinOptions): Promise<BinContext>;
package/lib/src/bin.js ADDED
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Install and reuse executable binaries under a per-tool home directory.
3
+ *
4
+ * @module
5
+ */
6
+ import { execFile } from "node:child_process";
7
+ import { randomUUID } from "node:crypto";
8
+ import { chmod, copyFile, mkdir, mkdtemp, readdir, rename, rm, stat, writeFile, } from "node:fs/promises";
9
+ import { homedir, tmpdir } from "node:os";
10
+ import { basename, join } from "node:path";
11
+ import { promisify } from "node:util";
12
+ import extractZip from "extract-zip";
13
+ import { x as extractTar } from "tar";
14
+ import { error, log } from "@dbx-tools/shared-core";
15
+ import { withProcessLock } from "./process-lock.js";
16
+ const execFileAsync = promisify(execFile);
17
+ const logger = log.logger("core:bin");
18
+ function context(name, homeDir) {
19
+ if (!name || basename(name) !== name || name === "." || name === "..") {
20
+ throw new TypeError(`invalid binary name: ${name}`);
21
+ }
22
+ const root = join(homeDir, `.${name}`);
23
+ const binDir = join(root, "bin");
24
+ return { root, binDir, path: join(binDir, name) };
25
+ }
26
+ function displayUrl(raw) {
27
+ try {
28
+ const url = new URL(raw);
29
+ if (url.protocol === "data:")
30
+ return "data:";
31
+ url.username = "";
32
+ url.password = "";
33
+ url.search = "";
34
+ url.hash = "";
35
+ return url.toString();
36
+ }
37
+ catch {
38
+ return raw;
39
+ }
40
+ }
41
+ async function accessContext(path) {
42
+ try {
43
+ const entry = await stat(path);
44
+ const file = entry.isFile();
45
+ return {
46
+ file,
47
+ executable: file && (process.platform === "win32" || (entry.mode & 0o111) !== 0),
48
+ };
49
+ }
50
+ catch {
51
+ return { file: false, executable: false };
52
+ }
53
+ }
54
+ function detectedVersions(output) {
55
+ const versions = [];
56
+ const pattern = /\bv?(\d+(?:\.\d+){0,2})(?:[-+._]?[a-z][0-9a-z.+_-]*)?/gi;
57
+ for (const match of output.matchAll(pattern)) {
58
+ const raw = match[0].replace(/^v/i, "");
59
+ const parts = match[1]?.split(".").map(Number);
60
+ if (parts?.every(Number.isFinite))
61
+ versions.push({ raw, parts });
62
+ }
63
+ return versions.sort((a, b) => {
64
+ if (a.parts.length !== b.parts.length)
65
+ return b.parts.length - a.parts.length;
66
+ for (let index = 0; index < a.parts.length; index += 1) {
67
+ const order = (b.parts[index] ?? 0) - (a.parts[index] ?? 0);
68
+ if (order !== 0)
69
+ return order;
70
+ }
71
+ return 0;
72
+ });
73
+ }
74
+ /**
75
+ * Parse the deepest, highest version from stdout, falling back to stderr only
76
+ * when stdout contains no version. Supports one to three numeric components
77
+ * and common suffixes such as `rc1`, `.post1`, and `-dev.2`.
78
+ */
79
+ export function parseVersion({ stdout, stderr }) {
80
+ return detectedVersions(stdout).at(0)?.raw ?? detectedVersions(stderr).at(0)?.raw;
81
+ }
82
+ function numericVersion(version, strict) {
83
+ const pattern = strict ? /^\s*v?(\d+(?:\.\d+){0,2})\s*$/i : /\bv?(\d+(?:\.\d+){0,2})/i;
84
+ const match = pattern.exec(version);
85
+ return match?.[1]?.split(".").map(Number);
86
+ }
87
+ function meetsMinVersion(version, minVersion) {
88
+ if (!minVersion)
89
+ return true;
90
+ const actual = numericVersion(version, false);
91
+ const minimum = numericVersion(minVersion, true);
92
+ if (!minimum) {
93
+ throw new TypeError(`invalid minimum binary version: ${minVersion}`);
94
+ }
95
+ if (!actual)
96
+ return false;
97
+ for (let index = 0; index < Math.max(actual.length, minimum.length); index += 1) {
98
+ const difference = (actual[index] ?? 0) - (minimum[index] ?? 0);
99
+ if (difference !== 0)
100
+ return difference > 0;
101
+ }
102
+ return true;
103
+ }
104
+ async function isValidBin(path, options) {
105
+ logger.debug("checking binary", {
106
+ path,
107
+ minVersion: options.minVersion,
108
+ versionArgument: options.versionArgument ?? "--version",
109
+ });
110
+ const access = await accessContext(path);
111
+ if (!access.file || !access.executable) {
112
+ logger.debug("binary access check failed", { path, ...access });
113
+ return false;
114
+ }
115
+ let stdout;
116
+ let stderr;
117
+ try {
118
+ const result = await execFileAsync(path, [options.versionArgument ?? "--version"], {
119
+ encoding: "utf8",
120
+ });
121
+ stdout = result.stdout;
122
+ stderr = result.stderr;
123
+ }
124
+ catch (cause) {
125
+ logger.debug("binary version command failed", {
126
+ path,
127
+ error: error.errorMessage(cause),
128
+ });
129
+ return false;
130
+ }
131
+ const version = (options.versionParser ?? parseVersion)({
132
+ stdout,
133
+ stderr,
134
+ });
135
+ const valid = version !== undefined && meetsMinVersion(version, options.minVersion);
136
+ logger.debug("binary version checked", {
137
+ path,
138
+ version,
139
+ minVersion: options.minVersion,
140
+ valid,
141
+ });
142
+ return valid;
143
+ }
144
+ function downloadName(url, name) {
145
+ try {
146
+ const candidate = basename(decodeURIComponent(new URL(url).pathname));
147
+ return candidate && Buffer.byteLength(candidate) <= 200 ? candidate : name;
148
+ }
149
+ catch {
150
+ return name;
151
+ }
152
+ }
153
+ async function unpack(archive, destination) {
154
+ const filename = archive.toLowerCase();
155
+ if (filename.endsWith(".zip")) {
156
+ await extractZip(archive, { dir: destination });
157
+ return;
158
+ }
159
+ if (filename.endsWith(".tar") || filename.endsWith(".tar.gz") || filename.endsWith(".tgz")) {
160
+ await extractTar({ file: archive, cwd: destination });
161
+ return;
162
+ }
163
+ throw new Error(`unsupported binary archive: ${basename(archive)}`);
164
+ }
165
+ async function filesUnder(root) {
166
+ const entries = await readdir(root, { withFileTypes: true });
167
+ const files = [];
168
+ for (const entry of entries) {
169
+ const path = join(root, entry.name);
170
+ if (entry.isDirectory()) {
171
+ files.push(...(await filesUnder(path)));
172
+ }
173
+ else if (entry.isFile()) {
174
+ files.push(path);
175
+ }
176
+ }
177
+ return files;
178
+ }
179
+ async function selectSingleFile(source) {
180
+ const files = await filesUnder(source);
181
+ const selected = files.at(0);
182
+ if (files.length !== 1 || !selected) {
183
+ throw new Error(`binary archive must contain one file, found ${files.length}`);
184
+ }
185
+ return selected;
186
+ }
187
+ async function selectedBin(destination, url, temp, options) {
188
+ const name = downloadName(url, basename(destination.path));
189
+ const downloadPath = join(temp, name);
190
+ logger.debug("downloading binary", {
191
+ from: displayUrl(url),
192
+ to: downloadPath,
193
+ });
194
+ const response = await fetch(url);
195
+ if (!response.ok) {
196
+ throw new Error(`binary download failed (${response.status})`);
197
+ }
198
+ await writeFile(downloadPath, Buffer.from(await response.arrayBuffer()), { mode: 0o755 });
199
+ let source = downloadPath;
200
+ if (options.autoUnpackage) {
201
+ source = join(temp, `unpacked-${randomUUID()}`);
202
+ await mkdir(source);
203
+ logger.debug("unpacking binary archive", {
204
+ archive: downloadPath,
205
+ to: source,
206
+ });
207
+ await unpack(downloadPath, source);
208
+ }
209
+ if (options.selector) {
210
+ const selected = await options.selector({ destination, downloadPath, source });
211
+ logger.debug("selected binary", { path: selected });
212
+ return selected;
213
+ }
214
+ const selected = options.autoUnpackage ? await selectSingleFile(source) : source;
215
+ logger.debug("selected binary", { path: selected });
216
+ return selected;
217
+ }
218
+ /**
219
+ * Return an existing executable or install it atomically under
220
+ * `$HOME/.<name>/bin/<name>`. Installation uses a check-lock-check-load
221
+ * sequence so concurrent callers resolve and download the binary only once.
222
+ * The downloaded candidate and final renamed executable must both pass the
223
+ * same executable and version checks.
224
+ */
225
+ export async function ensure(name, url, options = {}) {
226
+ if (options.minVersion && !numericVersion(options.minVersion, true)) {
227
+ throw new TypeError(`invalid minimum binary version: ${options.minVersion}`);
228
+ }
229
+ const destination = context(name, options.homeDir ?? homedir());
230
+ if (await isValidBin(destination.path, options)) {
231
+ logger.debug("using installed binary", { name, path: destination.path });
232
+ return destination;
233
+ }
234
+ logger.debug("waiting for binary install lock", { name, path: destination.path });
235
+ return withProcessLock(["bin.ensure", destination.path], async () => {
236
+ if (await isValidBin(destination.path, options)) {
237
+ logger.debug("using binary installed by another caller", {
238
+ name,
239
+ path: destination.path,
240
+ });
241
+ return destination;
242
+ }
243
+ const resolvedUrl = typeof url === "function" ? await url() : url;
244
+ const from = displayUrl(resolvedUrl);
245
+ logger.debug("installing binary", {
246
+ name,
247
+ from,
248
+ to: destination.path,
249
+ minVersion: options.minVersion,
250
+ });
251
+ const temp = await mkdtemp(join(tmpdir(), `${name}-`));
252
+ let staged;
253
+ try {
254
+ const selected = await selectedBin(destination, resolvedUrl, temp, options);
255
+ await chmod(selected, 0o755);
256
+ if (!(await isValidBin(selected, options))) {
257
+ throw new Error(`selected binary has no acceptable version: ${selected}`);
258
+ }
259
+ await mkdir(destination.binDir, { recursive: true });
260
+ staged = join(destination.binDir, `.${name}-${randomUUID()}`);
261
+ await copyFile(selected, staged);
262
+ await chmod(staged, 0o755);
263
+ await rename(staged, destination.path);
264
+ staged = undefined;
265
+ if (!(await isValidBin(destination.path, options))) {
266
+ throw new Error(`installed binary is invalid after rename: ${destination.path}`);
267
+ }
268
+ logger.info("installed binary", {
269
+ name,
270
+ from,
271
+ to: destination.path,
272
+ });
273
+ return destination;
274
+ }
275
+ finally {
276
+ if (staged)
277
+ await rm(staged, { force: true });
278
+ await rm(temp, { recursive: true, force: true });
279
+ }
280
+ });
281
+ }
282
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmluLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2Jpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7OztHQUlHO0FBQ0gsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDekMsT0FBTyxFQUNMLEtBQUssRUFDTCxRQUFRLEVBQ1IsS0FBSyxFQUNMLE9BQU8sRUFDUCxPQUFPLEVBQ1AsTUFBTSxFQUNOLEVBQUUsRUFDRixJQUFJLEVBQ0osU0FBUyxHQUNWLE1BQU0sa0JBQWtCLENBQUM7QUFDMUIsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDMUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDM0MsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUV0QyxPQUFPLFVBQVUsTUFBTSxhQUFhLENBQUM7QUFDckMsT0FBTyxFQUFFLENBQUMsSUFBSSxVQUFVLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFFdEMsT0FBTyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNwRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFcEQsTUFBTSxhQUFhLEdBQUcsU0FBUyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQzFDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7QUF5RHRDLFNBQVMsT0FBTyxDQUFDLElBQVksRUFBRSxPQUFlO0lBQzVDLElBQUksQ0FBQyxJQUFJLElBQUksUUFBUSxDQUFDLElBQUksQ0FBQyxLQUFLLElBQUksSUFBSSxJQUFJLEtBQUssR0FBRyxJQUFJLElBQUksS0FBSyxJQUFJLEVBQUUsQ0FBQztRQUN0RSxNQUFNLElBQUks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LEVBQUUsSUFBSSxJQUFJLElBQUksVUFBVSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQzlELE1BQU0sUUFBUSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQztZQUNqQyxNQUFNLEtBQUssQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7WUFDM0IsTUFBTSxNQUFNLENBQUMsTUFBTSxFQUFFLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUN2QyxNQUFNLEdBQUcsU0FBUyxDQUFDO1lBQ25CLElBQUksQ0FBQyxDQUFDLE1BQU0sVUFBVSxDQUFDLFdBQVcsQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUMsRUFBRSxDQUFDO2dCQUNuRCxNQUFNLElBQUksS0FBSyxDQUFDLDZDQUE2QyxXQUFXLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUNuRixDQUFDO1lBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxrQkFBa0IsRUFBRTtnQkFDOUIsSUFBSTtnQkFDSixJQUFJO2dCQUNKLEVBQUUsRUFBRSxXQUFXLENBQUMsSUFBSTthQUNyQixDQUFDLENBQUM7WUFDSCxPQUFPLFdBQVcsQ0FBQztRQUNyQixDQUFDO2dCQUFTLENBQUM7WUFDVCxJQUFJLE1BQU07Z0JBQUUsTUFBTSxFQUFFLENBQUMsTUFBTSxFQUFFLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7WUFDOUMsTUFBTSxFQUFFLENBQUMsSUFBSSxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUNuRCxDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBJbnN0YWxsIGFuZCByZXVzZSBleGVjdXRhYmxlIGJpbmFyaWVzIHVuZGVyIGEgcGVyLXRvb2wgaG9tZSBkaXJlY3RvcnkuXG4gKlxuICogQG1vZHVsZVxuICovXG5pbXBvcnQgeyBleGVjRmlsZSB9IGZyb20gXCJub2RlOmNoaWxkX3Byb2Nlc3NcIjtcbmltcG9ydCB7IHJhbmRvbVVVSUQgfSBmcm9tIFwibm9kZTpjcnlwdG9cIjtcbmltcG9ydCB7XG4gIGNobW9kLFxuICBjb3B5RmlsZSxcbiAgbWtkaXIsXG4gIG1rZHRlbXAsXG4gIHJlYWRkaXIsXG4gIHJlbmFtZSxcbiAgcm0sXG4gIHN0YXQsXG4gIHdyaXRlRmlsZSxcbn0gZnJvbSBcIm5vZGU6ZnMvcHJvbWlzZXNcIjtcbmltcG9ydCB7IGhvbWVkaXIsIHRtcGRpciB9IGZyb20gXCJub2RlOm9zXCI7XG5pbXBvcnQgeyBiYXNlbmFtZSwgam9pbiB9IGZyb20gXCJub2RlOnBhdGhcIjtcbmltcG9ydCB7IHByb21pc2lmeSB9IGZyb20gXCJub2RlOnV0aWxcIjtcblxuaW1wb3J0IGV4dHJhY3RaaXAgZnJvbSBcImV4dHJhY3QtemlwXCI7XG5pbXBvcnQgeyB4IGFzIGV4dHJhY3RUYXIgfSBmcm9tIFwidGFyXCI7XG5cbmltcG9ydCB7IGVycm9yLCBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgd2l0aFByb2Nlc3NMb2NrIH0gZnJvbSBcIi4vcHJvY2Vzcy1sb2NrLnRzXCI7XG5cbmNvbnN0IGV4ZWNGaWxlQXN5bmMgPSBwcm9taXNpZnkoZXhlY0ZpbGUpO1xuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcImNvcmU6YmluXCIpO1xuXG5pbnRlcmZhY2UgUGFyc2VkVmVyc2lvbiB7XG4gIHJhdzogc3RyaW5nO1xuICBwYXJ0czogbnVtYmVyW107XG59XG5cbmludGVyZmFjZSBCaW5BY2Nlc3NDb250ZXh0IHtcbiAgZmlsZTogYm9vbGVhbjtcbiAgZXhlY3V0YWJsZTogYm9vbGVhbjtcbn1cblxuLyoqIFN0YWJsZSBwYXRocyBmb3IgYW4gaW5zdGFsbGVkIGJpbmFyeS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQmluQ29udGV4dCB7XG4gIHJvb3Q6IHN0cmluZztcbiAgYmluRGlyOiBzdHJpbmc7XG4gIHBhdGg6IHN0cmluZztcbn1cblxuLyoqIFRlbXBvcmFyeSBkb3dubG9hZCBhbmQgZXh0cmFjdGlvbiBwYXRocyBzdXBwbGllZCB0byBhIGN1c3RvbSBzZWxlY3Rvci4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQmluU2VsZWN0aW9uQ29udGV4dCB7XG4gIGRlc3RpbmF0aW9uOiBCaW5Db250ZXh0O1xuICBkb3dubG9hZFBhdGg6IHN0cmluZztcbiAgc291cmNlOiBzdHJpbmc7XG59XG5cbi8qKlxuICogU2VsZWN0IHRoZSBleGVjdXRhYmxlIGZyb20gYSBkb3dubG9hZCBvciB1bnBhY2tlZCBhcmNoaXZlLiBBIHNlbGVjdG9yIG1heVxuICogYWxzbyBwcmVwYXJlIHRoZSBmaWxlLCBzdWNoIGFzIGFwcGx5aW5nIGl0cyBleGVjdXRhYmxlIG1vZGUuXG4gKi9cbmV4cG9ydCB0eXBlIEJpblNlbGVjdG9yID0gKGNvbnRleHQ6IEJpblNlbGVjdGlvbkNvbnRleHQpID0+IHN0cmluZyB8IFByb21pc2U8c3RyaW5nPjtcblxuLyoqIENhcHR1cmVkIG91dHB1dCBwYXNzZWQgdG8gYSBjdXN0b20gYmluYXJ5IHZlcnNpb24gcGFyc2VyLiAqL1xuZXhwb3J0IGludGVyZmFjZSBCaW5WZXJzaW9uT3V0cHV0IHtcbiAgc3Rkb3V0OiBzdHJpbmc7XG4gIHN0ZGVycjogc3RyaW5nO1xufVxuXG4vKiogRXh0cmFjdCBhIHZlcnNpb24gc3RyaW5nIGZyb20gYSBzdWNjZXNzZnVsIHZlcnNpb24tY29tbWFuZCByZXN1bHQuICovXG5leHBvcnQgdHlwZSBCaW5WZXJzaW9uUGFyc2VyID0gKG91dHB1dDogQmluVmVyc2lvbk91dHB1dCkgPT4gc3RyaW5nIHwgdW5kZWZpbmVkO1xuXG4vKiogT3B0aW9ucyBmb3Ige0BsaW5rIGVuc3VyZX0uICovXG5leHBvcnQgaW50ZXJmYWNlIEJpbk9wdGlvbnMge1xuICBhdXRvVW5wYWNrYWdlPzogYm9vbGVhbjtcbiAgc2VsZWN0b3I/OiBCaW5TZWxlY3RvcjtcbiAgaG9tZURpcj86IHN0cmluZztcbiAgLyoqIE1pbmltdW0gYWNjZXB0ZWQgbnVtZXJpYyB2ZXJzaW9uLCB3aXRoIG9uZSB0byB0aHJlZSBjb21wb25lbnRzLiAqL1xuICBtaW5WZXJzaW9uPzogc3RyaW5nO1xuICAvKiogQXJndW1lbnQgcGFzc2VkIHRvIHRoZSBiaW5hcnkgZm9yIHZlcnNpb24gZGV0ZWN0aW9uLiBEZWZhdWx0cyB0byBgLS12ZXJzaW9uYC4gKi9cbiAgdmVyc2lvbkFyZ3VtZW50Pzogc3RyaW5nO1xuICAvKiogVmVyc2lvbiBvdXRwdXQgcGFyc2VyLiBEZWZhdWx0cyB0byB7QGxpbmsgcGFyc2VWZXJzaW9ufS4gKi9cbiAgdmVyc2lvblBhcnNlcj86IEJpblZlcnNpb25QYXJzZXI7XG59XG5cbi8qKiBBIFVSTCByZXNvbHZlZCBvbmx5IHdoZW4gdGhlIGV4ZWN1dGFibGUgaXMgbm90IGFscmVhZHkgaW5zdGFsbGVkLiAqL1xuZXhwb3J0IHR5cGUgQmluVXJsID0gc3RyaW5nIHwgKCgpID0+IHN0cmluZyB8IFByb21pc2U8c3RyaW5nPik7XG5cbmZ1bmN0aW9uIGNvbnRleHQobmFtZTogc3RyaW5nLCBob21lRGlyOiBzdHJpbmcpOiBCaW5Db250ZXh0IHtcbiAgaWYgKCFuYW1lIHx8IGJhc2VuYW1lKG5hbWUpICE9PSBuYW1lIHx8IG5hbWUgPT09IFwiLlwiIHx8IG5hbWUgPT09IFwiLi5cIikge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoYGludmFsaWQgYmluYXJ5IG5hbWU6ICR7bmFtZX1gKTtcbiAgfVxuICBjb25zdCByb290ID0gam9pbihob21lRGlyLCBgLiR7bmFtZX1gKTtcbiAgY29uc3QgYmluRGlyID0gam9pbihyb290LCBcImJpblwiKTtcbiAgcmV0dXJuIHsgcm9vdCwgYmluRGlyLCBwYXRoOiBqb2luKGJpbkRpciwgbmFtZSkgfTtcbn1cblxuZnVuY3Rpb24gZGlzcGxheVVybChyYXc6IHN0cmluZyk6IHN0cmluZyB7XG4gIHRyeSB7XG4gICAgY29uc3QgdXJsID0gbmV3IFVSTChyYXcpO1xuICAgIGlmICh1cmwucHJvdG9jb2wgPT09IFwiZGF0YTpcIikgcmV0dXJuIFwiZGF0YTpcIjtcbiAgICB1cmwudXNlcm5hbWUgPSBcIlwiO1xuICAgIHVybC5wYXNzd29yZCA9IFwiXCI7XG4gICAgdXJsLnNlYXJjaCA9IFwiXCI7XG4gICAgdXJsLmhhc2ggPSBcIlwiO1xuICAgIHJldHVybiB1cmwudG9TdHJpbmcoKTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIHJhdztcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBhY2Nlc3NDb250ZXh0KHBhdGg6IHN0cmluZyk6IFByb21pc2U8QmluQWNjZXNzQ29udGV4dD4ge1xuICB0cnkge1xuICAgIGNvbnN0IGVudHJ5ID0gYXdhaXQgc3RhdChwYXRoKTtcbiAgICBjb25zdCBmaWxlID0gZW50cnkuaXNGaWxlKCk7XG4gICAgcmV0dXJuIHtcbiAgICAgIGZpbGUsXG4gICAgICBleGVjdXRhYmxlOiBmaWxlICYmIChwcm9jZXNzLnBsYXRmb3JtID09PSBcIndpbjMyXCIgfHwgKGVudHJ5Lm1vZGUgJiAwbzExMSkgIT09IDApLFxuICAgIH07XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiB7IGZpbGU6IGZhbHNlLCBleGVjdXRhYmxlOiBmYWxzZSB9O1xuICB9XG59XG5cbmZ1bmN0aW9uIGRldGVjdGVkVmVyc2lvbnMob3V0cHV0OiBzdHJpbmcpOiBQYXJzZWRWZXJzaW9uW10ge1xuICBjb25zdCB2ZXJzaW9uczogUGFyc2VkVmVyc2lvbltdID0gW107XG4gIGNvbnN0IHBhdHRlcm4gPSAvXFxidj8oXFxkKyg/OlxcLlxcZCspezAsMn0pKD86Wy0rLl9dP1thLXpdWzAtOWEtei4rXy1dKik/L2dpO1xuICBmb3IgKGNvbnN0IG1hdGNoIG9mIG91dHB1dC5tYXRjaEFsbChwYXR0ZXJuKSkge1xuICAgIGNvbnN0IHJhdyA9IG1hdGNoWzBdLnJlcGxhY2UoL152L2ksIFwiXCIpO1xuICAgIGNvbnN0IHBhcnRzID0gbWF0Y2hbMV0/LnNwbGl0KFwiLlwiKS5tYXAoTnVtYmVyKTtcbiAgICBpZiAocGFydHM/LmV2ZXJ5KE51bWJlci5pc0Zpbml0ZSkpIHZlcnNpb25zLnB1c2goeyByYXcsIHBhcnRzIH0pO1xuICB9XG4gIHJldHVybiB2ZXJzaW9ucy5zb3J0KChhLCBiKSA9PiB7XG4gICAgaWYgKGEucGFydHMubGVuZ3RoICE9PSBiLnBhcnRzLmxlbmd0aCkgcmV0dXJuIGIucGFydHMubGVuZ3RoIC0gYS5wYXJ0cy5sZW5ndGg7XG4gICAgZm9yIChsZXQgaW5kZXggPSAwOyBpbmRleCA8IGEucGFydHMubGVuZ3RoOyBpbmRleCArPSAxKSB7XG4gICAgICBjb25zdCBvcmRlciA9IChiLnBhcnRzW2luZGV4XSA/PyAwKSAtIChhLnBhcnRzW2luZGV4XSA/PyAwKTtcbiAgICAgIGlmIChvcmRlciAhPT0gMCkgcmV0dXJuIG9yZGVyO1xuICAgIH1cbiAgICByZXR1cm4gMDtcbiAgfSk7XG59XG5cbi8qKlxuICogUGFyc2UgdGhlIGRlZXBlc3QsIGhpZ2hlc3QgdmVyc2lvbiBmcm9tIHN0ZG91dCwgZmFsbGluZyBiYWNrIHRvIHN0ZGVyciBvbmx5XG4gKiB3aGVuIHN0ZG91dCBjb250YWlucyBubyB2ZXJzaW9uLiBTdXBwb3J0cyBvbmUgdG8gdGhyZWUgbnVtZXJpYyBjb21wb25lbnRzXG4gKiBhbmQgY29tbW9uIHN1ZmZpeGVzIHN1Y2ggYXMgYHJjMWAsIGAucG9zdDFgLCBhbmQgYC1kZXYuMmAuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVZlcnNpb24oeyBzdGRvdXQsIHN0ZGVyciB9OiBCaW5WZXJzaW9uT3V0cHV0KTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIGRldGVjdGVkVmVyc2lvbnMoc3Rkb3V0KS5hdCgwKT8ucmF3ID8/IGRldGVjdGVkVmVyc2lvbnMoc3RkZXJyKS5hdCgwKT8ucmF3O1xufVxuXG5mdW5jdGlvbiBudW1lcmljVmVyc2lvbih2ZXJzaW9uOiBzdHJpbmcsIHN0cmljdDogYm9vbGVhbik6IG51bWJlcltdIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgcGF0dGVybiA9IHN0cmljdCA/IC9eXFxzKnY/KFxcZCsoPzpcXC5cXGQrKXswLDJ9KVxccyokL2kgOiAvXFxidj8oXFxkKyg/OlxcLlxcZCspezAsMn0pL2k7XG4gIGNvbnN0IG1hdGNoID0gcGF0dGVybi5leGVjKHZlcnNpb24pO1xuICByZXR1cm4gbWF0Y2g/LlsxXT8uc3BsaXQoXCIuXCIpLm1hcChOdW1iZXIpO1xufVxuXG5mdW5jdGlvbiBtZWV0c01pblZlcnNpb24odmVyc2lvbjogc3RyaW5nLCBtaW5WZXJzaW9uOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBib29sZWFuIHtcbiAgaWYgKCFtaW5WZXJzaW9uKSByZXR1cm4gdHJ1ZTtcbiAgY29uc3QgYWN0dWFsID0gbnVtZXJpY1ZlcnNpb24odmVyc2lvbiwgZmFsc2UpO1xuICBjb25zdCBtaW5pbXVtID0gbnVtZXJpY1ZlcnNpb24obWluVmVyc2lvbiwgdHJ1ZSk7XG4gIGlmICghbWluaW11bSkge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoYGludmFsaWQgbWluaW11bSBiaW5hcnkgdmVyc2lvbjogJHttaW5WZXJzaW9ufWApO1xuICB9XG4gIGlmICghYWN0dWFsKSByZXR1cm4gZmFsc2U7XG4gIGZvciAobGV0IGluZGV4ID0gMDsgaW5kZXggPCBNYXRoLm1heChhY3R1YWwubGVuZ3RoLCBtaW5pbXVtLmxlbmd0aCk7IGluZGV4ICs9IDEpIHtcbiAgICBjb25zdCBkaWZmZXJlbmNlID0gKGFjdHVhbFtpbmRleF0gPz8gMCkgLSAobWluaW11bVtpbmRleF0gPz8gMCk7XG4gICAgaWYgKGRpZmZlcmVuY2UgIT09IDApIHJldHVybiBkaWZmZXJlbmNlID4gMDtcbiAgfVxuICByZXR1cm4gdHJ1ZTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gaXNWYWxpZEJpbihwYXRoOiBzdHJpbmcsIG9wdGlvbnM6IEJpbk9wdGlvbnMpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgbG9nZ2VyLmRlYnVnKFwiY2hlY2tpbmcgYmluYXJ5XCIsIHtcbiAgICBwYXRoLFxuICAgIG1pblZlcnNpb246IG9wdGlvbnMubWluVmVyc2lvbixcbiAgICB2ZXJzaW9uQXJndW1lbnQ6IG9wdGlvbnMudmVyc2lvbkFyZ3VtZW50ID8/IFwiLS12ZXJzaW9uXCIsXG4gIH0pO1xuICBjb25zdCBhY2Nlc3MgPSBhd2FpdCBhY2Nlc3NDb250ZXh0KHBhdGgpO1xuICBpZiAoIWFjY2Vzcy5maWxlIHx8ICFhY2Nlc3MuZXhlY3V0YWJsZSkge1xuICAgIGxvZ2dlci5kZWJ1ZyhcImJpbmFyeSBhY2Nlc3MgY2hlY2sgZmFpbGVkXCIsIHsgcGF0aCwgLi4uYWNjZXNzIH0pO1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBsZXQgc3Rkb3V0OiBzdHJpbmc7XG4gIGxldCBzdGRlcnI6IHN0cmluZztcbiAgdHJ5IHtcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBleGVjRmlsZUFzeW5jKHBhdGgsIFtvcHRpb25zLnZlcnNpb25Bcmd1bWVudCA/PyBcIi0tdmVyc2lvblwiXSwge1xuICAgICAgZW5jb2Rpbmc6IFwidXRmOFwiLFxuICAgIH0pO1xuICAgIHN0ZG91dCA9IHJlc3VsdC5zdGRvdXQ7XG4gICAgc3RkZXJyID0gcmVzdWx0LnN0ZGVycjtcbiAgfSBjYXRjaCAoY2F1c2UpIHtcbiAgICBsb2dnZXIuZGVidWcoXCJiaW5hcnkgdmVyc2lvbiBjb21tYW5kIGZhaWxlZFwiLCB7XG4gICAgICBwYXRoLFxuICAgICAgZXJyb3I6IGVycm9yLmVycm9yTWVzc2FnZShjYXVzZSksXG4gICAgfSk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIGNvbnN0IHZlcnNpb24gPSAob3B0aW9ucy52ZXJzaW9uUGFyc2VyID8/IHBhcnNlVmVyc2lvbikoe1xuICAgIHN0ZG91dCxcbiAgICBzdGRlcnIsXG4gIH0pO1xuICBjb25zdCB2YWxpZCA9IHZlcnNpb24gIT09IHVuZGVmaW5lZCAmJiBtZWV0c01pblZlcnNpb24odmVyc2lvbiwgb3B0aW9ucy5taW5WZXJzaW9uKTtcbiAgbG9nZ2VyLmRlYnVnKFwiYmluYXJ5IHZlcnNpb24gY2hlY2tlZFwiLCB7XG4gICAgcGF0aCxcbiAgICB2ZXJzaW9uLFxuICAgIG1pblZlcnNpb246IG9wdGlvbnMubWluVmVyc2lvbixcbiAgICB2YWxpZCxcbiAgfSk7XG4gIHJldHVybiB2YWxpZDtcbn1cblxuZnVuY3Rpb24gZG93bmxvYWROYW1lKHVybDogc3RyaW5nLCBuYW1lOiBzdHJpbmcpOiBzdHJpbmcge1xuICB0cnkge1xuICAgIGNvbnN0IGNhbmRpZGF0ZSA9IGJhc2VuYW1lKGRlY29kZVVSSUNvbXBvbmVudChuZXcgVVJMKHVybCkucGF0aG5hbWUpKTtcbiAgICByZXR1cm4gY2FuZGlkYXRlICYmIEJ1ZmZlci5ieXRlTGVuZ3RoKGNhbmRpZGF0ZSkgPD0gMjAwID8gY2FuZGlkYXRlIDogbmFtZTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIG5hbWU7XG4gIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gdW5wYWNrKGFyY2hpdmU6IHN0cmluZywgZGVzdGluYXRpb246IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCBmaWxlbmFtZSA9IGFyY2hpdmUudG9Mb3dlckNhc2UoKTtcbiAgaWYgKGZpbGVuYW1lLmVuZHNXaXRoKFwiLnppcFwiKSkge1xuICAgIGF3YWl0IGV4dHJhY3RaaXAoYXJjaGl2ZSwgeyBkaXI6IGRlc3RpbmF0aW9uIH0pO1xuICAgIHJldHVybjtcbiAgfVxuICBpZiAoZmlsZW5hbWUuZW5kc1dpdGgoXCIudGFyXCIpIHx8IGZpbGVuYW1lLmVuZHNXaXRoKFwiLnRhci5nelwiKSB8fCBmaWxlbmFtZS5lbmRzV2l0aChcIi50Z3pcIikpIHtcbiAgICBhd2FpdCBleHRyYWN0VGFyKHsgZmlsZTogYXJjaGl2ZSwgY3dkOiBkZXN0aW5hdGlvbiB9KTtcbiAgICByZXR1cm47XG4gIH1cbiAgdGhyb3cgbmV3IEVycm9yKGB1bnN1cHBvcnRlZCBiaW5hcnkgYXJjaGl2ZTogJHtiYXNlbmFtZShhcmNoaXZlKX1gKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZmlsZXNVbmRlcihyb290OiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZ1tdPiB7XG4gIGNvbnN0IGVudHJpZXMgPSBhd2FpdCByZWFkZGlyKHJvb3QsIHsgd2l0aEZpbGVUeXBlczogdHJ1ZSB9KTtcbiAgY29uc3QgZmlsZXM6IHN0cmluZ1tdID0gW107XG4gIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgIGNvbnN0IHBhdGggPSBqb2luKHJvb3QsIGVudHJ5Lm5hbWUpO1xuICAgIGlmIChlbnRyeS5pc0RpcmVjdG9yeSgpKSB7XG4gICAgICBmaWxlcy5wdXNoKC4uLihhd2FpdCBmaWxlc1VuZGVyKHBhdGgpKSk7XG4gICAgfSBlbHNlIGlmIChlbnRyeS5pc0ZpbGUoKSkge1xuICAgICAgZmlsZXMucHVzaChwYXRoKTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGZpbGVzO1xufVxuXG5hc3luYyBmdW5jdGlvbiBzZWxlY3RTaW5nbGVGaWxlKHNvdXJjZTogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3QgZmlsZXMgPSBhd2FpdCBmaWxlc1VuZGVyKHNvdXJjZSk7XG4gIGNvbnN0IHNlbGVjdGVkID0gZmlsZXMuYXQoMCk7XG4gIGlmIChmaWxlcy5sZW5ndGggIT09IDEgfHwgIXNlbGVjdGVkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBiaW5hcnkgYXJjaGl2ZSBtdXN0IGNvbnRhaW4gb25lIGZpbGUsIGZvdW5kICR7ZmlsZXMubGVuZ3RofWApO1xuICB9XG4gIHJldHVybiBzZWxlY3RlZDtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc2VsZWN0ZWRCaW4oXG4gIGRlc3RpbmF0aW9uOiBCaW5Db250ZXh0LFxuICB1cmw6IHN0cmluZyxcbiAgdGVtcDogc3RyaW5nLFxuICBvcHRpb25zOiBCaW5PcHRpb25zLFxuKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3QgbmFtZSA9IGRvd25sb2FkTmFtZSh1cmwsIGJhc2VuYW1lKGRlc3RpbmF0aW9uLnBhdGgpKTtcbiAgY29uc3QgZG93bmxvYWRQYXRoID0gam9pbih0ZW1wLCBuYW1lKTtcbiAgbG9nZ2VyLmRlYnVnKFwiZG93bmxvYWRpbmcgYmluYXJ5XCIsIHtcbiAgICBmcm9tOiBkaXNwbGF5VXJsKHVybCksXG4gICAgdG86IGRvd25sb2FkUGF0aCxcbiAgfSk7XG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsKTtcbiAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgIHRocm93IG5ldyBFcnJvcihgYmluYXJ5IGRvd25sb2FkIGZhaWxlZCAoJHtyZXNwb25zZS5zdGF0dXN9KWApO1xuICB9XG4gIGF3YWl0IHdyaXRlRmlsZShkb3dubG9hZFBhdGgsIEJ1ZmZlci5mcm9tKGF3YWl0IHJlc3BvbnNlLmFycmF5QnVmZmVyKCkpLCB7IG1vZGU6IDBvNzU1IH0pO1xuXG4gIGxldCBzb3VyY2UgPSBkb3dubG9hZFBhdGg7XG4gIGlmIChvcHRpb25zLmF1dG9VbnBhY2thZ2UpIHtcbiAgICBzb3VyY2UgPSBqb2luKHRlbXAsIGB1bnBhY2tlZC0ke3JhbmRvbVVVSUQoKX1gKTtcbiAgICBhd2FpdCBta2Rpcihzb3VyY2UpO1xuICAgIGxvZ2dlci5kZWJ1ZyhcInVucGFja2luZyBiaW5hcnkgYXJjaGl2ZVwiLCB7XG4gICAgICBhcmNoaXZlOiBkb3dubG9hZFBhdGgsXG4gICAgICB0bzogc291cmNlLFxuICAgIH0pO1xuICAgIGF3YWl0IHVucGFjayhkb3dubG9hZFBhdGgsIHNvdXJjZSk7XG4gIH1cblxuICBpZiAob3B0aW9ucy5zZWxlY3Rvcikge1xuICAgIGNvbnN0IHNlbGVjdGVkID0gYXdhaXQgb3B0aW9ucy5zZWxlY3Rvcih7IGRlc3RpbmF0aW9uLCBkb3dubG9hZFBhdGgsIHNvdXJjZSB9KTtcbiAgICBsb2dnZXIuZGVidWcoXCJzZWxlY3RlZCBiaW5hcnlcIiwgeyBwYXRoOiBzZWxlY3RlZCB9KTtcbiAgICByZXR1cm4gc2VsZWN0ZWQ7XG4gIH1cbiAgY29uc3Qgc2VsZWN0ZWQgPSBvcHRpb25zLmF1dG9VbnBhY2thZ2UgPyBhd2FpdCBzZWxlY3RTaW5nbGVGaWxlKHNvdXJjZSkgOiBzb3VyY2U7XG4gIGxvZ2dlci5kZWJ1ZyhcInNlbGVjdGVkIGJpbmFyeVwiLCB7IHBhdGg6IHNlbGVjdGVkIH0pO1xuICByZXR1cm4gc2VsZWN0ZWQ7XG59XG5cbi8qKlxuICogUmV0dXJuIGFuIGV4aXN0aW5nIGV4ZWN1dGFibGUgb3IgaW5zdGFsbCBpdCBhdG9taWNhbGx5IHVuZGVyXG4gKiBgJEhPTUUvLjxuYW1lPi9iaW4vPG5hbWU+YC4gSW5zdGFsbGF0aW9uIHVzZXMgYSBjaGVjay1sb2NrLWNoZWNrLWxvYWRcbiAqIHNlcXVlbmNlIHNvIGNvbmN1cnJlbnQgY2FsbGVycyByZXNvbHZlIGFuZCBkb3dubG9hZCB0aGUgYmluYXJ5IG9ubHkgb25jZS5cbiAqIFRoZSBkb3dubG9hZGVkIGNhbmRpZGF0ZSBhbmQgZmluYWwgcmVuYW1lZCBleGVjdXRhYmxlIG11c3QgYm90aCBwYXNzIHRoZVxuICogc2FtZSBleGVjdXRhYmxlIGFuZCB2ZXJzaW9uIGNoZWNrcy5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGVuc3VyZShcbiAgbmFtZTogc3RyaW5nLFxuICB1cmw6IEJpblVybCxcbiAgb3B0aW9uczogQmluT3B0aW9ucyA9IHt9LFxuKTogUHJvbWlzZTxCaW5Db250ZXh0PiB7XG4gIGlmIChvcHRpb25zLm1pblZlcnNpb24gJiYgIW51bWVyaWNWZXJzaW9uKG9wdGlvbnMubWluVmVyc2lvbiwgdHJ1ZSkpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKGBpbnZhbGlkIG1pbmltdW0gYmluYXJ5IHZlcnNpb246ICR7b3B0aW9ucy5taW5WZXJzaW9ufWApO1xuICB9XG4gIGNvbnN0IGRlc3RpbmF0aW9uID0gY29udGV4dChuYW1lLCBvcHRpb25zLmhvbWVEaXIgPz8gaG9tZWRpcigpKTtcbiAgaWYgKGF3YWl0IGlzVmFsaWRCaW4oZGVzdGluYXRpb24ucGF0aCwgb3B0aW9ucykpIHtcbiAgICBsb2dnZXIuZGVidWcoXCJ1c2luZyBpbnN0YWxsZWQgYmluYXJ5XCIsIHsgbmFtZSwgcGF0aDogZGVzdGluYXRpb24ucGF0aCB9KTtcbiAgICByZXR1cm4gZGVzdGluYXRpb247XG4gIH1cblxuICBsb2dnZXIuZGVidWcoXCJ3YWl0aW5nIGZvciBiaW5hcnkgaW5zdGFsbCBsb2NrXCIsIHsgbmFtZSwgcGF0aDogZGVzdGluYXRpb24ucGF0aCB9KTtcbiAgcmV0dXJuIHdpdGhQcm9jZXNzTG9jayhbXCJiaW4uZW5zdXJlXCIsIGRlc3RpbmF0aW9uLnBhdGhdLCBhc3luYyAoKSA9PiB7XG4gICAgaWYgKGF3YWl0IGlzVmFsaWRCaW4oZGVzdGluYXRpb24ucGF0aCwgb3B0aW9ucykpIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhcInVzaW5nIGJpbmFyeSBpbnN0YWxsZWQgYnkgYW5vdGhlciBjYWxsZXJcIiwge1xuICAgICAgICBuYW1lLFxuICAgICAgICBwYXRoOiBkZXN0aW5hdGlvbi5wYXRoLFxuICAgICAgfSk7XG4gICAgICByZXR1cm4gZGVzdGluYXRpb247XG4gICAgfVxuXG4gICAgY29uc3QgcmVzb2x2ZWRVcmwgPSB0eXBlb2YgdXJsID09PSBcImZ1bmN0aW9uXCIgPyBhd2FpdCB1cmwoKSA6IHVybDtcbiAgICBjb25zdCBmcm9tID0gZGlzcGxheVVybChyZXNvbHZlZFVybCk7XG4gICAgbG9nZ2VyLmRlYnVnKFwiaW5zdGFsbGluZyBiaW5hcnlcIiwge1xuICAgICAgbmFtZSxcbiAgICAgIGZyb20sXG4gICAgICB0bzogZGVzdGluYXRpb24ucGF0aCxcbiAgICAgIG1pblZlcnNpb246IG9wdGlvbnMubWluVmVyc2lvbixcbiAgICB9KTtcbiAgICBjb25zdCB0ZW1wID0gYXdhaXQgbWtkdGVtcChqb2luKHRtcGRpcigpLCBgJHtuYW1lfS1gKSk7XG4gICAgbGV0IHN0YWdlZDogc3RyaW5nIHwgdW5kZWZpbmVkO1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBzZWxlY3RlZCA9IGF3YWl0IHNlbGVjdGVkQmluKGRlc3RpbmF0aW9uLCByZXNvbHZlZFVybCwgdGVtcCwgb3B0aW9ucyk7XG4gICAgICBhd2FpdCBjaG1vZChzZWxlY3RlZCwgMG83NTUpO1xuICAgICAgaWYgKCEoYXdhaXQgaXNWYWxpZEJpbihzZWxlY3RlZCwgb3B0aW9ucykpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgc2VsZWN0ZWQgYmluYXJ5IGhhcyBubyBhY2NlcHRhYmxlIHZlcnNpb246ICR7c2VsZWN0ZWR9YCk7XG4gICAgICB9XG5cbiAgICAgIGF3YWl0IG1rZGlyKGRlc3RpbmF0aW9uLmJpbkRpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgICBzdGFnZWQgPSBqb2luKGRlc3RpbmF0aW9uLmJpbkRpciwgYC4ke25hbWV9LSR7cmFuZG9tVVVJRCgpfWApO1xuICAgICAgYXdhaXQgY29weUZpbGUoc2VsZWN0ZWQsIHN0YWdlZCk7XG4gICAgICBhd2FpdCBjaG1vZChzdGFnZWQsIDBvNzU1KTtcbiAgICAgIGF3YWl0IHJlbmFtZShzdGFnZWQsIGRlc3RpbmF0aW9uLnBhdGgpO1xuICAgICAgc3RhZ2VkID0gdW5kZWZpbmVkO1xuICAgICAgaWYgKCEoYXdhaXQgaXNWYWxpZEJpbihkZXN0aW5hdGlvbi5wYXRoLCBvcHRpb25zKSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBpbnN0YWxsZWQgYmluYXJ5IGlzIGludmFsaWQgYWZ0ZXIgcmVuYW1lOiAke2Rlc3RpbmF0aW9uLnBhdGh9YCk7XG4gICAgICB9XG4gICAgICBsb2dnZXIuaW5mbyhcImluc3RhbGxlZCBiaW5hcnlcIiwge1xuICAgICAgICBuYW1lLFxuICAgICAgICBmcm9tLFxuICAgICAgICB0bzogZGVzdGluYXRpb24ucGF0aCxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIGRlc3RpbmF0aW9uO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBpZiAoc3RhZ2VkKSBhd2FpdCBybShzdGFnZWQsIHsgZm9yY2U6IHRydWUgfSk7XG4gICAgICBhd2FpdCBybSh0ZW1wLCB7IHJlY3Vyc2l2ZTogdHJ1ZSwgZm9yY2U6IHRydWUgfSk7XG4gICAgfVxuICB9KTtcbn1cbiJdfQ==
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Keyed mutual exclusion across the main thread and its worker threads.
3
+ *
4
+ * {@link withProcessLock} serializes callbacks that share a key: one runs at a
5
+ * time, the rest queue in arrival order, and different keys proceed
6
+ * concurrently. Unlike a plain in-module `Promise` chain, the queue is shared by
7
+ * every thread wired up through {@link processLockWorkerOptions}, so a worker
8
+ * pool cannot run two callbacks for the same key at once.
9
+ *
10
+ * The name says `process`: this coordinates the THREADS of one Node process. It
11
+ * is not cross-process and not cross-host - a second `node` invocation, or a
12
+ * second app replica, has its own coordinator and shares nothing. When the scope
13
+ * is a deployment rather than a process, use
14
+ * `@dbx-tools/postgres`'s `withAdvisoryLock`, which puts the arbiter in
15
+ * PostgreSQL where every replica can see it.
16
+ *
17
+ * The main thread owns the only coordinator. Each participating thread gets one
18
+ * `MessagePort` to it, and a lock is granted by a message back over that port.
19
+ * Consequently:
20
+ *
21
+ * - the lock is ADVISORY, like Postgres advisory locks - it protects a
22
+ * critical section only insofar as every writer takes the same key;
23
+ * - fairness is FIFO per key, since the coordinator queues waiters in the
24
+ * order their requests arrive;
25
+ * - a thread that dies while holding a lock releases it, because its port
26
+ * closing is what hands the key to the next waiter (see
27
+ * {@link LockCoordinator.removePort}).
28
+ *
29
+ * @module
30
+ */
31
+ import { type Worker, type WorkerOptions } from "node:worker_threads";
32
+ /**
33
+ * Run `fn` while holding the lock named by `key`, releasing it when `fn`
34
+ * settles.
35
+ *
36
+ * Callers sharing a key are serialized across the main thread and every worker
37
+ * started through {@link processLockWorkerOptions}; distinct keys never block
38
+ * each other. Returns whatever `fn` returns and propagates what it throws, so it
39
+ * drops into an existing expression without restructuring.
40
+ *
41
+ * `key` is any value with a stable identity - a string, a `["invoice", id]`
42
+ * tuple, a config object - canonicalized by `object.toStableKey`, the same rule
43
+ * `@dbx-tools/postgres` uses for advisory-lock ids and channel names. Structure
44
+ * is part of the identity: `["invoice", 7]` and `"invoice_7"` are different
45
+ * locks.
46
+ *
47
+ * @example
48
+ * await withProcessLock(["cache", name], async () => {
49
+ * if (!(await exists(name))) await build(name);
50
+ * });
51
+ */
52
+ export declare function withProcessLock<T>(key: unknown, fn: () => T | Promise<T>): Promise<T>;
53
+ /**
54
+ * Add the coordinator port to a `Worker`'s options so the worker can lock
55
+ * immediately - during module initialization, before any message is handled.
56
+ *
57
+ * Preserves the caller's `workerData` and `transferList`; the port is
58
+ * transferred, as `MessagePort` cannot be cloned.
59
+ *
60
+ * @example
61
+ * new Worker(url, processLockWorkerOptions({ workerData: { tenant } }));
62
+ */
63
+ export declare function processLockWorkerOptions(options?: WorkerOptions): WorkerOptions;
64
+ /**
65
+ * Wire an ALREADY-RUNNING worker into the lock, for a worker this code did not
66
+ * construct (a pool from a library, say).
67
+ *
68
+ * Prefer {@link processLockWorkerOptions}: the port arrives with a message, so
69
+ * the worker cannot lock during module initialization and must await
70
+ * {@link processLockAttached} first. The worker side needs no other change -
71
+ * `withProcessLock` works normally once the port lands.
72
+ */
73
+ export declare function attachProcessLock(worker: Worker): void;
74
+ /**
75
+ * In a worker, resolve once the {@link attachProcessLock} port has arrived.
76
+ *
77
+ * Only needed on the attach path, and safe to await regardless: it returns
78
+ * immediately when the worker already has a port (the
79
+ * {@link processLockWorkerOptions} case) or when called on the main thread, so
80
+ * shared worker code does not branch on how it was started.
81
+ *
82
+ * A `parentPort` listener keeps the worker alive, which is CORRECT here and
83
+ * deliberately not unref'd: this promise is pending work, and a worker allowed to
84
+ * exit while awaiting its port would die silently instead of locking. The
85
+ * listener is removed as soon as the port lands, so the worker is free to exit
86
+ * again the moment the wait is over.
87
+ */
88
+ export declare function processLockAttached(): Promise<void>;