@orkestrel/worker 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["#worker","#promise","#resolve","#reject","#recordHandler","#recordExitHandler","#onlineHandler","#spawnErrorHandler","#spawnExitHandler","#record","#recordExit","#online","#spawnError","#spawnExit","#alive","#death","#thread","#worker","#input","#context","#result","#id","#promise","#fulfill","#reject","#messageHandler","#messageErrorHandler","#errorHandler","#exitHandler","#abortHandler","#message","#messageError","#error","#exit","#abort","#start","#fail","#terminate","#succeed","#settled","#detach","#on","#error","#script","#input","#result","#workerData","#concurrency","#retries","#timeout","#store","#create","#destroy","#validate","#handle"],"sources":["../../../src/server/helpers.ts","../../../src/server/handlers.ts","../../../src/server/Thread.ts","../../../src/server/Dispatch.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { Reply } from './types.js'\nimport { attempt, isRecord } from '@orkestrel/contract'\n\n// === The wire protocol (main ↔ thread)\n//\n// The reply half of the run/abort/reply protocol `serveWorker` answers — the leaf predicate\n// a `Dispatch` filters inbound messages with. The envelope types ({@link Reply},\n// `NodeThread`) live in `./types.js`; the public bridge across the\n// structured-clone boundary is the `input` / `result` `Guard`s, which narrow the envelopes'\n// opaque `unknown` payloads with no assertion. This file imports no\n// implementation class, so it stays the bottom of the module's graph.\n\n/**\n * Narrows 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 * It correlates against the `id` argument rather than narrowing one value alone, so it is a\n * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.\n *\n * @param value - The inbound message to narrow\n * @param id - The job id a matching reply must carry\n * @returns True if the value is this job's well-formed reply; false otherwise\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 { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side request handler. SELF-CONTAINED by necessity: this module loads as RAW\n// `.ts` 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// The inbound envelope is therefore narrowed inline rather than through a sibling guard in\n// `helpers.ts`, which would be a runtime import this module cannot make. A worker script\n// that needs the cloned `workerData` reads it directly from `node:worker_threads` (it is in\n// a thread already).\n\n/**\n * Registers 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, { id: job, 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 * The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;\n * its `job` is the stable Queue idempotency key exposed as `context.id` across retries\n * and restore. That job id identifies work, not a caller, and is not authentication or\n * authorization evidence. Each attempt has its own `AbortController`, so an `abort`\n * message for the correlation 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\t// Read the envelope's `command`, `id`, `job`, and `input` fields once, defensively. A\n\t\t// hostile message can be a revoked\n\t\t// proxy or carry a throwing getter, so every property access sits inside this one guard:\n\t\t// a read that throws leaves the envelope unrecognised and the message is dropped without\n\t\t// a reply, exactly as a malformed envelope is.\n\t\tlet command: unknown\n\t\tlet correlation: unknown\n\t\tlet job: unknown\n\t\tlet payload: unknown\n\t\tlet carried = false\n\t\ttry {\n\t\t\tif (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n\t\t\t\tif ('command' in raw) command = raw.command\n\t\t\t\tif ('id' in raw) correlation = raw.id\n\t\t\t\tif ('job' in raw) job = raw.job\n\t\t\t\tif ('input' in raw) {\n\t\t\t\t\tpayload = raw.input\n\t\t\t\t\tcarried = true\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tif (typeof correlation !== 'string') return\n\t\tconst id = correlation\n\t\tif (command === 'abort') {\n\t\t\tcontrollers.get(id)?.abort()\n\t\t\treturn\n\t\t}\n\t\t// A `run` envelope carries BOTH ids: `id` is the per-dispatch correlation, `job` the\n\t\t// stable Queue entry id handed to the handler. A malformed envelope\n\t\t// without a string `job`, or without an `input` at all, fails closed with no reply.\n\t\tif (command !== 'run' || typeof job !== 'string' || !carried) return\n\t\tconst entry = job\n\t\tconst value = payload\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(value)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\treturn handler(value, { id: entry, signal: controller.signal })\n\t\t\t})\n\t\t\t.then((result) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value: result })\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 { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Represents the internal mutable implementation of the readonly {@link NodeThread} observation\n * 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 { QueueContext } 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 { isReply } from './helpers.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents one dispatched worker-thread job — the lifecycle entity behind a job posted to a\n * leased {@link NodeThread}, whose {@link promise} settles with the narrowed reply.\n *\n * @remarks\n * Mints a fresh per-dispatch correlation `id`, posts it with `job: context.id`, and settles\n * when the thread replies for that correlation id. The stable Queue job id reaches the worker\n * handler for idempotency across retries and restore; it is not caller identity or\n * authentication / authorization evidence. Per-job consumer context is explicit,\n * structured-cloneable `input`; ambient context is not worker-thread transport. A success\n * `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`\n * type bridge); a failure rejects with the thread's error string. A thread that ALREADY died\n * rejects synchronously at construction from the latched {@link NodeThread.death} — its death\n * events fired before this dispatch existed and will never fire again, so waiting on the\n * listeners would dangle forever; the latch makes death total across every event ordering. If\n * the thread `error`s / `exit`s mid-flight the job rejects. On a `context.signal` abort it\n * contains the cooperative `abort` post, evicts the thread, and observes `terminate()`\n * settlement because CPU-bound work cannot honour the signal.\n *\n * It owns stable `message` / `messageerror` / death listener identities, settlement,\n * result-guard containment, and abort eviction for one dispatch. Deserialization failure, a\n * matching-id malformed reply, and abort each evict and terminate the thread before rejecting,\n * with termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter\n * is ignored. Every per-job listener (`message` / `messageerror` / `error` / `exit` / `abort`)\n * is removed on settle.\n *\n * Eviction reaches `alive` for a {@link NodeThread} this package produced. Against a\n * consumer-supplied `NodeThread` an abort or a `messageerror` still terminates the supplied\n * `worker` and rejects the job, and the implementer owns flipping its own `alive`.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n *\n * @example\n * ```ts\n * import { createThread, Dispatch } from '@orkestrel/worker/server'\n *\n * const isNumber = (value: unknown): value is number => typeof value === 'number'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * const controller = new AbortController()\n * const job = new Dispatch(thread, 21, { id: 'job-1', signal: controller.signal }, isNumber)\n * console.log(await job.promise) // 42\n * await thread.worker.terminate()\n * ```\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #context: QueueContext\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(thread: NodeThread, input: unknown, context: QueueContext, result: Guard<TResult>) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#context = context\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.#context.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#context.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({\n\t\t\t\tid: this.#id,\n\t\t\t\tjob: this.#context.id,\n\t\t\t\tcommand: 'run',\n\t\t\t\tinput: this.#input,\n\t\t\t})\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.#context.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.#context.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { EmitterErrorHandler, EmitterHooks } from '@orkestrel/emitter'\nimport type { WorkerEventMap, WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueContext, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents the 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 is the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #on: EmitterHooks<WorkerEventMap<TResult>> | undefined\n\treadonly #error: EmitterErrorHandler | undefined\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.#on = options.on\n\t\tthis.#error = options.error\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.#on !== undefined ? { on: this.#on } : {}),\n\t\t\t...(this.#error !== undefined ? { error: this.#error } : {}),\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 new Thread(this.#script, this.#workerData).promise\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, context: QueueContext): 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 new Dispatch(thread, input, context, this.#result).promise\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 { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Creates one live worker thread and resolves it as a {@link NodeThread} after it comes\n * 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 returned entity attaches persistent `error` /\n * `exit` 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} (through\n * its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a\n * dispatch that attaches AFTER the death (through the latch). A `messageerror` is terminal\n * too, 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 a {@link Dispatch}, leaving no future death event for that dispatch to observe.\n * Without the latch, that job would wait forever. {@link createNodeWorker} spawns its pooled\n * threads the same way; reach for this to drive one thread yourself.\n *\n * @param script - The worker module the thread runs (its module 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 *\n * @example\n * ```ts\n * import { createThread } from '@orkestrel/worker/server'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * await thread.worker.terminate()\n * ```\n */\nexport function createThread(script: string | URL, workerData?: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Creates 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 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 * Creates 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 (the same spawn\n * {@link createThread} publishes), `destroy`s it with `terminate()`, and `validate`s it by\n * `alive && threadId > 0` (so an evicted / crashed thread is dropped and replaced) — and an\n * internal handler that narrows the input through `options.input` (fail-fast before the\n * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,\n * 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. 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 * `on` / `error` / `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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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/server/helpers.ts","../../../src/server/handlers.ts","../../../src/server/Thread.ts","../../../src/server/Dispatch.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { Reply } from './types.js'\nimport { attempt, isRecord } from '@orkestrel/contract'\n\n// === The wire protocol (main ↔ thread)\n//\n// The reply half of the run/abort/reply protocol `serveWorker` answers — the leaf predicate\n// a `Dispatch` filters inbound messages with. The envelope types ({@link Reply},\n// `NodeThread`) live in `./types.js`; the public bridge across the\n// structured-clone boundary is the `input` / `result` `Guard`s, which narrow the envelopes'\n// opaque `unknown` payloads with no assertion. This file imports no\n// implementation class, so it stays the bottom of the module's graph.\n\n/**\n * Narrows an inbound `message` to a {@link Reply} for a given correlation `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 * It correlates against the `id` argument rather than narrowing one value alone, so it is a\n * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.\n *\n * @param value - The inbound message to narrow\n * @param id - The per-dispatch correlation id a matching reply must carry\n * @returns True if the value is this dispatch's well-formed reply; false otherwise\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 { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side request handler. It is self-contained by necessity: this module loads as\n// raw `.ts` 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// The inbound envelope is therefore narrowed inline rather than through a sibling guard in\n// `helpers.ts`, which would be a runtime import this module cannot make. A worker script\n// that needs the cloned `workerData` reads it directly from `node:worker_threads` (it is in\n// a thread already).\n\n/**\n * Registers 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, { id: job, 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 * The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;\n * its `job` is the stable Queue idempotency key exposed as `context.id` across retries\n * and restore. That job id identifies work, not a caller, and is not authentication or\n * authorization evidence. Each attempt has its own `AbortController`, so an `abort`\n * message for the correlation 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\t// Read the envelope's `command`, `id`, `job`, and `input` fields once, defensively. A\n\t\t// hostile message can be a revoked\n\t\t// proxy or carry a throwing getter, so every property access sits inside this one guard:\n\t\t// a read that throws leaves the envelope unrecognised and the message is dropped without\n\t\t// a reply, exactly as a malformed envelope is.\n\t\tlet command: unknown\n\t\tlet correlation: unknown\n\t\tlet job: unknown\n\t\tlet payload: unknown\n\t\tlet carried = false\n\t\ttry {\n\t\t\tif (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n\t\t\t\tif ('command' in raw) command = raw.command\n\t\t\t\tif ('id' in raw) correlation = raw.id\n\t\t\t\tif ('job' in raw) job = raw.job\n\t\t\t\tif ('input' in raw) {\n\t\t\t\t\tpayload = raw.input\n\t\t\t\t\tcarried = true\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tif (typeof correlation !== 'string') return\n\t\tconst id = correlation\n\t\tif (command === 'abort') {\n\t\t\tcontrollers.get(id)?.abort()\n\t\t\treturn\n\t\t}\n\t\t// A `run` envelope carries both ids: `id` is the per-dispatch correlation, `job` the\n\t\t// stable Queue entry id handed to the handler. A malformed envelope\n\t\t// without a string `job`, or without an `input` at all, fails closed with no reply.\n\t\tif (command !== 'run' || typeof job !== 'string' || !carried) return\n\t\tconst entry = job\n\t\tconst value = payload\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(value)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\treturn handler(value, { id: entry, signal: controller.signal })\n\t\t\t})\n\t\t\t.then((result) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value: result })\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 { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Represents the internal mutable implementation of the readonly {@link NodeThread} observation\n * 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 { QueueContext } 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 { isReply } from './helpers.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents one dispatched worker-thread job — the lifecycle entity behind a job posted to a\n * leased {@link NodeThread}, whose {@link promise} settles with the narrowed reply.\n *\n * @remarks\n * Mints a fresh per-dispatch correlation `id`, posts it with `job: context.id`, and settles\n * when the thread replies for that correlation id. The stable Queue job id reaches the worker\n * handler for idempotency across retries and restore; it is not caller identity or\n * authentication / authorization evidence. Per-job consumer context is explicit,\n * structured-cloneable `input`; ambient context is not worker-thread transport. A success\n * `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`\n * type bridge); a failure rejects with the thread's error string. A thread that had already died\n * rejects synchronously at construction from the latched {@link NodeThread.death} — its death\n * events fired before this dispatch existed and will never fire again, so waiting on the\n * listeners would dangle forever; the latch makes death total across every event ordering. If\n * the thread `error`s / `exit`s mid-flight the job rejects. On a `context.signal` abort it\n * contains the cooperative `abort` post, evicts the thread, and observes `terminate()`\n * settlement because CPU-bound work cannot honour the signal.\n *\n * It owns stable `message` / `messageerror` / death listener identities, settlement,\n * result-guard containment, and abort eviction for one dispatch. Deserialization failure, a\n * matching-id malformed reply, and abort each evict and terminate the thread before rejecting,\n * with termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter\n * is ignored. Every per-job listener (`message` / `messageerror` / `error` / `exit` / `abort`)\n * is removed on settle.\n *\n * Eviction reaches `alive` for a {@link NodeThread} this package produced. Against a\n * consumer-supplied `NodeThread` an abort or a `messageerror` still terminates the supplied\n * `worker` and rejects the job, and the implementer owns flipping its own `alive`.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n *\n * @example\n * ```ts\n * import { createThread, Dispatch } from '@orkestrel/worker/server'\n *\n * const isNumber = (value: unknown): value is number => typeof value === 'number'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * const controller = new AbortController()\n * const job = new Dispatch(thread, 21, { id: 'job-1', signal: controller.signal }, isNumber)\n * console.log(await job.promise) // 42\n * await thread.worker.terminate()\n * ```\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #context: QueueContext\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(thread: NodeThread, input: unknown, context: QueueContext, result: Guard<TResult>) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#context = context\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.#context.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#context.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({\n\t\t\t\tid: this.#id,\n\t\t\t\tjob: this.#context.id,\n\t\t\t\tcommand: 'run',\n\t\t\t\tinput: this.#input,\n\t\t\t})\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.#context.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.#context.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { EmitterErrorHandler, EmitterHooks } from '@orkestrel/emitter'\nimport type { WorkerEventMap, WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueContext, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents the 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 is the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #on: EmitterHooks<WorkerEventMap<TResult>> | undefined\n\treadonly #error: EmitterErrorHandler | undefined\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.#on = options.on\n\t\tthis.#error = options.error\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.#on !== undefined ? { on: this.#on } : {}),\n\t\t\t...(this.#error !== undefined ? { error: this.#error } : {}),\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 new Thread(this.#script, this.#workerData).promise\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, context: QueueContext): 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 new Dispatch(thread, input, context, this.#result).promise\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 { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Creates one live worker thread and resolves it as a {@link NodeThread} after it comes\n * 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 on 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 returned entity attaches persistent `error` /\n * `exit` 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} (through\n * its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a\n * dispatch that attaches only after the death (through the latch). A `messageerror` is terminal\n * too, 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 a {@link Dispatch}, leaving no future death event for that dispatch to observe.\n * Without the latch, that job would wait forever. {@link createNodeWorker} spawns its pooled\n * threads the same way; reach for this to drive one thread yourself.\n *\n * @param script - The worker module the thread runs (its module 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 *\n * @example\n * ```ts\n * import { createThread } from '@orkestrel/worker/server'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * await thread.worker.terminate()\n * ```\n */\nexport function createThread(script: string | URL, workerData?: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Creates 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 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 * Creates 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 (the same spawn\n * {@link createThread} publishes), `destroy`s it with `terminate()`, and `validate`s it by\n * `alive && threadId > 0` (so an evicted / crashed thread is dropped and replaced) — and an\n * internal handler that narrows the input through `options.input` (fail-fast before the\n * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,\n * narrowing the reply through\n * `options.result`. `TInput` and `TResult` infer from the `input` and `result` guards, so\n * call sites need no explicit type arguments. The boundary is crossed with no `as`: the\n * guards reconstruct `TInput` / `TResult` by validation. 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 * `on` / `error` / `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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"}
@@ -1,13 +1,13 @@
1
- import { ContractShape } from '@orkestrel/contract';
2
- import { EmitterErrorHandler } from '@orkestrel/emitter';
3
- import { EmitterHooks } from '@orkestrel/emitter';
4
- import { Guard } from '@orkestrel/contract';
5
- import { Infer } from '@orkestrel/contract';
6
- import { QueueContext } from '@orkestrel/queue';
7
- import { QueueStoreInterface } from '@orkestrel/queue';
8
- import { Worker } from 'node:worker_threads';
9
- import { WorkerEventMap } from '@orkestrel/worker';
10
- import { WorkerInterface } from '@orkestrel/worker';
1
+ import type { ContractShape } from '@orkestrel/contract';
2
+ import type { EmitterErrorHandler } from '@orkestrel/emitter';
3
+ import type { EmitterHooks } from '@orkestrel/emitter';
4
+ import type { Guard } from '@orkestrel/contract';
5
+ import type { Infer } from '@orkestrel/contract';
6
+ import type { QueueContext } from '@orkestrel/queue';
7
+ import type { QueueStoreInterface } from '@orkestrel/queue';
8
+ import type { Worker } from 'node:worker_threads';
9
+ import type { WorkerEventMap } from '@orkestrel/worker';
10
+ import type { WorkerInterface } from '@orkestrel/worker';
11
11
 
