@orkestrel/worker 0.0.13 → 0.0.15
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/dist/src/core/index.cjs +15 -6
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +13 -6
- package/dist/src/core/index.d.ts +13 -6
- package/dist/src/core/index.js +15 -6
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js.map +1 -1
- package/package.json +15 -15
package/dist/src/core/index.cjs
CHANGED
|
@@ -14,11 +14,15 @@ let _orkestrel_queue = require("@orkestrel/queue");
|
|
|
14
14
|
* timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
|
|
15
15
|
* - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
|
|
16
16
|
* safe integer after caller options are captured once. Only `undefined` defaults
|
|
17
|
-
* `concurrency` to `1
|
|
18
|
-
*
|
|
17
|
+
* `concurrency` to `1`; pool `max` defaults to concurrency only when `max` and `min` are
|
|
18
|
+
* both absent. With `min`, Pool defaults `max` to `min`, requires their equality and a
|
|
19
|
+
* `restarts` bound, and owns validation. Runtime `null` reaches the owning validator.
|
|
20
|
+
* The queue validates before the pool option is read; every declared pool member
|
|
19
21
|
* is then captured once by direct access, preserving inherited and non-enumerable structural
|
|
20
22
|
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
21
|
-
* reused across jobs.
|
|
23
|
+
* reused across jobs. A configured floor starts warming at construction, independently of
|
|
24
|
+
* queue concurrency, and can retain more resources than jobs in flight. A spent floor's
|
|
25
|
+
* startup failure reaches jobs through acquire; startup rejection is observed internally.
|
|
22
26
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
23
27
|
* `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
24
28
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
@@ -63,16 +67,20 @@ var Worker = class {
|
|
|
63
67
|
...timeout !== void 0 ? { timeout } : {},
|
|
64
68
|
...store !== void 0 ? { store } : {}
|
|
65
69
|
});
|
|
66
|
-
const { max, on: poolOn, error: poolError, create, destroy, validate } = options.pool;
|
|
70
|
+
const { max, min, restarts, watch, on: poolOn, error: poolError, create, destroy, validate } = options.pool;
|
|
67
71
|
this.#pool = new _orkestrel_pool.Pool({
|
|
68
72
|
create,
|
|
69
|
-
max
|
|
73
|
+
...max === void 0 ? min === void 0 ? { max: concurrency } : {} : { max },
|
|
74
|
+
...min !== void 0 ? { min } : {},
|
|
75
|
+
...restarts !== void 0 ? { restarts } : {},
|
|
76
|
+
...watch !== void 0 ? { watch } : {},
|
|
70
77
|
...poolOn !== void 0 ? { on: poolOn } : {},
|
|
71
78
|
...poolError !== void 0 ? { error: poolError } : {},
|
|
72
79
|
...destroy !== void 0 ? { destroy } : {},
|
|
73
80
|
...validate !== void 0 ? { validate } : {}
|
|
74
81
|
});
|
|
75
82
|
this.#bridge();
|
|
83
|
+
this.#pool.start().catch(() => {});
|
|
76
84
|
}
|
|
77
85
|
get emitter() {
|
|
78
86
|
return this.#emitter;
|
|
@@ -165,7 +173,8 @@ var Worker = class {
|
|
|
165
173
|
*
|
|
166
174
|
* @remarks
|
|
167
175
|
* Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.
|
|
168
|
-
*
|
|
176
|
+
* When neither pool `max` nor `min` is given, `max` defaults to `concurrency`. With `min`,
|
|
177
|
+
* Pool owns the capacity defaults and validation, and the worker starts warming the floor.
|
|
169
178
|
* Resources are reused across jobs. A handler that throws still releases its resource (the
|
|
170
179
|
* acquire/release pair brackets the call in a `finally`), so a later job reuses it. The
|
|
171
180
|
* lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/Worker.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { QueueContext, QueueEntryOptions } from '@orkestrel/queue'\nimport type { WorkerEventMap, WorkerHandler, WorkerInterface, WorkerOptions } from './types.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport { Pool } from '@orkestrel/pool'\nimport { Queue } from '@orkestrel/queue'\n\n/**\n * Represents a resource-backed job worker — a thin facade composing a `Queue`\n * (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).\n *\n * @remarks\n * - **Composition, not reimplementation.** The Worker owns a `Pool` (built from\n * `options.pool`) and a `Queue` whose handler `acquire`s a pooled resource, runs the\n * user handler against it, and `release`s it in a `finally`. All concurrency, retries,\n * timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.\n * - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive\n * safe integer after caller options are captured once. Only `undefined` defaults\n * `concurrency` to `1` or pool `max` to that value; runtime `null` reaches the owning\n * validator. The queue validates before the pool option is read; every declared pool member\n * is then captured once by direct access, preserving inherited and non-enumerable structural\n * options. At most one resource exists per in-flight job by default, and idle resources are\n * reused across jobs.\n * - **Acquire over the attempt signal.** Each job acquires using the attempt's\n * `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects\n * the acquire — the Queue then handles retry / rejection, and there is no token to\n * release (the resource was never leased).\n * - **Lifecycle (see the guide's `## Methods` section).** `enqueue` / `restore` / `start` /\n * `stop` / `pause` / `resume` / `abort` / `clear` delegate to the queue; `count` / `active` /\n * `paused` / `stopped` read it. `stop` / `abort` / `clear` return the queue's own cleanup\n * barriers. `destroy` returns one stable barrier while it tears down the queue, then the\n * pool, and destroys the worker emitter last. A sole cleanup failure is preserved by\n * identity; failures from both layers become an ordered `AggregateError`.\n * - **Durability.** An optional `store` is passed straight through to the queue, so the\n * worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).\n * - **Observable (see the guide's `## Observing` section).** The owned {@link emitter}\n * ({@link WorkerEventMap}) re-exposes the underlying queue's job lifecycle (`enqueue` /\n * `start` / `retry` / `success` / `failure` / `abort` / `drain`) as the worker's own events —\n * bridged from the inner queue's emitter at construction — so a consumer observes the worker\n * without reaching through to internals. The bridge re-emits directly on the worker's own\n * emitter; the worker emitter isolates a listener throw and routes it to its `error` handler\n * (the `error` option), so a buggy worker observer can never corrupt the inner queue or pool\n * — the bridge listener never throws, so the inner queue's own emit stays balanced. The\n * pool's create / acquire / release events stay the pool's internal concern (a Worker manages\n * its own resources); observe a `Pool` directly for those.\n */\nexport class Worker<TInput, TResource, TResult> implements WorkerInterface<TInput, TResult> {\n\treadonly #queue: Queue<TInput, TResult>\n\treadonly #pool: Pool<TResource>\n\t// The push observation surface (see the guide's `## Observing` section) — the worker's own\n\t// emitter, fed by the queue→worker bridge. The emitter isolates a worker observer's throw\n\t// (routing it to the `error` handler), so it never escapes into queue or pool.\n\treadonly #emitter: Emitter<WorkerEventMap<TResult>>\n\treadonly #handler: WorkerHandler<TInput, TResource, TResult>\n\t#ending: PromiseWithResolvers<void> | undefined\n\n\tconstructor(options: WorkerOptions<TInput, TResource, TResult>) {\n\t\tconst {\n\t\t\tconcurrency: capturedConcurrency,\n\t\t\thandler,\n\t\t\ton,\n\t\t\terror,\n\t\t\tretries,\n\t\t\ttimeout,\n\t\t\tstore,\n\t\t} = options\n\t\tconst concurrency = capturedConcurrency === undefined ? 1 : capturedConcurrency\n\t\tthis.#handler = handler\n\t\tthis.#emitter = new Emitter<WorkerEventMap<TResult>>({\n\t\t\t...(on !== undefined ? { on } : {}),\n\t\t\t...(error !== undefined ? { error } : {}),\n\t\t})\n\t\tthis.#queue = new Queue<TInput, TResult>({\n\t\t\thandler: this.#handle.bind(this),\n\t\t\tconcurrency,\n\t\t\t...(retries !== undefined ? { retries } : {}),\n\t\t\t...(timeout !== undefined ? { timeout } : {}),\n\t\t\t...(store !== undefined ? { store } : {}),\n\t\t})\n\t\tconst pool = options.pool\n\t\tconst { max, on: poolOn, error: poolError, create, destroy, validate } = pool\n\t\tthis.#pool = new Pool<TResource>({\n\t\t\tcreate,\n\t\t\tmax: max === undefined ? concurrency : max,\n\t\t\t...(poolOn !== undefined ? { on: poolOn } : {}),\n\t\t\t...(poolError !== undefined ? { error: poolError } : {}),\n\t\t\t...(destroy !== undefined ? { destroy } : {}),\n\t\t\t...(validate !== undefined ? { validate } : {}),\n\t\t})\n\t\tthis.#bridge()\n\t}\n\n\tget emitter(): EmitterInterface<WorkerEventMap<TResult>> {\n\t\treturn this.#emitter\n\t}\n\n\tget count(): number {\n\t\treturn this.#queue.count\n\t}\n\n\tget active(): number {\n\t\treturn this.#queue.active\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#queue.paused\n\t}\n\n\tget stopped(): boolean {\n\t\treturn this.#queue.stopped\n\t}\n\n\tenqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult> {\n\t\treturn this.#queue.enqueue(input, options)\n\t}\n\n\trestore(): Promise<void> {\n\t\treturn this.#queue.restore()\n\t}\n\n\tstart(): void {\n\t\tthis.#queue.start()\n\t}\n\n\tstop(): Promise<void> {\n\t\treturn this.#queue.stop()\n\t}\n\n\tpause(): void {\n\t\tthis.#queue.pause()\n\t}\n\n\tresume(): void {\n\t\tthis.#queue.resume()\n\t}\n\n\tabort(reason?: unknown): Promise<void> {\n\t\treturn this.#queue.abort(reason)\n\t}\n\n\tclear(): Promise<void> {\n\t\treturn this.#queue.clear()\n\t}\n\n\tdestroy(): Promise<void> {\n\t\tif (this.#ending !== undefined) return this.#ending.promise\n\t\tconst ending = Promise.withResolvers<void>()\n\t\tthis.#ending = ending\n\t\tvoid this.#teardown(ending)\n\t\treturn ending.promise\n\t}\n\n\tasync #handle(input: TInput, context: QueueContext): Promise<TResult> {\n\t\tconst token = await this.#pool.acquire(context.signal)\n\t\ttry {\n\t\t\treturn await this.#handler(input, token.value, context)\n\t\t} finally {\n\t\t\ttoken.release()\n\t\t}\n\t}\n\n\tasync #teardown(ending: PromiseWithResolvers<void>): Promise<void> {\n\t\tconst failures: unknown[] = []\n\t\ttry {\n\t\t\tawait this.#queue.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\ttry {\n\t\t\tawait this.#pool.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\tthis.#emitter.destroy()\n\t\tif (failures.length === 0) ending.resolve()\n\t\telse if (failures.length === 1) ending.reject(failures[0])\n\t\telse ending.reject(new AggregateError(failures, 'worker destroy cleanup failed'))\n\t}\n\n\t// Bridge the inner queue's lifecycle onto the worker's own emitter, once at construction.\n\t// Each listener re-emits the queue event directly on the worker's emitter, which isolates a\n\t// worker observer's throw (routing it to the worker's `error` handler). Because the bridge\n\t// listener itself never throws, the queue's own `#emitter.emit` — which invoked this\n\t// listener — sees no throw, so the inner queue's engine stays balanced regardless of what a\n\t// worker observer does. The events are already post-transition (they fire from the queue's\n\t// own post-settle / post-wake emits), so this stays observation.\n\t#bridge(): void {\n\t\tconst queue = this.#queue.emitter\n\t\tqueue.on('enqueue', (id) => this.#emitter.emit('enqueue', id))\n\t\tqueue.on('start', (id) => this.#emitter.emit('start', id))\n\t\tqueue.on('retry', (id, attempt) => this.#emitter.emit('retry', id, attempt))\n\t\tqueue.on('success', (id, result) => this.#emitter.emit('success', id, result))\n\t\tqueue.on('failure', (id, error) => this.#emitter.emit('failure', id, error))\n\t\tqueue.on('abort', (reason) => this.#emitter.emit('abort', reason))\n\t\tqueue.on('drain', () => this.#emitter.emit('drain'))\n\t}\n}\n","import type { WorkerInterface, WorkerOptions } from './types.js'\nimport { Worker } from './Worker.js'\n\n/**\n * Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) composed with a\n * `Pool` (`@orkestrel/pool`), where each enqueued input runs through the handler against\n * an automatically acquired pooled resource released when the job settles.\n *\n * @remarks\n * Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.\n * Default for the pool's `max`: the `concurrency` value, so resources match the jobs in flight.\n * Resources are reused across jobs. A handler that throws still releases its resource (the\n * acquire/release pair brackets the call in a `finally`), so a later job reuses it. The\n * lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)\n * delegates to the queue; `destroy` also tears the pool down. It is observable (see the\n * guide's `## Observing` section): a typed `emitter` surfaces the queue lifecycle\n * (`enqueue` / `start` / `success` / `failure` / …).\n *\n * @typeParam TInput - The work input each job carries\n * @typeParam TResource - The pooled resource each job runs against\n * @typeParam TResult - The value the handler resolves for a job\n * @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,\n * `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})\n * @returns A working {@link WorkerInterface}\n *\n * @example A resource-backed worker\n * ```ts\n * import { createWorker } from '@orkestrel/worker'\n *\n * // A Queue whose handler runs each job against a pooled resource (acquired before the\n * // handler, released after it — even on throw). The pool's `max` defaults to `concurrency`.\n * const worker = createWorker<Query, Connection, Rows>({\n * \tpool: { create: () => connect(), destroy: (connection) => connection.close() },\n * \thandler: (query, connection, { signal }) => connection.run(query, signal),\n * \tconcurrency: 4,\n * \tretries: 1,\n * })\n *\n * const rows = await worker.enqueue(query)\n * await worker.destroy() // awaits queue cleanup, pool cleanup, then emitter teardown\n * ```\n */\nexport function createWorker<TInput, TResource, TResult>(\n\toptions: WorkerOptions<TInput, TResource, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new Worker(options)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Emitter","Queue","Pool"],"sources":["../../../src/core/Worker.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { QueueContext, QueueEntryOptions } from '@orkestrel/queue'\nimport type { WorkerEventMap, WorkerHandler, WorkerInterface, WorkerOptions } from './types.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport { Pool } from '@orkestrel/pool'\nimport { Queue } from '@orkestrel/queue'\n\n/**\n * Represents a resource-backed job worker — a thin facade composing a `Queue`\n * (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).\n *\n * @remarks\n * - **Composition, not reimplementation.** The Worker owns a `Pool` (built from\n * `options.pool`) and a `Queue` whose handler `acquire`s a pooled resource, runs the\n * user handler against it, and `release`s it in a `finally`. All concurrency, retries,\n * timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.\n * - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive\n * safe integer after caller options are captured once. Only `undefined` defaults\n * `concurrency` to `1`; pool `max` defaults to concurrency only when `max` and `min` are\n * both absent. With `min`, Pool defaults `max` to `min`, requires their equality and a\n * `restarts` bound, and owns validation. Runtime `null` reaches the owning validator.\n * The queue validates before the pool option is read; every declared pool member\n * is then captured once by direct access, preserving inherited and non-enumerable structural\n * options. At most one resource exists per in-flight job by default, and idle resources are\n * reused across jobs. A configured floor starts warming at construction, independently of\n * queue concurrency, and can retain more resources than jobs in flight. A spent floor's\n * startup failure reaches jobs through acquire; startup rejection is observed internally.\n * - **Acquire over the attempt signal.** Each job acquires using the attempt's\n * `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects\n * the acquire — the Queue then handles retry / rejection, and there is no token to\n * release (the resource was never leased).\n * - **Lifecycle (see the guide's `## Methods` section).** `enqueue` / `restore` / `start` /\n * `stop` / `pause` / `resume` / `abort` / `clear` delegate to the queue; `count` / `active` /\n * `paused` / `stopped` read it. `stop` / `abort` / `clear` return the queue's own cleanup\n * barriers. `destroy` returns one stable barrier while it tears down the queue, then the\n * pool, and destroys the worker emitter last. A sole cleanup failure is preserved by\n * identity; failures from both layers become an ordered `AggregateError`.\n * - **Durability.** An optional `store` is passed straight through to the queue, so the\n * worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).\n * - **Observable (see the guide's `## Observing` section).** The owned {@link emitter}\n * ({@link WorkerEventMap}) re-exposes the underlying queue's job lifecycle (`enqueue` /\n * `start` / `retry` / `success` / `failure` / `abort` / `drain`) as the worker's own events —\n * bridged from the inner queue's emitter at construction — so a consumer observes the worker\n * without reaching through to internals. The bridge re-emits directly on the worker's own\n * emitter; the worker emitter isolates a listener throw and routes it to its `error` handler\n * (the `error` option), so a buggy worker observer can never corrupt the inner queue or pool\n * — the bridge listener never throws, so the inner queue's own emit stays balanced. The\n * pool's create / acquire / release events stay the pool's internal concern (a Worker manages\n * its own resources); observe a `Pool` directly for those.\n */\nexport class Worker<TInput, TResource, TResult> implements WorkerInterface<TInput, TResult> {\n\treadonly #queue: Queue<TInput, TResult>\n\treadonly #pool: Pool<TResource>\n\t// The push observation surface (see the guide's `## Observing` section) — the worker's own\n\t// emitter, fed by the queue→worker bridge. The emitter isolates a worker observer's throw\n\t// (routing it to the `error` handler), so it never escapes into queue or pool.\n\treadonly #emitter: Emitter<WorkerEventMap<TResult>>\n\treadonly #handler: WorkerHandler<TInput, TResource, TResult>\n\t#ending: PromiseWithResolvers<void> | undefined\n\n\tconstructor(options: WorkerOptions<TInput, TResource, TResult>) {\n\t\tconst {\n\t\t\tconcurrency: capturedConcurrency,\n\t\t\thandler,\n\t\t\ton,\n\t\t\terror,\n\t\t\tretries,\n\t\t\ttimeout,\n\t\t\tstore,\n\t\t} = options\n\t\tconst concurrency = capturedConcurrency === undefined ? 1 : capturedConcurrency\n\t\tthis.#handler = handler\n\t\tthis.#emitter = new Emitter<WorkerEventMap<TResult>>({\n\t\t\t...(on !== undefined ? { on } : {}),\n\t\t\t...(error !== undefined ? { error } : {}),\n\t\t})\n\t\tthis.#queue = new Queue<TInput, TResult>({\n\t\t\thandler: this.#handle.bind(this),\n\t\t\tconcurrency,\n\t\t\t...(retries !== undefined ? { retries } : {}),\n\t\t\t...(timeout !== undefined ? { timeout } : {}),\n\t\t\t...(store !== undefined ? { store } : {}),\n\t\t})\n\t\tconst pool = options.pool\n\t\tconst {\n\t\t\tmax,\n\t\t\tmin,\n\t\t\trestarts,\n\t\t\twatch,\n\t\t\ton: poolOn,\n\t\t\terror: poolError,\n\t\t\tcreate,\n\t\t\tdestroy,\n\t\t\tvalidate,\n\t\t} = pool\n\t\tthis.#pool = new Pool<TResource>({\n\t\t\tcreate,\n\t\t\t...(max === undefined ? (min === undefined ? { max: concurrency } : {}) : { max }),\n\t\t\t...(min !== undefined ? { min } : {}),\n\t\t\t...(restarts !== undefined ? { restarts } : {}),\n\t\t\t...(watch !== undefined ? { watch } : {}),\n\t\t\t...(poolOn !== undefined ? { on: poolOn } : {}),\n\t\t\t...(poolError !== undefined ? { error: poolError } : {}),\n\t\t\t...(destroy !== undefined ? { destroy } : {}),\n\t\t\t...(validate !== undefined ? { validate } : {}),\n\t\t})\n\t\tthis.#bridge()\n\t\t// Acquires report a spent floor's failure; observe startup rejection before jobs arrive.\n\t\tvoid this.#pool.start().catch(() => {})\n\t}\n\n\tget emitter(): EmitterInterface<WorkerEventMap<TResult>> {\n\t\treturn this.#emitter\n\t}\n\n\tget count(): number {\n\t\treturn this.#queue.count\n\t}\n\n\tget active(): number {\n\t\treturn this.#queue.active\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#queue.paused\n\t}\n\n\tget stopped(): boolean {\n\t\treturn this.#queue.stopped\n\t}\n\n\tenqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult> {\n\t\treturn this.#queue.enqueue(input, options)\n\t}\n\n\trestore(): Promise<void> {\n\t\treturn this.#queue.restore()\n\t}\n\n\tstart(): void {\n\t\tthis.#queue.start()\n\t}\n\n\tstop(): Promise<void> {\n\t\treturn this.#queue.stop()\n\t}\n\n\tpause(): void {\n\t\tthis.#queue.pause()\n\t}\n\n\tresume(): void {\n\t\tthis.#queue.resume()\n\t}\n\n\tabort(reason?: unknown): Promise<void> {\n\t\treturn this.#queue.abort(reason)\n\t}\n\n\tclear(): Promise<void> {\n\t\treturn this.#queue.clear()\n\t}\n\n\tdestroy(): Promise<void> {\n\t\tif (this.#ending !== undefined) return this.#ending.promise\n\t\tconst ending = Promise.withResolvers<void>()\n\t\tthis.#ending = ending\n\t\tvoid this.#teardown(ending)\n\t\treturn ending.promise\n\t}\n\n\tasync #handle(input: TInput, context: QueueContext): Promise<TResult> {\n\t\tconst token = await this.#pool.acquire(context.signal)\n\t\ttry {\n\t\t\treturn await this.#handler(input, token.value, context)\n\t\t} finally {\n\t\t\ttoken.release()\n\t\t}\n\t}\n\n\tasync #teardown(ending: PromiseWithResolvers<void>): Promise<void> {\n\t\tconst failures: unknown[] = []\n\t\ttry {\n\t\t\tawait this.#queue.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\ttry {\n\t\t\tawait this.#pool.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\tthis.#emitter.destroy()\n\t\tif (failures.length === 0) ending.resolve()\n\t\telse if (failures.length === 1) ending.reject(failures[0])\n\t\telse ending.reject(new AggregateError(failures, 'worker destroy cleanup failed'))\n\t}\n\n\t// Bridge the inner queue's lifecycle onto the worker's own emitter, once at construction.\n\t// Each listener re-emits the queue event directly on the worker's emitter, which isolates a\n\t// worker observer's throw (routing it to the worker's `error` handler). Because the bridge\n\t// listener itself never throws, the queue's own `#emitter.emit` — which invoked this\n\t// listener — sees no throw, so the inner queue's engine stays balanced regardless of what a\n\t// worker observer does. The events are already post-transition (they fire from the queue's\n\t// own post-settle / post-wake emits), so this stays observation.\n\t#bridge(): void {\n\t\tconst queue = this.#queue.emitter\n\t\tqueue.on('enqueue', (id) => this.#emitter.emit('enqueue', id))\n\t\tqueue.on('start', (id) => this.#emitter.emit('start', id))\n\t\tqueue.on('retry', (id, attempt) => this.#emitter.emit('retry', id, attempt))\n\t\tqueue.on('success', (id, result) => this.#emitter.emit('success', id, result))\n\t\tqueue.on('failure', (id, error) => this.#emitter.emit('failure', id, error))\n\t\tqueue.on('abort', (reason) => this.#emitter.emit('abort', reason))\n\t\tqueue.on('drain', () => this.#emitter.emit('drain'))\n\t}\n}\n","import type { WorkerInterface, WorkerOptions } from './types.js'\nimport { Worker } from './Worker.js'\n\n/**\n * Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) composed with a\n * `Pool` (`@orkestrel/pool`), where each enqueued input runs through the handler against\n * an automatically acquired pooled resource released when the job settles.\n *\n * @remarks\n * Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.\n * When neither pool `max` nor `min` is given, `max` defaults to `concurrency`. With `min`,\n * Pool owns the capacity defaults and validation, and the worker starts warming the floor.\n * Resources are reused across jobs. A handler that throws still releases its resource (the\n * acquire/release pair brackets the call in a `finally`), so a later job reuses it. The\n * lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)\n * delegates to the queue; `destroy` also tears the pool down. It is observable (see the\n * guide's `## Observing` section): a typed `emitter` surfaces the queue lifecycle\n * (`enqueue` / `start` / `success` / `failure` / …).\n *\n * @typeParam TInput - The work input each job carries\n * @typeParam TResource - The pooled resource each job runs against\n * @typeParam TResult - The value the handler resolves for a job\n * @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,\n * `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})\n * @returns A working {@link WorkerInterface}\n *\n * @example A resource-backed worker\n * ```ts\n * import { createWorker } from '@orkestrel/worker'\n *\n * // A Queue whose handler runs each job against a pooled resource (acquired before the\n * // handler, released after it — even on throw). The pool's `max` defaults to `concurrency`.\n * const worker = createWorker<Query, Connection, Rows>({\n * \tpool: { create: () => connect(), destroy: (connection) => connection.close() },\n * \thandler: (query, connection, { signal }) => connection.run(query, signal),\n * \tconcurrency: 4,\n * \tretries: 1,\n * })\n *\n * const rows = await worker.enqueue(query)\n * await worker.destroy() // awaits queue cleanup, pool cleanup, then emitter teardown\n * ```\n */\nexport function createWorker<TInput, TResource, TResult>(\n\toptions: WorkerOptions<TInput, TResource, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new Worker(options)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,SAAb,MAA4F;CAC3F;CACA;CAIA;CACA;CACA;CAEA,YAAY,SAAoD;EAC/D,MAAM,EACL,aAAa,qBACb,SACA,IACA,OACA,SACA,SACA,UACG;EACJ,MAAM,cAAc,wBAAwB,KAAA,IAAY,IAAI;EAC5D,KAAK,WAAW;EAChB,KAAK,WAAW,IAAIA,mBAAAA,QAAiC;GACpD,GAAI,OAAO,KAAA,IAAY,EAAE,GAAG,IAAI,CAAC;GACjC,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;EACxC,CAAC;EACD,KAAK,SAAS,IAAIC,iBAAAA,MAAuB;GACxC,SAAS,KAAK,QAAQ,KAAK,IAAI;GAC/B;GACA,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;GAC3C,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;GAC3C,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;EACxC,CAAC;EAED,MAAM,EACL,KACA,KACA,UACA,OACA,IAAI,QACJ,OAAO,WACP,QACA,SACA,aAVY,QAAQ;EAYrB,KAAK,QAAQ,IAAIC,gBAAAA,KAAgB;GAChC;GACA,GAAI,QAAQ,KAAA,IAAa,QAAQ,KAAA,IAAY,EAAE,KAAK,YAAY,IAAI,CAAC,IAAK,EAAE,IAAI;GAChF,GAAI,QAAQ,KAAA,IAAY,EAAE,IAAI,IAAI,CAAC;GACnC,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;GAC7C,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;GACvC,GAAI,WAAW,KAAA,IAAY,EAAE,IAAI,OAAO,IAAI,CAAC;GAC7C,GAAI,cAAc,KAAA,IAAY,EAAE,OAAO,UAAU,IAAI,CAAC;GACtD,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;GAC3C,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;EAC9C,CAAC;EACD,KAAK,QAAQ;EAEb,KAAU,MAAM,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;CACvC;CAEA,IAAI,UAAqD;EACxD,OAAO,KAAK;CACb;CAEA,IAAI,QAAgB;EACnB,OAAO,KAAK,OAAO;CACpB;CAEA,IAAI,SAAiB;EACpB,OAAO,KAAK,OAAO;CACpB;CAEA,IAAI,SAAkB;EACrB,OAAO,KAAK,OAAO;CACpB;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAK,OAAO;CACpB;CAEA,QAAQ,OAAe,SAA+C;EACrE,OAAO,KAAK,OAAO,QAAQ,OAAO,OAAO;CAC1C;CAEA,UAAyB;EACxB,OAAO,KAAK,OAAO,QAAQ;CAC5B;CAEA,QAAc;EACb,KAAK,OAAO,MAAM;CACnB;CAEA,OAAsB;EACrB,OAAO,KAAK,OAAO,KAAK;CACzB;CAEA,QAAc;EACb,KAAK,OAAO,MAAM;CACnB;CAEA,SAAe;EACd,KAAK,OAAO,OAAO;CACpB;CAEA,MAAM,QAAiC;EACtC,OAAO,KAAK,OAAO,MAAM,MAAM;CAChC;CAEA,QAAuB;EACtB,OAAO,KAAK,OAAO,MAAM;CAC1B;CAEA,UAAyB;EACxB,IAAI,KAAK,YAAY,KAAA,GAAW,OAAO,KAAK,QAAQ;EACpD,MAAM,SAAS,QAAQ,cAAoB;EAC3C,KAAK,UAAU;EACf,KAAU,UAAU,MAAM;EAC1B,OAAO,OAAO;CACf;CAEA,MAAM,QAAQ,OAAe,SAAyC;EACrE,MAAM,QAAQ,MAAM,KAAK,MAAM,QAAQ,QAAQ,MAAM;EACrD,IAAI;GACH,OAAO,MAAM,KAAK,SAAS,OAAO,MAAM,OAAO,OAAO;EACvD,UAAU;GACT,MAAM,QAAQ;EACf;CACD;CAEA,MAAM,UAAU,QAAmD;EAClE,MAAM,WAAsB,CAAC;EAC7B,IAAI;GACH,MAAM,KAAK,OAAO,QAAQ;EAC3B,SAAS,OAAO;GACf,SAAS,KAAK,KAAK;EACpB;EACA,IAAI;GACH,MAAM,KAAK,MAAM,QAAQ;EAC1B,SAAS,OAAO;GACf,SAAS,KAAK,KAAK;EACpB;EACA,KAAK,SAAS,QAAQ;EACtB,IAAI,SAAS,WAAW,GAAG,OAAO,QAAQ;OACrC,IAAI,SAAS,WAAW,GAAG,OAAO,OAAO,SAAS,EAAE;OACpD,OAAO,OAAO,IAAI,eAAe,UAAU,+BAA+B,CAAC;CACjF;CASA,UAAgB;EACf,MAAM,QAAQ,KAAK,OAAO;EAC1B,MAAM,GAAG,YAAY,OAAO,KAAK,SAAS,KAAK,WAAW,EAAE,CAAC;EAC7D,MAAM,GAAG,UAAU,OAAO,KAAK,SAAS,KAAK,SAAS,EAAE,CAAC;EACzD,MAAM,GAAG,UAAU,IAAI,YAAY,KAAK,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC;EAC3E,MAAM,GAAG,YAAY,IAAI,WAAW,KAAK,SAAS,KAAK,WAAW,IAAI,MAAM,CAAC;EAC7E,MAAM,GAAG,YAAY,IAAI,UAAU,KAAK,SAAS,KAAK,WAAW,IAAI,KAAK,CAAC;EAC3E,MAAM,GAAG,UAAU,WAAW,KAAK,SAAS,KAAK,SAAS,MAAM,CAAC;EACjE,MAAM,GAAG,eAAe,KAAK,SAAS,KAAK,OAAO,CAAC;CACpD;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5KA,SAAgB,aACf,SACmC;CACnC,OAAO,IAAI,OAAO,OAAO;AAC1B"}
|
|
@@ -13,7 +13,8 @@ import type { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
13
13
|
*
|
|
14
14
|
* @remarks
|
|
15
15
|
* Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.
|
|
16
|
-
*
|
|
16
|
+
* When neither pool `max` nor `min` is given, `max` defaults to `concurrency`. With `min`,
|
|
17
|
+
* Pool owns the capacity defaults and validation, and the worker starts warming the floor.
|
|
17
18
|
* Resources are reused across jobs. A handler that throws still releases its resource (the
|
|
18
19
|
* acquire/release pair brackets the call in a `finally`), so a later job reuses it. The
|
|
19
20
|
* lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)
|
|
@@ -58,11 +59,15 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
58
59
|
* timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
|
|
59
60
|
* - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
|
|
60
61
|
* safe integer after caller options are captured once. Only `undefined` defaults
|
|
61
|
-
* `concurrency` to `1
|
|
62
|
-
*
|
|
62
|
+
* `concurrency` to `1`; pool `max` defaults to concurrency only when `max` and `min` are
|
|
63
|
+
* both absent. With `min`, Pool defaults `max` to `min`, requires their equality and a
|
|
64
|
+
* `restarts` bound, and owns validation. Runtime `null` reaches the owning validator.
|
|
65
|
+
* The queue validates before the pool option is read; every declared pool member
|
|
63
66
|
* is then captured once by direct access, preserving inherited and non-enumerable structural
|
|
64
67
|
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
65
|
-
* reused across jobs.
|
|
68
|
+
* reused across jobs. A configured floor starts warming at construction, independently of
|
|
69
|
+
* queue concurrency, and can retain more resources than jobs in flight. A spent floor's
|
|
70
|
+
* startup failure reaches jobs through acquire; startup rejection is observed internally.
|
|
66
71
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
67
72
|
* `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
68
73
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
@@ -211,8 +216,10 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
211
216
|
* @remarks
|
|
212
217
|
* - `handler` — runs each job against an acquired pool resource; rejecting triggers a
|
|
213
218
|
* retry while attempts remain (delegated to the underlying queue).
|
|
214
|
-
* - `pool` — the {@link PoolOptions}
|
|
215
|
-
*
|
|
219
|
+
* - `pool` — the {@link PoolOptions} forwarded to the owned pool for validation. When both
|
|
220
|
+
* `max` and `min` are absent, `max` defaults to `concurrency`. With `min`, the pool defaults
|
|
221
|
+
* `max` to `min`, requires equality and `restarts`, and starts warming at construction.
|
|
222
|
+
* The floor can exceed queue concurrency; `watch` observes each resource's loss.
|
|
216
223
|
* - `concurrency` — the maximum jobs in flight at once; it must be a positive safe
|
|
217
224
|
* integer, as validated by the underlying queue. Default: 1.
|
|
218
225
|
* - `retries` — the default extra attempts per job on failure. Default: 0.
|
package/dist/src/core/index.d.ts
CHANGED
|
@@ -13,7 +13,8 @@ import type { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
13
13
|
*
|
|
14
14
|
* @remarks
|
|
15
15
|
* Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.
|
|
16
|
-
*
|
|
16
|
+
* When neither pool `max` nor `min` is given, `max` defaults to `concurrency`. With `min`,
|
|
17
|
+
* Pool owns the capacity defaults and validation, and the worker starts warming the floor.
|
|
17
18
|
* Resources are reused across jobs. A handler that throws still releases its resource (the
|
|
18
19
|
* acquire/release pair brackets the call in a `finally`), so a later job reuses it. The
|
|
19
20
|
* lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)
|
|
@@ -58,11 +59,15 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
58
59
|
* timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
|
|
59
60
|
* - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
|
|
60
61
|
* safe integer after caller options are captured once. Only `undefined` defaults
|
|
61
|
-
* `concurrency` to `1
|
|
62
|
-
*
|
|
62
|
+
* `concurrency` to `1`; pool `max` defaults to concurrency only when `max` and `min` are
|
|
63
|
+
* both absent. With `min`, Pool defaults `max` to `min`, requires their equality and a
|
|
64
|
+
* `restarts` bound, and owns validation. Runtime `null` reaches the owning validator.
|
|
65
|
+
* The queue validates before the pool option is read; every declared pool member
|
|
63
66
|
* is then captured once by direct access, preserving inherited and non-enumerable structural
|
|
64
67
|
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
65
|
-
* reused across jobs.
|
|
68
|
+
* reused across jobs. A configured floor starts warming at construction, independently of
|
|
69
|
+
* queue concurrency, and can retain more resources than jobs in flight. A spent floor's
|
|
70
|
+
* startup failure reaches jobs through acquire; startup rejection is observed internally.
|
|
66
71
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
67
72
|
* `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
68
73
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
@@ -211,8 +216,10 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
211
216
|
* @remarks
|
|
212
217
|
* - `handler` — runs each job against an acquired pool resource; rejecting triggers a
|
|
213
218
|
* retry while attempts remain (delegated to the underlying queue).
|
|
214
|
-
* - `pool` — the {@link PoolOptions}
|
|
215
|
-
*
|
|
219
|
+
* - `pool` — the {@link PoolOptions} forwarded to the owned pool for validation. When both
|
|
220
|
+
* `max` and `min` are absent, `max` defaults to `concurrency`. With `min`, the pool defaults
|
|
221
|
+
* `max` to `min`, requires equality and `restarts`, and starts warming at construction.
|
|
222
|
+
* The floor can exceed queue concurrency; `watch` observes each resource's loss.
|
|
216
223
|
* - `concurrency` — the maximum jobs in flight at once; it must be a positive safe
|
|
217
224
|
* integer, as validated by the underlying queue. Default: 1.
|
|
218
225
|
* - `retries` — the default extra attempts per job on failure. Default: 0.
|
package/dist/src/core/index.js
CHANGED
|
@@ -13,11 +13,15 @@ import { Queue } from "@orkestrel/queue";
|
|
|
13
13
|
* timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
|
|
14
14
|
* - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
|
|
15
15
|
* safe integer after caller options are captured once. Only `undefined` defaults
|
|
16
|
-
* `concurrency` to `1
|
|
17
|
-
*
|
|
16
|
+
* `concurrency` to `1`; pool `max` defaults to concurrency only when `max` and `min` are
|
|
17
|
+
* both absent. With `min`, Pool defaults `max` to `min`, requires their equality and a
|
|
18
|
+
* `restarts` bound, and owns validation. Runtime `null` reaches the owning validator.
|
|
19
|
+
* The queue validates before the pool option is read; every declared pool member
|
|
18
20
|
* is then captured once by direct access, preserving inherited and non-enumerable structural
|
|
19
21
|
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
20
|
-
* reused across jobs.
|
|
22
|
+
* reused across jobs. A configured floor starts warming at construction, independently of
|
|
23
|
+
* queue concurrency, and can retain more resources than jobs in flight. A spent floor's
|
|
24
|
+
* startup failure reaches jobs through acquire; startup rejection is observed internally.
|
|
21
25
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
22
26
|
* `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
23
27
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
@@ -62,16 +66,20 @@ var Worker = class {
|
|
|
62
66
|
...timeout !== void 0 ? { timeout } : {},
|
|
63
67
|
...store !== void 0 ? { store } : {}
|
|
64
68
|
});
|
|
65
|
-
const { max, on: poolOn, error: poolError, create, destroy, validate } = options.pool;
|
|
69
|
+
const { max, min, restarts, watch, on: poolOn, error: poolError, create, destroy, validate } = options.pool;
|
|
66
70
|
this.#pool = new Pool({
|
|
67
71
|
create,
|
|
68
|
-
max
|
|
72
|
+
...max === void 0 ? min === void 0 ? { max: concurrency } : {} : { max },
|
|
73
|
+
...min !== void 0 ? { min } : {},
|
|
74
|
+
...restarts !== void 0 ? { restarts } : {},
|
|
75
|
+
...watch !== void 0 ? { watch } : {},
|
|
69
76
|
...poolOn !== void 0 ? { on: poolOn } : {},
|
|
70
77
|
...poolError !== void 0 ? { error: poolError } : {},
|
|
71
78
|
...destroy !== void 0 ? { destroy } : {},
|
|
72
79
|
...validate !== void 0 ? { validate } : {}
|
|
73
80
|
});
|
|
74
81
|
this.#bridge();
|
|
82
|
+
this.#pool.start().catch(() => {});
|
|
75
83
|
}
|
|
76
84
|
get emitter() {
|
|
77
85
|
return this.#emitter;
|
|
@@ -164,7 +172,8 @@ var Worker = class {
|
|
|
164
172
|
*
|
|
165
173
|
* @remarks
|
|
166
174
|
* Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.
|
|
167
|
-
*
|
|
175
|
+
* When neither pool `max` nor `min` is given, `max` defaults to `concurrency`. With `min`,
|
|
176
|
+
* Pool owns the capacity defaults and validation, and the worker starts warming the floor.
|
|
168
177
|
* Resources are reused across jobs. A handler that throws still releases its resource (the
|
|
169
178
|
* acquire/release pair brackets the call in a `finally`), so a later job reuses it. The
|
|
170
179
|
* lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/Worker.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { QueueContext, QueueEntryOptions } from '@orkestrel/queue'\nimport type { WorkerEventMap, WorkerHandler, WorkerInterface, WorkerOptions } from './types.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport { Pool } from '@orkestrel/pool'\nimport { Queue } from '@orkestrel/queue'\n\n/**\n * Represents a resource-backed job worker — a thin facade composing a `Queue`\n * (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).\n *\n * @remarks\n * - **Composition, not reimplementation.** The Worker owns a `Pool` (built from\n * `options.pool`) and a `Queue` whose handler `acquire`s a pooled resource, runs the\n * user handler against it, and `release`s it in a `finally`. All concurrency, retries,\n * timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.\n * - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive\n * safe integer after caller options are captured once. Only `undefined` defaults\n * `concurrency` to `1` or pool `max` to that value; runtime `null` reaches the owning\n * validator. The queue validates before the pool option is read; every declared pool member\n * is then captured once by direct access, preserving inherited and non-enumerable structural\n * options. At most one resource exists per in-flight job by default, and idle resources are\n * reused across jobs.\n * - **Acquire over the attempt signal.** Each job acquires using the attempt's\n * `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects\n * the acquire — the Queue then handles retry / rejection, and there is no token to\n * release (the resource was never leased).\n * - **Lifecycle (see the guide's `## Methods` section).** `enqueue` / `restore` / `start` /\n * `stop` / `pause` / `resume` / `abort` / `clear` delegate to the queue; `count` / `active` /\n * `paused` / `stopped` read it. `stop` / `abort` / `clear` return the queue's own cleanup\n * barriers. `destroy` returns one stable barrier while it tears down the queue, then the\n * pool, and destroys the worker emitter last. A sole cleanup failure is preserved by\n * identity; failures from both layers become an ordered `AggregateError`.\n * - **Durability.** An optional `store` is passed straight through to the queue, so the\n * worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).\n * - **Observable (see the guide's `## Observing` section).** The owned {@link emitter}\n * ({@link WorkerEventMap}) re-exposes the underlying queue's job lifecycle (`enqueue` /\n * `start` / `retry` / `success` / `failure` / `abort` / `drain`) as the worker's own events —\n * bridged from the inner queue's emitter at construction — so a consumer observes the worker\n * without reaching through to internals. The bridge re-emits directly on the worker's own\n * emitter; the worker emitter isolates a listener throw and routes it to its `error` handler\n * (the `error` option), so a buggy worker observer can never corrupt the inner queue or pool\n * — the bridge listener never throws, so the inner queue's own emit stays balanced. The\n * pool's create / acquire / release events stay the pool's internal concern (a Worker manages\n * its own resources); observe a `Pool` directly for those.\n */\nexport class Worker<TInput, TResource, TResult> implements WorkerInterface<TInput, TResult> {\n\treadonly #queue: Queue<TInput, TResult>\n\treadonly #pool: Pool<TResource>\n\t// The push observation surface (see the guide's `## Observing` section) — the worker's own\n\t// emitter, fed by the queue→worker bridge. The emitter isolates a worker observer's throw\n\t// (routing it to the `error` handler), so it never escapes into queue or pool.\n\treadonly #emitter: Emitter<WorkerEventMap<TResult>>\n\treadonly #handler: WorkerHandler<TInput, TResource, TResult>\n\t#ending: PromiseWithResolvers<void> | undefined\n\n\tconstructor(options: WorkerOptions<TInput, TResource, TResult>) {\n\t\tconst {\n\t\t\tconcurrency: capturedConcurrency,\n\t\t\thandler,\n\t\t\ton,\n\t\t\terror,\n\t\t\tretries,\n\t\t\ttimeout,\n\t\t\tstore,\n\t\t} = options\n\t\tconst concurrency = capturedConcurrency === undefined ? 1 : capturedConcurrency\n\t\tthis.#handler = handler\n\t\tthis.#emitter = new Emitter<WorkerEventMap<TResult>>({\n\t\t\t...(on !== undefined ? { on } : {}),\n\t\t\t...(error !== undefined ? { error } : {}),\n\t\t})\n\t\tthis.#queue = new Queue<TInput, TResult>({\n\t\t\thandler: this.#handle.bind(this),\n\t\t\tconcurrency,\n\t\t\t...(retries !== undefined ? { retries } : {}),\n\t\t\t...(timeout !== undefined ? { timeout } : {}),\n\t\t\t...(store !== undefined ? { store } : {}),\n\t\t})\n\t\tconst pool = options.pool\n\t\tconst { max, on: poolOn, error: poolError, create, destroy, validate } = pool\n\t\tthis.#pool = new Pool<TResource>({\n\t\t\tcreate,\n\t\t\tmax: max === undefined ? concurrency : max,\n\t\t\t...(poolOn !== undefined ? { on: poolOn } : {}),\n\t\t\t...(poolError !== undefined ? { error: poolError } : {}),\n\t\t\t...(destroy !== undefined ? { destroy } : {}),\n\t\t\t...(validate !== undefined ? { validate } : {}),\n\t\t})\n\t\tthis.#bridge()\n\t}\n\n\tget emitter(): EmitterInterface<WorkerEventMap<TResult>> {\n\t\treturn this.#emitter\n\t}\n\n\tget count(): number {\n\t\treturn this.#queue.count\n\t}\n\n\tget active(): number {\n\t\treturn this.#queue.active\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#queue.paused\n\t}\n\n\tget stopped(): boolean {\n\t\treturn this.#queue.stopped\n\t}\n\n\tenqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult> {\n\t\treturn this.#queue.enqueue(input, options)\n\t}\n\n\trestore(): Promise<void> {\n\t\treturn this.#queue.restore()\n\t}\n\n\tstart(): void {\n\t\tthis.#queue.start()\n\t}\n\n\tstop(): Promise<void> {\n\t\treturn this.#queue.stop()\n\t}\n\n\tpause(): void {\n\t\tthis.#queue.pause()\n\t}\n\n\tresume(): void {\n\t\tthis.#queue.resume()\n\t}\n\n\tabort(reason?: unknown): Promise<void> {\n\t\treturn this.#queue.abort(reason)\n\t}\n\n\tclear(): Promise<void> {\n\t\treturn this.#queue.clear()\n\t}\n\n\tdestroy(): Promise<void> {\n\t\tif (this.#ending !== undefined) return this.#ending.promise\n\t\tconst ending = Promise.withResolvers<void>()\n\t\tthis.#ending = ending\n\t\tvoid this.#teardown(ending)\n\t\treturn ending.promise\n\t}\n\n\tasync #handle(input: TInput, context: QueueContext): Promise<TResult> {\n\t\tconst token = await this.#pool.acquire(context.signal)\n\t\ttry {\n\t\t\treturn await this.#handler(input, token.value, context)\n\t\t} finally {\n\t\t\ttoken.release()\n\t\t}\n\t}\n\n\tasync #teardown(ending: PromiseWithResolvers<void>): Promise<void> {\n\t\tconst failures: unknown[] = []\n\t\ttry {\n\t\t\tawait this.#queue.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\ttry {\n\t\t\tawait this.#pool.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\tthis.#emitter.destroy()\n\t\tif (failures.length === 0) ending.resolve()\n\t\telse if (failures.length === 1) ending.reject(failures[0])\n\t\telse ending.reject(new AggregateError(failures, 'worker destroy cleanup failed'))\n\t}\n\n\t// Bridge the inner queue's lifecycle onto the worker's own emitter, once at construction.\n\t// Each listener re-emits the queue event directly on the worker's emitter, which isolates a\n\t// worker observer's throw (routing it to the worker's `error` handler). Because the bridge\n\t// listener itself never throws, the queue's own `#emitter.emit` — which invoked this\n\t// listener — sees no throw, so the inner queue's engine stays balanced regardless of what a\n\t// worker observer does. The events are already post-transition (they fire from the queue's\n\t// own post-settle / post-wake emits), so this stays observation.\n\t#bridge(): void {\n\t\tconst queue = this.#queue.emitter\n\t\tqueue.on('enqueue', (id) => this.#emitter.emit('enqueue', id))\n\t\tqueue.on('start', (id) => this.#emitter.emit('start', id))\n\t\tqueue.on('retry', (id, attempt) => this.#emitter.emit('retry', id, attempt))\n\t\tqueue.on('success', (id, result) => this.#emitter.emit('success', id, result))\n\t\tqueue.on('failure', (id, error) => this.#emitter.emit('failure', id, error))\n\t\tqueue.on('abort', (reason) => this.#emitter.emit('abort', reason))\n\t\tqueue.on('drain', () => this.#emitter.emit('drain'))\n\t}\n}\n","import type { WorkerInterface, WorkerOptions } from './types.js'\nimport { Worker } from './Worker.js'\n\n/**\n * Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) composed with a\n * `Pool` (`@orkestrel/pool`), where each enqueued input runs through the handler against\n * an automatically acquired pooled resource released when the job settles.\n *\n * @remarks\n * Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.\n * Default for the pool's `max`: the `concurrency` value, so resources match the jobs in flight.\n * Resources are reused across jobs. A handler that throws still releases its resource (the\n * acquire/release pair brackets the call in a `finally`), so a later job reuses it. The\n * lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)\n * delegates to the queue; `destroy` also tears the pool down. It is observable (see the\n * guide's `## Observing` section): a typed `emitter` surfaces the queue lifecycle\n * (`enqueue` / `start` / `success` / `failure` / …).\n *\n * @typeParam TInput - The work input each job carries\n * @typeParam TResource - The pooled resource each job runs against\n * @typeParam TResult - The value the handler resolves for a job\n * @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,\n * `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})\n * @returns A working {@link WorkerInterface}\n *\n * @example A resource-backed worker\n * ```ts\n * import { createWorker } from '@orkestrel/worker'\n *\n * // A Queue whose handler runs each job against a pooled resource (acquired before the\n * // handler, released after it — even on throw). The pool's `max` defaults to `concurrency`.\n * const worker = createWorker<Query, Connection, Rows>({\n * \tpool: { create: () => connect(), destroy: (connection) => connection.close() },\n * \thandler: (query, connection, { signal }) => connection.run(query, signal),\n * \tconcurrency: 4,\n * \tretries: 1,\n * })\n *\n * const rows = await worker.enqueue(query)\n * await worker.destroy() // awaits queue cleanup, pool cleanup, then emitter teardown\n * ```\n */\nexport function createWorker<TInput, TResource, TResult>(\n\toptions: WorkerOptions<TInput, TResource, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new Worker(options)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/Worker.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { QueueContext, QueueEntryOptions } from '@orkestrel/queue'\nimport type { WorkerEventMap, WorkerHandler, WorkerInterface, WorkerOptions } from './types.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport { Pool } from '@orkestrel/pool'\nimport { Queue } from '@orkestrel/queue'\n\n/**\n * Represents a resource-backed job worker — a thin facade composing a `Queue`\n * (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).\n *\n * @remarks\n * - **Composition, not reimplementation.** The Worker owns a `Pool` (built from\n * `options.pool`) and a `Queue` whose handler `acquire`s a pooled resource, runs the\n * user handler against it, and `release`s it in a `finally`. All concurrency, retries,\n * timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.\n * - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive\n * safe integer after caller options are captured once. Only `undefined` defaults\n * `concurrency` to `1`; pool `max` defaults to concurrency only when `max` and `min` are\n * both absent. With `min`, Pool defaults `max` to `min`, requires their equality and a\n * `restarts` bound, and owns validation. Runtime `null` reaches the owning validator.\n * The queue validates before the pool option is read; every declared pool member\n * is then captured once by direct access, preserving inherited and non-enumerable structural\n * options. At most one resource exists per in-flight job by default, and idle resources are\n * reused across jobs. A configured floor starts warming at construction, independently of\n * queue concurrency, and can retain more resources than jobs in flight. A spent floor's\n * startup failure reaches jobs through acquire; startup rejection is observed internally.\n * - **Acquire over the attempt signal.** Each job acquires using the attempt's\n * `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects\n * the acquire — the Queue then handles retry / rejection, and there is no token to\n * release (the resource was never leased).\n * - **Lifecycle (see the guide's `## Methods` section).** `enqueue` / `restore` / `start` /\n * `stop` / `pause` / `resume` / `abort` / `clear` delegate to the queue; `count` / `active` /\n * `paused` / `stopped` read it. `stop` / `abort` / `clear` return the queue's own cleanup\n * barriers. `destroy` returns one stable barrier while it tears down the queue, then the\n * pool, and destroys the worker emitter last. A sole cleanup failure is preserved by\n * identity; failures from both layers become an ordered `AggregateError`.\n * - **Durability.** An optional `store` is passed straight through to the queue, so the\n * worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).\n * - **Observable (see the guide's `## Observing` section).** The owned {@link emitter}\n * ({@link WorkerEventMap}) re-exposes the underlying queue's job lifecycle (`enqueue` /\n * `start` / `retry` / `success` / `failure` / `abort` / `drain`) as the worker's own events —\n * bridged from the inner queue's emitter at construction — so a consumer observes the worker\n * without reaching through to internals. The bridge re-emits directly on the worker's own\n * emitter; the worker emitter isolates a listener throw and routes it to its `error` handler\n * (the `error` option), so a buggy worker observer can never corrupt the inner queue or pool\n * — the bridge listener never throws, so the inner queue's own emit stays balanced. The\n * pool's create / acquire / release events stay the pool's internal concern (a Worker manages\n * its own resources); observe a `Pool` directly for those.\n */\nexport class Worker<TInput, TResource, TResult> implements WorkerInterface<TInput, TResult> {\n\treadonly #queue: Queue<TInput, TResult>\n\treadonly #pool: Pool<TResource>\n\t// The push observation surface (see the guide's `## Observing` section) — the worker's own\n\t// emitter, fed by the queue→worker bridge. The emitter isolates a worker observer's throw\n\t// (routing it to the `error` handler), so it never escapes into queue or pool.\n\treadonly #emitter: Emitter<WorkerEventMap<TResult>>\n\treadonly #handler: WorkerHandler<TInput, TResource, TResult>\n\t#ending: PromiseWithResolvers<void> | undefined\n\n\tconstructor(options: WorkerOptions<TInput, TResource, TResult>) {\n\t\tconst {\n\t\t\tconcurrency: capturedConcurrency,\n\t\t\thandler,\n\t\t\ton,\n\t\t\terror,\n\t\t\tretries,\n\t\t\ttimeout,\n\t\t\tstore,\n\t\t} = options\n\t\tconst concurrency = capturedConcurrency === undefined ? 1 : capturedConcurrency\n\t\tthis.#handler = handler\n\t\tthis.#emitter = new Emitter<WorkerEventMap<TResult>>({\n\t\t\t...(on !== undefined ? { on } : {}),\n\t\t\t...(error !== undefined ? { error } : {}),\n\t\t})\n\t\tthis.#queue = new Queue<TInput, TResult>({\n\t\t\thandler: this.#handle.bind(this),\n\t\t\tconcurrency,\n\t\t\t...(retries !== undefined ? { retries } : {}),\n\t\t\t...(timeout !== undefined ? { timeout } : {}),\n\t\t\t...(store !== undefined ? { store } : {}),\n\t\t})\n\t\tconst pool = options.pool\n\t\tconst {\n\t\t\tmax,\n\t\t\tmin,\n\t\t\trestarts,\n\t\t\twatch,\n\t\t\ton: poolOn,\n\t\t\terror: poolError,\n\t\t\tcreate,\n\t\t\tdestroy,\n\t\t\tvalidate,\n\t\t} = pool\n\t\tthis.#pool = new Pool<TResource>({\n\t\t\tcreate,\n\t\t\t...(max === undefined ? (min === undefined ? { max: concurrency } : {}) : { max }),\n\t\t\t...(min !== undefined ? { min } : {}),\n\t\t\t...(restarts !== undefined ? { restarts } : {}),\n\t\t\t...(watch !== undefined ? { watch } : {}),\n\t\t\t...(poolOn !== undefined ? { on: poolOn } : {}),\n\t\t\t...(poolError !== undefined ? { error: poolError } : {}),\n\t\t\t...(destroy !== undefined ? { destroy } : {}),\n\t\t\t...(validate !== undefined ? { validate } : {}),\n\t\t})\n\t\tthis.#bridge()\n\t\t// Acquires report a spent floor's failure; observe startup rejection before jobs arrive.\n\t\tvoid this.#pool.start().catch(() => {})\n\t}\n\n\tget emitter(): EmitterInterface<WorkerEventMap<TResult>> {\n\t\treturn this.#emitter\n\t}\n\n\tget count(): number {\n\t\treturn this.#queue.count\n\t}\n\n\tget active(): number {\n\t\treturn this.#queue.active\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#queue.paused\n\t}\n\n\tget stopped(): boolean {\n\t\treturn this.#queue.stopped\n\t}\n\n\tenqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult> {\n\t\treturn this.#queue.enqueue(input, options)\n\t}\n\n\trestore(): Promise<void> {\n\t\treturn this.#queue.restore()\n\t}\n\n\tstart(): void {\n\t\tthis.#queue.start()\n\t}\n\n\tstop(): Promise<void> {\n\t\treturn this.#queue.stop()\n\t}\n\n\tpause(): void {\n\t\tthis.#queue.pause()\n\t}\n\n\tresume(): void {\n\t\tthis.#queue.resume()\n\t}\n\n\tabort(reason?: unknown): Promise<void> {\n\t\treturn this.#queue.abort(reason)\n\t}\n\n\tclear(): Promise<void> {\n\t\treturn this.#queue.clear()\n\t}\n\n\tdestroy(): Promise<void> {\n\t\tif (this.#ending !== undefined) return this.#ending.promise\n\t\tconst ending = Promise.withResolvers<void>()\n\t\tthis.#ending = ending\n\t\tvoid this.#teardown(ending)\n\t\treturn ending.promise\n\t}\n\n\tasync #handle(input: TInput, context: QueueContext): Promise<TResult> {\n\t\tconst token = await this.#pool.acquire(context.signal)\n\t\ttry {\n\t\t\treturn await this.#handler(input, token.value, context)\n\t\t} finally {\n\t\t\ttoken.release()\n\t\t}\n\t}\n\n\tasync #teardown(ending: PromiseWithResolvers<void>): Promise<void> {\n\t\tconst failures: unknown[] = []\n\t\ttry {\n\t\t\tawait this.#queue.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\ttry {\n\t\t\tawait this.#pool.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\tthis.#emitter.destroy()\n\t\tif (failures.length === 0) ending.resolve()\n\t\telse if (failures.length === 1) ending.reject(failures[0])\n\t\telse ending.reject(new AggregateError(failures, 'worker destroy cleanup failed'))\n\t}\n\n\t// Bridge the inner queue's lifecycle onto the worker's own emitter, once at construction.\n\t// Each listener re-emits the queue event directly on the worker's emitter, which isolates a\n\t// worker observer's throw (routing it to the worker's `error` handler). Because the bridge\n\t// listener itself never throws, the queue's own `#emitter.emit` — which invoked this\n\t// listener — sees no throw, so the inner queue's engine stays balanced regardless of what a\n\t// worker observer does. The events are already post-transition (they fire from the queue's\n\t// own post-settle / post-wake emits), so this stays observation.\n\t#bridge(): void {\n\t\tconst queue = this.#queue.emitter\n\t\tqueue.on('enqueue', (id) => this.#emitter.emit('enqueue', id))\n\t\tqueue.on('start', (id) => this.#emitter.emit('start', id))\n\t\tqueue.on('retry', (id, attempt) => this.#emitter.emit('retry', id, attempt))\n\t\tqueue.on('success', (id, result) => this.#emitter.emit('success', id, result))\n\t\tqueue.on('failure', (id, error) => this.#emitter.emit('failure', id, error))\n\t\tqueue.on('abort', (reason) => this.#emitter.emit('abort', reason))\n\t\tqueue.on('drain', () => this.#emitter.emit('drain'))\n\t}\n}\n","import type { WorkerInterface, WorkerOptions } from './types.js'\nimport { Worker } from './Worker.js'\n\n/**\n * Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) composed with a\n * `Pool` (`@orkestrel/pool`), where each enqueued input runs through the handler against\n * an automatically acquired pooled resource released when the job settles.\n *\n * @remarks\n * Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.\n * When neither pool `max` nor `min` is given, `max` defaults to `concurrency`. With `min`,\n * Pool owns the capacity defaults and validation, and the worker starts warming the floor.\n * Resources are reused across jobs. A handler that throws still releases its resource (the\n * acquire/release pair brackets the call in a `finally`), so a later job reuses it. The\n * lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)\n * delegates to the queue; `destroy` also tears the pool down. It is observable (see the\n * guide's `## Observing` section): a typed `emitter` surfaces the queue lifecycle\n * (`enqueue` / `start` / `success` / `failure` / …).\n *\n * @typeParam TInput - The work input each job carries\n * @typeParam TResource - The pooled resource each job runs against\n * @typeParam TResult - The value the handler resolves for a job\n * @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,\n * `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})\n * @returns A working {@link WorkerInterface}\n *\n * @example A resource-backed worker\n * ```ts\n * import { createWorker } from '@orkestrel/worker'\n *\n * // A Queue whose handler runs each job against a pooled resource (acquired before the\n * // handler, released after it — even on throw). The pool's `max` defaults to `concurrency`.\n * const worker = createWorker<Query, Connection, Rows>({\n * \tpool: { create: () => connect(), destroy: (connection) => connection.close() },\n * \thandler: (query, connection, { signal }) => connection.run(query, signal),\n * \tconcurrency: 4,\n * \tretries: 1,\n * })\n *\n * const rows = await worker.enqueue(query)\n * await worker.destroy() // awaits queue cleanup, pool cleanup, then emitter teardown\n * ```\n */\nexport function createWorker<TInput, TResource, TResult>(\n\toptions: WorkerOptions<TInput, TResource, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new Worker(options)\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/server/helpers.ts","../../../src/server/handlers.ts","../../../src/server/Thread.ts","../../../src/server/Dispatch.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { Reply } from './types.js'\nimport { attempt, isRecord, isString } from '@orkestrel/contract'\n\n// === The wire protocol (main ↔ thread)\n//\n// The reply half of the run/abort/reply protocol `serveWorker` answers — the leaf predicate\n// a `Dispatch` filters inbound messages with. The envelope types ({@link Reply},\n// `NodeThread`) live in `./types.js`; the public bridge across the\n// structured-clone boundary is the `input` / `result` `Guard`s, which narrow the envelopes'\n// opaque `unknown` payloads with no assertion. This file imports no\n// implementation class, so it stays the bottom of the module's graph.\n\n/**\n * Narrows an inbound `message` to a {@link Reply} for a given correlation `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n * It correlates against the `id` argument rather than narrowing one value alone, so it is a\n * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.\n *\n * @param value - The inbound message to narrow\n * @param id - The per-dispatch correlation id a matching reply must carry\n * @returns True if the value is this dispatch's well-formed reply; false otherwise\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && isString(value.error)\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side request handler. It is self-contained by necessity: this module loads as\n// raw `.ts` inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports only\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// The inbound envelope is therefore narrowed inline rather than through a sibling guard in\n// `helpers.ts`, which would be a runtime import this module cannot make. A worker script\n// that needs the cloned `workerData` reads it directly from `node:worker_threads` (it is in\n// a thread already).\n\n/**\n * Registers a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or\n * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a\n * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also\n * fails, the parent port closes so the main side observes thread exit instead of waiting forever.\n * The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;\n * its `job` is the stable Queue idempotency key exposed as `context.id` across retries\n * and restore. That job id identifies work, not a caller, and is not authentication or\n * authorization evidence. Each attempt has its own `AbortController`, so an `abort`\n * message for the correlation id fires the handler's `signal` (cooperative — the main\n * side also terminates the thread, so a handler that ignores its signal is still stopped).\n * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread\n * (`parentPort === null`) it is a no-op.\n *\n * @typeParam TInput - The work payload (inferred from `options.input`)\n * @typeParam TResult - The value the handler resolves (the reply payload)\n * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})\n *\n * @example\n * ```ts\n * // double.ts — a worker script\n * import { serveWorker } from '@orkestrel/worker/server'\n *\n * serveWorker<number, number>({\n * \tinput: (value): value is number => typeof value === 'number',\n * \thandler: (value) => value * 2,\n * })\n * ```\n */\nexport function serveWorker<TInput, TResult>(options: ServeWorkerOptions<TInput, TResult>): void {\n\tconst port = parentPort\n\tif (port === null) return\n\tconst input = options.input\n\tconst handler = options.handler\n\tconst controllers = new Map<string, AbortController>()\n\tport.on('message', (raw: unknown) => {\n\t\t// Read the envelope's `command`, `id`, `job`, and `input` fields once, defensively. A\n\t\t// hostile message can be a revoked\n\t\t// proxy or carry a throwing getter, so every property access sits inside this one guard:\n\t\t// a read that throws leaves the envelope unrecognised and the message is dropped without\n\t\t// a reply, exactly as a malformed envelope is.\n\t\tlet command: unknown\n\t\tlet correlation: unknown\n\t\tlet job: unknown\n\t\tlet payload: unknown\n\t\tlet carried = false\n\t\ttry {\n\t\t\tif (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n\t\t\t\tif ('command' in raw) command = raw.command\n\t\t\t\tif ('id' in raw) correlation = raw.id\n\t\t\t\tif ('job' in raw) job = raw.job\n\t\t\t\tif ('input' in raw) {\n\t\t\t\t\tpayload = raw.input\n\t\t\t\t\tcarried = true\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tif (typeof correlation !== 'string') return\n\t\tconst id = correlation\n\t\tif (command === 'abort') {\n\t\t\tcontrollers.get(id)?.abort()\n\t\t\treturn\n\t\t}\n\t\t// A `run` envelope carries both ids: `id` is the per-dispatch correlation, `job` the\n\t\t// stable Queue entry id handed to the handler. A malformed envelope\n\t\t// without a string `job`, or without an `input` at all, fails closed with no reply.\n\t\tif (command !== 'run' || typeof job !== 'string' || !carried) return\n\t\tconst entry = job\n\t\tconst value = payload\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => {\n\t\t\t\tif (!input(value)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\treturn handler(value, { id: entry, signal: controller.signal })\n\t\t\t})\n\t\t\t.then((result) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value: result })\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tlet message = 'worker operation failed'\n\t\t\t\ttry {\n\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error)\n\t\t\t\t} catch {}\n\t\t\t\ttry {\n\t\t\t\t\tport.postMessage({ id, ok: false, error: message })\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tport.close()\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n","import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Represents the internal mutable implementation of the readonly {@link NodeThread} observation\n * contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { QueueContext } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isError, isRecord } from '@orkestrel/contract'\nimport { isReply } from './helpers.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents one dispatched worker-thread job — the lifecycle entity behind a job posted to a\n * leased {@link NodeThread}, whose {@link promise} settles with the narrowed reply.\n *\n * @remarks\n * Mints a fresh per-dispatch correlation `id`, posts it with `job: context.id`, and settles\n * when the thread replies for that correlation id. The stable Queue job id reaches the worker\n * handler for idempotency across retries and restore; it is not caller identity or\n * authentication / authorization evidence. Per-job consumer context is explicit,\n * structured-cloneable `input`; ambient context is not worker-thread transport. A success\n * `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`\n * type bridge); a failure rejects with the thread's error string. A thread that had already died\n * rejects synchronously at construction from the latched {@link NodeThread.death} — its death\n * events fired before this dispatch existed and will never fire again, so waiting on the\n * listeners would dangle forever; the latch makes death total across every event ordering. If\n * the thread `error`s / `exit`s mid-flight the job rejects. On a `context.signal` abort it\n * contains the cooperative `abort` post, evicts the thread, and observes `terminate()`\n * settlement because CPU-bound work cannot honour the signal.\n *\n * It owns stable `message` / `messageerror` / death listener identities, settlement,\n * result-guard containment, and abort eviction for one dispatch. Deserialization failure, a\n * matching-id malformed reply, and abort each evict and terminate the thread before rejecting,\n * with termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter\n * is ignored. Every per-job listener (`message` / `messageerror` / `error` / `exit` / `abort`)\n * is removed on settle.\n *\n * Eviction reaches `alive` for a {@link NodeThread} this package produced. Against a\n * consumer-supplied `NodeThread` an abort or a `messageerror` still terminates the supplied\n * `worker` and rejects the job, and the implementer owns flipping its own `alive`.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n *\n * @example\n * ```ts\n * import { createThread, Dispatch } from '@orkestrel/worker/server'\n *\n * const isNumber = (value: unknown): value is number => typeof value === 'number'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * const controller = new AbortController()\n * const job = new Dispatch(thread, 21, { id: 'job-1', signal: controller.signal }, isNumber)\n * console.log(await job.promise) // 42\n * await thread.worker.terminate()\n * ```\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #context: QueueContext\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(thread: NodeThread, input: unknown, context: QueueContext, result: Guard<TResult>) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#context = context\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#context.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#context.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({\n\t\t\t\tid: this.#id,\n\t\t\t\tjob: this.#context.id,\n\t\t\t\tcommand: 'run',\n\t\t\t\tinput: this.#input,\n\t\t\t})\n\t\t} catch (error: unknown) {\n\t\t\tthis.#fail(isError(error) ? error : new Error(String(error)))\n\t\t}\n\t}\n\n\t#message(value: unknown): void {\n\t\tif (!isRecord(value)) return\n\t\tconst id = attempt(() => value.id)\n\t\tif (!id.success || id.value !== this.#id) return\n\t\tif (!isReply(value, this.#id)) {\n\t\t\tthis.#terminate(new Error('worker reply was malformed'))\n\t\t\treturn\n\t\t}\n\t\tif (value.ok) {\n\t\t\tconst reply = value.value\n\t\t\ttry {\n\t\t\t\tif (this.#result(reply)) this.#succeed(reply)\n\t\t\t\telse this.#fail(new Error('reply did not satisfy result guard'))\n\t\t\t} catch (error: unknown) {\n\t\t\t\tthis.#fail(error)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tthis.#fail(new Error(value.error))\n\t}\n\n\t#messageError(error: Error): void {\n\t\tthis.#terminate(error)\n\t}\n\n\t#error(error: Error): void {\n\t\tthis.#fail(error)\n\t}\n\n\t#exit(): void {\n\t\tthis.#fail(this.#thread.death ?? new Error('worker thread exited'))\n\t}\n\n\t#abort(): void {\n\t\tconst notification: unknown[] = []\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'abort' })\n\t\t} catch (cause: unknown) {\n\t\t\tnotification.push(cause)\n\t\t}\n\t\tthis.#terminate(this.#context.signal.reason, notification)\n\t}\n\n\t#terminate(error: unknown, notification: readonly unknown[] = []): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tif (this.#thread instanceof Thread) this.#thread.evict()\n\t\tlet termination: Promise<number>\n\t\ttry {\n\t\t\ttermination = this.#worker.terminate()\n\t\t} catch (cause: unknown) {\n\t\t\tthis.#reject(new AggregateError([error, ...notification, cause], 'worker termination failed'))\n\t\t\treturn\n\t\t}\n\t\tvoid termination.then(\n\t\t\t() => {\n\t\t\t\tif (notification.length === 0) this.#reject(error)\n\t\t\t\telse {\n\t\t\t\t\tthis.#reject(\n\t\t\t\t\t\tnew AggregateError([error, ...notification], 'worker abort notification failed'),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\t\t\t(cause: unknown) =>\n\t\t\t\tthis.#reject(\n\t\t\t\t\tnew AggregateError([error, ...notification, cause], 'worker termination failed'),\n\t\t\t\t),\n\t\t)\n\t}\n\n\t#succeed(value: TResult): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#fulfill(value)\n\t}\n\n\t#fail(error: unknown): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#reject(error)\n\t}\n\n\t#detach(): void {\n\t\tthis.#worker.off('message', this.#messageHandler)\n\t\tthis.#worker.off('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.off('error', this.#errorHandler)\n\t\tthis.#worker.off('exit', this.#exitHandler)\n\t\tthis.#context.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { EmitterErrorHandler, EmitterHooks } from '@orkestrel/emitter'\nimport type { WorkerEventMap, WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueContext, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents the internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity is the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #on: EmitterHooks<WorkerEventMap<TResult>> | undefined\n\treadonly #error: EmitterErrorHandler | undefined\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#on = options.on\n\t\tthis.#error = options.error\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#on !== undefined ? { on: this.#on } : {}),\n\t\t\t...(this.#error !== undefined ? { error: this.#error } : {}),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn new Thread(this.#script, this.#workerData).promise\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, context: QueueContext): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn new Dispatch(thread, input, context, this.#result).promise\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Creates one live worker thread and resolves it as a {@link NodeThread} after it comes\n * online.\n *\n * @remarks\n * Constructs the thread with the `script` module and the cloned `workerData`, then\n * resolves on the thread's `online` event (rejecting on an early `error` or on an `exit`\n * that arrives before `online`, so the spawn promise is total — it can never dangle on a\n * thread that died without erroring). The returned entity attaches persistent `error` /\n * `exit` listeners that flip `alive` to `false` and latch the first terminal event on\n * {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through\n * its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a\n * dispatch that attaches only after the death (through the latch). A `messageerror` is terminal\n * too, so a thread whose inbound payload could not be deserialized is never reused. The latch\n * closes a real race: a thread can become terminal before the readiness promise continuation\n * hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.\n * Without the latch, that job would wait forever. {@link createNodeWorker} spawns its pooled\n * threads the same way; reach for this to drive one thread yourself.\n *\n * @param script - The worker module the thread runs (its module must call `serveWorker`)\n * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn\n * @returns A promise resolving the online {@link NodeThread}\n *\n * @example\n * ```ts\n * import { createThread } from '@orkestrel/worker/server'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * await thread.worker.terminate()\n * ```\n */\nexport function createThread(script: string | URL, workerData?: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Creates a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the same `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Creates a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker thread.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (the same spawn\n * {@link createThread} publishes), `destroy`s it with `terminate()`, and `validate`s it by\n * `alive && threadId > 0` (so an evicted / crashed thread is dropped and replaced) — and an\n * internal handler that narrows the input through `options.input` (fail-fast before the\n * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,\n * narrowing the reply through\n * `options.result`. `TInput` and `TResult` infer from the `input` and `result` guards, so\n * call sites need no explicit type arguments. The boundary is crossed with no `as`: the\n * guards reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`\n * terminates the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `on` / `error` / `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["attempt","isRecord","isString","parentPort","ThreadWorker","isError","isRecord","attempt","createWorker","attempt","createDatabaseQueueStore","createJSONDriver"],"sources":["../../../src/server/helpers.ts","../../../src/server/handlers.ts","../../../src/server/Thread.ts","../../../src/server/Dispatch.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { Reply } from './types.js'\nimport { attempt, isRecord, isString } from '@orkestrel/contract'\n\n// === The wire protocol (main ↔ thread)\n//\n// The reply half of the run/abort/reply protocol `serveWorker` answers — the leaf predicate\n// a `Dispatch` filters inbound messages with. The envelope types ({@link Reply},\n// `NodeThread`) live in `./types.js`; the public bridge across the\n// structured-clone boundary is the `input` / `result` `Guard`s, which narrow the envelopes'\n// opaque `unknown` payloads with no assertion. This file imports no\n// implementation class, so it stays the bottom of the module's graph.\n\n/**\n * Narrows an inbound `message` to a {@link Reply} for a given correlation `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n * It correlates against the `id` argument rather than narrowing one value alone, so it is a\n * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.\n *\n * @param value - The inbound message to narrow\n * @param id - The per-dispatch correlation id a matching reply must carry\n * @returns True if the value is this dispatch's well-formed reply; false otherwise\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && isString(value.error)\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side request handler. It is self-contained by necessity: this module loads as\n// raw `.ts` inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports only\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// The inbound envelope is therefore narrowed inline rather than through a sibling guard in\n// `helpers.ts`, which would be a runtime import this module cannot make. A worker script\n// that needs the cloned `workerData` reads it directly from `node:worker_threads` (it is in\n// a thread already).\n\n/**\n * Registers a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or\n * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a\n * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also\n * fails, the parent port closes so the main side observes thread exit instead of waiting forever.\n * The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;\n * its `job` is the stable Queue idempotency key exposed as `context.id` across retries\n * and restore. That job id identifies work, not a caller, and is not authentication or\n * authorization evidence. Each attempt has its own `AbortController`, so an `abort`\n * message for the correlation id fires the handler's `signal` (cooperative — the main\n * side also terminates the thread, so a handler that ignores its signal is still stopped).\n * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread\n * (`parentPort === null`) it is a no-op.\n *\n * @typeParam TInput - The work payload (inferred from `options.input`)\n * @typeParam TResult - The value the handler resolves (the reply payload)\n * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})\n *\n * @example\n * ```ts\n * // double.ts — a worker script\n * import { serveWorker } from '@orkestrel/worker/server'\n *\n * serveWorker<number, number>({\n * \tinput: (value): value is number => typeof value === 'number',\n * \thandler: (value) => value * 2,\n * })\n * ```\n */\nexport function serveWorker<TInput, TResult>(options: ServeWorkerOptions<TInput, TResult>): void {\n\tconst port = parentPort\n\tif (port === null) return\n\tconst input = options.input\n\tconst handler = options.handler\n\tconst controllers = new Map<string, AbortController>()\n\tport.on('message', (raw: unknown) => {\n\t\t// Read the envelope's `command`, `id`, `job`, and `input` fields once, defensively. A\n\t\t// hostile message can be a revoked\n\t\t// proxy or carry a throwing getter, so every property access sits inside this one guard:\n\t\t// a read that throws leaves the envelope unrecognised and the message is dropped without\n\t\t// a reply, exactly as a malformed envelope is.\n\t\tlet command: unknown\n\t\tlet correlation: unknown\n\t\tlet job: unknown\n\t\tlet payload: unknown\n\t\tlet carried = false\n\t\ttry {\n\t\t\tif (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n\t\t\t\tif ('command' in raw) command = raw.command\n\t\t\t\tif ('id' in raw) correlation = raw.id\n\t\t\t\tif ('job' in raw) job = raw.job\n\t\t\t\tif ('input' in raw) {\n\t\t\t\t\tpayload = raw.input\n\t\t\t\t\tcarried = true\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tif (typeof correlation !== 'string') return\n\t\tconst id = correlation\n\t\tif (command === 'abort') {\n\t\t\tcontrollers.get(id)?.abort()\n\t\t\treturn\n\t\t}\n\t\t// A `run` envelope carries both ids: `id` is the per-dispatch correlation, `job` the\n\t\t// stable Queue entry id handed to the handler. A malformed envelope\n\t\t// without a string `job`, or without an `input` at all, fails closed with no reply.\n\t\tif (command !== 'run' || typeof job !== 'string' || !carried) return\n\t\tconst entry = job\n\t\tconst value = payload\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => {\n\t\t\t\tif (!input(value)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\treturn handler(value, { id: entry, signal: controller.signal })\n\t\t\t})\n\t\t\t.then((result) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value: result })\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tlet message = 'worker operation failed'\n\t\t\t\ttry {\n\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error)\n\t\t\t\t} catch {}\n\t\t\t\ttry {\n\t\t\t\t\tport.postMessage({ id, ok: false, error: message })\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tport.close()\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n","import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Represents the internal mutable implementation of the readonly {@link NodeThread} observation\n * contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { QueueContext } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isError, isRecord } from '@orkestrel/contract'\nimport { isReply } from './helpers.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents one dispatched worker-thread job — the lifecycle entity behind a job posted to a\n * leased {@link NodeThread}, whose {@link promise} settles with the narrowed reply.\n *\n * @remarks\n * Mints a fresh per-dispatch correlation `id`, posts it with `job: context.id`, and settles\n * when the thread replies for that correlation id. The stable Queue job id reaches the worker\n * handler for idempotency across retries and restore; it is not caller identity or\n * authentication / authorization evidence. Per-job consumer context is explicit,\n * structured-cloneable `input`; ambient context is not worker-thread transport. A success\n * `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`\n * type bridge); a failure rejects with the thread's error string. A thread that had already died\n * rejects synchronously at construction from the latched {@link NodeThread.death} — its death\n * events fired before this dispatch existed and will never fire again, so waiting on the\n * listeners would dangle forever; the latch makes death total across every event ordering. If\n * the thread `error`s / `exit`s mid-flight the job rejects. On a `context.signal` abort it\n * contains the cooperative `abort` post, evicts the thread, and observes `terminate()`\n * settlement because CPU-bound work cannot honour the signal.\n *\n * It owns stable `message` / `messageerror` / death listener identities, settlement,\n * result-guard containment, and abort eviction for one dispatch. Deserialization failure, a\n * matching-id malformed reply, and abort each evict and terminate the thread before rejecting,\n * with termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter\n * is ignored. Every per-job listener (`message` / `messageerror` / `error` / `exit` / `abort`)\n * is removed on settle.\n *\n * Eviction reaches `alive` for a {@link NodeThread} this package produced. Against a\n * consumer-supplied `NodeThread` an abort or a `messageerror` still terminates the supplied\n * `worker` and rejects the job, and the implementer owns flipping its own `alive`.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n *\n * @example\n * ```ts\n * import { createThread, Dispatch } from '@orkestrel/worker/server'\n *\n * const isNumber = (value: unknown): value is number => typeof value === 'number'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * const controller = new AbortController()\n * const job = new Dispatch(thread, 21, { id: 'job-1', signal: controller.signal }, isNumber)\n * console.log(await job.promise) // 42\n * await thread.worker.terminate()\n * ```\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #context: QueueContext\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(thread: NodeThread, input: unknown, context: QueueContext, result: Guard<TResult>) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#context = context\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#context.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#context.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({\n\t\t\t\tid: this.#id,\n\t\t\t\tjob: this.#context.id,\n\t\t\t\tcommand: 'run',\n\t\t\t\tinput: this.#input,\n\t\t\t})\n\t\t} catch (error: unknown) {\n\t\t\tthis.#fail(isError(error) ? error : new Error(String(error)))\n\t\t}\n\t}\n\n\t#message(value: unknown): void {\n\t\tif (!isRecord(value)) return\n\t\tconst id = attempt(() => value.id)\n\t\tif (!id.success || id.value !== this.#id) return\n\t\tif (!isReply(value, this.#id)) {\n\t\t\tthis.#terminate(new Error('worker reply was malformed'))\n\t\t\treturn\n\t\t}\n\t\tif (value.ok) {\n\t\t\tconst reply = value.value\n\t\t\ttry {\n\t\t\t\tif (this.#result(reply)) this.#succeed(reply)\n\t\t\t\telse this.#fail(new Error('reply did not satisfy result guard'))\n\t\t\t} catch (error: unknown) {\n\t\t\t\tthis.#fail(error)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tthis.#fail(new Error(value.error))\n\t}\n\n\t#messageError(error: Error): void {\n\t\tthis.#terminate(error)\n\t}\n\n\t#error(error: Error): void {\n\t\tthis.#fail(error)\n\t}\n\n\t#exit(): void {\n\t\tthis.#fail(this.#thread.death ?? new Error('worker thread exited'))\n\t}\n\n\t#abort(): void {\n\t\tconst notification: unknown[] = []\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'abort' })\n\t\t} catch (cause: unknown) {\n\t\t\tnotification.push(cause)\n\t\t}\n\t\tthis.#terminate(this.#context.signal.reason, notification)\n\t}\n\n\t#terminate(error: unknown, notification: readonly unknown[] = []): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tif (this.#thread instanceof Thread) this.#thread.evict()\n\t\tlet termination: Promise<number>\n\t\ttry {\n\t\t\ttermination = this.#worker.terminate()\n\t\t} catch (cause: unknown) {\n\t\t\tthis.#reject(new AggregateError([error, ...notification, cause], 'worker termination failed'))\n\t\t\treturn\n\t\t}\n\t\tvoid termination.then(\n\t\t\t() => {\n\t\t\t\tif (notification.length === 0) this.#reject(error)\n\t\t\t\telse {\n\t\t\t\t\tthis.#reject(\n\t\t\t\t\t\tnew AggregateError([error, ...notification], 'worker abort notification failed'),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\t\t\t(cause: unknown) =>\n\t\t\t\tthis.#reject(\n\t\t\t\t\tnew AggregateError([error, ...notification, cause], 'worker termination failed'),\n\t\t\t\t),\n\t\t)\n\t}\n\n\t#succeed(value: TResult): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#fulfill(value)\n\t}\n\n\t#fail(error: unknown): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#reject(error)\n\t}\n\n\t#detach(): void {\n\t\tthis.#worker.off('message', this.#messageHandler)\n\t\tthis.#worker.off('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.off('error', this.#errorHandler)\n\t\tthis.#worker.off('exit', this.#exitHandler)\n\t\tthis.#context.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { EmitterErrorHandler, EmitterHooks } from '@orkestrel/emitter'\nimport type { WorkerEventMap, WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueContext, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents the internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity is the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #on: EmitterHooks<WorkerEventMap<TResult>> | undefined\n\treadonly #error: EmitterErrorHandler | undefined\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#on = options.on\n\t\tthis.#error = options.error\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#on !== undefined ? { on: this.#on } : {}),\n\t\t\t...(this.#error !== undefined ? { error: this.#error } : {}),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn new Thread(this.#script, this.#workerData).promise\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, context: QueueContext): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn new Dispatch(thread, input, context, this.#result).promise\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Creates one live worker thread and resolves it as a {@link NodeThread} after it comes\n * online.\n *\n * @remarks\n * Constructs the thread with the `script` module and the cloned `workerData`, then\n * resolves on the thread's `online` event (rejecting on an early `error` or on an `exit`\n * that arrives before `online`, so the spawn promise is total — it can never dangle on a\n * thread that died without erroring). The returned entity attaches persistent `error` /\n * `exit` listeners that flip `alive` to `false` and latch the first terminal event on\n * {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through\n * its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a\n * dispatch that attaches only after the death (through the latch). A `messageerror` is terminal\n * too, so a thread whose inbound payload could not be deserialized is never reused. The latch\n * closes a real race: a thread can become terminal before the readiness promise continuation\n * hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.\n * Without the latch, that job would wait forever. {@link createNodeWorker} spawns its pooled\n * threads the same way; reach for this to drive one thread yourself.\n *\n * @param script - The worker module the thread runs (its module must call `serveWorker`)\n * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn\n * @returns A promise resolving the online {@link NodeThread}\n *\n * @example\n * ```ts\n * import { createThread } from '@orkestrel/worker/server'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * await thread.worker.terminate()\n * ```\n */\nexport function createThread(script: string | URL, workerData?: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Creates a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the same `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Creates a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker thread.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (the same spawn\n * {@link createThread} publishes), `destroy`s it with `terminate()`, and `validate`s it by\n * `alive && threadId > 0` (so an evicted / crashed thread is dropped and replaced) — and an\n * internal handler that narrows the input through `options.input` (fail-fast before the\n * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,\n * narrowing the reply through\n * `options.result`. `TInput` and `TResult` infer from the `input` and `result` guards, so\n * call sites need no explicit type arguments. The boundary is crossed with no `as`: the\n * guards reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`\n * terminates the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `on` / `error` / `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/server/helpers.ts","../../../src/server/handlers.ts","../../../src/server/Thread.ts","../../../src/server/Dispatch.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { Reply } from './types.js'\nimport { attempt, isRecord, isString } from '@orkestrel/contract'\n\n// === The wire protocol (main ↔ thread)\n//\n// The reply half of the run/abort/reply protocol `serveWorker` answers — the leaf predicate\n// a `Dispatch` filters inbound messages with. The envelope types ({@link Reply},\n// `NodeThread`) live in `./types.js`; the public bridge across the\n// structured-clone boundary is the `input` / `result` `Guard`s, which narrow the envelopes'\n// opaque `unknown` payloads with no assertion. This file imports no\n// implementation class, so it stays the bottom of the module's graph.\n\n/**\n * Narrows an inbound `message` to a {@link Reply} for a given correlation `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n * It correlates against the `id` argument rather than narrowing one value alone, so it is a\n * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.\n *\n * @param value - The inbound message to narrow\n * @param id - The per-dispatch correlation id a matching reply must carry\n * @returns True if the value is this dispatch's well-formed reply; false otherwise\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && isString(value.error)\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side request handler. It is self-contained by necessity: this module loads as\n// raw `.ts` inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports only\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// The inbound envelope is therefore narrowed inline rather than through a sibling guard in\n// `helpers.ts`, which would be a runtime import this module cannot make. A worker script\n// that needs the cloned `workerData` reads it directly from `node:worker_threads` (it is in\n// a thread already).\n\n/**\n * Registers a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or\n * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a\n * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also\n * fails, the parent port closes so the main side observes thread exit instead of waiting forever.\n * The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;\n * its `job` is the stable Queue idempotency key exposed as `context.id` across retries\n * and restore. That job id identifies work, not a caller, and is not authentication or\n * authorization evidence. Each attempt has its own `AbortController`, so an `abort`\n * message for the correlation id fires the handler's `signal` (cooperative — the main\n * side also terminates the thread, so a handler that ignores its signal is still stopped).\n * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread\n * (`parentPort === null`) it is a no-op.\n *\n * @typeParam TInput - The work payload (inferred from `options.input`)\n * @typeParam TResult - The value the handler resolves (the reply payload)\n * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})\n *\n * @example\n * ```ts\n * // double.ts — a worker script\n * import { serveWorker } from '@orkestrel/worker/server'\n *\n * serveWorker<number, number>({\n * \tinput: (value): value is number => typeof value === 'number',\n * \thandler: (value) => value * 2,\n * })\n * ```\n */\nexport function serveWorker<TInput, TResult>(options: ServeWorkerOptions<TInput, TResult>): void {\n\tconst port = parentPort\n\tif (port === null) return\n\tconst input = options.input\n\tconst handler = options.handler\n\tconst controllers = new Map<string, AbortController>()\n\tport.on('message', (raw: unknown) => {\n\t\t// Read the envelope's `command`, `id`, `job`, and `input` fields once, defensively. A\n\t\t// hostile message can be a revoked\n\t\t// proxy or carry a throwing getter, so every property access sits inside this one guard:\n\t\t// a read that throws leaves the envelope unrecognised and the message is dropped without\n\t\t// a reply, exactly as a malformed envelope is.\n\t\tlet command: unknown\n\t\tlet correlation: unknown\n\t\tlet job: unknown\n\t\tlet payload: unknown\n\t\tlet carried = false\n\t\ttry {\n\t\t\tif (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n\t\t\t\tif ('command' in raw) command = raw.command\n\t\t\t\tif ('id' in raw) correlation = raw.id\n\t\t\t\tif ('job' in raw) job = raw.job\n\t\t\t\tif ('input' in raw) {\n\t\t\t\t\tpayload = raw.input\n\t\t\t\t\tcarried = true\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tif (typeof correlation !== 'string') return\n\t\tconst id = correlation\n\t\tif (command === 'abort') {\n\t\t\tcontrollers.get(id)?.abort()\n\t\t\treturn\n\t\t}\n\t\t// A `run` envelope carries both ids: `id` is the per-dispatch correlation, `job` the\n\t\t// stable Queue entry id handed to the handler. A malformed envelope\n\t\t// without a string `job`, or without an `input` at all, fails closed with no reply.\n\t\tif (command !== 'run' || typeof job !== 'string' || !carried) return\n\t\tconst entry = job\n\t\tconst value = payload\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => {\n\t\t\t\tif (!input(value)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\treturn handler(value, { id: entry, signal: controller.signal })\n\t\t\t})\n\t\t\t.then((result) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value: result })\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tlet message = 'worker operation failed'\n\t\t\t\ttry {\n\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error)\n\t\t\t\t} catch {}\n\t\t\t\ttry {\n\t\t\t\t\tport.postMessage({ id, ok: false, error: message })\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tport.close()\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n","import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Represents the internal mutable implementation of the readonly {@link NodeThread} observation\n * contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { QueueContext } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isError, isRecord } from '@orkestrel/contract'\nimport { isReply } from './helpers.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents one dispatched worker-thread job — the lifecycle entity behind a job posted to a\n * leased {@link NodeThread}, whose {@link promise} settles with the narrowed reply.\n *\n * @remarks\n * Mints a fresh per-dispatch correlation `id`, posts it with `job: context.id`, and settles\n * when the thread replies for that correlation id. The stable Queue job id reaches the worker\n * handler for idempotency across retries and restore; it is not caller identity or\n * authentication / authorization evidence. Per-job consumer context is explicit,\n * structured-cloneable `input`; ambient context is not worker-thread transport. A success\n * `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`\n * type bridge); a failure rejects with the thread's error string. A thread that had already died\n * rejects synchronously at construction from the latched {@link NodeThread.death} — its death\n * events fired before this dispatch existed and will never fire again, so waiting on the\n * listeners would dangle forever; the latch makes death total across every event ordering. If\n * the thread `error`s / `exit`s mid-flight the job rejects. On a `context.signal` abort it\n * contains the cooperative `abort` post, evicts the thread, and observes `terminate()`\n * settlement because CPU-bound work cannot honour the signal.\n *\n * It owns stable `message` / `messageerror` / death listener identities, settlement,\n * result-guard containment, and abort eviction for one dispatch. Deserialization failure, a\n * matching-id malformed reply, and abort each evict and terminate the thread before rejecting,\n * with termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter\n * is ignored. Every per-job listener (`message` / `messageerror` / `error` / `exit` / `abort`)\n * is removed on settle.\n *\n * Eviction reaches `alive` for a {@link NodeThread} this package produced. Against a\n * consumer-supplied `NodeThread` an abort or a `messageerror` still terminates the supplied\n * `worker` and rejects the job, and the implementer owns flipping its own `alive`.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n *\n * @example\n * ```ts\n * import { createThread, Dispatch } from '@orkestrel/worker/server'\n *\n * const isNumber = (value: unknown): value is number => typeof value === 'number'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * const controller = new AbortController()\n * const job = new Dispatch(thread, 21, { id: 'job-1', signal: controller.signal }, isNumber)\n * console.log(await job.promise) // 42\n * await thread.worker.terminate()\n * ```\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #context: QueueContext\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(thread: NodeThread, input: unknown, context: QueueContext, result: Guard<TResult>) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#context = context\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#context.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#context.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({\n\t\t\t\tid: this.#id,\n\t\t\t\tjob: this.#context.id,\n\t\t\t\tcommand: 'run',\n\t\t\t\tinput: this.#input,\n\t\t\t})\n\t\t} catch (error: unknown) {\n\t\t\tthis.#fail(isError(error) ? error : new Error(String(error)))\n\t\t}\n\t}\n\n\t#message(value: unknown): void {\n\t\tif (!isRecord(value)) return\n\t\tconst id = attempt(() => value.id)\n\t\tif (!id.success || id.value !== this.#id) return\n\t\tif (!isReply(value, this.#id)) {\n\t\t\tthis.#terminate(new Error('worker reply was malformed'))\n\t\t\treturn\n\t\t}\n\t\tif (value.ok) {\n\t\t\tconst reply = value.value\n\t\t\ttry {\n\t\t\t\tif (this.#result(reply)) this.#succeed(reply)\n\t\t\t\telse this.#fail(new Error('reply did not satisfy result guard'))\n\t\t\t} catch (error: unknown) {\n\t\t\t\tthis.#fail(error)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tthis.#fail(new Error(value.error))\n\t}\n\n\t#messageError(error: Error): void {\n\t\tthis.#terminate(error)\n\t}\n\n\t#error(error: Error): void {\n\t\tthis.#fail(error)\n\t}\n\n\t#exit(): void {\n\t\tthis.#fail(this.#thread.death ?? new Error('worker thread exited'))\n\t}\n\n\t#abort(): void {\n\t\tconst notification: unknown[] = []\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'abort' })\n\t\t} catch (cause: unknown) {\n\t\t\tnotification.push(cause)\n\t\t}\n\t\tthis.#terminate(this.#context.signal.reason, notification)\n\t}\n\n\t#terminate(error: unknown, notification: readonly unknown[] = []): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tif (this.#thread instanceof Thread) this.#thread.evict()\n\t\tlet termination: Promise<number>\n\t\ttry {\n\t\t\ttermination = this.#worker.terminate()\n\t\t} catch (cause: unknown) {\n\t\t\tthis.#reject(new AggregateError([error, ...notification, cause], 'worker termination failed'))\n\t\t\treturn\n\t\t}\n\t\tvoid termination.then(\n\t\t\t() => {\n\t\t\t\tif (notification.length === 0) this.#reject(error)\n\t\t\t\telse {\n\t\t\t\t\tthis.#reject(\n\t\t\t\t\t\tnew AggregateError([error, ...notification], 'worker abort notification failed'),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\t\t\t(cause: unknown) =>\n\t\t\t\tthis.#reject(\n\t\t\t\t\tnew AggregateError([error, ...notification, cause], 'worker termination failed'),\n\t\t\t\t),\n\t\t)\n\t}\n\n\t#succeed(value: TResult): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#fulfill(value)\n\t}\n\n\t#fail(error: unknown): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#reject(error)\n\t}\n\n\t#detach(): void {\n\t\tthis.#worker.off('message', this.#messageHandler)\n\t\tthis.#worker.off('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.off('error', this.#errorHandler)\n\t\tthis.#worker.off('exit', this.#exitHandler)\n\t\tthis.#context.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { EmitterErrorHandler, EmitterHooks } from '@orkestrel/emitter'\nimport type { WorkerEventMap, WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueContext, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents the internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity is the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #on: EmitterHooks<WorkerEventMap<TResult>> | undefined\n\treadonly #error: EmitterErrorHandler | undefined\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#on = options.on\n\t\tthis.#error = options.error\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#on !== undefined ? { on: this.#on } : {}),\n\t\t\t...(this.#error !== undefined ? { error: this.#error } : {}),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn new Thread(this.#script, this.#workerData).promise\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, context: QueueContext): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn new Dispatch(thread, input, context, this.#result).promise\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Creates one live worker thread and resolves it as a {@link NodeThread} after it comes\n * online.\n *\n * @remarks\n * Constructs the thread with the `script` module and the cloned `workerData`, then\n * resolves on the thread's `online` event (rejecting on an early `error` or on an `exit`\n * that arrives before `online`, so the spawn promise is total — it can never dangle on a\n * thread that died without erroring). The returned entity attaches persistent `error` /\n * `exit` listeners that flip `alive` to `false` and latch the first terminal event on\n * {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through\n * its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a\n * dispatch that attaches only after the death (through the latch). A `messageerror` is terminal\n * too, so a thread whose inbound payload could not be deserialized is never reused. The latch\n * closes a real race: a thread can become terminal before the readiness promise continuation\n * hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.\n * Without the latch, that job would wait forever. {@link createNodeWorker} spawns its pooled\n * threads the same way; reach for this to drive one thread yourself.\n *\n * @param script - The worker module the thread runs (its module must call `serveWorker`)\n * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn\n * @returns A promise resolving the online {@link NodeThread}\n *\n * @example\n * ```ts\n * import { createThread } from '@orkestrel/worker/server'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * await thread.worker.terminate()\n * ```\n */\nexport function createThread(script: string | URL, workerData?: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Creates a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the same `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Creates a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker thread.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (the same spawn\n * {@link createThread} publishes), `destroy`s it with `terminate()`, and `validate`s it by\n * `alive && threadId > 0` (so an evicted / crashed thread is dropped and replaced) — and an\n * internal handler that narrows the input through `options.input` (fail-fast before the\n * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,\n * narrowing the reply through\n * `options.result`. `TInput` and `TResult` infer from the `input` and `result` guards, so\n * call sites need no explicit type arguments. The boundary is crossed with no `as`: the\n * guards reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`\n * terminates the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `on` / `error` / `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["ThreadWorker"],"sources":["../../../src/server/helpers.ts","../../../src/server/handlers.ts","../../../src/server/Thread.ts","../../../src/server/Dispatch.ts","../../../src/server/NodeWorker.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { Reply } from './types.js'\nimport { attempt, isRecord, isString } from '@orkestrel/contract'\n\n// === The wire protocol (main ↔ thread)\n//\n// The reply half of the run/abort/reply protocol `serveWorker` answers — the leaf predicate\n// a `Dispatch` filters inbound messages with. The envelope types ({@link Reply},\n// `NodeThread`) live in `./types.js`; the public bridge across the\n// structured-clone boundary is the `input` / `result` `Guard`s, which narrow the envelopes'\n// opaque `unknown` payloads with no assertion. This file imports no\n// implementation class, so it stays the bottom of the module's graph.\n\n/**\n * Narrows an inbound `message` to a {@link Reply} for a given correlation `id` — no assertion.\n *\n * @remarks\n * A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.\n * Anything else is rejected so a dispatch listener can ignore foreign or malformed messages.\n * It correlates against the `id` argument rather than narrowing one value alone, so it is a\n * correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.\n *\n * @param value - The inbound message to narrow\n * @param id - The per-dispatch correlation id a matching reply must carry\n * @returns True if the value is this dispatch's well-formed reply; false otherwise\n */\nexport function isReply(value: unknown, id: string): value is Reply {\n\tconst outcome = attempt(() => {\n\t\tif (!isRecord(value)) return false\n\t\tif (value.id !== id) return false\n\t\tif (value.ok === true) return 'value' in value\n\t\treturn value.ok === false && isString(value.error)\n\t})\n\treturn outcome.success && outcome.value\n}\n","import type { ServeWorkerOptions } from './types.js'\nimport { parentPort } from 'node:worker_threads'\n\n// The worker-side request handler. It is self-contained by necessity: this module loads as\n// raw `.ts` inside a spawned thread (Node ≥ 23.6 type-stripping), so it imports only\n// `node:worker_threads` at runtime — no `@src/*`, no `.js`-relative value imports (the\n// only non-node import is the type-only `ServeWorkerOptions`, fully erased at runtime).\n// The inbound envelope is therefore narrowed inline rather than through a sibling guard in\n// `helpers.ts`, which would be a runtime import this module cannot make. A worker script\n// that needs the cloned `workerData` reads it directly from `node:worker_threads` (it is in\n// a thread already).\n\n/**\n * Registers a worker-thread handler — the worker-side half of {@link createNodeWorker}.\n *\n * @remarks\n * Must be the spawned thread's module entry. It listens on the parent port for the\n * run/abort protocol: a `run` message narrows its `input` through `options.input` (an\n * invalid payload replies with an error envelope, never running the handler), then runs\n * `options.handler(input, { id: job, signal })` and replies `{ id, ok: true, value }` on success or\n * `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a\n * success value cannot be cloned, the post is retried as a clone-safe failure; if that post also\n * fails, the parent port closes so the main side observes thread exit instead of waiting forever.\n * The run envelope's `id` is fresh per dispatch and keys controllers, aborts, and replies;\n * its `job` is the stable Queue idempotency key exposed as `context.id` across retries\n * and restore. That job id identifies work, not a caller, and is not authentication or\n * authorization evidence. Each attempt has its own `AbortController`, so an `abort`\n * message for the correlation id fires the handler's `signal` (cooperative — the main\n * side also terminates the thread, so a handler that ignores its signal is still stopped).\n * Every inbound message is narrowed with the inlined guards — no `as`. On the main thread\n * (`parentPort === null`) it is a no-op.\n *\n * @typeParam TInput - The work payload (inferred from `options.input`)\n * @typeParam TResult - The value the handler resolves (the reply payload)\n * @param options - The `input` guard and the `handler` (see {@link ServeWorkerOptions})\n *\n * @example\n * ```ts\n * // double.ts — a worker script\n * import { serveWorker } from '@orkestrel/worker/server'\n *\n * serveWorker<number, number>({\n * \tinput: (value): value is number => typeof value === 'number',\n * \thandler: (value) => value * 2,\n * })\n * ```\n */\nexport function serveWorker<TInput, TResult>(options: ServeWorkerOptions<TInput, TResult>): void {\n\tconst port = parentPort\n\tif (port === null) return\n\tconst input = options.input\n\tconst handler = options.handler\n\tconst controllers = new Map<string, AbortController>()\n\tport.on('message', (raw: unknown) => {\n\t\t// Read the envelope's `command`, `id`, `job`, and `input` fields once, defensively. A\n\t\t// hostile message can be a revoked\n\t\t// proxy or carry a throwing getter, so every property access sits inside this one guard:\n\t\t// a read that throws leaves the envelope unrecognised and the message is dropped without\n\t\t// a reply, exactly as a malformed envelope is.\n\t\tlet command: unknown\n\t\tlet correlation: unknown\n\t\tlet job: unknown\n\t\tlet payload: unknown\n\t\tlet carried = false\n\t\ttry {\n\t\t\tif (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {\n\t\t\t\tif ('command' in raw) command = raw.command\n\t\t\t\tif ('id' in raw) correlation = raw.id\n\t\t\t\tif ('job' in raw) job = raw.job\n\t\t\t\tif ('input' in raw) {\n\t\t\t\t\tpayload = raw.input\n\t\t\t\t\tcarried = true\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tif (typeof correlation !== 'string') return\n\t\tconst id = correlation\n\t\tif (command === 'abort') {\n\t\t\tcontrollers.get(id)?.abort()\n\t\t\treturn\n\t\t}\n\t\t// A `run` envelope carries both ids: `id` is the per-dispatch correlation, `job` the\n\t\t// stable Queue entry id handed to the handler. A malformed envelope\n\t\t// without a string `job`, or without an `input` at all, fails closed with no reply.\n\t\tif (command !== 'run' || typeof job !== 'string' || !carried) return\n\t\tconst entry = job\n\t\tconst value = payload\n\t\tconst controller = new AbortController()\n\t\tcontrollers.set(id, controller)\n\t\tvoid Promise.resolve()\n\t\t\t.then(() => {\n\t\t\t\tif (!input(value)) {\n\t\t\t\t\tthrow new Error('input did not satisfy input guard')\n\t\t\t\t}\n\t\t\t\treturn handler(value, { id: entry, signal: controller.signal })\n\t\t\t})\n\t\t\t.then((result) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tport.postMessage({ id, ok: true, value: result })\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tcontrollers.delete(id)\n\t\t\t\tlet message = 'worker operation failed'\n\t\t\t\ttry {\n\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error)\n\t\t\t\t} catch {}\n\t\t\t\ttry {\n\t\t\t\t\tport.postMessage({ id, ok: false, error: message })\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tport.close()\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t})\n\t})\n}\n","import type { NodeThread } from './types.js'\nimport { Worker as ThreadWorker } from 'node:worker_threads'\n\n/**\n * Represents the internal mutable implementation of the readonly {@link NodeThread} observation\n * contract.\n *\n * @remarks\n * Liveness and the first terminal error live behind runtime-private fields. Thread `error`,\n * `messageerror`, and `exit` all latch death, so pool validation cannot reuse a thread whose\n * inbound message could not be deserialized.\n */\nexport class Thread implements NodeThread {\n\treadonly #worker: ThreadWorker\n\treadonly #promise: Promise<NodeThread>\n\treadonly #resolve: (value: NodeThread | PromiseLike<NodeThread>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #recordHandler: (error: Error) => void\n\treadonly #recordExitHandler: (code: number) => void\n\treadonly #onlineHandler: () => void\n\treadonly #spawnErrorHandler: (error: Error) => void\n\treadonly #spawnExitHandler: (code: number) => void\n\t#alive = true\n\t#death: Error | undefined\n\n\tconstructor(script: string | URL, workerData: unknown) {\n\t\tthis.#worker = new ThreadWorker(script, {\n\t\t\t...(workerData !== undefined ? { workerData } : {}),\n\t\t})\n\t\tconst readiness = Promise.withResolvers<NodeThread>()\n\t\tthis.#promise = readiness.promise\n\t\tthis.#resolve = readiness.resolve\n\t\tthis.#reject = readiness.reject\n\t\tthis.#recordHandler = this.#record.bind(this)\n\t\tthis.#recordExitHandler = this.#recordExit.bind(this)\n\t\tthis.#onlineHandler = this.#online.bind(this)\n\t\tthis.#spawnErrorHandler = this.#spawnError.bind(this)\n\t\tthis.#spawnExitHandler = this.#spawnExit.bind(this)\n\n\t\tthis.#worker.on('error', this.#recordHandler)\n\t\tthis.#worker.on('messageerror', this.#recordHandler)\n\t\tthis.#worker.on('exit', this.#recordExitHandler)\n\t\tthis.#worker.once('online', this.#onlineHandler)\n\t\tthis.#worker.once('error', this.#spawnErrorHandler)\n\t\tthis.#worker.once('exit', this.#spawnExitHandler)\n\t}\n\n\tget worker(): ThreadWorker {\n\t\treturn this.#worker\n\t}\n\n\tget alive(): boolean {\n\t\treturn this.#alive\n\t}\n\n\tget death(): Error | undefined {\n\t\treturn this.#death\n\t}\n\n\tget promise(): Promise<NodeThread> {\n\t\treturn this.#promise\n\t}\n\n\tevict(): void {\n\t\tthis.#alive = false\n\t}\n\n\t#record(error: Error): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) this.#death = error\n\t}\n\n\t#recordExit(code: number): void {\n\t\tthis.#alive = false\n\t\tif (this.#death === undefined) {\n\t\t\tthis.#death = new Error(`worker thread exited (code ${String(code)})`)\n\t\t}\n\t}\n\n\t#online(): void {\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#resolve(this)\n\t}\n\n\t#spawnError(error: Error): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('exit', this.#spawnExitHandler)\n\t\tthis.#reject(error)\n\t}\n\n\t#spawnExit(code: number): void {\n\t\tthis.#worker.off('online', this.#onlineHandler)\n\t\tthis.#worker.off('error', this.#spawnErrorHandler)\n\t\tthis.#reject(new Error(`worker thread exited before coming online (code ${String(code)})`))\n\t}\n}\n","import type { QueueContext } from '@orkestrel/queue'\nimport type { Guard } from '@orkestrel/contract'\nimport type { NodeThread } from './types.js'\nimport type { Worker as ThreadWorker } from 'node:worker_threads'\nimport { attempt, isError, isRecord } from '@orkestrel/contract'\nimport { isReply } from './helpers.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents one dispatched worker-thread job — the lifecycle entity behind a job posted to a\n * leased {@link NodeThread}, whose {@link promise} settles with the narrowed reply.\n *\n * @remarks\n * Mints a fresh per-dispatch correlation `id`, posts it with `job: context.id`, and settles\n * when the thread replies for that correlation id. The stable Queue job id reaches the worker\n * handler for idempotency across retries and restore; it is not caller identity or\n * authentication / authorization evidence. Per-job consumer context is explicit,\n * structured-cloneable `input`; ambient context is not worker-thread transport. A success\n * `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`\n * type bridge); a failure rejects with the thread's error string. A thread that had already died\n * rejects synchronously at construction from the latched {@link NodeThread.death} — its death\n * events fired before this dispatch existed and will never fire again, so waiting on the\n * listeners would dangle forever; the latch makes death total across every event ordering. If\n * the thread `error`s / `exit`s mid-flight the job rejects. On a `context.signal` abort it\n * contains the cooperative `abort` post, evicts the thread, and observes `terminate()`\n * settlement because CPU-bound work cannot honour the signal.\n *\n * It owns stable `message` / `messageerror` / death listener identities, settlement,\n * result-guard containment, and abort eviction for one dispatch. Deserialization failure, a\n * matching-id malformed reply, and abort each evict and terminate the thread before rejecting,\n * with termination failure preserved. Non-record, id-less, hostile-id, and foreign-id chatter\n * is ignored. Every per-job listener (`message` / `messageerror` / `error` / `exit` / `abort`)\n * is removed on settle.\n *\n * Eviction reaches `alive` for a {@link NodeThread} this package produced. Against a\n * consumer-supplied `NodeThread` an abort or a `messageerror` still terminates the supplied\n * `worker` and rejects the job, and the implementer owns flipping its own `alive`.\n *\n * @typeParam TResult - The reply type the `result` guard narrows to\n *\n * @example\n * ```ts\n * import { createThread, Dispatch } from '@orkestrel/worker/server'\n *\n * const isNumber = (value: unknown): value is number => typeof value === 'number'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * const controller = new AbortController()\n * const job = new Dispatch(thread, 21, { id: 'job-1', signal: controller.signal }, isNumber)\n * console.log(await job.promise) // 42\n * await thread.worker.terminate()\n * ```\n */\nexport class Dispatch<TResult> {\n\treadonly #thread: NodeThread\n\treadonly #worker: ThreadWorker\n\treadonly #input: unknown\n\treadonly #context: QueueContext\n\treadonly #result: Guard<TResult>\n\treadonly #id = crypto.randomUUID()\n\treadonly #promise: Promise<TResult>\n\treadonly #fulfill: (value: TResult | PromiseLike<TResult>) => void\n\treadonly #reject: (reason?: unknown) => void\n\treadonly #messageHandler: (value: unknown) => void\n\treadonly #messageErrorHandler: (error: Error) => void\n\treadonly #errorHandler: (error: Error) => void\n\treadonly #exitHandler: () => void\n\treadonly #abortHandler: () => void\n\t#settled = false\n\n\tconstructor(thread: NodeThread, input: unknown, context: QueueContext, result: Guard<TResult>) {\n\t\tthis.#thread = thread\n\t\tthis.#worker = thread.worker\n\t\tthis.#input = input\n\t\tthis.#context = context\n\t\tthis.#result = result\n\t\tconst settlement = Promise.withResolvers<TResult>()\n\t\tthis.#promise = settlement.promise\n\t\tthis.#fulfill = settlement.resolve\n\t\tthis.#reject = settlement.reject\n\t\tthis.#messageHandler = this.#message.bind(this)\n\t\tthis.#messageErrorHandler = this.#messageError.bind(this)\n\t\tthis.#errorHandler = this.#error.bind(this)\n\t\tthis.#exitHandler = this.#exit.bind(this)\n\t\tthis.#abortHandler = this.#abort.bind(this)\n\t\tthis.#start()\n\t}\n\n\tget promise(): Promise<TResult> {\n\t\treturn this.#promise\n\t}\n\n\t#start(): void {\n\t\tif (this.#thread.death !== undefined || !this.#thread.alive) {\n\t\t\tthis.#fail(this.#thread.death ?? new Error('worker thread is dead'))\n\t\t\treturn\n\t\t}\n\t\tthis.#worker.on('message', this.#messageHandler)\n\t\tthis.#worker.on('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.on('error', this.#errorHandler)\n\t\tthis.#worker.on('exit', this.#exitHandler)\n\t\tif (this.#context.signal.aborted) {\n\t\t\tthis.#abort()\n\t\t\treturn\n\t\t}\n\t\tthis.#context.signal.addEventListener('abort', this.#abortHandler, { once: true })\n\t\ttry {\n\t\t\tthis.#worker.postMessage({\n\t\t\t\tid: this.#id,\n\t\t\t\tjob: this.#context.id,\n\t\t\t\tcommand: 'run',\n\t\t\t\tinput: this.#input,\n\t\t\t})\n\t\t} catch (error: unknown) {\n\t\t\tthis.#fail(isError(error) ? error : new Error(String(error)))\n\t\t}\n\t}\n\n\t#message(value: unknown): void {\n\t\tif (!isRecord(value)) return\n\t\tconst id = attempt(() => value.id)\n\t\tif (!id.success || id.value !== this.#id) return\n\t\tif (!isReply(value, this.#id)) {\n\t\t\tthis.#terminate(new Error('worker reply was malformed'))\n\t\t\treturn\n\t\t}\n\t\tif (value.ok) {\n\t\t\tconst reply = value.value\n\t\t\ttry {\n\t\t\t\tif (this.#result(reply)) this.#succeed(reply)\n\t\t\t\telse this.#fail(new Error('reply did not satisfy result guard'))\n\t\t\t} catch (error: unknown) {\n\t\t\t\tthis.#fail(error)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tthis.#fail(new Error(value.error))\n\t}\n\n\t#messageError(error: Error): void {\n\t\tthis.#terminate(error)\n\t}\n\n\t#error(error: Error): void {\n\t\tthis.#fail(error)\n\t}\n\n\t#exit(): void {\n\t\tthis.#fail(this.#thread.death ?? new Error('worker thread exited'))\n\t}\n\n\t#abort(): void {\n\t\tconst notification: unknown[] = []\n\t\ttry {\n\t\t\tthis.#worker.postMessage({ id: this.#id, command: 'abort' })\n\t\t} catch (cause: unknown) {\n\t\t\tnotification.push(cause)\n\t\t}\n\t\tthis.#terminate(this.#context.signal.reason, notification)\n\t}\n\n\t#terminate(error: unknown, notification: readonly unknown[] = []): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tif (this.#thread instanceof Thread) this.#thread.evict()\n\t\tlet termination: Promise<number>\n\t\ttry {\n\t\t\ttermination = this.#worker.terminate()\n\t\t} catch (cause: unknown) {\n\t\t\tthis.#reject(new AggregateError([error, ...notification, cause], 'worker termination failed'))\n\t\t\treturn\n\t\t}\n\t\tvoid termination.then(\n\t\t\t() => {\n\t\t\t\tif (notification.length === 0) this.#reject(error)\n\t\t\t\telse {\n\t\t\t\t\tthis.#reject(\n\t\t\t\t\t\tnew AggregateError([error, ...notification], 'worker abort notification failed'),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\t\t\t(cause: unknown) =>\n\t\t\t\tthis.#reject(\n\t\t\t\t\tnew AggregateError([error, ...notification, cause], 'worker termination failed'),\n\t\t\t\t),\n\t\t)\n\t}\n\n\t#succeed(value: TResult): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#fulfill(value)\n\t}\n\n\t#fail(error: unknown): void {\n\t\tif (this.#settled) return\n\t\tthis.#settled = true\n\t\tthis.#detach()\n\t\tthis.#reject(error)\n\t}\n\n\t#detach(): void {\n\t\tthis.#worker.off('message', this.#messageHandler)\n\t\tthis.#worker.off('messageerror', this.#messageErrorHandler)\n\t\tthis.#worker.off('error', this.#errorHandler)\n\t\tthis.#worker.off('exit', this.#exitHandler)\n\t\tthis.#context.signal.removeEventListener('abort', this.#abortHandler)\n\t}\n}\n","import type { EmitterErrorHandler, EmitterHooks } from '@orkestrel/emitter'\nimport type { WorkerEventMap, WorkerInterface } from '@src/core'\nimport type { Guard } from '@orkestrel/contract'\nimport type { QueueContext, QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createWorker } from '@src/core'\nimport { attempt } from '@orkestrel/contract'\nimport { Dispatch } from './Dispatch.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Represents the internal composition entity backing {@link createNodeWorker}.\n *\n * @remarks\n * Supplies bound Pool and Queue operations without nested function assignments. The resulting\n * public entity is the plain core {@link WorkerInterface}.\n */\nexport class NodeWorker<TInput, TResult> {\n\treadonly #on: EmitterHooks<WorkerEventMap<TResult>> | undefined\n\treadonly #error: EmitterErrorHandler | undefined\n\treadonly #script: string | URL\n\treadonly #input: Guard<TInput>\n\treadonly #result: Guard<TResult>\n\treadonly #workerData: unknown\n\treadonly #concurrency: number | undefined\n\treadonly #retries: number | undefined\n\treadonly #timeout: number | undefined\n\treadonly #store: QueueStoreInterface<TInput> | undefined\n\n\tconstructor(options: NodeWorkerOptions<TInput, TResult>) {\n\t\tthis.#on = options.on\n\t\tthis.#error = options.error\n\t\tthis.#script = options.script\n\t\tthis.#input = options.input\n\t\tthis.#result = options.result\n\t\tthis.#workerData = options.workerData\n\t\tthis.#concurrency = options.concurrency\n\t\tthis.#retries = options.retries\n\t\tthis.#timeout = options.timeout\n\t\tthis.#store = options.store\n\t}\n\n\tbuild(): WorkerInterface<TInput, TResult> {\n\t\treturn createWorker<TInput, NodeThread, TResult>({\n\t\t\tpool: {\n\t\t\t\tcreate: this.#create.bind(this),\n\t\t\t\tdestroy: this.#destroy.bind(this),\n\t\t\t\tvalidate: this.#validate.bind(this),\n\t\t\t\t...(this.#concurrency !== undefined ? { max: this.#concurrency } : {}),\n\t\t\t},\n\t\t\thandler: this.#handle.bind(this),\n\t\t\t...(this.#on !== undefined ? { on: this.#on } : {}),\n\t\t\t...(this.#error !== undefined ? { error: this.#error } : {}),\n\t\t\t...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),\n\t\t\t...(this.#retries !== undefined ? { retries: this.#retries } : {}),\n\t\t\t...(this.#timeout !== undefined ? { timeout: this.#timeout } : {}),\n\t\t\t...(this.#store !== undefined ? { store: this.#store } : {}),\n\t\t})\n\t}\n\n\t#create(): Promise<NodeThread> {\n\t\treturn new Thread(this.#script, this.#workerData).promise\n\t}\n\n\tasync #destroy(thread: NodeThread): Promise<void> {\n\t\tawait thread.worker.terminate()\n\t}\n\n\t#validate(thread: NodeThread): boolean {\n\t\treturn thread.alive && thread.worker.threadId > 0\n\t}\n\n\t#handle(input: TInput, thread: NodeThread, context: QueueContext): Promise<TResult> {\n\t\tconst outcome = attempt(() => this.#input(input))\n\t\tif (!outcome.success) return Promise.reject(outcome.error)\n\t\tif (!outcome.value) {\n\t\t\treturn Promise.reject(new Error('input did not satisfy input guard'))\n\t\t}\n\t\treturn new Dispatch(thread, input, context, this.#result).promise\n\t}\n}\n","import type { WorkerInterface } from '@src/core'\nimport type { ContractShape, Infer } from '@orkestrel/contract'\nimport type { QueueStoreInterface } from '@orkestrel/queue'\nimport type { NodeThread, NodeWorkerOptions } from './types.js'\nimport { createJSONDriver } from '@orkestrel/database/server'\nimport { createDatabaseQueueStore } from '@orkestrel/queue'\nimport { NodeWorker } from './NodeWorker.js'\nimport { Thread } from './Thread.js'\n\n/**\n * Creates one live worker thread and resolves it as a {@link NodeThread} after it comes\n * online.\n *\n * @remarks\n * Constructs the thread with the `script` module and the cloned `workerData`, then\n * resolves on the thread's `online` event (rejecting on an early `error` or on an `exit`\n * that arrives before `online`, so the spawn promise is total — it can never dangle on a\n * thread that died without erroring). The returned entity attaches persistent `error` /\n * `exit` listeners that flip `alive` to `false` and latch the first terminal event on\n * {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through\n * its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a\n * dispatch that attaches only after the death (through the latch). A `messageerror` is terminal\n * too, so a thread whose inbound payload could not be deserialized is never reused. The latch\n * closes a real race: a thread can become terminal before the readiness promise continuation\n * hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.\n * Without the latch, that job would wait forever. {@link createNodeWorker} spawns its pooled\n * threads the same way; reach for this to drive one thread yourself.\n *\n * @param script - The worker module the thread runs (its module must call `serveWorker`)\n * @param workerData - Opaque, structured-cloneable data handed to the thread at spawn\n * @returns A promise resolving the online {@link NodeThread}\n *\n * @example\n * ```ts\n * import { createThread } from '@orkestrel/worker/server'\n *\n * const thread = await createThread(new URL('./double.js', import.meta.url))\n * await thread.worker.terminate()\n * ```\n */\nexport function createThread(script: string | URL, workerData?: unknown): Promise<NodeThread> {\n\treturn new Thread(script, workerData).promise\n}\n\n/**\n * Creates a persistent JSON-file {@link QueueStoreInterface} — the core\n * `createDatabaseQueueStore` over a server {@link createJSONDriver}.\n *\n * @remarks\n * A queue's durable state is a database table, so JSON persistence reuses the\n * existing JSON-file driver rather than a bespoke store: the entries are written to\n * (and reloaded from) the file at `path`, surviving a process restart. There is no new\n * class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the\n * driver changes where the bytes live. The `input` shape must be JSON-serializable\n * (the JSON driver round-trips it as JSON). Build a second store over the same `path` to\n * resume the outstanding entries a prior store persisted.\n *\n * @typeParam TInput - The contract shape of each entry's `input` payload\n * @param path - The JSON file the entries are loaded from and flushed to\n * @param input - The {@link ContractShape} for the work payload (the `input` column)\n * @returns A JSON-file-backed {@link QueueStoreInterface}, typed by `input`\n *\n * @example\n * ```ts\n * import { stringShape } from '@orkestrel/contract'\n * import { createJSONQueueStore } from '@orkestrel/worker/server'\n *\n * const store = createJSONQueueStore('data/queue.json', stringShape())\n * await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })\n * // A later process resumes the outstanding work:\n * const resumed = createJSONQueueStore('data/queue.json', stringShape())\n * const outstanding = await resumed.load()\n * ```\n */\nexport function createJSONQueueStore<TInput extends ContractShape>(\n\tpath: string,\n\tinput: TInput,\n): QueueStoreInterface<Infer<TInput>> {\n\treturn createDatabaseQueueStore(input, createJSONDriver(path))\n}\n\n/**\n * Creates a CPU-parallel worker over `node:worker_threads` — a thin specialization of the\n * core `createWorker` whose pooled resource is a worker thread.\n *\n * @remarks\n * Composition, not reimplementation: all concurrency, retries, per-attempt timeout,\n * lifecycle, and durability are the core `Worker`'s (a `Queue` ⨉ `Pool`). This factory\n * supplies only the thread pairing — the pool `create`s a thread (the same spawn\n * {@link createThread} publishes), `destroy`s it with `terminate()`, and `validate`s it by\n * `alive && threadId > 0` (so an evicted / crashed thread is dropped and replaced) — and an\n * internal handler that narrows the input through `options.input` (fail-fast before the\n * structured-clone boundary) then runs a {@link Dispatch} against the leased thread,\n * narrowing the reply through\n * `options.result`. `TInput` and `TResult` infer from the `input` and `result` guards, so\n * call sites need no explicit type arguments. The boundary is crossed with no `as`: the\n * guards reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`\n * terminates the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a\n * subsequent job spawns a fresh thread. The worker script's module must call\n * `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.\n *\n * @typeParam TInput - The work payload each job carries (inferred from `input`)\n * @typeParam TResult - The value a thread resolves for a job (inferred from `result`)\n * @param options - The `script` plus the `input` / `result` guards and optional\n * `on` / `error` / `workerData` / `concurrency` / `retries` / `timeout` / `store`\n * (see {@link NodeWorkerOptions})\n * @returns A working {@link WorkerInterface} backed by a thread pool\n *\n * @example\n * ```ts\n * import { createNodeWorker } from '@orkestrel/worker/server'\n *\n * const worker = createNodeWorker({\n * \tscript: new URL('./double.js', import.meta.url),\n * \tinput: (value): value is number => typeof value === 'number',\n * \tresult: (value): value is number => typeof value === 'number',\n * \tconcurrency: 4,\n * })\n *\n * const doubled = await worker.enqueue(21) // 42, computed on a worker thread\n * await worker.destroy() // terminates every thread\n * ```\n */\nexport function createNodeWorker<TInput, TResult>(\n\toptions: NodeWorkerOptions<TInput, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new NodeWorker(options).build()\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkestrel/worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
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",
|
|
@@ -83,23 +83,23 @@
|
|
|
83
83
|
"test:setup": "vitest run --config vite.config.ts --no-cache --reporter=dot --project setup"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@orkestrel/contract": "^0.0.
|
|
87
|
-
"@orkestrel/database": "^0.0.
|
|
88
|
-
"@orkestrel/emitter": "^0.0.
|
|
89
|
-
"@orkestrel/pool": "^0.0.
|
|
90
|
-
"@orkestrel/queue": "^0.0.
|
|
86
|
+
"@orkestrel/contract": "^0.0.19",
|
|
87
|
+
"@orkestrel/database": "^0.0.17",
|
|
88
|
+
"@orkestrel/emitter": "^0.0.11",
|
|
89
|
+
"@orkestrel/pool": "^0.0.14",
|
|
90
|
+
"@orkestrel/queue": "^0.0.16"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
|
-
"@microsoft/api-extractor": "^7.59.
|
|
94
|
-
"@orkestrel/guide": "^0.0.
|
|
95
|
-
"@orkestrel/probe": "^0.0.
|
|
96
|
-
"@orkestrel/scaffold": "^0.0.
|
|
97
|
-
"@orkestrel/test": "^0.0.
|
|
98
|
-
"@types/node": "^26.6.
|
|
99
|
-
"oxfmt": "^0.
|
|
100
|
-
"oxlint": "^1.
|
|
93
|
+
"@microsoft/api-extractor": "^7.59.3",
|
|
94
|
+
"@orkestrel/guide": "^0.0.24",
|
|
95
|
+
"@orkestrel/probe": "^0.0.19",
|
|
96
|
+
"@orkestrel/scaffold": "^0.0.90",
|
|
97
|
+
"@orkestrel/test": "^0.0.24",
|
|
98
|
+
"@types/node": "^26.6.4",
|
|
99
|
+
"oxfmt": "^0.71.0",
|
|
100
|
+
"oxlint": "^1.86.0",
|
|
101
101
|
"typescript": "^6.0.3",
|
|
102
|
-
"vite": "^8.3.
|
|
102
|
+
"vite": "^8.3.2",
|
|
103
103
|
"vitest": "^4.1.11"
|
|
104
104
|
},
|
|
105
105
|
"engines": {
|