@hardlydifficult/worker-server 1.0.10 → 1.0.12

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 +159 -431
  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
 
@@ -13,565 +13,293 @@ npm install @hardlydifficult/worker-server
13
13
  ```typescript
14
14
  import { WorkerServer } from "@hardlydifficult/worker-server";
15
15
 
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
- });
16
+ const server = new WorkerServer({
17
+ port: 19100,
18
+ authToken: "secret-token", // optional
35
19
  });
36
- ```
37
-
38
- ## Core Concepts
39
-
40
- ### Worker Registration & Lifecycle
41
-
42
- Workers connect via WebSocket and register with authentication (optional). The server tracks their status, heartbeat, and request load.
43
-
44
- ```typescript
45
- import { WorkerServer } from "@hardlydifficult/worker-server";
46
20
 
47
- const server = new WorkerServer({
48
- port: 3000,
49
- authToken: "secret-token" // Optional
21
+ server.onWorkerConnected((worker) => {
22
+ console.log(`Worker ${worker.name} connected`);
50
23
  });
51
24
 
52
- server.start();
53
- server.onWorkerConnected((worker) => {
54
- console.log("Worker connected:", worker.id, worker.name);
25
+ server.onWorkerMessage("work_complete", (worker, message) => {
26
+ console.log(`Worker ${worker.id} completed:`, message);
55
27
  });
56
- ```
57
28
 
58
- Workers send a `worker_registration` message:
29
+ await server.start();
30
+ console.log("Server listening on port", server.port);
59
31
 
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"
32
+ // Send a message to a worker
33
+ const worker = server.getAvailableWorker("sonnet");
34
+ if (worker) {
35
+ server.send(worker.id, { type: "work_request", requestId: "req-123" });
78
36
  }
79
37
  ```
80
38
 
81
- The server responds with a registration acknowledgment:
82
-
83
- ```json
84
- {
85
- "type": "worker_registration_ack",
86
- "success": true,
87
- "sessionId": "uuid-here",
88
- "heartbeatIntervalMs": 15000
89
- }
90
- ```
39
+ ## Core Concepts
91
40
 
92
- ### Message Routing
41
+ ### WorkerServer
93
42
 
94
- Messages are routed by the `type` field. Handlers receive the worker info and message payload.
43
+ Main entry point for managing worker connections via WebSocket.
95
44
 
96
45
  ```typescript
97
- server.onWorkerMessage("status_update", (worker, message) => {
98
- console.log(`Worker ${worker.id} status: ${message.statusText}`);
99
- });
46
+ import { WorkerServer, WorkerStatus } from "@hardlydifficult/worker-server";
100
47
 
