@orkestrel/worker 0.0.4 → 0.0.5

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.
@@ -1,5 +1,5 @@
1
- import { Worker, parentPort } from "node:worker_threads";
2
1
  import { attempt, isRecord } from "@orkestrel/contract";
2
+ import { Worker, parentPort } from "node:worker_threads";
3
3
  import { createJSONDriver } from "@orkestrel/database/server";
4
4
  import { createDatabaseQueueStore } from "@orkestrel/queue";
5
5
  import { createWorker } from "../core/index.js";
@@ -8,16 +8,16 @@ import { createWorker } from "../core/index.js";
8
8
  * Internal mutable implementation of the readonly {@link NodeThread} observation contract.
9
9
  *
10
10
  * @remarks
11
- * Liveness and the first terminal error live behind runtime-private fields. Consumers observe
12
- * their current values through readonly getters, while the worker lifecycle records transitions
13
- * through bound instance methods without exposing writable contract properties.
11
+ * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,
12
+ * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose
13
+ * inbound message could not be deserialized.
14
14
  */
15
15
  var Thread = class {
16
16
  #worker;
17
17
  #promise;
18
18
  #resolve;
19
19
  #reject;
20
- #recordErrorHandler;
20
+ #recordHandler;
21
21
  #recordExitHandler;
22
22
  #onlineHandler;
23
23
  #spawnErrorHandler;
@@ -30,12 +30,13 @@ var Thread = class {
30
30
  this.#promise = readiness.promise;
31
31
  this.#resolve = readiness.resolve;
32
32
  this.#reject = readiness.reject;
33
- this.#recordErrorHandler = this.#recordError.bind(this);
33
+ this.#recordHandler = this.#record.bind(this);
34
34
  this.#recordExitHandler = this.#recordExit.bind(this);
35
35
  this.#onlineHandler = this.#online.bind(this);
36
36
  this.#spawnErrorHandler = this.#spawnError.bind(this);
37
37
  this.#spawnExitHandler = this.#spawnExit.bind(this);
38
- this.#worker.on("error", this.#recordErrorHandler);
38
+ this.#worker.on("error", this.#recordHandler);
39
+ this.#worker.on("messageerror", this.#recordHandler);
39
40
  this.#worker.on("exit", this.#recordExitHandler);
40
41
  this.#worker.once("online", this.#onlineHandler);
41
42
  this.#worker.once("error", this.#spawnErrorHandler);
@@ -56,7 +57,7 @@ var Thread = class {
56
57
  evict() {
57
58
  this.#alive = false;
58
59
  }
59
- #recordError(error) {
60
+ #record(error) {
60
61
  this.#alive = false;
61
62
  if (this.#death === void 0) this.#death = error;
62
63
  }
@@ -108,9 +109,10 @@ function isReply(value, id) {
108
109
  * Internal lifecycle entity for one dispatched worker-thread job.
109
110
  *
110
111
  * @remarks
111
- * Owns the stable listener identities, settlement guard, cleanup, result narrowing, and abort
112
- * eviction for one dispatch. The public {@link dispatch} helper constructs this entity and returns
113
- * its promise.
112
+ * Owns stable `message` / `messageerror` / death listener identities, settlement, result-guard
113
+ * containment, and abort eviction for one dispatch. Deserialization failure, a matching-id
114
+ * malformed reply, and abort each evict and terminate the thread before rejecting, with
115
+ * termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter is ignored.
114
116
  */
115
117
  var Dispatch = class {
116
118
  #thread;
@@ -123,6 +125,7 @@ var Dispatch = class {
123
125
  #fulfill;
124
126
  #reject;
125
127
  #messageHandler;
128
+ #messageErrorHandler;
126
129
  #errorHandler;
127
130
  #exitHandler;
128
131
  #abortHandler;
@@ -138,6 +141,7 @@ var Dispatch = class {
138
141
  this.#fulfill = settlement.resolve;
139
142
  this.#reject = settlement.reject;
140
143
  this.#messageHandler = this.#message.bind(this);
144
+ this.#messageErrorHandler = this.#messageError.bind(this);
141
145
  this.#errorHandler = this.#error.bind(this);
142
146
  this.#exitHandler = this.#exit.bind(this);
143
147
  this.#abortHandler = this.#abort.bind(this);
@@ -152,6 +156,7 @@ var Dispatch = class {
152
156
  return;
153
157
  }
154
158
  this.#worker.on("message", this.#messageHandler);
159
+ this.#worker.on("messageerror", this.#messageErrorHandler);
155
160
  this.#worker.on("error", this.#errorHandler);
156
161
  this.#worker.on("exit", this.#exitHandler);
157
162
  if (this.#execution.signal.aborted) {
@@ -170,29 +175,70 @@ var Dispatch = class {
170
175
  }
171
176
  }
172
177
  #message(value) {
173
- if (!isReply(value, this.#id)) return;
178
+ if (!isRecord(value)) return;
179
+ const id = attempt(() => value.id);
180
+ if (!id.success || id.value !== this.#id) return;
181
+ if (!isReply(value, this.#id)) {
182
+ this.#terminate(/* @__PURE__ */ new Error("worker reply was malformed"));
183
+ return;
184
+ }
174
185
  if (value.ok) {
175
186
  const reply = value.value;
176
- if (this.#result(reply)) this.#succeed(reply);
177
- else this.#fail(/* @__PURE__ */ new Error("reply did not satisfy result guard"));
187
+ try {
188
+ if (this.#result(reply)) this.#succeed(reply);
189
+ else this.#fail(/* @__PURE__ */ new Error("reply did not satisfy result guard"));
190
+ } catch (error) {
191
+ this.#fail(error);
192
+ }
178
193
  return;
179
194
  }
180
195
  this.#fail(new Error(value.error));
181
196
  }
197
+ #messageError(error) {
198
+ this.#terminate(error);
199
+ }
182
200
  #error(error) {
183
201
  this.#fail(error);
184
202
  }
185
203
  #exit() {
186
- this.#fail(/* @__PURE__ */ new Error("worker thread exited"));
204
+ this.#fail(this.#thread.death ?? /* @__PURE__ */ new Error("worker thread exited"));
187
205
  }
188
206
  #abort() {
189
- this.#worker.postMessage({
190
- id: this.#id,
191
- command: "abort"
192
- });
207
+ const notification = [];
208
+ try {
209
+ this.#worker.postMessage({
210
+ id: this.#id,
211
+ command: "abort"
212
+ });
213
+ } catch (cause) {
214
+ notification.push(cause);
215
+ }
216
+ this.#terminate(this.#execution.signal.reason, notification);
217
+ }
218
+ #terminate(error, notification = []) {
219
+ if (this.#settled) return;
220
+ this.#settled = true;
221
+ this.#detach();
193
222
  if (this.#thread instanceof Thread) this.#thread.evict();
194
- this.#worker.terminate();
195
- this.#fail(/* @__PURE__ */ new Error("job aborted"));
223
+ let termination;
224
+ try {
225
+ termination = this.#worker.terminate();
226
+ } catch (cause) {
227
+ this.#reject(new AggregateError([
228
+ error,
229
+ ...notification,
230
+ cause
231
+ ], "worker termination failed"));
232
+ return;
233
+ }
234
+ termination.then(() => {
235
+ if (notification.length === 0) this.#reject(error);
236
+ else this.#reject(new AggregateError([error, ...notification], "worker abort notification failed"));
237
+ }, (cause) => this.#reject(new AggregateError([
238
+ error,
239
+ ...notification,
240
+ cause
241
+ ], "worker termination failed")));
196
242
  }
