@hardlydifficult/worker-server 1.0.3 → 1.0.4

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 +246 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # @hardlydifficult/worker-server
2
+
3
+ A WebSocket-based worker server that manages remote worker connections, health monitoring, request routing, and load balancing through a clean TypeScript interface.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hardlydifficult/worker-server
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { WorkerServer } from "@hardlydifficult/worker-server";
15
+
16
+ const server = new WorkerServer({
17
+ port: 8080,
18
+ heartbeatTimeoutMs: 60000, // 60 seconds
19
+ healthCheckIntervalMs: 10000, // 10 seconds
20
+ });
21
+
22
+ server.onWorkerConnected((worker) => {
23
+ console.log(`Worker connected: ${worker.name} (${worker.id})`);
24
+ });
25
+
26
+ server.onWorkerDisconnected((worker, pendingRequests) => {
27
+ console.log(`Worker disconnected: ${worker.name} (${worker.id})`);
28
+ if (pendingRequests.size > 0) {
29
+ console.log(`Rescheduling ${pendingRequests.size} pending requests`);
30
+ }
31
+ });
32
+
33
+ server.onWorkerMessage("work_complete", (worker, message) => {
34
+ console.log(`Work complete: ${message.requestId}`);
35
+ });
36
+
37
+ server.addHttpHandler(async (req, res) => {
38
+ if (req.url === "/health") {
39
+ res.writeHead(200, { "Content-Type": "application/json" });
40
+ res.end(JSON.stringify({ status: "ok" }));
41
+ return true;
42
+ }
43
+ return false;
44
+ });
45
+
46
+ await server.start();
47
+ console.log("Worker server running on port 8080");
48
+ ```
49
+
50
+ ## Core Concepts
51
+
52
+ ### Worker Management
53
+
54
+ Workers connect via WebSocket, register with capabilities (supported models, concurrency limits), and send periodic heartbeats. The server tracks their status (available, busy, draining, unhealthy) and makes routing decisions based on load and model compatibility.
55
+
56
+ ### Request Tracking
57
+
58
+ Requests are tracked per-worker and can be released when complete. Optional categories enable per-category concurrency limits when workers declare `concurrencyLimits` in their capabilities.
59
+
60
+ ### Load Balancing
61
+
62
+ Workers are selected based on:
63
+ - Model support (exact or substring match)
64
+ - Current load (least-loaded algorithm)
65
+ - Category-specific limits (when `category` is provided)
66
+
67
+ ## API Reference
68
+
69
+ ### WorkerServer
70
+
71
+ Main entry point for managing worker connections.
72
+
73
+ #### Constructor
74
+
75
+ ```typescript
76
+ constructor(options: WorkerServerOptions)
77
+ ```
78
+
79
+ | Option | Type | Default | Description |
80
+ |--------|------|---------|-------------|
81
+ | `port` | `number` | — | HTTP + WebSocket server port |
82
+ | `authToken` | `string` | `undefined` | Optional token required for worker registration |
83
+ | `heartbeatTimeoutMs` | `number` | `60000` | Milliseconds before a missed heartbeat marks a worker unhealthy |
84
+ | `healthCheckIntervalMs` | `number` | `10000` | Interval (ms) for health checks |
85
+ | `heartbeatIntervalMs` | `number` | `15000` | Heartbeat interval communicated to workers |
86
+ | `logger` | `WorkerServerLogger` | `undefined` | Logger instance (defaults to no-op) |
87
+
88
+ #### Lifecycle Events
89
+
90
+ ```typescript
91
+ // Called when a worker successfully registers
92
+ onWorkerConnected(handler: WorkerConnectedHandler): () => void;
93
+
94
+ // Called when a worker disconnects with pending request IDs
95
+ onWorkerDisconnected(handler: WorkerDisconnectedHandler): () => void;
96
+
97
+ // Register a handler for a specific message type (dispatched by 'type' field)
98
+ onWorkerMessage<T = Record<string, unknown>>(
99
+ type: string,
100
+ handler: WorkerMessageHandler<T>
101
+ ): () => void;
102
+ ```
103
+
104
+ #### Message Operations
105
+
106
+ ```typescript
107
+ // Send a JSON message to a specific worker (false if failed)
108
+ send(workerId: string, message: Record<string, unknown>): boolean;
109
+
110
+ // Broadcast to all connected workers
111
+ broadcast(message: Record<string, unknown>): void;
112
+ ```
113
+
114
+ #### Pool Queries
115
+
116
+ ```typescript
117
+ // Get least-loaded worker supporting the given model
118
+ getAvailableWorker(model: string, category?: string): WorkerInfo | null;
119
+
120
+ // Get any available worker (model-agnostic)
121
+ getAnyAvailableWorker(): WorkerInfo | null;
122
+
123
+ // Total connected worker count
124
+ getWorkerCount(): number;
125
+
126
+ // Available worker count
127
+ getAvailableWorkerCount(): number;
128
+
129
+ // Get info about all connected workers
130
+ getWorkerInfo(): WorkerInfo[];
131
+ ```
132
+
133
+ #### Request Tracking
134
+
135
+ ```typescript
136
+ // Track a request assigned to a worker (optional category)
137
+ trackRequest(workerId: string, requestId: string, category?: string): void;
138
+
139
+ // Release a tracked request (optionally increment completed count)
140
+ releaseRequest(
141
+ requestId: string,
142
+ options?: { incrementCompleted?: boolean }
143
+ ): void;
144
+ ```
145
+
146
+ #### HTTP & WebSocket Extensibility
147
+
148
+ ```typescript
149
+ // Add an HTTP handler (called in order until one returns true)
150
+ addHttpHandler(handler: HttpRequestHandler): void;
151
+
152
+ // Add an additional WebSocket endpoint at a path
153
+ addWebSocketEndpoint(
154
+ path: string,
155
+ handler: WebSocketConnectionHandler
156
+ ): void;
157
+ ```
158
+
159
+ #### Server Lifecycle
160
+
161
+ ```typescript
162
+ // Start the HTTP + WebSocket server
163
+ start(): Promise<void>;
164
+
165
+ // Stop the server and close all connections
166
+ stop(): Promise<void>;
167
+ ```
168
+
169
+ ### WorkerInfo
170
+
171
+ Public interface representing a connected worker:
172
+
173
+ ```typescript
174
+ interface WorkerInfo {
175
+ readonly id: string;
176
+ readonly name: string;
177
+ readonly status: WorkerStatus; // "available" | "busy" | "draining" | "unhealthy"
178
+ readonly capabilities: WorkerCapabilities;
179
+ readonly sessionId: string;
180
+ readonly connectedAt: Date;
181
+ readonly lastHeartbeat: Date;
182
+ readonly activeRequests: number;
183
+ readonly completedRequests: number;
184
+ readonly pendingRequestIds: ReadonlySet<string>;
185
+ readonly categoryActiveRequests: ReadonlyMap<string, number>;
186
+ }
187
+ ```
188
+
189
+ ### WorkerCapabilities
190
+
191
+ Describes a worker's capabilities:
192
+
193
+ ```typescript
194
+ interface WorkerCapabilities {
195
+ models: ModelInfo[];
196
+ maxConcurrentRequests: number;
197
+ metadata?: Record<string, unknown>;
198
+ concurrencyLimits?: Record<string, number>;
199
+ }
200
+ ```
201
+
202
+ ### ModelInfo
203
+
204
+ Describes a supported model:
205
+
206
+ ```typescript
207
+ interface ModelInfo {
208
+ modelId: string;
209
+ displayName: string;
210
+ maxContextTokens: number;
211
+ maxOutputTokens: number;
212
+ supportsStreaming: boolean;
213
+ supportsVision?: boolean;
214
+ supportsTools?: boolean;
215
+ }
216
+ ```
217
+
218
+ ### WorkerStatus
219
+
220
+ Worker state enumeration:
221
+
222
+ | Status | Description |
223
+ |--------|-------------|
224
+ | `available` | Worker is idle and can accept new requests |
225
+ | `busy` | Worker is at max concurrent requests but may accept model-agnostic tasks |
226
+ | `draining` | Worker is being gracefully decommissioned |
227
+ | `unhealthy` | Worker has missed heartbeats and is presumed degraded |
228
+
229
+ ## Worker Protocol
230
+
231
+ Workers communicate using JSON messages with a `type` field:
232
+
233
+ | Message | Direction | Description |
234
+ |---------|-----------|-------------|
235
+ | `worker_registration` | Worker → Server | Register with capabilities |
236
+ | `worker_registration_ack` | Server → Worker | Acknowledgment with session ID |
237
+ | `heartbeat` | Worker → Server | Periodic health check |
238
+ | `heartbeat_ack` | Server → Worker | Acknowledgment with next deadline |
239
+
240
+ All other message types are routed to registered handlers via `onWorkerMessage()`.
241
+
242
+ ## Health Monitoring
243
+
244
+ - Workers missing heartbeats for `heartbeatTimeoutMs` are marked `unhealthy`
245
+ - Workers missing heartbeats for `3 × heartbeatTimeoutMs` are disconnected
246
+ - Health checks run every `healthCheckIntervalMs`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/worker-server",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [