@orkestrel/worker 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,9 +3,140 @@ 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";
6
+ //#region src/server/helpers.ts
7
+ /**
8
+ * Narrows an inbound `message` to a {@link Reply} for a given job `id` — no assertion.
9
+ *
10
+ * @remarks
11
+ * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.
12
+ * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.
13
+ * It correlates against the `id` argument rather than narrowing one value alone, so it is a
14
+ * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.
15
+ *
16
+ * @param value - The inbound message to narrow
17
+ * @param id - The job id a matching reply must carry
18
+ * @returns True if the value is this job's well-formed reply; false otherwise
19
+ */
20
+ function isReply(value, id) {
21
+ const outcome = attempt(() => {
22
+ if (!isRecord(value)) return false;
23
+ if (value.id !== id) return false;
24
+ if (value.ok === true) return "value" in value;
25
+ return value.ok === false && typeof value.error === "string";
26
+ });
27
+ return outcome.success && outcome.value;
28
+ }
29
+ //#endregion
30
+ //#region src/server/handlers.ts
31
+ /**
32
+ * Registers a worker-thread handler — the worker-side half of {@link createNodeWorker}.
33
+ *
34
+ * @remarks
35
+ * Must be the spawned thread's module entry. It listens on the parent port for the
36
+ * run/abort protocol: a `run` message narrows its `input` through `options.input` (an
37
+ * invalid payload replies with an error envelope, never running the handler), then runs
38
+ * `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or
39
+ * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a
40
+ * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
41
+ * fails, the parent port closes so the main side observes thread exit instead of waiting forever.
42
+ * The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;
43
+ * its `job` is the stable Queue idempotency key exposed as `context.id` across retries
44
+ * and restore. That job id identifies work, not a caller, and is not authentication or
45
+ * authorization evidence. Each attempt has its own `AbortController`, so an `abort`
46
+ * message for the correlation id fires the handler's `signal` (cooperative — the main
47
+ * side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
48
+ * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
49
+ * (`parentPort === null`) it is a no-op.
50
+ *
51
+ * @typeParam TInput - The work payload (inferred from `options.input`)
52
+ * @typeParam TResult - The value the handler resolves (the reply payload)
53
+ * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * // double.ts — a worker script
58
+ * import { serveWorker } from '@orkestrel/worker/server'
59
+ *
60
+ * serveWorker<number, number>({
61
+ * input: (value): value is number => typeof value === 'number',
62
+ * handler: (value) => value * 2,
63
+ * })
64
+ * ```
65
+ */
66
+ function serveWorker(options) {
67
+ const port = parentPort;
68
+ if (port === null) return;
69
+ const input = options.input;
70
+ const handler = options.handler;
71
+ const controllers = /* @__PURE__ */ new Map();
72
+ port.on("message", (raw) => {
73
+ let command;
74
+ let correlation;
75
+ let job;
76
+ let payload;
77
+ let carried = false;
78
+ try {
79
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
80
+ if ("command" in raw) command = raw.command;
81
+ if ("id" in raw) correlation = raw.id;
82
+ if ("job" in raw) job = raw.job;
83
+ if ("input" in raw) {
84
+ payload = raw.input;
85
+ carried = true;
86
+ }
87
+ }
88
+ } catch {
89
+ return;
90
+ }
91
+ if (typeof correlation !== "string") return;
92
+ const id = correlation;
93
+ if (command === "abort") {
94
+ controllers.get(id)?.abort();
95
+ return;
96
+ }
97
+ if (command !== "run" || typeof job !== "string" || !carried) return;
98
+ const entry = job;
99
+ const value = payload;
100
+ const controller = new AbortController();
101
+ controllers.set(id, controller);
102
+ Promise.resolve().then(() => {
103
+ if (!input(value)) throw new Error("input did not satisfy input guard");
104
+ return handler(value, {
105
+ id: entry,
106
+ signal: controller.signal
107
+ });
108
+ }).then((result) => {
109
+ controllers.delete(id);
110
+ port.postMessage({
111
+ id,
112
+ ok: true,
113
+ value: result
114
+ });
115
+ }).catch((error) => {
116
+ controllers.delete(id);
117
+ let message = "worker operation failed";
118
+ try {
119
+ message = error instanceof Error ? error.message : String(error);
120
+ } catch {}
121
+ try {
122
+ port.postMessage({
123
+ id,
124
+ ok: false,
125
+ error: message
126
+ });
127
+ } catch {
128
+ try {
129
+ port.close();
130
+ } catch {}
131
+ }
132
+ });
133
+ });
134
+ }
135
+ //#endregion
6
136
  //#region src/server/Thread.ts
7
137
  /**
8
- * Internal mutable implementation of the readonly {@link NodeThread} observation contract.
138
+ * Represents the internal mutable implementation of the readonly {@link NodeThread} observation
139
+ * contract.
9
140
  *
10
141
  * @remarks
11
142
  * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,
@@ -82,43 +213,57 @@ var Thread = class {
82
213
  }
83
214
  };
84
215
  //#endregion
85
- //#region src/server/validators.ts
216
+ //#region src/server/Dispatch.ts
86
217
  /**
87
- * Narrow an inbound `message` to a {@link Reply} for a given job `id` no assertion.
218
+ * Represents one dispatched worker-thread job the lifecycle entity behind a job posted to a
219
+ * leased {@link NodeThread}, whose {@link promise} settles with the narrowed reply.
88
220
  *
89
221
  * @remarks
90
- * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.
91
- * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.
222
+ * Mints a fresh per-dispatch correlation `id`, posts it with `job: context.id`, and settles
223
+ * when the thread replies for that correlation id. The stable Queue job id reaches the worker
224
+ * handler for idempotency across retries and restore; it is not caller identity or
225
+ * authentication / authorization evidence. Per-job consumer context is explicit,
226
+ * structured-cloneable `input`; ambient context is not worker-thread transport. A success
227
+ * `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`
228
+ * type bridge); a failure rejects with the thread's error string. A thread that ALREADY died
229
+ * rejects synchronously at construction from the latched {@link NodeThread.death} — its death
230
+ * events fired before this dispatch existed and will never fire again, so waiting on the
231
+ * listeners would dangle forever; the latch makes death total across every event ordering. If
232
+ * the thread `error`s / `exit`s mid-flight the job rejects. On a `context.signal` abort it
233
+ * contains the cooperative `abort` post, evicts the thread, and observes `terminate()`
234
+ * settlement because CPU-bound work cannot honour the signal.
92
235
  *
93
- * @param value - The inbound message to narrow
94
- * @param id - The job id a matching reply must carry
95
- * @returns `true` when the value is this job's well-formed reply
96
- */
97
- function isReply(value, id) {
98
- const outcome = attempt(() => {
99
- if (!isRecord(value)) return false;
100
- if (value.id !== id) return false;
101
- if (value.ok === true) return "value" in value;
102
- return value.ok === false && typeof value.error === "string";
103
- });
104
- return outcome.success && outcome.value;
105
- }
106
- //#endregion
107
- //#region src/server/Dispatch.ts
108
- /**
109
- * Internal lifecycle entity for one dispatched worker-thread job.
236
+ * It owns stable `message` / `messageerror` / death listener identities, settlement,
237
+ * result-guard containment, and abort eviction for one dispatch. Deserialization failure, a
238
+ * matching-id malformed reply, and abort each evict and terminate the thread before rejecting,
239
+ * with termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter
240
+ * is ignored. Every per-job listener (`message` / `messageerror` / `error` / `exit` / `abort`)
241
+ * is removed on settle.
110
242
  *
111
- * @remarks
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.
243
+ * Eviction reaches `alive` for a {@link NodeThread} this package produced. Against a
244
+ * consumer-supplied `NodeThread` an abort or a `messageerror` still terminates the supplied
245
+ * `worker` and rejects the job, and the implementer owns flipping its own `alive`.
246
+ *
247
+ * @typeParam TResult - The reply type the `result` guard narrows to
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * import { createThread, Dispatch } from '@orkestrel/worker/server'
252
+ *
253
+ * const isNumber = (value: unknown): value is number => typeof value === 'number'
254
+ *
255
+ * const thread = await createThread(new URL('./double.js', import.meta.url))
256
+ * const controller = new AbortController()
257
+ * const job = new Dispatch(thread, 21, { id: 'job-1', signal: controller.signal }, isNumber)
258
+ * console.log(await job.promise) // 42
259
+ * await thread.worker.terminate()
260
+ * ```
116
261
  */
