@orkestrel/worker 0.0.10 → 0.0.11
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 +6 -4
- package/dist/src/core/index.cjs +11 -11
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +36 -35
- package/dist/src/core/index.d.ts +36 -35
- package/dist/src/core/index.js +11 -11
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +238 -216
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +131 -81
- package/dist/src/server/index.d.ts +131 -81
- package/dist/src/server/index.js +237 -215
- package/dist/src/server/index.js.map +1 -1
- package/package.json +14 -15
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@ observable (a typed `emitter` re-exposes the underlying queue's job lifecycle
|
|
|
10
10
|
— `enqueue` / `start` / `retry` / `success` / `failure` / `abort` / `drain`).
|
|
11
11
|
For CPU-parallel work, the server surface's `createNodeWorker` specializes the
|
|
12
12
|
core `createWorker` over a pool of `node:worker_threads`, crossing the
|
|
13
|
-
structured-clone boundary with zero `as`
|
|
13
|
+
structured-clone boundary with zero `as` through `input` / `result` guards. Each
|
|
14
14
|
thread handler receives `{ id, signal }`: `id` is the Queue's stable idempotency
|
|
15
15
|
key across retries and crash restore, while `signal` is per attempt. The wire
|
|
16
16
|
protocol separately mints a fresh correlation id for each dispatch so a stale
|
|
17
17
|
reply cannot settle a later retry. The stable id identifies work, not a caller,
|
|
18
18
|
and is not authentication or authorization evidence; per-job consumer context
|
|
19
|
-
|
|
19
|
+
is explicit, structured-cloneable input rather than ambient thread state.
|
|
20
20
|
Part of the `@orkestrel` line.
|
|
21
21
|
|
|
22
22
|
## Install
|
|
@@ -27,6 +27,8 @@ npm install @orkestrel/worker
|
|
|
27
27
|
|
|
28
28
|
## Requirements
|
|
29
29
|
|
|
30
|
+
The package runs under these conditions:
|
|
31
|
+
|
|
30
32
|
- Node.js >= 22.12.0
|
|
31
33
|
- ESM and CommonJS builds ship for both the core and server entry points
|
|
32
34
|
|
|
@@ -68,11 +70,11 @@ await worker.destroy()
|
|
|
68
70
|
|
|
69
71
|
For the full surface — the `Worker` facade, `createNodeWorker` / `serveWorker`,
|
|
70
72
|
the durable `createJSONQueueStore`, the observable `emitter`, and usage
|
|
71
|
-
patterns — see [`guides/
|
|
73
|
+
patterns — see [`guides/worker.md`](guides/worker.md).
|
|
72
74
|
|
|
73
75
|
## Package
|
|
74
76
|
|
|
75
|
-
Published with
|
|
77
|
+
Published with the entry points the `exports` field in `package.json` names:
|
|
76
78
|
the environment-agnostic core (`.`) and the Node-only server surface
|
|
77
79
|
(`./server`).
|
|
78
80
|
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -4,8 +4,8 @@ let _orkestrel_pool = require("@orkestrel/pool");
|
|
|
4
4
|
let _orkestrel_queue = require("@orkestrel/queue");
|
|
5
5
|
//#region src/core/Worker.ts
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
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
|
|
@@ -20,7 +20,7 @@ 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
|
-
* `
|
|
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
26
|
* - **Lifecycle (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /
|
|
@@ -120,10 +120,10 @@ var Worker = class {
|
|
|
120
120
|
this.#teardown(ending);
|
|
121
121
|
return ending.promise;
|
|
122
122
|
}
|
|
123
|
-
async #handle(input,
|
|
124
|
-
const token = await this.#pool.acquire(
|
|
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,
|
|
126
|
+
return await this.#handler(input, token.value, context);
|
|
127
127
|
} finally {
|
|
128
128
|
token.release();
|
|
129
129
|
}
|
|
@@ -159,14 +159,14 @@ var Worker = class {
|
|
|
159
159
|
//#endregion
|
|
160
160
|
//#region src/core/factories.ts
|
|
161
161
|
/**
|
|
162
|
-
*
|
|
162
|
+
* Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) marrying a `Pool`
|
|
163
163
|
* (`@orkestrel/pool`). Each enqueued input runs through the handler against an
|
|
164
164
|
* automatically acquired pooled resource (released when the job settles), with the
|
|
165
165
|
* queue's bounded concurrency, retries, and per-attempt timeout / abort.
|
|
166
166
|
*
|
|
167
167
|
* @remarks
|
|
168
|
-
*
|
|
169
|
-
* are reused across jobs. A handler that throws still releases its resource (the
|
|
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
172
|
* delegates to the queue; `destroy` also tears the pool down. Observable (§13): a typed
|
|
@@ -175,8 +175,8 @@ var Worker = class {
|
|
|
175
175
|
* @typeParam TInput - The work input each job carries
|
|
176
176
|
* @typeParam TResource - The pooled resource each job runs against
|
|
177
177
|
* @typeParam TResult - The value the handler resolves for a job
|
|
178
|
-
* @param options - The `handler` and `pool` plus optional `concurrency
|
|
179
|
-
* `
|
|
178
|
+
* @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,
|
|
179
|
+
* `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})
|
|
180
180
|
* @returns A working {@link WorkerInterface}
|
|
181
181
|
*
|
|
182
182
|
* @example
|
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,KAAKG,WAAW;EAChB,KAAKD,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,KAAKF,SAAS,IAAI,iBAAA,MAAuB;GACxC,SAAS,KAAKI,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,KAAKH,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,KAAKI,QAAQ;CACd;CAEA,IAAI,UAAqD;EACxD,OAAO,KAAKH;CACb;CAEA,IAAI,QAAgB;EACnB,OAAO,KAAKF,OAAO;CACpB;CAEA,IAAI,SAAiB;EACpB,OAAO,KAAKA,OAAO;CACpB;CAEA,IAAI,SAAkB;EACrB,OAAO,KAAKA,OAAO;CACpB;CAEA,IAAI,UAAmB;EACtB,OAAO,KAAKA,OAAO;CACpB;CAEA,QAAQ,OAAe,SAA+C;EACrE,OAAO,KAAKA,OAAO,QAAQ,OAAO,OAAO;CAC1C;CAEA,UAAyB;EACxB,OAAO,KAAKA,OAAO,QAAQ;CAC5B;CAEA,QAAc;EACb,KAAKA,OAAO,MAAM;CACnB;CAEA,OAAsB;EACrB,OAAO,KAAKA,OAAO,KAAK;CACzB;CAEA,QAAc;EACb,KAAKA,OAAO,MAAM;CACnB;CAEA,SAAe;EACd,KAAKA,OAAO,OAAO;CACpB;CAEA,MAAM,QAAiC;EACtC,OAAO,KAAKA,OAAO,MAAM,MAAM;CAChC;CAEA,QAAuB;EACtB,OAAO,KAAKA,OAAO,MAAM;CAC1B;CAEA,UAAyB;EACxB,IAAI,KAAKM,YAAY,KAAA,GAAW,OAAO,KAAKA,QAAQ;EACpD,MAAM,SAAS,QAAQ,cAAoB;EAC3C,KAAKA,UAAU;EACf,KAAUC,UAAU,MAAM;EAC1B,OAAO,OAAO;CACf;CAEA,MAAMH,QAAQ,OAAe,WAA6C;EACzE,MAAM,QAAQ,MAAM,KAAKH,MAAM,QAAQ,UAAU,MAAM;EACvD,IAAI;GACH,OAAO,MAAM,KAAKE,SAAS,OAAO,MAAM,OAAO,SAAS;EACzD,UAAU;GACT,MAAM,QAAQ;EACf;CACD;CAEA,MAAMI,UAAU,QAAmD;EAClE,MAAM,WAAsB,CAAC;EAC7B,IAAI;GACH,MAAM,KAAKP,OAAO,QAAQ;EAC3B,SAAS,OAAO;GACf,SAAS,KAAK,KAAK;EACpB;EACA,IAAI;GACH,MAAM,KAAKC,MAAM,QAAQ;EAC1B,SAAS,OAAO;GACf,SAAS,KAAK,KAAK;EACpB;EACA,KAAKC,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,KAAKF,OAAO;EAC1B,MAAM,GAAG,YAAY,OAAO,KAAKE,SAAS,KAAK,WAAW,EAAE,CAAC;EAC7D,MAAM,GAAG,UAAU,OAAO,KAAKA,SAAS,KAAK,SAAS,EAAE,CAAC;EACzD,MAAM,GAAG,UAAU,IAAI,YAAY,KAAKA,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC;EAC3E,MAAM,GAAG,YAAY,IAAI,WAAW,KAAKA,SAAS,KAAK,WAAW,IAAI,MAAM,CAAC;EAC7E,MAAM,GAAG,YAAY,IAAI,UAAU,KAAKA,SAAS,KAAK,WAAW,IAAI,KAAK,CAAC;EAC3E,MAAM,GAAG,UAAU,WAAW,KAAKA,SAAS,KAAK,SAAS,MAAM,CAAC;EACjE,MAAM,GAAG,eAAe,KAAKA,SAAS,KAAK,OAAO,CAAC;CACpD;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9JA,SAAgB,aACf,SACmC;CACnC,OAAO,IAAI,OAAO,OAAO;AAC1B"}
|
|
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 { 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 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 * `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 (§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, 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`) 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 * 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. 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 the optional `concurrency`, `retries`,\n * `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})\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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"}
|
|
@@ -2,19 +2,19 @@ import { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
|
2
2
|
import { EmitterHooks } from '@orkestrel/emitter';
|
|
3
3
|
import { EmitterInterface } from '@orkestrel/emitter';
|
|
4
4
|
import { PoolOptions } from '@orkestrel/pool';
|
|
5
|
+
import { QueueContext } from '@orkestrel/queue';
|
|
5
6
|
import { QueueEntryOptions } from '@orkestrel/queue';
|
|
6
|
-
import { QueueExecution } from '@orkestrel/queue';
|
|
7
7
|
import { QueueStoreInterface } from '@orkestrel/queue';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) marrying a `Pool`
|
|
11
11
|
* (`@orkestrel/pool`). Each enqueued input runs through the handler against an
|
|
12
12
|
* automatically acquired pooled resource (released when the job settles), with the
|
|
13
13
|
* queue's bounded concurrency, retries, and per-attempt timeout / abort.
|
|
14
14
|
*
|
|
15
15
|
* @remarks
|
|
16
|
-
*
|
|
17
|
-
* are reused across jobs. A handler that throws still releases its resource (the
|
|
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
20
|
* delegates to the queue; `destroy` also tears the pool down. Observable (§13): a typed
|
|
@@ -23,8 +23,8 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
23
23
|
* @typeParam TInput - The work input each job carries
|
|
24
24
|
* @typeParam TResource - The pooled resource each job runs against
|
|
25
25
|
* @typeParam TResult - The value the handler resolves for a job
|
|
26
|
-
* @param options - The `handler` and `pool` plus optional `concurrency
|
|
27
|
-
* `
|
|
26
|
+
* @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,
|
|
27
|
+
* `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})
|
|
28
28
|
* @returns A working {@link WorkerInterface}
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
@@ -44,8 +44,8 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
44
44
|
export declare function createWorker<TInput, TResource, TResult>(options: WorkerOptions_2<TInput, TResource, TResult>): WorkerInterface<TInput, TResult>;
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
* with a `Pool` (`@orkestrel/pool`).
|
|
47
|
+
* Represents a resource-backed job worker — a thin facade composing a `Queue`
|
|
48
|
+
* (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).
|
|
49
49
|
*
|
|
50
50
|
* @remarks
|
|
51
51
|
* - **Composition, not reimplementation.** The Worker owns a `Pool` (built from
|
|
@@ -60,7 +60,7 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
60
60
|
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
61
61
|
* reused across jobs.
|
|
62
62
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
63
|
-
* `
|
|
63
|
+
* `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
64
64
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
65
65
|
* release (the resource was never leased).
|
|
66
66
|
* - **Lifecycle (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /
|
|
@@ -103,7 +103,7 @@ declare class Worker_2<TInput, TResource, TResult> implements WorkerInterface<TI
|
|
|
103
103
|
export { Worker_2 as Worker }
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
|
-
*
|
|
106
|
+
* Represents the push observation surface of a {@link WorkerInterface} — the job
|
|
107
107
|
* lifecycle a fire-and-forget observer subscribes to, surfacing the underlying queue's
|
|
108
108
|
* moments so a Worker consumer never reaches through to the internal `Queue`.
|
|
109
109
|
*
|
|
@@ -115,37 +115,37 @@ export { Worker_2 as Worker }
|
|
|
115
115
|
* map RE-EXPOSES the queue lifecycle the worker surfaces (`enqueue` / `start` / `retry` /
|
|
116
116
|
* `success` / `failure` / `abort` / `drain`) as the worker's OWN events — wired from the
|
|
117
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
|
|
118
|
+
* on the queue (a throw routes to the worker emitter's `error` handler). The
|
|
119
119
|
* pool's create / acquire / release events stay the pool's internal concern (a Worker
|
|
120
120
|
* manages its own resources); a consumer who wants them observes a `Pool` directly.
|
|
121
121
|
* Declared as a `type` alias (§4.5).
|
|
122
122
|
*/
|
|
123
123
|
declare type WorkerEventMap_2<TResult> = {
|
|
124
|
-
/**
|
|
124
|
+
/** Fires when a job is accepted — its id (delegated from the underlying queue's `enqueue`). */
|
|
125
125
|
readonly enqueue: readonly [id: string];
|
|
126
|
-
/**
|
|
126
|
+
/** Fires when a job's attempt begins running — its id. */
|
|
127
127
|
readonly start: readonly [id: string];
|
|
128
|
-
/**
|
|
128
|
+
/** Fires when a failed job attempt is being retried — its id + the next (1-based) attempt index. */
|
|
129
129
|
readonly retry: readonly [id: string, attempt: number];
|
|
130
|
-
/**
|
|
130
|
+
/** Fires when a job settles successfully — its id + the resolved result. */
|
|
131
131
|
readonly success: readonly [id: string, result: TResult];
|
|
132
|
-
/**
|
|
132
|
+
/** Fires when a job settles with a terminal failure — its id + the error. */
|
|
133
133
|
readonly failure: readonly [id: string, error: unknown];
|
|
134
|
-
/**
|
|
134
|
+
/** Fires when the worker is aborted — the queue's coded abort error retaining the caller reason. */
|
|
135
135
|
readonly abort: readonly [reason: unknown];
|
|
136
|
-
/**
|
|
136
|
+
/** Fires when the worker goes idle — no pending jobs and none in flight. */
|
|
137
137
|
readonly drain: readonly [];
|
|
138
138
|
};
|
|
139
139
|
export { WorkerEventMap_2 as WorkerEventMap }
|
|
140
140
|
|
|
141
141
|
/** Runs one worker job with a leased pool resource. */
|
|
142
|
-
export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput, resource: TResource,
|
|
142
|
+
export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput, resource: TResource, context: QueueContext) => Promise<TResult> | TResult;
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
*
|
|
145
|
+
* Represents a resource-backed job worker — a Queue whose handler runs against a pooled resource.
|
|
146
146
|
*
|
|
147
147
|
* @remarks
|
|
148
|
-
* Exposes a typed {@link emitter}
|
|
148
|
+
* Exposes a typed {@link emitter} carrying the job lifecycle
|
|
149
149
|
* ({@link WorkerEventMap}) — the underlying queue's moments re-exposed as the worker's own,
|
|
150
150
|
* so a consumer never reaches through to internals. Emitting is observation-only: a buggy
|
|
151
151
|
* observer is isolated exactly as on the queue (a throw routes to the emitter's `error`
|
|
@@ -158,24 +158,24 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
158
158
|
readonly paused: boolean;
|
|
159
159
|
readonly stopped: boolean;
|
|
160
160
|
enqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult>;
|
|
161
|
-
/** Re-
|
|
161
|
+
/** Re-enqueues outstanding entries loaded from the store; no-op without a store. */
|
|
162
162
|
restore(): Promise<void>;
|
|
163
163
|
start(): void;
|
|
164
|
-
/**
|
|
164
|
+
/** Stops the queue and awaits current-loop and durable cleanup quiescence. */
|
|
165
165
|
stop(): Promise<void>;
|
|
166
166
|
pause(): void;
|
|
167
167
|
resume(): void;
|
|
168
168
|
/**
|
|
169
|
-
*
|
|
169
|
+
* Cancels in-flight work, rejects pending work, and awaits queue-owned cleanup.
|
|
170
170
|
*
|
|
171
171
|
* @param reason - Optional cause retained by the queue's coded abort error
|
|
172
172
|
* @returns The underlying queue's stable abort barrier
|
|
173
173
|
*/
|
|
174
174
|
abort(reason?: unknown): Promise<void>;
|
|
175
|
-
/**
|
|
175
|
+
/** Drops pending work and awaits its durable cleanup. */
|
|
176
176
|
clear(): Promise<void>;
|
|
177
177
|
/**
|
|
178
|
-
*
|
|
178
|
+
* Tears down the queue, then the pool, and finally the worker emitter.
|
|
179
179
|
*
|
|
180
180
|
* @returns One stable barrier shared by every call; it rejects with the original sole
|
|
181
181
|
* cleanup failure or an ordered `AggregateError` when both queue and pool fail
|
|
@@ -184,17 +184,18 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
|
-
*
|
|
187
|
+
* Configures `createWorker`.
|
|
188
188
|
*
|
|
189
189
|
* @remarks
|
|
190
190
|
* - `handler` — runs each job against an acquired pool resource; rejecting triggers a
|
|
191
191
|
* retry while attempts remain (delegated to the underlying queue).
|
|
192
|
-
* - `pool` — the {@link PoolOptions} for the resource the handler runs against
|
|
193
|
-
*
|
|
194
|
-
* - `concurrency` — the maximum jobs in flight at once;
|
|
195
|
-
*
|
|
196
|
-
* - `retries` — the default extra attempts per job on failure
|
|
197
|
-
* - `timeout` — the default per-attempt deadline in milliseconds
|
|
192
|
+
* - `pool` — the {@link PoolOptions} for the resource the handler runs against, sized so
|
|
193
|
+
* resources match the jobs in flight. Default for its `max`: the `concurrency` value.
|
|
194
|
+
* - `concurrency` — the maximum jobs in flight at once; it must be a positive safe
|
|
195
|
+
* integer, as validated by the underlying queue. Default: 1.
|
|
196
|
+
* - `retries` — the default extra attempts per job on failure. Default: 0.
|
|
197
|
+
* - `timeout` — the default per-attempt deadline in milliseconds. Default: no per-attempt
|
|
198
|
+
* deadline.
|
|
198
199
|
* - `store` — durable backing; outstanding entries survive a restart; call
|
|
199
200
|
* `restore()` to re-run them.
|
|
200
201
|
* - `on` — the reserved {@link EmitterHooks} key (§8): initial listeners for the worker's
|
|
@@ -203,13 +204,13 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
203
204
|
*/
|
|
204
205
|
declare interface WorkerOptions_2<TInput, TResource, TResult> {
|
|
205
206
|
readonly on?: EmitterHooks<WorkerEventMap_2<TResult>>;
|
|
206
|
-
/**
|
|
207
|
+
/** Holds the emitter's listener-error handler; a listener throw routes here, not to a domain event. */
|
|
207
208
|
readonly error?: EmitterErrorHandler;
|
|
208
209
|
readonly handler: WorkerHandler<TInput, TResource, TResult>;
|
|
209
210
|
readonly pool: PoolOptions<TResource>;
|
|
210
211
|
readonly concurrency?: number;
|
|
211
212
|
readonly retries?: number;
|
|
212
|
-
/**
|
|
213
|
+
/** Holds integer milliseconds in `0..2_147_483_647`; `0` disables the per-attempt deadline. */
|
|
213
214
|
readonly timeout?: number;
|
|
214
215
|
readonly store?: QueueStoreInterface<TInput>;
|
|
215
216
|
}
|
package/dist/src/core/index.d.ts
CHANGED
|
@@ -2,19 +2,19 @@ import { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
|
2
2
|
import { EmitterHooks } from '@orkestrel/emitter';
|
|
3
3
|
import { EmitterInterface } from '@orkestrel/emitter';
|
|
4
4
|
import { PoolOptions } from '@orkestrel/pool';
|
|
5
|
+
import { QueueContext } from '@orkestrel/queue';
|
|
5
6
|
import { QueueEntryOptions } from '@orkestrel/queue';
|
|
6
|
-
import { QueueExecution } from '@orkestrel/queue';
|
|
7
7
|
import { QueueStoreInterface } from '@orkestrel/queue';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) marrying a `Pool`
|
|
11
11
|
* (`@orkestrel/pool`). Each enqueued input runs through the handler against an
|
|
12
12
|
* automatically acquired pooled resource (released when the job settles), with the
|
|
13
13
|
* queue's bounded concurrency, retries, and per-attempt timeout / abort.
|
|
14
14
|
*
|
|
15
15
|
* @remarks
|
|
16
|
-
*
|
|
17
|
-
* are reused across jobs. A handler that throws still releases its resource (the
|
|
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
20
|
* delegates to the queue; `destroy` also tears the pool down. Observable (§13): a typed
|
|
@@ -23,8 +23,8 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
23
23
|
* @typeParam TInput - The work input each job carries
|
|
24
24
|
* @typeParam TResource - The pooled resource each job runs against
|
|
25
25
|
* @typeParam TResult - The value the handler resolves for a job
|
|
26
|
-
* @param options - The `handler` and `pool` plus optional `concurrency
|
|
27
|
-
* `
|
|
26
|
+
* @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,
|
|
27
|
+
* `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})
|
|
28
28
|
* @returns A working {@link WorkerInterface}
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
@@ -44,8 +44,8 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
44
44
|
export declare function createWorker<TInput, TResource, TResult>(options: WorkerOptions_2<TInput, TResource, TResult>): WorkerInterface<TInput, TResult>;
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
* with a `Pool` (`@orkestrel/pool`).
|
|
47
|
+
* Represents a resource-backed job worker — a thin facade composing a `Queue`
|
|
48
|
+
* (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).
|
|
49
49
|
*
|
|
50
50
|
* @remarks
|
|
51
51
|
* - **Composition, not reimplementation.** The Worker owns a `Pool` (built from
|
|
@@ -60,7 +60,7 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
60
60
|
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
61
61
|
* reused across jobs.
|
|
62
62
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
63
|
-
* `
|
|
63
|
+
* `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
64
64
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
65
65
|
* release (the resource was never leased).
|
|
66
66
|
* - **Lifecycle (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /
|
|
@@ -103,7 +103,7 @@ declare class Worker_2<TInput, TResource, TResult> implements WorkerInterface<TI
|
|
|
103
103
|
export { Worker_2 as Worker }
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
|
-
*
|
|
106
|
+
* Represents the push observation surface of a {@link WorkerInterface} — the job
|
|
107
107
|
* lifecycle a fire-and-forget observer subscribes to, surfacing the underlying queue's
|
|
108
108
|
* moments so a Worker consumer never reaches through to the internal `Queue`.
|
|
109
109
|
*
|
|
@@ -115,37 +115,37 @@ export { Worker_2 as Worker }
|
|
|
115
115
|
* map RE-EXPOSES the queue lifecycle the worker surfaces (`enqueue` / `start` / `retry` /
|
|
116
116
|
* `success` / `failure` / `abort` / `drain`) as the worker's OWN events — wired from the
|
|
117
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
|
|
118
|
+
* on the queue (a throw routes to the worker emitter's `error` handler). The
|
|
119
119
|
* pool's create / acquire / release events stay the pool's internal concern (a Worker
|
|
120
120
|
* manages its own resources); a consumer who wants them observes a `Pool` directly.
|
|
121
121
|
* Declared as a `type` alias (§4.5).
|
|
122
122
|
*/
|
|
123
123
|
declare type WorkerEventMap_2<TResult> = {
|
|
124
|
-
/**
|
|
124
|
+
/** Fires when a job is accepted — its id (delegated from the underlying queue's `enqueue`). */
|
|
125
125
|
readonly enqueue: readonly [id: string];
|
|
126
|
-
/**
|
|
126
|
+
/** Fires when a job's attempt begins running — its id. */
|
|
127
127
|
readonly start: readonly [id: string];
|
|
128
|
-
/**
|
|
128
|
+
/** Fires when a failed job attempt is being retried — its id + the next (1-based) attempt index. */
|
|
129
129
|
readonly retry: readonly [id: string, attempt: number];
|
|
130
|
-
/**
|
|
130
|
+
/** Fires when a job settles successfully — its id + the resolved result. */
|
|
131
131
|
readonly success: readonly [id: string, result: TResult];
|
|
132
|
-
/**
|
|
132
|
+
/** Fires when a job settles with a terminal failure — its id + the error. */
|
|
133
133
|
readonly failure: readonly [id: string, error: unknown];
|
|
134
|
-
/**
|
|
134
|
+
/** Fires when the worker is aborted — the queue's coded abort error retaining the caller reason. */
|
|
135
135
|
readonly abort: readonly [reason: unknown];
|
|
136
|
-
/**
|
|
136
|
+
/** Fires when the worker goes idle — no pending jobs and none in flight. */
|
|
137
137
|
readonly drain: readonly [];
|
|
138
138
|
};
|
|
139
139
|
export { WorkerEventMap_2 as WorkerEventMap }
|
|
140
140
|
|
|
141
141
|
/** Runs one worker job with a leased pool resource. */
|
|
142
|
-
export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput, resource: TResource,
|
|
142
|
+
export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput, resource: TResource, context: QueueContext) => Promise<TResult> | TResult;
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
*
|
|
145
|
+
* Represents a resource-backed job worker — a Queue whose handler runs against a pooled resource.
|
|
146
146
|
*
|
|
147
147
|
* @remarks
|
|
148
|
-
* Exposes a typed {@link emitter}
|
|
148
|
+
* Exposes a typed {@link emitter} carrying the job lifecycle
|
|
149
149
|
* ({@link WorkerEventMap}) — the underlying queue's moments re-exposed as the worker's own,
|
|
150
150
|
* so a consumer never reaches through to internals. Emitting is observation-only: a buggy
|
|
151
151
|
* observer is isolated exactly as on the queue (a throw routes to the emitter's `error`
|
|
@@ -158,24 +158,24 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
158
158
|
readonly paused: boolean;
|
|
159
159
|
readonly stopped: boolean;
|
|
160
160
|
enqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult>;
|
|
161
|
-
/** Re-
|
|
161
|
+
/** Re-enqueues outstanding entries loaded from the store; no-op without a store. */
|
|
162
162
|
restore(): Promise<void>;
|
|
163
163
|
start(): void;
|
|
164
|
-
/**
|
|
164
|
+
/** Stops the queue and awaits current-loop and durable cleanup quiescence. */
|
|
165
165
|
stop(): Promise<void>;
|
|
166
166
|
pause(): void;
|
|
167
167
|
resume(): void;
|
|
168
168
|
/**
|
|
169
|
-
*
|
|
169
|
+
* Cancels in-flight work, rejects pending work, and awaits queue-owned cleanup.
|
|
170
170
|
*
|
|
171
171
|
* @param reason - Optional cause retained by the queue's coded abort error
|
|
172
172
|
* @returns The underlying queue's stable abort barrier
|
|
173
173
|
*/
|
|
174
174
|
abort(reason?: unknown): Promise<void>;
|
|
175
|
-
/**
|
|
175
|
+
/** Drops pending work and awaits its durable cleanup. */
|
|
176
176
|
clear(): Promise<void>;
|
|
177
177
|
/**
|
|
178
|
-
*
|
|
178
|
+
* Tears down the queue, then the pool, and finally the worker emitter.
|
|
179
179
|
*
|
|
180
180
|
* @returns One stable barrier shared by every call; it rejects with the original sole
|
|
181
181
|
* cleanup failure or an ordered `AggregateError` when both queue and pool fail
|
|
@@ -184,17 +184,18 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
|
-
*
|
|
187
|
+
* Configures `createWorker`.
|
|
188
188
|
*
|
|
189
189
|
* @remarks
|
|
190
190
|
* - `handler` — runs each job against an acquired pool resource; rejecting triggers a
|
|
191
191
|
* retry while attempts remain (delegated to the underlying queue).
|
|
192
|
-
* - `pool` — the {@link PoolOptions} for the resource the handler runs against
|
|
193
|
-
*
|
|
194
|
-
* - `concurrency` — the maximum jobs in flight at once;
|
|
195
|
-
*
|
|
196
|
-
* - `retries` — the default extra attempts per job on failure
|
|
197
|
-
* - `timeout` — the default per-attempt deadline in milliseconds
|
|
192
|
+
* - `pool` — the {@link PoolOptions} for the resource the handler runs against, sized so
|
|
193
|
+
* resources match the jobs in flight. Default for its `max`: the `concurrency` value.
|
|
194
|
+
* - `concurrency` — the maximum jobs in flight at once; it must be a positive safe
|
|
195
|
+
* integer, as validated by the underlying queue. Default: 1.
|
|
196
|
+
* - `retries` — the default extra attempts per job on failure. Default: 0.
|
|
197
|
+
* - `timeout` — the default per-attempt deadline in milliseconds. Default: no per-attempt
|
|
198
|
+
* deadline.
|
|
198
199
|
* - `store` — durable backing; outstanding entries survive a restart; call
|
|
199
200
|
* `restore()` to re-run them.
|
|
200
201
|
* - `on` — the reserved {@link EmitterHooks} key (§8): initial listeners for the worker's
|
|
@@ -203,13 +204,13 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
203
204
|
*/
|
|
204
205
|
declare interface WorkerOptions_2<TInput, TResource, TResult> {
|
|
205
206
|
readonly on?: EmitterHooks<WorkerEventMap_2<TResult>>;
|
|
206
|
-
/**
|
|
207
|
+
/** Holds the emitter's listener-error handler; a listener throw routes here, not to a domain event. */
|
|
207
208
|
readonly error?: EmitterErrorHandler;
|
|
208
209
|
readonly handler: WorkerHandler<TInput, TResource, TResult>;
|
|
209
210
|
readonly pool: PoolOptions<TResource>;
|
|
210
211
|
readonly concurrency?: number;
|
|
211
212
|
readonly retries?: number;
|
|
212
|
-
/**
|
|
213
|
+
/** Holds integer milliseconds in `0..2_147_483_647`; `0` disables the per-attempt deadline. */
|
|
213
214
|
readonly timeout?: number;
|
|
214
215
|
readonly store?: QueueStoreInterface<TInput>;
|
|
215
216
|
}
|
package/dist/src/core/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { Pool } from "@orkestrel/pool";
|
|
|
3
3
|
import { Queue } from "@orkestrel/queue";
|
|
4
4
|
//#region src/core/Worker.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* with a `Pool` (`@orkestrel/pool`).
|
|
6
|
+
* Represents a resource-backed job worker — a thin facade composing a `Queue`
|
|
7
|
+
* (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).
|
|
8
8
|
*
|
|
9
9
|
* @remarks
|
|
10
10
|
* - **Composition, not reimplementation.** The Worker owns a `Pool` (built from
|
|
@@ -19,7 +19,7 @@ import { Queue } from "@orkestrel/queue";
|
|
|
19
19
|
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
20
20
|
* reused across jobs.
|
|
21
21
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
22
|
-
* `
|
|
22
|
+
* `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
23
23
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
24
24
|
* release (the resource was never leased).
|
|
25
25
|
* - **Lifecycle (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /
|
|
@@ -119,10 +119,10 @@ var Worker = class {
|
|
|
119
119
|
this.#teardown(ending);
|
|
120
120
|
return ending.promise;
|
|
121
121
|
}
|
|
122
|
-
async #handle(input,
|
|
123
|
-
const token = await this.#pool.acquire(
|
|
122
|
+
async #handle(input, context) {
|
|
123
|
+
const token = await this.#pool.acquire(context.signal);
|
|
124
124
|
try {
|
|
125
|
-
return await this.#handler(input, token.value,
|
|
125
|
+
return await this.#handler(input, token.value, context);
|
|
126
126
|
} finally {
|
|
127
127
|
token.release();
|
|
128
128
|
}
|
|
@@ -158,14 +158,14 @@ var Worker = class {
|
|
|
158
158
|
//#endregion
|
|
159
159
|
//#region src/core/factories.ts
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
161
|
+
* Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) marrying a `Pool`
|
|
162
162
|
* (`@orkestrel/pool`). Each enqueued input runs through the handler against an
|
|
163
163
|
* automatically acquired pooled resource (released when the job settles), with the
|
|
164
164
|
* queue's bounded concurrency, retries, and per-attempt timeout / abort.
|
|
165
165
|
*
|
|
166
166
|
* @remarks
|
|
167
|
-
*
|
|
168
|
-
* are reused across jobs. A handler that throws still releases its resource (the
|
|
167
|
+
* Default for the pool's `max`: the `concurrency` value, so resources match the jobs in flight.
|
|
168
|
+
* Resources are reused across jobs. A handler that throws still releases its resource (the
|
|
169
169
|
* acquire/release pair brackets the call in a `finally`), so a later job reuses it. The
|
|
170
170
|
* lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)
|
|
171
171
|
* delegates to the queue; `destroy` also tears the pool down. Observable (§13): a typed
|
|
@@ -174,8 +174,8 @@ var Worker = class {
|
|
|
174
174
|
* @typeParam TInput - The work input each job carries
|
|
175
175
|
* @typeParam TResource - The pooled resource each job runs against
|
|
176
176
|
* @typeParam TResult - The value the handler resolves for a job
|
|
177
|
-
* @param options - The `handler` and `pool` plus optional `concurrency
|
|
178
|
-
* `
|
|
177
|
+
* @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,
|
|
178
|
+
* `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})
|
|
179
179
|
* @returns A working {@link WorkerInterface}
|
|
180
180
|
*
|
|
181
181
|
* @example
|