101
- // Send messages to workers
102
- server.send(workerId, {
103
- type: "execute",
104
- requestId: "req-1",
105
- prompt: "Hello, world!"
48
+ const server = new WorkerServer({
49
+ port: 19100,
50
+ heartbeatTimeoutMs: 60_000,
51
+ healthCheckIntervalMs: 10_000,
52
+ heartbeatIntervalMs: 15_000,
106
53
  });
107
54
 
108
- // Broadcast to all workers
109
- server.broadcast({ type: "shutdown" });
55
+ await server.start();
56
+ await server.stop();
110
57
  ```
111
58
 
112
- ### Worker Selection & Load Balancing
59
+ #### Lifecycle Events
113
60
 
114
- Select workers by model support or use any available worker. Workers are automatically assigned least-loaded.
61
+ | Method | Description |
62
+ |--------|-------------|
63
+ | `onWorkerConnected(handler)` | Called when a worker registers successfully |
64
+ | `onWorkerDisconnected(handler)` | Called when a worker disconnects; includes pending request IDs |
115
65
 
116
66
  ```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
- }
67
+ server.onWorkerConnected((worker) => {
68
+ console.log(`Connected: ${worker.id} (${worker.name})`);
69
+ });
122
70
 
123
- // Get any available worker (model-agnostic)
124
- const anyWorker = server.getAnyAvailableWorker();
71
+ server.onWorkerDisconnected((worker, pendingRequestIds) => {
72
+ console.log(`Disconnected: ${worker.id} with ${pendingRequestIds.size} pending requests`);
73
+ });
125
74
  ```
126
75
 
127
- Request tracking ensures accurate load reporting:
76
+ #### Message Routing
77
+
78
+ Register handlers for message types sent by workers:
128
79
 
129
80
  ```typescript
130
- // Track when a request is assigned
131
- server.trackRequest(worker.id, requestId);
81
+ server.onWorkerMessage("work_complete", (worker, message) => {
82
+ const { requestId, result } = message;
83
+ console.log(`Worker ${worker.id} completed ${requestId}`);
84
+ });
132
85
 
133
- // Release when the response is received (optionally increment completed count)
134
- server.releaseRequest(requestId, { incrementCompleted: true });
86
+ // Send messages to workers
87
+ const success = server.send(workerId, { type: "work_request", requestId: "req-1" });
88
+ server.broadcast({ type: "shutdown" });
135
89
  ```
136
90
 
137
- ### Health Monitoring
91
+ #### Worker Selection & Pool Queries
138
92
 
139
- Workers must send periodic heartbeats. Unresponsive workers are marked unhealthy and eventually removed.
93
+ | Method | Description |
94
+ |--------|-------------|
95
+ | `getAvailableWorker(model, category?)` | Least-loaded worker supporting the model |
96
+ | `getAnyAvailableWorker()` | Any available or busy worker (model-agnostic) |
97
+ | `getAvailableSlotCount(model, category?)` | Total free slots across all available workers |
98
+ | `getWorkerCount()` | Total connected workers |
99
+ | `getAvailableWorkerCount()` | Available workers count |
100
+ | `getWorkerInfo()` | Public info for all workers |
140
101
 
141
102
  ```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"
103
+ // Get least-loaded worker supporting a model
104
+ const worker = server.getAvailableWorker("sonnet");
105
+ if (worker) {
106
+ server.trackRequest(worker.id, "req-123", "local");
154
107
  }
155
- ```
156
108
 
157
- Health checks run automatically at the configured interval (default: 10s). Workers missing heartbeats for >3× timeout are removed.
109
+ // Slot counts with category-aware limits
110
+ console.log("Available slots:", server.getAvailableSlotCount("sonnet", "local"));
158
111
 
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
- });
112
+ // View all workers
113
+ for (const info of server.getWorkerInfo()) {
114
+ console.log(`${info.name}: ${info.status} (${info.activeRequests}/${info.capabilities.maxConcurrentRequests})`);
115
+ }
165
116
  ```
166
117
 
167
- ### Category-Aware Concurrency
118
+ #### Request Tracking
168
119
 
169
- Workers can specify per-category concurrency limits for fine-grained control.
120
+ Track and release requests for accurate availability:
170
121
 
171
122
  ```typescript
172
- const server = new WorkerServer({ port: 3000 });
173
-
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");
123
+ // When assigning a request to a worker
124
+ server.trackRequest(workerId, requestId, "local");
189
125
 
190
- // Release without specifying category (looked up automatically)
191
- server.releaseRequest(requestId);
126
+ // When the request completes
127
+ server.releaseRequest(requestId, { incrementCompleted: true });
192
128
  ```
193
129
 
194
- ## HTTP & WebSocket Extensibility
195
-
196
- ### Custom HTTP Endpoints
130
+ #### Extensibility
197
131
 
198
- Add HTTP handlers that return `true` when they handle the request.
132
+ Add HTTP endpoints and custom WebSocket paths:
199
133
 
200
134
  ```typescript
135
+ // HTTP handler
201
136
  server.addHttpHandler(async (req, res) => {
202
137
  if (req.url === "/health") {
203
138
  res.writeHead(200, { "Content-Type": "application/json" });
204
- res.end(JSON.stringify({ status: "ok" }));
139
+ res.end(JSON.stringify({ ok: true }));
205
140
  return true;
206
141
  }
207
142
  return false;
208
143
  });
209
- ```
210
144
 
211
- ### Additional WebSocket Endpoints
212
-
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
- });
145
+ // Custom WebSocket endpoint
146
+ server.addWebSocketEndpoint("/ws/dashboard", (ws) => {
147
+ ws.send(JSON.stringify({ type: "hello" }));
220
148
  });