117
262
  var Dispatch = class {
118
263
  #thread;
119
264
  #worker;
120
265
  #input;
121
- #execution;
266
+ #context;
122
267
  #result;
123
268
  #id = crypto.randomUUID();
124
269
  #promise;
@@ -130,11 +275,11 @@ var Dispatch = class {
130
275
  #exitHandler;
131
276
  #abortHandler;
132
277
  #settled = false;
133
- constructor(thread, input, execution, result) {
278
+ constructor(thread, input, context, result) {
134
279
  this.#thread = thread;
135
280
  this.#worker = thread.worker;
136
281
  this.#input = input;
137
- this.#execution = execution;
282
+ this.#context = context;
138
283
  this.#result = result;
139
284
  const settlement = Promise.withResolvers();
140
285
  this.#promise = settlement.promise;
@@ -159,15 +304,15 @@ var Dispatch = class {
159
304
  this.#worker.on("messageerror", this.#messageErrorHandler);
160
305
  this.#worker.on("error", this.#errorHandler);
161
306
  this.#worker.on("exit", this.#exitHandler);
162
- if (this.#execution.signal.aborted) {
307
+ if (this.#context.signal.aborted) {
163
308
  this.#abort();
164
309
  return;
165
310
  }
166
- this.#execution.signal.addEventListener("abort", this.#abortHandler, { once: true });
311
+ this.#context.signal.addEventListener("abort", this.#abortHandler, { once: true });
167
312
  try {
168
313
  this.#worker.postMessage({
169
314
  id: this.#id,
170
- job: this.#execution.id,
315
+ job: this.#context.id,
171
316
  command: "run",
172
317
  input: this.#input
173
318
  });
@@ -214,7 +359,7 @@ var Dispatch = class {
214
359
  } catch (cause) {
215
360
  notification.push(cause);
216
361
  }
217
- this.#terminate(this.#execution.signal.reason, notification);
362
+ this.#terminate(this.#context.signal.reason, notification);
218
363
  }
219
364
  #terminate(error, notification = []) {
220
365
  if (this.#settled) return;
@@ -258,183 +403,21 @@ var Dispatch = class {
258
403
  this.#worker.off("messageerror", this.#messageErrorHandler);
259
404
  this.#worker.off("error", this.#errorHandler);
260
405
  this.#worker.off("exit", this.#exitHandler);
261
- this.#execution.signal.removeEventListener("abort", this.#abortHandler);
406
+ this.#context.signal.removeEventListener("abort", this.#abortHandler);
262
407
  }
263
408
  };
264
409
  //#endregion
265
- //#region src/server/helpers.ts
266
- /**
267
- * Spawn one worker thread and resolve a live {@link NodeThread} once it comes online.
268
- *
269
- * @remarks
270
- * Constructs the thread with the `script` module and the cloned `workerData`, then
271
- * resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`
272
- * that arrives before `online`, so the spawn promise is total — it can never dangle on a
273
- * thread that died without erroring). The wrapper attaches persistent `error` / `exit`
274
- * listeners that flip `alive` to `false` AND latch the first terminal event on
275
- * {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via
276
- * its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a
277
- * dispatch that attaches AFTER the death (via the latch). A `messageerror` is terminal too,
278
- * so a thread whose inbound payload could not be deserialized is never reused. The latch
279
- * closes a real race: a thread can become terminal before the readiness promise continuation
280
- * hands it to `dispatch`, leaving no future death event for that dispatch to observe. Without
281
- * the latch, that job would wait forever. The pool's `create` hook calls this.
282
- *
283
- * @param script - The worker module each thread runs (must call `serveWorker`)
284
- * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn
285
- * @returns A promise resolving the online {@link NodeThread}
286
- */
287
- function spawnThread(script, workerData) {
288
- return new Thread(script, workerData).promise;
289
- }
290
- /**
291
- * Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.
292
- *
293
- * @remarks
294
- * Mints a fresh per-dispatch correlation `id`, posts it with `job: execution.id`, and
295
- * resolves when the thread replies for that correlation id. The stable Queue job id reaches
296
- * the worker handler for idempotency across retries and restore; it is not caller identity or
297
- * authentication / authorization evidence. Per-job consumer context remains explicit,
298
- * structured-cloneable `input`; ambient context is not worker-thread transport. A success
299
- * `value` is narrowed through `result` (a value that fails the guard
300
- * rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.
301
- * A thread that ALREADY died rejects synchronously at entry from the latched
302
- * {@link NodeThread.death} — its death events fired before this dispatch existed and will
303
- * never fire again, so waiting on the listeners below would dangle forever; the latch makes
304
- * death total across every event ordering. If the thread `error`s / `exit`s mid-flight it is
305
- * marked dead and the
306
- * job rejects. An inbound `messageerror` also evicts and terminates the thread before
307
- * rejection. On `execution.signal` abort it contains the cooperative `abort` post,
308
- * evicts the thread, and observes `terminate()` settlement because CPU-bound work cannot
309
- * honour the signal; the freed pool slot then gets a fresh thread. Every per-job listener
310
- * (`message` / `messageerror` / `error` / `exit` / `abort`) is removed on settle.
311
- *
312
- * @typeParam TResult - The reply type the `result` guard narrows to
313
- * @param thread - The leased thread to run the job on
314
- * @param input - The work payload (structured-cloned to the thread)
315
- * @param execution - The per-attempt handle; its `signal` aborts → terminate + evict
316
- * @param result - The {@link Guard} narrowing the reply value with no assertion
317
- * @returns A promise resolving the narrowed `TResult`, or rejecting on error / abort
318
- */
319
- function dispatch(thread, input, execution, result) {
320
- return new Dispatch(thread, input, execution, result).promise;
321
- }
322
- //#endregion
323
- //#region src/server/handlers.ts
324
- /**
325
- * Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.
326
- *
327
- * @remarks
328
- * Must be the spawned thread's module entry. It listens on the parent port for the
329
- * run/abort protocol: a `run` message narrows its `input` through `options.input` (an
330
- * invalid payload replies with an error envelope, never running the handler), then runs
331
- * `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or
332
- * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a
333
- * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
334
- * fails, the parent port closes so the main side observes thread exit instead of waiting forever.
335
- * The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;
336
- * its `job` is the stable Queue idempotency key exposed as `execution.id` across retries
337
- * and restore. That job id identifies work, not a caller, and is not authentication or
338
- * authorization evidence. Each attempt has its own `AbortController`, so an `abort`
339
- * message for the correlation id fires the handler's `signal` (cooperative — the main
340
- * side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
341
- * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
342
- * (`parentPort === null`) it is a no-op.
343
- *
344
- * @typeParam TInput - The work payload (inferred from `options.input`)
345
- * @typeParam TResult - The value the handler resolves (the reply payload)
346
- * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})
347
- *
348
- * @example
349
- * ```ts
350
- * // double.ts — a worker script
351
- * import { serveWorker } from '@orkestrel/worker/server'
352
- *
353
- * serveWorker<number, number>({
354
- * input: (value): value is number => typeof value === 'number',
355
- * handler: (value) => value * 2,
356
- * })
357
- * ```
358
- */
359
- function serveWorker(options) {
360
- const port = parentPort;
361
- if (port === null) return;
362
- const input = options.input;
363
- const handler = options.handler;
364
- const controllers = /* @__PURE__ */ new Map();
365
- port.on("message", (raw) => {
366
- let command;
367
- let correlation;
368
- let job;
369
- let payload;
370
- let carried = false;
371
- try {
372
- if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
373
- if ("command" in raw) command = raw.command;
374
- if ("id" in raw) correlation = raw.id;
375
- if ("job" in raw) job = raw.job;
376
- if ("input" in raw) {
377
- payload = raw.input;
378
- carried = true;
379
- }
380
- }
381
- } catch {
382
- return;
383
- }
384
- if (typeof correlation !== "string") return;
385
- const id = correlation;
386
- if (command === "abort") {
387
- controllers.get(id)?.abort();
388
- return;
389
- }
390
- if (command !== "run" || typeof job !== "string" || !carried) return;
391
- const execution = job;
392
- const value = payload;
393
- const controller = new AbortController();
394
- controllers.set(id, controller);
395
- Promise.resolve().then(() => {
396
- if (!input(value)) throw new Error("input did not satisfy input guard");
397
- return handler(value, {
398
- id: execution,
399
- signal: controller.signal
400
- });
401
- }).then((result) => {
402
- controllers.delete(id);
403
- port.postMessage({
404
- id,
405
- ok: true,
406
- value: result
407
- });
408
- }).catch((error) => {
409
- controllers.delete(id);
410
- let message = "worker operation failed";
411
- try {
412
- message = error instanceof Error ? error.message : String(error);
413
- } catch {}
414
- try {
415
- port.postMessage({
416
- id,
417
- ok: false,
418
- error: message
419
- });
420
- } catch {
421
- try {
422
- port.close();
423
- } catch {}
424
- }
425
- });
426
- });
427
- }
428
- //#endregion
429
410
  //#region src/server/NodeWorker.ts
430
411
  /**
431
- * Internal composition entity backing {@link createNodeWorker}.
412
+ * Represents the internal composition entity backing {@link createNodeWorker}.
432
413
  *
433
414
  * @remarks
434
415
  * Supplies bound Pool and Queue operations without nested function assignments. The resulting
435
- * public entity remains the plain core {@link WorkerInterface}.
416
+ * public entity is the plain core {@link WorkerInterface}.
436
417
  */
437
418
  var NodeWorker = class {
419
+ #on;
420
+ #error;
438
421
  #script;
439
422
  #input;
440
423
  #result;
@@ -444,6 +427,8 @@ var NodeWorker = class {
444
427
  #timeout;
445
428
  #store;
446
429
  constructor(options) {
430
+ this.#on = options.on;
431
+ this.#error = options.error;
447
432
  this.#script = options.script;
448
433
  this.#input = options.input;
449
434
  this.#result = options.result;
@@ -462,6 +447,8 @@ var NodeWorker = class {
462
447
  ...this.#concurrency !== void 0 ? { max: this.#concurrency } : {}
463
448
  },
464
449
  handler: this.#handle.bind(this),
450
+ ...this.#on !== void 0 ? { on: this.#on } : {},
451
+ ...this.#error !== void 0 ? { error: this.#error } : {},
465
452
  ...this.#concurrency !== void 0 ? { concurrency: this.#concurrency } : {},
466
453
  ...this.#retries !== void 0 ? { retries: this.#retries } : {},
467
454
  ...this.#timeout !== void 0 ? { timeout: this.#timeout } : {},
@@ -469,7 +456,7 @@ var NodeWorker = class {
469
456
  });
470
457
  }
471
458
  #create() {
472
- return spawnThread(this.#script, this.#workerData);
459
+ return new Thread(this.#script, this.#workerData).promise;
473
460
  }
474
461
  async #destroy(thread) {
475
462
  await thread.worker.terminate();
@@ -477,21 +464,55 @@ var NodeWorker = class {
477
464
  #validate(thread) {
478
465
  return thread.alive && thread.worker.threadId > 0;
479
466
  }
480
- #handle(input, thread, execution) {
467
+ #handle(input, thread, context) {
481
468
  const outcome = attempt(() => this.#input(input));
482
469
  if (!outcome.success) return Promise.reject(outcome.error);
483
470
  if (!outcome.value) return Promise.reject(/* @__PURE__ */ new Error("input did not satisfy input guard"));
484
- return dispatch(thread, input, execution, this.#result);
471
+ return new Dispatch(thread, input, context, this.#result).promise;
485
472
  }
486
473
  };
487
474
  //#endregion
488
475
  //#region src/server/factories.ts
489
476
  /**
490
- * Create a persistent JSON-file {@link QueueStoreInterface} the core
477
+ * Creates one live worker thread and resolves it as a {@link NodeThread} after it comes
478
+ * online.
479
+ *
480
+ * @remarks
481
+ * Constructs the thread with the `script` module and the cloned `workerData`, then
482
+ * resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`
483
+ * that arrives before `online`, so the spawn promise is total — it can never dangle on a
484
+ * thread that died without erroring). The returned entity attaches persistent `error` /
485
+ * `exit` listeners that flip `alive` to `false` AND latch the first terminal event on
486
+ * {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through
487
+ * its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a
488
+ * dispatch that attaches AFTER the death (through the latch). A `messageerror` is terminal
489
+ * too, so a thread whose inbound payload could not be deserialized is never reused. The latch
490
+ * closes a real race: a thread can become terminal before the readiness promise continuation
491
+ * hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.
492
+ * Without the latch, that job would wait forever. {@link createNodeWorker} spawns its pooled
493
+ * threads the same way; reach for this to drive one thread yourself.
494
+ *
495
+ * @param script - The worker module the thread runs (its module must call `serveWorker`)
496
+ * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn
497
+ * @returns A promise resolving the online {@link NodeThread}
498
+ *
499
+ * @example
500
+ * ```ts
501
+ * import { createThread } from '@orkestrel/worker/server'
502
+ *
503
+ * const thread = await createThread(new URL('./double.js', import.meta.url))
504
+ * await thread.worker.terminate()
505
+ * ```
506
+ */
507
+ function createThread(script, workerData) {
508
+ return new Thread(script, workerData).promise;
509
+ }
510
+ /**
511
+ * Creates a persistent JSON-file {@link QueueStoreInterface} — the core
491
512
  * `createDatabaseQueueStore` over a server {@link createJSONDriver}.
492
513
  *
493
514
  * @remarks
494
- * A queue's durable state is just a database table, so JSON persistence reuses the
515
+ * A queue's durable state is a database table, so JSON persistence reuses the
495
516
  * existing JSON-file driver rather than a bespoke store: the entries are written to
496
517
  * (and reloaded from) the file at `path`, surviving a process restart. There is no new
497
518
  * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the
@@ -520,20 +541,21 @@ function createJSONQueueStore(path, input) {
520
541
  return createDatabaseQueueStore(input, createJSONDriver(path));
521
542
  }
522
543
  /**
523
- * Create a CPU-parallel worker over `node:worker_threads` — a thin specialization of the
544
+ * Creates a CPU-parallel worker over `node:worker_threads` — a thin specialization of the
524
545
  * core `createWorker` whose pooled resource is a worker THREAD.
525
546
  *
526
547
  * @remarks
527
548
  * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,
528
549
  * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory
529
- * supplies only the thread pairing — the pool `create`s a thread (via `spawnThread`),
530
- * `destroy`s it with `terminate()`, and `validate`s it by `alive && threadId > 0` (so an
531
- * evicted / crashed thread is dropped and replaced) — and an internal handler that
532
- * narrows the input through `options.input` (fail-fast before the structured-clone
533
- * boundary) then `dispatch`es the job to the leased thread, narrowing the reply through
550
+ * supplies only the thread pairing — the pool `create`s a thread (the same spawn
551
+ * {@link createThread} publishes), `destroy`s it with `terminate()`, and `validate`s it by
552
+ * `alive && threadId > 0` (so an evicted / crashed thread is dropped and replaced) — and an
553
+ * internal handler that narrows the input through `options.input` (fail-fast before the
554
+ * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,
555
+ * narrowing the reply through
534
556
  * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites
535
557
  * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards
536
- * reconstruct `TInput` / `TResult` by validation (AGENTS §14). An `abort` / `timeout`
558
+ * reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`
537
559
  * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a
538
560
  * subsequent job spawns a fresh thread. The worker script's module must call
539
561
  * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.
@@ -541,7 +563,7 @@ function createJSONQueueStore(path, input) {
541
563
  * @typeParam TInput - The work payload each job carries (inferred from `input`)
542
564
  * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)
543
565
  * @param options - The `script` plus the `input` / `result` guards and optional
544
- * `workerData` / `concurrency` / `retries` / `timeout` / `store`
566
+ * `on` / `error` / `workerData` / `concurrency` / `retries` / `timeout` / `store`
545
567
  * (see {@link NodeWorkerOptions})
546
568
  * @returns A working {@link WorkerInterface} backed by a thread pool
547
569
  *
@@ -564,6 +586,6 @@ function createNodeWorker(options) {
564
586
  return new NodeWorker(options).build();
565
587
  }
566
588
  //#endregion
567
- export { createJSONQueueStore, createNodeWorker, dispatch, isReply, serveWorker, spawnThread };
589
+ export { Dispatch, createJSONQueueStore, createNodeWorker, createThread, isReply, serveWorker };
568
590
 
569
591
  //# sourceMappingURL=index.js.map