197
243
  #succeed(value) {
198
244
  if (this.#settled) return;
@@ -208,6 +254,7 @@ var Dispatch = class {
208
254
  }
209
255
  #detach() {
210
256
  this.#worker.off("message", this.#messageHandler);
257
+ this.#worker.off("messageerror", this.#messageErrorHandler);
211
258
  this.#worker.off("error", this.#errorHandler);
212
259
  this.#worker.off("exit", this.#exitHandler);
213
260
  this.#execution.signal.removeEventListener("abort", this.#abortHandler);
@@ -226,11 +273,11 @@ var Dispatch = class {
226
273
  * listeners that flip `alive` to `false` AND latch the first terminal event on
227
274
  * {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via
228
275
  * its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a
229
- * dispatch that attaches AFTER the death (via the latch). The latch closes a real race:
230
- * under event-loop pressure a dead thread's `online` + `error` + `exit` are delivered in
231
- * ONE synchronous exit-drain batch, so every death event fires before the microtask chain
232
- * resolving this spawn can hand the thread to `dispatch` without the latch that job
233
- * would await events that already fired, forever. The pool's `create` hook calls this.
276
+ * dispatch that attaches AFTER the death (via the latch). A `messageerror` is terminal too,
277
+ * so a thread whose inbound payload could not be deserialized is never reused. The latch
278
+ * closes a real race: a thread can become terminal before the readiness promise continuation
279
+ * hands it to `dispatch`, leaving no future death event for that dispatch to observe. Without
280
+ * the latch, that job would wait forever. The pool's `create` hook calls this.
234
281
  *
235
282
  * @param script - The worker module each thread runs (must call `serveWorker`)
236
283
  * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn
@@ -247,15 +294,15 @@ function spawnThread(script, workerData) {
247
294
  * that id: a success `value` is narrowed through `result` (a value that fails the guard
248
295
  * rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.
249
296
  * A thread that ALREADY died rejects synchronously at entry from the latched
250
- * {@link NodeThread.death} — its death events fired before this dispatch existed (under
251
- * load they arrive in one batched exit drain) and will never fire again, so waiting on
252
- * the listeners below would dangle forever; the latch makes the death total across every
253
- * event ordering. If the thread `error`s / `exit`s mid-flight it is marked dead and the
254
- * job rejects. On `execution.signal` abort it posts an `abort` envelope (cooperative) AND
255
- * evicts the thread `alive = false` + `terminate()` — because CPU-bound work cannot
256
- * honour the signal; the freed pool slot then gets a fresh thread. Every listener (the
257
- * thread's `message` / `error` / `exit` and the signal's `abort`) is removed on settle,
258
- * and a `settled` guard prevents a double-settle.
297
+ * {@link NodeThread.death} — its death events fired before this dispatch existed and will
298
+ * never fire again, so waiting on the listeners below would dangle forever; the latch makes
299
+ * death total across every event ordering. If the thread `error`s / `exit`s mid-flight it is
300
+ * marked dead and the
301
+ * job rejects. An inbound `messageerror` also evicts and terminates the thread before
302
+ * rejection. On `execution.signal` abort it contains the cooperative `abort` post,
303
+ * evicts the thread, and observes `terminate()` settlement because CPU-bound work cannot
304
+ * honour the signal; the freed pool slot then gets a fresh thread. Every per-job listener
305
+ * (`message` / `messageerror` / `error` / `exit` / `abort`) is removed on settle.
259
306
  *
260
307
  * @typeParam TResult - The reply type the `result` guard narrows to
261
308
  * @param thread - The leased thread to run the job on
@@ -286,7 +333,10 @@ function isAbort(value) {
286
333
  * run/abort protocol: a `run` message narrows its `input` through `options.input` (an
287
334
  * invalid payload replies with an error envelope, never running the handler), then runs
288
335
  * `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or
289
- * `{ id, ok: false, error }` on throw. Each in-flight job has its own `AbortController`,
336
+ * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a
337
+ * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
338
+ * fails, the parent port closes so the main side observes thread exit instead of waiting forever.
339
+ * Each in-flight job has its own `AbortController`,
290
340
  * so an `abort` message for that id fires the handler's `signal` (cooperative — the main
291
341
  * side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
292
342
  * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
@@ -299,7 +349,7 @@ function isAbort(value) {
299
349
  * @example
300
350
  * ```ts
301
351
  * // double.ts — a worker script
302
- * import { serveWorker } from '@src/server'
352
+ * import { serveWorker } from '@orkestrel/worker/server'
303
353
  *
304
354
  * serveWorker<number, number>({
305
355
  * input: (value): value is number => typeof value === 'number',
@@ -310,6 +360,8 @@ function isAbort(value) {
310
360
  function serveWorker(options) {
311
361
  const port = parentPort;
312
362
  if (port === null) return;
363
+ const input = options.input;
364
+ const handler = options.handler;
313
365
  const controllers = /* @__PURE__ */ new Map();
314
366
  port.on("message", (raw) => {
315
367
  if (isAbort(raw)) {
@@ -318,31 +370,36 @@ function serveWorker(options) {
318
370
  }
319
371
  if (!isRun(raw)) return;
320
372
  const id = raw.id;
321
- if (!options.input(raw.input)) {
322
- port.postMessage({
323
- id,
324
- ok: false,
325
- error: "input did not satisfy input guard"
326
- });
327
- return;
328
- }
329
- const input = raw.input;
330
373
  const controller = new AbortController();
331
374
  controllers.set(id, controller);
332
- Promise.resolve().then(() => options.handler(input, { signal: controller.signal })).then((value) => {
375
+ Promise.resolve().then(() => {
376
+ if (!input(raw.input)) throw new Error("input did not satisfy input guard");
377
+ const value = raw.input;
378
+ return handler(value, { signal: controller.signal });
379
+ }).then((value) => {
333
380
  controllers.delete(id);
334
381
  port.postMessage({
335
382
  id,
336
383
  ok: true,
337
384
  value
338
385
  });
339
- }, (error) => {
386
+ }).catch((error) => {
340
387
  controllers.delete(id);
341
- port.postMessage({
342
- id,
343
- ok: false,
344
- error: error instanceof Error ? error.message : String(error)
345
- });
388
+ let message = "worker operation failed";
389
+ try {
390
+ message = error instanceof Error ? error.message : String(error);
391
+ } catch {}
392
+ try {
393
+ port.postMessage({
394
+ id,
395
+ ok: false,
396
+ error: message
397
+ });
398
+ } catch {
399
+ try {
400
+ port.close();
401
+ } catch {}
402
+ }
346
403
  });
347
404
  });
348
405
  }
@@ -356,9 +413,23 @@ function serveWorker(options) {
356
413
  * public entity remains the plain core {@link WorkerInterface}.
357
414
  */
358
415
  var NodeWorker = class {
359
- #options;
416
+ #script;
417
+ #input;
418
+ #result;
419
+ #workerData;
420
+ #concurrency;
421
+ #retries;
422
+ #timeout;
423
+ #store;
360
424
  constructor(options) {
361
- this.#options = options;
425
+ this.#script = options.script;
426
+ this.#input = options.input;
427
+ this.#result = options.result;
428
+ this.#workerData = options.workerData;
429
+ this.#concurrency = options.concurrency;
430
+ this.#retries = options.retries;
431
+ this.#timeout = options.timeout;
432
+ this.#store = options.store;
362
433
  }
363
434
  build() {
364
435
  return createWorker({
@@ -366,17 +437,17 @@ var NodeWorker = class {
366
437
  create: this.#create.bind(this),
367
438
  destroy: this.#destroy.bind(this),
368
439
  validate: this.#validate.bind(this),
369
- ...this.#options.concurrency !== void 0 ? { max: this.#options.concurrency } : {}
440
+ ...this.#concurrency !== void 0 ? { max: this.#concurrency } : {}
370
441
  },
371
442
  handler: this.#handle.bind(this),
372
- ...this.#options.concurrency !== void 0 ? { concurrency: this.#options.concurrency } : {},
373
- ...this.#options.retries !== void 0 ? { retries: this.#options.retries } : {},
374
- ...this.#options.timeout !== void 0 ? { timeout: this.#options.timeout } : {},
375
- ...this.#options.store !== void 0 ? { store: this.#options.store } : {}
443
+ ...this.#concurrency !== void 0 ? { concurrency: this.#concurrency } : {},
444
+ ...this.#retries !== void 0 ? { retries: this.#retries } : {},
445
+ ...this.#timeout !== void 0 ? { timeout: this.#timeout } : {},
446
+ ...this.#store !== void 0 ? { store: this.#store } : {}
376
447
  });
377
448
  }
378
449
  #create() {
379
- return spawnThread(this.#options.script, this.#options.workerData);
450
+ return spawnThread(this.#script, this.#workerData);
380
451
  }
381
452
  async #destroy(thread) {
382
453
  await thread.worker.terminate();
@@ -385,8 +456,10 @@ var NodeWorker = class {
385
456
  return thread.alive && thread.worker.threadId > 0;
386
457
  }
387
458
  #handle(input, thread, execution) {
388
- if (!this.#options.input(input)) return Promise.reject(/* @__PURE__ */ new Error("input did not satisfy input guard"));
389
- return dispatch(thread, input, execution, this.#options.result);
459
+ const outcome = attempt(() => this.#input(input));
460
+ if (!outcome.success) return Promise.reject(outcome.error);
461
+ if (!outcome.value) return Promise.reject(/* @__PURE__ */ new Error("input did not satisfy input guard"));
462
+ return dispatch(thread, input, execution, this.#result);
390
463
  }
391
464
  };
392
465
  //#endregion
@@ -411,8 +484,8 @@ var NodeWorker = class {
411
484
  *
412
485
  * @example
413
486
  * ```ts
414
- * import { stringShape } from '@src/core'
415
- * import { createJSONQueueStore } from '@src/server'
487
+ * import { stringShape } from '@orkestrel/contract'
488
+ * import { createJSONQueueStore } from '@orkestrel/worker/server'
416
489
  *
417
490
  * const store = createJSONQueueStore('data/queue.json', stringShape())
418
491
  * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })
@@ -452,7 +525,7 @@ function createJSONQueueStore(path, input) {
452
525
  *
453
526
  * @example
454
527
  * ```ts
455
- * import { createNodeWorker } from '@src/server'
528
+ * import { createNodeWorker } from '@orkestrel/worker/server'
456
529
  *
457
530
  * const worker = createNodeWorker({
458
531
  * script: new URL('./double.js', import.meta.url),
@@ -462,7 +535,7 @@ function createJSONQueueStore(path, input) {
462
535
  * })
463
536
  *
464
537
  * const doubled = await worker.enqueue(21) // 42, computed on a worker thread
465
- * worker.destroy() // terminates every thread
538
+ * await worker.destroy() // terminates every thread
466
539
  * ```
467
540
  */
468
541
  function createNodeWorker(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#worker","#promise","#resolve","#reject","#recordErrorHandler","#recordExitHandler","#onlineHandler","#spawnErrorHandler","#spawnExitHandler","#recordError","#recordExit","#online","#spawnError","#spawnExit","#alive","#death","#thread","#worker","#input","#execution","#result","#id","#promise","#fulfill","#reject","#messageHandler","#errorHandler","#exitHandler","#abortHandler","#message","#error","#exit","#abort","#start","#fail","#succeed","#settled","#detach","#options","#create","#destroy","#validate","#handle"],"sources":["../../../src/server/Thread.ts","../../../src/server/validators.ts","../../../src/server/Dispatch.ts","../../../src/server/helpers.ts","../../../src/server/serve.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Internal mutable implementation of the readonly {@link NodeThread} observation contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Consumers observe\n * their current values through readonly getters, while the worker lifecycle records transitions\n * through bound instance methods without exposing writable contract properties.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordErrorHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordErrorHandler = this.#recordError.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordErrorHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#recordError(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { Reply } from './types.js'\nimport { attempt, isRecord } from '@orkestrel/contract'\n\n/**\n * Narrow an inbound `message` to a {@link Reply} for a given job `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n *\n * @param value - The inbound message to narrow\n * @param id - The job id a matching reply must carry\n * @returns `true` when the value is this job's well-formed reply\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && typeof value.error === 'string'\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard, NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { Thread } from './Thread.js'\nimport { isReply } from './validators.js'\n\n/**\n * Internal lifecycle entity for one dispatched worker-thread job.\n *\n * @remarks\n * Owns the stable listener identities, settlement guard, cleanup, result narrowing, and abort\n * eviction for one dispatch. The public {@link dispatch} helper constructs this entity and returns\n * its promise.\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #execution: QueueExecution\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(\n\t\tthread: NodeThread,\n\t\tinput: unknown,\n\t\texecution: QueueExecution,\n\t\tresult: Guard<TResult>,\n\t) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#execution = execution\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#execution.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#execution.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'run', input: this.#input })\n\t\t} catch (error: unknown) {\n\t\t\tthis.#fail(error instanceof Error ? error : new Error(String(error)))\n\t\t}\n\t}\n\n\t#message(value: unknown): void {\n\t\tif (!isReply(value, this.#id)) return\n\t\tif (value.ok) {\n\t\t\tconst reply = value.value\n\t\t\tif (this.#result(reply)) this.#succeed(reply)\n\t\t\telse this.#fail(new Error('reply did not satisfy result guard'))\n\t\t\treturn\n\t\t}\n\t\tthis.#fail(new Error(value.error))\n\t}\n\n\t#error(error: Error): void {\n\t\tthis.#fail(error)\n\t}\n\n\t#exit(): void {\n\t\tthis.#fail(new Error('worker thread exited'))\n\t}\n\n\t#abort(): void {\n\t\tthis.#worker.postMessage({ id: this.#id, command: 'abort' })\n\t\tif (this.#thread instanceof Thread) this.#thread.evict()\n\t\tvoid this.#worker.terminate()\n\t\tthis.#fail(new Error('job aborted'))\n\t}\n\n\t#succeed(value: TResult): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#fulfill(value)\n\t}\n\n\t#fail(error: unknown): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#reject(error)\n\t}\n\n\t#detach(): void {\n\t\tthis.#worker.off('message', this.#messageHandler)\n\t\tthis.#worker.off('error', this.#errorHandler)\n\t\tthis.#worker.off('exit', this.#exitHandler)\n\t\tthis.#execution.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard, NodeThread } from './types.js'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n// === The wire protocol (main ↔ thread)\n//\n// The main-side half of the run/abort/reply protocol `serveWorker` answers — spawning a\n// pooled thread, narrowing its replies, and dispatching one job at a time. The envelope\n// types ({@link Reply}, {@link NodeThread}) live in `./types.js` (AGENTS §5); the public\n// bridge across the structured-clone boundary is the `input` / `result` `Guard`s, which\n// narrow the envelopes' opaque `unknown` payloads with no assertion (AGENTS §14).\n\n/**\n * Spawn one worker thread and resolve a live {@link NodeThread} once it comes online.\n *\n * @remarks\n * Constructs the thread with the `script` module and the cloned `workerData`, then\n * resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`\n * that arrives before `online`, so the spawn promise is total — it can never dangle on a\n * thread that died without erroring). The wrapper attaches persistent `error` / `exit`\n * listeners that flip `alive` to `false` AND latch the first terminal event on\n * {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via\n * its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a\n * dispatch that attaches AFTER the death (via the latch). The latch closes a real race:\n * under event-loop pressure a dead thread's `online` + `error` + `exit` are delivered in\n * ONE synchronous exit-drain batch, so every death event fires before the microtask chain\n * resolving this spawn can hand the thread to `dispatch` — without the latch that job\n * would await events that already fired, forever. The pool's `create` hook calls this.\n *\n * @param script - The worker module each thread runs (must call `serveWorker`)\n * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn\n * @returns A promise resolving the online {@link NodeThread}\n */\nexport function spawnThread(script: string | URL, workerData: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.\n *\n * @remarks\n * Mints a fresh `id`, posts a `run` envelope, and resolves when the thread replies for\n * that id: a success `value` is narrowed through `result` (a value that fails the guard\n * rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.\n * A thread that ALREADY died rejects synchronously at entry from the latched\n * {@link NodeThread.death} — its death events fired before this dispatch existed (under\n * load they arrive in one batched exit drain) and will never fire again, so waiting on\n * the listeners below would dangle forever; the latch makes the death total across every\n * event ordering. If the thread `error`s / `exit`s mid-flight it is marked dead and the\n * job rejects. On `execution.signal` abort it posts an `abort` envelope (cooperative) AND\n * evicts the thread — `alive = false` + `terminate()` — because CPU-bound work cannot\n * honour the signal; the freed pool slot then gets a fresh thread. Every listener (the\n * thread's `message` / `error` / `exit` and the signal's `abort`) is removed on settle,\n * and a `settled` guard prevents a double-settle.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n * @param thread - The leased thread to run the job on\n * @param input - The work payload (structured-cloned to the thread)\n * @param execution - The per-attempt handle; its `signal` aborts → terminate + evict\n * @param result - The {@link Guard} narrowing the reply value with no assertion\n * @returns A promise resolving the narrowed `TResult`, or rejecting on error / abort\n */\nexport function dispatch<TResult>(\n\tthread: NodeThread,\n\tinput: unknown,\n\texecution: QueueExecution,\n\tresult: Guard<TResult>,\n): Promise<TResult> {\n\treturn new Dispatch(thread, input, execution, result).promise\n}\n","import type { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side entry. SELF-CONTAINED by necessity: this module loads as RAW `.ts`\n// inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports ONLY\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// Its guards are inlined for the same reason. A worker script that needs the cloned\n// `workerData` reads it directly from `node:worker_threads` (it is in a thread already).\n\n// Inlined record guard (do NOT import `isRecord` from `@src/core` — see above). Total:\n// adversarial input returns `false`, never throws (AGENTS §14).\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n// Narrow an inbound message to a `run` envelope (a string `id` + a `'run'` command + an\n// `input` payload) — no assertion.\nfunction isRun(value: unknown): value is { readonly id: string; readonly input: unknown } {\n\treturn (\n\t\tisRecord(value) && typeof value.id === 'string' && value.command === 'run' && 'input' in value\n\t)\n}\n\n// Narrow an inbound message to an `abort` envelope (a string `id` + an `'abort'` command).\nfunction isAbort(value: unknown): value is { readonly id: string } {\n\treturn isRecord(value) && typeof value.id === 'string' && value.command === 'abort'\n}\n\n/**\n * Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or\n * `{ id, ok: false, error }` on throw. Each in-flight job has its own `AbortController`,\n * so an `abort` message for that id fires the handler's `signal` (cooperative — the main\n * side ALSO terminates the thread, so a handler that ignores its signal is still stopped).\n * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread\n * (`parentPort === null`) it is a no-op.\n *\n * @typeParam TInput - The work payload (inferred from `options.input`)\n * @typeParam TResult - The value the handler resolves (the reply payload)\n * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})\n *\n * @example\n * ```ts\n * // double.ts — a worker script\n * import { serveWorker } from '@src/server'\n *\n * serveWorker<number, number>({\n * \tinput: (value): value is number => typeof value === 'number',\n * \thandler: (value) => value * 2,\n * })\n * ```\n */\nexport function serveWorker<TInput, TResult>(options: ServeWorkerOptions<TInput, TResult>): void {\n\tconst port = parentPort\n\tif (port === null) return\n\tconst controllers = new Map<string, AbortController>()\n\tport.on('message', (raw: unknown) => {\n\t\tif (isAbort(raw)) {\n\t\t\tcontrollers.get(raw.id)?.abort()\n\t\t\treturn\n\t\t}\n\t\tif (!isRun(raw)) return\n\t\tconst id = raw.id\n\t\tif (!options.input(raw.input)) {\n\t\t\tport.postMessage({ id, ok: false, error: 'input did not satisfy input guard' })\n\t\t\treturn\n\t\t}\n\t\tconst input = raw.input\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\t// Defer the handler call into the `then` so a SYNCHRONOUS throw becomes a rejection\n\t\t// (not an uncaught thread exception) and is reported as an error reply.\n\t\tPromise.resolve()\n\t\t\t.then(() => options.handler(input, { signal: controller.signal }))\n\t\t\t.then(\n\t\t\t\t(value) => {\n\t\t\t\t\tcontrollers.delete(id)\n\t\t\t\t\tport.postMessage({ id, ok: true, value })\n\t\t\t\t},\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\tcontrollers.delete(id)\n\t\t\t\t\tport.postMessage({\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t)\n\t})\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { QueueExecution } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { dispatch, spawnThread } from './helpers.js'\n\n/**\n * Internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity remains the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #options: NodeWorkerOptions<TInput, TResult>\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#options = options\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#options.concurrency !== undefined ? { max: this.#options.concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#options.concurrency !== undefined\n\t\t\t\t? { concurrency: this.#options.concurrency }\n\t\t\t\t: {}),\n\t\t\t...(this.#options.retries !== undefined ? { retries: this.#options.retries } : {}),\n\t\t\t...(this.#options.timeout !== undefined ? { timeout: this.#options.timeout } : {}),\n\t\t\t...(this.#options.store !== undefined ? { store: this.#options.store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn spawnThread(this.#options.script, this.#options.workerData)\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, execution: QueueExecution): Promise<TResult> {\n\t\tif (!this.#options.input(input)) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn dispatch(thread, input, execution, this.#options.result)\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\n\n/**\n * Create a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is just a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@src/core'\n * import { createJSONQueueStore } from '@src/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Create a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker THREAD.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (via `spawnThread`),\n * `destroy`s it with `terminate()`, and `validate`s it by `alive && threadId > 0` (so an\n * evicted / crashed thread is dropped and replaced) — and an internal handler that\n * narrows the input through `options.input` (fail-fast before the structured-clone\n * boundary) then `dispatch`es the job to the leased thread, narrowing the reply through\n * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites\n * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards\n * reconstruct `TInput` / `TResult` by validation (AGENTS §14). An `abort` / `timeout`\n * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@src/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["#worker","#promise","#resolve","#reject","#recordHandler","#recordExitHandler","#onlineHandler","#spawnErrorHandler","#spawnExitHandler","#record","#recordExit","#online","#spawnError","#spawnExit","#alive","#death","#thread","#worker","#input","#execution","#result","#id","#promise","#fulfill","#reject","#messageHandler","#messageErrorHandler","#errorHandler","#exitHandler","#abortHandler","#message","#messageError","#error","#exit","#abort","#start","#fail","#terminate","#succeed","#settled","#detach","#script","#input","#result","#workerData","#concurrency","#retries","#timeout","#store","#create","#destroy","#validate","#handle"],"sources":["../../../src/server/Thread.ts","../../../src/server/validators.ts","../../../src/server/Dispatch.ts","../../../src/server/helpers.ts","../../../src/server/serve.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Internal mutable implementation of the readonly {@link NodeThread} observation contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { Reply } from './types.js'\nimport { attempt, isRecord } from '@orkestrel/contract'\n\n/**\n * Narrow an inbound `message` to a {@link Reply} for a given job `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n *\n * @param value - The inbound message to narrow\n * @param id - The job id a matching reply must carry\n * @returns `true` when the value is this job's well-formed reply\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && typeof value.error === 'string'\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isRecord } from '@orkestrel/contract'\nimport { Thread } from './Thread.js'\nimport { isReply } from './validators.js'\n\n/**\n * Internal lifecycle entity for one dispatched worker-thread job.\n *\n * @remarks\n * Owns stable `message` / `messageerror` / death listener identities, settlement, result-guard\n * containment, and abort eviction for one dispatch. Deserialization failure, a matching-id\n * malformed reply, and abort each evict and terminate the thread before rejecting, with\n * termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter is ignored.\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #execution: QueueExecution\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(\n\t\tthread: NodeThread,\n\t\tinput: unknown,\n\t\texecution: QueueExecution,\n\t\tresult: Guard<TResult>,\n\t) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#execution = execution\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#execution.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#execution.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'run', input: this.#input })\n\t\t} catch (error: unknown) {\n\t\t\tthis.#fail(error instanceof Error ? error : new Error(String(error)))\n\t\t}\n\t}\n\n\t#message(value: unknown): void {\n\t\tif (!isRecord(value)) return\n\t\tconst id = attempt(() => value.id)\n\t\tif (!id.success || id.value !== this.#id) return\n\t\tif (!isReply(value, this.#id)) {\n\t\t\tthis.#terminate(new Error('worker reply was malformed'))\n\t\t\treturn\n\t\t}\n\t\tif (value.ok) {\n\t\t\tconst reply = value.value\n\t\t\ttry {\n\t\t\t\tif (this.#result(reply)) this.#succeed(reply)\n\t\t\t\telse this.#fail(new Error('reply did not satisfy result guard'))\n\t\t\t} catch (error: unknown) {\n\t\t\t\tthis.#fail(error)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tthis.#fail(new Error(value.error))\n\t}\n\n\t#messageError(error: Error): void {\n\t\tthis.#terminate(error)\n\t}\n\n\t#error(error: Error): void {\n\t\tthis.#fail(error)\n\t}\n\n\t#exit(): void {\n\t\tthis.#fail(this.#thread.death ?? new Error('worker thread exited'))\n\t}\n\n\t#abort(): void {\n\t\tconst notification: unknown[] = []\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'abort' })\n\t\t} catch (cause: unknown) {\n\t\t\tnotification.push(cause)\n\t\t}\n\t\tthis.#terminate(this.#execution.signal.reason, notification)\n\t}\n\n\t#terminate(error: unknown, notification: readonly unknown[] = []): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tif (this.#thread instanceof Thread) this.#thread.evict()\n\t\tlet termination: Promise<number>\n\t\ttry {\n\t\t\ttermination = this.#worker.terminate()\n\t\t} catch (cause: unknown) {\n\t\t\tthis.#reject(new AggregateError([error, ...notification, cause], 'worker termination failed'))\n\t\t\treturn\n\t\t}\n\t\tvoid termination.then(\n\t\t\t() => {\n\t\t\t\tif (notification.length === 0) this.#reject(error)\n\t\t\t\telse {\n\t\t\t\t\tthis.#reject(\n\t\t\t\t\t\tnew AggregateError([error, ...notification], 'worker abort notification failed'),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\t\t\t(cause: unknown) =>\n\t\t\t\tthis.#reject(\n\t\t\t\t\tnew AggregateError([error, ...notification, cause], 'worker termination failed'),\n\t\t\t\t),\n\t\t)\n\t}\n\n\t#succeed(value: TResult): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#fulfill(value)\n\t}\n\n\t#fail(error: unknown): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#reject(error)\n\t}\n\n\t#detach(): void {\n\t\tthis.#worker.off('message', this.#messageHandler)\n\t\tthis.#worker.off('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.off('error', this.#errorHandler)\n\t\tthis.#worker.off('exit', this.#exitHandler)\n\t\tthis.#execution.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n// === The wire protocol (main ↔ thread)\n//\n// The main-side half of the run/abort/reply protocol `serveWorker` answers — spawning a\n// pooled thread, narrowing its replies, and dispatching one job at a time. The envelope\n// types ({@link Reply}, {@link NodeThread}) live in `./types.js` (AGENTS §5); the public\n// bridge across the structured-clone boundary is the `input` / `result` `Guard`s, which\n// narrow the envelopes' opaque `unknown` payloads with no assertion (AGENTS §14).\n\n/**\n * Spawn one worker thread and resolve a live {@link NodeThread} once it comes online.\n *\n * @remarks\n * Constructs the thread with the `script` module and the cloned `workerData`, then\n * resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`\n * that arrives before `online`, so the spawn promise is total — it can never dangle on a\n * thread that died without erroring). The wrapper attaches persistent `error` / `exit`\n * listeners that flip `alive` to `false` AND latch the first terminal event on\n * {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via\n * its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a\n * dispatch that attaches AFTER the death (via the latch). A `messageerror` is terminal too,\n * so a thread whose inbound payload could not be deserialized is never reused. The latch\n * closes a real race: a thread can become terminal before the readiness promise continuation\n * hands it to `dispatch`, leaving no future death event for that dispatch to observe. Without\n * the latch, that job would wait forever. The pool's `create` hook calls this.\n *\n * @param script - The worker module each thread runs (must call `serveWorker`)\n * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn\n * @returns A promise resolving the online {@link NodeThread}\n */\nexport function spawnThread(script: string | URL, workerData: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.\n *\n * @remarks\n * Mints a fresh `id`, posts a `run` envelope, and resolves when the thread replies for\n * that id: a success `value` is narrowed through `result` (a value that fails the guard\n * rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.\n * A thread that ALREADY died rejects synchronously at entry from the latched\n * {@link NodeThread.death} — its death events fired before this dispatch existed and will\n * never fire again, so waiting on the listeners below would dangle forever; the latch makes\n * death total across every event ordering. If the thread `error`s / `exit`s mid-flight it is\n * marked dead and the\n * job rejects. An inbound `messageerror` also evicts and terminates the thread before\n * rejection. On `execution.signal` abort it contains the cooperative `abort` post,\n * evicts the thread, and observes `terminate()` settlement because CPU-bound work cannot\n * honour the signal; the freed pool slot then gets a fresh thread. Every per-job listener\n * (`message` / `messageerror` / `error` / `exit` / `abort`) is removed on settle.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n * @param thread - The leased thread to run the job on\n * @param input - The work payload (structured-cloned to the thread)\n * @param execution - The per-attempt handle; its `signal` aborts → terminate + evict\n * @param result - The {@link Guard} narrowing the reply value with no assertion\n * @returns A promise resolving the narrowed `TResult`, or rejecting on error / abort\n */\nexport function dispatch<TResult>(\n\tthread: NodeThread,\n\tinput: unknown,\n\texecution: QueueExecution,\n\tresult: Guard<TResult>,\n): Promise<TResult> {\n\treturn new Dispatch(thread, input, execution, result).promise\n}\n","import type { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side entry. SELF-CONTAINED by necessity: this module loads as RAW `.ts`\n// inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports ONLY\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// Its guards are inlined for the same reason. A worker script that needs the cloned\n// `workerData` reads it directly from `node:worker_threads` (it is in a thread already).\n\n// Inlined record guard (do NOT import `isRecord` from `@src/core` — see above). Total:\n// adversarial input returns `false`, never throws (AGENTS §14).\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n// Narrow an inbound message to a `run` envelope (a string `id` + a `'run'` command + an\n// `input` payload) — no assertion.\nfunction isRun(value: unknown): value is { readonly id: string; readonly input: unknown } {\n\treturn (\n\t\tisRecord(value) && typeof value.id === 'string' && value.command === 'run' && 'input' in value\n\t)\n}\n\n// Narrow an inbound message to an `abort` envelope (a string `id` + an `'abort'` command).\nfunction isAbort(value: unknown): value is { readonly id: string } {\n\treturn isRecord(value) && typeof value.id === 'string' && value.command === 'abort'\n}\n\n/**\n * Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or\n * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a\n * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also\n * fails, the parent port closes so the main side observes thread exit instead of waiting forever.\n * Each in-flight job has its own `AbortController`,\n * so an `abort` message for that id fires the handler's `signal` (cooperative — the main\n * side ALSO terminates the thread, so a handler that ignores its signal is still stopped).\n * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread\n * (`parentPort === null`) it is a no-op.\n *\n * @typeParam TInput - The work payload (inferred from `options.input`)\n * @typeParam TResult - The value the handler resolves (the reply payload)\n * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})\n *\n * @example\n * ```ts\n * // double.ts — a worker script\n * import { serveWorker } from '@orkestrel/worker/server'\n *\n * serveWorker<number, number>({\n * \tinput: (value): value is number => typeof value === 'number',\n * \thandler: (value) => value * 2,\n * })\n * ```\n */\nexport function serveWorker<TInput, TResult>(options: ServeWorkerOptions<TInput, TResult>): void {\n\tconst port = parentPort\n\tif (port === null) return\n\tconst input = options.input\n\tconst handler = options.handler\n\tconst controllers = new Map<string, AbortController>()\n\tport.on('message', (raw: unknown) => {\n\t\tif (isAbort(raw)) {\n\t\t\tcontrollers.get(raw.id)?.abort()\n\t\t\treturn\n\t\t}\n\t\tif (!isRun(raw)) return\n\t\tconst id = raw.id\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => {\n\t\t\t\tif (!input(raw.input)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\tconst value = raw.input\n\t\t\t\treturn handler(value, { signal: controller.signal })\n\t\t\t})\n\t\t\t.then((value) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value })\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tlet message = 'worker operation failed'\n\t\t\t\ttry {\n\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error)\n\t\t\t\t} catch {}\n\t\t\t\ttry {\n\t\t\t\t\tport.postMessage({ id, ok: false, error: message })\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tport.close()\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueExecution, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { dispatch, spawnThread } from './helpers.js'\n\n/**\n * Internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity remains the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn spawnThread(this.#script, this.#workerData)\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, execution: QueueExecution): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn dispatch(thread, input, execution, this.#result)\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\n\n/**\n * Create a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is just a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Create a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker THREAD.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (via `spawnThread`),\n * `destroy`s it with `terminate()`, and `validate`s it by `alive && threadId > 0` (so an\n * evicted / crashed thread is dropped and replaced) — and an internal handler that\n * narrows the input through `options.input` (fail-fast before the structured-clone\n * boundary) then `dispatch`es the job to the leased thread, narrowing the reply through\n * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites\n * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards\n * reconstruct `TInput` / `TResult` by validation (AGENTS §14). An `abort` / `timeout`\n * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orkestrel/worker",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "A typed, resource-backed job worker for the @orkestrel line — a Queue paired with a Pool over an execution seam, plus a node:worker_threads server surface for CPU-parallel jobs. Part of the @orkestrel line.",
5
5
  "keywords": [
6
6
  "async",
@@ -77,22 +77,22 @@
77
77
  "prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test"
78
78
  },
79
79
  "dependencies": {
80
- "@orkestrel/contract": "^0.0.8",
81
- "@orkestrel/database": "^0.0.6",
82
- "@orkestrel/emitter": "^0.0.4",
83
- "@orkestrel/pool": "^0.0.4",
84
- "@orkestrel/queue": "^0.0.4"
80
+ "@orkestrel/contract": "^0.0.9",
81
+ "@orkestrel/database": "^0.0.7",
82
+ "@orkestrel/emitter": "^0.0.5",
83
+ "@orkestrel/pool": "^0.0.6",
84
+ "@orkestrel/queue": "^0.0.7"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@microsoft/api-extractor": "^7.58.12",
88
- "@orkestrel/guide": "^0.0.7",
89
- "@orkestrel/scaffold": "^0.0.7",
88
+ "@orkestrel/guide": "^0.0.8",
89
+ "@orkestrel/scaffold": "^0.0.16",
90
90
  "@types/node": "^26.1.2",
91
91
  "@vitest/browser-playwright": "^4.1.10",
92
92
  "oxfmt": "^0.61.0",
93
93
  "oxlint": "^1.76.0",
94
94
  "typescript": "^6.0.3",
95
- "vite": "^8.1.5",
95
+ "vite": "^8.2.0",
96
96
  "vite-plugin-dts": "^5.0.3",
97
97
  "vitest": "^4.1.10"
98
98
  },