@orkestrel/worker 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,22 +1,19 @@
1
1
  # @orkestrel/worker
2
2
 
3
- A typed, resource-backed **job worker** for the `@orkestrel` line: a `Worker`
4
- is a `Queue` (`@orkestrel/queue`) whose handler runs against an automatically
5
- acquired resource leased from a `Pool` (`@orkestrel/pool`) — released when the
6
- job settles, even on throw. Composition, not reimplementation: all
7
- concurrency, retries, per-attempt timeout, abort, and durability are the
8
- Queue's; all idle reuse and `max` backpressure are the Pool's. The worker is
9
- observable (a typed `emitter` re-exposes the underlying queue's job lifecycle
10
- `enqueue` / `start` / `retry` / `success` / `failure` / `abort` / `drain`).
11
- For CPU-parallel work, the server surface's `createNodeWorker` specializes the
12
- core `createWorker` over a pool of `node:worker_threads`, crossing the
13
- structured-clone boundary with zero `as` via `input` / `result` guards. Each
14
- thread handler receives `{ id, signal }`: `id` is the Queue's stable idempotency
15
- key across retries and crash restore, while `signal` is per attempt. The wire
16
- protocol separately mints a fresh correlation id for each dispatch so a stale
17
- reply cannot settle a later retry. The stable id identifies work, not a caller,
18
- and is not authentication or authorization evidence; per-job consumer context
19
- remains explicit structured-cloneable input rather than ambient thread state.
3
+ > A resource-backed job worker: a thin facade composing a `Queue` (`@orkestrel/queue`) with
4
+ > a `Pool` (`@orkestrel/pool`), where each job's handler runs against an automatically
5
+ > acquired pooled resource released when the job settles.
6
+
7
+ Create a worker with the `createWorker` function, give it the pool's `create` and
8
+ `destroy` plus the handler each job runs, then `enqueue` inputs and await their
9
+ results. Subscribe to the typed `emitter` for the job lifecycle the worker
10
+ re-exposes from its underlying queue. Reach for the server surface's
11
+ `createNodeWorker` where the work is CPU-bound: it specializes `createWorker`
12
+ over a pool of `node:worker_threads` and crosses the structured-clone boundary
13
+ through `input` and `result` guards with no `as`. A thread handler receives
14
+ `{ id, signal }`: `id` is the Queue's stable idempotency key across retries and
15
+ crash restore, and `signal` is per attempt. That id identifies work, not a
16
+ caller, and is not authentication or authorization evidence.
20
17
  Part of the `@orkestrel` line.
21
18
 
22
19
  ## Install
@@ -27,6 +24,8 @@ npm install @orkestrel/worker
27
24
 
28
25
  ## Requirements
29
26
 
27
+ The package runs under these conditions:
28
+
30
29
  - Node.js >= 22.12.0
31
30
  - ESM and CommonJS builds ship for both the core and server entry points
32
31
 
@@ -68,11 +67,11 @@ await worker.destroy()
68
67
 
69
68
  For the full surface — the `Worker` facade, `createNodeWorker` / `serveWorker`,
70
69
  the durable `createJSONQueueStore`, the observable `emitter`, and usage
71
- patterns — see [`guides/src/worker.md`](guides/src/worker.md).
70
+ patterns — see [`guides/worker.md`](guides/worker.md).
72
71
 
73
72
  ## Package
74
73
 
75
- Published with two entry points per the `exports` field in `package.json`:
74
+ Published with the entry points the `exports` field in `package.json` names:
76
75
  the environment-agnostic core (`.`) and the Node-only server surface
77
76
  (`./server`).
78
77
 
@@ -4,13 +4,13 @@ let _orkestrel_pool = require("@orkestrel/pool");
4
4
  let _orkestrel_queue = require("@orkestrel/queue");
5
5
  //#region src/core/Worker.ts
