dflockd-client 1.0.0 → 1.2.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/README.md +155 -13
- package/dist/client.cjs +289 -6
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +85 -3
- package/dist/client.d.ts +85 -3
- package/dist/client.js +282 -6
- package/dist/client.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -10,10 +10,10 @@ npm install dflockd-client
|
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
|
-
Start the dflockd server
|
|
13
|
+
Start the [dflockd](https://github.com/mtingers/dflockd) server:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
16
|
+
dflockd
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
### `withLock` (recommended)
|
|
@@ -98,14 +98,142 @@ without blocking. If the lock is contended, `enqueue()` returns `"queued"` and
|
|
|
98
98
|
|
|
99
99
|
### Options
|
|
100
100
|
|
|
101
|
-
| Option
|
|
102
|
-
|
|
103
|
-
| `key`
|
|
104
|
-
| `acquireTimeoutS
|
|
105
|
-
| `leaseTtlS`
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
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
|
+
| `servers` | `Array<[string, number]>` | `[["127.0.0.1", 6388]]` | List of `[host, port]` pairs |
|
|
107
|
+
| `shardingStrategy` | `ShardingStrategy` | `stableHashShard` | Function mapping `(key, numServers)` to a server index |
|
|
108
|
+
| `host` | `string` | `127.0.0.1` | Server host *(deprecated — use `servers`)* |
|
|
109
|
+
| `port` | `number` | `6388` | Server port *(deprecated — use `servers`)* |
|
|
110
|
+
| `renewRatio` | `number` | `0.5` | Renew at `lease * ratio` seconds (e.g. 50% of TTL)|
|
|
111
|
+
|
|
112
|
+
### Multi-server sharding
|
|
113
|
+
|
|
114
|
+
Distribute locks across multiple dflockd instances. Each key is consistently
|
|
115
|
+
routed to the same server using CRC32-based hashing (matching Python's
|
|
116
|
+
`zlib.crc32`).
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { DistributedLock } from "dflockd-client";
|
|
120
|
+
|
|
121
|
+
const lock = new DistributedLock({
|
|
122
|
+
key: "my-resource",
|
|
123
|
+
servers: [
|
|
124
|
+
["10.0.0.1", 6388],
|
|
125
|
+
["10.0.0.2", 6388],
|
|
126
|
+
["10.0.0.3", 6388],
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await lock.withLock(async () => {
|
|
131
|
+
// critical section — routed to a consistent server based on key
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You can supply a custom sharding strategy:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { DistributedLock, ShardingStrategy } from "dflockd-client";
|
|
139
|
+
|
|
140
|
+
const roundRobin: ShardingStrategy = (_key, numServers) => {
|
|
141
|
+
return Math.floor(Math.random() * numServers);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const lock = new DistributedLock({
|
|
145
|
+
key: "my-resource",
|
|
146
|
+
servers: [
|
|
147
|
+
["10.0.0.1", 6388],
|
|
148
|
+
["10.0.0.2", 6388],
|
|
149
|
+
],
|
|
150
|
+
shardingStrategy: roundRobin,
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Semaphore
|
|
155
|
+
|
|
156
|
+
A semaphore allows up to N concurrent holders per key (instead of exactly 1
|
|
157
|
+
for a lock). The `DistributedSemaphore` API mirrors `DistributedLock`.
|
|
158
|
+
|
|
159
|
+
### `withLock` (recommended)
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { DistributedSemaphore } from "dflockd-client";
|
|
163
|
+
|
|
164
|
+
const sem = new DistributedSemaphore({ key: "my-resource", limit: 5 });
|
|
165
|
+
|
|
166
|
+
await sem.withLock(async () => {
|
|
167
|
+
// critical section — up to 5 concurrent holders
|
|
168
|
+
});
|
|
169
|
+
// slot is released
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Manual `acquire` / `release`
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { DistributedSemaphore } from "dflockd-client";
|
|
176
|
+
|
|
177
|
+
const sem = new DistributedSemaphore({
|
|
178
|
+
key: "my-resource",
|
|
179
|
+
limit: 5,
|
|
180
|
+
acquireTimeoutS: 10,
|
|
181
|
+
leaseTtlS: 20,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const ok = await sem.acquire(); // true on success, false on timeout
|
|
185
|
+
if (!ok) {
|
|
186
|
+
console.error("could not acquire semaphore slot");
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
// critical section — slot is held and auto-renewed
|
|
192
|
+
} finally {
|
|
193
|
+
await sem.release();
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Two-phase semaphore (enqueue / wait)
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { DistributedSemaphore } from "dflockd-client";
|
|
201
|
+
|
|
202
|
+
const sem = new DistributedSemaphore({
|
|
203
|
+
key: "my-resource",
|
|
204
|
+
limit: 5,
|
|
205
|
+
acquireTimeoutS: 10,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const status = await sem.enqueue(); // "acquired" or "queued"
|
|
209
|
+
console.log(`enqueue status: ${status}`);
|
|
210
|
+
|
|
211
|
+
const granted = await sem.wait(10); // true on success, false on timeout
|
|
212
|
+
if (!granted) {
|
|
213
|
+
console.error("timed out waiting for semaphore slot");
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
// critical section — slot is held and auto-renewed
|
|
219
|
+
} finally {
|
|
220
|
+
await sem.release();
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Semaphore options
|
|
225
|
+
|
|
226
|
+
| Option | Type | Default | Description |
|
|
227
|
+
|--------------------|-------------------------------|--------------------------|--------------------------------------------------|
|
|
228
|
+
| `key` | `string` | *(required)* | Semaphore name |
|
|
229
|
+
| `limit` | `number` | *(required)* | Max concurrent holders |
|
|
230
|
+
| `acquireTimeoutS` | `number` | `10` | Seconds to wait before giving up |
|
|
231
|
+
| `leaseTtlS` | `number` | server default | Server-side lease duration in seconds |
|
|
232
|
+
| `servers` | `Array<[string, number]>` | `[["127.0.0.1", 6388]]` | List of `[host, port]` pairs |
|
|
233
|
+
| `shardingStrategy` | `ShardingStrategy` | `stableHashShard` | Function mapping `(key, numServers)` to a server index |
|
|
234
|
+
| `host` | `string` | `127.0.0.1` | Server host *(deprecated — use `servers`)* |
|
|
235
|
+
| `port` | `number` | `6388` | Server port *(deprecated — use `servers`)* |
|
|
236
|
+
| `renewRatio` | `number` | `0.5` | Renew at `lease * ratio` seconds (e.g. 50% of TTL)|
|
|
109
237
|
|
|
110
238
|
### Error handling
|
|
111
239
|
|
|
@@ -129,21 +257,35 @@ For cases where you manage the socket yourself:
|
|
|
129
257
|
|
|
130
258
|
```ts
|
|
131
259
|
import * as net from "net";
|
|
132
|
-
import {
|
|
260
|
+
import {
|
|
261
|
+
acquire, enqueue, waitForLock, renew, release,
|
|
262
|
+
semAcquire, semEnqueue, semWaitForLock, semRenew, semRelease,
|
|
263
|
+
} from "dflockd-client";
|
|
133
264
|
|
|
134
265
|
const sock = net.createConnection({ host: "127.0.0.1", port: 6388 });
|
|
135
266
|
|
|
136
|
-
//
|
|
267
|
+
// Lock — single-phase
|
|
137
268
|
const { token, lease } = await acquire(sock, "my-key", 10);
|
|
138
269
|
const remaining = await renew(sock, "my-key", token, 60);
|
|
139
270
|
await release(sock, "my-key", token);
|
|
140
271
|
|
|
141
|
-
//
|
|
272
|
+
// Lock — two-phase
|
|
142
273
|
const result = await enqueue(sock, "another-key"); // { status, token, lease }
|
|
143
274
|
if (result.status === "queued") {
|
|
144
275
|
const granted = await waitForLock(sock, "another-key", 10); // { token, lease }
|
|
145
276
|
}
|
|
146
277
|
|
|
278
|
+
// Semaphore — single-phase (limit = 5)
|
|
279
|
+
const sem = await semAcquire(sock, "sem-key", 10, 5); // { token, lease }
|
|
280
|
+
const semRemaining = await semRenew(sock, "sem-key", sem.token, 60);
|
|
281
|
+
await semRelease(sock, "sem-key", sem.token);
|
|
282
|
+
|
|
283
|
+
// Semaphore — two-phase
|
|
284
|
+
const semResult = await semEnqueue(sock, "sem-key", 5); // { status, token, lease }
|
|
285
|
+
if (semResult.status === "queued") {
|
|
286
|
+
const semGranted = await semWaitForLock(sock, "sem-key", 10); // { token, lease }
|
|
287
|
+
}
|
|
288
|
+
|
|
147
289
|
sock.destroy();
|
|
148
290
|
```
|
|
149
291
|
|
package/dist/client.cjs
CHANGED
|
@@ -32,11 +32,18 @@ var client_exports = {};
|
|
|
32
32
|
__export(client_exports, {
|
|
33
33
|
AcquireTimeoutError: () => AcquireTimeoutError,
|
|
34
34
|
DistributedLock: () => DistributedLock,
|
|
35
|
+
DistributedSemaphore: () => DistributedSemaphore,
|
|
35
36
|
LockError: () => LockError,
|
|
36
37
|
acquire: () => acquire,
|
|
37
38
|
enqueue: () => enqueue,
|
|
38
39
|
release: () => release,
|
|
39
40
|
renew: () => renew,
|
|
41
|
+
semAcquire: () => semAcquire,
|
|
42
|
+
semEnqueue: () => semEnqueue,
|
|
43
|
+
semRelease: () => semRelease,
|
|
44
|
+
semRenew: () => semRenew,
|
|
45
|
+
semWaitForLock: () => semWaitForLock,
|
|
46
|
+
stableHashShard: () => stableHashShard,
|
|
40
47
|
waitForLock: () => waitForLock
|
|
41
48
|
});
|
|
42
49
|
module.exports = __toCommonJS(client_exports);
|
|
@@ -93,6 +100,24 @@ function connect(host, port) {
|
|
|
93
100
|
sock.on("error", reject);
|
|
94
101
|
});
|
|
95
102
|
}
|
|
103
|
+
var CRC32_TABLE = new Uint32Array(256);
|
|
104
|
+
for (let i = 0; i < 256; i++) {
|
|
105
|
+
let c = i;
|
|
106
|
+
for (let j = 0; j < 8; j++) {
|
|
107
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
108
|
+
}
|
|
109
|
+
CRC32_TABLE[i] = c;
|
|
110
|
+
}
|
|
111
|
+
function crc32(buf) {
|
|
112
|
+
let crc = 4294967295;
|
|
113
|
+
for (let i = 0; i < buf.length; i++) {
|
|
114
|
+
crc = CRC32_TABLE[(crc ^ buf[i]) & 255] ^ crc >>> 8;
|
|
115
|
+
}
|
|
116
|
+
return (crc ^ 4294967295) >>> 0;
|
|
117
|
+
}
|
|
118
|
+
function stableHashShard(key, numServers) {
|
|
119
|
+
return (crc32(Buffer.from(key, "utf-8")) >>> 0) % numServers;
|
|
120
|
+
}
|
|
96
121
|
async function acquire(sock, key, acquireTimeoutS, leaseTtlS) {
|
|
97
122
|
const arg = leaseTtlS == null ? String(acquireTimeoutS) : `${acquireTimeoutS} ${leaseTtlS}`;
|
|
98
123
|
sock.write(encodeLines("l", key, arg));
|
|
@@ -160,12 +185,79 @@ async function release(sock, key, token) {
|
|
|
160
185
|
throw new LockError(`release failed: '${resp}'`);
|
|
161
186
|
}
|
|
162
187
|
}
|
|
188
|
+
async function semAcquire(sock, key, acquireTimeoutS, limit, leaseTtlS) {
|
|
189
|
+
const arg = leaseTtlS == null ? `${acquireTimeoutS} ${limit}` : `${acquireTimeoutS} ${limit} ${leaseTtlS}`;
|
|
190
|
+
sock.write(encodeLines("sl", key, arg));
|
|
191
|
+
const resp = await readline(sock);
|
|
192
|
+
if (resp === "timeout") {
|
|
193
|
+
throw new AcquireTimeoutError(key);
|
|
194
|
+
}
|
|
195
|
+
if (!resp.startsWith("ok ")) {
|
|
196
|
+
throw new LockError(`sem_acquire failed: '${resp}'`);
|
|
197
|
+
}
|
|
198
|
+
const parts = resp.split(" ");
|
|
199
|
+
if (parts.length < 2) {
|
|
200
|
+
throw new LockError(`bad ok response: '${resp}'`);
|
|
201
|
+
}
|
|
202
|
+
const token = parts[1];
|
|
203
|
+
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
204
|
+
return { token, lease };
|
|
205
|
+
}
|
|
206
|
+
async function semRenew(sock, key, token, leaseTtlS) {
|
|
207
|
+
const arg = leaseTtlS == null ? token : `${token} ${leaseTtlS}`;
|
|
208
|
+
sock.write(encodeLines("sn", key, arg));
|
|
209
|
+
const resp = await readline(sock);
|
|
210
|
+
if (!resp.startsWith("ok")) {
|
|
211
|
+
throw new LockError(`sem_renew failed: '${resp}'`);
|
|
212
|
+
}
|
|
213
|
+
const parts = resp.split(" ");
|
|
214
|
+
if (parts.length >= 2 && /^\d+$/.test(parts[1])) {
|
|
215
|
+
return parseInt(parts[1], 10);
|
|
216
|
+
}
|
|
217
|
+
return -1;
|
|
218
|
+
}
|
|
219
|
+
async function semEnqueue(sock, key, limit, leaseTtlS) {
|
|
220
|
+
const arg = leaseTtlS == null ? String(limit) : `${limit} ${leaseTtlS}`;
|
|
221
|
+
sock.write(encodeLines("se", key, arg));
|
|
222
|
+
const resp = await readline(sock);
|
|
223
|
+
if (resp.startsWith("acquired ")) {
|
|
224
|
+
const parts = resp.split(" ");
|
|
225
|
+
const token = parts[1];
|
|
226
|
+
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
227
|
+
return { status: "acquired", token, lease };
|
|
228
|
+
}
|
|
229
|
+
if (resp === "queued") {
|
|
230
|
+
return { status: "queued", token: null, lease: null };
|
|
231
|
+
}
|
|
232
|
+
throw new LockError(`sem_enqueue failed: '${resp}'`);
|
|
233
|
+
}
|
|
234
|
+
async function semWaitForLock(sock, key, waitTimeoutS) {
|
|
235
|
+
sock.write(encodeLines("sw", key, String(waitTimeoutS)));
|
|
236
|
+
const resp = await readline(sock);
|
|
237
|
+
if (resp === "timeout") {
|
|
238
|
+
throw new AcquireTimeoutError(key);
|
|
239
|
+
}
|
|
240
|
+
if (!resp.startsWith("ok ")) {
|
|
241
|
+
throw new LockError(`sem_wait failed: '${resp}'`);
|
|
242
|
+
}
|
|
243
|
+
const parts = resp.split(" ");
|
|
244
|
+
const token = parts[1];
|
|
245
|
+
const lease = parts.length >= 3 ? parseInt(parts[2], 10) : 30;
|
|
246
|
+
return { token, lease };
|
|
247
|
+
}
|
|
248
|
+
async function semRelease(sock, key, token) {
|
|
249
|
+
sock.write(encodeLines("sr", key, token));
|
|
250
|
+
const resp = await readline(sock);
|
|
251
|
+
if (resp !== "ok") {
|
|
252
|
+
throw new LockError(`sem_release failed: '${resp}'`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
163
255
|
var DistributedLock = class {
|
|
164
256
|
key;
|
|
165
257
|
acquireTimeoutS;
|
|
166
258
|
leaseTtlS;
|
|
167
|
-
|
|
168
|
-
|
|
259
|
+
servers;
|
|
260
|
+
shardingStrategy;
|
|
169
261
|
renewRatio;
|
|
170
262
|
token = null;
|
|
171
263
|
lease = 0;
|
|
@@ -176,14 +268,26 @@ var DistributedLock = class {
|
|
|
176
268
|
this.key = opts.key;
|
|
177
269
|
this.acquireTimeoutS = opts.acquireTimeoutS ?? 10;
|
|
178
270
|
this.leaseTtlS = opts.leaseTtlS;
|
|
179
|
-
|
|
180
|
-
|
|
271
|
+
if (opts.servers) {
|
|
272
|
+
if (opts.servers.length === 0) {
|
|
273
|
+
throw new LockError("servers list must not be empty");
|
|
274
|
+
}
|
|
275
|
+
this.servers = opts.servers;
|
|
276
|
+
} else {
|
|
277
|
+
this.servers = [[opts.host ?? DEFAULT_HOST, opts.port ?? DEFAULT_PORT]];
|
|
278
|
+
}
|
|
279
|
+
this.shardingStrategy = opts.shardingStrategy ?? stableHashShard;
|
|
181
280
|
this.renewRatio = opts.renewRatio ?? 0.5;
|
|
182
281
|
}
|
|
282
|
+
pickServer() {
|
|
283
|
+
const idx = this.shardingStrategy(this.key, this.servers.length);
|
|
284
|
+
return this.servers[idx];
|
|
285
|
+
}
|
|
183
286
|
/** Acquire the lock. Returns `true` on success, `false` on timeout. */
|
|
184
287
|
async acquire() {
|
|
185
288
|
this.closed = false;
|
|
186
|
-
|
|
289
|
+
const [host, port] = this.pickServer();
|
|
290
|
+
this.sock = await connect(host, port);
|
|
187
291
|
try {
|
|
188
292
|
const result = await acquire(
|
|
189
293
|
this.sock,
|
|
@@ -219,7 +323,8 @@ var DistributedLock = class {
|
|
|
219
323
|
*/
|
|
220
324
|
async enqueue() {
|
|
221
325
|
this.closed = false;
|
|
222
|
-
|
|
326
|
+
const [host, port] = this.pickServer();
|
|
327
|
+
this.sock = await connect(host, port);
|
|
223
328
|
try {
|
|
224
329
|
const result = await enqueue(this.sock, this.key, this.leaseTtlS);
|
|
225
330
|
if (result.status === "acquired") {
|
|
@@ -315,15 +420,193 @@ var DistributedLock = class {
|
|
|
315
420
|
}
|
|
316
421
|
}
|
|
317
422
|
};
|
|
423
|
+
var DistributedSemaphore = class {
|
|
424
|
+
key;
|
|
425
|
+
limit;
|
|
426
|
+
acquireTimeoutS;
|
|
427
|
+
leaseTtlS;
|
|
428
|
+
servers;
|
|
429
|
+
shardingStrategy;
|
|
430
|
+
renewRatio;
|
|
431
|
+
token = null;
|
|
432
|
+
lease = 0;
|
|
433
|
+
sock = null;
|
|
434
|
+
renewTimer = null;
|
|
435
|
+
closed = false;
|
|
436
|
+
constructor(opts) {
|
|
437
|
+
this.key = opts.key;
|
|
438
|
+
this.limit = opts.limit;
|
|
439
|
+
this.acquireTimeoutS = opts.acquireTimeoutS ?? 10;
|
|
440
|
+
this.leaseTtlS = opts.leaseTtlS;
|
|
441
|
+
if (opts.servers) {
|
|
442
|
+
if (opts.servers.length === 0) {
|
|
443
|
+
throw new LockError("servers list must not be empty");
|
|
444
|
+
}
|
|
445
|
+
this.servers = opts.servers;
|
|
446
|
+
} else {
|
|
447
|
+
this.servers = [[opts.host ?? DEFAULT_HOST, opts.port ?? DEFAULT_PORT]];
|
|
448
|
+
}
|
|
449
|
+
this.shardingStrategy = opts.shardingStrategy ?? stableHashShard;
|
|
450
|
+
this.renewRatio = opts.renewRatio ?? 0.5;
|
|
451
|
+
}
|
|
452
|
+
pickServer() {
|
|
453
|
+
const idx = this.shardingStrategy(this.key, this.servers.length);
|
|
454
|
+
return this.servers[idx];
|
|
455
|
+
}
|
|
456
|
+
/** Acquire a semaphore slot. Returns `true` on success, `false` on timeout. */
|
|
457
|
+
async acquire() {
|
|
458
|
+
this.closed = false;
|
|
459
|
+
const [host, port] = this.pickServer();
|
|
460
|
+
this.sock = await connect(host, port);
|
|
461
|
+
try {
|
|
462
|
+
const result = await semAcquire(
|
|
463
|
+
this.sock,
|
|
464
|
+
this.key,
|
|
465
|
+
this.acquireTimeoutS,
|
|
466
|
+
this.limit,
|
|
467
|
+
this.leaseTtlS
|
|
468
|
+
);
|
|
469
|
+
this.token = result.token;
|
|
470
|
+
this.lease = result.lease;
|
|
471
|
+
} catch (err) {
|
|
472
|
+
await this.close();
|
|
473
|
+
if (err instanceof AcquireTimeoutError) return false;
|
|
474
|
+
throw err;
|
|
475
|
+
}
|
|
476
|
+
this.startRenew();
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
/** Release the semaphore slot and close the connection. */
|
|
480
|
+
async release() {
|
|
481
|
+
try {
|
|
482
|
+
this.stopRenew();
|
|
483
|
+
if (this.sock && this.token) {
|
|
484
|
+
await semRelease(this.sock, this.key, this.token);
|
|
485
|
+
}
|
|
486
|
+
} finally {
|
|
487
|
+
await this.close();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Two-phase step 1: connect and join the FIFO queue.
|
|
492
|
+
* Returns `"acquired"` (fast-path, slot granted immediately) or `"queued"`.
|
|
493
|
+
* If acquired immediately, the renew loop starts automatically.
|
|
494
|
+
*/
|
|
495
|
+
async enqueue() {
|
|
496
|
+
this.closed = false;
|
|
497
|
+
const [host, port] = this.pickServer();
|
|
498
|
+
this.sock = await connect(host, port);
|
|
499
|
+
try {
|
|
500
|
+
const result = await semEnqueue(this.sock, this.key, this.limit, this.leaseTtlS);
|
|
501
|
+
if (result.status === "acquired") {
|
|
502
|
+
this.token = result.token;
|
|
503
|
+
this.lease = result.lease ?? 0;
|
|
504
|
+
this.startRenew();
|
|
505
|
+
}
|
|
506
|
+
return result.status;
|
|
507
|
+
} catch (err) {
|
|
508
|
+
await this.close();
|
|
509
|
+
throw err;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Two-phase step 2: block until a semaphore slot is granted.
|
|
514
|
+
* Returns `true` if granted, `false` on timeout.
|
|
515
|
+
* If already acquired during `enqueue()`, returns `true` immediately.
|
|
516
|
+
*/
|
|
517
|
+
async wait(timeoutS) {
|
|
518
|
+
if (this.token !== null) {
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
if (!this.sock) {
|
|
522
|
+
throw new LockError("not connected; call enqueue() first");
|
|
523
|
+
}
|
|
524
|
+
const timeout = timeoutS ?? this.acquireTimeoutS;
|
|
525
|
+
try {
|
|
526
|
+
const result = await semWaitForLock(this.sock, this.key, timeout);
|
|
527
|
+
this.token = result.token;
|
|
528
|
+
this.lease = result.lease;
|
|
529
|
+
} catch (err) {
|
|
530
|
+
await this.close();
|
|
531
|
+
if (err instanceof AcquireTimeoutError) return false;
|
|
532
|
+
throw err;
|
|
533
|
+
}
|
|
534
|
+
this.startRenew();
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Run `fn` while holding a semaphore slot, then release automatically.
|
|
539
|
+
*
|
|
540
|
+
* ```ts
|
|
541
|
+
* const sem = new DistributedSemaphore({ key: "my-resource", limit: 5 });
|
|
542
|
+
* await sem.withLock(async () => {
|
|
543
|
+
* // critical section (up to 5 concurrent holders)
|
|
544
|
+
* });
|
|
545
|
+
* ```
|
|
546
|
+
*/
|
|
547
|
+
async withLock(fn) {
|
|
548
|
+
const ok = await this.acquire();
|
|
549
|
+
if (!ok) {
|
|
550
|
+
throw new AcquireTimeoutError(this.key);
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
return await fn();
|
|
554
|
+
} finally {
|
|
555
|
+
await this.release();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
/** Close the underlying socket (idempotent). */
|
|
559
|
+
async close() {
|
|
560
|
+
if (this.closed) return;
|
|
561
|
+
this.closed = true;
|
|
562
|
+
this.stopRenew();
|
|
563
|
+
if (this.sock) {
|
|
564
|
+
this.sock.destroy();
|
|
565
|
+
this.sock = null;
|
|
566
|
+
}
|
|
567
|
+
this.token = null;
|
|
568
|
+
}
|
|
569
|
+
// -- internals --
|
|
570
|
+
startRenew() {
|
|
571
|
+
const interval = Math.max(1, this.lease * this.renewRatio) * 1e3;
|
|
572
|
+
const loop = async () => {
|
|
573
|
+
if (!this.sock || !this.token) return;
|
|
574
|
+
try {
|
|
575
|
+
await semRenew(this.sock, this.key, this.token, this.leaseTtlS);
|
|
576
|
+
} catch {
|
|
577
|
+
console.error(
|
|
578
|
+
`semaphore lost (renew failed): key=${this.key} token=${this.token}`
|
|
579
|
+
);
|
|
580
|
+
this.token = null;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
this.renewTimer = setTimeout(loop, interval);
|
|
584
|
+
};
|
|
585
|
+
this.renewTimer = setTimeout(loop, interval);
|
|
586
|
+
}
|
|
587
|
+
stopRenew() {
|
|
588
|
+
if (this.renewTimer != null) {
|
|
589
|
+
clearTimeout(this.renewTimer);
|
|
590
|
+
this.renewTimer = null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
};
|
|
318
594
|
// Annotate the CommonJS export names for ESM import in node:
|
|
319
595
|
0 && (module.exports = {
|
|
320
596
|
AcquireTimeoutError,
|
|
321
597
|
DistributedLock,
|
|
598
|
+
DistributedSemaphore,
|
|
322
599
|
LockError,
|
|
323
600
|
acquire,
|
|
324
601
|
enqueue,
|
|
325
602
|
release,
|
|
326
603
|
renew,
|
|
604
|
+
semAcquire,
|
|
605
|
+
semEnqueue,
|
|
606
|
+
semRelease,
|
|
607
|
+
semRenew,
|
|
608
|
+
semWaitForLock,
|
|
609
|
+
stableHashShard,
|
|
327
610
|
waitForLock
|
|
328
611
|
});
|
|
329
612
|
//# sourceMappingURL=client.cjs.map
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +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":[]}
|
|
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// CRC32 (same algorithm as Python's zlib.crc32)\n// ---------------------------------------------------------------------------\n\nconst CRC32_TABLE = new Uint32Array(256);\nfor (let i = 0; i < 256; i++) {\n let c = i;\n for (let j = 0; j < 8; j++) {\n c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n }\n CRC32_TABLE[i] = c;\n}\n\nfunction crc32(buf: Buffer): number {\n let crc = 0xffffffff;\n for (let i = 0; i < buf.length; i++) {\n crc = CRC32_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);\n }\n return (crc ^ 0xffffffff) >>> 0;\n}\n\n// ---------------------------------------------------------------------------\n// Sharding\n// ---------------------------------------------------------------------------\n\nexport type ShardingStrategy = (key: string, numServers: number) => number;\n\nexport function stableHashShard(key: string, numServers: number): number {\n return (crc32(Buffer.from(key, \"utf-8\")) >>> 0) % numServers;\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// Semaphore protocol functions\n// ---------------------------------------------------------------------------\n\nexport async function semAcquire(\n sock: net.Socket,\n key: string,\n acquireTimeoutS: number,\n limit: number,\n leaseTtlS?: number,\n): Promise<{ token: string; lease: number }> {\n const arg =\n leaseTtlS == null\n ? `${acquireTimeoutS} ${limit}`\n : `${acquireTimeoutS} ${limit} ${leaseTtlS}`;\n\n sock.write(encodeLines(\"sl\", 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(`sem_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 semRenew(\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(\"sn\", key, arg));\n\n const resp = await readline(sock);\n if (!resp.startsWith(\"ok\")) {\n throw new LockError(`sem_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 semEnqueue(\n sock: net.Socket,\n key: string,\n limit: number,\n leaseTtlS?: number,\n): Promise<{ status: \"acquired\" | \"queued\"; token: string | null; lease: number | null }> {\n const arg = leaseTtlS == null ? String(limit) : `${limit} ${leaseTtlS}`;\n sock.write(encodeLines(\"se\", 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) : 30;\n return { status: \"acquired\", token, lease };\n }\n if (resp === \"queued\") {\n return { status: \"queued\", token: null, lease: null };\n }\n throw new LockError(`sem_enqueue failed: '${resp}'`);\n}\n\nexport async function semWaitForLock(\n sock: net.Socket,\n key: string,\n waitTimeoutS: number,\n): Promise<{ token: string; lease: number }> {\n sock.write(encodeLines(\"sw\", 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(`sem_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) : 30;\n return { token, lease };\n}\n\nexport async function semRelease(\n sock: net.Socket,\n key: string,\n token: string,\n): Promise<void> {\n sock.write(encodeLines(\"sr\", key, token));\n\n const resp = await readline(sock);\n if (resp !== \"ok\") {\n throw new LockError(`sem_release failed: '${resp}'`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// DistributedLock\n// ---------------------------------------------------------------------------\n\nexport interface DistributedLockOptions {\n key: string;\n acquireTimeoutS?: number;\n leaseTtlS?: number;\n /** @deprecated Use `servers` instead. */\n host?: string;\n /** @deprecated Use `servers` instead. */\n port?: number;\n servers?: Array<[host: string, port: number]>;\n shardingStrategy?: ShardingStrategy;\n renewRatio?: number;\n}\n\nexport class DistributedLock {\n readonly key: string;\n readonly acquireTimeoutS: number;\n readonly leaseTtlS: number | undefined;\n readonly servers: Array<[string, number]>;\n readonly shardingStrategy: ShardingStrategy;\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\n if (opts.servers) {\n if (opts.servers.length === 0) {\n throw new LockError(\"servers list must not be empty\");\n }\n this.servers = opts.servers;\n } else {\n this.servers = [[opts.host ?? DEFAULT_HOST, opts.port ?? DEFAULT_PORT]];\n }\n\n this.shardingStrategy = opts.shardingStrategy ?? stableHashShard;\n this.renewRatio = opts.renewRatio ?? 0.5;\n }\n\n private pickServer(): [string, number] {\n const idx = this.shardingStrategy(this.key, this.servers.length);\n return this.servers[idx];\n }\n\n /** Acquire the lock. Returns `true` on success, `false` on timeout. */\n async acquire(): Promise<boolean> {\n this.closed = false;\n const [host, port] = this.pickServer();\n this.sock = await connect(host, 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 const [host, port] = this.pickServer();\n this.sock = await connect(host, 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\n// ---------------------------------------------------------------------------\n// DistributedSemaphore\n// ---------------------------------------------------------------------------\n\nexport interface DistributedSemaphoreOptions {\n key: string;\n limit: number;\n acquireTimeoutS?: number;\n leaseTtlS?: number;\n /** @deprecated Use `servers` instead. */\n host?: string;\n /** @deprecated Use `servers` instead. */\n port?: number;\n servers?: Array<[host: string, port: number]>;\n shardingStrategy?: ShardingStrategy;\n renewRatio?: number;\n}\n\nexport class DistributedSemaphore {\n readonly key: string;\n readonly limit: number;\n readonly acquireTimeoutS: number;\n readonly leaseTtlS: number | undefined;\n readonly servers: Array<[string, number]>;\n readonly shardingStrategy: ShardingStrategy;\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: DistributedSemaphoreOptions) {\n this.key = opts.key;\n this.limit = opts.limit;\n this.acquireTimeoutS = opts.acquireTimeoutS ?? 10;\n this.leaseTtlS = opts.leaseTtlS;\n\n if (opts.servers) {\n if (opts.servers.length === 0) {\n throw new LockError(\"servers list must not be empty\");\n }\n this.servers = opts.servers;\n } else {\n this.servers = [[opts.host ?? DEFAULT_HOST, opts.port ?? DEFAULT_PORT]];\n }\n\n this.shardingStrategy = opts.shardingStrategy ?? stableHashShard;\n this.renewRatio = opts.renewRatio ?? 0.5;\n }\n\n private pickServer(): [string, number] {\n const idx = this.shardingStrategy(this.key, this.servers.length);\n return this.servers[idx];\n }\n\n /** Acquire a semaphore slot. Returns `true` on success, `false` on timeout. */\n async acquire(): Promise<boolean> {\n this.closed = false;\n const [host, port] = this.pickServer();\n this.sock = await connect(host, port);\n try {\n const result = await semAcquire(\n this.sock,\n this.key,\n this.acquireTimeoutS,\n this.limit,\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 semaphore slot and close the connection. */\n async release(): Promise<void> {\n try {\n this.stopRenew();\n if (this.sock && this.token) {\n await semRelease(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, slot granted immediately) or `\"queued\"`.\n * If acquired immediately, the renew loop starts automatically.\n */\n async enqueue(): Promise<\"acquired\" | \"queued\"> {\n this.closed = false;\n const [host, port] = this.pickServer();\n this.sock = await connect(host, port);\n try {\n const result = await semEnqueue(this.sock, this.key, this.limit, 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 a semaphore slot 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 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 semWaitForLock(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 a semaphore slot, then release automatically.\n *\n * ```ts\n * const sem = new DistributedSemaphore({ key: \"my-resource\", limit: 5 });\n * await sem.withLock(async () => {\n * // critical section (up to 5 concurrent holders)\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 semRenew(this.sock, this.key, this.token, this.leaseTtlS);\n } catch {\n console.error(\n `semaphore 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAAqB;AAErB,IAAM,eAAe;AACrB,IAAM,eAAe;AAMd,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YAAY,KAAa;AACvB,UAAM,sBAAsB,GAAG,GAAG;AAClC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,YAAN,cAAwB,MAAM;AAAA,EACnC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAMA,SAAS,eAAe,OAAyB;AAC/C,SAAO,OAAO,KAAK,MAAM,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO;AACjE;AAOA,SAAS,SAAS,MAAmC;AACnD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,MAAM;AAEV,UAAM,SAAS,CAAC,UAAkB;AAChC,aAAO,MAAM,SAAS,OAAO;AAC7B,YAAM,MAAM,IAAI,QAAQ,IAAI;AAC5B,UAAI,QAAQ,IAAI;AACd,gBAAQ;AACR,gBAAQ,IAAI,MAAM,GAAG,GAAG,EAAE,QAAQ,OAAO,EAAE,CAAC;AAAA,MAC9C;AAAA,IACF;AAEA,UAAM,UAAU,CAAC,QAAe;AAC9B,cAAQ;AACR,aAAO,GAAG;AAAA,IACZ;AAEA,UAAM,UAAU,MAAM;AACpB,cAAQ;AACR,aAAO,IAAI,UAAU,0BAA0B,CAAC;AAAA,IAClD;AAEA,UAAM,UAAU,MAAM;AACpB,WAAK,eAAe,QAAQ,MAAM;AAClC,WAAK,eAAe,SAAS,OAAO;AACpC,WAAK,eAAe,SAAS,OAAO;AAAA,IACtC;AAEA,SAAK,GAAG,QAAQ,MAAM;AACtB,SAAK,GAAG,SAAS,OAAO;AACxB,SAAK,GAAG,SAAS,OAAO;AAAA,EAC1B,CAAC;AACH;AAEA,SAAS,QAAQ,MAAc,MAAmC;AAChE,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,OAAW,qBAAiB,EAAE,MAAM,KAAK,GAAG,MAAM,QAAQ,IAAI,CAAC;AACrE,SAAK,GAAG,SAAS,MAAM;AAAA,EACzB,CAAC;AACH;AAMA,IAAM,cAAc,IAAI,YAAY,GAAG;AACvC,SAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,IAAI,IAAI,aAAc,MAAM,IAAK,MAAM;AAAA,EAC7C;AACA,cAAY,CAAC,IAAI;AACnB;AAEA,SAAS,MAAM,KAAqB;AAClC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAM,aAAa,MAAM,IAAI,CAAC,KAAK,GAAI,IAAK,QAAQ;AAAA,EACtD;AACA,UAAQ,MAAM,gBAAgB;AAChC;AAQO,SAAS,gBAAgB,KAAa,YAA4B;AACvE,UAAQ,MAAM,OAAO,KAAK,KAAK,OAAO,CAAC,MAAM,KAAK;AACpD;AAMA,eAAsB,QACpB,MACA,KACA,iBACA,WAC2C;AAC3C,QAAM,MACJ,aAAa,OACT,OAAO,eAAe,IACtB,GAAG,eAAe,IAAI,SAAS;AAErC,OAAK,MAAM,YAAY,KAAK,KAAK,GAAG,CAAC;AAErC,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,SAAS,WAAW;AACtB,UAAM,IAAI,oBAAoB,GAAG;AAAA,EACnC;AACA,MAAI,CAAC,KAAK,WAAW,KAAK,GAAG;AAC3B,UAAM,IAAI,UAAU,oBAAoB,IAAI,GAAG;AAAA,EACjD;AAEA,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,IAAI,UAAU,qBAAqB,IAAI,GAAG;AAAA,EAClD;AACA,QAAM,QAAQ,MAAM,CAAC;AACrB,QAAM,QAAQ,MAAM,UAAU,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC3D,SAAO,EAAE,OAAO,MAAM;AACxB;AAEA,eAAsB,MACpB,MACA,KACA,OACA,WACiB;AACjB,QAAM,MAAM,aAAa,OAAO,QAAQ,GAAG,KAAK,IAAI,SAAS;AAC7D,OAAK,MAAM,YAAY,KAAK,KAAK,GAAG,CAAC;AAErC,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,CAAC,KAAK,WAAW,IAAI,GAAG;AAC1B,UAAM,IAAI,UAAU,kBAAkB,IAAI,GAAG;AAAA,EAC/C;AAEA,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,MAAM,UAAU,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,GAAG;AAC/C,WAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,EAC9B;AACA,SAAO;AACT;AAEA,eAAsB,QACpB,MACA,KACA,WACwF;AACxF,QAAM,MAAM,aAAa,OAAO,KAAK,OAAO,SAAS;AACrD,OAAK,MAAM,YAAY,KAAK,KAAK,GAAG,CAAC;AAErC,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,KAAK,WAAW,WAAW,GAAG;AAChC,UAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,QAAQ,MAAM,UAAU,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC3D,WAAO,EAAE,QAAQ,YAAY,OAAO,MAAM;AAAA,EAC5C;AACA,MAAI,SAAS,UAAU;AACrB,WAAO,EAAE,QAAQ,UAAU,OAAO,MAAM,OAAO,KAAK;AAAA,EACtD;AACA,QAAM,IAAI,UAAU,oBAAoB,IAAI,GAAG;AACjD;AAEA,eAAsB,YACpB,MACA,KACA,cAC2C;AAC3C,OAAK,MAAM,YAAY,KAAK,KAAK,OAAO,YAAY,CAAC,CAAC;AAEtD,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,SAAS,WAAW;AACtB,UAAM,IAAI,oBAAoB,GAAG;AAAA,EACnC;AACA,MAAI,CAAC,KAAK,WAAW,KAAK,GAAG;AAC3B,UAAM,IAAI,UAAU,iBAAiB,IAAI,GAAG;AAAA,EAC9C;AAEA,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAM,QAAQ,MAAM,CAAC;AACrB,QAAM,QAAQ,MAAM,UAAU,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC3D,SAAO,EAAE,OAAO,MAAM;AACxB;AAEA,eAAsB,QACpB,MACA,KACA,OACe;AACf,OAAK,MAAM,YAAY,KAAK,KAAK,KAAK,CAAC;AAEvC,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,SAAS,MAAM;AACjB,UAAM,IAAI,UAAU,oBAAoB,IAAI,GAAG;AAAA,EACjD;AACF;AAMA,eAAsB,WACpB,MACA,KACA,iBACA,OACA,WAC2C;AAC3C,QAAM,MACJ,aAAa,OACT,GAAG,eAAe,IAAI,KAAK,KAC3B,GAAG,eAAe,IAAI,KAAK,IAAI,SAAS;AAE9C,OAAK,MAAM,YAAY,MAAM,KAAK,GAAG,CAAC;AAEtC,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,SAAS,WAAW;AACtB,UAAM,IAAI,oBAAoB,GAAG;AAAA,EACnC;AACA,MAAI,CAAC,KAAK,WAAW,KAAK,GAAG;AAC3B,UAAM,IAAI,UAAU,wBAAwB,IAAI,GAAG;AAAA,EACrD;AAEA,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,IAAI,UAAU,qBAAqB,IAAI,GAAG;AAAA,EAClD;AACA,QAAM,QAAQ,MAAM,CAAC;AACrB,QAAM,QAAQ,MAAM,UAAU,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC3D,SAAO,EAAE,OAAO,MAAM;AACxB;AAEA,eAAsB,SACpB,MACA,KACA,OACA,WACiB;AACjB,QAAM,MAAM,aAAa,OAAO,QAAQ,GAAG,KAAK,IAAI,SAAS;AAC7D,OAAK,MAAM,YAAY,MAAM,KAAK,GAAG,CAAC;AAEtC,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,CAAC,KAAK,WAAW,IAAI,GAAG;AAC1B,UAAM,IAAI,UAAU,sBAAsB,IAAI,GAAG;AAAA,EACnD;AAEA,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,MAAM,UAAU,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,GAAG;AAC/C,WAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,EAC9B;AACA,SAAO;AACT;AAEA,eAAsB,WACpB,MACA,KACA,OACA,WACwF;AACxF,QAAM,MAAM,aAAa,OAAO,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS;AACrE,OAAK,MAAM,YAAY,MAAM,KAAK,GAAG,CAAC;AAEtC,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,KAAK,WAAW,WAAW,GAAG;AAChC,UAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,QAAQ,MAAM,UAAU,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC3D,WAAO,EAAE,QAAQ,YAAY,OAAO,MAAM;AAAA,EAC5C;AACA,MAAI,SAAS,UAAU;AACrB,WAAO,EAAE,QAAQ,UAAU,OAAO,MAAM,OAAO,KAAK;AAAA,EACtD;AACA,QAAM,IAAI,UAAU,wBAAwB,IAAI,GAAG;AACrD;AAEA,eAAsB,eACpB,MACA,KACA,cAC2C;AAC3C,OAAK,MAAM,YAAY,MAAM,KAAK,OAAO,YAAY,CAAC,CAAC;AAEvD,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,SAAS,WAAW;AACtB,UAAM,IAAI,oBAAoB,GAAG;AAAA,EACnC;AACA,MAAI,CAAC,KAAK,WAAW,KAAK,GAAG;AAC3B,UAAM,IAAI,UAAU,qBAAqB,IAAI,GAAG;AAAA,EAClD;AAEA,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAM,QAAQ,MAAM,CAAC;AACrB,QAAM,QAAQ,MAAM,UAAU,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC3D,SAAO,EAAE,OAAO,MAAM;AACxB;AAEA,eAAsB,WACpB,MACA,KACA,OACe;AACf,OAAK,MAAM,YAAY,MAAM,KAAK,KAAK,CAAC;AAExC,QAAM,OAAO,MAAM,SAAS,IAAI;AAChC,MAAI,SAAS,MAAM;AACjB,UAAM,IAAI,UAAU,wBAAwB,IAAI,GAAG;AAAA,EACrD;AACF;AAmBO,IAAM,kBAAN,MAAsB;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,QAAuB;AAAA,EACvB,QAAgB;AAAA,EAER,OAA0B;AAAA,EAC1B,aAAmD;AAAA,EACnD,SAAS;AAAA,EAEjB,YAAY,MAA8B;AACxC,SAAK,MAAM,KAAK;AAChB,SAAK,kBAAkB,KAAK,mBAAmB;AAC/C,SAAK,YAAY,KAAK;AAEtB,QAAI,KAAK,SAAS;AAChB,UAAI,KAAK,QAAQ,WAAW,GAAG;AAC7B,cAAM,IAAI,UAAU,gCAAgC;AAAA,MACtD;AACA,WAAK,UAAU,KAAK;AAAA,IACtB,OAAO;AACL,WAAK,UAAU,CAAC,CAAC,KAAK,QAAQ,cAAc,KAAK,QAAQ,YAAY,CAAC;AAAA,IACxE;AAEA,SAAK,mBAAmB,KAAK,oBAAoB;AACjD,SAAK,aAAa,KAAK,cAAc;AAAA,EACvC;AAAA,EAEQ,aAA+B;AACrC,UAAM,MAAM,KAAK,iBAAiB,KAAK,KAAK,KAAK,QAAQ,MAAM;AAC/D,WAAO,KAAK,QAAQ,GAAG;AAAA,EACzB;AAAA;AAAA,EAGA,MAAM,UAA4B;AAChC,SAAK,SAAS;AACd,UAAM,CAAC,MAAM,IAAI,IAAI,KAAK,WAAW;AACrC,SAAK,OAAO,MAAM,QAAQ,MAAM,IAAI;AACpC,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP;AACA,WAAK,QAAQ,OAAO;AACpB,WAAK,QAAQ,OAAO;AAAA,IACtB,SAAS,KAAK;AACZ,YAAM,KAAK,MAAM;AACjB,UAAI,eAAe,oBAAqB,QAAO;AAC/C,YAAM;AAAA,IACR;AACA,SAAK,WAAW;AAChB,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,UAAyB;AAC7B,QAAI;AACF,WAAK,UAAU;AACf,UAAI,KAAK,QAAQ,KAAK,OAAO;AAC3B,cAAM,QAAQ,KAAK,MAAM,KAAK,KAAK,KAAK,KAAK;AAAA,MAC/C;AAAA,IACF,UAAE;AACA,YAAM,KAAK,MAAM;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAA0C;AAC9C,SAAK,SAAS;AACd,UAAM,CAAC,MAAM,IAAI,IAAI,KAAK,WAAW;AACrC,SAAK,OAAO,MAAM,QAAQ,MAAM,IAAI;AACpC,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,KAAK,KAAK,KAAK,SAAS;AAChE,UAAI,OAAO,WAAW,YAAY;AAChC,aAAK,QAAQ,OAAO;AACpB,aAAK,QAAQ,OAAO,SAAS;AAC7B,aAAK,WAAW;AAAA,MAClB;AACA,aAAO,OAAO;AAAA,IAChB,SAAS,KAAK;AACZ,YAAM,KAAK,MAAM;AACjB,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KAAK,UAAqC;AAC9C,QAAI,KAAK,UAAU,MAAM;AAEvB,aAAO;AAAA,IACT;AACA,QAAI,CAAC,KAAK,MAAM;AACd,YAAM,IAAI,UAAU,qCAAqC;AAAA,IAC3D;AACA,UAAM,UAAU,YAAY,KAAK;AACjC,QAAI;AACF,YAAM,SAAS,MAAM,YAAY,KAAK,MAAM,KAAK,KAAK,OAAO;AAC7D,WAAK,QAAQ,OAAO;AACpB,WAAK,QAAQ,OAAO;AAAA,IACtB,SAAS,KAAK;AACZ,YAAM,KAAK,MAAM;AACjB,UAAI,eAAe,oBAAqB,QAAO;AAC/C,YAAM;AAAA,IACR;AACA,SAAK,WAAW;AAChB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,SAAY,IAAsC;AACtD,UAAM,KAAK,MAAM,KAAK,QAAQ;AAC9B,QAAI,CAAC,IAAI;AACP,YAAM,IAAI,oBAAoB,KAAK,GAAG;AAAA,IACxC;AACA,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,UAAE;AACA,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,SAAK,UAAU;AACf,QAAI,KAAK,MAAM;AACb,WAAK,KAAK,QAAQ;AAClB,WAAK,OAAO;AAAA,IACd;AACA,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAIQ,aAAmB;AACzB,UAAM,WAAW,KAAK,IAAI,GAAG,KAAK,QAAQ,KAAK,UAAU,IAAI;AAC7D,UAAM,OAAO,YAAY;AACvB,UAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,MAAO;AAC/B,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,KAAK,KAAK,KAAK,OAAO,KAAK,SAAS;AAAA,MAC7D,QAAQ;AACN,gBAAQ;AAAA,UACN,iCAAiC,KAAK,GAAG,UAAU,KAAK,KAAK;AAAA,QAC/D;AACA,aAAK,QAAQ;AACb;AAAA,MACF;AACA,WAAK,aAAa,WAAW,MAAM,QAAQ;AAAA,IAC7C;AACA,SAAK,aAAa,WAAW,MAAM,QAAQ;AAAA,EAC7C;AAAA,EAEQ,YAAkB;AACxB,QAAI,KAAK,cAAc,MAAM;AAC3B,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AACF;AAoBO,IAAM,uBAAN,MAA2B;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,QAAuB;AAAA,EACvB,QAAgB;AAAA,EAER,OAA0B;AAAA,EAC1B,aAAmD;AAAA,EACnD,SAAS;AAAA,EAEjB,YAAY,MAAmC;AAC7C,SAAK,MAAM,KAAK;AAChB,SAAK,QAAQ,KAAK;AAClB,SAAK,kBAAkB,KAAK,mBAAmB;AAC/C,SAAK,YAAY,KAAK;AAEtB,QAAI,KAAK,SAAS;AAChB,UAAI,KAAK,QAAQ,WAAW,GAAG;AAC7B,cAAM,IAAI,UAAU,gCAAgC;AAAA,MACtD;AACA,WAAK,UAAU,KAAK;AAAA,IACtB,OAAO;AACL,WAAK,UAAU,CAAC,CAAC,KAAK,QAAQ,cAAc,KAAK,QAAQ,YAAY,CAAC;AAAA,IACxE;AAEA,SAAK,mBAAmB,KAAK,oBAAoB;AACjD,SAAK,aAAa,KAAK,cAAc;AAAA,EACvC;AAAA,EAEQ,aAA+B;AACrC,UAAM,MAAM,KAAK,iBAAiB,KAAK,KAAK,KAAK,QAAQ,MAAM;AAC/D,WAAO,KAAK,QAAQ,GAAG;AAAA,EACzB;AAAA;AAAA,EAGA,MAAM,UAA4B;AAChC,SAAK,SAAS;AACd,UAAM,CAAC,MAAM,IAAI,IAAI,KAAK,WAAW;AACrC,SAAK,OAAO,MAAM,QAAQ,MAAM,IAAI;AACpC,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP;AACA,WAAK,QAAQ,OAAO;AACpB,WAAK,QAAQ,OAAO;AAAA,IACtB,SAAS,KAAK;AACZ,YAAM,KAAK,MAAM;AACjB,UAAI,eAAe,oBAAqB,QAAO;AAC/C,YAAM;AAAA,IACR;AACA,SAAK,WAAW;AAChB,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,UAAyB;AAC7B,QAAI;AACF,WAAK,UAAU;AACf,UAAI,KAAK,QAAQ,KAAK,OAAO;AAC3B,cAAM,WAAW,KAAK,MAAM,KAAK,KAAK,KAAK,KAAK;AAAA,MAClD;AAAA,IACF,UAAE;AACA,YAAM,KAAK,MAAM;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAA0C;AAC9C,SAAK,SAAS;AACd,UAAM,CAAC,MAAM,IAAI,IAAI,KAAK,WAAW;AACrC,SAAK,OAAO,MAAM,QAAQ,MAAM,IAAI;AACpC,QAAI;AACF,YAAM,SAAS,MAAM,WAAW,KAAK,MAAM,KAAK,KAAK,KAAK,OAAO,KAAK,SAAS;AAC/E,UAAI,OAAO,WAAW,YAAY;AAChC,aAAK,QAAQ,OAAO;AACpB,aAAK,QAAQ,OAAO,SAAS;AAC7B,aAAK,WAAW;AAAA,MAClB;AACA,aAAO,OAAO;AAAA,IAChB,SAAS,KAAK;AACZ,YAAM,KAAK,MAAM;AACjB,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KAAK,UAAqC;AAC9C,QAAI,KAAK,UAAU,MAAM;AACvB,aAAO;AAAA,IACT;AACA,QAAI,CAAC,KAAK,MAAM;AACd,YAAM,IAAI,UAAU,qCAAqC;AAAA,IAC3D;AACA,UAAM,UAAU,YAAY,KAAK;AACjC,QAAI;AACF,YAAM,SAAS,MAAM,eAAe,KAAK,MAAM,KAAK,KAAK,OAAO;AAChE,WAAK,QAAQ,OAAO;AACpB,WAAK,QAAQ,OAAO;AAAA,IACtB,SAAS,KAAK;AACZ,YAAM,KAAK,MAAM;AACjB,UAAI,eAAe,oBAAqB,QAAO;AAC/C,YAAM;AAAA,IACR;AACA,SAAK,WAAW;AAChB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,SAAY,IAAsC;AACtD,UAAM,KAAK,MAAM,KAAK,QAAQ;AAC9B,QAAI,CAAC,IAAI;AACP,YAAM,IAAI,oBAAoB,KAAK,GAAG;AAAA,IACxC;AACA,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,UAAE;AACA,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,SAAK,UAAU;AACf,QAAI,KAAK,MAAM;AACb,WAAK,KAAK,QAAQ;AAClB,WAAK,OAAO;AAAA,IACd;AACA,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAIQ,aAAmB;AACzB,UAAM,WAAW,KAAK,IAAI,GAAG,KAAK,QAAQ,KAAK,UAAU,IAAI;AAC7D,UAAM,OAAO,YAAY;AACvB,UAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,MAAO;AAC/B,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,KAAK,KAAK,KAAK,OAAO,KAAK,SAAS;AAAA,MAChE,QAAQ;AACN,gBAAQ;AAAA,UACN,sCAAsC,KAAK,GAAG,UAAU,KAAK,KAAK;AAAA,QACpE;AACA,aAAK,QAAQ;AACb;AAAA,MACF;AACA,WAAK,aAAa,WAAW,MAAM,QAAQ;AAAA,IAC7C;AACA,SAAK,aAAa,WAAW,MAAM,QAAQ;AAAA,EAC7C;AAAA,EAEQ,YAAkB;AACxB,QAAI,KAAK,cAAc,MAAM;AAC3B,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AACF;","names":[]}
|