@hardlydifficult/worker-server 1.0.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.
@@ -0,0 +1,38 @@
1
+ import type WebSocket from "ws";
2
+ import { type WorkerConnectedHandler, type WorkerDisconnectedHandler, type WorkerMessageHandler, type WorkerServerLogger } from "./types.js";
3
+ import { type WorkerPool } from "./WorkerPool.js";
4
+ export interface ConnectionHandlerConfig {
5
+ authToken?: string;
6
+ heartbeatTimeoutMs: number;
7
+ heartbeatIntervalMs: number;
8
+ }
9
+ /**
10
+ * Handles WebSocket connection lifecycle and message routing.
11
+ *
12
+ * Protocol messages (registration, heartbeat) are handled internally.
13
+ * All other messages are dispatched by `type` to registered handlers.
14
+ */
15
+ export declare class ConnectionHandler {
16
+ private readonly pool;
17
+ private readonly config;
18
+ private readonly messageHandlers;
19
+ private readonly connectedHandlers;
20
+ private readonly disconnectedHandlers;
21
+ private readonly logger;
22
+ constructor(pool: WorkerPool, config: ConnectionHandlerConfig, logger?: WorkerServerLogger);
23
+ /**
24
+ * Register a handler for a specific message type.
25
+ * Returns an unsubscribe function.
26
+ */
27
+ onMessage<T = Record<string, unknown>>(type: string, handler: WorkerMessageHandler<T>): () => void;
28
+ onWorkerConnected(handler: WorkerConnectedHandler): () => void;
29
+ onWorkerDisconnected(handler: WorkerDisconnectedHandler): () => void;
30
+ /** Set up event handlers for a new WebSocket connection. */
31
+ handleConnection(ws: WebSocket): void;
32
+ private handleMessage;
33
+ private handleRegistration;
34
+ private handleHeartbeat;
35
+ private handleDisconnect;
36
+ private unknownWorkerInfo;
37
+ }
38
+ //# sourceMappingURL=ConnectionHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConnectionHandler.d.ts","sourceRoot":"","sources":["../src/ConnectionHandler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;AAGhC,OAAO,EAML,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAE9B,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EAExB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAahE,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;IAU1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAVzB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG5B;IACJ,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqC;IACvE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAwC;IAC7E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAGzB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,uBAAuB,EAChD,MAAM,CAAC,EAAE,kBAAkB;IAK7B;;;OAGG;IACH,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC/B,MAAM,IAAI;IAab,iBAAiB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,IAAI;IAO9D,oBAAoB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,IAAI;IAOpE,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAkBrC,OAAO,CAAC,aAAa;IAmDrB,OAAO,CAAC,kBAAkB;IA2G1B,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,gBAAgB;IAqCxB,OAAO,CAAC,iBAAiB;CAc1B"}
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectionHandler = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const safeCompare_js_1 = require("./safeCompare.js");
6
+ const types_js_1 = require("./types.js");
7
+ const WorkerPool_js_1 = require("./WorkerPool.js");
8
+ function noop() {
9
+ // intentional no-op
10
+ }
11
+ const NO_OP_LOGGER = {
12
+ debug: noop,
13
+ info: noop,
14
+ warn: noop,
15
+ error: noop,
16
+ };
17
+ /**
18
+ * Handles WebSocket connection lifecycle and message routing.
19
+ *
20
+ * Protocol messages (registration, heartbeat) are handled internally.
21
+ * All other messages are dispatched by `type` to registered handlers.
22
+ */
23
+ class ConnectionHandler {
24
+ pool;
25
+ config;
26
+ messageHandlers = new Map();
27
+ connectedHandlers = new Set();
28
+ disconnectedHandlers = new Set();
29
+ logger;
30
+ constructor(pool, config, logger) {
31
+ this.pool = pool;
32
+ this.config = config;
33
+ this.logger = logger ?? NO_OP_LOGGER;
34
+ }
35
+ /**
36
+ * Register a handler for a specific message type.
37
+ * Returns an unsubscribe function.
38
+ */
39
+ onMessage(type, handler) {
40
+ let set = this.messageHandlers.get(type);
41
+ if (set === undefined) {
42
+ set = new Set();
43
+ this.messageHandlers.set(type, set);
44
+ }
45
+ const h = handler;
46
+ set.add(h);
47
+ return () => {
48
+ set.delete(h);
49
+ };
50
+ }
51
+ onWorkerConnected(handler) {
52
+ this.connectedHandlers.add(handler);
53
+ return () => {
54
+ this.connectedHandlers.delete(handler);
55
+ };
56
+ }
57
+ onWorkerDisconnected(handler) {
58
+ this.disconnectedHandlers.add(handler);
59
+ return () => {
60
+ this.disconnectedHandlers.delete(handler);
61
+ };
62
+ }
63
+ /** Set up event handlers for a new WebSocket connection. */
64
+ handleConnection(ws) {
65
+ this.logger.debug("New WebSocket connection from potential worker");
66
+ ws.on("message", (data) => {
67
+ this.handleMessage(ws, data);
68
+ });
69
+ ws.on("close", (code, reason) => {
70
+ this.handleDisconnect(ws, code, reason.toString());
71
+ });
72
+ ws.on("error", (error) => {
73
+ this.logger.error("Worker WebSocket error", {
74
+ error: error.message,
75
+ });
76
+ });
77
+ }
78
+ handleMessage(ws, data) {
79
+ let message;
80
+ try {
81
+ message = JSON.parse(data.toString());
82
+ }
83
+ catch {
84
+ this.logger.warn("Invalid JSON message from worker");
85
+ return;
86
+ }
87
+ const { type } = message;
88
+ if (typeof type !== "string") {
89
+ this.logger.warn("Message missing type field");
90
+ return;
91
+ }
92
+ // Handle protocol messages internally
93
+ if (type === "worker_registration") {
94
+ this.handleRegistration(ws, message);
95
+ return;
96
+ }
97
+ if (type === "heartbeat") {
98
+ this.handleHeartbeat(ws, message);
99
+ return;
100
+ }
101
+ // Dispatch to registered handlers by type
102
+ const handlers = this.messageHandlers.get(type);
103
+ if (handlers !== undefined && handlers.size > 0) {
104
+ const { workerId } = ws;
105
+ const worker = workerId !== undefined ? this.pool.get(workerId) : undefined;
106
+ const info = worker !== undefined
107
+ ? (0, WorkerPool_js_1.toWorkerInfo)(worker)
108
+ : this.unknownWorkerInfo(workerId);
109
+ for (const handler of handlers) {
110
+ try {
111
+ handler(info, message);
112
+ }
113
+ catch (err) {
114
+ this.logger.error("Error in message handler", {
115
+ type,
116
+ error: err instanceof Error ? err.message : String(err),
117
+ });
118
+ }
119
+ }
120
+ }
121
+ else {
122
+ this.logger.warn("Unhandled message type from worker", { type });
123
+ }
124
+ }
125
+ handleRegistration(ws, message) {
126
+ // Validate auth token if required
127
+ if (this.config.authToken !== undefined &&
128
+ !(0, safeCompare_js_1.safeCompare)(message.authToken ?? "", this.config.authToken)) {
129
+ this.logger.warn("Worker registration rejected: invalid auth token", {
130
+ workerId: message.workerId,
131
+ });
132
+ const ack = {
133
+ type: "worker_registration_ack",
134
+ success: false,
135
+ error: "Invalid authentication token",
136
+ };
137
+ ws.send(JSON.stringify(ack));
138
+ ws.close(4001, "Authentication failed");
139
+ return;
140
+ }
141
+ // Replace existing worker with same ID
142
+ const existing = this.pool.get(message.workerId);
143
+ if (existing !== undefined) {
144
+ this.logger.info("Replacing existing worker connection", {
145
+ workerId: message.workerId,
146
+ });
147
+ existing.websocket.workerId =
148
+ undefined;
149
+ try {
150
+ existing.websocket.close(4003, "Replaced by new connection");
151
+ }
152
+ catch {
153
+ // Old socket may already be dead
154
+ }
155
+ // Notify disconnected handlers for pending requests on old connection
156
+ if (existing.pendingRequests.size > 0) {
157
+ this.logger.warn("Replaced worker had pending requests", {
158
+ workerId: message.workerId,
159
+ count: existing.pendingRequests.size,
160
+ });
161
+ const info = (0, WorkerPool_js_1.toWorkerInfo)(existing);
162
+ for (const handler of this.disconnectedHandlers) {
163
+ try {
164
+ handler(info, existing.pendingRequests);
165
+ }
166
+ catch (err) {
167
+ this.logger.error("Error in disconnected handler", {
168
+ error: err instanceof Error ? err.message : String(err),
169
+ });
170
+ }
171
+ }
172
+ }
173
+ this.pool.remove(message.workerId);
174
+ }
175
+ const sessionId = (0, crypto_1.randomUUID)();
176
+ const now = new Date();
177
+ const worker = {
178
+ id: message.workerId,
179
+ name: message.workerName,
180
+ websocket: ws,
181
+ capabilities: message.capabilities,
182
+ status: types_js_1.WorkerStatus.Available,
183
+ sessionId,
184
+ connectedAt: now,
185
+ lastHeartbeat: now,
186
+ activeRequests: 0,
187
+ pendingRequests: new Set(),
188
+ completedRequests: 0,
189
+ };
190
+ this.pool.add(worker);
191
+ ws.workerId = message.workerId;
192
+ const ack = {
193
+ type: "worker_registration_ack",
194
+ success: true,
195
+ sessionId,
196
+ heartbeatIntervalMs: this.config.heartbeatIntervalMs,
197
+ };
198
+ ws.send(JSON.stringify(ack));
199
+ this.logger.info("Worker registered", {
200
+ workerId: message.workerId,
201
+ workerName: message.workerName,
202
+ models: message.capabilities.models.map((m) => m.modelId),
203
+ maxConcurrentRequests: message.capabilities.maxConcurrentRequests,
204
+ });
205
+ // Notify connected handlers
206
+ const info = (0, WorkerPool_js_1.toWorkerInfo)(worker);
207
+ for (const handler of this.connectedHandlers) {
208
+ try {
209
+ handler(info);
210
+ }
211
+ catch (err) {
212
+ this.logger.error("Error in connected handler", {
213
+ error: err instanceof Error ? err.message : String(err),
214
+ });
215
+ }
216
+ }
217
+ }
218
+ handleHeartbeat(ws, message) {
219
+ const worker = this.pool.get(message.workerId);
220
+ if (worker === undefined) {
221
+ this.logger.warn("Heartbeat from unknown worker", {
222
+ workerId: message.workerId,
223
+ });
224
+ return;
225
+ }
226
+ worker.lastHeartbeat = new Date();
227
+ if (worker.status === types_js_1.WorkerStatus.Unhealthy) {
228
+ worker.status =
229
+ worker.activeRequests < worker.capabilities.maxConcurrentRequests
230
+ ? types_js_1.WorkerStatus.Available
231
+ : types_js_1.WorkerStatus.Busy;
232
+ }
233
+ const nextDeadline = new Date(Date.now() + this.config.heartbeatTimeoutMs);
234
+ const ack = {
235
+ type: "heartbeat_ack",
236
+ timestamp: message.timestamp,
237
+ nextHeartbeatDeadline: nextDeadline.toISOString(),
238
+ };
239
+ ws.send(JSON.stringify(ack));
240
+ }
241
+ handleDisconnect(ws, code, reason) {
242
+ const { workerId } = ws;
243
+ if (workerId === undefined) {
244
+ this.logger.debug("Unregistered WebSocket connection closed", {
245
+ code,
246
+ reason,
247
+ });
248
+ return;
249
+ }
250
+ const worker = this.pool.get(workerId);
251
+ if (worker !== undefined) {
252
+ const info = (0, WorkerPool_js_1.toWorkerInfo)(worker);
253
+ const pendingCopy = new Set(worker.pendingRequests);
254
+ if (worker.pendingRequests.size > 0) {
255
+ this.logger.warn("Worker disconnected with pending requests", {
256
+ workerId,
257
+ count: worker.pendingRequests.size,
258
+ });
259
+ }
260
+ this.pool.remove(workerId);
261
+ this.logger.info("Worker disconnected", { workerId, code, reason });
262
+ for (const handler of this.disconnectedHandlers) {
263
+ try {
264
+ handler(info, pendingCopy);
265
+ }
266
+ catch (err) {
267
+ this.logger.error("Error in disconnected handler", {
268
+ error: err instanceof Error ? err.message : String(err),
269
+ });
270
+ }
271
+ }
272
+ }
273
+ }
274
+ unknownWorkerInfo(workerId) {
275
+ return {
276
+ id: workerId ?? "unknown",
277
+ name: "unknown",
278
+ status: types_js_1.WorkerStatus.Unhealthy,
279
+ capabilities: { models: [], maxConcurrentRequests: 0 },
280
+ sessionId: "",
281
+ connectedAt: new Date(0),
282
+ lastHeartbeat: new Date(0),
283
+ activeRequests: 0,
284
+ completedRequests: 0,
285
+ pendingRequestIds: new Set(),
286
+ };
287
+ }
288
+ }
289
+ exports.ConnectionHandler = ConnectionHandler;
290
+ //# sourceMappingURL=ConnectionHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConnectionHandler.js","sourceRoot":"","sources":["../src/ConnectionHandler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { type ConnectedWorker, type WorkerInfo, type WorkerServerLogger } from "./types.js";
2
+ /** Convert internal ConnectedWorker to public WorkerInfo (strips websocket). */
3
+ export declare function toWorkerInfo(w: ConnectedWorker): WorkerInfo;
4
+ /**
5
+ * Manages connected worker state and selection.
6
+ */
7
+ export declare class WorkerPool {
8
+ private readonly workers;
9
+ private readonly logger;
10
+ constructor(logger?: WorkerServerLogger);
11
+ add(worker: ConnectedWorker): void;
12
+ remove(id: string): ConnectedWorker | undefined;
13
+ get(id: string): ConnectedWorker | undefined;
14
+ has(id: string): boolean;
15
+ values(): IterableIterator<ConnectedWorker>;
16
+ /**
17
+ * Get the least-loaded available worker supporting the given model.
18
+ */
19
+ getAvailableWorker(model: string): ConnectedWorker | null;
20
+ /**
21
+ * Get any available worker (for tasks that don't need a specific model).
22
+ */
23
+ getAnyAvailableWorker(): ConnectedWorker | null;
24
+ /**
25
+ * Track a request as sent to a worker.
26
+ */
27
+ trackRequest(workerId: string, requestId: string): void;
28
+ /**
29
+ * Release a request from whichever worker is handling it.
30
+ */
31
+ releaseRequest(requestId: string, options?: {
32
+ incrementCompleted?: boolean;
33
+ }): void;
34
+ getCount(): number;
35
+ getAvailableCount(): number;
36
+ /** Get public info about all connected workers. */
37
+ getWorkerInfoList(): WorkerInfo[];
38
+ /**
39
+ * Check health of all workers based on heartbeat timeout.
40
+ * Returns IDs of workers that are presumed dead (heartbeat > timeout * 3).
41
+ */
42
+ checkHealth(heartbeatTimeoutMs: number): string[];
43
+ /** Send a JSON message to a specific worker. */
44
+ send(workerId: string, message: Record<string, unknown>): boolean;
45
+ /** Broadcast a JSON message to all connected workers with open sockets. */
46
+ broadcast(message: Record<string, unknown>): void;
47
+ /** Close all worker connections. */
48
+ closeAll(): void;
49
+ }
50
+ //# sourceMappingURL=WorkerPool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkerPool.d.ts","sourceRoot":"","sources":["../src/WorkerPool.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,kBAAkB,EAExB,MAAM,YAAY,CAAC;AAapB,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,CAAC,EAAE,eAAe,GAAG,UAAU,CAa3D;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,MAAM,CAAC,EAAE,kBAAkB;IAIvC,GAAG,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IAIlC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAQ/C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAI5C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB,MAAM,IAAI,gBAAgB,CAAC,eAAe,CAAC;IAI3C;;OAEG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAkCzD;;OAEG;IACH,qBAAqB,IAAI,eAAe,GAAG,IAAI;IAY/C;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAcvD;;OAEG;IACH,cAAc,CACZ,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,GACzC,IAAI;IAqBP,QAAQ,IAAI,MAAM;IAIlB,iBAAiB,IAAI,MAAM;IAU3B,mDAAmD;IACnD,iBAAiB,IAAI,UAAU,EAAE;IAIjC;;;OAGG;IACH,WAAW,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,EAAE;IA0BjD,gDAAgD;IAChD,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAiBjE,2EAA2E;IAC3E,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAajD,oCAAoC;IACpC,QAAQ,IAAI,IAAI;CAUjB"}
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WorkerPool = void 0;
7
+ exports.toWorkerInfo = toWorkerInfo;
8
+ const ws_1 = __importDefault(require("ws"));
9
+ const types_js_1 = require("./types.js");
10
+ function noop() {
11
+ // intentional no-op
12
+ }
13
+ const NO_OP_LOGGER = {
14
+ debug: noop,
15
+ info: noop,
16
+ warn: noop,
17
+ error: noop,
18
+ };
19
+ /** Convert internal ConnectedWorker to public WorkerInfo (strips websocket). */
20
+ function toWorkerInfo(w) {
21
+ return {
22
+ id: w.id,
23
+ name: w.name,
24
+ status: w.status,
25
+ capabilities: w.capabilities,
26
+ sessionId: w.sessionId,
27
+ connectedAt: w.connectedAt,
28
+ lastHeartbeat: w.lastHeartbeat,
29
+ activeRequests: w.activeRequests,
30
+ completedRequests: w.completedRequests,
31
+ pendingRequestIds: w.pendingRequests,
32
+ };
33
+ }
34
+ /**
35
+ * Manages connected worker state and selection.
36
+ */
37
+ class WorkerPool {
38
+ workers = new Map();
39
+ logger;
40
+ constructor(logger) {
41
+ this.logger = logger ?? NO_OP_LOGGER;
42
+ }
43
+ add(worker) {
44
+ this.workers.set(worker.id, worker);
45
+ }
46
+ remove(id) {
47
+ const worker = this.workers.get(id);
48
+ if (worker !== undefined) {
49
+ this.workers.delete(id);
50
+ }
51
+ return worker;
52
+ }
53
+ get(id) {
54
+ return this.workers.get(id);
55
+ }
56
+ has(id) {
57
+ return this.workers.has(id);
58
+ }
59
+ values() {
60
+ return this.workers.values();
61
+ }
62
+ /**
63
+ * Get the least-loaded available worker supporting the given model.
64
+ */
65
+ getAvailableWorker(model) {
66
+ let bestWorker = null;
67
+ let lowestLoad = Infinity;
68
+ for (const worker of this.workers.values()) {
69
+ if (worker.status !== types_js_1.WorkerStatus.Available) {
70
+ continue;
71
+ }
72
+ const supportsModel = worker.capabilities.models.some((m) => m.modelId === model ||
73
+ m.modelId.includes(model) ||
74
+ model.includes(m.modelId));
75
+ if (!supportsModel) {
76
+ continue;
77
+ }
78
+ if (worker.activeRequests >= worker.capabilities.maxConcurrentRequests) {
79
+ continue;
80
+ }
81
+ const load = worker.activeRequests / worker.capabilities.maxConcurrentRequests;
82
+ if (load < lowestLoad) {
83
+ lowestLoad = load;
84
+ bestWorker = worker;
85
+ }
86
+ }
87
+ return bestWorker;
88
+ }
89
+ /**
90
+ * Get any available worker (for tasks that don't need a specific model).
91
+ */
92
+ getAnyAvailableWorker() {
93
+ for (const w of this.workers.values()) {
94
+ if (w.status === types_js_1.WorkerStatus.Available ||
95
+ w.status === types_js_1.WorkerStatus.Busy) {
96
+ return w;
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * Track a request as sent to a worker.
103
+ */
104
+ trackRequest(workerId, requestId) {
105
+ const worker = this.workers.get(workerId);
106
+ if (worker === undefined) {
107
+ return;
108
+ }
109
+ worker.activeRequests++;
110
+ worker.pendingRequests.add(requestId);
111
+ if (worker.activeRequests >= worker.capabilities.maxConcurrentRequests) {
112
+ worker.status = types_js_1.WorkerStatus.Busy;
113
+ }
114
+ }
115
+ /**
116
+ * Release a request from whichever worker is handling it.
117
+ */
118
+ releaseRequest(requestId, options) {
119
+ for (const worker of this.workers.values()) {
120
+ if (worker.pendingRequests.has(requestId)) {
121
+ worker.pendingRequests.delete(requestId);
122
+ worker.activeRequests = Math.max(0, worker.activeRequests - 1);
123
+ if (options?.incrementCompleted === true) {
124
+ worker.completedRequests++;
125
+ }
126
+ if (worker.status === types_js_1.WorkerStatus.Busy &&
127
+ worker.activeRequests < worker.capabilities.maxConcurrentRequests) {
128
+ worker.status = types_js_1.WorkerStatus.Available;
129
+ }
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ getCount() {
135
+ return this.workers.size;
136
+ }
137
+ getAvailableCount() {
138
+ let count = 0;
139
+ for (const worker of this.workers.values()) {
140
+ if (worker.status === types_js_1.WorkerStatus.Available) {
141
+ count++;
142
+ }
143
+ }
144
+ return count;
145
+ }
146
+ /** Get public info about all connected workers. */
147
+ getWorkerInfoList() {
148
+ return Array.from(this.workers.values()).map(toWorkerInfo);
149
+ }
150
+ /**
151
+ * Check health of all workers based on heartbeat timeout.
152
+ * Returns IDs of workers that are presumed dead (heartbeat > timeout * 3).
153
+ */
154
+ checkHealth(heartbeatTimeoutMs) {
155
+ const now = Date.now();
156
+ const deadWorkerIds = [];
157
+ for (const worker of this.workers.values()) {
158
+ const timeSinceHeartbeat = now - worker.lastHeartbeat.getTime();
159
+ if (timeSinceHeartbeat > heartbeatTimeoutMs) {
160
+ if (worker.status !== types_js_1.WorkerStatus.Unhealthy) {
161
+ this.logger.warn("Worker heartbeat timeout", {
162
+ workerId: worker.id,
163
+ timeSinceHeartbeat,
164
+ threshold: heartbeatTimeoutMs,
165
+ });
166
+ worker.status = types_js_1.WorkerStatus.Unhealthy;
167
+ }
168
+ if (timeSinceHeartbeat > heartbeatTimeoutMs * 3) {
169
+ deadWorkerIds.push(worker.id);
170
+ }
171
+ }
172
+ }
173
+ return deadWorkerIds;
174
+ }
175
+ /** Send a JSON message to a specific worker. */
176
+ send(workerId, message) {
177
+ const worker = this.workers.get(workerId);
178
+ if (worker === undefined) {
179
+ return false;
180
+ }
181
+ if (worker.websocket.readyState !== ws_1.default.OPEN) {
182
+ return false;
183
+ }
184
+ try {
185
+ worker.websocket.send(JSON.stringify(message));
186
+ return true;
187
+ }
188
+ catch {
189
+ return false;
190
+ }
191
+ }
192
+ /** Broadcast a JSON message to all connected workers with open sockets. */
193
+ broadcast(message) {
194
+ const json = JSON.stringify(message);
195
+ for (const worker of this.workers.values()) {
196
+ if (worker.websocket.readyState === ws_1.default.OPEN) {
197
+ try {
198
+ worker.websocket.send(json);
199
+ }
200
+ catch {
201
+ // Worker may be disconnecting
202
+ }
203
+ }
204
+ }
205
+ }
206
+ /** Close all worker connections. */
207
+ closeAll() {
208
+ for (const worker of this.workers.values()) {
209
+ try {
210
+ worker.websocket.close(1001, "Server shutting down");
211
+ }
212
+ catch {
213
+ // Ignore close errors
214
+ }
215
+ }
216
+ this.workers.clear();
217
+ }
218
+ }
219
+ exports.WorkerPool = WorkerPool;
220
+ //# sourceMappingURL=WorkerPool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkerPool.js","sourceRoot":"","sources":["../src/WorkerPool.ts"],"names":[],"mappings":";;;;;;AAqBA,oCAaC;AAlCD,4CAA2B;AAE3B,yCAKoB;AAEpB,SAAS,IAAI;IACX,oBAAoB;AACtB,CAAC;AAED,MAAM,YAAY,GAAuB;IACvC,KAAK,EAAE,IAAI;IACX,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF,gFAAgF;AAChF,SAAgB,YAAY,CAAC,CAAkB;IAC7C,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,cAAc,EAAE,CAAC,CAAC,cAAc;QAChC,iBAAiB,EAAE,CAAC,CAAC,iBAAiB;QACtC,iBAAiB,EAAE,CAAC,CAAC,eAAe;KACrC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAa,UAAU;IACJ,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC7C,MAAM,CAAqB;IAE5C,YAAY,MAA2B;QACrC,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,YAAY,CAAC;IACvC,CAAC;IAED,GAAG,CAAC,MAAuB;QACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,CAAC,EAAU;QACf,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,kBAAkB,CAAC,KAAa;QAC9B,IAAI,UAAU,GAA2B,IAAI,CAAC;QAC9C,IAAI,UAAU,GAAG,QAAQ,CAAC;QAE1B,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,MAAM,CAAC,MAAM,KAAK,uBAAY,CAAC,SAAS,EAAE,CAAC;gBAC7C,SAAS;YACX,CAAC;YAED,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CACnD,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,OAAO,KAAK,KAAK;gBACnB,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBACzB,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAC5B,CAAC;YACF,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,SAAS;YACX,CAAC;YAED,IAAI,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,YAAY,CAAC,qBAAqB,EAAE,CAAC;gBACvE,SAAS;YACX,CAAC;YAED,MAAM,IAAI,GACR,MAAM,CAAC,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,qBAAqB,CAAC;YACpE,IAAI,IAAI,GAAG,UAAU,EAAE,CAAC;gBACtB,UAAU,GAAG,IAAI,CAAC;gBAClB,UAAU,GAAG,MAAM,CAAC;YACtB,CAAC;QACH,CAAC;QAED,OAAO,UAAU,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,qBAAqB;QACnB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YACtC,IACE,CAAC,CAAC,MAAM,KAAK,uBAAY,CAAC,SAAS;gBACnC,CAAC,CAAC,MAAM,KAAK,uBAAY,CAAC,IAAI,EAC9B,CAAC;gBACD,OAAO,CAAC,CAAC;YACX,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,QAAgB,EAAE,SAAiB;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QAED,MAAM,CAAC,cAAc,EAAE,CAAC;QACxB,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAEtC,IAAI,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,YAAY,CAAC,qBAAqB,EAAE,CAAC;YACvE,MAAM,CAAC,MAAM,GAAG,uBAAY,CAAC,IAAI,CAAC;QACpC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,cAAc,CACZ,SAAiB,EACjB,OAA0C;QAE1C,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC1C,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACzC,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC;gBAE/D,IAAI,OAAO,EAAE,kBAAkB,KAAK,IAAI,EAAE,CAAC;oBACzC,MAAM,CAAC,iBAAiB,EAAE,CAAC;gBAC7B,CAAC;gBAED,IACE,MAAM,CAAC,MAAM,KAAK,uBAAY,CAAC,IAAI;oBACnC,MAAM,CAAC,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,qBAAqB,EACjE,CAAC;oBACD,MAAM,CAAC,MAAM,GAAG,uBAAY,CAAC,SAAS,CAAC;gBACzC,CAAC;gBACD,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,iBAAiB;QACf,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,MAAM,CAAC,MAAM,KAAK,uBAAY,CAAC,SAAS,EAAE,CAAC;gBAC7C,KAAK,EAAE,CAAC;YACV,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,mDAAmD;IACnD,iBAAiB;QACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC7D,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,kBAA0B;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,aAAa,GAAa,EAAE,CAAC;QAEnC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,MAAM,kBAAkB,GAAG,GAAG,GAAG,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;YAEhE,IAAI,kBAAkB,GAAG,kBAAkB,EAAE,CAAC;gBAC5C,IAAI,MAAM,CAAC,MAAM,KAAK,uBAAY,CAAC,SAAS,EAAE,CAAC;oBAC7C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;wBAC3C,QAAQ,EAAE,MAAM,CAAC,EAAE;wBACnB,kBAAkB;wBAClB,SAAS,EAAE,kBAAkB;qBAC9B,CAAC,CAAC;oBACH,MAAM,CAAC,MAAM,GAAG,uBAAY,CAAC,SAAS,CAAC;gBACzC,CAAC;gBAED,IAAI,kBAAkB,GAAG,kBAAkB,GAAG,CAAC,EAAE,CAAC;oBAChD,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,gDAAgD;IAChD,IAAI,CAAC,QAAgB,EAAE,OAAgC;QACrD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,MAAM,CAAC,SAAS,CAAC,UAAU,KAAK,YAAS,CAAC,IAAI,EAAE,CAAC;YACnD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC;YACH,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,SAAS,CAAC,OAAgC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACrC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,MAAM,CAAC,SAAS,CAAC,UAAU,KAAK,YAAS,CAAC,IAAI,EAAE,CAAC;gBACnD,IAAI,CAAC;oBACH,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC9B,CAAC;gBAAC,MAAM,CAAC;oBACP,8BAA8B;gBAChC,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,oCAAoC;IACpC,QAAQ;QACN,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3C,IAAI,CAAC;gBACH,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;YACvD,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;CACF;AA5ND,gCA4NC"}
@@ -0,0 +1,67 @@
1
+ import type { HttpRequestHandler, WebSocketConnectionHandler, WorkerConnectedHandler, WorkerDisconnectedHandler, WorkerInfo, WorkerMessageHandler, WorkerServerOptions } from "./types.js";
2
+ /**
3
+ * WebSocket server for managing remote worker connections.
4
+ *
5
+ * Handles all transport-level concerns:
6
+ * - HTTP + WebSocket server with path-based routing
7
+ * - Worker registration with optional auth
8
+ * - Heartbeat protocol and health checks
9
+ * - Message routing by `type` field to registered handlers
10
+ * - Worker pool management (selection, request tracking)
11
+ *
12
+ * Consumers register handlers for domain-specific messages
13
+ * via `onWorkerMessage()` and interact with workers via `send()`.
14
+ */
15
+ export declare class WorkerServer {
16
+ private readonly port;
17
+ private readonly heartbeatTimeoutMs;
18
+ private readonly healthCheckIntervalMs;
19
+ private readonly logger;
20
+ private httpServer;
21
+ private workerWss;
22
+ private readonly additionalEndpoints;
23
+ private healthCheckInterval;
24
+ private readonly httpHandlers;
25
+ private readonly pool;
26
+ private readonly connectionHandler;
27
+ constructor(options: WorkerServerOptions);
28
+ /** Called when a worker successfully registers. Returns unsubscribe. */
29
+ onWorkerConnected(handler: WorkerConnectedHandler): () => void;
30
+ /** Called when a worker disconnects. Returns unsubscribe. */
31
+ onWorkerDisconnected(handler: WorkerDisconnectedHandler): () => void;
32
+ /**
33
+ * Register a handler for a specific worker message type.
34
+ * Messages are routed by the `type` field in the parsed JSON.
35
+ * Returns an unsubscribe function.
36
+ */
37
+ onWorkerMessage<T = Record<string, unknown>>(type: string, handler: WorkerMessageHandler<T>): () => void;
38
+ /** Send a JSON message to a specific worker. Returns false if failed. */
39
+ send(workerId: string, message: Record<string, unknown>): boolean;
40
+ /** Broadcast a JSON message to all connected workers. */
41
+ broadcast(message: Record<string, unknown>): void;
42
+ /** Get the least-loaded available worker supporting the given model. */
43
+ getAvailableWorker(model: string): WorkerInfo | null;
44
+ /** Get any available worker (model-agnostic). */
45
+ getAnyAvailableWorker(): WorkerInfo | null;
46
+ /** Total connected worker count. */
47
+ getWorkerCount(): number;
48
+ /** Available worker count. */
49
+ getAvailableWorkerCount(): number;
50
+ /** Get public info about all connected workers. */
51
+ getWorkerInfo(): WorkerInfo[];
52
+ /** Track a request as assigned to a worker. */
53
+ trackRequest(workerId: string, requestId: string): void;
54
+ /** Release a tracked request. */
55
+ releaseRequest(requestId: string, options?: {
56
+ incrementCompleted?: boolean;
57
+ }): void;
58
+ /** Add an HTTP handler. Handlers are called in order until one returns true. */
59
+ addHttpHandler(handler: HttpRequestHandler): void;
60
+ /** Add an additional WebSocket endpoint at a specific path. */
61
+ addWebSocketEndpoint(path: string, handler: WebSocketConnectionHandler): void;
62
+ start(): Promise<void>;
63
+ stop(): Promise<void>;
64
+ private runHealthCheck;
65
+ private handleHttpRequest;
66
+ }
67
+ //# sourceMappingURL=WorkerServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkerServer.d.ts","sourceRoot":"","sources":["../src/WorkerServer.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,EACzB,UAAU,EACV,oBAAoB,EAEpB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAoBpB;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAE5C,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAGhC;IACJ,OAAO,CAAC,mBAAmB,CAA+C;IAC1E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA4B;IAEzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;gBAE1C,OAAO,EAAE,mBAAmB;IAuBxC,wEAAwE;IACxE,iBAAiB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,IAAI;IAI9D,6DAA6D;IAC7D,oBAAoB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,IAAI;IAIpE;;;;OAIG;IACH,eAAe,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC/B,MAAM,IAAI;IAMb,yEAAyE;IACzE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAIjE,yDAAyD;IACzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAMjD,wEAAwE;IACxE,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAKpD,iDAAiD;IACjD,qBAAqB,IAAI,UAAU,GAAG,IAAI;IAK1C,oCAAoC;IACpC,cAAc,IAAI,MAAM;IAIxB,8BAA8B;IAC9B,uBAAuB,IAAI,MAAM;IAIjC,mDAAmD;IACnD,aAAa,IAAI,UAAU,EAAE;IAM7B,+CAA+C;IAC/C,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIvD,iCAAiC;IACjC,cAAc,CACZ,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAE,GACzC,IAAI;IAMP,gFAAgF;IAChF,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAIjD,+DAA+D;IAC/D,oBAAoB,CAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,0BAA0B,GAClC,IAAI;IAgBP,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmEhB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkC3B,OAAO,CAAC,cAAc;YAkBR,iBAAiB;CAqBhC"}
@@ -0,0 +1,255 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkerServer = void 0;
4
+ const http_1 = require("http");
5
+ const ws_1 = require("ws");
6
+ const ConnectionHandler_js_1 = require("./ConnectionHandler.js");
7
+ const WorkerPool_js_1 = require("./WorkerPool.js");
8
+ const DEFAULTS = {
9
+ heartbeatTimeoutMs: 60_000,
10
+ healthCheckIntervalMs: 10_000,
11
+ heartbeatIntervalMs: 15_000,
12
+ };
13
+ function noop() {
14
+ // intentional no-op
15
+ }
16
+ const NO_OP_LOGGER = {
17
+ debug: noop,
18
+ info: noop,
19
+ warn: noop,
20
+ error: noop,
21
+ };
22
+ /**
23
+ * WebSocket server for managing remote worker connections.
24
+ *
25
+ * Handles all transport-level concerns:
26
+ * - HTTP + WebSocket server with path-based routing
27
+ * - Worker registration with optional auth
28
+ * - Heartbeat protocol and health checks
29
+ * - Message routing by `type` field to registered handlers
30
+ * - Worker pool management (selection, request tracking)
31
+ *
32
+ * Consumers register handlers for domain-specific messages
33
+ * via `onWorkerMessage()` and interact with workers via `send()`.
34
+ */
35
+ class WorkerServer {
36
+ port;
37
+ heartbeatTimeoutMs;
38
+ healthCheckIntervalMs;
39
+ logger;
40
+ httpServer = null;
41
+ workerWss = null;
42
+ additionalEndpoints = new Map();
43
+ healthCheckInterval = null;
44
+ httpHandlers = [];
45
+ pool;
46
+ connectionHandler;
47
+ constructor(options) {
48
+ this.port = options.port;
49
+ this.heartbeatTimeoutMs =
50
+ options.heartbeatTimeoutMs ?? DEFAULTS.heartbeatTimeoutMs;
51
+ this.healthCheckIntervalMs =
52
+ options.healthCheckIntervalMs ?? DEFAULTS.healthCheckIntervalMs;
53
+ this.logger = options.logger ?? NO_OP_LOGGER;
54
+ this.pool = new WorkerPool_js_1.WorkerPool(this.logger);
55
+ this.connectionHandler = new ConnectionHandler_js_1.ConnectionHandler(this.pool, {
56
+ authToken: options.authToken,
57
+ heartbeatTimeoutMs: this.heartbeatTimeoutMs,
58
+ heartbeatIntervalMs: options.heartbeatIntervalMs ?? DEFAULTS.heartbeatIntervalMs,
59
+ }, this.logger);
60
+ }
61
+ // ========== Lifecycle Events ==========
62
+ /** Called when a worker successfully registers. Returns unsubscribe. */
63
+ onWorkerConnected(handler) {
64
+ return this.connectionHandler.onWorkerConnected(handler);
65
+ }
66
+ /** Called when a worker disconnects. Returns unsubscribe. */
67
+ onWorkerDisconnected(handler) {
68
+ return this.connectionHandler.onWorkerDisconnected(handler);
69
+ }
70
+ /**
71
+ * Register a handler for a specific worker message type.
72
+ * Messages are routed by the `type` field in the parsed JSON.
73
+ * Returns an unsubscribe function.
74
+ */
75
+ onWorkerMessage(type, handler) {
76
+ return this.connectionHandler.onMessage(type, handler);
77
+ }
78
+ // ========== Send Messages ==========
79
+ /** Send a JSON message to a specific worker. Returns false if failed. */
80
+ send(workerId, message) {
81
+ return this.pool.send(workerId, message);
82
+ }
83
+ /** Broadcast a JSON message to all connected workers. */
84
+ broadcast(message) {
85
+ this.pool.broadcast(message);
86
+ }
87
+ // ========== Pool Queries ==========
88
+ /** Get the least-loaded available worker supporting the given model. */
89
+ getAvailableWorker(model) {
90
+ const worker = this.pool.getAvailableWorker(model);
91
+ return worker !== null ? (0, WorkerPool_js_1.toWorkerInfo)(worker) : null;
92
+ }
93
+ /** Get any available worker (model-agnostic). */
94
+ getAnyAvailableWorker() {
95
+ const worker = this.pool.getAnyAvailableWorker();
96
+ return worker !== null ? (0, WorkerPool_js_1.toWorkerInfo)(worker) : null;
97
+ }
98
+ /** Total connected worker count. */
99
+ getWorkerCount() {
100
+ return this.pool.getCount();
101
+ }
102
+ /** Available worker count. */
103
+ getAvailableWorkerCount() {
104
+ return this.pool.getAvailableCount();
105
+ }
106
+ /** Get public info about all connected workers. */
107
+ getWorkerInfo() {
108
+ return this.pool.getWorkerInfoList();
109
+ }
110
+ // ========== Request Tracking ==========
111
+ /** Track a request as assigned to a worker. */
112
+ trackRequest(workerId, requestId) {
113
+ this.pool.trackRequest(workerId, requestId);
114
+ }
115
+ /** Release a tracked request. */
116
+ releaseRequest(requestId, options) {
117
+ this.pool.releaseRequest(requestId, options);
118
+ }
119
+ // ========== HTTP & WebSocket Extensibility ==========
120
+ /** Add an HTTP handler. Handlers are called in order until one returns true. */
121
+ addHttpHandler(handler) {
122
+ this.httpHandlers.push(handler);
123
+ }
124
+ /** Add an additional WebSocket endpoint at a specific path. */
125
+ addWebSocketEndpoint(path, handler) {
126
+ const wss = new ws_1.WebSocketServer({ noServer: true });
127
+ wss.on("connection", (ws) => {
128
+ handler(ws);
129
+ });
130
+ wss.on("error", (error) => {
131
+ this.logger.error("WebSocket server error", {
132
+ path,
133
+ error: error.message,
134
+ });
135
+ });
136
+ this.additionalEndpoints.set(path, { wss, handler });
137
+ }
138
+ // ========== Server Lifecycle ==========
139
+ start() {
140
+ return new Promise((resolve, reject) => {
141
+ if (this.httpServer !== null) {
142
+ reject(new Error("WorkerServer is already running"));
143
+ return;
144
+ }
145
+ this.httpServer = (0, http_1.createServer)((req, res) => {
146
+ void this.handleHttpRequest(req, res);
147
+ });
148
+ this.workerWss = new ws_1.WebSocketServer({ noServer: true });
149
+ this.workerWss.on("connection", (ws) => {
150
+ this.connectionHandler.handleConnection(ws);
151
+ });
152
+ this.workerWss.on("error", (error) => {
153
+ this.logger.error("Worker WebSocket server error", {
154
+ error: error.message,
155
+ });
156
+ });
157
+ // Route WebSocket upgrades by URL path
158
+ this.httpServer.on("upgrade", (request, socket, head) => {
159
+ const pathname = request.url ?? "/";
160
+ // Check additional endpoints first
161
+ const endpoint = this.additionalEndpoints.get(pathname);
162
+ if (endpoint !== undefined) {
163
+ endpoint.wss.handleUpgrade(request, socket, head, (ws) => {
164
+ endpoint.wss.emit("connection", ws, request);
165
+ });
166
+ return;
167
+ }
168
+ // Default: worker connections
169
+ const wss = this.workerWss;
170
+ if (wss !== null) {
171
+ wss.handleUpgrade(request, socket, head, (ws) => {
172
+ wss.emit("connection", ws, request);
173
+ });
174
+ }
175
+ });
176
+ this.httpServer.on("error", (error) => {
177
+ this.logger.error("HTTP server error", { error: error.message });
178
+ reject(error);
179
+ });
180
+ this.httpServer.listen(this.port, () => {
181
+ this.logger.info("HTTP + WebSocket server started", {
182
+ port: this.port,
183
+ });
184
+ this.healthCheckInterval = setInterval(() => {
185
+ this.runHealthCheck();
186
+ }, this.healthCheckIntervalMs);
187
+ resolve();
188
+ });
189
+ });
190
+ }
191
+ async stop() {
192
+ if (this.healthCheckInterval !== null) {
193
+ clearInterval(this.healthCheckInterval);
194
+ this.healthCheckInterval = null;
195
+ }
196
+ if (this.httpServer === null) {
197
+ return;
198
+ }
199
+ this.pool.closeAll();
200
+ if (this.workerWss !== null) {
201
+ this.workerWss.close();
202
+ this.workerWss = null;
203
+ }
204
+ for (const { wss } of this.additionalEndpoints.values()) {
205
+ wss.close();
206
+ }
207
+ this.additionalEndpoints.clear();
208
+ const server = this.httpServer;
209
+ return new Promise((resolve) => {
210
+ server.close(() => {
211
+ this.httpServer = null;
212
+ this.logger.info("HTTP + WebSocket server stopped");
213
+ resolve();
214
+ });
215
+ });
216
+ }
217
+ // ========== Private ==========
218
+ runHealthCheck() {
219
+ const deadWorkerIds = this.pool.checkHealth(this.heartbeatTimeoutMs);
220
+ for (const workerId of deadWorkerIds) {
221
+ const worker = this.pool.get(workerId);
222
+ if (worker !== undefined) {
223
+ this.logger.warn("Worker connection presumed dead, closing", {
224
+ workerId,
225
+ });
226
+ try {
227
+ worker.websocket.close(4003, "Heartbeat timeout");
228
+ }
229
+ catch {
230
+ // Ignore close errors
231
+ }
232
+ }
233
+ }
234
+ }
235
+ async handleHttpRequest(req, res) {
236
+ for (const handler of this.httpHandlers) {
237
+ try {
238
+ const handled = await handler(req, res);
239
+ if (handled) {
240
+ return;
241
+ }
242
+ }
243
+ catch (err) {
244
+ this.logger.error("HTTP handler error", {
245
+ error: err instanceof Error ? err.message : String(err),
246
+ url: req.url,
247
+ });
248
+ }
249
+ }
250
+ res.writeHead(404, { "Content-Type": "application/json" });
251
+ res.end(JSON.stringify({ error: "Not found" }));
252
+ }
253
+ }
254
+ exports.WorkerServer = WorkerServer;
255
+ //# sourceMappingURL=WorkerServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkerServer.js","sourceRoot":"","sources":["../src/WorkerServer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { WorkerServer } from "./WorkerServer.js";
2
+ export { WorkerPool } from "./WorkerPool.js";
3
+ export { WorkerStatus, type ModelInfo, type WorkerCapabilities, type WorkerInfo, type WorkerServerOptions, type WorkerServerLogger, type HttpRequestHandler, type WorkerMessageHandler, type WorkerConnectedHandler, type WorkerDisconnectedHandler, type WebSocketConnectionHandler, } from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,YAAY,EACZ,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,GAChC,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkerStatus = exports.WorkerPool = exports.WorkerServer = void 0;
4
+ var WorkerServer_js_1 = require("./WorkerServer.js");
5
+ Object.defineProperty(exports, "WorkerServer", { enumerable: true, get: function () { return WorkerServer_js_1.WorkerServer; } });
6
+ var WorkerPool_js_1 = require("./WorkerPool.js");
7
+ Object.defineProperty(exports, "WorkerPool", { enumerable: true, get: function () { return WorkerPool_js_1.WorkerPool; } });
8
+ var types_js_1 = require("./types.js");
9
+ Object.defineProperty(exports, "WorkerStatus", { enumerable: true, get: function () { return types_js_1.WorkerStatus; } });
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,qDAAiD;AAAxC,+GAAA,YAAY,OAAA;AACrB,iDAA6C;AAApC,2GAAA,UAAU,OAAA;AACnB,uCAYoB;AAXlB,wGAAA,YAAY,OAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Timing-safe string comparison to prevent brute-force attacks.
3
+ * Always takes constant time regardless of where strings differ.
4
+ */
5
+ export declare function safeCompare(a: string, b: string): boolean;
6
+ //# sourceMappingURL=safeCompare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safeCompare.d.ts","sourceRoot":"","sources":["../src/safeCompare.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAWzD"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.safeCompare = safeCompare;
4
+ const crypto_1 = require("crypto");
5
+ /**
6
+ * Timing-safe string comparison to prevent brute-force attacks.
7
+ * Always takes constant time regardless of where strings differ.
8
+ */
9
+ function safeCompare(a, b) {
10
+ const bufA = Buffer.from(a);
11
+ const bufB = Buffer.from(b);
12
+ if (bufA.length !== bufB.length) {
13
+ // Compare bufA against itself so the timing is consistent
14
+ (0, crypto_1.timingSafeEqual)(bufA, bufA);
15
+ return false;
16
+ }
17
+ return (0, crypto_1.timingSafeEqual)(bufA, bufB);
18
+ }
19
+ //# sourceMappingURL=safeCompare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safeCompare.js","sourceRoot":"","sources":["../src/safeCompare.ts"],"names":[],"mappings":";;AAMA,kCAWC;AAjBD,mCAAyC;AAEzC;;;GAGG;AACH,SAAgB,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE5B,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,0DAA0D;QAC1D,IAAA,wBAAe,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAA,wBAAe,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,124 @@
1
+ import type { IncomingMessage, ServerResponse } from "http";
2
+ import type WebSocket from "ws";
3
+ /**
4
+ * Status of a connected worker.
5
+ * Managed automatically based on request tracking and heartbeat.
6
+ */
7
+ export declare enum WorkerStatus {
8
+ Available = "available",
9
+ Busy = "busy",
10
+ Draining = "draining",
11
+ Unhealthy = "unhealthy"
12
+ }
13
+ /**
14
+ * Describes a model that a worker can run.
15
+ */
16
+ export interface ModelInfo {
17
+ modelId: string;
18
+ displayName: string;
19
+ maxContextTokens: number;
20
+ maxOutputTokens: number;
21
+ supportsStreaming: boolean;
22
+ supportsVision?: boolean;
23
+ supportsTools?: boolean;
24
+ }
25
+ /**
26
+ * Describes the capabilities and resources of a worker.
27
+ */
28
+ export interface WorkerCapabilities {
29
+ models: ModelInfo[];
30
+ maxConcurrentRequests: number;
31
+ metadata?: Record<string, unknown>;
32
+ }
33
+ /**
34
+ * Public worker info exposed to consumers.
35
+ * Does NOT include the raw WebSocket reference.
36
+ */
37
+ export interface WorkerInfo {
38
+ readonly id: string;
39
+ readonly name: string;
40
+ readonly status: WorkerStatus;
41
+ readonly capabilities: WorkerCapabilities;
42
+ readonly sessionId: string;
43
+ readonly connectedAt: Date;
44
+ readonly lastHeartbeat: Date;
45
+ readonly activeRequests: number;
46
+ readonly completedRequests: number;
47
+ readonly pendingRequestIds: ReadonlySet<string>;
48
+ }
49
+ /**
50
+ * Internal connected worker state (includes WebSocket).
51
+ */
52
+ export interface ConnectedWorker {
53
+ readonly id: string;
54
+ readonly name: string;
55
+ readonly websocket: WebSocket;
56
+ readonly capabilities: WorkerCapabilities;
57
+ status: WorkerStatus;
58
+ readonly sessionId: string;
59
+ readonly connectedAt: Date;
60
+ lastHeartbeat: Date;
61
+ activeRequests: number;
62
+ readonly pendingRequests: Set<string>;
63
+ completedRequests: number;
64
+ }
65
+ /** Configuration for the WorkerServer. */
66
+ export interface WorkerServerOptions {
67
+ /** Port for the HTTP + WebSocket server */
68
+ port: number;
69
+ /** Authentication token required from workers (optional) */
70
+ authToken?: string;
71
+ /** How long before a missed heartbeat marks unhealthy (default: 60000) */
72
+ heartbeatTimeoutMs?: number;
73
+ /** How often to check for stale connections (default: 10000) */
74
+ healthCheckIntervalMs?: number;
75
+ /** Heartbeat interval communicated to workers (default: 15000) */
76
+ heartbeatIntervalMs?: number;
77
+ /** Logger instance (optional, defaults to no-op) */
78
+ logger?: WorkerServerLogger;
79
+ }
80
+ /** Minimal logger interface compatible with @hardlydifficult/logger. */
81
+ export interface WorkerServerLogger {
82
+ debug(message: string, context?: Record<string, unknown>): void;
83
+ info(message: string, context?: Record<string, unknown>): void;
84
+ warn(message: string, context?: Record<string, unknown>): void;
85
+ error(message: string, context?: Record<string, unknown>): void;
86
+ }
87
+ /** HTTP request handler. Return true if handled. */
88
+ export type HttpRequestHandler = (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
89
+ /** Handler for typed worker messages. */
90
+ export type WorkerMessageHandler<T = Record<string, unknown>> = (worker: WorkerInfo, message: T) => void;
91
+ /** Handler for worker lifecycle events. */
92
+ export type WorkerConnectedHandler = (worker: WorkerInfo) => void;
93
+ export type WorkerDisconnectedHandler = (worker: WorkerInfo, pendingRequestIds: ReadonlySet<string>) => void;
94
+ /** Handler for additional WebSocket endpoints. */
95
+ export type WebSocketConnectionHandler = (ws: WebSocket) => void;
96
+ /** Worker registration message (worker → server) */
97
+ export interface RegistrationMessage {
98
+ type: "worker_registration";
99
+ workerId: string;
100
+ workerName: string;
101
+ capabilities: WorkerCapabilities;
102
+ authToken?: string;
103
+ }
104
+ /** Registration acknowledgment (server → worker) */
105
+ export interface RegistrationAckMessage {
106
+ type: "worker_registration_ack";
107
+ success: boolean;
108
+ error?: string;
109
+ sessionId?: string;
110
+ heartbeatIntervalMs?: number;
111
+ }
112
+ /** Heartbeat message (worker → server) */
113
+ export interface HeartbeatMessage {
114
+ type: "heartbeat";
115
+ workerId: string;
116
+ timestamp: string;
117
+ }
118
+ /** Heartbeat acknowledgment (server → worker) */
119
+ export interface HeartbeatAckMessage {
120
+ type: "heartbeat_ack";
121
+ timestamp: string;
122
+ nextHeartbeatDeadline: string;
123
+ }
124
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAE5D,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;AAEhC;;;GAGG;AACH,oBAAY,YAAY;IACtB,SAAS,cAAc;IACvB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,SAAS,cAAc;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,aAAa,EAAE,IAAI,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,0CAA0C;AAC1C,MAAM,WAAW,mBAAmB;IAClC,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kEAAkE;IAClE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oDAAoD;IACpD,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAChE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACjE;AAED,oDAAoD;AACpD,MAAM,MAAM,kBAAkB,GAAG,CAC/B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,KAChB,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,yCAAyC;AACzC,MAAM,MAAM,oBAAoB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAC9D,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,CAAC,KACP,IAAI,CAAC;AAEV,2CAA2C;AAC3C,MAAM,MAAM,sBAAsB,GAAG,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;AAClE,MAAM,MAAM,yBAAyB,GAAG,CACtC,MAAM,EAAE,UAAU,EAClB,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,KACnC,IAAI,CAAC;AAEV,kDAAkD;AAClD,MAAM,MAAM,0BAA0B,GAAG,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAC;AAMjE,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,kBAAkB,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,oDAAoD;AACpD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iDAAiD;AACjD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;CAC/B"}
package/dist/types.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkerStatus = void 0;
4
+ /**
5
+ * Status of a connected worker.
6
+ * Managed automatically based on request tracking and heartbeat.
7
+ */
8
+ var WorkerStatus;
9
+ (function (WorkerStatus) {
10
+ WorkerStatus["Available"] = "available";
11
+ WorkerStatus["Busy"] = "busy";
12
+ WorkerStatus["Draining"] = "draining";
13
+ WorkerStatus["Unhealthy"] = "unhealthy";
14
+ })(WorkerStatus || (exports.WorkerStatus = WorkerStatus = {}));
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAIA;;;GAGG;AACH,IAAY,YAKX;AALD,WAAY,YAAY;IACtB,uCAAuB,CAAA;IACvB,6BAAa,CAAA;IACb,qCAAqB,CAAA;IACrB,uCAAuB,CAAA;AACzB,CAAC,EALW,YAAY,4BAAZ,YAAY,QAKvB"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@hardlydifficult/worker-server",
3
+ "version": "1.0.0",
4
+ "main": "./dist/index.js",
5
+ "types": "./dist/index.d.ts",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest",
13
+ "test:coverage": "vitest run --coverage",
14
+ "lint": "tsc --noEmit",
15
+ "clean": "rm -rf dist"
16
+ },
17
+ "dependencies": {
18
+ "ws": "8.19.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "25.2.3",
22
+ "@types/ws": "8.18.1",
23
+ "typescript": "5.9.3",
24
+ "vitest": "4.0.18"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ }
29
+ }