12
12
  /**
13
13
  * Creates a persistent JSON-file {@link QueueStoreInterface} — the core
@@ -19,7 +19,7 @@ import { WorkerInterface } from '@orkestrel/worker';
19
19
  * (and reloaded from) the file at `path`, surviving a process restart. There is no new
20
20
  * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the
21
21
  * driver changes where the bytes live. The `input` shape must be JSON-serializable
22
- * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to
22
+ * (the JSON driver round-trips it as JSON). Build a second store over the same `path` to
23
23
  * resume the outstanding entries a prior store persisted.
24
24
  *
25
25
  * @typeParam TInput - The contract shape of each entry's `input` payload
@@ -43,7 +43,7 @@ export declare function createJSONQueueStore<TInput extends ContractShape>(path:
43
43
 
44
44
  /**
45
45
  * Creates a CPU-parallel worker over `node:worker_threads` — a thin specialization of the
46
- * core `createWorker` whose pooled resource is a worker THREAD.
46
+ * core `createWorker` whose pooled resource is a worker thread.
47
47
  *
48
48
  * @remarks
49
49
  * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,
@@ -54,10 +54,10 @@ export declare function createJSONQueueStore<TInput extends ContractShape>(path:
54
54
  * internal handler that narrows the input through `options.input` (fail-fast before the
55
55
  * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,
56
56
  * narrowing the reply through
57
- * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites
58
- * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards
59
- * reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`
60
- * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a
57
+ * `options.result`. `TInput` and `TResult` infer from the `input` and `result` guards, so
58
+ * call sites need no explicit type arguments. The boundary is crossed with no `as`: the
59
+ * guards reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`
60
+ * terminates the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a
61
61
  * subsequent job spawns a fresh thread. The worker script's module must call
62
62
  * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.
63
63
  *
@@ -91,13 +91,13 @@ export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOpt
91
91
  *
92
92
  * @remarks
93
93
  * Constructs the thread with the `script` module and the cloned `workerData`, then
94
- * resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`
94
+ * resolves on the thread's `online` event (rejecting on an early `error` or on an `exit`
95
95
  * that arrives before `online`, so the spawn promise is total — it can never dangle on a
96
96
  * thread that died without erroring). The returned entity attaches persistent `error` /
97
- * `exit` listeners that flip `alive` to `false` AND latch the first terminal event on
97
+ * `exit` listeners that flip `alive` to `false` and latch the first terminal event on
98
98
  * {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through
99
99
  * its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a
100
- * dispatch that attaches AFTER the death (through the latch). A `messageerror` is terminal
100
+ * dispatch that attaches only after the death (through the latch). A `messageerror` is terminal
101
101
  * too, so a thread whose inbound payload could not be deserialized is never reused. The latch
102
102
  * closes a real race: a thread can become terminal before the readiness promise continuation
103
103
  * hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.
@@ -129,7 +129,7 @@ export declare function createThread(script: string | URL, workerData?: unknown)
129
129
  * authentication / authorization evidence. Per-job consumer context is explicit,
130
130
  * structured-cloneable `input`; ambient context is not worker-thread transport. A success
131
131
  * `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`
132
- * type bridge); a failure rejects with the thread's error string. A thread that ALREADY died
132
+ * type bridge); a failure rejects with the thread's error string. A thread that had already died
133
133
  * rejects synchronously at construction from the latched {@link NodeThread.death} — its death
134
134
  * events fired before this dispatch existed and will never fire again, so waiting on the
135
135
  * listeners would dangle forever; the latch makes death total across every event ordering. If
@@ -170,7 +170,7 @@ export declare class Dispatch<TResult> {
170
170
  }
171
171
 
172
172
  /**
173
- * Narrows an inbound `message` to a {@link Reply} for a given job `id` — no assertion.
173
+ * Narrows an inbound `message` to a {@link Reply} for a given correlation `id` — no assertion.
174
174
  *
175
175
  * @remarks
176
176
  * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.
@@ -179,8 +179,8 @@ export declare class Dispatch<TResult> {
179
179
  * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.
180
180
  *
181
181
  * @param value - The inbound message to narrow
182
- * @param id - The job id a matching reply must carry
183
- * @returns True if the value is this job's well-formed reply; false otherwise
182
+ * @param id - The per-dispatch correlation id a matching reply must carry
183
+ * @returns True if the value is this dispatch's well-formed reply; false otherwise
184
184
  */
