dflockd-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Matthew Francis-Landau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # dflockd-client
2
+
3
+ TypeScript client for the [dflockd](https://github.com/mtingers/dflockd) distributed lock daemon.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install dflockd-client
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Start the dflockd server first:
14
+
15
+ ```bash
16
+ uv run dflockd
17
+ ```
18
+
19
+ ### `withLock` (recommended)
20
+
21
+ The simplest way to use a lock. Acquires, runs your callback, and releases
22
+ automatically — even if the callback throws.
23
+
24
+ ```ts
25
+ import { DistributedLock } from "dflockd-client";
26
+
27
+ const lock = new DistributedLock({ key: "my-resource" });
28
+
29
+ await lock.withLock(async () => {
30
+ // critical section — lock is held here
31
+ });
32
+ // lock is released
33
+ ```
34
+
35
+ ### Manual `acquire` / `release`
36
+
37
+ ```ts
38
+ import { DistributedLock } from "dflockd-client";
39
+
40
+ const lock = new DistributedLock({
41
+ key: "my-resource",
42
+ acquireTimeoutS: 10, // wait up to 10 s (default)
43
+ leaseTtlS: 20, // server-side lease duration
44
+ });
45
+
46
+ const ok = await lock.acquire(); // true on success, false on timeout
47
+ if (!ok) {
48
+ console.error("could not acquire lock");
49
+ process.exit(1);
50
+ }
51
+
52
+ try {
53
+ // critical section — lock is held and auto-renewed
54
+ } finally {
55
+ await lock.release();
56
+ }
57
+ ```
58
+
59
+ ### Two-phase lock (enqueue / wait)
60
+
61
+ Split acquisition into two steps so you can notify an external system
62
+ (webhook, database, queue) between joining the FIFO queue and blocking.
63
+
64
+ ```ts
65
+ import { DistributedLock } from "dflockd-client";
66
+
67
+ const lock = new DistributedLock({
68
+ key: "my-resource",
69
+ acquireTimeoutS: 10,
70
+ leaseTtlS: 20,
71
+ });
72
+
73
+ // Step 1: join the queue (non-blocking, returns immediately)
74
+ const status = await lock.enqueue(); // "acquired" or "queued"
75
+
76
+ // Step 2: do something between enqueue and blocking
77
+ console.log(`enqueue status: ${status}`);
78
+ notifyExternalSystem(status);
79
+
80
+ // Step 3: block until the lock is granted (or timeout)
81
+ const granted = await lock.wait(10); // true on success, false on timeout
82
+ if (!granted) {
83
+ console.error("timed out waiting for lock");
84
+ process.exit(1);
85
+ }
86
+
87
+ try {
88
+ // critical section — lock is held and auto-renewed
89
+ } finally {
90
+ await lock.release();
91
+ }
92
+ ```
93
+
94
+ If the lock is free when `enqueue()` is called, it returns `"acquired"` and
95
+ the lock is already held (fast path). `wait()` then returns `true` immediately
96
+ without blocking. If the lock is contended, `enqueue()` returns `"queued"` and
97
+ `wait()` blocks until the lock is granted or the timeout expires.
98
+
99
+ ### Options
100
+
101
+ | Option | Type | Default | Description |
102
+ |------------------|----------|---------------|--------------------------------------------------|
103
+ | `key` | `string` | *(required)* | Lock name |
104
+ | `acquireTimeoutS`| `number` | `10` | Seconds to wait for the lock before giving up |
105
+ | `leaseTtlS` | `number` | server default| Server-side lease duration in seconds |
106
+ | `host` | `string` | `127.0.0.1` | Server host |
107
+ | `port` | `number` | `6388` | Server port |
108
+ | `renewRatio` | `number` | `0.5` | Renew at `lease * ratio` seconds (e.g. 50% of TTL)|
109
+
110
+ ### Error handling
111
+
112
+ ```ts
113
+ import { DistributedLock, AcquireTimeoutError, LockError } from "dflockd-client";
114
+
115
+ try {
116
+ await lock.withLock(async () => { /* ... */ });
117
+ } catch (err) {
118
+ if (err instanceof AcquireTimeoutError) {
119
+ // lock could not be acquired within acquireTimeoutS
120
+ } else if (err instanceof LockError) {
121
+ // protocol-level error (bad token, server disconnect, etc.)
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### Low-level functions
127
+
128
+ For cases where you manage the socket yourself:
129
+
130
+ ```ts
131
+ import * as net from "net";
132
+ import { acquire, enqueue, waitForLock, renew, release } from "dflockd-client";
133
+
134
+ const sock = net.createConnection({ host: "127.0.0.1", port: 6388 });
135
+
136
+ // Single-phase
137
+ const { token, lease } = await acquire(sock, "my-key", 10);
138
+ const remaining = await renew(sock, "my-key", token, 60);
139
+ await release(sock, "my-key", token);
140
+
141
+ // Two-phase
142
+ const result = await enqueue(sock, "another-key"); // { status, token, lease }
143
+ if (result.status === "queued") {
144
+ const granted = await waitForLock(sock, "another-key", 10); // { token, lease }
145
+ }
146
+
147
+ sock.destroy();
148
+ ```
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/client.ts
31
+ var client_exports = {};
32
+ __export(client_exports, {
33
+ AcquireTimeoutError: () => AcquireTimeoutError,
34
+ DistributedLock: () => DistributedLock,
35
+ LockError: () => LockError,
36
+ acquire: () => acquire,
37
+ enqueue: () => enqueue,
38
+ release: () => release,
39
+ renew: () => renew,
40
+ waitForLock: () => waitForLock
41
+ });
42
+ module.exports = __toCommonJS(client_exports);
43
+ var net = __toESM(require("net"), 1);
44
+ var DEFAULT_HOST = "127.0.0.1";
45
+ var DEFAULT_PORT = 6388;
46
+ var AcquireTimeoutError = class extends Error {
47
+ constructor(key) {
48
+ super(`timeout acquiring '${key}'`);
49
+ this.name = "AcquireTimeoutError";
50
+ }
51
+ };
52
+ var LockError = class extends Error {
53
+ constructor(message) {
54
+ super(message);
55
+ this.name = "LockError";
56
+ }
57
+ };
58
+ function encodeLines(...lines) {
59
+ return Buffer.from(lines.map((l) => l + "\n").join(""), "utf-8");
60
+ }
61
+ function readline(sock) {
62
+ return new Promise((resolve, reject) => {
63
+ let buf = "";
64
+ const onData = (chunk) => {
65
+ buf += chunk.toString("utf-8");
66
+ const idx = buf.indexOf("\n");
67
+ if (idx !== -1) {
68
+ cleanup();
69
+ resolve(buf.slice(0, idx).replace(/\r$/, ""));
70
+ }
71
+ };
72
+ const onError = (err) => {
73
+ cleanup();
74
+ reject(err);
75
+ };
76
+ const onClose = () => {
77
+ cleanup();
78
+ reject(new LockError("server closed connection"));
79
+ };
80
+ const cleanup = () => {
81
+ sock.removeListener("data", onData);
82
+ sock.removeListener("error", onError);
83
+ sock.removeListener("close", onClose);
84
+ };
85
+ sock.on("data", onData);
86
+ sock.on("error", onError);
87
+ sock.on("close", onClose);
88
+ });
89
+ }
90
+ function connect(host, port) {
91
+ return new Promise((resolve, reject) => {
92
+ const sock = net.createConnection({ host, port }, () => resolve(sock));
93
+ sock.on("error", reject);
94
+ });
95
+ }
96
+ async function acquire(sock, key, acquireTimeoutS, leaseTtlS) {
97
+ const arg = leaseTtlS == null ? String(acquireTimeoutS) : `${acquireTimeoutS} ${leaseTtlS}`;
98
+ sock.write(encodeLines("l", key, arg));
99
+ const resp = await readline(sock);
100
+ if (resp === "timeout") {
101
+ throw new AcquireTimeoutError(key);
102
+ }
103
+ if (!resp.startsWith("ok ")) {
104
+ throw new LockError(`acquire failed: '${resp}'`);
105
+ }
106
+ const parts = resp.split(" ");
107
+ if (parts.length < 2) {
108
+ throw new LockError(`bad ok response: '${resp}'`);
109
+ }
110
+ const token = parts[1];
111
+ const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
112
+ return { token, lease };
113
+ }
114
+ async function renew(sock, key, token, leaseTtlS) {
115
+ const arg = leaseTtlS == null ? token : `${token} ${leaseTtlS}`;
116
+ sock.write(encodeLines("n", key, arg));
117
+ const resp = await readline(sock);
118
+ if (!resp.startsWith("ok")) {
119
+ throw new LockError(`renew failed: '${resp}'`);
120
+ }
121
+ const parts = resp.split(" ");
122
+ if (parts.length >= 2 && /^\d+$/.test(parts[1])) {
123
+ return parseInt(parts[1], 10);
124
+ }
125
+ return -1;
126
+ }
127
+ async function enqueue(sock, key, leaseTtlS) {
128
+ const arg = leaseTtlS == null ? "" : String(leaseTtlS);
129
+ sock.write(encodeLines("e", key, arg));
130
+ const resp = await readline(sock);
131
+ if (resp.startsWith("acquired ")) {
132
+ const parts = resp.split(" ");
133
+ const token = parts[1];
134
+ const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 33;
135
+ return { status: "acquired", token, lease };
136
+ }
137
+ if (resp === "queued") {
138
+ return { status: "queued", token: null, lease: null };
139
+ }
140
+ throw new LockError(`enqueue failed: '${resp}'`);
141
+ }
142
+ async function waitForLock(sock, key, waitTimeoutS) {
143
+ sock.write(encodeLines("w", key, String(waitTimeoutS)));
144
+ const resp = await readline(sock);
145
+ if (resp === "timeout") {
146
+ throw new AcquireTimeoutError(key);
147
+ }
148
+ if (!resp.startsWith("ok ")) {
149
+ throw new LockError(`wait failed: '${resp}'`);
150
+ }
151
+ const parts = resp.split(" ");
152
+ const token = parts[1];
153
+ const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 33;
154
+ return { token, lease };
155
+ }
156
+ async function release(sock, key, token) {
157
+ sock.write(encodeLines("r", key, token));
158
+ const resp = await readline(sock);
159
+ if (resp !== "ok") {
160
+ throw new LockError(`release failed: '${resp}'`);
161
+ }
162
+ }
163
+ var DistributedLock = class {
164
+ key;
165
+ acquireTimeoutS;
166
+ leaseTtlS;
167
+ host;
168
+ port;
169
+ renewRatio;
170
+ token = null;
171
+ lease = 0;
172
+ sock = null;
173
+ renewTimer = null;
174
+ closed = false;
175
+ constructor(opts) {
176
+ this.key = opts.key;
177
+ this.acquireTimeoutS = opts.acquireTimeoutS ?? 10;
178
+ this.leaseTtlS = opts.leaseTtlS;
179
+ this.host = opts.host ?? DEFAULT_HOST;
180
+ this.port = opts.port ?? DEFAULT_PORT;
181
+ this.renewRatio = opts.renewRatio ?? 0.5;
182
+ }
183
+ /** Acquire the lock. Returns `true` on success, `false` on timeout. */
184
+ async acquire() {
185
+ this.closed = false;
186
+ this.sock = await connect(this.host, this.port);
187
+ try {
188
+ const result = await acquire(
189
+ this.sock,
190
+ this.key,
191
+ this.acquireTimeoutS,
192
+ this.leaseTtlS
193
+ );
194
+ this.token = result.token;
195
+ this.lease = result.lease;
196
+ } catch (err) {
197
+ await this.close();
198
+ if (err instanceof AcquireTimeoutError) return false;
199
+ throw err;
200
+ }
201
+ this.startRenew();
202
+ return true;
203
+ }
204
+ /** Release the lock and close the connection. */
205
+ async release() {
206
+ try {
207
+ this.stopRenew();
208
+ if (this.sock && this.token) {
209
+ await release(this.sock, this.key, this.token);
210
+ }
211
+ } finally {
212
+ await this.close();
213
+ }
214
+ }
215
+ /**
216
+ * Two-phase step 1: connect and join the FIFO queue.
217
+ * Returns `"acquired"` (fast-path, lock is already held) or `"queued"`.
218
+ * If acquired immediately, the renew loop starts automatically.
219
+ */
220
+ async enqueue() {
221
+ this.closed = false;
222
+ this.sock = await connect(this.host, this.port);
223
+ try {
224
+ const result = await enqueue(this.sock, this.key, this.leaseTtlS);
225
+ if (result.status === "acquired") {
226
+ this.token = result.token;
227
+ this.lease = result.lease ?? 0;
228
+ this.startRenew();
229
+ }
230
+ return result.status;
231
+ } catch (err) {
232
+ await this.close();
233
+ throw err;
234
+ }
235
+ }
236
+ /**
237
+ * Two-phase step 2: block until the lock is granted.
238
+ * Returns `true` if granted, `false` on timeout.
239
+ * If already acquired during `enqueue()`, returns `true` immediately.
240
+ */
241
+ async wait(timeoutS) {
242
+ if (this.token !== null) {
243
+ return true;
244
+ }
245
+ if (!this.sock) {
246
+ throw new LockError("not connected; call enqueue() first");
247
+ }
248
+ const timeout = timeoutS ?? this.acquireTimeoutS;
249
+ try {
250
+ const result = await waitForLock(this.sock, this.key, timeout);
251
+ this.token = result.token;
252
+ this.lease = result.lease;
253
+ } catch (err) {
254
+ await this.close();
255
+ if (err instanceof AcquireTimeoutError) return false;
256
+ throw err;
257
+ }
258
+ this.startRenew();
259
+ return true;
260
+ }
261
+ /**
262
+ * Run `fn` while holding the lock, then release automatically.
263
+ *
264
+ * ```ts
265
+ * const lock = new DistributedLock({ key: "my-resource" });
266
+ * await lock.withLock(async () => {
267
+ * // critical section
268
+ * });
269
+ * ```
270
+ */
271
+ async withLock(fn) {
272
+ const ok = await this.acquire();
273
+ if (!ok) {
274
+ throw new AcquireTimeoutError(this.key);
275
+ }
276
+ try {
277
+ return await fn();
278
+ } finally {
279
+ await this.release();
280
+ }
281
+ }
282
+ /** Close the underlying socket (idempotent). */
283
+ async close() {
284
+ if (this.closed) return;
285
+ this.closed = true;
286
+ this.stopRenew();
287
+ if (this.sock) {
288
+ this.sock.destroy();
289
+ this.sock = null;
290
+ }
291
+ this.token = null;
292
+ }
293
+ // -- internals --
294
+ startRenew() {
295
+ const interval = Math.max(1, this.lease * this.renewRatio) * 1e3;
296
+ const loop = async () => {
297
+ if (!this.sock || !this.token) return;
298
+ try {
299
+ await renew(this.sock, this.key, this.token, this.leaseTtlS);
300
+ } catch {
301
+ console.error(
302
+ `lock lost (renew failed): key=${this.key} token=${this.token}`
303
+ );
304
+ this.token = null;
305
+ return;
306
+ }
307
+ this.renewTimer = setTimeout(loop, interval);
308
+ };
309
+ this.renewTimer = setTimeout(loop, interval);
310
+ }
311
+ stopRenew() {
312
+ if (this.renewTimer != null) {
313
+ clearTimeout(this.renewTimer);
314
+ this.renewTimer = null;
315
+ }
316
+ }
317
+ };
318
+ // Annotate the CommonJS export names for ESM import in node:
319
+ 0 && (module.exports = {
320
+ AcquireTimeoutError,
321
+ DistributedLock,
322
+ LockError,
323
+ acquire,
324
+ enqueue,
325
+ release,
326
+ renew,
327
+ waitForLock
328
+ });
329
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import * as net from \"net\";\n\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst DEFAULT_PORT = 6388;\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\nexport class AcquireTimeoutError extends Error {\n constructor(key: string) {\n super(`timeout acquiring '${key}'`);\n this.name = \"AcquireTimeoutError\";\n }\n}\n\nexport class LockError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"LockError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Low-level helpers\n// ---------------------------------------------------------------------------\n\nfunction encodeLines(...lines: string[]): Buffer {\n return Buffer.from(lines.map((l) => l + \"\\n\").join(\"\"), \"utf-8\");\n}\n\n/**\n * Read one newline-terminated line from the socket.\n * Resolves with the line (without trailing \\r\\n).\n * Rejects if the connection closes before a full line arrives.\n */\nfunction readline(sock: net.Socket): Promise<string> {\n return new Promise((resolve, reject) => {\n let buf = \"\";\n\n const onData = (chunk: Buffer) => {\n buf += chunk.toString(\"utf-8\");\n const idx = buf.indexOf(\"\\n\");\n if (idx !== -1) {\n cleanup();\n resolve(buf.slice(0, idx).replace(/\\r$/, \"\"));\n }\n };\n\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n\n const onClose = () => {\n cleanup();\n reject(new LockError(\"server closed connection\"));\n };\n\n const cleanup = () => {\n sock.removeListener(\"data\", onData);\n sock.removeListener(\"error\", onError);\n sock.removeListener(\"close\", onClose);\n };\n\n sock.on(\"data\", onData);\n sock.on(\"error\", onError);\n sock.on(\"close\", onClose);\n });\n}\n\nfunction connect(host: string, port: number): Promise<net.Socket> {\n return new Promise((resolve, reject) => {\n const sock = net.createConnection({ host, port }, () => resolve(sock));\n sock.on(\"error\", reject);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Protocol functions\n// ---------------------------------------------------------------------------\n\nexport async function acquire(\n sock: net.Socket,\n key: string,\n acquireTimeoutS: number,\n leaseTtlS?: number,\n): Promise<{ token: string; lease: number }> {\n const arg =\n leaseTtlS == null\n ? String(acquireTimeoutS)\n : `${acquireTimeoutS} ${leaseTtlS}`;\n\n sock.write(encodeLines(\"l\", key, arg));\n\n const resp = await readline(sock);\n if (resp === \"timeout\") {\n throw new AcquireTimeoutError(key);\n }\n if (!resp.startsWith(\"ok \")) {\n throw new LockError(`acquire failed: '${resp}'`);\n }\n\n const parts = resp.split(\" \");\n if (parts.length < 2) {\n throw new LockError(`bad ok response: '${resp}'`);\n }\n const token = parts[1];\n const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;\n return { token, lease };\n}\n\nexport async function renew(\n sock: net.Socket,\n key: string,\n token: string,\n leaseTtlS?: number,\n): Promise<number> {\n const arg = leaseTtlS == null ? token : `${token} ${leaseTtlS}`;\n sock.write(encodeLines(\"n\", key, arg));\n\n const resp = await readline(sock);\n if (!resp.startsWith(\"ok\")) {\n throw new LockError(`renew failed: '${resp}'`);\n }\n\n const parts = resp.split(\" \");\n if (parts.length >= 2 && /^\\d+$/.test(parts[1])) {\n return parseInt(parts[1], 10);\n }\n return -1;\n}\n\nexport async function enqueue(\n sock: net.Socket,\n key: string,\n leaseTtlS?: number,\n): Promise<{ status: \"acquired\" | \"queued\"; token: string | null; lease: number | null }> {\n const arg = leaseTtlS == null ? \"\" : String(leaseTtlS);\n sock.write(encodeLines(\"e\", key, arg));\n\n const resp = await readline(sock);\n if (resp.startsWith(\"acquired \")) {\n const parts = resp.split(\" \");\n const token = parts[1];\n const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 33;\n return { status: \"acquired\", token, lease };\n }\n if (resp === \"queued\") {\n return { status: \"queued\", token: null, lease: null };\n }\n throw new LockError(`enqueue failed: '${resp}'`);\n}\n\nexport async function waitForLock(\n sock: net.Socket,\n key: string,\n waitTimeoutS: number,\n): Promise<{ token: string; lease: number }> {\n sock.write(encodeLines(\"w\", key, String(waitTimeoutS)));\n\n const resp = await readline(sock);\n if (resp === \"timeout\") {\n throw new AcquireTimeoutError(key);\n }\n if (!resp.startsWith(\"ok \")) {\n throw new LockError(`wait failed: '${resp}'`);\n }\n\n const parts = resp.split(\" \");\n const token = parts[1];\n const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 33;\n return { token, lease };\n}\n\nexport async function release(\n sock: net.Socket,\n key: string,\n token: string,\n): Promise<void> {\n sock.write(encodeLines(\"r\", key, token));\n\n const resp = await readline(sock);\n if (resp !== \"ok\") {\n throw new LockError(`release failed: '${resp}'`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// DistributedLock\n// ---------------------------------------------------------------------------\n\nexport interface DistributedLockOptions {\n key: string;\n acquireTimeoutS?: number;\n leaseTtlS?: number;\n host?: string;\n port?: number;\n renewRatio?: number;\n}\n\nexport class DistributedLock {\n readonly key: string;\n readonly acquireTimeoutS: number;\n readonly leaseTtlS: number | undefined;\n readonly host: string;\n readonly port: number;\n readonly renewRatio: number;\n\n token: string | null = null;\n lease: number = 0;\n\n private sock: net.Socket | null = null;\n private renewTimer: ReturnType<typeof setTimeout> | null = null;\n private closed = false;\n\n constructor(opts: DistributedLockOptions) {\n this.key = opts.key;\n this.acquireTimeoutS = opts.acquireTimeoutS ?? 10;\n this.leaseTtlS = opts.leaseTtlS;\n this.host = opts.host ?? DEFAULT_HOST;\n this.port = opts.port ?? DEFAULT_PORT;\n this.renewRatio = opts.renewRatio ?? 0.5;\n }\n\n /** Acquire the lock. Returns `true` on success, `false` on timeout. */\n async acquire(): Promise<boolean> {\n this.closed = false;\n this.sock = await connect(this.host, this.port);\n try {\n const result = await acquire(\n this.sock,\n this.key,\n this.acquireTimeoutS,\n this.leaseTtlS,\n );\n this.token = result.token;\n this.lease = result.lease;\n } catch (err) {\n await this.close();\n if (err instanceof AcquireTimeoutError) return false;\n throw err;\n }\n this.startRenew();\n return true;\n }\n\n /** Release the lock and close the connection. */\n async release(): Promise<void> {\n try {\n this.stopRenew();\n if (this.sock && this.token) {\n await release(this.sock, this.key, this.token);\n }\n } finally {\n await this.close();\n }\n }\n\n /**\n * Two-phase step 1: connect and join the FIFO queue.\n * Returns `\"acquired\"` (fast-path, lock is already held) or `\"queued\"`.\n * If acquired immediately, the renew loop starts automatically.\n */\n async enqueue(): Promise<\"acquired\" | \"queued\"> {\n this.closed = false;\n this.sock = await connect(this.host, this.port);\n try {\n const result = await enqueue(this.sock, this.key, this.leaseTtlS);\n if (result.status === \"acquired\") {\n this.token = result.token;\n this.lease = result.lease ?? 0;\n this.startRenew();\n }\n return result.status;\n } catch (err) {\n await this.close();\n throw err;\n }\n }\n\n /**\n * Two-phase step 2: block until the lock is granted.\n * Returns `true` if granted, `false` on timeout.\n * If already acquired during `enqueue()`, returns `true` immediately.\n */\n async wait(timeoutS?: number): Promise<boolean> {\n if (this.token !== null) {\n // Already acquired during enqueue (fast path)\n return true;\n }\n if (!this.sock) {\n throw new LockError(\"not connected; call enqueue() first\");\n }\n const timeout = timeoutS ?? this.acquireTimeoutS;\n try {\n const result = await waitForLock(this.sock, this.key, timeout);\n this.token = result.token;\n this.lease = result.lease;\n } catch (err) {\n await this.close();\n if (err instanceof AcquireTimeoutError) return false;\n throw err;\n }\n this.startRenew();\n return true;\n }\n\n /**\n * Run `fn` while holding the lock, then release automatically.\n *\n * ```ts\n * const lock = new DistributedLock({ key: \"my-resource\" });\n * await lock.withLock(async () => {\n * // critical section\n * });\n * ```\n */\n async withLock<T>(fn: () => T | Promise<T>): Promise<T> {\n const ok = await this.acquire();\n if (!ok) {\n throw new AcquireTimeoutError(this.key);\n }\n try {\n return await fn();\n } finally {\n await this.release();\n }\n }\n\n /** Close the underlying socket (idempotent). */\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n this.stopRenew();\n if (this.sock) {\n this.sock.destroy();\n this.sock = null;\n }\n this.token = null;\n }\n\n // -- internals --\n\n private startRenew(): void {\n const interval = Math.max(1, this.lease * this.renewRatio) * 1000;\n const loop = async () => {\n if (!this.sock || !this.token) return;\n try {\n await renew(this.sock, this.key, this.token, this.leaseTtlS);\n } catch {\n console.error(\n `lock lost (renew failed): key=${this.key} token=${this.token}`,\n );\n this.token = null;\n return;\n }\n this.renewTimer = setTimeout(loop, interval);\n };\n this.renewTimer = setTimeout(loop, interval);\n }\n\n private stopRenew(): void {\n if (this.renewTimer != null) {\n clearTimeout(this.renewTimer);\n this.renewTimer = null;\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,78 @@
1
+ import * as net from 'net';
2
+
3
+ declare class AcquireTimeoutError extends Error {
4
+ constructor(key: string);
5
+ }
6
+ declare class LockError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ declare function acquire(sock: net.Socket, key: string, acquireTimeoutS: number, leaseTtlS?: number): Promise<{
10
+ token: string;
11
+ lease: number;
12
+ }>;
13
+ declare function renew(sock: net.Socket, key: string, token: string, leaseTtlS?: number): Promise<number>;
14
+ declare function enqueue(sock: net.Socket, key: string, leaseTtlS?: number): Promise<{
15
+ status: "acquired" | "queued";
16
+ token: string | null;
17
+ lease: number | null;
18
+ }>;
19
+ declare function waitForLock(sock: net.Socket, key: string, waitTimeoutS: number): Promise<{
20
+ token: string;
21
+ lease: number;
22
+ }>;
23
+ declare function release(sock: net.Socket, key: string, token: string): Promise<void>;
24
+ interface DistributedLockOptions {
25
+ key: string;
26
+ acquireTimeoutS?: number;
27
+ leaseTtlS?: number;
28
+ host?: string;
29
+ port?: number;
30
+ renewRatio?: number;
31
+ }
32
+ declare class DistributedLock {
33
+ readonly key: string;
34
+ readonly acquireTimeoutS: number;
35
+ readonly leaseTtlS: number | undefined;
36
+ readonly host: string;
37
+ readonly port: number;
38
+ readonly renewRatio: number;
39
+ token: string | null;
40
+ lease: number;
41
+ private sock;
42
+ private renewTimer;
43
+ private closed;
44
+ constructor(opts: DistributedLockOptions);
45
+ /** Acquire the lock. Returns `true` on success, `false` on timeout. */
46
+ acquire(): Promise<boolean>;
47
+ /** Release the lock and close the connection. */
48
+ release(): Promise<void>;
49
+ /**
50
+ * Two-phase step 1: connect and join the FIFO queue.
51
+ * Returns `"acquired"` (fast-path, lock is already held) or `"queued"`.
52
+ * If acquired immediately, the renew loop starts automatically.
53
+ */
54
+ enqueue(): Promise<"acquired" | "queued">;
55
+ /**
56
+ * Two-phase step 2: block until the lock is granted.
57
+ * Returns `true` if granted, `false` on timeout.
58
+ * If already acquired during `enqueue()`, returns `true` immediately.
59
+ */
60
+ wait(timeoutS?: number): Promise<boolean>;
61
+ /**
62
+ * Run `fn` while holding the lock, then release automatically.
63
+ *
64
+ * ```ts
65
+ * const lock = new DistributedLock({ key: "my-resource" });
66
+ * await lock.withLock(async () => {
67
+ * // critical section
68
+ * });
69
+ * ```
70
+ */
71
+ withLock<T>(fn: () => T | Promise<T>): Promise<T>;
72
+ /** Close the underlying socket (idempotent). */
73
+ close(): Promise<void>;
74
+ private startRenew;
75
+ private stopRenew;
76
+ }
77
+
78
+ export { AcquireTimeoutError, DistributedLock, type DistributedLockOptions, LockError, acquire, enqueue, release, renew, waitForLock };
@@ -0,0 +1,78 @@
1
+ import * as net from 'net';
2
+
3
+ declare class AcquireTimeoutError extends Error {
4
+ constructor(key: string);
5
+ }
6
+ declare class LockError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ declare function acquire(sock: net.Socket, key: string, acquireTimeoutS: number, leaseTtlS?: number): Promise<{
10
+ token: string;
11
+ lease: number;
12
+ }>;
13
+ declare function renew(sock: net.Socket, key: string, token: string, leaseTtlS?: number): Promise<number>;
14
+ declare function enqueue(sock: net.Socket, key: string, leaseTtlS?: number): Promise<{
15
+ status: "acquired" | "queued";
16
+ token: string | null;
17
+ lease: number | null;
18
+ }>;
19
+ declare function waitForLock(sock: net.Socket, key: string, waitTimeoutS: number): Promise<{
20
+ token: string;
21
+ lease: number;
22
+ }>;
23
+ declare function release(sock: net.Socket, key: string, token: string): Promise<void>;
24
+ interface DistributedLockOptions {
25
+ key: string;
26
+ acquireTimeoutS?: number;
27
+ leaseTtlS?: number;
28
+ host?: string;
29
+ port?: number;
30
+ renewRatio?: number;
31
+ }
32
+ declare class DistributedLock {
33
+ readonly key: string;
34
+ readonly acquireTimeoutS: number;
35
+ readonly leaseTtlS: number | undefined;
36
+ readonly host: string;
37
+ readonly port: number;
38
+ readonly renewRatio: number;
39
+ token: string | null;
40
+ lease: number;
41
+ private sock;
42
+ private renewTimer;
43
+ private closed;
44
+ constructor(opts: DistributedLockOptions);
45
+ /** Acquire the lock. Returns `true` on success, `false` on timeout. */
46
+ acquire(): Promise<boolean>;
47
+ /** Release the lock and close the connection. */
48
+ release(): Promise<void>;
49
+ /**
50
+ * Two-phase step 1: connect and join the FIFO queue.
51
+ * Returns `"acquired"` (fast-path, lock is already held) or `"queued"`.
52
+ * If acquired immediately, the renew loop starts automatically.
53
+ */
54
+ enqueue(): Promise<"acquired" | "queued">;
55
+ /**
56
+ * Two-phase step 2: block until the lock is granted.
57
+ * Returns `true` if granted, `false` on timeout.
58
+ * If already acquired during `enqueue()`, returns `true` immediately.
59
+ */
60
+ wait(timeoutS?: number): Promise<boolean>;
61
+ /**
62
+ * Run `fn` while holding the lock, then release automatically.
63
+ *
64
+ * ```ts
65
+ * const lock = new DistributedLock({ key: "my-resource" });
66
+ * await lock.withLock(async () => {
67
+ * // critical section
68
+ * });
69
+ * ```
70
+ */
71
+ withLock<T>(fn: () => T | Promise<T>): Promise<T>;
72
+ /** Close the underlying socket (idempotent). */
73
+ close(): Promise<void>;
74
+ private startRenew;
75
+ private stopRenew;
76
+ }
77
+
78
+ export { AcquireTimeoutError, DistributedLock, type DistributedLockOptions, LockError, acquire, enqueue, release, renew, waitForLock };
package/dist/client.js ADDED
@@ -0,0 +1,287 @@
1
+ // src/client.ts
2
+ import * as net from "net";
3
+ var DEFAULT_HOST = "127.0.0.1";
4
+ var DEFAULT_PORT = 6388;
5
+ var AcquireTimeoutError = class extends Error {
6
+ constructor(key) {
7
+ super(`timeout acquiring '${key}'`);
8
+ this.name = "AcquireTimeoutError";
9
+ }
10
+ };
11
+ var LockError = class extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "LockError";
15
+ }
16
+ };
17
+ function encodeLines(...lines) {
18
+ return Buffer.from(lines.map((l) => l + "\n").join(""), "utf-8");
19
+ }
20
+ function readline(sock) {
21
+ return new Promise((resolve, reject) => {
22
+ let buf = "";
23
+ const onData = (chunk) => {
24
+ buf += chunk.toString("utf-8");
25
+ const idx = buf.indexOf("\n");
26
+ if (idx !== -1) {
27
+ cleanup();
28
+ resolve(buf.slice(0, idx).replace(/\r$/, ""));
29
+ }
30
+ };
31
+ const onError = (err) => {
32
+ cleanup();
33
+ reject(err);
34
+ };
35
+ const onClose = () => {
36
+ cleanup();
37
+ reject(new LockError("server closed connection"));
38
+ };
39
+ const cleanup = () => {
40
+ sock.removeListener("data", onData);
41
+ sock.removeListener("error", onError);
42
+ sock.removeListener("close", onClose);
43
+ };
44
+ sock.on("data", onData);
45
+ sock.on("error", onError);
46
+ sock.on("close", onClose);
47
+ });
48
+ }
49
+ function connect(host, port) {
50
+ return new Promise((resolve, reject) => {
51
+ const sock = net.createConnection({ host, port }, () => resolve(sock));
52
+ sock.on("error", reject);
53
+ });
54
+ }
55
+ async function acquire(sock, key, acquireTimeoutS, leaseTtlS) {
56
+ const arg = leaseTtlS == null ? String(acquireTimeoutS) : `${acquireTimeoutS} ${leaseTtlS}`;
57
+ sock.write(encodeLines("l", key, arg));
58
+ const resp = await readline(sock);
59
+ if (resp === "timeout") {
60
+ throw new AcquireTimeoutError(key);
61
+ }
62
+ if (!resp.startsWith("ok ")) {
63
+ throw new LockError(`acquire failed: '${resp}'`);
64
+ }
65
+ const parts = resp.split(" ");
66
+ if (parts.length < 2) {
67
+ throw new LockError(`bad ok response: '${resp}'`);
68
+ }
69
+ const token = parts[1];
70
+ const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
71
+ return { token, lease };
72
+ }
73
+ async function renew(sock, key, token, leaseTtlS) {
74
+ const arg = leaseTtlS == null ? token : `${token} ${leaseTtlS}`;
75
+ sock.write(encodeLines("n", key, arg));
76
+ const resp = await readline(sock);
77
+ if (!resp.startsWith("ok")) {
78
+ throw new LockError(`renew failed: '${resp}'`);
79
+ }
80
+ const parts = resp.split(" ");
81
+ if (parts.length >= 2 && /^\d+$/.test(parts[1])) {
82
+ return parseInt(parts[1], 10);
83
+ }
84
+ return -1;
85
+ }
86
+ async function enqueue(sock, key, leaseTtlS) {
87
+ const arg = leaseTtlS == null ? "" : String(leaseTtlS);
88
+ sock.write(encodeLines("e", key, arg));
89
+ const resp = await readline(sock);
90
+ if (resp.startsWith("acquired ")) {
91
+ const parts = resp.split(" ");
92
+ const token = parts[1];
93
+ const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 33;
94
+ return { status: "acquired", token, lease };
95
+ }
96
+ if (resp === "queued") {
97
+ return { status: "queued", token: null, lease: null };
98
+ }
99
+ throw new LockError(`enqueue failed: '${resp}'`);
100
+ }
101
+ async function waitForLock(sock, key, waitTimeoutS) {
102
+ sock.write(encodeLines("w", key, String(waitTimeoutS)));
103
+ const resp = await readline(sock);
104
+ if (resp === "timeout") {
105
+ throw new AcquireTimeoutError(key);
106
+ }
107
+ if (!resp.startsWith("ok ")) {
108
+ throw new LockError(`wait failed: '${resp}'`);
109
+ }
110
+ const parts = resp.split(" ");
111
+ const token = parts[1];
112
+ const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 33;
113
+ return { token, lease };
114
+ }
115
+ async function release(sock, key, token) {
116
+ sock.write(encodeLines("r", key, token));
117
+ const resp = await readline(sock);
118
+ if (resp !== "ok") {
119
+ throw new LockError(`release failed: '${resp}'`);
120
+ }
121
+ }
122
+ var DistributedLock = class {
123
+ key;
124
+ acquireTimeoutS;
125
+ leaseTtlS;
126
+ host;
127
+ port;
128
+ renewRatio;
129
+ token = null;
130
+ lease = 0;
131
+ sock = null;
132
+ renewTimer = null;
133
+ closed = false;
134
+ constructor(opts) {
135
+ this.key = opts.key;
136
+ this.acquireTimeoutS = opts.acquireTimeoutS ?? 10;
137
+ this.leaseTtlS = opts.leaseTtlS;
138
+ this.host = opts.host ?? DEFAULT_HOST;
139
+ this.port = opts.port ?? DEFAULT_PORT;
140
+ this.renewRatio = opts.renewRatio ?? 0.5;
141
+ }
142
+ /** Acquire the lock. Returns `true` on success, `false` on timeout. */
143
+ async acquire() {
144
+ this.closed = false;
145
+ this.sock = await connect(this.host, this.port);
146
+ try {
147
+ const result = await acquire(
148
+ this.sock,
149
+ this.key,
150
+ this.acquireTimeoutS,
151
+ this.leaseTtlS
152
+ );
153
+ this.token = result.token;
154
+ this.lease = result.lease;
155
+ } catch (err) {
156
+ await this.close();
157
+ if (err instanceof AcquireTimeoutError) return false;
158
+ throw err;
159
+ }
160
+ this.startRenew();
161
+ return true;
162
+ }
163
+ /** Release the lock and close the connection. */
164
+ async release() {
165
+ try {
166
+ this.stopRenew();
167
+ if (this.sock && this.token) {
168
+ await release(this.sock, this.key, this.token);
169
+ }
170
+ } finally {
171
+ await this.close();
172
+ }
173
+ }
174
+ /**
175
+ * Two-phase step 1: connect and join the FIFO queue.
176
+ * Returns `"acquired"` (fast-path, lock is already held) or `"queued"`.
177
+ * If acquired immediately, the renew loop starts automatically.
178
+ */
179
+ async enqueue() {
180
+ this.closed = false;
181
+ this.sock = await connect(this.host, this.port);
182
+ try {
183
+ const result = await enqueue(this.sock, this.key, this.leaseTtlS);
184
+ if (result.status === "acquired") {
185
+ this.token = result.token;
186
+ this.lease = result.lease ?? 0;
187
+ this.startRenew();
188
+ }
189
+ return result.status;
190
+ } catch (err) {
191
+ await this.close();
192
+ throw err;
193
+ }
194
+ }
195
+ /**
196
+ * Two-phase step 2: block until the lock is granted.
197
+ * Returns `true` if granted, `false` on timeout.
198
+ * If already acquired during `enqueue()`, returns `true` immediately.
199
+ */
200
+ async wait(timeoutS) {
201
+ if (this.token !== null) {
202
+ return true;
203
+ }
204
+ if (!this.sock) {
205
+ throw new LockError("not connected; call enqueue() first");
206
+ }
207
+ const timeout = timeoutS ?? this.acquireTimeoutS;
208
+ try {
209
+ const result = await waitForLock(this.sock, this.key, timeout);
210
+ this.token = result.token;
211
+ this.lease = result.lease;
212
+ } catch (err) {
213
+ await this.close();
214
+ if (err instanceof AcquireTimeoutError) return false;
215
+ throw err;
216
+ }
217
+ this.startRenew();
218
+ return true;
219
+ }
220
+ /**
221
+ * Run `fn` while holding the lock, then release automatically.
222
+ *
223
+ * ```ts
224
+ * const lock = new DistributedLock({ key: "my-resource" });
225
+ * await lock.withLock(async () => {
226
+ * // critical section
227
+ * });
228
+ * ```
229
+ */
230
+ async withLock(fn) {
231
+ const ok = await this.acquire();
232
+ if (!ok) {
233
+ throw new AcquireTimeoutError(this.key);
234
+ }
235
+ try {
236
+ return await fn();
237
+ } finally {
238
+ await this.release();
239
+ }
240
+ }
241
+ /** Close the underlying socket (idempotent). */
242
+ async close() {
243
+ if (this.closed) return;
244
+ this.closed = true;
245
+ this.stopRenew();
246
+ if (this.sock) {
247
+ this.sock.destroy();
248
+ this.sock = null;
249
+ }
250
+ this.token = null;
251
+ }
252
+ // -- internals --
253
+ startRenew() {
254
+ const interval = Math.max(1, this.lease * this.renewRatio) * 1e3;
255
+ const loop = async () => {
256
+ if (!this.sock || !this.token) return;
257
+ try {
258
+ await renew(this.sock, this.key, this.token, this.leaseTtlS);
259
+ } catch {
260
+ console.error(
261
+ `lock lost (renew failed): key=${this.key} token=${this.token}`
262
+ );
263
+ this.token = null;
264
+ return;
265
+ }
266
+ this.renewTimer = setTimeout(loop, interval);
267
+ };
268
+ this.renewTimer = setTimeout(loop, interval);
269
+ }
270
+ stopRenew() {
271
+ if (this.renewTimer != null) {
272
+ clearTimeout(this.renewTimer);
273
+ this.renewTimer = null;
274
+ }
275
+ }
276
+ };
277
+ export {
278
+ AcquireTimeoutError,
279
+ DistributedLock,
280
+ LockError,
281
+ acquire,
282
+ enqueue,
283
+ release,
284
+ renew,
285
+ waitForLock
286
+ };
287
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import * as net from \"net\";\n\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst DEFAULT_PORT = 6388;\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\nexport class AcquireTimeoutError extends Error {\n constructor(key: string) {\n super(`timeout acquiring '${key}'`);\n this.name = \"AcquireTimeoutError\";\n }\n}\n\nexport class LockError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"LockError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Low-level helpers\n// ---------------------------------------------------------------------------\n\nfunction encodeLines(...lines: string[]): Buffer {\n return Buffer.from(lines.map((l) => l + \"\\n\").join(\"\"), \"utf-8\");\n}\n\n/**\n * Read one newline-terminated line from the socket.\n * Resolves with the line (without trailing \\r\\n).\n * Rejects if the connection closes before a full line arrives.\n */\nfunction readline(sock: net.Socket): Promise<string> {\n return new Promise((resolve, reject) => {\n let buf = \"\";\n\n const onData = (chunk: Buffer) => {\n buf += chunk.toString(\"utf-8\");\n const idx = buf.indexOf(\"\\n\");\n if (idx !== -1) {\n cleanup();\n resolve(buf.slice(0, idx).replace(/\\r$/, \"\"));\n }\n };\n\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n\n const onClose = () => {\n cleanup();\n reject(new LockError(\"server closed connection\"));\n };\n\n const cleanup = () => {\n sock.removeListener(\"data\", onData);\n sock.removeListener(\"error\", onError);\n sock.removeListener(\"close\", onClose);\n };\n\n sock.on(\"data\", onData);\n sock.on(\"error\", onError);\n sock.on(\"close\", onClose);\n });\n}\n\nfunction connect(host: string, port: number): Promise<net.Socket> {\n return new Promise((resolve, reject) => {\n const sock = net.createConnection({ host, port }, () => resolve(sock));\n sock.on(\"error\", reject);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Protocol functions\n// ---------------------------------------------------------------------------\n\nexport async function acquire(\n sock: net.Socket,\n key: string,\n acquireTimeoutS: number,\n leaseTtlS?: number,\n): Promise<{ token: string; lease: number }> {\n const arg =\n leaseTtlS == null\n ? String(acquireTimeoutS)\n : `${acquireTimeoutS} ${leaseTtlS}`;\n\n sock.write(encodeLines(\"l\", key, arg));\n\n const resp = await readline(sock);\n if (resp === \"timeout\") {\n throw new AcquireTimeoutError(key);\n }\n if (!resp.startsWith(\"ok \")) {\n throw new LockError(`acquire failed: '${resp}'`);\n }\n\n const parts = resp.split(\" \");\n if (parts.length < 2) {\n throw new LockError(`bad ok response: '${resp}'`);\n }\n const token = parts[1];\n const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;\n return { token, lease };\n}\n\nexport async function renew(\n sock: net.Socket,\n key: string,\n token: string,\n leaseTtlS?: number,\n): Promise<number> {\n const arg = leaseTtlS == null ? token : `${token} ${leaseTtlS}`;\n sock.write(encodeLines(\"n\", key, arg));\n\n const resp = await readline(sock);\n if (!resp.startsWith(\"ok\")) {\n throw new LockError(`renew failed: '${resp}'`);\n }\n\n const parts = resp.split(\" \");\n if (parts.length >= 2 && /^\\d+$/.test(parts[1])) {\n return parseInt(parts[1], 10);\n }\n return -1;\n}\n\nexport async function enqueue(\n sock: net.Socket,\n key: string,\n leaseTtlS?: number,\n): Promise<{ status: \"acquired\" | \"queued\"; token: string | null; lease: number | null }> {\n const arg = leaseTtlS == null ? \"\" : String(leaseTtlS);\n sock.write(encodeLines(\"e\", key, arg));\n\n const resp = await readline(sock);\n if (resp.startsWith(\"acquired \")) {\n const parts = resp.split(\" \");\n const token = parts[1];\n const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 33;\n return { status: \"acquired\", token, lease };\n }\n if (resp === \"queued\") {\n return { status: \"queued\", token: null, lease: null };\n }\n throw new LockError(`enqueue failed: '${resp}'`);\n}\n\nexport async function waitForLock(\n sock: net.Socket,\n key: string,\n waitTimeoutS: number,\n): Promise<{ token: string; lease: number }> {\n sock.write(encodeLines(\"w\", key, String(waitTimeoutS)));\n\n const resp = await readline(sock);\n if (resp === \"timeout\") {\n throw new AcquireTimeoutError(key);\n }\n if (!resp.startsWith(\"ok \")) {\n throw new LockError(`wait failed: '${resp}'`);\n }\n\n const parts = resp.split(\" \");\n const token = parts[1];\n const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 33;\n return { token, lease };\n}\n\nexport async function release(\n sock: net.Socket,\n key: string,\n token: string,\n): Promise<void> {\n sock.write(encodeLines(\"r\", key, token));\n\n const resp = await readline(sock);\n if (resp !== \"ok\") {\n throw new LockError(`release failed: '${resp}'`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// DistributedLock\n// ---------------------------------------------------------------------------\n\nexport interface DistributedLockOptions {\n key: string;\n acquireTimeoutS?: number;\n leaseTtlS?: number;\n host?: string;\n port?: number;\n renewRatio?: number;\n}\n\nexport class DistributedLock {\n readonly key: string;\n readonly acquireTimeoutS: number;\n readonly leaseTtlS: number | undefined;\n readonly host: string;\n readonly port: number;\n readonly renewRatio: number;\n\n token: string | null = null;\n lease: number = 0;\n\n private sock: net.Socket | null = null;\n private renewTimer: ReturnType<typeof setTimeout> | null = null;\n private closed = false;\n\n constructor(opts: DistributedLockOptions) {\n this.key = opts.key;\n this.acquireTimeoutS = opts.acquireTimeoutS ?? 10;\n this.leaseTtlS = opts.leaseTtlS;\n this.host = opts.host ?? DEFAULT_HOST;\n this.port = opts.port ?? DEFAULT_PORT;\n this.renewRatio = opts.renewRatio ?? 0.5;\n }\n\n /** Acquire the lock. Returns `true` on success, `false` on timeout. */\n async acquire(): Promise<boolean> {\n this.closed = false;\n this.sock = await connect(this.host, this.port);\n try {\n const result = await acquire(\n this.sock,\n this.key,\n this.acquireTimeoutS,\n this.leaseTtlS,\n );\n this.token = result.token;\n this.lease = result.lease;\n } catch (err) {\n await this.close();\n if (err instanceof AcquireTimeoutError) return false;\n throw err;\n }\n this.startRenew();\n return true;\n }\n\n /** Release the lock and close the connection. */\n async release(): Promise<void> {\n try {\n this.stopRenew();\n if (this.sock && this.token) {\n await release(this.sock, this.key, this.token);\n }\n } finally {\n await this.close();\n }\n }\n\n /**\n * Two-phase step 1: connect and join the FIFO queue.\n * Returns `\"acquired\"` (fast-path, lock is already held) or `\"queued\"`.\n * If acquired immediately, the renew loop starts automatically.\n */\n async enqueue(): Promise<\"acquired\" | \"queued\"> {\n this.closed = false;\n this.sock = await connect(this.host, this.port);\n try {\n const result = await enqueue(this.sock, this.key, this.leaseTtlS);\n if (result.status === \"acquired\") {\n this.token = result.token;\n this.lease = result.lease ?? 0;\n this.startRenew();\n }\n return result.status;\n } catch (err) {\n await this.close();\n throw err;\n }\n }\n\n /**\n * Two-phase step 2: block until the lock is granted.\n * Returns `true` if granted, `false` on timeout.\n * If already acquired during `enqueue()`, returns `true` immediately.\n */\n async wait(timeoutS?: number): Promise<boolean> {\n if (this.token !== null) {\n // Already acquired during enqueue (fast path)\n return true;\n }\n if (!this.sock) {\n throw new LockError(\"not connected; call enqueue() first\");\n }\n const timeout = timeoutS ?? this.acquireTimeoutS;\n try {\n const result = await waitForLock(this.sock, this.key, timeout);\n this.token = result.token;\n this.lease = result.lease;\n } catch (err) {\n await this.close();\n if (err instanceof AcquireTimeoutError) return false;\n throw err;\n }\n this.startRenew();\n return true;\n }\n\n /**\n * Run `fn` while holding the lock, then release automatically.\n *\n * ```ts\n * const lock = new DistributedLock({ key: \"my-resource\" });\n * await lock.withLock(async () => {\n * // critical section\n * });\n * ```\n */\n async withLock<T>(fn: () => T | Promise<T>): Promise<T> {\n const ok = await this.acquire();\n if (!ok) {\n throw new AcquireTimeoutError(this.key);\n }\n try {\n return await fn();\n } finally {\n await this.release();\n }\n }\n\n /** Close the underlying socket (idempotent). */\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n this.stopRenew();\n if (this.sock) {\n this.sock.destroy();\n this.sock = null;\n }\n this.token = null;\n }\n\n // -- internals --\n\n private startRenew(): void {\n const interval = Math.max(1, this.lease * this.renewRatio) * 1000;\n const loop = async () => {\n if (!this.sock || !this.token) return;\n try {\n await renew(this.sock, this.key, this.token, this.leaseTtlS);\n } catch {\n console.error(\n `lock lost (renew failed): key=${this.key} token=${this.token}`,\n );\n this.token = null;\n return;\n }\n this.renewTimer = setTimeout(loop, interval);\n };\n this.renewTimer = setTimeout(loop, interval);\n }\n\n private stopRenew(): void {\n if (this.renewTimer != null) {\n clearTimeout(this.renewTimer);\n this.renewTimer = null;\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "dflockd-client",
3
+ "version": "1.0.0",
4
+ "description": "TypeScript client for the dflockd distributed lock daemon",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": {
9
+ "types": "./dist/client.d.ts",
10
+ "default": "./dist/client.js"
11
+ },
12
+ "require": {
13
+ "types": "./dist/client.d.cts",
14
+ "default": "./dist/client.cjs"
15
+ }
16
+ }
17
+ },
18
+ "main": "dist/client.cjs",
19
+ "module": "dist/client.js",
20
+ "types": "dist/client.d.ts",
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "typecheck": "tsc --noEmit",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "keywords": [
30
+ "distributed-lock",
31
+ "lock",
32
+ "mutex",
33
+ "dflockd",
34
+ "distributed"
35
+ ],
36
+ "author": "",
37
+ "license": "MIT",
38
+ "engines": {
39
+ "node": ">=16"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/mtingers/dflockd-client-ts.git"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.0.0",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5.7.0"
49
+ }
50
+ }