6
6
  /**
7
- * A resource-backed job worker — a thin facade composing a `Queue` (`@orkestrel/queue`)
8
- * with a `Pool` (`@orkestrel/pool`).
7
+ * Represents a resource-backed job worker — a thin facade composing a `Queue`
8
+ * (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).
9
9
  *
10
10
  * @remarks
11
11
  * - **Composition, not reimplementation.** The Worker owns a `Pool` (built from
12
- * `options.pool`) and a `Queue` whose handler ACQUIRES a pooled resource, runs the
13
- * user handler against it, and RELEASES it in a `finally`. All concurrency, retries,
12
+ * `options.pool`) and a `Queue` whose handler `acquire`s a pooled resource, runs the
13
+ * user handler against it, and `release`s it in a `finally`. All concurrency, retries,
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
@@ -20,27 +20,27 @@ let _orkestrel_queue = require("@orkestrel/queue");
20
20
  * options. At most one resource exists per in-flight job by default, and idle resources are
21
21
  * reused across jobs.
22
22
  * - **Acquire over the attempt signal.** Each job acquires using the attempt's
23
- * `execution.signal`, so an `abort` / `timeout` while waiting for a resource rejects
23
+ * `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
24
24
  * the acquire — the Queue then handles retry / rejection, and there is no token to
25
25
  * release (the resource was never leased).
26
- * - **Lifecycle (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /
27
- * `abort` / `clear` delegate to the queue; `count` / `active` / `paused` / `stopped`
28
- * read it. `stop` / `abort` / `clear` return the queue's own cleanup barriers.
29
- * `destroy` returns one stable barrier while it tears down the queue, then the pool,
30
- * and destroys the worker emitter last. A sole cleanup failure is preserved by
26
+ * - **Lifecycle (see the guide's `## Methods` section).** `enqueue` / `restore` / `start` /
27
+ * `stop` / `pause` / `resume` / `abort` / `clear` delegate to the queue; `count` / `active` /
28
+ * `paused` / `stopped` read it. `stop` / `abort` / `clear` return the queue's own cleanup
29
+ * barriers. `destroy` returns one stable barrier while it tears down the queue, then the
30
+ * pool, and destroys the worker emitter last. A sole cleanup failure is preserved by
31
31
  * identity; failures from both layers become an ordered `AggregateError`.
32
32
  * - **Durability.** An optional `store` is passed straight through to the queue, so the
33
33
  * worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).
34
- * - **Observable (§13).** The owned {@link emitter} ({@link WorkerEventMap}) RE-EXPOSES the
35
- * underlying queue's job lifecycle (`enqueue` / `start` / `retry` / `success` / `failure` /
36
- * `abort` / `drain`) as the worker's OWN events — bridged from the inner queue's emitter at
37
- * construction — so a consumer observes the worker without reaching through to internals.
38
- * The bridge re-emits directly on the worker's own emitter; the worker emitter isolates a
39
- * listener throw and routes it to its `error` handler (the `error` option), so a buggy
40
- * worker observer can never corrupt the inner queue or pool — the bridge listener never
41
- * throws, so the inner queue's own emit stays balanced. The pool's create / acquire /
42
- * release events stay the pool's internal concern (a Worker manages its own resources);
43
- * observe a `Pool` directly for those.
34
+ * - **Observable (see the guide's `## Observing` section).** The owned {@link emitter}
35
+ * ({@link WorkerEventMap}) re-exposes the underlying queue's job lifecycle (`enqueue` /
36
+ * `start` / `retry` / `success` / `failure` / `abort` / `drain`) as the worker's own events —
37
+ * bridged from the inner queue's emitter at construction — so a consumer observes the worker
38
+ * without reaching through to internals. The bridge re-emits directly on the worker's own
39
+ * emitter; the worker emitter isolates a listener throw and routes it to its `error` handler
40
+ * (the `error` option), so a buggy worker observer can never corrupt the inner queue or pool
41
+ * — the bridge listener never throws, so the inner queue's own emit stays balanced. The
42
+ * pool's create / acquire / release events stay the pool's internal concern (a Worker manages
43
+ * its own resources); observe a `Pool` directly for those.
44
44
  */