221
149
  ```
222
150
 
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
- ```
151
+ ### WorkerPool
302
152
 
303
- #### Pool Queries
153
+ Low-level pool manager for worker state and selection.
304
154
 
305
155
  ```typescript
306
- // Get least-loaded worker supporting a specific model
307
- const worker = server.getAvailableWorker("sonnet-3.5");
156
+ import { WorkerPool, toWorkerInfo, WorkerStatus } from "@hardlydifficult/worker-server";
308
157
 
309
- // Get any available worker (model-agnostic)
310
- const anyWorker = server.getAnyAvailableWorker();
158
+ const pool = new WorkerPool(logger);
311
159
 
312
- // Get all worker info
313
- const workers = server.getWorkerInfo(); // Returns WorkerInfo[]
160
+ // Add/remove workers
161
+ pool.add(worker);
162
+ pool.remove(workerId);
163
+ const worker = pool.get(workerId);
314
164
  ```
315
165
 
316
- #### Request Tracking
317
-
318
- ```typescript
319
- // Track a request assigned to a worker
320
- server.trackRequest("worker-1", "req-123", "inference");
321
-
322
- // Release a completed request
323
- server.releaseRequest("req-123", { incrementCompleted: true });
324
- ```
325
-
326
- #### Extensibility
166
+ #### Selection Logic
327
167
 
328
168
  | Method | Description |
329
169
  |--------|-------------|
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
170
+ | `getAvailableWorker(model, category?)` | Least-loaded worker supporting the model, respecting per-category concurrency limits |
171
+ | `getAnyAvailableWorker()` | Any available or busy worker (model-agnostic) |
172
+ | `getAvailableSlotCount(model, category?)` | Total free slots across all available workers for the model |
173
+ | `getCount()` | Total connected workers |
174
+ | `getAvailableCount()` | Available workers count |
175
+ | `getWorkerInfoList()` | Public info for all workers |
334
176
 
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 |
177
+ #### Request Management
340
178
 
341
- ### WorkerPool
179
+ | Method | Description |
180
+ |--------|-------------|
181
+ | `trackRequest(workerId, requestId, category?)` | Marks request as in-flight and updates status |
182
+ | `releaseRequest(requestId, options?)` | Decrements active count, optionally increments completed count |
342
183
 
343
- Internal class managing worker state and selection. Exposed via `WorkerServer`.
184
+ #### Health Monitoring
344
185
 
345
186
  | Method | Description |
346
187
  |--------|-------------|
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 |
188
+ | `checkHealth(timeoutMs)` | Returns IDs of workers exceeding `3x` timeout; marks unhealthy ones |
356
189
 
357
190
  ### ConnectionHandler
358
191
 
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);
367
-
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
- });
380
-
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
192
+ Handles WebSocket lifecycle, registration, heartbeats, and message routing. Most consumers use `WorkerServer`, which encapsulates this.
416
193
 
417
- Optionally require authentication tokens from workers:
194
+ ### Message Protocol
418
195
 
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:
196
+ Workers send JSON messages with a `type` field:
431
197
 
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
- ```
198
+ - `worker_registration` — Register with capabilities and optional `authToken`
199
+ - `heartbeat` Send periodically to confirm liveness
442
200
 
443
- Requests are then tracked by category:
201
+ The server responds with:
202
+ - `worker_registration_ack` — Success/failure with `sessionId` and `heartbeatIntervalMs`
203
+ - `heartbeat_ack` — Acknowledgment with `nextHeartbeatDeadline`
444
204
 
445
- ```typescript
446
- server.trackRequest("worker-1", "req-1", "inference");
447
- server.releaseRequest("req-1"); // category looked up automatically
448
- ```
449
-
450
- ## Type Definitions
205
+ ### Types & Interfaces
451
206
 
452
- ### WorkerStatus
207
+ #### `WorkerStatus`
453
208
 
454
209
  | Value | Description |
455
210
  |-------|-------------|
456
211
  | `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 |
