@orkestrel/worker 0.0.5 → 0.0.7
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 +8 -2
- package/dist/src/server/index.cjs +32 -9
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +19 -11
- package/dist/src/server/index.d.ts +19 -11
- package/dist/src/server/index.js +32 -9
- package/dist/src/server/index.js.map +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -10,8 +10,14 @@ observable (a typed `emitter` re-exposes the underlying queue's job lifecycle
|
|
|
10
10
|
— `enqueue` / `start` / `retry` / `success` / `failure` / `abort` / `drain`).
|
|
11
11
|
For CPU-parallel work, the server surface's `createNodeWorker` specializes the
|
|
12
12
|
core `createWorker` over a pool of `node:worker_threads`, crossing the
|
|
13
|
-
structured-clone boundary with zero `as` via `input` / `result` guards.
|
|
14
|
-
|
|
13
|
+
structured-clone boundary with zero `as` via `input` / `result` guards. Each
|
|
14
|
+
thread handler receives `{ id, signal }`: `id` is the Queue's stable idempotency
|
|
15
|
+
key across retries and crash restore, while `signal` is per attempt. The wire
|
|
16
|
+
protocol separately mints a fresh correlation id for each dispatch so a stale
|
|
17
|
+
reply cannot settle a later retry. The stable id identifies work, not a caller,
|
|
18
|
+
and is not authentication or authorization evidence; per-job consumer context
|
|
19
|
+
remains explicit structured-cloneable input rather than ambient thread state.
|
|
20
|
+
Part of the `@orkestrel` line.
|
|
15
21
|
|
|
16
22
|
## Install
|
|
17
23
|
|
|
@@ -168,6 +168,7 @@ var Dispatch = class {
|
|
|
168
168
|
try {
|
|
169
169
|
this.#worker.postMessage({
|
|
170
170
|
id: this.#id,
|
|
171
|
+
job: this.#execution.id,
|
|
171
172
|
command: "run",
|
|
172
173
|
input: this.#input
|
|
173
174
|
});
|
|
@@ -291,8 +292,12 @@ function spawnThread(script, workerData) {
|
|
|
291
292
|
* Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.
|
|
292
293
|
*
|
|
293
294
|
* @remarks
|
|
294
|
-
* Mints a fresh `id`, posts
|
|
295
|
-
*
|
|
295
|
+
* Mints a fresh per-dispatch correlation `id`, posts it with `job: execution.id`, and
|
|
296
|
+
* resolves when the thread replies for that correlation id. The stable Queue job id reaches
|
|
297
|
+
* the worker handler for idempotency across retries and restore; it is not caller identity or
|
|
298
|
+
* authentication / authorization evidence. Per-job consumer context remains explicit,
|
|
299
|
+
* structured-cloneable `input`; ambient context is not worker-thread transport. A success
|
|
300
|
+
* `value` is narrowed through `result` (a value that fails the guard
|
|
296
301
|
* rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.
|
|
297
302
|
* A thread that ALREADY died rejects synchronously at entry from the latched
|
|
298
303
|
* {@link NodeThread.death} — its death events fired before this dispatch existed and will
|
|
@@ -318,13 +323,25 @@ function dispatch(thread, input, execution, result) {
|
|
|
318
323
|
//#endregion
|
|
319
324
|
//#region src/server/serve.ts
|
|
320
325
|
function isRecord(value) {
|
|
321
|
-
|
|
326
|
+
try {
|
|
327
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
328
|
+
} catch {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
322
331
|
}
|
|
323
332
|
function isRun(value) {
|
|
324
|
-
|
|
333
|
+
try {
|
|
334
|
+
return isRecord(value) && typeof value.id === "string" && typeof value.job === "string" && value.command === "run" && "input" in value;
|
|
335
|
+
} catch {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
325
338
|
}
|
|
326
339
|
function isAbort(value) {
|
|
327
|
-
|
|
340
|
+
try {
|
|
341
|
+
return isRecord(value) && typeof value.id === "string" && value.command === "abort";
|
|
342
|
+
} catch {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
328
345
|
}
|
|
329
346
|
/**
|
|
330
347
|
* Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.
|
|
@@ -333,12 +350,15 @@ function isAbort(value) {
|
|
|
333
350
|
* Must be the spawned thread's module entry. It listens on the parent port for the
|
|
334
351
|
* run/abort protocol: a `run` message narrows its `input` through `options.input` (an
|
|
335
352
|
* invalid payload replies with an error envelope, never running the handler), then runs
|
|
336
|
-
* `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or
|
|
353
|
+
* `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or
|
|
337
354
|
* `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a
|
|
338
355
|
* success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
|
|
339
356
|
* fails, the parent port closes so the main side observes thread exit instead of waiting forever.
|
|
340
|
-
*
|
|
341
|
-
*
|
|
357
|
+
* The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;
|
|
358
|
+
* its `job` is the stable Queue idempotency key exposed as `execution.id` across retries
|
|
359
|
+
* and restore. That job id identifies work, not a caller, and is not authentication or
|
|
360
|
+
* authorization evidence. Each attempt has its own `AbortController`, so an `abort`
|
|
361
|
+
* message for the correlation id fires the handler's `signal` (cooperative — the main
|
|
342
362
|
* side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
|
|
343
363
|
* Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
|
|
344
364
|
* (`parentPort === null`) it is a no-op.
|
|
@@ -376,7 +396,10 @@ function serveWorker(options) {
|
|
|
376
396
|
Promise.resolve().then(() => {
|
|
377
397
|
if (!input(raw.input)) throw new Error("input did not satisfy input guard");
|
|
378
398
|
const value = raw.input;
|
|
379
|
-
return handler(value, {
|
|
399
|
+
return handler(value, {
|
|
400
|
+
id: raw.job,
|
|
401
|
+
signal: controller.signal
|
|
402
|
+
});
|
|
380
403
|
}).then((value) => {
|
|
381
404
|
controllers.delete(id);
|
|
382
405
|
port.postMessage({
|
|
@@ -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/serve.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Internal mutable implementation of the readonly {@link NodeThread} observation contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { Reply } from './types.js'\nimport { attempt, isRecord } from '@orkestrel/contract'\n\n/**\n * Narrow an inbound `message` to a {@link Reply} for a given job `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n *\n * @param value - The inbound message to narrow\n * @param id - The job id a matching reply must carry\n * @returns `true` when the value is this job's well-formed reply\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && typeof value.error === 'string'\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isRecord } from '@orkestrel/contract'\nimport { Thread } from './Thread.js'\nimport { isReply } from './validators.js'\n\n/**\n * Internal lifecycle entity for one dispatched worker-thread job.\n *\n * @remarks\n * Owns stable `message` / `messageerror` / death listener identities, settlement, result-guard\n * containment, and abort eviction for one dispatch. Deserialization failure, a matching-id\n * malformed reply, and abort each evict and terminate the thread before rejecting, with\n * termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter is ignored.\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #execution: QueueExecution\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(\n\t\tthread: NodeThread,\n\t\tinput: unknown,\n\t\texecution: QueueExecution,\n\t\tresult: Guard<TResult>,\n\t) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#execution = execution\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#execution.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#execution.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'run', input: this.#input })\n\t\t} catch (error: unknown) {\n\t\t\tthis.#fail(error instanceof Error ? error : new Error(String(error)))\n\t\t}\n\t}\n\n\t#message(value: unknown): void {\n\t\tif (!isRecord(value)) return\n\t\tconst id = attempt(() => value.id)\n\t\tif (!id.success || id.value !== this.#id) return\n\t\tif (!isReply(value, this.#id)) {\n\t\t\tthis.#terminate(new Error('worker reply was malformed'))\n\t\t\treturn\n\t\t}\n\t\tif (value.ok) {\n\t\t\tconst reply = value.value\n\t\t\ttry {\n\t\t\t\tif (this.#result(reply)) this.#succeed(reply)\n\t\t\t\telse this.#fail(new Error('reply did not satisfy result guard'))\n\t\t\t} catch (error: unknown) {\n\t\t\t\tthis.#fail(error)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tthis.#fail(new Error(value.error))\n\t}\n\n\t#messageError(error: Error): void {\n\t\tthis.#terminate(error)\n\t}\n\n\t#error(error: Error): void {\n\t\tthis.#fail(error)\n\t}\n\n\t#exit(): void {\n\t\tthis.#fail(this.#thread.death ?? new Error('worker thread exited'))\n\t}\n\n\t#abort(): void {\n\t\tconst notification: unknown[] = []\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'abort' })\n\t\t} catch (cause: unknown) {\n\t\t\tnotification.push(cause)\n\t\t}\n\t\tthis.#terminate(this.#execution.signal.reason, notification)\n\t}\n\n\t#terminate(error: unknown, notification: readonly unknown[] = []): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tif (this.#thread instanceof Thread) this.#thread.evict()\n\t\tlet termination: Promise<number>\n\t\ttry {\n\t\t\ttermination = this.#worker.terminate()\n\t\t} catch (cause: unknown) {\n\t\t\tthis.#reject(new AggregateError([error, ...notification, cause], 'worker termination failed'))\n\t\t\treturn\n\t\t}\n\t\tvoid termination.then(\n\t\t\t() => {\n\t\t\t\tif (notification.length === 0) this.#reject(error)\n\t\t\t\telse {\n\t\t\t\t\tthis.#reject(\n\t\t\t\t\t\tnew AggregateError([error, ...notification], 'worker abort notification failed'),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\t\t\t(cause: unknown) =>\n\t\t\t\tthis.#reject(\n\t\t\t\t\tnew AggregateError([error, ...notification, cause], 'worker termination failed'),\n\t\t\t\t),\n\t\t)\n\t}\n\n\t#succeed(value: TResult): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#fulfill(value)\n\t}\n\n\t#fail(error: unknown): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#reject(error)\n\t}\n\n\t#detach(): void {\n\t\tthis.#worker.off('message', this.#messageHandler)\n\t\tthis.#worker.off('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.off('error', this.#errorHandler)\n\t\tthis.#worker.off('exit', this.#exitHandler)\n\t\tthis.#execution.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n// === The wire protocol (main ↔ thread)\n//\n// The main-side half of the run/abort/reply protocol `serveWorker` answers — spawning a\n// pooled thread, narrowing its replies, and dispatching one job at a time. The envelope\n// types ({@link Reply}, {@link NodeThread}) live in `./types.js` (AGENTS §5); the public\n// bridge across the structured-clone boundary is the `input` / `result` `Guard`s, which\n// narrow the envelopes' opaque `unknown` payloads with no assertion (AGENTS §14).\n\n/**\n * Spawn one worker thread and resolve a live {@link NodeThread} once it comes online.\n *\n * @remarks\n * Constructs the thread with the `script` module and the cloned `workerData`, then\n * resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`\n * that arrives before `online`, so the spawn promise is total — it can never dangle on a\n * thread that died without erroring). The wrapper attaches persistent `error` / `exit`\n * listeners that flip `alive` to `false` AND latch the first terminal event on\n * {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via\n * its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a\n * dispatch that attaches AFTER the death (via the latch). A `messageerror` is terminal too,\n * so a thread whose inbound payload could not be deserialized is never reused. The latch\n * closes a real race: a thread can become terminal before the readiness promise continuation\n * hands it to `dispatch`, leaving no future death event for that dispatch to observe. Without\n * the latch, that job would wait forever. The pool's `create` hook calls this.\n *\n * @param script - The worker module each thread runs (must call `serveWorker`)\n * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn\n * @returns A promise resolving the online {@link NodeThread}\n */\nexport function spawnThread(script: string | URL, workerData: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.\n *\n * @remarks\n * Mints a fresh `id`, posts a `run` envelope, and resolves when the thread replies for\n * that id: a success `value` is narrowed through `result` (a value that fails the guard\n * rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.\n * A thread that ALREADY died rejects synchronously at entry from the latched\n * {@link NodeThread.death} — its death events fired before this dispatch existed and will\n * never fire again, so waiting on the listeners below would dangle forever; the latch makes\n * death total across every event ordering. If the thread `error`s / `exit`s mid-flight it is\n * marked dead and the\n * job rejects. An inbound `messageerror` also evicts and terminates the thread before\n * rejection. On `execution.signal` abort it contains the cooperative `abort` post,\n * evicts the thread, and observes `terminate()` settlement because CPU-bound work cannot\n * honour the signal; the freed pool slot then gets a fresh thread. Every per-job listener\n * (`message` / `messageerror` / `error` / `exit` / `abort`) is removed on settle.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n * @param thread - The leased thread to run the job on\n * @param input - The work payload (structured-cloned to the thread)\n * @param execution - The per-attempt handle; its `signal` aborts → terminate + evict\n * @param result - The {@link Guard} narrowing the reply value with no assertion\n * @returns A promise resolving the narrowed `TResult`, or rejecting on error / abort\n */\nexport function dispatch<TResult>(\n\tthread: NodeThread,\n\tinput: unknown,\n\texecution: QueueExecution,\n\tresult: Guard<TResult>,\n): Promise<TResult> {\n\treturn new Dispatch(thread, input, execution, result).promise\n}\n","import type { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side entry. SELF-CONTAINED by necessity: this module loads as RAW `.ts`\n// inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports ONLY\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// Its guards are inlined for the same reason. A worker script that needs the cloned\n// `workerData` reads it directly from `node:worker_threads` (it is in a thread already).\n\n// Inlined record guard (do NOT import `isRecord` from `@src/core` — see above). Total:\n// adversarial input returns `false`, never throws (AGENTS §14).\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n// Narrow an inbound message to a `run` envelope (a string `id` + a `'run'` command + an\n// `input` payload) — no assertion.\nfunction isRun(value: unknown): value is { readonly id: string; readonly input: unknown } {\n\treturn (\n\t\tisRecord(value) && typeof value.id === 'string' && value.command === 'run' && 'input' in value\n\t)\n}\n\n// Narrow an inbound message to an `abort` envelope (a string `id` + an `'abort'` command).\nfunction isAbort(value: unknown): value is { readonly id: string } {\n\treturn isRecord(value) && typeof value.id === 'string' && value.command === 'abort'\n}\n\n/**\n * Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or\n * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a\n * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also\n * fails, the parent port closes so the main side observes thread exit instead of waiting forever.\n * Each in-flight job has its own `AbortController`,\n * so an `abort` message for that id fires the handler's `signal` (cooperative — the main\n * side ALSO terminates the thread, so a handler that ignores its signal is still stopped).\n * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread\n * (`parentPort === null`) it is a no-op.\n *\n * @typeParam TInput - The work payload (inferred from `options.input`)\n * @typeParam TResult - The value the handler resolves (the reply payload)\n * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})\n *\n * @example\n * ```ts\n * // double.ts — a worker script\n * import { serveWorker } from '@orkestrel/worker/server'\n *\n * serveWorker<number, number>({\n * \tinput: (value): value is number => typeof value === 'number',\n * \thandler: (value) => value * 2,\n * })\n * ```\n */\nexport function serveWorker<TInput, TResult>(options: ServeWorkerOptions<TInput, TResult>): void {\n\tconst port = parentPort\n\tif (port === null) return\n\tconst input = options.input\n\tconst handler = options.handler\n\tconst controllers = new Map<string, AbortController>()\n\tport.on('message', (raw: unknown) => {\n\t\tif (isAbort(raw)) {\n\t\t\tcontrollers.get(raw.id)?.abort()\n\t\t\treturn\n\t\t}\n\t\tif (!isRun(raw)) return\n\t\tconst id = raw.id\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => {\n\t\t\t\tif (!input(raw.input)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\tconst value = raw.input\n\t\t\t\treturn handler(value, { signal: controller.signal })\n\t\t\t})\n\t\t\t.then((value) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value })\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tlet message = 'worker operation failed'\n\t\t\t\ttry {\n\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error)\n\t\t\t\t} catch {}\n\t\t\t\ttry {\n\t\t\t\t\tport.postMessage({ id, ok: false, error: message })\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tport.close()\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueExecution, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { dispatch, spawnThread } from './helpers.js'\n\n/**\n * Internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity remains the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn spawnThread(this.#script, this.#workerData)\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, execution: QueueExecution): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn dispatch(thread, input, execution, this.#result)\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\n\n/**\n * Create a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is just a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Create a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker THREAD.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (via `spawnThread`),\n * `destroy`s it with `terminate()`, and `validate`s it by `alive && threadId > 0` (so an\n * evicted / crashed thread is dropped and replaced) — and an internal handler that\n * narrows the input through `options.input` (fail-fast before the structured-clone\n * boundary) then `dispatch`es the job to the leased thread, narrowing the reply through\n * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites\n * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards\n * reconstruct `TInput` / `TResult` by validation (AGENTS §14). An `abort` / `timeout`\n * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
|
|
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/serve.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Internal mutable implementation of the readonly {@link NodeThread} observation contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { Reply } from './types.js'\nimport { attempt, isRecord } from '@orkestrel/contract'\n\n/**\n * Narrow an inbound `message` to a {@link Reply} for a given job `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n *\n * @param value - The inbound message to narrow\n * @param id - The job id a matching reply must carry\n * @returns `true` when the value is this job's well-formed reply\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && typeof value.error === 'string'\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isRecord } from '@orkestrel/contract'\nimport { Thread } from './Thread.js'\nimport { isReply } from './validators.js'\n\n/**\n * Internal lifecycle entity for one dispatched worker-thread job.\n *\n * @remarks\n * Owns stable `message` / `messageerror` / death listener identities, settlement, result-guard\n * containment, and abort eviction for one dispatch. Deserialization failure, a matching-id\n * malformed reply, and abort each evict and terminate the thread before rejecting, with\n * termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter is ignored.\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #execution: QueueExecution\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(\n\t\tthread: NodeThread,\n\t\tinput: unknown,\n\t\texecution: QueueExecution,\n\t\tresult: Guard<TResult>,\n\t) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#execution = execution\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#execution.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#execution.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({\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 entry. SELF-CONTAINED by necessity: this module loads as RAW `.ts`\n// inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports ONLY\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// Its guards are inlined for the same reason. A worker script that needs the cloned\n// `workerData` reads it directly from `node:worker_threads` (it is in a thread already).\n\n// Inlined record guard (do NOT import `isRecord` from `@src/core` — see above). Total:\n// adversarial input returns `false`, never throws (AGENTS §14).\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\ttry {\n\t\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n\t} catch {\n\t\treturn false\n\t}\n}\n\n// Narrow an inbound message to a `run` envelope: `id` is the per-dispatch correlation,\n// while `job` is the stable Queue execution id exposed to the handler. Both are required;\n// a legacy or malformed envelope without a string `job` fails closed without a reply.\nfunction isRun(\n\tvalue: unknown,\n): value is { readonly id: string; readonly job: string; readonly input: unknown } {\n\ttry {\n\t\treturn (\n\t\t\tisRecord(value) &&\n\t\t\ttypeof value.id === 'string' &&\n\t\t\ttypeof value.job === 'string' &&\n\t\t\tvalue.command === 'run' &&\n\t\t\t'input' in value\n\t\t)\n\t} catch {\n\t\treturn false\n\t}\n}\n\n// Narrow an inbound message to an `abort` envelope (a string `id` + an `'abort'` command).\nfunction isAbort(value: unknown): value is { readonly id: string } {\n\ttry {\n\t\treturn isRecord(value) && typeof value.id === 'string' && value.command === 'abort'\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { 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\tif (isAbort(raw)) {\n\t\t\tcontrollers.get(raw.id)?.abort()\n\t\t\treturn\n\t\t}\n\t\tif (!isRun(raw)) return\n\t\tconst id = raw.id\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => {\n\t\t\t\tif (!input(raw.input)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\tconst value = raw.input\n\t\t\t\treturn handler(value, { id: raw.job, signal: controller.signal })\n\t\t\t})\n\t\t\t.then((value) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value })\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tlet message = 'worker operation failed'\n\t\t\t\ttry {\n\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error)\n\t\t\t\t} catch {}\n\t\t\t\ttry {\n\t\t\t\t\tport.postMessage({ id, ok: false, error: message })\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tport.close()\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueExecution, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { dispatch, spawnThread } from './helpers.js'\n\n/**\n * Internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity remains the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn spawnThread(this.#script, this.#workerData)\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, execution: QueueExecution): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn dispatch(thread, input, execution, this.#result)\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\n\n/**\n * Create a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is just a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Create a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker THREAD.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (via `spawnThread`),\n * `destroy`s it with `terminate()`, and `validate`s it by `alive && threadId > 0` (so an\n * evicted / crashed thread is dropped and replaced) — and an internal handler that\n * narrows the input through `options.input` (fail-fast before the structured-clone\n * boundary) then `dispatch`es the job to the leased thread, narrowing the reply through\n * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites\n * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards\n * reconstruct `TInput` / `TResult` by validation (AGENTS §14). An `abort` / `timeout`\n * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
|
|
@@ -85,8 +85,12 @@ export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOpt
|
|
|
85
85
|
* Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.
|
|
86
86
|
*
|
|
87
87
|
* @remarks
|
|
88
|
-
* Mints a fresh `id`, posts
|
|
89
|
-
*
|
|
88
|
+
* Mints a fresh per-dispatch correlation `id`, posts it with `job: execution.id`, and
|
|
89
|
+
* resolves when the thread replies for that correlation id. The stable Queue job id reaches
|
|
90
|
+
* the worker handler for idempotency across retries and restore; it is not caller identity or
|
|
91
|
+
* authentication / authorization evidence. Per-job consumer context remains explicit,
|
|
92
|
+
* structured-cloneable `input`; ambient context is not worker-thread transport. A success
|
|
93
|
+
* `value` is narrowed through `result` (a value that fails the guard
|
|
90
94
|
* rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.
|
|
91
95
|
* A thread that ALREADY died rejects synchronously at entry from the latched
|
|
92
96
|
* {@link NodeThread.death} — its death events fired before this dispatch existed and will
|
|
@@ -211,12 +215,15 @@ export declare type Reply = {
|
|
|
211
215
|
* Must be the spawned thread's module entry. It listens on the parent port for the
|
|
212
216
|
* run/abort protocol: a `run` message narrows its `input` through `options.input` (an
|
|
213
217
|
* invalid payload replies with an error envelope, never running the handler), then runs
|
|
214
|
-
* `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or
|
|
218
|
+
* `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or
|
|
215
219
|
* `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a
|
|
216
220
|
* success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
|
|
217
221
|
* fails, the parent port closes so the main side observes thread exit instead of waiting forever.
|
|
218
|
-
*
|
|
219
|
-
*
|
|
222
|
+
* 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 `execution.id` across retries
|
|
224
|
+
* and restore. That job id identifies work, not a caller, and is not authentication or
|
|
225
|
+
* authorization evidence. Each attempt has its own `AbortController`, so an `abort`
|
|
226
|
+
* message for the correlation id fires the handler's `signal` (cooperative — the main
|
|
220
227
|
* side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
|
|
221
228
|
* Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
|
|
222
229
|
* (`parentPort === null`) it is a no-op.
|
|
@@ -245,18 +252,19 @@ export declare function serveWorker<TInput, TResult>(options: ServeWorkerOptions
|
|
|
245
252
|
* - `input` — narrows each inbound payload inside the thread; an invalid payload replies
|
|
246
253
|
* with an error envelope rather than running the handler. Supplies the `TInput`
|
|
247
254
|
* inference for the handler.
|
|
248
|
-
* - `handler` — runs one job; receives the narrowed input and
|
|
249
|
-
*
|
|
250
|
-
*
|
|
255
|
+
* - `handler` — runs one job; receives the narrowed input and the Queue's execution.
|
|
256
|
+
* `execution.id` is the stable Queue idempotency key across retries and crash restore;
|
|
257
|
+
* it identifies work, not a caller, and is not authentication or authorization evidence.
|
|
258
|
+
* `execution.signal` is per attempt and fires when the main side aborts that attempt
|
|
259
|
+
* (cooperative). The handler may be async; its resolved value (which must be
|
|
260
|
+
* structured-cloneable) is the reply.
|
|
251
261
|
*
|
|
252
262
|
* @typeParam TInput - The work payload (inferred from `input`)
|
|
253
263
|
* @typeParam TResult - The value the handler resolves (the reply payload)
|
|
254
264
|
*/
|
|
255
265
|
export declare interface ServeWorkerOptions<TInput, TResult> {
|
|
256
266
|
readonly input: Guard<TInput>;
|
|
257
|
-
readonly handler: (input: TInput, execution:
|
|
258
|
-
readonly signal: AbortSignal;
|
|
259
|
-
}) => Promise<TResult> | TResult;
|
|
267
|
+
readonly handler: (input: TInput, execution: QueueExecution) => Promise<TResult> | TResult;
|
|
260
268
|
}
|
|
261
269
|
|
|
262
270
|
/**
|
|
@@ -85,8 +85,12 @@ export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOpt
|
|
|
85
85
|
* Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.
|
|
86
86
|
*
|
|
87
87
|
* @remarks
|
|
88
|
-
* Mints a fresh `id`, posts
|
|
89
|
-
*
|
|
88
|
+
* Mints a fresh per-dispatch correlation `id`, posts it with `job: execution.id`, and
|
|
89
|
+
* resolves when the thread replies for that correlation id. The stable Queue job id reaches
|
|
90
|
+
* the worker handler for idempotency across retries and restore; it is not caller identity or
|
|
91
|
+
* authentication / authorization evidence. Per-job consumer context remains explicit,
|
|
92
|
+
* structured-cloneable `input`; ambient context is not worker-thread transport. A success
|
|
93
|
+
* `value` is narrowed through `result` (a value that fails the guard
|
|
90
94
|
* rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.
|
|
91
95
|
* A thread that ALREADY died rejects synchronously at entry from the latched
|
|
92
96
|
* {@link NodeThread.death} — its death events fired before this dispatch existed and will
|
|
@@ -211,12 +215,15 @@ export declare type Reply = {
|
|
|
211
215
|
* Must be the spawned thread's module entry. It listens on the parent port for the
|
|
212
216
|
* run/abort protocol: a `run` message narrows its `input` through `options.input` (an
|
|
213
217
|
* invalid payload replies with an error envelope, never running the handler), then runs
|
|
214
|
-
* `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or
|
|
218
|
+
* `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or
|
|
215
219
|
* `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a
|
|
216
220
|
* success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
|
|
217
221
|
* fails, the parent port closes so the main side observes thread exit instead of waiting forever.
|
|
218
|
-
*
|
|
219
|
-
*
|
|
222
|
+
* 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 `execution.id` across retries
|
|
224
|
+
* and restore. That job id identifies work, not a caller, and is not authentication or
|
|
225
|
+
* authorization evidence. Each attempt has its own `AbortController`, so an `abort`
|
|
226
|
+
* message for the correlation id fires the handler's `signal` (cooperative — the main
|
|
220
227
|
* side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
|
|
221
228
|
* Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
|
|
222
229
|
* (`parentPort === null`) it is a no-op.
|
|
@@ -245,18 +252,19 @@ export declare function serveWorker<TInput, TResult>(options: ServeWorkerOptions
|
|
|
245
252
|
* - `input` — narrows each inbound payload inside the thread; an invalid payload replies
|
|
246
253
|
* with an error envelope rather than running the handler. Supplies the `TInput`
|
|
247
254
|
* inference for the handler.
|
|
248
|
-
* - `handler` — runs one job; receives the narrowed input and
|
|
249
|
-
*
|
|
250
|
-
*
|
|
255
|
+
* - `handler` — runs one job; receives the narrowed input and the Queue's execution.
|
|
256
|
+
* `execution.id` is the stable Queue idempotency key across retries and crash restore;
|
|
257
|
+
* it identifies work, not a caller, and is not authentication or authorization evidence.
|
|
258
|
+
* `execution.signal` is per attempt and fires when the main side aborts that attempt
|
|
259
|
+
* (cooperative). The handler may be async; its resolved value (which must be
|
|
260
|
+
* structured-cloneable) is the reply.
|
|
251
261
|
*
|
|
252
262
|
* @typeParam TInput - The work payload (inferred from `input`)
|
|
253
263
|
* @typeParam TResult - The value the handler resolves (the reply payload)
|
|
254
264
|
*/
|
|
255
265
|
export declare interface ServeWorkerOptions<TInput, TResult> {
|
|
256
266
|
readonly input: Guard<TInput>;
|
|
257
|
-
readonly handler: (input: TInput, execution:
|
|
258
|
-
readonly signal: AbortSignal;
|
|
259
|
-
}) => Promise<TResult> | TResult;
|
|
267
|
+
readonly handler: (input: TInput, execution: QueueExecution) => Promise<TResult> | TResult;
|
|
260
268
|
}
|
|
261
269
|
|
|
262
270
|
/**
|
package/dist/src/server/index.js
CHANGED
|
@@ -167,6 +167,7 @@ var Dispatch = class {
|
|
|
167
167
|
try {
|
|
168
168
|
this.#worker.postMessage({
|
|
169
169
|
id: this.#id,
|
|
170
|
+
job: this.#execution.id,
|
|
170
171
|
command: "run",
|
|
171
172
|
input: this.#input
|
|
172
173
|
});
|
|
@@ -290,8 +291,12 @@ function spawnThread(script, workerData) {
|
|
|
290
291
|
* Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.
|
|
291
292
|
*
|
|
292
293
|
* @remarks
|
|
293
|
-
* Mints a fresh `id`, posts
|
|
294
|
-
*
|
|
294
|
+
* Mints a fresh per-dispatch correlation `id`, posts it with `job: execution.id`, and
|
|
295
|
+
* resolves when the thread replies for that correlation id. The stable Queue job id reaches
|
|
296
|
+
* the worker handler for idempotency across retries and restore; it is not caller identity or
|
|
297
|
+
* authentication / authorization evidence. Per-job consumer context remains explicit,
|
|
298
|
+
* structured-cloneable `input`; ambient context is not worker-thread transport. A success
|
|
299
|
+
* `value` is narrowed through `result` (a value that fails the guard
|
|
295
300
|
* rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.
|
|
296
301
|
* A thread that ALREADY died rejects synchronously at entry from the latched
|
|
297
302
|
* {@link NodeThread.death} — its death events fired before this dispatch existed and will
|
|
@@ -317,13 +322,25 @@ function dispatch(thread, input, execution, result) {
|
|
|
317
322
|
//#endregion
|
|
318
323
|
//#region src/server/serve.ts
|
|
319
324
|
function isRecord$1(value) {
|
|
320
|
-
|
|
325
|
+
try {
|
|
326
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
327
|
+
} catch {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
321
330
|
}
|
|
322
331
|
function isRun(value) {
|
|
323
|
-
|
|
332
|
+
try {
|
|
333
|
+
return isRecord$1(value) && typeof value.id === "string" && typeof value.job === "string" && value.command === "run" && "input" in value;
|
|
334
|
+
} catch {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
324
337
|
}
|
|
325
338
|
function isAbort(value) {
|
|
326
|
-
|
|
339
|
+
try {
|
|
340
|
+
return isRecord$1(value) && typeof value.id === "string" && value.command === "abort";
|
|
341
|
+
} catch {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
327
344
|
}
|
|
328
345
|
/**
|
|
329
346
|
* Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.
|
|
@@ -332,12 +349,15 @@ function isAbort(value) {
|
|
|
332
349
|
* Must be the spawned thread's module entry. It listens on the parent port for the
|
|
333
350
|
* run/abort protocol: a `run` message narrows its `input` through `options.input` (an
|
|
334
351
|
* invalid payload replies with an error envelope, never running the handler), then runs
|
|
335
|
-
* `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or
|
|
352
|
+
* `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or
|
|
336
353
|
* `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a
|
|
337
354
|
* success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
|
|
338
355
|
* fails, the parent port closes so the main side observes thread exit instead of waiting forever.
|
|
339
|
-
*
|
|
340
|
-
*
|
|
356
|
+
* The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;
|
|
357
|
+
* its `job` is the stable Queue idempotency key exposed as `execution.id` across retries
|
|
358
|
+
* and restore. That job id identifies work, not a caller, and is not authentication or
|
|
359
|
+
* authorization evidence. Each attempt has its own `AbortController`, so an `abort`
|
|
360
|
+
* message for the correlation id fires the handler's `signal` (cooperative — the main
|
|
341
361
|
* side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
|
|
342
362
|
* Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
|
|
343
363
|
* (`parentPort === null`) it is a no-op.
|
|
@@ -375,7 +395,10 @@ function serveWorker(options) {
|
|
|
375
395
|
Promise.resolve().then(() => {
|
|
376
396
|
if (!input(raw.input)) throw new Error("input did not satisfy input guard");
|
|
377
397
|
const value = raw.input;
|
|
378
|
-
return handler(value, {
|
|
398
|
+
return handler(value, {
|
|
399
|
+
id: raw.job,
|
|
400
|
+
signal: controller.signal
|
|
401
|
+
});
|
|
379
402
|
}).then((value) => {
|
|
380
403
|
controllers.delete(id);
|
|
381
404
|
port.postMessage({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#worker","#promise","#resolve","#reject","#recordHandler","#recordExitHandler","#onlineHandler","#spawnErrorHandler","#spawnExitHandler","#record","#recordExit","#online","#spawnError","#spawnExit","#alive","#death","#thread","#worker","#input","#execution","#result","#id","#promise","#fulfill","#reject","#messageHandler","#messageErrorHandler","#errorHandler","#exitHandler","#abortHandler","#message","#messageError","#error","#exit","#abort","#start","#fail","#terminate","#succeed","#settled","#detach","#script","#input","#result","#workerData","#concurrency","#retries","#timeout","#store","#create","#destroy","#validate","#handle"],"sources":["../../../src/server/Thread.ts","../../../src/server/validators.ts","../../../src/server/Dispatch.ts","../../../src/server/helpers.ts","../../../src/server/serve.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Internal mutable implementation of the readonly {@link NodeThread} observation contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { Reply } from './types.js'\nimport { attempt, isRecord } from '@orkestrel/contract'\n\n/**\n * Narrow an inbound `message` to a {@link Reply} for a given job `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n *\n * @param value - The inbound message to narrow\n * @param id - The job id a matching reply must carry\n * @returns `true` when the value is this job's well-formed reply\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && typeof value.error === 'string'\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isRecord } from '@orkestrel/contract'\nimport { Thread } from './Thread.js'\nimport { isReply } from './validators.js'\n\n/**\n * Internal lifecycle entity for one dispatched worker-thread job.\n *\n * @remarks\n * Owns stable `message` / `messageerror` / death listener identities, settlement, result-guard\n * containment, and abort eviction for one dispatch. Deserialization failure, a matching-id\n * malformed reply, and abort each evict and terminate the thread before rejecting, with\n * termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter is ignored.\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #execution: QueueExecution\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(\n\t\tthread: NodeThread,\n\t\tinput: unknown,\n\t\texecution: QueueExecution,\n\t\tresult: Guard<TResult>,\n\t) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#execution = execution\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#execution.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#execution.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'run', input: this.#input })\n\t\t} catch (error: unknown) {\n\t\t\tthis.#fail(error instanceof Error ? error : new Error(String(error)))\n\t\t}\n\t}\n\n\t#message(value: unknown): void {\n\t\tif (!isRecord(value)) return\n\t\tconst id = attempt(() => value.id)\n\t\tif (!id.success || id.value !== this.#id) return\n\t\tif (!isReply(value, this.#id)) {\n\t\t\tthis.#terminate(new Error('worker reply was malformed'))\n\t\t\treturn\n\t\t}\n\t\tif (value.ok) {\n\t\t\tconst reply = value.value\n\t\t\ttry {\n\t\t\t\tif (this.#result(reply)) this.#succeed(reply)\n\t\t\t\telse this.#fail(new Error('reply did not satisfy result guard'))\n\t\t\t} catch (error: unknown) {\n\t\t\t\tthis.#fail(error)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tthis.#fail(new Error(value.error))\n\t}\n\n\t#messageError(error: Error): void {\n\t\tthis.#terminate(error)\n\t}\n\n\t#error(error: Error): void {\n\t\tthis.#fail(error)\n\t}\n\n\t#exit(): void {\n\t\tthis.#fail(this.#thread.death ?? new Error('worker thread exited'))\n\t}\n\n\t#abort(): void {\n\t\tconst notification: unknown[] = []\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'abort' })\n\t\t} catch (cause: unknown) {\n\t\t\tnotification.push(cause)\n\t\t}\n\t\tthis.#terminate(this.#execution.signal.reason, notification)\n\t}\n\n\t#terminate(error: unknown, notification: readonly unknown[] = []): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tif (this.#thread instanceof Thread) this.#thread.evict()\n\t\tlet termination: Promise<number>\n\t\ttry {\n\t\t\ttermination = this.#worker.terminate()\n\t\t} catch (cause: unknown) {\n\t\t\tthis.#reject(new AggregateError([error, ...notification, cause], 'worker termination failed'))\n\t\t\treturn\n\t\t}\n\t\tvoid termination.then(\n\t\t\t() => {\n\t\t\t\tif (notification.length === 0) this.#reject(error)\n\t\t\t\telse {\n\t\t\t\t\tthis.#reject(\n\t\t\t\t\t\tnew AggregateError([error, ...notification], 'worker abort notification failed'),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\t\t\t(cause: unknown) =>\n\t\t\t\tthis.#reject(\n\t\t\t\t\tnew AggregateError([error, ...notification, cause], 'worker termination failed'),\n\t\t\t\t),\n\t\t)\n\t}\n\n\t#succeed(value: TResult): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#fulfill(value)\n\t}\n\n\t#fail(error: unknown): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#reject(error)\n\t}\n\n\t#detach(): void {\n\t\tthis.#worker.off('message', this.#messageHandler)\n\t\tthis.#worker.off('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.off('error', this.#errorHandler)\n\t\tthis.#worker.off('exit', this.#exitHandler)\n\t\tthis.#execution.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n// === The wire protocol (main ↔ thread)\n//\n// The main-side half of the run/abort/reply protocol `serveWorker` answers — spawning a\n// pooled thread, narrowing its replies, and dispatching one job at a time. The envelope\n// types ({@link Reply}, {@link NodeThread}) live in `./types.js` (AGENTS §5); the public\n// bridge across the structured-clone boundary is the `input` / `result` `Guard`s, which\n// narrow the envelopes' opaque `unknown` payloads with no assertion (AGENTS §14).\n\n/**\n * Spawn one worker thread and resolve a live {@link NodeThread} once it comes online.\n *\n * @remarks\n * Constructs the thread with the `script` module and the cloned `workerData`, then\n * resolves on the thread's `online` event (rejecting on an early `error` OR an `exit`\n * that arrives before `online`, so the spawn promise is total — it can never dangle on a\n * thread that died without erroring). The wrapper attaches persistent `error` / `exit`\n * listeners that flip `alive` to `false` AND latch the first terminal event on\n * {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via\n * its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a\n * dispatch that attaches AFTER the death (via the latch). A `messageerror` is terminal too,\n * so a thread whose inbound payload could not be deserialized is never reused. The latch\n * closes a real race: a thread can become terminal before the readiness promise continuation\n * hands it to `dispatch`, leaving no future death event for that dispatch to observe. Without\n * the latch, that job would wait forever. The pool's `create` hook calls this.\n *\n * @param script - The worker module each thread runs (must call `serveWorker`)\n * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn\n * @returns A promise resolving the online {@link NodeThread}\n */\nexport function spawnThread(script: string | URL, workerData: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Dispatch one job to a leased {@link NodeThread} and await its narrowed reply.\n *\n * @remarks\n * Mints a fresh `id`, posts a `run` envelope, and resolves when the thread replies for\n * that id: a success `value` is narrowed through `result` (a value that fails the guard\n * rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.\n * A thread that ALREADY died rejects synchronously at entry from the latched\n * {@link NodeThread.death} — its death events fired before this dispatch existed and will\n * never fire again, so waiting on the listeners below would dangle forever; the latch makes\n * death total across every event ordering. If the thread `error`s / `exit`s mid-flight it is\n * marked dead and the\n * job rejects. An inbound `messageerror` also evicts and terminates the thread before\n * rejection. On `execution.signal` abort it contains the cooperative `abort` post,\n * evicts the thread, and observes `terminate()` settlement because CPU-bound work cannot\n * honour the signal; the freed pool slot then gets a fresh thread. Every per-job listener\n * (`message` / `messageerror` / `error` / `exit` / `abort`) is removed on settle.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n * @param thread - The leased thread to run the job on\n * @param input - The work payload (structured-cloned to the thread)\n * @param execution - The per-attempt handle; its `signal` aborts → terminate + evict\n * @param result - The {@link Guard} narrowing the reply value with no assertion\n * @returns A promise resolving the narrowed `TResult`, or rejecting on error / abort\n */\nexport function dispatch<TResult>(\n\tthread: NodeThread,\n\tinput: unknown,\n\texecution: QueueExecution,\n\tresult: Guard<TResult>,\n): Promise<TResult> {\n\treturn new Dispatch(thread, input, execution, result).promise\n}\n","import type { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side entry. SELF-CONTAINED by necessity: this module loads as RAW `.ts`\n// inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports ONLY\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// Its guards are inlined for the same reason. A worker script that needs the cloned\n// `workerData` reads it directly from `node:worker_threads` (it is in a thread already).\n\n// Inlined record guard (do NOT import `isRecord` from `@src/core` — see above). Total:\n// adversarial input returns `false`, never throws (AGENTS §14).\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n// Narrow an inbound message to a `run` envelope (a string `id` + a `'run'` command + an\n// `input` payload) — no assertion.\nfunction isRun(value: unknown): value is { readonly id: string; readonly input: unknown } {\n\treturn (\n\t\tisRecord(value) && typeof value.id === 'string' && value.command === 'run' && 'input' in value\n\t)\n}\n\n// Narrow an inbound message to an `abort` envelope (a string `id` + an `'abort'` command).\nfunction isAbort(value: unknown): value is { readonly id: string } {\n\treturn isRecord(value) && typeof value.id === 'string' && value.command === 'abort'\n}\n\n/**\n * Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or\n * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a\n * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also\n * fails, the parent port closes so the main side observes thread exit instead of waiting forever.\n * Each in-flight job has its own `AbortController`,\n * so an `abort` message for that id fires the handler's `signal` (cooperative — the main\n * side ALSO terminates the thread, so a handler that ignores its signal is still stopped).\n * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread\n * (`parentPort === null`) it is a no-op.\n *\n * @typeParam TInput - The work payload (inferred from `options.input`)\n * @typeParam TResult - The value the handler resolves (the reply payload)\n * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})\n *\n * @example\n * ```ts\n * // double.ts — a worker script\n * import { serveWorker } from '@orkestrel/worker/server'\n *\n * serveWorker<number, number>({\n * \tinput: (value): value is number => typeof value === 'number',\n * \thandler: (value) => value * 2,\n * })\n * ```\n */\nexport function serveWorker<TInput, TResult>(options: ServeWorkerOptions<TInput, TResult>): void {\n\tconst port = parentPort\n\tif (port === null) return\n\tconst input = options.input\n\tconst handler = options.handler\n\tconst controllers = new Map<string, AbortController>()\n\tport.on('message', (raw: unknown) => {\n\t\tif (isAbort(raw)) {\n\t\t\tcontrollers.get(raw.id)?.abort()\n\t\t\treturn\n\t\t}\n\t\tif (!isRun(raw)) return\n\t\tconst id = raw.id\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => {\n\t\t\t\tif (!input(raw.input)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\tconst value = raw.input\n\t\t\t\treturn handler(value, { signal: controller.signal })\n\t\t\t})\n\t\t\t.then((value) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value })\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tlet message = 'worker operation failed'\n\t\t\t\ttry {\n\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error)\n\t\t\t\t} catch {}\n\t\t\t\ttry {\n\t\t\t\t\tport.postMessage({ id, ok: false, error: message })\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tport.close()\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueExecution, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { dispatch, spawnThread } from './helpers.js'\n\n/**\n * Internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity remains the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn spawnThread(this.#script, this.#workerData)\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, execution: QueueExecution): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn dispatch(thread, input, execution, this.#result)\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\n\n/**\n * Create a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is just a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Create a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker THREAD.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (via `spawnThread`),\n * `destroy`s it with `terminate()`, and `validate`s it by `alive && threadId > 0` (so an\n * evicted / crashed thread is dropped and replaced) — and an internal handler that\n * narrows the input through `options.input` (fail-fast before the structured-clone\n * boundary) then `dispatch`es the job to the leased thread, narrowing the reply through\n * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites\n * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards\n * reconstruct `TInput` / `TResult` by validation (AGENTS §14). An `abort` / `timeout`\n * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["#worker","#promise","#resolve","#reject","#recordHandler","#recordExitHandler","#onlineHandler","#spawnErrorHandler","#spawnExitHandler","#record","#recordExit","#online","#spawnError","#spawnExit","#alive","#death","#thread","#worker","#input","#execution","#result","#id","#promise","#fulfill","#reject","#messageHandler","#messageErrorHandler","#errorHandler","#exitHandler","#abortHandler","#message","#messageError","#error","#exit","#abort","#start","#fail","#terminate","#succeed","#settled","#detach","#script","#input","#result","#workerData","#concurrency","#retries","#timeout","#store","#create","#destroy","#validate","#handle"],"sources":["../../../src/server/Thread.ts","../../../src/server/validators.ts","../../../src/server/Dispatch.ts","../../../src/server/helpers.ts","../../../src/server/serve.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Internal mutable implementation of the readonly {@link NodeThread} observation contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { Reply } from './types.js'\nimport { attempt, isRecord } from '@orkestrel/contract'\n\n/**\n * Narrow an inbound `message` to a {@link Reply} for a given job `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n *\n * @param value - The inbound message to narrow\n * @param id - The job id a matching reply must carry\n * @returns `true` when the value is this job's well-formed reply\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && typeof value.error === 'string'\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { QueueExecution } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isRecord } from '@orkestrel/contract'\nimport { Thread } from './Thread.js'\nimport { isReply } from './validators.js'\n\n/**\n * Internal lifecycle entity for one dispatched worker-thread job.\n *\n * @remarks\n * Owns stable `message` / `messageerror` / death listener identities, settlement, result-guard\n * containment, and abort eviction for one dispatch. Deserialization failure, a matching-id\n * malformed reply, and abort each evict and terminate the thread before rejecting, with\n * termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter is ignored.\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #execution: QueueExecution\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(\n\t\tthread: NodeThread,\n\t\tinput: unknown,\n\t\texecution: QueueExecution,\n\t\tresult: Guard<TResult>,\n\t) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#execution = execution\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#execution.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#execution.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({\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 entry. SELF-CONTAINED by necessity: this module loads as RAW `.ts`\n// inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports ONLY\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// Its guards are inlined for the same reason. A worker script that needs the cloned\n// `workerData` reads it directly from `node:worker_threads` (it is in a thread already).\n\n// Inlined record guard (do NOT import `isRecord` from `@src/core` — see above). Total:\n// adversarial input returns `false`, never throws (AGENTS §14).\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\ttry {\n\t\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n\t} catch {\n\t\treturn false\n\t}\n}\n\n// Narrow an inbound message to a `run` envelope: `id` is the per-dispatch correlation,\n// while `job` is the stable Queue execution id exposed to the handler. Both are required;\n// a legacy or malformed envelope without a string `job` fails closed without a reply.\nfunction isRun(\n\tvalue: unknown,\n): value is { readonly id: string; readonly job: string; readonly input: unknown } {\n\ttry {\n\t\treturn (\n\t\t\tisRecord(value) &&\n\t\t\ttypeof value.id === 'string' &&\n\t\t\ttypeof value.job === 'string' &&\n\t\t\tvalue.command === 'run' &&\n\t\t\t'input' in value\n\t\t)\n\t} catch {\n\t\treturn false\n\t}\n}\n\n// Narrow an inbound message to an `abort` envelope (a string `id` + an `'abort'` command).\nfunction isAbort(value: unknown): value is { readonly id: string } {\n\ttry {\n\t\treturn isRecord(value) && typeof value.id === 'string' && value.command === 'abort'\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Register a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { 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\tif (isAbort(raw)) {\n\t\t\tcontrollers.get(raw.id)?.abort()\n\t\t\treturn\n\t\t}\n\t\tif (!isRun(raw)) return\n\t\tconst id = raw.id\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => {\n\t\t\t\tif (!input(raw.input)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\tconst value = raw.input\n\t\t\t\treturn handler(value, { id: raw.job, signal: controller.signal })\n\t\t\t})\n\t\t\t.then((value) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value })\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tlet message = 'worker operation failed'\n\t\t\t\ttry {\n\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error)\n\t\t\t\t} catch {}\n\t\t\t\ttry {\n\t\t\t\t\tport.postMessage({ id, ok: false, error: message })\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tport.close()\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueExecution, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { dispatch, spawnThread } from './helpers.js'\n\n/**\n * Internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity remains the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn spawnThread(this.#script, this.#workerData)\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, execution: QueueExecution): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn dispatch(thread, input, execution, this.#result)\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\n\n/**\n * Create a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is just a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the SAME `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Create a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker THREAD.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (via `spawnThread`),\n * `destroy`s it with `terminate()`, and `validate`s it by `alive && threadId > 0` (so an\n * evicted / crashed thread is dropped and replaced) — and an internal handler that\n * narrows the input through `options.input` (fail-fast before the structured-clone\n * boundary) then `dispatch`es the job to the leased thread, narrowing the reply through\n * `options.result`. Both generics INFER from the `input` / `result` guards, so call sites\n * need no explicit type arguments. The boundary is crossed with ZERO `as`: the guards\n * reconstruct `TInput` / `TResult` by validation (AGENTS §14). An `abort` / `timeout`\n * TERMINATES the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkestrel/worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "A typed, resource-backed job worker for the @orkestrel line — a Queue paired with a Pool over an execution seam, plus a node:worker_threads server surface for CPU-parallel jobs. Part of the @orkestrel line.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"async",
|
|
@@ -77,20 +77,20 @@
|
|
|
77
77
|
"prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@orkestrel/contract": "^0.0.
|
|
81
|
-
"@orkestrel/database": "^0.0.
|
|
82
|
-
"@orkestrel/emitter": "^0.0.
|
|
83
|
-
"@orkestrel/pool": "^0.0.
|
|
84
|
-
"@orkestrel/queue": "^0.0.
|
|
80
|
+
"@orkestrel/contract": "^0.0.11",
|
|
81
|
+
"@orkestrel/database": "^0.0.8",
|
|
82
|
+
"@orkestrel/emitter": "^0.0.6",
|
|
83
|
+
"@orkestrel/pool": "^0.0.7",
|
|
84
|
+
"@orkestrel/queue": "^0.0.8"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@microsoft/api-extractor": "^7.58.12",
|
|
88
|
-
"@orkestrel/guide": "^0.0.
|
|
89
|
-
"@orkestrel/scaffold": "^0.0.
|
|
88
|
+
"@orkestrel/guide": "^0.0.10",
|
|
89
|
+
"@orkestrel/scaffold": "^0.0.26",
|
|
90
90
|
"@types/node": "^26.1.2",
|
|
91
91
|
"@vitest/browser-playwright": "^4.1.10",
|
|
92
|
-
"oxfmt": "^0.
|
|
93
|
-
"oxlint": "^1.
|
|
92
|
+
"oxfmt": "^0.62.0",
|
|
93
|
+
"oxlint": "^1.77.0",
|
|
94
94
|
"typescript": "^6.0.3",
|
|
95
95
|
"vite": "^8.2.0",
|
|
96
96
|
"vite-plugin-dts": "^5.0.3",
|