45
45
  var Worker = class {
46
46
  #queue;
@@ -120,10 +120,10 @@ var Worker = class {
120
120
  this.#teardown(ending);
121
121
  return ending.promise;
122
122
  }
123
- async #handle(input, execution) {
124
- const token = await this.#pool.acquire(execution.signal);
123
+ async #handle(input, context) {
124
+ const token = await this.#pool.acquire(context.signal);
125
125
  try {
126
- return await this.#handler(input, token.value, execution);
126
+ return await this.#handler(input, token.value, context);
127
127
  } finally {
128
128
  token.release();
129
129
  }
@@ -159,30 +159,33 @@ var Worker = class {
159
159
  //#endregion
160
160
  //#region src/core/factories.ts
161
161
  /**
162
- * Create a resource-backed job worker — a `Queue` (`@orkestrel/queue`) marrying a `Pool`
163
- * (`@orkestrel/pool`). Each enqueued input runs through the handler against an
164
- * automatically acquired pooled resource (released when the job settles), with the
165
- * queue's bounded concurrency, retries, and per-attempt timeout / abort.
162
+ * Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) composed with a
163
+ * `Pool` (`@orkestrel/pool`), where each enqueued input runs through the handler against
164
+ * an automatically acquired pooled resource released when the job settles.
166
165
  *
167
166
  * @remarks
168
- * The pool's `max` defaults to `concurrency`, so resources match the jobs in flight and
169
- * are reused across jobs. A handler that throws still releases its resource (the
167
+ * Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.
168
+ * Default for the pool's `max`: the `concurrency` value, so resources match the jobs in flight.
169
+ * Resources are reused across jobs. A handler that throws still releases its resource (the
170
170
  * acquire/release pair brackets the call in a `finally`), so a later job reuses it. The
171
171
  * lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)
172
- * delegates to the queue; `destroy` also tears the pool down. Observable (§13): a typed
173
- * `emitter` surfaces the queue lifecycle (`enqueue` / `start` / `success` / `failure` / …).
172
+ * delegates to the queue; `destroy` also tears the pool down. It is observable (see the
173
+ * guide's `## Observing` section): a typed `emitter` surfaces the queue lifecycle
174
+ * (`enqueue` / `start` / `success` / `failure` / …).
174
175
  *
175
176
  * @typeParam TInput - The work input each job carries
176
177
  * @typeParam TResource - The pooled resource each job runs against
177
178
  * @typeParam TResult - The value the handler resolves for a job
178
- * @param options - The `handler` and `pool` plus optional `concurrency` (default `1`),
179
- * `retries` (default `0`), and a default per-attempt `timeout` in milliseconds
179
+ * @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,
180
+ * `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})
180
181
  * @returns A working {@link WorkerInterface}
181
182
  *
182
- * @example
183
+ * @example A resource-backed worker
183
184
  * ```ts
184
185
  * import { createWorker } from '@orkestrel/worker'
185
186
  *
187
+ * // A Queue whose handler runs each job against a pooled resource (acquired before the
188
+ * // handler, released after it — even on throw). The pool's `max` defaults to `concurrency`.
186
189
  * const worker = createWorker<Query, Connection, Rows>({
187
190
  * pool: { create: () => connect(), destroy: (connection) => connection.close() },
188
191
  * handler: (query, connection, { signal }) => connection.run(query, signal),
@@ -191,6 +194,7 @@ var Worker = class {
191
194
  * })
192
195
  *
193
196
  * const rows = await worker.enqueue(query)
197
+ * await worker.destroy() // awaits queue cleanup, pool cleanup, then emitter teardown
194
198
  * ```
195
199
  */
196
200
  function createWorker(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["#queue","#pool","#emitter","#handler","#handle","#bridge","#ending","#teardown"],"sources":["../../../src/core/Worker.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { QueueEntryOptions, QueueExecution } 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 * A resource-backed job worker — a thin facade composing a `Queue` (`@orkestrel/queue`)\n * 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 ACQUIRES a pooled resource, runs the\n * user handler against it, and RELEASES 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 * `execution.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 (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /\n * `abort` / `clear` delegate to the queue; `count` / `active` / `paused` / `stopped`\n * read it. `stop` / `abort` / `clear` return the queue's own cleanup barriers.\n * `destroy` returns one stable barrier while it tears down the queue, then the pool,\n * 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 (§13).** The owned {@link emitter} ({@link WorkerEventMap}) RE-EXPOSES the\n * underlying queue's job lifecycle (`enqueue` / `start` / `retry` / `success` / `failure` /\n * `abort` / `drain`) as the worker's OWN events — bridged from the inner queue's emitter at\n * construction — so a consumer observes the worker without reaching through to internals.\n * The bridge re-emits directly on the worker's own emitter; the worker emitter isolates a\n * listener throw and routes it to its `error` handler (the `error` option), so a buggy\n * worker observer can never corrupt the inner queue or pool — the bridge listener never\n * throws, so the inner queue's own emit stays balanced. The pool's create / acquire /\n * release events stay the pool's internal concern (a Worker manages its own resources);\n * 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 (§13) — the worker's OWN emitter, fed by the queue→worker\n\t// bridge. The emitter isolates a worker observer's throw (routing it to the `error`\n\t// 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, execution: QueueExecution): Promise<TResult> {\n\t\tconst token = await this.#pool.acquire(execution.signal)\n\t\ttry {\n\t\t\treturn await this.#handler(input, token.value, execution)\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 * Create a resource-backed job worker — a `Queue` (`@orkestrel/queue`) marrying a `Pool`\n * (`@orkestrel/pool`). Each enqueued input runs through the handler against an\n * automatically acquired pooled resource (released when the job settles), with the\n * queue's bounded concurrency, retries, and per-attempt timeout / abort.\n *\n * @remarks\n * The pool's `max` defaults to `concurrency`, so resources match the jobs in flight and\n * 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. Observable (§13): a typed\n * `emitter` surfaces the queue lifecycle (`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 optional `concurrency` (default `1`),\n * `retries` (default `0`), and a default per-attempt `timeout` in milliseconds\n * @returns A working {@link WorkerInterface}\n *\n * @example\n * ```ts\n * import { createWorker } from '@orkestrel/worker'\n *\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 * ```\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":[],"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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,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,IAAI,mBAAA,QAAiC;GACpD,GAAI,OAAO,KAAA,IAAY,EAAE,GAAG,IAAI,CAAC;GACjC,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;EACxC,CAAC;EACD,KAAK,SAAS,IAAI,iBAAA,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,EAAE,KAAK,IAAI,QAAQ,OAAO,WAAW,QAAQ,SAAS,aAD/C,QAAQ;EAErB,KAAK,QAAQ,IAAI,gBAAA,KAAgB;GAChC;GACA,KAAK,QAAQ,KAAA,IAAY,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;CACd;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC1JA,SAAgB,aACf,SACmC;CACnC,OAAO,IAAI,OAAO,OAAO;AAC1B"}
@@ -1,36 +1,39 @@
1
- import { EmitterErrorHandler } from '@orkestrel/emitter';
2
- import { EmitterHooks } from '@orkestrel/emitter';
3
- import { EmitterInterface } from '@orkestrel/emitter';
4
- import { PoolOptions } from '@orkestrel/pool';
5
- import { QueueEntryOptions } from '@orkestrel/queue';
6
- import { QueueExecution } from '@orkestrel/queue';
7
- import { QueueStoreInterface } from '@orkestrel/queue';
1
+ import type { EmitterErrorHandler } from '@orkestrel/emitter';
2
+ import type { EmitterHooks } from '@orkestrel/emitter';
3
+ import type { EmitterInterface } from '@orkestrel/emitter';
4
+ import type { PoolOptions } from '@orkestrel/pool';
5
+ import type { QueueContext } from '@orkestrel/queue';
6
+ import type { QueueEntryOptions } from '@orkestrel/queue';
7
+ import type { QueueStoreInterface } from '@orkestrel/queue';
8
8
 
9
9
  /**
10
- * Create a resource-backed job worker — a `Queue` (`@orkestrel/queue`) marrying a `Pool`
11
- * (`@orkestrel/pool`). Each enqueued input runs through the handler against an
12
- * automatically acquired pooled resource (released when the job settles), with the
13
- * queue's bounded concurrency, retries, and per-attempt timeout / abort.
10
+ * Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) composed with a
11
+ * `Pool` (`@orkestrel/pool`), where each enqueued input runs through the handler against
12
+ * an automatically acquired pooled resource released when the job settles.
14
13
  *
15
14
  * @remarks
16
- * The pool's `max` defaults to `concurrency`, so resources match the jobs in flight and
17
- * are reused across jobs. A handler that throws still releases its resource (the
15
+ * Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.
16
+ * Default for the pool's `max`: the `concurrency` value, so resources match the jobs in flight.
17
+ * Resources are reused across jobs. A handler that throws still releases its resource (the
18
18
  * acquire/release pair brackets the call in a `finally`), so a later job reuses it. The
19
19
  * lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)
20
- * delegates to the queue; `destroy` also tears the pool down. Observable (§13): a typed
21
- * `emitter` surfaces the queue lifecycle (`enqueue` / `start` / `success` / `failure` / …).
20
+ * delegates to the queue; `destroy` also tears the pool down. It is observable (see the
21
+ * guide's `## Observing` section): a typed `emitter` surfaces the queue lifecycle
22
+ * (`enqueue` / `start` / `success` / `failure` / …).
22
23
  *
23
24
  * @typeParam TInput - The work input each job carries
24
25
  * @typeParam TResource - The pooled resource each job runs against
25
26
  * @typeParam TResult - The value the handler resolves for a job
26
- * @param options - The `handler` and `pool` plus optional `concurrency` (default `1`),
27
- * `retries` (default `0`), and a default per-attempt `timeout` in milliseconds
27
+ * @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,
28
+ * `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})
28
29
  * @returns A working {@link WorkerInterface}
29
30
  *
30
- * @example
31
+ * @example A resource-backed worker
31
32
  * ```ts
32
33
  * import { createWorker } from '@orkestrel/worker'
33
34
  *
35
+ * // A Queue whose handler runs each job against a pooled resource (acquired before the
36
+ * // handler, released after it — even on throw). The pool's `max` defaults to `concurrency`.
34
37
  * const worker = createWorker<Query, Connection, Rows>({
35
38
  * pool: { create: () => connect(), destroy: (connection) => connection.close() },
36
39
  * handler: (query, connection, { signal }) => connection.run(query, signal),
@@ -39,18 +42,19 @@ import { QueueStoreInterface } from '@orkestrel/queue';
39
42
  * })
40
43
  *
41
44
  * const rows = await worker.enqueue(query)
45
+ * await worker.destroy() // awaits queue cleanup, pool cleanup, then emitter teardown
42
46
  * ```
43
47
  */
44
48
  export declare function createWorker<TInput, TResource, TResult>(options: WorkerOptions_2<TInput, TResource, TResult>): WorkerInterface<TInput, TResult>;
45
49
 
46
50
  /**
47
- * A resource-backed job worker — a thin facade composing a `Queue` (`@orkestrel/queue`)
48
- * with a `Pool` (`@orkestrel/pool`).
51
+ * Represents a resource-backed job worker — a thin facade composing a `Queue`
52
+ * (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).
49
53
  *
50
54
  * @remarks
51
55
  * - **Composition, not reimplementation.** The Worker owns a `Pool` (built from
52
- * `options.pool`) and a `Queue` whose handler ACQUIRES a pooled resource, runs the
53
- * user handler against it, and RELEASES it in a `finally`. All concurrency, retries,
56
+ * `options.pool`) and a `Queue` whose handler `acquire`s a pooled resource, runs the
57
+ * user handler against it, and `release`s it in a `finally`. All concurrency, retries,
54
58
  * timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
55
59
  * - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
56
60
  * safe integer after caller options are captured once. Only `undefined` defaults
@@ -60,27 +64,27 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
60
64
  * options. At most one resource exists per in-flight job by default, and idle resources are
61
65
  * reused across jobs.
62
66
  * - **Acquire over the attempt signal.** Each job acquires using the attempt's
63
- * `execution.signal`, so an `abort` / `timeout` while waiting for a resource rejects
67
+ * `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
64
68
  * the acquire — the Queue then handles retry / rejection, and there is no token to
65
69
  * release (the resource was never leased).
66
- * - **Lifecycle (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /
67
- * `abort` / `clear` delegate to the queue; `count` / `active` / `paused` / `stopped`
68
- * read it. `stop` / `abort` / `clear` return the queue's own cleanup barriers.
69
- * `destroy` returns one stable barrier while it tears down the queue, then the pool,
70
- * and destroys the worker emitter last. A sole cleanup failure is preserved by
70
+ * - **Lifecycle (see the guide's `## Methods` section).** `enqueue` / `restore` / `start` /
71
+ * `stop` / `pause` / `resume` / `abort` / `clear` delegate to the queue; `count` / `active` /
72
+ * `paused` / `stopped` read it. `stop` / `abort` / `clear` return the queue's own cleanup
73
+ * barriers. `destroy` returns one stable barrier while it tears down the queue, then the
74
+ * pool, and destroys the worker emitter last. A sole cleanup failure is preserved by
71
75
  * identity; failures from both layers become an ordered `AggregateError`.
72
76
  * - **Durability.** An optional `store` is passed straight through to the queue, so the
73
77
  * worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).
74
- * - **Observable (§13).** The owned {@link emitter} ({@link WorkerEventMap}) RE-EXPOSES the
75
- * underlying queue's job lifecycle (`enqueue` / `start` / `retry` / `success` / `failure` /
76
- * `abort` / `drain`) as the worker's OWN events — bridged from the inner queue's emitter at
77
- * construction — so a consumer observes the worker without reaching through to internals.
78
- * The bridge re-emits directly on the worker's own emitter; the worker emitter isolates a
79
- * listener throw and routes it to its `error` handler (the `error` option), so a buggy
80
- * worker observer can never corrupt the inner queue or pool — the bridge listener never
81
- * throws, so the inner queue's own emit stays balanced. The pool's create / acquire /
82
- * release events stay the pool's internal concern (a Worker manages its own resources);
83
- * observe a `Pool` directly for those.
78
+ * - **Observable (see the guide's `## Observing` section).** The owned {@link emitter}
79
+ * ({@link WorkerEventMap}) re-exposes the underlying queue's job lifecycle (`enqueue` /
80
+ * `start` / `retry` / `success` / `failure` / `abort` / `drain`) as the worker's own events —
81
+ * bridged from the inner queue's emitter at construction — so a consumer observes the worker
82
+ * without reaching through to internals. The bridge re-emits directly on the worker's own
83
+ * emitter; the worker emitter isolates a listener throw and routes it to its `error` handler
84
+ * (the `error` option), so a buggy worker observer can never corrupt the inner queue or pool
85
+ * — the bridge listener never throws, so the inner queue's own emit stays balanced. The
86
+ * pool's create / acquire / release events stay the pool's internal concern (a Worker manages
87
+ * its own resources); observe a `Pool` directly for those.
84
88
  */
85
89
  declare class Worker_2<TInput, TResource, TResult> implements WorkerInterface<TInput, TResult> {
86
90
  #private;
@@ -103,49 +107,54 @@ declare class Worker_2<TInput, TResource, TResult> implements WorkerInterface<TI
103
107
  export { Worker_2 as Worker }
104
108
 
105
109
  /**
106
- * The push observation surface of a {@link WorkerInterface} (AGENTS §13) — the job
107
- * lifecycle a fire-and-forget observer subscribes to, surfacing the underlying queue's
108
- * moments so a Worker consumer never reaches through to the internal `Queue`.
110
+ * Represents the push observation surface of a {@link WorkerInterface} — the job
111
+ * lifecycle a fire-and-forget observer subscribes to.
109
112
  *
110
113
  * @typeParam TResult - The value a job resolves (the `success` payload), mirroring the
111
114
  * {@link WorkerInterface}'s own `TResult`.
112
115
  *
113
116
  * @remarks
114
117
  * A Worker is a `Queue`⨉`Pool` facade (both from their own `@orkestrel` packages); this
115
- * map RE-EXPOSES the queue lifecycle the worker surfaces (`enqueue` / `start` / `retry` /
116
- * `success` / `failure` / `abort` / `drain`) as the worker's OWN events — wired from the
117
- * underlying queue's emitter at construction, so a buggy observer is isolated exactly as
118
- * on the queue (a throw routes to the worker emitter's `error` handler, AGENTS §13). The
118
+ * map re-exposes the queue lifecycle the worker surfaces (`enqueue` / `start` / `retry` /
119
+ * `success` / `failure` / `abort` / `drain`) as the worker's own events — wired from the
120
+ * underlying queue's emitter at construction, so a consumer never reaches through to the
121
+ * internal `Queue` and a buggy observer is isolated exactly as on the queue (a throw
122
+ * routes to the worker emitter's `error` handler). The
119
123
  * pool's create / acquire / release events stay the pool's internal concern (a Worker
120
124
  * manages its own resources); a consumer who wants them observes a `Pool` directly.
121
- * Declared as a `type` alias (§4.5).
125
+ *
126
+ * Declared as a `type` alias (not `interface extends EventMap` — `EventMap` is a
127
+ * `type` kind): a type-literal satisfies the `EventMap` constraint
128
+ * (`Record<string, readonly unknown[]>`) structurally, whereas an interface lacks the
129
+ * required index signature.
122
130
  */
123
131
  declare type WorkerEventMap_2<TResult> = {
124
- /** A job was accepted — its id (delegated from the underlying queue's `enqueue`). */
132
+ /** Fires when a job is accepted — its id (delegated from the underlying queue's `enqueue`). */
125
133
  readonly enqueue: readonly [id: string];
126
- /** A job's attempt began running — its id. */
134
+ /** Fires when a job's attempt begins running — its id. */
127
135
  readonly start: readonly [id: string];
128
- /** A failed job attempt is being retried — its id + the next (1-based) attempt index. */
136
+ /** Fires when a failed job attempt is being retried — its id + the next (1-based) attempt index. */
129
137
  readonly retry: readonly [id: string, attempt: number];
130
- /** A job settled successfully — its id + the resolved result. */
138
+ /** Fires when a job settles successfully — its id + the resolved result. */
131
139
  readonly success: readonly [id: string, result: TResult];
132
- /** A job settled with a terminal failure — its id + the error. */
140
+ /** Fires when a job settles with a terminal failure — its id + the error. */
133
141
  readonly failure: readonly [id: string, error: unknown];
134
- /** The worker was aborted — the queue's coded abort error retaining the caller reason. */
142
+ /** Fires when the worker is aborted — the queue's coded abort error retaining the caller reason. */
135
143
  readonly abort: readonly [reason: unknown];
136
- /** The worker went idle — no pending jobs and none in flight. */
144
+ /** Fires when the worker goes idle — no pending jobs and none in flight. */
137
145
  readonly drain: readonly [];
138
146
  };
139
147
  export { WorkerEventMap_2 as WorkerEventMap }
140
148
 
141
149
  /** Runs one worker job with a leased pool resource. */
142
- export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput, resource: TResource, execution: QueueExecution) => Promise<TResult> | TResult;
150
+ export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput, resource: TResource, context: QueueContext) => Promise<TResult> | TResult;
143
151
 
144
152
  /**
145
- * A resource-backed job worker — a Queue whose handler runs against a pooled resource.
153
+ * Represents the job-worker contract a consumer holds — a `Queue` whose handler runs each
154
+ * job against a pooled resource.
146
155
  *
147
156
  * @remarks
148
- * Exposes a typed {@link emitter} (AGENTS §13) carrying the job lifecycle
157
+ * Exposes a typed {@link emitter} carrying the job lifecycle
149
158
  * ({@link WorkerEventMap}) — the underlying queue's moments re-exposed as the worker's own,
150
159
  * so a consumer never reaches through to internals. Emitting is observation-only: a buggy
151
160
  * observer is isolated exactly as on the queue (a throw routes to the emitter's `error`
@@ -157,25 +166,38 @@ export declare interface WorkerInterface<TInput, TResult> {
157
166
  readonly active: number;
158
167
  readonly paused: boolean;
159
168
  readonly stopped: boolean;
169
+ /**
170
+ * Submits one job in FIFO order; the handler runs against an acquired resource, released
171
+ * when the job settles.
172
+ *
173
+ * @param input - The work payload the handler receives
174
+ * @param options - Optional id, retry and timeout overrides, and an entry abort signal
175
+ * @returns The job's settle-once execution promise
176
+ */
160
177
  enqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult>;
161
- /** Re-enqueue outstanding entries loaded from the store; no-op without a store. */
178
+ /** Re-enqueues the store's outstanding entries through the underlying queue; no-op without a store. */
162
179
  restore(): Promise<void>;
180
+ /** Starts or restarts the underlying queue's worker loops. */
163
181
  start(): void;
164
- /** Stop the queue and await current-loop and durable cleanup quiescence. */
182
+ /** Stops the queue, rejects pending work, and awaits current-loop and durable cleanup quiescence. */
165
183
  stop(): Promise<void>;
184
+ /** Suspends dequeuing through the underlying queue, leaving in-flight jobs untouched. */
166
185
  pause(): void;
186
+ /** Continues a paused worker through the underlying queue. */
167
187
  resume(): void;
168
188
  /**
169
- * Cancel in-flight work, reject pending work, and await queue-owned cleanup.
189
+ * Cancels in-flight work, rejects pending work, and awaits queue-owned cleanup; an
190
+ * aborted attempt is never retried.
170
191
  *
171
192
  * @param reason - Optional cause retained by the queue's coded abort error
172
193
  * @returns The underlying queue's stable abort barrier
173
194
  */
174
195
  abort(reason?: unknown): Promise<void>;
175
- /** Drop pending work and await its durable cleanup. */
196
+ /** Drops pending jobs and awaits their durable cleanup, leaving in-flight jobs untouched. */
176
197
  clear(): Promise<void>;
177
198
  /**
178
- * Tear down the queue, then the pool, and finally the worker emitter.
199
+ * Tears down the queue, then the pool, and finally the worker emitter, behind one stable
200
+ * barrier.
179
201
  *
180
202
  * @returns One stable barrier shared by every call; it rejects with the original sole
181
203
  * cleanup failure or an ordered `AggregateError` when both queue and pool fail
@@ -184,32 +206,33 @@ export declare interface WorkerInterface<TInput, TResult> {
184
206
  }
185
207
 
186
208
  /**
187
- * Options for `createWorker`.
209
+ * Configures `createWorker`.
188
210
  *
189
211
  * @remarks
190
212
  * - `handler` — runs each job against an acquired pool resource; rejecting triggers a
191
213
  * retry while attempts remain (delegated to the underlying queue).
192
- * - `pool` — the {@link PoolOptions} for the resource the handler runs against; its
193
- * `max` defaults to `concurrency` so resources match the jobs in flight.
194
- * - `concurrency` — the maximum jobs in flight at once; defaults to `1` and must be a
195
- * positive safe integer, as validated by the underlying queue.
196
- * - `retries` — the default extra attempts per job on failure; defaults to `0`.
197
- * - `timeout` — the default per-attempt deadline in milliseconds; defaults to none.
214
+ * - `pool` — the {@link PoolOptions} for the resource the handler runs against, sized so
215
+ * resources match the jobs in flight. Default for its `max`: the `concurrency` value.
216
+ * - `concurrency` — the maximum jobs in flight at once; it must be a positive safe
217
+ * integer, as validated by the underlying queue. Default: 1.
218
+ * - `retries` — the default extra attempts per job on failure. Default: 0.
219
+ * - `timeout` — the default per-attempt deadline in milliseconds. Default: no per-attempt
220
+ * deadline.
198
221
  * - `store` — durable backing; outstanding entries survive a restart; call
199
222
  * `restore()` to re-run them.
200
- * - `on` — the reserved {@link EmitterHooks} key (§8): initial listeners for the worker's
223
+ * - `on` — the reserved {@link EmitterHooks} key: initial listeners for the worker's
201
224
  * {@link WorkerEventMap} (the job lifecycle it surfaces from its underlying queue), wired
202
225
  * at construction.
203
226
  */
204
227
  declare interface WorkerOptions_2<TInput, TResource, TResult> {
205
228
  readonly on?: EmitterHooks<WorkerEventMap_2<TResult>>;
206
- /** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
229
+ /** Holds the emitter's listener-error handler; a listener throw routes here, not to a domain event. */
207
230
  readonly error?: EmitterErrorHandler;
208
231
  readonly handler: WorkerHandler<TInput, TResource, TResult>;
209
232
  readonly pool: PoolOptions<TResource>;
210
233
  readonly concurrency?: number;
211
234
  readonly retries?: number;
212
- /** Integer milliseconds in `0..2_147_483_647`; `0` disables the per-attempt deadline. */
235
+ /** Holds integer milliseconds in `0..2_147_483_647`; `0` disables the per-attempt deadline. */
213
236
  readonly timeout?: number;
214
237
  readonly store?: QueueStoreInterface<TInput>;
215
238
  }