@orkestrel/worker 0.0.10 → 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.
- package/README.md +18 -19
- package/dist/src/core/index.cjs +38 -34
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +94 -71
- package/dist/src/core/index.d.ts +94 -71
- package/dist/src/core/index.js +38 -34
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +243 -221
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +146 -96
- package/dist/src/server/index.d.ts +146 -96
- package/dist/src/server/index.js +242 -220
- package/dist/src/server/index.js.map +1 -1
- package/package.json +17 -19
|
@@ -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","#execution","#result","#id","#promise","#fulfill","#reject","#messageHandler","#messageErrorHandler","#errorHandler","#exitHandler","#abortHandler","#message","#messageError","#error","#exit","#abort","#start","#fail","#terminate","#succeed","#settled","#detach","#script","#input","#result","#workerData","#concurrency","#retries","#timeout","#store","#create","#destroy","#validate","#handle"],"sources":["../../../src/server/Thread.ts","../../../src/server/validators.ts","../../../src/server/Dispatch.ts","../../../src/server/helpers.ts","../../../src/server/handlers.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Internal mutable implementation of the readonly {@link NodeThread} observation contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { Reply } from './types.js'\nimport { attempt, isRecord } from '@orkestrel/contract'\n\n/**\n * Narrow an inbound `message` to a {@link Reply} for a given job `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n *\n * @param value - The inbound message to narrow\n * @param id - The job id a matching reply must carry\n * @returns `true` when the value is this job's well-formed reply\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && typeof value.error === 'string'\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isRecord } from '@orkestrel/contract'\nimport { Thread } from './Thread.js'\nimport { isReply } from './validators.js'\n\n/**\n * Internal lifecycle entity for one dispatched worker-thread job.\n *\n * @remarks\n * Owns stable `message` / `messageerror` / death listener identities, settlement, result-guard\n * containment, and abort eviction for one dispatch. Deserialization failure, a matching-id\n * malformed reply, and abort each evict and terminate the thread before rejecting, with\n * termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter is ignored.\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #execution: QueueExecution\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(\n\t\tthread: NodeThread,\n\t\tinput: unknown,\n\t\texecution: QueueExecution,\n\t\tresult: Guard<TResult>,\n\t) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#execution = execution\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#execution.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#execution.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({\n\t\t\t\tid: this.#id,\n\t\t\t\tjob: this.#execution.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.#execution.signal.reason, notification)\n\t}\n\n\t#terminate(error: unknown, notification: readonly unknown[] = []): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tif (this.#thread instanceof Thread) this.#thread.evict()\n\t\tlet termination: Promise<number>\n\t\ttry {\n\t\t\ttermination = this.#worker.terminate()\n\t\t} catch (cause: unknown) {\n\t\t\tthis.#reject(new AggregateError([error, ...notification, cause], 'worker termination failed'))\n\t\t\treturn\n\t\t}\n\t\tvoid termination.then(\n\t\t\t() => {\n\t\t\t\tif (notification.length === 0) this.#reject(error)\n\t\t\t\telse {\n\t\t\t\t\tthis.#reject(\n\t\t\t\t\t\tnew AggregateError([error, ...notification], 'worker abort notification failed'),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\t\t\t(cause: unknown) =>\n\t\t\t\tthis.#reject(\n\t\t\t\t\tnew AggregateError([error, ...notification, cause], 'worker termination failed'),\n\t\t\t\t),\n\t\t)\n\t}\n\n\t#succeed(value: TResult): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#fulfill(value)\n\t}\n\n\t#fail(error: unknown): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#reject(error)\n\t}\n\n\t#detach(): void {\n\t\tthis.#worker.off('message', this.#messageHandler)\n\t\tthis.#worker.off('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.off('error', this.#errorHandler)\n\t\tthis.#worker.off('exit', this.#exitHandler)\n\t\tthis.#execution.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n// === The wire protocol (main ↔ thread)\n//\n// The main-side half of the run/abort/reply protocol `serveWorker` answers — spawning a\n// pooled thread, narrowing its replies, and dispatching one job at a time. The envelope\n// types ({@link Reply}, {@link NodeThread}) live in `./types.js` (AGENTS §5); the public\n// bridge across the structured-clone boundary is the `input` / `result` `Guard`s, which\n// narrow the envelopes' opaque `unknown` payloads with no assertion (AGENTS §14).\n\n/**\n * Spawn one worker thread and resolve a live {@link NodeThread} once it comes online.\n *\n * @remarks\n * Constructs the thread with the `script` module and the cloned `workerData`, then\n * resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`\n * that arrives before `online`, so the spawn promise is total — it can never dangle on a\n * thread that died without erroring). The wrapper attaches persistent `error` / `exit`\n * listeners that flip `alive` to `false` AND latch the first terminal event on\n * {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via\n * its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a\n * dispatch that attaches AFTER the death (via the latch). A `messageerror` is terminal too,\n * so a thread whose inbound payload could not be deserialized is never reused. The latch\n * closes a real race: a thread can become terminal before the readiness promise continuation\n * hands it to `dispatch`, leaving no future death event for that dispatch to observe. Without\n * the latch, that job would wait forever. The pool's `create` hook calls this.\n *\n * @param script - The worker module each thread runs (must call `serveWorker`)\n * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn\n * @returns A promise resolving the online {@link NodeThread}\n */\nexport function spawnThread(script: string | URL, workerData: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.\n *\n * @remarks\n * Mints a fresh per-dispatch correlation `id`, posts it with `job: execution.id`, and\n * resolves when the thread replies for that correlation id. The stable Queue job id reaches\n * the worker handler for idempotency across retries and restore; it is not caller identity or\n * authentication / authorization evidence. Per-job consumer context remains 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\n * rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.\n * A thread that ALREADY died rejects synchronously at entry from the latched\n * {@link NodeThread.death} — its death events fired before this dispatch existed and will\n * never fire again, so waiting on the listeners below would dangle forever; the latch makes\n * death total across every event ordering. If the thread `error`s / `exit`s mid-flight it is\n * marked dead and the\n * job rejects. An inbound `messageerror` also evicts and terminates the thread before\n * rejection. On `execution.signal` abort it contains the cooperative `abort` post,\n * evicts the thread, and observes `terminate()` settlement because CPU-bound work cannot\n * honour the signal; the freed pool slot then gets a fresh thread. Every per-job listener\n * (`message` / `messageerror` / `error` / `exit` / `abort`) is removed on settle.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n * @param thread - The leased thread to run the job on\n * @param input - The work payload (structured-cloned to the thread)\n * @param execution - The per-attempt handle; its `signal` aborts → terminate + evict\n * @param result - The {@link Guard} narrowing the reply value with no assertion\n * @returns A promise resolving the narrowed `TResult`, or rejecting on error / abort\n */\nexport function dispatch<TResult>(\n\tthread: NodeThread,\n\tinput: unknown,\n\texecution: QueueExecution,\n\tresult: Guard<TResult>,\n): Promise<TResult> {\n\treturn new Dispatch(thread, input, execution, result).promise\n}\n","import type { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side 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// `validators.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 * Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { 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 `execution.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 four fields once, defensively. A 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 execution id handed to the handler. A legacy or 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 execution = 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: execution, 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 { WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueExecution, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { dispatch, spawnThread } from './helpers.js'\n\n/**\n * Internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity remains the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn spawnThread(this.#script, this.#workerData)\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, execution: QueueExecution): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn dispatch(thread, input, execution, this.#result)\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\n\n/**\n * Create a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is just a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Create a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker THREAD.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (via `spawnThread`),\n * `destroy`s it with `terminate()`, and `validate`s it by `alive && threadId > 0` (so an\n * evicted / crashed thread is dropped and replaced) — and an internal handler that\n * narrows the input through `options.input` (fail-fast before the structured-clone\n * boundary) then `dispatch`es the job to the leased thread, narrowing the reply through\n * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites\n * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards\n * reconstruct `TInput` / `TResult` by validation (AGENTS §14). An `abort` / `timeout`\n * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
|
|
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,22 +1,25 @@
|
|
|
1
|
-
import { ContractShape } from '@orkestrel/contract';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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';
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
|
-
*
|
|
13
|
+
* Creates a persistent JSON-file {@link QueueStoreInterface} — the core
|
|
11
14
|
* `createDatabaseQueueStore` over a server {@link createJSONDriver}.
|
|
12
15
|
*
|
|
13
16
|
* @remarks
|
|
14
|
-
* A queue's durable state is
|
|
17
|
+
* A queue's durable state is a database table, so JSON persistence reuses the
|
|
15
18
|
* existing JSON-file driver rather than a bespoke store: the entries are written to
|
|
16
19
|
* (and reloaded from) the file at `path`, surviving a process restart. There is no new
|
|
17
20
|
* class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the
|
|
18
21
|
* driver changes where the bytes live. The `input` shape must be JSON-serializable
|
|
19
|
-
* (the JSON driver round-trips it as JSON). Build a second store over the
|
|
22
|
+
* (the JSON driver round-trips it as JSON). Build a second store over the same `path` to
|
|
20
23
|
* resume the outstanding entries a prior store persisted.
|
|
21
24
|
*
|
|
22
25
|
* @typeParam TInput - The contract shape of each entry's `input` payload
|
|
@@ -39,28 +42,29 @@ import { WorkerInterface } from '@orkestrel/worker';
|
|
|
39
42
|
export declare function createJSONQueueStore<TInput extends ContractShape>(path: string, input: TInput): QueueStoreInterface<Infer<TInput>>;
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
|
-
*
|
|
43
|
-
* core `createWorker` whose pooled resource is a worker
|
|
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.
|
|
44
47
|
*
|
|
45
48
|
* @remarks
|
|
46
49
|
* Composition, not reimplementation: all concurrency, retries, per-attempt timeout,
|
|
47
50
|
* lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory
|
|
48
|
-
* supplies only the thread pairing — the pool `create`s a thread (
|
|
49
|
-
* `destroy`s it with `terminate()`, and `validate`s it by
|
|
50
|
-
* evicted / crashed thread is dropped and replaced) — and an
|
|
51
|
-
* narrows the input through `options.input` (fail-fast before the
|
|
52
|
-
* boundary) then
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
51
|
+
* supplies only the thread pairing — the pool `create`s a thread (the same spawn
|
|
52
|
+
* {@link createThread} publishes), `destroy`s it with `terminate()`, and `validate`s it by
|
|
53
|
+
* `alive && threadId > 0` (so an evicted / crashed thread is dropped and replaced) — and an
|
|
54
|
+
* internal handler that narrows the input through `options.input` (fail-fast before the
|
|
55
|
+
* structured-clone boundary) then runs a {@link Dispatch} against the leased thread,
|
|
56
|
+
* narrowing the reply through
|
|
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
|
|
57
61
|
* subsequent job spawns a fresh thread. The worker script's module must call
|
|
58
62
|
* `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.
|
|
59
63
|
*
|
|
60
64
|
* @typeParam TInput - The work payload each job carries (inferred from `input`)
|
|
61
65
|
* @typeParam TResult - The value a thread resolves for a job (inferred from `result`)
|
|
62
66
|
* @param options - The `script` plus the `input` / `result` guards and optional
|
|
63
|
-
* `workerData` / `concurrency` / `retries` / `timeout` / `store`
|
|
67
|
+
* `on` / `error` / `workerData` / `concurrency` / `retries` / `timeout` / `store`
|
|
64
68
|
* (see {@link NodeWorkerOptions})
|
|
65
69
|
* @returns A working {@link WorkerInterface} backed by a thread pool
|
|
66
70
|
*
|
|
@@ -82,65 +86,126 @@ export declare function createJSONQueueStore<TInput extends ContractShape>(path:
|
|
|
82
86
|
export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOptions<TInput, TResult>): WorkerInterface<TInput, TResult>;
|
|
83
87
|
|
|
84
88
|
/**
|
|
85
|
-
*
|
|
89
|
+
* Creates one live worker thread and resolves it as a {@link NodeThread} after it comes
|
|
90
|
+
* online.
|
|
86
91
|
*
|
|
87
92
|
* @remarks
|
|
88
|
-
*
|
|
89
|
-
* resolves
|
|
90
|
-
*
|
|
91
|
-
*
|
|
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 on an `exit`
|
|
95
|
+
* that arrives before `online`, so the spawn promise is total — it can never dangle on a
|
|
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
|
|
98
|
+
* {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through
|
|
99
|
+
* its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a
|
|
100
|
+
* dispatch that attaches only after the death (through the latch). A `messageerror` is terminal
|
|
101
|
+
* too, so a thread whose inbound payload could not be deserialized is never reused. The latch
|
|
102
|
+
* closes a real race: a thread can become terminal before the readiness promise continuation
|
|
103
|
+
* hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.
|
|
104
|
+
* Without the latch, that job would wait forever. {@link createNodeWorker} spawns its pooled
|
|
105
|
+
* threads the same way; reach for this to drive one thread yourself.
|
|
106
|
+
*
|
|
107
|
+
* @param script - The worker module the thread runs (its module must call `serveWorker`)
|
|
108
|
+
* @param workerData - Opaque, structured-cloneable data handed to the thread at spawn
|
|
109
|
+
* @returns A promise resolving the online {@link NodeThread}
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { createThread } from '@orkestrel/worker/server'
|
|
114
|
+
*
|
|
115
|
+
* const thread = await createThread(new URL('./double.js', import.meta.url))
|
|
116
|
+
* await thread.worker.terminate()
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function createThread(script: string | URL, workerData?: unknown): Promise<NodeThread>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Represents one dispatched worker-thread job — the lifecycle entity behind a job posted to a
|
|
123
|
+
* leased {@link NodeThread}, whose {@link promise} settles with the narrowed reply.
|
|
124
|
+
*
|
|
125
|
+
* @remarks
|
|
126
|
+
* Mints a fresh per-dispatch correlation `id`, posts it with `job: context.id`, and settles
|
|
127
|
+
* when the thread replies for that correlation id. The stable Queue job id reaches the worker
|
|
128
|
+
* handler for idempotency across retries and restore; it is not caller identity or
|
|
129
|
+
* authentication / authorization evidence. Per-job consumer context is explicit,
|
|
92
130
|
* structured-cloneable `input`; ambient context is not worker-thread transport. A success
|
|
93
|
-
* `value` is narrowed through `result` (a value that fails the guard
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
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 had already died
|
|
133
|
+
* rejects synchronously at construction from the latched {@link NodeThread.death} — its death
|
|
134
|
+
* events fired before this dispatch existed and will never fire again, so waiting on the
|
|
135
|
+
* listeners would dangle forever; the latch makes death total across every event ordering. If
|
|
136
|
+
* the thread `error`s / `exit`s mid-flight the job rejects. On a `context.signal` abort it
|
|
137
|
+
* contains the cooperative `abort` post, evicts the thread, and observes `terminate()`
|
|
138
|
+
* settlement because CPU-bound work cannot honour the signal.
|
|
139
|
+
*
|
|
140
|
+
* It owns stable `message` / `messageerror` / death listener identities, settlement,
|
|
141
|
+
* result-guard containment, and abort eviction for one dispatch. Deserialization failure, a
|
|
142
|
+
* matching-id malformed reply, and abort each evict and terminate the thread before rejecting,
|
|
143
|
+
* with termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter
|
|
144
|
+
* is ignored. Every per-job listener (`message` / `messageerror` / `error` / `exit` / `abort`)
|
|
145
|
+
* is removed on settle.
|
|
146
|
+
*
|
|
147
|
+
* Eviction reaches `alive` for a {@link NodeThread} this package produced. Against a
|
|
148
|
+
* consumer-supplied `NodeThread` an abort or a `messageerror` still terminates the supplied
|
|
149
|
+
* `worker` and rejects the job, and the implementer owns flipping its own `alive`.
|
|
105
150
|
*
|
|
106
151
|
* @typeParam TResult - The reply type the `result` guard narrows to
|
|
107
|
-
*
|
|
108
|
-
* @
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* import { createThread, Dispatch } from '@orkestrel/worker/server'
|
|
156
|
+
*
|
|
157
|
+
* const isNumber = (value: unknown): value is number => typeof value === 'number'
|
|
158
|
+
*
|
|
159
|
+
* const thread = await createThread(new URL('./double.js', import.meta.url))
|
|
160
|
+
* const controller = new AbortController()
|
|
161
|
+
* const job = new Dispatch(thread, 21, { id: 'job-1', signal: controller.signal }, isNumber)
|
|
162
|
+
* console.log(await job.promise) // 42
|
|
163
|
+
* await thread.worker.terminate()
|
|
164
|
+
* ```
|
|
112
165
|
*/
|
|
113
|
-
export declare
|
|
166
|
+
export declare class Dispatch<TResult> {
|
|
167
|
+
#private;
|
|
168
|
+
constructor(thread: NodeThread, input: unknown, context: QueueContext, result: Guard<TResult>);
|
|
169
|
+
get promise(): Promise<TResult>;
|
|
170
|
+
}
|
|
114
171
|
|
|
115
172
|
/**
|
|
116
|
-
*
|
|
173
|
+
* Narrows an inbound `message` to a {@link Reply} for a given correlation `id` — no assertion.
|
|
117
174
|
*
|
|
118
175
|
* @remarks
|
|
119
176
|
* A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.
|
|
120
177
|
* Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.
|
|
178
|
+
* It correlates against the `id` argument rather than narrowing one value alone, so it is a
|
|
179
|
+
* correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.
|
|
121
180
|
*
|
|
122
181
|
* @param value - The inbound message to narrow
|
|
123
|
-
* @param id - The
|
|
124
|
-
* @returns
|
|
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
|
|
125
184
|
*/
|
|
126
185
|
export declare function isReply(value: unknown, id: string): value is Reply;
|
|
127
186
|
|
|
128
187
|
/**
|
|
129
|
-
*
|
|
188
|
+
* Represents a live worker thread plus its latched liveness state — the pooled resource a
|
|
130
189
|
* {@link createNodeWorker} leases per job.
|
|
131
190
|
*
|
|
132
191
|
* @remarks
|
|
133
192
|
* `alive` starts `true` and flips to `false` when the thread `error`s, reports a
|
|
134
193
|
* `messageerror`, exits, or is evicted on abort; the pool's `validate` reads
|
|
135
194
|
* `alive && worker.threadId > 0`, so a
|
|
136
|
-
* dead thread is destroyed and replaced rather than reused. `death`
|
|
195
|
+
* dead thread is destroyed and replaced rather than reused. `death` latches the first
|
|
137
196
|
* terminal event (`error` / `messageerror`, or a synthesized error on `exit`) — the death-signal
|
|
138
|
-
* record a
|
|
197
|
+
* record a {@link Dispatch} checks at construction, so a job dispatched after the thread died (its
|
|
139
198
|
* death events already fired and will never fire again) rejects immediately instead of
|
|
140
199
|
* awaiting events that already happened. A thread can become terminal before the readiness
|
|
141
200
|
* promise continuation attaches dispatch listeners; the latch is what makes that ordering
|
|
142
201
|
* safe. `worker` is the underlying
|
|
143
202
|
* `node:worker_threads` thread (its `postMessage` / `terminate` drive the protocol).
|
|
203
|
+
*
|
|
204
|
+
* A dispatch marks a thread dead for a `NodeThread` this package produced, through
|
|
205
|
+
* {@link createThread} or a {@link createNodeWorker} pool. A foreign implementation of this
|
|
206
|
+
* interface owns flipping its own `alive` when its `worker` is terminated: an abort or a
|
|
207
|
+
* `messageerror` terminates the supplied `worker` and rejects the job, and leaves the
|
|
208
|
+
* implementer's `alive` and `death` exactly as the implementer reports them.
|
|
144
209
|
*/
|
|
145
210
|
export declare interface NodeThread {
|
|
146
211
|
readonly worker: Worker;
|
|
@@ -149,31 +214,40 @@ export declare interface NodeThread {
|
|
|
149
214
|
}
|
|
150
215
|
|
|
151
216
|
/**
|
|
152
|
-
*
|
|
217
|
+
* Configures `createNodeWorker` — a CPU-parallel worker over `node:worker_threads`.
|
|
153
218
|
*
|
|
154
219
|
* @remarks
|
|
155
220
|
* - `script` — the worker module each pooled thread runs; its module must call
|
|
156
221
|
* `serveWorker(...)`. Raw TypeScript is unflagged on Node 22.18+ and Node 23.6+;
|
|
157
222
|
* Node 22.12–22.17 and Node 23.0–23.5 require `--experimental-strip-types`. A built
|
|
158
|
-
* `.js` / `.mjs` script
|
|
159
|
-
* - `input` — narrows the work payload
|
|
223
|
+
* `.js` / `.mjs` script is an alternative across supported Node versions.
|
|
224
|
+
* - `input` — narrows the work payload before it crosses the structured-clone boundary
|
|
160
225
|
* (fail-fast) and supplies the `TInput` inference, so call sites need no type argument.
|
|
161
226
|
* - `result` — narrows every reply value coming back from a thread; an invalid reply
|
|
162
227
|
* rejects the job. This is the zero-`as` type bridge — `TResult` is inferred from it.
|
|
163
|
-
* - `workerData` — opaque data cloned to every thread
|
|
164
|
-
* `
|
|
228
|
+
* - `workerData` — opaque data cloned to every thread at spawn; the key mirrors the
|
|
229
|
+
* `node:worker_threads` `Worker` constructor option of the same name, and the thread reads
|
|
230
|
+
* it back from `node:worker_threads`. It must be structured-cloneable.
|
|
165
231
|
* - `concurrency` — the maximum jobs in flight at once; the thread pool's `max` matches
|
|
166
|
-
* it, so at most this many threads exist.
|
|
167
|
-
*
|
|
168
|
-
* - `retries` — the default extra attempts per job on failure / timeout
|
|
169
|
-
* - `timeout` — the default per-attempt deadline in milliseconds
|
|
232
|
+
* it, so at most this many threads exist. It must be a positive safe integer, as
|
|
233
|
+
* validated by the underlying queue. Default: 1.
|
|
234
|
+
* - `retries` — the default extra attempts per job on failure / timeout. Default: 0.
|
|
235
|
+
* - `timeout` — the default per-attempt deadline in milliseconds. Default: no per-attempt
|
|
236
|
+
* deadline.
|
|
170
237
|
* - `store` — durable backing for outstanding jobs (survives a restart; `restore()`
|
|
171
238
|
* re-runs them).
|
|
239
|
+
* - `on` — the reserved {@link EmitterHooks} key: initial listeners for the worker's
|
|
240
|
+
* {@link WorkerEventMap} (the job lifecycle it surfaces from its underlying queue), wired
|
|
241
|
+
* at construction. A thread worker takes the same hooks as the core worker.
|
|
242
|
+
* - `error` — the emitter's listener-error handler; a listener throw routes
|
|
243
|
+
* here, not to a domain event.
|
|
172
244
|
*
|
|
173
245
|
* @typeParam TInput - The work payload each job carries (inferred from `input`)
|
|
174
246
|
* @typeParam TResult - The value a thread resolves for a job (inferred from `result`)
|
|
175
247
|
*/
|
|
176
248
|
export declare interface NodeWorkerOptions<TInput, TResult> {
|
|
249
|
+
readonly on?: EmitterHooks<WorkerEventMap<TResult>>;
|
|
250
|
+
readonly error?: EmitterErrorHandler;
|
|
177
251
|
readonly script: string | URL;
|
|
178
252
|
readonly input: Guard<TInput>;
|
|
179
253
|
readonly result: Guard<TResult>;
|
|
@@ -185,16 +259,15 @@ export declare interface NodeWorkerOptions<TInput, TResult> {
|
|
|
185
259
|
}
|
|
186
260
|
|
|
187
261
|
/**
|
|
188
|
-
*
|
|
189
|
-
* message —
|
|
262
|
+
* Represents a thread→main reply envelope — a success carrying an opaque `value`, or a failure with a
|
|
263
|
+
* message — the reply half of the wire protocol `createNodeWorker` posts and `serveWorker`
|
|
190
264
|
* answers.
|
|
191
265
|
*
|
|
192
266
|
* @remarks
|
|
193
|
-
*
|
|
194
|
-
* impl file holds only its class / functions). A reply is a discriminated union on `ok`: a
|
|
267
|
+
* A reply is a discriminated union on `ok`: a
|
|
195
268
|
* `true` carries any opaque `value` (narrowed at the boundary by the `result` guard,
|
|
196
269
|
* with no `as`); a `false` carries a string `error`. The worker-side `serve.ts` cannot import
|
|
197
|
-
* this
|
|
270
|
+
* this because it loads as raw source in a spawned thread, and posts the same
|
|
198
271
|
* shape structurally. The `id` ties a reply to its job: id-less / foreign-id chatter is ignored,
|
|
199
272
|
* while a matching-id malformed envelope taints the thread and causes dispatch to terminate it.
|
|
200
273
|
*/
|
|
@@ -209,7 +282,7 @@ export declare type Reply = {
|
|
|
209
282
|
};
|
|
210
283
|
|
|
211
284
|
/**
|
|
212
|
-
*
|
|
285
|
+
* Registers a worker-thread handler — the worker-side half of {@link createNodeWorker}.
|
|
213
286
|
*
|
|
214
287
|
* @remarks
|
|
215
288
|
* Must be the spawned thread's module entry. It listens on the parent port for the
|
|
@@ -220,11 +293,11 @@ export declare type Reply = {
|
|
|
220
293
|
* success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
|
|
221
294
|
* fails, the parent port closes so the main side observes thread exit instead of waiting forever.
|
|
222
295
|
* The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;
|
|
223
|
-
* its `job` is the stable Queue idempotency key exposed as `
|
|
296
|
+
* its `job` is the stable Queue idempotency key exposed as `context.id` across retries
|
|
224
297
|
* and restore. That job id identifies work, not a caller, and is not authentication or
|
|
225
298
|
* authorization evidence. Each attempt has its own `AbortController`, so an `abort`
|
|
226
299
|
* message for the correlation id fires the handler's `signal` (cooperative — the main
|
|
227
|
-
* side
|
|
300
|
+
* side also terminates the thread, so a handler that ignores its signal is still stopped).
|
|
228
301
|
* Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
|
|
229
302
|
* (`parentPort === null`) it is a no-op.
|
|
230
303
|
*
|
|
@@ -246,16 +319,16 @@ export declare type Reply = {
|
|
|
246
319
|
export declare function serveWorker<TInput, TResult>(options: ServeWorkerOptions<TInput, TResult>): void;
|
|
247
320
|
|
|
248
321
|
/**
|
|
249
|
-
*
|
|
322
|
+
* Configures `serveWorker` — the worker-side entry a thread script registers.
|
|
250
323
|
*
|
|
251
324
|
* @remarks
|
|
252
325
|
* - `input` — narrows each inbound payload inside the thread; an invalid payload replies
|
|
253
326
|
* with an error envelope rather than running the handler. Supplies the `TInput`
|
|
254
327
|
* inference for the handler.
|
|
255
|
-
* - `handler` — runs one job; receives the narrowed input and the Queue's
|
|
256
|
-
* `
|
|
328
|
+
* - `handler` — runs one job; receives the narrowed input and the Queue's context.
|
|
329
|
+
* `context.id` is the stable Queue idempotency key across retries and crash restore;
|
|
257
330
|
* it identifies work, not a caller, and is not authentication or authorization evidence.
|
|
258
|
-
* `
|
|
331
|
+
* `context.signal` is per attempt and fires when the main side aborts that attempt
|
|
259
332
|
* (cooperative). The handler may be async; its resolved value (which must be
|
|
260
333
|
* structured-cloneable) is the reply.
|
|
261
334
|
*
|
|
@@ -264,30 +337,7 @@ export declare function serveWorker<TInput, TResult>(options: ServeWorkerOptions
|
|
|
264
337
|
*/
|
|
265
338
|
export declare interface ServeWorkerOptions<TInput, TResult> {
|
|
266
339
|
readonly input: Guard<TInput>;
|
|
267
|
-
readonly handler: (input: TInput,
|
|
340
|
+
readonly handler: (input: TInput, context: QueueContext) => Promise<TResult> | TResult;
|
|
268
341
|
}
|
|
269
342
|
|
|
270
|
-
/**
|
|
271
|
-
* Spawn one worker thread and resolve a live {@link NodeThread} once it comes online.
|
|
272
|
-
*
|
|
273
|
-
* @remarks
|
|
274
|
-
* Constructs the thread with the `script` module and the cloned `workerData`, then
|
|
275
|
-
* resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`
|
|
276
|
-
* that arrives before `online`, so the spawn promise is total — it can never dangle on a
|
|
277
|
-
* thread that died without erroring). The wrapper attaches persistent `error` / `exit`
|
|
278
|
-
* listeners that flip `alive` to `false` AND latch the first terminal event on
|
|
279
|
-
* {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via
|
|
280
|
-
* its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a
|
|
281
|
-
* dispatch that attaches AFTER the death (via the latch). A `messageerror` is terminal too,
|
|
282
|
-
* so a thread whose inbound payload could not be deserialized is never reused. The latch
|
|
283
|
-
* closes a real race: a thread can become terminal before the readiness promise continuation
|
|
284
|
-
* hands it to `dispatch`, leaving no future death event for that dispatch to observe. Without
|
|
285
|
-
* the latch, that job would wait forever. The pool's `create` hook calls this.
|
|
286
|
-
*
|
|
287
|
-
* @param script - The worker module each thread runs (must call `serveWorker`)
|
|
288
|
-
* @param workerData - Opaque, structured-cloneable data handed to the thread at spawn
|
|
289
|
-
* @returns A promise resolving the online {@link NodeThread}
|
|
290
|
-
*/
|
|
291
|
-
export declare function spawnThread(script: string | URL, workerData: unknown): Promise<NodeThread>;
|
|
292
|
-
|
|
293
343
|
export { }
|