212
+ | `busy` | Worker at capacity, but can accept model-agnostic tasks |
213
+ | `draining` | Worker finishing current work before shutdown |
214
+ | `unhealthy` | Worker failed heartbeat checks |
215
+
216
+ #### `WorkerInfo`
460
217
 
461
- ### ModelInfo
218
+ Public worker metadata (excludes raw WebSocket):
462
219
 
463
220
  ```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;
221
+ interface WorkerInfo {
222
+ readonly id: string;
223
+ readonly name: string;
224
+ readonly status: WorkerStatus;
225
+ readonly capabilities: WorkerCapabilities;
226
+ readonly sessionId: string;
227
+ readonly connectedAt: Date;
228
+ readonly lastHeartbeat: Date;
229
+ readonly activeRequests: number;
230
+ readonly completedRequests: number;
231
+ readonly pendingRequestIds: ReadonlySet<string>;
232
+ readonly categoryActiveRequests: ReadonlyMap<string, number>;
472
233
  }
473
234
  ```
474
235
 
475
- ### WorkerCapabilities
236
+ #### `WorkerCapabilities`
476
237
 
477
238
  ```typescript
478
239
  interface WorkerCapabilities {
479
240
  models: ModelInfo[];
480
241
  maxConcurrentRequests: number;
481
242
  metadata?: Record<string, unknown>;
482
- concurrencyLimits?: Record<string, number>;
243
+ concurrencyLimits?: Record<string, number>; // per-category limits
483
244
  }
484
245
  ```
485
246
 
486
- ### WorkerInfo
247
+ #### `ModelInfo`
487
248
 
488
249
  ```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>;
250
+ interface ModelInfo {
251
+ modelId: string;
252
+ displayName: string;
253
+ maxContextTokens: number;
254
+ maxOutputTokens: number;
255
+ supportsStreaming: boolean;
256
+ supportsVision?: boolean;
257
+ supportsTools?: boolean;
501
258
  }
502
259
  ```
503
260
 
504
- ## Logging
261
+ ### Secure Authentication
505
262
 
506
- The server accepts a logger implementing `WorkerServerLogger`:
263
+ Authentication tokens are compared using timing-safe comparison to prevent brute-force attacks:
507
264
 
508
265
  ```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
- }
266
+ import { safeCompare } from "@hardlydifficult/worker-server";
267
+ // Internally used by ConnectionHandler; exposed for testing
268
+ const valid = safeCompare("a", "b"); // false
515
269
  ```
516
270
 
517
- Default is a no-op logger. To use a custom logger:
518
-
519
- ```typescript
520
- 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
- },
528
- });
529
- ```
530
-
531
- ## Appendix
532
-
533
- ### Protocol Messages
534
-
535
- **Worker Registration (worker → server)**
271
+ Workers must send the token in registration:
536
272
 
537
273
  ```json
538
274
  {
539
275
  "type": "worker_registration",
540
- "workerId": "string",
541
- "workerName": "string",
542
- "capabilities": WorkerCapabilities,
543
- "authToken?": "string"
276
+ "workerId": "worker-1",
277
+ "workerName": "My Worker",
278
+ "capabilities": { ... },
279
+ "authToken": "secret-token"
544
280
  }
545
281
  ```
546
282
 
547
- **Registration Acknowledgment (server → worker)**
283
+ ### Heartbeat Protocol
548
284
 
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)**
285
+ Workers must send periodic heartbeat messages:
560
286
 
561
287
  ```json
562
288
  {
563
289
  "type": "heartbeat",
564
- "workerId": "string",
565
- "timestamp": "string"
290
+ "workerId": "worker-1",
291
+ "timestamp": "2024-01-01T00:00:00.000Z"
566
292
  }
567
293
  ```
568
294
 
569
- **Heartbeat Acknowledgment (server worker)**
295
+ The server responds with:
570
296
 
571
297
  ```json
572
298
  {
573
299
  "type": "heartbeat_ack",
574
- "timestamp": "string",
575
- "nextHeartbeatDeadline": "string"
300
+ "timestamp": "2024-01-01T00:00:00.000Z",
301
+ "nextHeartbeatDeadline": "2024-01-01T00:01:15.000Z"
576
302
  }
577
- ```
303
+ ```
304
+
305
+ 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.12",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [