dflockd-client 1.10.1 → 1.10.2
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 +11 -451
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,26 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
TypeScript client for the [dflockd](https://github.com/mtingers/dflockd) distributed lock daemon.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
5
|
+
## Install
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
8
|
npm install dflockd-client
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
Start the [dflockd](https://github.com/mtingers/dflockd) server:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
dflockd
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### `withLock` (recommended)
|
|
22
|
-
|
|
23
|
-
The simplest way to use a lock. Acquires, runs your callback, and releases
|
|
24
|
-
automatically — even if the callback throws.
|
|
11
|
+
## Quick example
|
|
25
12
|
|
|
26
13
|
```ts
|
|
27
14
|
import { DistributedLock } from "dflockd-client";
|
|
@@ -34,445 +21,18 @@ await lock.withLock(async () => {
|
|
|
34
21
|
// lock is released
|
|
35
22
|
```
|
|
36
23
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
import { DistributedLock } from "dflockd-client";
|
|
41
|
-
|
|
42
|
-
const lock = new DistributedLock({
|
|
43
|
-
key: "my-resource",
|
|
44
|
-
acquireTimeoutS: 10, // wait up to 10 s (default)
|
|
45
|
-
leaseTtlS: 20, // server-side lease duration
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const ok = await lock.acquire(); // true on success, false on timeout
|
|
49
|
-
if (!ok) {
|
|
50
|
-
console.error("could not acquire lock");
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
// critical section — lock is held and auto-renewed
|
|
56
|
-
} finally {
|
|
57
|
-
await lock.release();
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Two-phase lock (enqueue / wait)
|
|
62
|
-
|
|
63
|
-
Split acquisition into two steps so you can notify an external system
|
|
64
|
-
(webhook, database, queue) between joining the FIFO queue and blocking.
|
|
65
|
-
|
|
66
|
-
```ts
|
|
67
|
-
import { DistributedLock } from "dflockd-client";
|
|
68
|
-
|
|
69
|
-
const lock = new DistributedLock({
|
|
70
|
-
key: "my-resource",
|
|
71
|
-
acquireTimeoutS: 10,
|
|
72
|
-
leaseTtlS: 20,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Step 1: join the queue (non-blocking, returns immediately)
|
|
76
|
-
const status = await lock.enqueue(); // "acquired" or "queued"
|
|
77
|
-
|
|
78
|
-
// Step 2: do something between enqueue and blocking
|
|
79
|
-
console.log(`enqueue status: ${status}`);
|
|
80
|
-
notifyExternalSystem(status);
|
|
81
|
-
|
|
82
|
-
// Step 3: block until the lock is granted (or timeout)
|
|
83
|
-
const granted = await lock.wait(10); // true on success, false on timeout
|
|
84
|
-
if (!granted) {
|
|
85
|
-
console.error("timed out waiting for lock");
|
|
86
|
-
process.exit(1);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
// critical section — lock is held and auto-renewed
|
|
91
|
-
} finally {
|
|
92
|
-
await lock.release();
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
If the lock is free when `enqueue()` is called, it returns `"acquired"` and
|
|
97
|
-
the lock is already held (fast path). `wait()` then returns `true` immediately
|
|
98
|
-
without blocking. If the lock is contended, `enqueue()` returns `"queued"` and
|
|
99
|
-
`wait()` blocks until the lock is granted or the timeout expires.
|
|
100
|
-
|
|
101
|
-
### Options
|
|
102
|
-
|
|
103
|
-
| Option | Type | Default | Description |
|
|
104
|
-
|--------------------|-------------------------------|--------------------------|--------------------------------------------------|
|
|
105
|
-
| `key` | `string` | *(required)* | Lock name |
|
|
106
|
-
| `acquireTimeoutS` | `number` | `10` | Seconds to wait for the lock before giving up (integer ≥ 0) |
|
|
107
|
-
| `leaseTtlS` | `number` | server default | Server-side lease duration in seconds (integer ≥ 1) |
|
|
108
|
-
| `servers` | `Array<[string, number]>` | `[["127.0.0.1", 6388]]` | List of `[host, port]` pairs |
|
|
109
|
-
| `shardingStrategy` | `ShardingStrategy` | `stableHashShard` | Function mapping `(key, numServers)` to a server index |
|
|
110
|
-
| `host` | `string` | `127.0.0.1` | Server host *(deprecated — use `servers`)* |
|
|
111
|
-
| `port` | `number` | `6388` | Server port *(deprecated — use `servers`)* |
|
|
112
|
-
| `renewRatio` | `number` | `0.5` | Renew at `lease * ratio` seconds (e.g. 50% of TTL)|
|
|
113
|
-
| `tls` | `tls.ConnectionOptions` | `undefined` | TLS options; pass `{}` for default system CA |
|
|
114
|
-
| `auth` | `string` | `undefined` | Auth token for servers started with `--auth-token` |
|
|
115
|
-
| `onLockLost` | `(key: string, token: string) => void` | `undefined` | Called when background lease renewal fails and the lock is lost |
|
|
116
|
-
| `connectTimeoutMs` | `number` | `undefined` | TCP connect timeout in milliseconds |
|
|
117
|
-
| `socketTimeoutMs` | `number` | `undefined` | Socket idle timeout in milliseconds |
|
|
24
|
+
## Features
|
|
118
25
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```ts
|
|
126
|
-
import { DistributedLock } from "dflockd-client";
|
|
127
|
-
|
|
128
|
-
const lock = new DistributedLock({
|
|
129
|
-
key: "my-resource",
|
|
130
|
-
servers: [
|
|
131
|
-
["10.0.0.1", 6388],
|
|
132
|
-
["10.0.0.2", 6388],
|
|
133
|
-
["10.0.0.3", 6388],
|
|
134
|
-
],
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
await lock.withLock(async () => {
|
|
138
|
-
// critical section — routed to a consistent server based on key
|
|
139
|
-
});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
You can supply a custom sharding strategy:
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
import { DistributedLock, ShardingStrategy } from "dflockd-client";
|
|
146
|
-
|
|
147
|
-
const roundRobin: ShardingStrategy = (_key, numServers) => {
|
|
148
|
-
return Math.floor(Math.random() * numServers);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const lock = new DistributedLock({
|
|
152
|
-
key: "my-resource",
|
|
153
|
-
servers: [
|
|
154
|
-
["10.0.0.1", 6388],
|
|
155
|
-
["10.0.0.2", 6388],
|
|
156
|
-
],
|
|
157
|
-
shardingStrategy: roundRobin,
|
|
158
|
-
});
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Semaphore
|
|
162
|
-
|
|
163
|
-
A semaphore allows up to N concurrent holders per key (instead of exactly 1
|
|
164
|
-
for a lock). The `DistributedSemaphore` API mirrors `DistributedLock`.
|
|
165
|
-
|
|
166
|
-
### `withLock` (recommended)
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
import { DistributedSemaphore } from "dflockd-client";
|
|
170
|
-
|
|
171
|
-
const sem = new DistributedSemaphore({ key: "my-resource", limit: 5 });
|
|
172
|
-
|
|
173
|
-
await sem.withLock(async () => {
|
|
174
|
-
// critical section — up to 5 concurrent holders
|
|
175
|
-
});
|
|
176
|
-
// slot is released
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Manual `acquire` / `release`
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
import { DistributedSemaphore } from "dflockd-client";
|
|
183
|
-
|
|
184
|
-
const sem = new DistributedSemaphore({
|
|
185
|
-
key: "my-resource",
|
|
186
|
-
limit: 5,
|
|
187
|
-
acquireTimeoutS: 10,
|
|
188
|
-
leaseTtlS: 20,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const ok = await sem.acquire(); // true on success, false on timeout
|
|
192
|
-
if (!ok) {
|
|
193
|
-
console.error("could not acquire semaphore slot");
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
// critical section — slot is held and auto-renewed
|
|
199
|
-
} finally {
|
|
200
|
-
await sem.release();
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### Two-phase semaphore (enqueue / wait)
|
|
205
|
-
|
|
206
|
-
```ts
|
|
207
|
-
import { DistributedSemaphore } from "dflockd-client";
|
|
208
|
-
|
|
209
|
-
const sem = new DistributedSemaphore({
|
|
210
|
-
key: "my-resource",
|
|
211
|
-
limit: 5,
|
|
212
|
-
acquireTimeoutS: 10,
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
const status = await sem.enqueue(); // "acquired" or "queued"
|
|
216
|
-
console.log(`enqueue status: ${status}`);
|
|
217
|
-
|
|
218
|
-
const granted = await sem.wait(10); // true on success, false on timeout
|
|
219
|
-
if (!granted) {
|
|
220
|
-
console.error("timed out waiting for semaphore slot");
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
// critical section — slot is held and auto-renewed
|
|
226
|
-
} finally {
|
|
227
|
-
await sem.release();
|
|
228
|
-
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Semaphore options
|
|
232
|
-
|
|
233
|
-
| Option | Type | Default | Description |
|
|
234
|
-
|--------------------|-------------------------------|--------------------------|--------------------------------------------------|
|
|
235
|
-
| `key` | `string` | *(required)* | Semaphore name |
|
|
236
|
-
| `limit` | `number` | *(required)* | Max concurrent holders (integer ≥ 1) |
|
|
237
|
-
| `acquireTimeoutS` | `number` | `10` | Seconds to wait before giving up (integer ≥ 0) |
|
|
238
|
-
| `leaseTtlS` | `number` | server default | Server-side lease duration in seconds (integer ≥ 1) |
|
|
239
|
-
| `servers` | `Array<[string, number]>` | `[["127.0.0.1", 6388]]` | List of `[host, port]` pairs |
|
|
240
|
-
| `shardingStrategy` | `ShardingStrategy` | `stableHashShard` | Function mapping `(key, numServers)` to a server index |
|
|
241
|
-
| `host` | `string` | `127.0.0.1` | Server host *(deprecated — use `servers`)* |
|
|
242
|
-
| `port` | `number` | `6388` | Server port *(deprecated — use `servers`)* |
|
|
243
|
-
| `renewRatio` | `number` | `0.5` | Renew at `lease * ratio` seconds (e.g. 50% of TTL)|
|
|
244
|
-
| `tls` | `tls.ConnectionOptions` | `undefined` | TLS options; pass `{}` for default system CA |
|
|
245
|
-
| `auth` | `string` | `undefined` | Auth token for servers started with `--auth-token` |
|
|
246
|
-
| `onLockLost` | `(key: string, token: string) => void` | `undefined` | Called when background lease renewal fails and the slot is lost |
|
|
247
|
-
| `connectTimeoutMs` | `number` | `undefined` | TCP connect timeout in milliseconds |
|
|
248
|
-
| `socketTimeoutMs` | `number` | `undefined` | Socket idle timeout in milliseconds |
|
|
249
|
-
|
|
250
|
-
## Stats
|
|
251
|
-
|
|
252
|
-
Query server runtime statistics (active connections, held locks, semaphores,
|
|
253
|
-
idle entries).
|
|
254
|
-
|
|
255
|
-
```ts
|
|
256
|
-
import { stats } from "dflockd-client";
|
|
257
|
-
|
|
258
|
-
const s = await stats();
|
|
259
|
-
console.log(`connections: ${s.connections}`);
|
|
260
|
-
console.log(`locks held: ${s.locks.length}`);
|
|
261
|
-
console.log(`semaphores active: ${s.semaphores.length}`);
|
|
262
|
-
|
|
263
|
-
for (const lock of s.locks) {
|
|
264
|
-
console.log(` ${lock.key} owner=${lock.owner_conn_id} expires_in=${lock.lease_expires_in_s}s waiters=${lock.waiters}`);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
for (const sem of s.semaphores) {
|
|
268
|
-
console.log(` ${sem.key} holders=${sem.holders}/${sem.limit} waiters=${sem.waiters}`);
|
|
269
|
-
}
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
Pass `{ host, port }` to query a specific server:
|
|
273
|
-
|
|
274
|
-
```ts
|
|
275
|
-
const s = await stats({ host: "10.0.0.1", port: 6388 });
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
## TLS
|
|
279
|
-
|
|
280
|
-
When the dflockd server is started with `--tls-cert` and `--tls-key`, all
|
|
281
|
-
connections must use TLS. Pass a `tls` option (accepting Node's
|
|
282
|
-
`tls.ConnectionOptions`) to enable TLS on the client:
|
|
283
|
-
|
|
284
|
-
```ts
|
|
285
|
-
import { DistributedLock, DistributedSemaphore, stats } from "dflockd-client";
|
|
286
|
-
|
|
287
|
-
// TLS with default system CA validation
|
|
288
|
-
const lock = new DistributedLock({ key: "my-resource", tls: {} });
|
|
289
|
-
|
|
290
|
-
// Self-signed CA
|
|
291
|
-
import * as fs from "fs";
|
|
292
|
-
const lock2 = new DistributedLock({
|
|
293
|
-
key: "my-resource",
|
|
294
|
-
tls: { ca: fs.readFileSync("ca.pem") },
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// Semaphore over TLS
|
|
298
|
-
const sem = new DistributedSemaphore({ key: "my-resource", limit: 5, tls: {} });
|
|
299
|
-
|
|
300
|
-
// Stats over TLS
|
|
301
|
-
const s = await stats({ host: "10.0.0.1", port: 6388, tls: {} });
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
## Authentication
|
|
305
|
-
|
|
306
|
-
When the dflockd server is started with `--auth-token`, every connection must
|
|
307
|
-
authenticate before sending any other command. Pass the `auth` option to enable
|
|
308
|
-
token-based authentication:
|
|
309
|
-
|
|
310
|
-
```ts
|
|
311
|
-
import { DistributedLock, DistributedSemaphore, stats } from "dflockd-client";
|
|
312
|
-
|
|
313
|
-
// Lock with auth
|
|
314
|
-
const lock = new DistributedLock({ key: "my-resource", auth: "my-secret-token" });
|
|
315
|
-
|
|
316
|
-
// Semaphore with auth
|
|
317
|
-
const sem = new DistributedSemaphore({ key: "my-resource", limit: 5, auth: "my-secret-token" });
|
|
318
|
-
|
|
319
|
-
// Stats with auth
|
|
320
|
-
const s = await stats({ host: "10.0.0.1", port: 6388, auth: "my-secret-token" });
|
|
321
|
-
|
|
322
|
-
// Combined with TLS
|
|
323
|
-
const secureLock = new DistributedLock({
|
|
324
|
-
key: "my-resource",
|
|
325
|
-
tls: {},
|
|
326
|
-
auth: "my-secret-token",
|
|
327
|
-
});
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
If authentication fails, an `AuthError` (a subclass of `LockError`) is thrown.
|
|
331
|
-
|
|
332
|
-
### Error handling
|
|
333
|
-
|
|
334
|
-
```ts
|
|
335
|
-
import { DistributedLock, AcquireTimeoutError, AuthError, LockError } from "dflockd-client";
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
await lock.withLock(async () => { /* ... */ });
|
|
339
|
-
} catch (err) {
|
|
340
|
-
if (err instanceof AcquireTimeoutError) {
|
|
341
|
-
// lock could not be acquired within acquireTimeoutS
|
|
342
|
-
} else if (err instanceof AuthError) {
|
|
343
|
-
// authentication failed (bad or missing token)
|
|
344
|
-
} else if (err instanceof LockError) {
|
|
345
|
-
// protocol-level error (bad token, server disconnect, etc.)
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Low-level functions
|
|
351
|
-
|
|
352
|
-
For cases where you manage the socket yourself:
|
|
353
|
-
|
|
354
|
-
```ts
|
|
355
|
-
import * as net from "net";
|
|
356
|
-
import {
|
|
357
|
-
acquire, enqueue, waitForLock, renew, release,
|
|
358
|
-
semAcquire, semEnqueue, semWaitForLock, semRenew, semRelease,
|
|
359
|
-
publish, stats,
|
|
360
|
-
} from "dflockd-client";
|
|
361
|
-
|
|
362
|
-
const sock = net.createConnection({ host: "127.0.0.1", port: 6388 });
|
|
363
|
-
|
|
364
|
-
// Lock — single-phase
|
|
365
|
-
const { token, lease } = await acquire(sock, "my-key", 10);
|
|
366
|
-
const remaining = await renew(sock, "my-key", token, 60);
|
|
367
|
-
await release(sock, "my-key", token);
|
|
368
|
-
|
|
369
|
-
// Lock — two-phase
|
|
370
|
-
const result = await enqueue(sock, "another-key"); // { status, token, lease }
|
|
371
|
-
if (result.status === "queued") {
|
|
372
|
-
const granted = await waitForLock(sock, "another-key", 10); // { token, lease }
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Semaphore — single-phase (limit = 5)
|
|
376
|
-
const sem = await semAcquire(sock, "sem-key", 10, 5); // { token, lease }
|
|
377
|
-
const semRemaining = await semRenew(sock, "sem-key", sem.token, 60);
|
|
378
|
-
await semRelease(sock, "sem-key", sem.token);
|
|
379
|
-
|
|
380
|
-
// Semaphore — two-phase
|
|
381
|
-
const semResult = await semEnqueue(sock, "sem-key", 5); // { status, token, lease }
|
|
382
|
-
if (semResult.status === "queued") {
|
|
383
|
-
const semGranted = await semWaitForLock(sock, "sem-key", 10); // { token, lease }
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Signal — publish only (no subscription on raw sockets)
|
|
387
|
-
const delivered = await publish(sock, "events.user.login", '{"user":"alice"}');
|
|
388
|
-
|
|
389
|
-
sock.destroy();
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
## Signals
|
|
393
|
-
|
|
394
|
-
Publish/subscribe messaging with NATS-style pattern matching and optional
|
|
395
|
-
queue groups for load-balanced consumption.
|
|
396
|
-
|
|
397
|
-
### `SignalConnection` (recommended)
|
|
398
|
-
|
|
399
|
-
```ts
|
|
400
|
-
import { SignalConnection } from "dflockd-client";
|
|
401
|
-
|
|
402
|
-
const conn = await SignalConnection.connect();
|
|
403
|
-
|
|
404
|
-
// Subscribe to signals
|
|
405
|
-
conn.onSignal((sig) => {
|
|
406
|
-
console.log(`${sig.channel}: ${sig.payload}`);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
await conn.listen("events.>");
|
|
410
|
-
|
|
411
|
-
// ... later
|
|
412
|
-
await conn.unlisten("events.>");
|
|
413
|
-
conn.close();
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
### Publish a signal
|
|
417
|
-
|
|
418
|
-
```ts
|
|
419
|
-
import { SignalConnection } from "dflockd-client";
|
|
420
|
-
|
|
421
|
-
const conn = await SignalConnection.connect();
|
|
422
|
-
const delivered = await conn.emit("events.user.login", '{"user":"alice"}');
|
|
423
|
-
console.log(`delivered to ${delivered} listener(s)`);
|
|
424
|
-
conn.close();
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Pattern matching
|
|
428
|
-
|
|
429
|
-
Subscription patterns support NATS-style wildcards:
|
|
430
|
-
|
|
431
|
-
- `*` matches exactly one dot-separated token (`events.*.login` matches
|
|
432
|
-
`events.user.login` but not `events.user.admin.login`)
|
|
433
|
-
- `>` matches one or more trailing tokens (`events.>` matches
|
|
434
|
-
`events.user.login` and `events.order.created`)
|
|
435
|
-
|
|
436
|
-
Publishing always uses literal channel names (no wildcards).
|
|
437
|
-
|
|
438
|
-
### Queue groups
|
|
439
|
-
|
|
440
|
-
Listeners can join a named queue group. Within a group, each signal is
|
|
441
|
-
delivered to exactly one member (round-robin), enabling load-balanced
|
|
442
|
-
processing. Non-grouped listeners always receive every matching signal.
|
|
443
|
-
|
|
444
|
-
```ts
|
|
445
|
-
// Worker 1
|
|
446
|
-
await conn.listen("tasks.>", "workers");
|
|
447
|
-
|
|
448
|
-
// Worker 2 (same group — only one receives each signal)
|
|
449
|
-
await conn2.listen("tasks.>", "workers");
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
### Async iterator
|
|
453
|
-
|
|
454
|
-
`SignalConnection` implements `Symbol.asyncIterator`, so you can consume
|
|
455
|
-
signals with a `for-await-of` loop:
|
|
456
|
-
|
|
457
|
-
```ts
|
|
458
|
-
const conn = await SignalConnection.connect();
|
|
459
|
-
await conn.listen("events.>");
|
|
460
|
-
|
|
461
|
-
for await (const sig of conn) {
|
|
462
|
-
console.log(sig.channel, sig.payload);
|
|
463
|
-
}
|
|
464
|
-
// Loop ends when conn.close() is called
|
|
465
|
-
```
|
|
26
|
+
- **Locks and semaphores** with automatic lease renewal
|
|
27
|
+
- **Two-phase locking** — enqueue then wait, with hooks between steps
|
|
28
|
+
- **Signal pub/sub** — NATS-style pattern matching and queue groups
|
|
29
|
+
- **Multi-server sharding** — CRC32-based consistent hashing
|
|
30
|
+
- **TLS and authentication** support
|
|
31
|
+
- **Low-level API** for direct socket control
|
|
466
32
|
|
|
467
|
-
|
|
33
|
+
## Documentation
|
|
468
34
|
|
|
469
|
-
|
|
470
|
-
|--------------------|-------------------------|----------------|-------------------------------------|
|
|
471
|
-
| `host` | `string` | `127.0.0.1` | Server host |
|
|
472
|
-
| `port` | `number` | `6388` | Server port |
|
|
473
|
-
| `tls` | `tls.ConnectionOptions` | `undefined` | TLS options; pass `{}` for system CA|
|
|
474
|
-
| `auth` | `string` | `undefined` | Auth token |
|
|
475
|
-
| `connectTimeoutMs` | `number` | `undefined` | TCP connect timeout in milliseconds |
|
|
35
|
+
Full docs at **[mtingers.github.io/dflockd-client-ts](https://mtingers.github.io/dflockd-client-ts/)**
|
|
476
36
|
|
|
477
37
|
## License
|
|
478
38
|
|