185
185
  export declare function isReply(value: unknown, id: string): value is Reply;
186
186
 
@@ -192,9 +192,9 @@ export declare function isReply(value: unknown, id: string): value is Reply;
192
192
  * `alive` starts `true` and flips to `false` when the thread `error`s, reports a
193
193
  * `messageerror`, exits, or is evicted on abort; the pool's `validate` reads
194
194
  * `alive && worker.threadId > 0`, so a
195
- * dead thread is destroyed and replaced rather than reused. `death` LATCHES the first
195
+ * dead thread is destroyed and replaced rather than reused. `death` latches the first
196
196
  * terminal event (`error` / `messageerror`, or a synthesized error on `exit`) — the death-signal
197
- * record a {@link Dispatch} checks at construction, so a job dispatched AFTER the thread died (its
197
+ * record a {@link Dispatch} checks at construction, so a job dispatched after the thread died (its
198
198
  * death events already fired and will never fire again) rejects immediately instead of
199
199
  * awaiting events that already happened. A thread can become terminal before the readiness
200
200
  * promise continuation attaches dispatch listeners; the latch is what makes that ordering
@@ -221,7 +221,7 @@ export declare interface NodeThread {
221
221
  * `serveWorker(...)`. Raw TypeScript is unflagged on Node 22.18+ and Node 23.6+;
222
222
  * Node 22.12–22.17 and Node 23.0–23.5 require `--experimental-strip-types`. A built
223
223
  * `.js` / `.mjs` script is an alternative across supported Node versions.
224
- * - `input` — narrows the work payload BEFORE it crosses the structured-clone boundary
224
+ * - `input` — narrows the work payload before it crosses the structured-clone boundary
225
225
  * (fail-fast) and supplies the `TInput` inference, so call sites need no type argument.
226
226
  * - `result` — narrows every reply value coming back from a thread; an invalid reply
227
227
  * rejects the job. This is the zero-`as` type bridge — `TResult` is inferred from it.
@@ -297,7 +297,7 @@ export declare type Reply = {
297
297
  * and restore. That job id identifies work, not a caller, and is not authentication or
298
298
  * authorization evidence. Each attempt has its own `AbortController`, so an `abort`
299
299
  * message for the correlation id fires the handler's `signal` (cooperative — the main
300
- * side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
300
+ * side also terminates the thread, so a handler that ignores its signal is still stopped).
301
301
  * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
302
302
  * (`parentPort === null`) it is a no-op.
303
303
  *
@@ -1,13 +1,13 @@
1
- import { ContractShape } from '@orkestrel/contract';
2
- import { EmitterErrorHandler } from '@orkestrel/emitter';
3
- import { EmitterHooks } from '@orkestrel/emitter';
4
- import { Guard } from '@orkestrel/contract';
5
- import { Infer } from '@orkestrel/contract';
6
- import { QueueContext } from '@orkestrel/queue';
7
- import { QueueStoreInterface } from '@orkestrel/queue';
8
- import { Worker } from 'node:worker_threads';
9
- import { WorkerEventMap } from '@orkestrel/worker';
10
- import { WorkerInterface } from '@orkestrel/worker';
1
+ import type { ContractShape } from '@orkestrel/contract';
2
+ import type { EmitterErrorHandler } from '@orkestrel/emitter';
3
+ import type { EmitterHooks } from '@orkestrel/emitter';
4
+ import type { Guard } from '@orkestrel/contract';
5
+ import type { Infer } from '@orkestrel/contract';
6
+ import type { QueueContext } from '@orkestrel/queue';
7
+ import type { QueueStoreInterface } from '@orkestrel/queue';
8
+ import type { Worker } from 'node:worker_threads';
9
+ import type { WorkerEventMap } from '@orkestrel/worker';
10
+ import type { WorkerInterface } from '@orkestrel/worker';
11
11
 
12
12
  /**
13
13
  * Creates a persistent JSON-file {@link QueueStoreInterface} — the core
@@ -19,7 +19,7 @@ import { WorkerInterface } from '@orkestrel/worker';
19
19
  * (and reloaded from) the file at `path`, surviving a process restart. There is no new
20
20
  * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the
21
21
  * driver changes where the bytes live. The `input` shape must be JSON-serializable
22
- * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to
22
+ * (the JSON driver round-trips it as JSON). Build a second store over the same `path` to
23
23
  * resume the outstanding entries a prior store persisted.
24
24
  *
25
25
  * @typeParam TInput - The contract shape of each entry's `input` payload
@@ -43,7 +43,7 @@ export declare function createJSONQueueStore<TInput extends ContractShape>(path:
43
43
 
44
44
  /**
45
45
  * Creates a CPU-parallel worker over `node:worker_threads` — a thin specialization of the
46
- * core `createWorker` whose pooled resource is a worker THREAD.
46
+ * core `createWorker` whose pooled resource is a worker thread.
47
47
  *
48
48
  * @remarks
49
49
  * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,
@@ -54,10 +54,10 @@ export declare function createJSONQueueStore<TInput extends ContractShape>(path:
54
54
  * internal handler that narrows the input through `options.input` (fail-fast before the
55
55
  * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,
56
56
  * narrowing the reply through
57
- * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites
58
- * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards
59
- * reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`
60
- * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a
57
+ * `options.result`. `TInput` and `TResult` infer from the `input` and `result` guards, so
58
+ * call sites need no explicit type arguments. The boundary is crossed with no `as`: the
59
+ * guards reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`
60
+ * terminates the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a
61
61
  * subsequent job spawns a fresh thread. The worker script's module must call
62
62
  * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.
63
63
  *
@@ -91,13 +91,13 @@ export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOpt
91
91
  *
92
92
  * @remarks
93
93
  * Constructs the thread with the `script` module and the cloned `workerData`, then
94
- * resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`
94
+ * resolves on the thread's `online` event (rejecting on an early `error` or on an `exit`
95
95
  * that arrives before `online`, so the spawn promise is total — it can never dangle on a
96
96
  * thread that died without erroring). The returned entity attaches persistent `error` /
97
- * `exit` listeners that flip `alive` to `false` AND latch the first terminal event on
97
+ * `exit` listeners that flip `alive` to `false` and latch the first terminal event on
98
98
  * {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through
99
99
  * its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a
100
- * dispatch that attaches AFTER the death (through the latch). A `messageerror` is terminal
100
+ * dispatch that attaches only after the death (through the latch). A `messageerror` is terminal
101
101
  * too, so a thread whose inbound payload could not be deserialized is never reused. The latch
102
102
  * closes a real race: a thread can become terminal before the readiness promise continuation
103
103
  * hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.
@@ -129,7 +129,7 @@ export declare function createThread(script: string | URL, workerData?: unknown)
129
129
  * authentication / authorization evidence. Per-job consumer context is explicit,
130
130
  * structured-cloneable `input`; ambient context is not worker-thread transport. A success
131
131
  * `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`
132
- * type bridge); a failure rejects with the thread's error string. A thread that ALREADY died
132
+ * type bridge); a failure rejects with the thread's error string. A thread that had already died
133
133
  * rejects synchronously at construction from the latched {@link NodeThread.death} — its death
134
134
  * events fired before this dispatch existed and will never fire again, so waiting on the
135
135
  * listeners would dangle forever; the latch makes death total across every event ordering. If
@@ -170,7 +170,7 @@ export declare class Dispatch<TResult> {
170
170
  }
171
171
 
172
172
  /**
173
- * Narrows an inbound `message` to a {@link Reply} for a given job `id` — no assertion.
173
+ * Narrows an inbound `message` to a {@link Reply} for a given correlation `id` — no assertion.
174
174
  *
175
175
  * @remarks
176
176
  * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.
@@ -179,8 +179,8 @@ export declare class Dispatch<TResult> {
179
179
  * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.
180
180
  *
181
181
  * @param value - The inbound message to narrow
182
- * @param id - The job id a matching reply must carry
183
- * @returns True if the value is this job's well-formed reply; false otherwise
182
+ * @param id - The per-dispatch correlation id a matching reply must carry
183
+ * @returns True if the value is this dispatch's well-formed reply; false otherwise
184
184
  */
185
185
  export declare function isReply(value: unknown, id: string): value is Reply;
186
186
 
@@ -192,9 +192,9 @@ export declare function isReply(value: unknown, id: string): value is Reply;
192
192
  * `alive` starts `true` and flips to `false` when the thread `error`s, reports a
193
193
  * `messageerror`, exits, or is evicted on abort; the pool's `validate` reads
194
194
  * `alive && worker.threadId > 0`, so a
195
- * dead thread is destroyed and replaced rather than reused. `death` LATCHES the first
195
+ * dead thread is destroyed and replaced rather than reused. `death` latches the first
196
196
  * terminal event (`error` / `messageerror`, or a synthesized error on `exit`) — the death-signal
197
- * record a {@link Dispatch} checks at construction, so a job dispatched AFTER the thread died (its
197
+ * record a {@link Dispatch} checks at construction, so a job dispatched after the thread died (its
198
198
  * death events already fired and will never fire again) rejects immediately instead of
199
199
  * awaiting events that already happened. A thread can become terminal before the readiness
200
200
  * promise continuation attaches dispatch listeners; the latch is what makes that ordering
@@ -221,7 +221,7 @@ export declare interface NodeThread {
221
221
  * `serveWorker(...)`. Raw TypeScript is unflagged on Node 22.18+ and Node 23.6+;
222
222
  * Node 22.12–22.17 and Node 23.0–23.5 require `--experimental-strip-types`. A built
223
223
  * `.js` / `.mjs` script is an alternative across supported Node versions.
224
- * - `input` — narrows the work payload BEFORE it crosses the structured-clone boundary
224
+ * - `input` — narrows the work payload before it crosses the structured-clone boundary
225
225
  * (fail-fast) and supplies the `TInput` inference, so call sites need no type argument.
226
226
  * - `result` — narrows every reply value coming back from a thread; an invalid reply
227
227
  * rejects the job. This is the zero-`as` type bridge — `TResult` is inferred from it.
@@ -297,7 +297,7 @@ export declare type Reply = {
297
297
  * and restore. That job id identifies work, not a caller, and is not authentication or
298
298
  * authorization evidence. Each attempt has its own `AbortController`, so an `abort`
299
299
  * message for the correlation id fires the handler's `signal` (cooperative — the main
300
- * side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
300
+ * side also terminates the thread, so a handler that ignores its signal is still stopped).
301
301
  * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
302
302
  * (`parentPort === null`) it is a no-op.
303
303
  *
@@ -5,7 +5,7 @@ import { createDatabaseQueueStore } from "@orkestrel/queue";
5
5
  import { createWorker } from "../core/index.js";
6
6
  //#region src/server/helpers.ts
7
7
  /**
8
- * Narrows an inbound `message` to a {@link Reply} for a given job `id` — no assertion.
8
+ * Narrows an inbound `message` to a {@link Reply} for a given correlation `id` — no assertion.
9
9
  *
10
10
  * @remarks
11
11
  * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.
@@ -14,8 +14,8 @@ import { createWorker } from "../core/index.js";
14
14
  * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.
15
15
  *
16
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
17
+ * @param id - The per-dispatch correlation id a matching reply must carry
18
+ * @returns True if the value is this dispatch's well-formed reply; false otherwise
19
19
  */
20
20
  function isReply(value, id) {
21
21
  const outcome = attempt(() => {
@@ -44,7 +44,7 @@ function isReply(value, id) {
44
44
  * and restore. That job id identifies work, not a caller, and is not authentication or
45
45
  * authorization evidence. Each attempt has its own `AbortController`, so an `abort`
46
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).
47
+ * side also terminates the thread, so a handler that ignores its signal is still stopped).
48
48
  * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
49
49
  * (`parentPort === null`) it is a no-op.
50
50
  *
@@ -225,7 +225,7 @@ var Thread = class {
225
225
  * authentication / authorization evidence. Per-job consumer context is explicit,
226
226
  * structured-cloneable `input`; ambient context is not worker-thread transport. A success
227
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
228
+ * type bridge); a failure rejects with the thread's error string. A thread that had already died
229
229
  * rejects synchronously at construction from the latched {@link NodeThread.death} — its death
230
230
  * events fired before this dispatch existed and will never fire again, so waiting on the
231
231
  * listeners would dangle forever; the latch makes death total across every event ordering. If
@@ -479,13 +479,13 @@ var NodeWorker = class {
479
479
  *
480
480
  * @remarks
481
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`
482
+ * resolves on the thread's `online` event (rejecting on an early `error` or on an `exit`
483
483
  * that arrives before `online`, so the spawn promise is total — it can never dangle on a
484
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
485
+ * `exit` listeners that flip `alive` to `false` and latch the first terminal event on
486
486
  * {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through
487
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
488
+ * dispatch that attaches only after the death (through the latch). A `messageerror` is terminal
489
489
  * too, so a thread whose inbound payload could not be deserialized is never reused. The latch
490
490
  * closes a real race: a thread can become terminal before the readiness promise continuation
491
491
  * hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.
@@ -517,7 +517,7 @@ function createThread(script, workerData) {
517
517
  * (and reloaded from) the file at `path`, surviving a process restart. There is no new
518
518
  * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the
519
519
  * driver changes where the bytes live. The `input` shape must be JSON-serializable
520
- * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to
520
+ * (the JSON driver round-trips it as JSON). Build a second store over the same `path` to
521
521
  * resume the outstanding entries a prior store persisted.
522
522
  *
523
523
  * @typeParam TInput - The contract shape of each entry's `input` payload
@@ -542,7 +542,7 @@ function createJSONQueueStore(path, input) {
542
542
  }
543
543
  /**
544
544
  * Creates a CPU-parallel worker over `node:worker_threads` — a thin specialization of the
545
- * core `createWorker` whose pooled resource is a worker THREAD.
545
+ * core `createWorker` whose pooled resource is a worker thread.
546
546
  *
547
547
  * @remarks
548
548
  * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,
@@ -553,10 +553,10 @@ function createJSONQueueStore(path, input) {
553
553
  * internal handler that narrows the input through `options.input` (fail-fast before the
554
554
  * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,
555
555
  * narrowing the reply through
556
- * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites
557
- * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards
558
- * reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`
559
- * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a
556
+ * `options.result`. `TInput` and `TResult` infer from the `input` and `result` guards, so
557
+ * call sites need no explicit type arguments. The boundary is crossed with no `as`: the
558
+ * guards reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`
559
+ * terminates the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a
560
560
  * subsequent job spawns a fresh thread. The worker script's module must call
561
561
  * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.
562
562
  *