@hardlydifficult/worker-server 1.0.10 → 1.0.11

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.
Files changed (2) hide show
  1. package/README.md +134 -434
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @hardlydifficult/worker-server
2
2
 
3
- A WebSocket-based server for managing remote worker connections with health monitoring, message routing, and load balancing.
3
+ WebSocket-based remote worker server with health monitoring, message routing, and load balancing.
4
4
 
5
5
  ## Installation
6
6
 
@@ -11,468 +11,200 @@ npm install @hardlydifficult/worker-server
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { WorkerServer } from "@hardlydifficult/worker-server";
14
+ import { WorkerServer, WorkerStatus } from "@hardlydifficult/worker-server";
15
+
16
+ const server = new WorkerServer({
17
+ port: 19100,
18
+ authToken: "secret-token", // optional
19
+ });
20
+
21
+ server.onWorkerConnected((worker) => {
22
+ console.log(`Worker ${worker.name} connected with status ${worker.status}`);
23
+ });
15
24
 
16
- // Create and start the server
17
- const server = new WorkerServer({ port: 3000 });
18
-
19
- server.start().then(() => {
20
- console.log("Worker server running on port 3000");
21
-
22
- // Listen for worker connections
23
- server.onWorkerConnected((worker) => {
24
- console.log(`Worker ${worker.name} (${worker.id}) connected`);
25
- });
26
-
27
- server.onWorkerDisconnected((worker) => {
28
- console.log(`Worker ${worker.name} (${worker.id}) disconnected`);
29
- });
30
-
31
- // Route messages by type
32
- server.onWorkerMessage("work_complete", (worker, message) => {
33
- console.log(`Worker ${worker.id} completed request: ${message.requestId}`);
34
- });
25
+ server.onWorkerMessage("work_result", (worker, message) => {
26
+ console.log(`Worker ${worker.id} completed request:`, message);
35
27
  });
28
+
29
+ await server.start();
30
+ console.log("Worker server listening on port", server.port);
36
31
  ```
37
32
 
38
33
  ## Core Concepts
39
34
 
40
- ### Worker Registration & Lifecycle
35
+ ### WorkerServer
41
36
 
42
- Workers connect via WebSocket and register with authentication (optional). The server tracks their status, heartbeat, and request load.
37
+ Main entry point for managing worker connections via WebSocket.
43
38
 
44
39
  ```typescript
45
40
  import { WorkerServer } from "@hardlydifficult/worker-server";
46
41
 
47
- const server = new WorkerServer({
48
- port: 3000,
49
- authToken: "secret-token" // Optional
42
+ const server = new WorkerServer({
43
+ port: 19100,
44
+ authToken: "secret", // optional
45
+ heartbeatTimeoutMs: 60_000,
46
+ healthCheckIntervalMs: 10_000,
47
+ heartbeatIntervalMs: 15_000,
48
+ logger: myLogger,
50
49
  });
51
50
 
52
- server.start();
53
- server.onWorkerConnected((worker) => {
54
- console.log("Worker connected:", worker.id, worker.name);
55
- });
51
+ await server.start();
52
+ // Handle connections, messages, and shutdown
53
+ await server.stop();
56
54
  ```
57
55
 
58
- Workers send a `worker_registration` message:
56
+ #### Lifecycle Events
59
57
 
60
- ```json
61
- {
62
- "type": "worker_registration",
63
- "workerId": "worker-1",
64
- "workerName": "GPU Worker",
65
- "capabilities": {
66
- "models": [
67
- {
68
- "modelId": "gpt-4",
69
- "displayName": "GPT-4",
70
- "maxContextTokens": 32768,
71
- "maxOutputTokens": 4096,
72
- "supportsStreaming": true
73
- }
74
- ],
75
- "maxConcurrentRequests": 2
76
- },
77
- "authToken": "secret-token"
78
- }
79
- ```
58
+ | Method | Description |
59
+ |--------|-------------|
60
+ | `onWorkerConnected(handler)` | Called when a worker registers successfully |
61
+ | `onWorkerDisconnected(handler)` | Called when a worker disconnects; includes pending request IDs |
80
62
 
81
- The server responds with a registration acknowledgment:
63
+ ```typescript
64
+ server.onWorkerConnected((worker) => {
65
+ console.log(`Connected: ${worker.id} (${worker.name})`);
66
+ });
82
67
 
83
- ```json
84
- {
85
- "type": "worker_registration_ack",
86
- "success": true,
87
- "sessionId": "uuid-here",
88
- "heartbeatIntervalMs": 15000
89
- }
68
+ server.onWorkerDisconnected((worker, pendingRequestIds) => {
69
+ console.log(`Disconnected: ${worker.id} with ${pendingRequestIds.size} pending requests`);
70
+ });
90
71
  ```
91
72
 
92
- ### Message Routing
73
+ #### Message Routing
93
74
 
94
- Messages are routed by the `type` field. Handlers receive the worker info and message payload.
75
+ Register handlers for message types sent by workers:
95
76
 
96
77
  ```typescript
97
- server.onWorkerMessage("status_update", (worker, message) => {
98
- console.log(`Worker ${worker.id} status: ${message.statusText}`);
78
+ server.onWorkerMessage("work_complete", (worker, message) => {
79
+ const { requestId, result } = message;
80
+ console.log(`Worker ${worker.id} completed ${requestId}`);
99
81
  });
100
82
 
101
83
  // Send messages to workers
102
- server.send(workerId, {
103
- type: "execute",
104
- requestId: "req-1",
105
- prompt: "Hello, world!"
106
- });
107
-
108
- // Broadcast to all workers
84
+ const success = server.send(workerId, { type: "work_request", requestId: "req-1" });
109
85
  server.broadcast({ type: "shutdown" });
110
86
  ```
111
87
 
112
- ### Worker Selection & Load Balancing
113
-
114
- Select workers by model support or use any available worker. Workers are automatically assigned least-loaded.
88
+ #### Worker Selection & Pool Queries
115
89
 
116
90
  ```typescript
117
- // Get the least-loaded worker that supports a specific model
118
- const worker = server.getAvailableWorker("gpt-4");
119
- if (worker) {
120
- server.send(worker.id, { type: "execute", prompt: "..." });
121
- }
91
+ // Get least-loaded worker supporting a model
92
+ const worker = server.getAvailableWorker("sonnet");
122
93
 
123
94
  // Get any available worker (model-agnostic)
124
- const anyWorker = server.getAnyAvailableWorker();
125
- ```
126
-
127
- Request tracking ensures accurate load reporting:
128
-
129
- ```typescript
130
- // Track when a request is assigned
131
- server.trackRequest(worker.id, requestId);
95
+ const any = server.getAnyAvailableWorker();
132
96
 
133
- // Release when the response is received (optionally increment completed count)
134
- server.releaseRequest(requestId, { incrementCompleted: true });
135
- ```
136
-
137
- ### Health Monitoring
138
-
139
- Workers must send periodic heartbeats. Unresponsive workers are marked unhealthy and eventually removed.
97
+ // Slot counts
98
+ console.log("Available slots:", server.getAvailableSlotCount("sonnet", "local"));
140
99
 
141
- ```typescript
142
- // Heartbeat message format (worker → server)
143
- {
144
- "type": "heartbeat",
145
- "workerId": "worker-1",
146
- "timestamp": "2024-01-01T00:00:00.000Z"
147
- }
148
-
149
- // Server response
150
- {
151
- "type": "heartbeat_ack",
152
- "timestamp": "2024-01-01T00:00:00.000Z",
153
- "nextHeartbeatDeadline": "2024-01-01T00:01:00.000Z"
100
+ // Worker info
101
+ for (const info of server.getWorkerInfo()) {
102
+ console.log(`${info.name}: ${info.status} (${info.activeRequests}/${info.capabilities.maxConcurrentRequests})`);
154
103
  }
155
104
  ```
156
105
 
157
- Health checks run automatically at the configured interval (default: 10s). Workers missing heartbeats for >3× timeout are removed.
158
-
159
- ```typescript
160
- const server = new WorkerServer({
161
- port: 3000,
162
- heartbeatTimeoutMs: 60_000, // 60 seconds before unhealthy
163
- healthCheckIntervalMs: 10_000, // Check every 10 seconds
164
- });
165
- ```
166
-
167
- ### Category-Aware Concurrency
106
+ #### Request Tracking
168
107
 
169
- Workers can specify per-category concurrency limits for fine-grained control.
108
+ Track and release requests for accurate availability:
170
109
 
171
110
  ```typescript
172
- const server = new WorkerServer({ port: 3000 });
111
+ // When assigning a request to a worker
112
+ server.trackRequest(workerId, requestId, "local");
173
113
 
174
- // Worker registration includes concurrency limits
175
- {
176
- "capabilities": {
177
- "models": [.],
178
- "maxConcurrentRequests": 4,
179
- "concurrencyLimits": {
180
- "chat": 2,
181
- "embedding": 3,
182
- "tool_use": 1
183
- }
184
- }
185
- }
186
-
187
- // Track with category
188
- server.trackRequest(worker.id, requestId, "chat");
189
-
190
- // Release without specifying category (looked up automatically)
191
- server.releaseRequest(requestId);
114
+ // When the request completes
115
+ server.releaseRequest(requestId, { incrementCompleted: true });
192
116
  ```
193
117
 
194
- ## HTTP & WebSocket Extensibility
195
-
196
- ### Custom HTTP Endpoints
118
+ #### Extensibility
197
119
 
198
- Add HTTP handlers that return `true` when they handle the request.
120
+ Add HTTP endpoints and custom WebSocket paths:
199
121
 
200
122
  ```typescript
123
+ // HTTP handler
201
124
  server.addHttpHandler(async (req, res) => {
202
125
  if (req.url === "/health") {
203
126
  res.writeHead(200, { "Content-Type": "application/json" });
204
- res.end(JSON.stringify({ status: "ok" }));
127
+ res.end(JSON.stringify({ ok: true }));
205
128
  return true;
206
129
  }
207
130
  return false;
208
131
  });
209
- ```
210
-
211
- ### Additional WebSocket Endpoints
212
132
 
213
- Register additional WebSocket paths for non-worker connections.
214
-
215
- ```typescript
216
- server.addWebSocketEndpoint("/ws/admin", (ws) => {
217
- ws.on("message", (data) => {
218
- // Handle admin messages
219
- });
133
+ // Custom WebSocket endpoint
134
+ server.addWebSocketEndpoint("/ws/dashboard", (ws) => {
135
+ ws.send(JSON.stringify({ type: "hello" }));
220
136
  });
221
137
  ```
222
138
 
223
- ### Worker Info
224
-
225
- Public worker info (without WebSocket reference):
226
-
227
- ```typescript
228
- const worker = server.getAvailableWorker("gpt-4");
229
- if (worker) {
230
- console.log("Active requests:", worker.activeRequests);
231
- console.log("Completed requests:", worker.completedRequests);
232
- console.log("Pending request IDs:", [...worker.pendingRequestIds]);
233
- console.log("Per-category active requests:", worker.categoryActiveRequests);
234
- }
235
- ```
236
-
237
- ## Core Components
238
-
239
- ### WorkerServer
240
-
241
- Main server class managing WebSocket connections, HTTP endpoints, and worker pool.
242
-
243
- #### Constructor
244
-
245
- | Parameter | Type | Default | Description |
246
- |-----------|------|---------|-------------|
247
- | `port` | `number` | — | HTTP + WebSocket server port |
248
- | `authToken` | `string` (optional) | — | Token required for worker registration |
249
- | `heartbeatTimeoutMs` | `number` | 60000 | Timeout before marking worker unhealthy |
250
- | `healthCheckIntervalMs` | `number` | 10000 | Interval for health checks |
251
- | `heartbeatIntervalMs` | `number` | 15000 | Heartbeat interval communicated to workers |
252
- | `logger` | `WorkerServerLogger` (optional) | No-op | Logger instance |
253
-
254
- #### Lifecycle Management
255
-
256
- ```typescript
257
- const server = new WorkerServer({ port: 8080, authToken: "secret" });
258
-
259
- // Start the server
260
- await server.start();
261
-
262
- // Stop the server gracefully
263
- await server.stop();
264
- ```
265
-
266
- #### Registration Handlers
267
-
268
- ```typescript
269
- // Called when a worker successfully registers
270
- const unsubscribeConnected = server.onWorkerConnected((worker) => {
271
- console.log(`Worker connected: ${worker.name}`);
272
- });
273
-
274
- // Called when a worker disconnects
275
- const unsubscribeDisconnected = server.onWorkerDisconnected((worker, pending) => {
276
- console.log(`Worker disconnected with ${pending.size} pending requests`);
277
- });
278
- ```
279
-
280
- #### Message Handling
281
-
282
- ```typescript
283
- // Register handlers for domain-specific messages by type
284
- server.onWorkerMessage("work_request", (worker, message) => {
285
- // Process work request from worker
286
- });
287
-
288
- server.onWorkerMessage("status_update", (worker, message) => {
289
- // Handle status updates from worker
290
- });
291
- ```
292
-
293
- #### Sending Messages
294
-
295
- ```typescript
296
- // Send to a specific worker
297
- const success = server.send("worker-1", { type: "stop", reason: "shutdown" });
298
-
299
- // Broadcast to all connected workers
300
- server.broadcast({ type: "maintenance_start" });
301
- ```
139
+ ### WorkerPool
302
140
 
303
- #### Pool Queries
141
+ Low-level pool manager for worker state and selection.
304
142
 
305
143
  ```typescript
306
- // Get least-loaded worker supporting a specific model
307
- const worker = server.getAvailableWorker("sonnet-3.5");
144
+ import { WorkerPool, WorkerStatus } from "@hardlydifficult/worker-server";
308
145
 
309
- // Get any available worker (model-agnostic)
310
- const anyWorker = server.getAnyAvailableWorker();
146
+ const pool = new WorkerPool(logger);
311
147
 
312
- // Get all worker info
313
- const workers = server.getWorkerInfo(); // Returns WorkerInfo[]
148
+ pool.add(worker);
149
+ pool.remove(workerId);
150
+ const worker = pool.get(workerId);
314
151
  ```
315
152
 
316
- #### Request Tracking
153
+ #### Selection Logic
317
154
 
318
- ```typescript
319
- // Track a request assigned to a worker
320
- server.trackRequest("worker-1", "req-123", "inference");
155
+ - `getAvailableWorker(model, category?)`: Returns least-loaded worker supporting the model, respecting per-category concurrency limits
156
+ - `getAnyAvailableWorker()`: Returns any worker regardless of model (both Available and Busy)
157
+ - `getAvailableSlotCount(model, category?)`: Total free slots across all available workers for the model
321
158
 
322
- // Release a completed request
323
- server.releaseRequest("req-123", { incrementCompleted: true });
324
- ```
325
-
326
- #### Extensibility
159
+ #### Request Management
327
160
 
328
161
  | Method | Description |
329
162
  |--------|-------------|
330
- | `addHttpHandler(handler)` | Add an HTTP handler (called in order until one returns `true`) |
331
- | `addWebSocketEndpoint(path, handler)` | Add a WebSocket endpoint at a custom path |
332
-
333
- #### Event Handlers
163
+ | `trackRequest(workerId, requestId, category?)` | Marks request as in-flight and updates status |
164
+ | `releaseRequest(requestId, options?)` | Decrements active count, optionally increments completed count |
334
165
 
335
- | Method | Return | Description |
336
- |--------|--------|-------------|
337
- | `onWorkerConnected(handler)` | `() => void` | Called when worker registers |
338
- | `onWorkerDisconnected(handler)` | `() => void` | Called when worker disconnects (includes pending requests) |
339
- | `onWorkerMessage(type, handler)` | `() => void` | Register handler for a message type |
340
-
341
- ### WorkerPool
342
-
343
- Internal class managing worker state and selection. Exposed via `WorkerServer`.
166
+ #### Health Monitoring
344
167
 
345
168
  | Method | Description |
346
169
  |--------|-------------|
347
- | `getAvailableWorker(model, category?)` | Get least-loaded available worker supporting model |
348
- | `getAnyAvailableWorker()` | Get any available/busy worker |
349
- | `trackRequest(workerId, requestId, category?)` | Mark request as in-progress |
350
- | `releaseRequest(requestId, { incrementCompleted? })` | Release tracked request |
351
- | `getWorkerInfoList()` | Get public info for all workers |
352
- | `checkHealth(timeoutMs)` | Return IDs of dead workers (heartbeat > 3x timeout) |
353
- | `send(workerId, message)` | Send message to specific worker |
354
- | `broadcast(message)` | Broadcast to all workers |
355
- | `closeAll()` | Close all worker connections |
170
+ | `checkHealth(timeoutMs)` | Returns IDs of workers exceeding `3x` timeout; marks unhealthy ones |
356
171
 
357
172
  ### ConnectionHandler
358
173
 
359
- Handles WebSocket connection lifecycle and protocol message routing.
360
-
361
- #### Message Routing
362
-
363
- ```typescript
364
- import { ConnectionHandler } from "@hardlydifficult/worker-server";
365
-
366
- const handler = new ConnectionHandler(pool, config, logger);
174
+ Handles WebSocket lifecycle, registration, heartbeats, and message routing. Most consumers use `WorkerServer`, which encapsulates this.
367
175
 
368
- // Register handlers for custom message types
369
- const unregister = handler.onMessage("custom_type", (worker, message) => {
370
- console.log(`Received from ${worker.id}:`, message);
371
- });
372
- ```
373
-
374
- #### Event Handlers
375
-
376
- ```typescript
377
- handler.onWorkerConnected((worker) => {
378
- console.log("Worker connected:", worker.id);
379
- });
176
+ ### Types & Interfaces
380
177
 
381
- handler.onWorkerDisconnected((worker, pending) => {
382
- console.log("Worker disconnected with pending:", pending.size);
383
- });
384
- ```
385
-
386
- ## Advanced Features
387
-
388
- ### HTTP Endpoints
389
-
390
- Custom HTTP handlers can be added:
391
-
392
- ```typescript
393
- server.addHttpHandler(async (req, res) => {
394
- if (req.url === "/health") {
395
- res.writeHead(200, { "Content-Type": "application/json" });
396
- res.end(JSON.stringify({ status: "ok" }));
397
- return true;
398
- }
399
- return false; // continue to next handler
400
- });
401
- ```
402
-
403
- ### Custom WebSocket Endpoints
404
-
405
- Additional WebSocket paths can be handled:
406
-
407
- ```typescript
408
- server.addWebSocketEndpoint("/ws/admin", (ws) => {
409
- ws.on("message", (data) => {
410
- // Handle admin WebSocket messages
411
- });
412
- });
413
- ```
414
-
415
- ### Authentication
416
-
417
- Optionally require authentication tokens from workers:
418
-
419
- ```typescript
420
- const server = new WorkerServer({
421
- port: 8080,
422
- authToken: "your-secret-token"
423
- });
424
-
425
- // Workers must send registration with matching authToken
426
- ```
427
-
428
- ### Load Balancing with Category Limits
429
-
430
- Workers can declare per-category concurrency limits:
431
-
432
- ```typescript
433
- const capabilities = {
434
- models: [{ modelId: "sonnet", ... }],
435
- maxConcurrentRequests: 10,
436
- concurrencyLimits: {
437
- inference: 5, // max 5 concurrent inference requests
438
- embeddings: 2 // max 2 concurrent embedding requests
439
- }
440
- };
441
- ```
442
-
443
- Requests are then tracked by category:
444
-
445
- ```typescript
446
- server.trackRequest("worker-1", "req-1", "inference");
447
- server.releaseRequest("req-1"); // category looked up automatically
448
- ```
449
-
450
- ## Type Definitions
451
-
452
- ### WorkerStatus
178
+ #### `WorkerStatus`
453
179
 
454
180
  | Value | Description |
455
181
  |-------|-------------|
456
182
  | `available` | Worker can accept new requests |
457
- | `busy` | Worker is at max concurrent requests |
458
- | `draining` | Worker is shutting down |
459
- | `unhealthy` | Worker heartbeat has timed out |
183
+ | `busy` | Worker at capacity, but can accept model-agnostic tasks |
184
+ | `draining` | Worker finishing current work before shutdown |
185
+ | `unhealthy` | Worker failed heartbeat checks |
186
+
187
+ #### `WorkerInfo`
460
188
 
461
- ### ModelInfo
189
+ Public worker metadata (excludes raw WebSocket):
462
190
 
463
191
  ```typescript
464
- interface ModelInfo {
465
- modelId: string;
466
- displayName: string;
467
- maxContextTokens: number;
468
- maxOutputTokens: number;
469
- supportsStreaming: boolean;
470
- supportsVision?: boolean;
471
- supportsTools?: boolean;
192
+ interface WorkerInfo {
193
+ readonly id: string;
194
+ readonly name: string;
195
+ readonly status: WorkerStatus;
196
+ readonly capabilities: WorkerCapabilities;
197
+ readonly sessionId: string;
198
+ readonly connectedAt: Date;
199
+ readonly lastHeartbeat: Date;
200
+ readonly activeRequests: number;
201
+ readonly completedRequests: number;
202
+ readonly pendingRequestIds: ReadonlySet<string>;
203
+ readonly categoryActiveRequests: ReadonlyMap<string, number>;
472
204
  }
473
205
  ```
474
206
 
475
- ### WorkerCapabilities
207
+ #### `WorkerCapabilities`
476
208
 
477
209
  ```typescript
478
210
  interface WorkerCapabilities {
@@ -483,95 +215,63 @@ interface WorkerCapabilities {
483
215
  }
484
216
  ```
485
217
 
486
- ### WorkerInfo
218
+ #### `ModelInfo`
487
219
 
488
220
  ```typescript
489
- interface WorkerInfo {
490
- id: string;
491
- name: string;
492
- status: WorkerStatus;
493
- capabilities: WorkerCapabilities;
494
- sessionId: string;
495
- connectedAt: Date;
496
- lastHeartbeat: Date;
497
- activeRequests: number;
498
- completedRequests: number;
499
- pendingRequestIds: ReadonlySet<string>;
500
- categoryActiveRequests: ReadonlyMap<string, number>;
221
+ interface ModelInfo {
222
+ modelId: string;
223
+ displayName: string;
224
+ maxContextTokens: number;
225
+ maxOutputTokens: number;
226
+ supportsStreaming: boolean;
227
+ supportsVision?: boolean;
228
+ supportsTools?: boolean;
501
229
  }
502
230
  ```
503
231
 
504
- ## Logging
232
+ ### Secure Authentication
505
233
 
506
- The server accepts a logger implementing `WorkerServerLogger`:
507
-
508
- ```typescript
509
- interface WorkerServerLogger {
510
- debug(message: string, context?: Record<string, unknown>): void;
511
- info(message: string, context?: Record<string, unknown>): void;
512
- warn(message: string, context?: Record<string, unknown>): void;
513
- error(message: string, context?: Record<string, unknown>): void;
514
- }
515
- ```
516
-
517
- Default is a no-op logger. To use a custom logger:
234
+ Authentication tokens are compared using timing-safe comparison to prevent brute-force attacks:
518
235
 
519
236
  ```typescript
520
237
  const server = new WorkerServer({
521
- port: 3000,
522
- logger: {
523
- debug: console.debug,
524
- info: console.info,
525
- warn: console.warn,
526
- error: console.error,
527
- },
238
+ port: 19100,
239
+ authToken: "secret-token",
528
240
  });
529
241
  ```
530
242
 
531
- ## Appendix
532
-
533
- ### Protocol Messages
534
-
535
- **Worker Registration (worker → server)**
243
+ Workers must send:
536
244
 
537
245
  ```json
538
246
  {
539
247
  "type": "worker_registration",
540
- "workerId": "string",
541
- "workerName": "string",
542
- "capabilities": WorkerCapabilities,
543
- "authToken?": "string"
248
+ "workerId": "worker-1",
249
+ "workerName": "My Worker",
250
+ "capabilities": { ... },
251
+ "authToken": "secret-token"
544
252
  }
545
253
  ```
546
254
 
547
- **Registration Acknowledgment (server → worker)**
255
+ ### Heartbeat Protocol
548
256
 
549
- ```json
550
- {
551
- "type": "worker_registration_ack",
552
- "success": "boolean",
553
- "error?": "string",
554
- "sessionId?": "string",
555
- "heartbeatIntervalMs?": "number"
556
- }
557
- ```
558
-
559
- **Heartbeat (worker → server)**
257
+ Workers must send periodic heartbeat messages:
560
258
 
561
259
  ```json
562
260
  {
563
261
  "type": "heartbeat",
564
- "workerId": "string",
565
- "timestamp": "string"
262
+ "workerId": "worker-1",
263
+ "timestamp": "2024-01-01T00:00:00.000Z"
566
264
  }
567
265
  ```
568
266
 
569
- **Heartbeat Acknowledgment (server worker)**
267
+ The server responds with:
570
268
 
571
269
  ```json
572
270
  {
573
271
  "type": "heartbeat_ack",
574
- "timestamp": "string",
575
- "nextHeartbeatDeadline": "string"
272
+ "timestamp": "2024-01-01T00:00:00.000Z",
273
+ "nextHeartbeatDeadline": "2024-01-01T00:01:15.000Z"
576
274
  }
577
- ```
275
+ ```
276
+
277
+ A worker is considered unhealthy if its heartbeat exceeds `heartbeatTimeoutMs`. It is marked dead and disconnected after `3x` the timeout.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/worker-server",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [