@orkestrel/worker 0.0.3 → 0.0.5
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 +3 -2
- package/dist/src/core/index.cjs +65 -31
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +47 -22
- package/dist/src/core/index.d.ts +47 -22
- package/dist/src/core/index.js +65 -31
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +368 -175
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +44 -58
- package/dist/src/server/index.d.ts +44 -58
- package/dist/src/server/index.js +368 -175
- package/dist/src/server/index.js.map +1 -1
- package/package.json +22 -19
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npm install @orkestrel/worker
|
|
|
21
21
|
|
|
22
22
|
## Requirements
|
|
23
23
|
|
|
24
|
-
- Node.js >=
|
|
24
|
+
- Node.js >= 22.12.0
|
|
25
25
|
- ESM and CommonJS builds ship for both the core and server entry points
|
|
26
26
|
|
|
27
27
|
## Usage
|
|
@@ -37,7 +37,7 @@ const worker = createWorker<Query, Connection, Rows>({
|
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
const rows = await worker.enqueue(query)
|
|
40
|
-
worker.destroy() //
|
|
40
|
+
await worker.destroy() // awaits queue cleanup, then pool cleanup, then emitter teardown
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
CPU-parallel jobs over `node:worker_threads`:
|
|
@@ -55,6 +55,7 @@ const worker = createNodeWorker({
|
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
const doubled = await worker.enqueue(21) // 42, computed on a worker thread
|
|
58
|
+
await worker.destroy()
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
## Guide
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -12,16 +12,23 @@ let _orkestrel_queue = require("@orkestrel/queue");
|
|
|
12
12
|
* `options.pool`) and a `Queue` whose handler ACQUIRES a pooled resource, runs the
|
|
13
13
|
* user handler against it, and RELEASES it in a `finally`. All concurrency, retries,
|
|
14
14
|
* timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
|
|
15
|
-
* - **Resource ↔ concurrency.** The
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
|
|
16
|
+
* safe integer after caller options are captured once. Only `undefined` defaults
|
|
17
|
+
* `concurrency` to `1` or pool `max` to that value; runtime `null` reaches the owning
|
|
18
|
+
* validator. The queue validates before the pool option is read; every declared pool member
|
|
19
|
+
* is then captured once by direct access, preserving inherited and non-enumerable structural
|
|
20
|
+
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
21
|
+
* reused across jobs.
|
|
18
22
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
19
23
|
* `execution.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
20
24
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
21
25
|
* release (the resource was never leased).
|
|
22
26
|
* - **Lifecycle (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /
|
|
23
27
|
* `abort` / `clear` delegate to the queue; `count` / `active` / `paused` / `stopped`
|
|
24
|
-
* read it. `
|
|
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
|
|
31
|
+
* identity; failures from both layers become an ordered `AggregateError`.
|
|
25
32
|
* - **Durability.** An optional `store` is passed straight through to the queue, so the
|
|
26
33
|
* worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).
|
|
27
34
|
* - **Observable (§13).** The owned {@link emitter} ({@link WorkerEventMap}) RE-EXPOSES the
|
|
@@ -39,30 +46,31 @@ var Worker = class {
|
|
|
39
46
|
#queue;
|
|
40
47
|
#pool;
|
|
41
48
|
#emitter;
|
|
42
|
-
#
|
|
49
|
+
#handler;
|
|
50
|
+
#ending;
|
|
43
51
|
constructor(options) {
|
|
44
|
-
const concurrency
|
|
52
|
+
const { concurrency: capturedConcurrency, handler, on, error, retries, timeout, store } = options;
|
|
53
|
+
const concurrency = capturedConcurrency === void 0 ? 1 : capturedConcurrency;
|
|
54
|
+
this.#handler = handler;
|
|
45
55
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
46
|
-
on:
|
|
47
|
-
error:
|
|
48
|
-
});
|
|
49
|
-
this.#pool = new _orkestrel_pool.Pool({
|
|
50
|
-
...options.pool,
|
|
51
|
-
max: options.pool.max ?? concurrency
|
|
56
|
+
...on !== void 0 ? { on } : {},
|
|
57
|
+
...error !== void 0 ? { error } : {}
|
|
52
58
|
});
|
|
53
59
|
this.#queue = new _orkestrel_queue.Queue({
|
|
54
|
-
handler:
|
|
55
|
-
const token = await this.#pool.acquire(execution.signal);
|
|
56
|
-
try {
|
|
57
|
-
return await options.handler(input, token.value, execution);
|
|
58
|
-
} finally {
|
|
59
|
-
token.release();
|
|
60
|
-
}
|
|
61
|
-
},
|
|
60
|
+
handler: this.#handle.bind(this),
|
|
62
61
|
concurrency,
|
|
63
|
-
retries:
|
|
64
|
-
timeout:
|
|
65
|
-
store:
|
|
62
|
+
...retries !== void 0 ? { retries } : {},
|
|
63
|
+
...timeout !== void 0 ? { timeout } : {},
|
|
64
|
+
...store !== void 0 ? { store } : {}
|
|
65
|
+
});
|
|
66
|
+
const { max, on: poolOn, error: poolError, create, destroy, validate } = options.pool;
|
|
67
|
+
this.#pool = new _orkestrel_pool.Pool({
|
|
68
|
+
create,
|
|
69
|
+
max: max === void 0 ? concurrency : max,
|
|
70
|
+
...poolOn !== void 0 ? { on: poolOn } : {},
|
|
71
|
+
...poolError !== void 0 ? { error: poolError } : {},
|
|
72
|
+
...destroy !== void 0 ? { destroy } : {},
|
|
73
|
+
...validate !== void 0 ? { validate } : {}
|
|
66
74
|
});
|
|
67
75
|
this.#bridge();
|
|
68
76
|
}
|
|
@@ -91,7 +99,7 @@ var Worker = class {
|
|
|
91
99
|
this.#queue.start();
|
|
92
100
|
}
|
|
93
101
|
stop() {
|
|
94
|
-
this.#queue.stop();
|
|
102
|
+
return this.#queue.stop();
|
|
95
103
|
}
|
|
96
104
|
pause() {
|
|
97
105
|
this.#queue.pause();
|
|
@@ -100,16 +108,42 @@ var Worker = class {
|
|
|
100
108
|
this.#queue.resume();
|
|
101
109
|
}
|
|
102
110
|
abort(reason) {
|
|
103
|
-
this.#queue.abort(reason);
|
|
111
|
+
return this.#queue.abort(reason);
|
|
104
112
|
}
|
|
105
113
|
clear() {
|
|
106
|
-
this.#queue.clear();
|
|
114
|
+
return this.#queue.clear();
|
|
107
115
|
}
|
|
108
116
|
destroy() {
|
|
109
|
-
if (this.#
|
|
110
|
-
|
|
111
|
-
this.#
|
|
112
|
-
this.#
|
|
117
|
+
if (this.#ending !== void 0) return this.#ending.promise;
|
|
118
|
+
const ending = Promise.withResolvers();
|
|
119
|
+
this.#ending = ending;
|
|
120
|
+
this.#teardown(ending);
|
|
121
|
+
return ending.promise;
|
|
122
|
+
}
|
|
123
|
+
async #handle(input, execution) {
|
|
124
|
+
const token = await this.#pool.acquire(execution.signal);
|
|
125
|
+
try {
|
|
126
|
+
return await this.#handler(input, token.value, execution);
|
|
127
|
+
} finally {
|
|
128
|
+
token.release();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async #teardown(ending) {
|
|
132
|
+
const failures = [];
|
|
133
|
+
try {
|
|
134
|
+
await this.#queue.destroy();
|
|
135
|
+
} catch (error) {
|
|
136
|
+
failures.push(error);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
await this.#pool.destroy();
|
|
140
|
+
} catch (error) {
|
|
141
|
+
failures.push(error);
|
|
142
|
+
}
|
|
143
|
+
this.#emitter.destroy();
|
|
144
|
+
if (failures.length === 0) ending.resolve();
|
|
145
|
+
else if (failures.length === 1) ending.reject(failures[0]);
|
|
146
|
+
else ending.reject(new AggregateError(failures, "worker destroy cleanup failed"));
|
|
113
147
|
}
|
|
114
148
|
#bridge() {
|
|
115
149
|
const queue = this.#queue.emitter;
|
|
@@ -147,7 +181,7 @@ var Worker = class {
|
|
|
147
181
|
*
|
|
148
182
|
* @example
|
|
149
183
|
* ```ts
|
|
150
|
-
* import { createWorker } from '@
|
|
184
|
+
* import { createWorker } from '@orkestrel/worker'
|
|
151
185
|
*
|
|
152
186
|
* const worker = createWorker<Query, Connection, Rows>({
|
|
153
187
|
* pool: { create: () => connect(), destroy: (connection) => connection.close() },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["#queue","#pool","#emitter","#bridge","#destroyed"],"sources":["../../../src/core/Worker.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { QueueEntryOptions } from '@orkestrel/queue'\nimport type { WorkerEventMap, 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 pool's `max` defaults to the worker's\n * `concurrency` (default `1`), so at most one resource exists per in-flight job and\n * idle resources are 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. `destroy` destroys the queue then tears the pool down, idempotently.\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\t#destroyed = false\n\n\tconstructor(options: WorkerOptions<TInput, TResource, TResult>) {\n\t\tconst concurrency = Math.max(1, options.concurrency ?? 1)\n\t\tthis.#emitter = new Emitter<WorkerEventMap<TResult>>({ on: options?.on, error: options?.error })\n\t\tthis.#pool = new Pool<TResource>({\n\t\t\t...options.pool,\n\t\t\tmax: options.pool.max ?? concurrency,\n\t\t})\n\t\tthis.#queue = new Queue<TInput, TResult>({\n\t\t\thandler: async (input, execution) => {\n\t\t\t\tconst token = await this.#pool.acquire(execution.signal)\n\t\t\t\ttry {\n\t\t\t\t\treturn await options.handler(input, token.value, execution)\n\t\t\t\t} finally {\n\t\t\t\t\ttoken.release()\n\t\t\t\t}\n\t\t\t},\n\t\t\tconcurrency,\n\t\t\tretries: options.retries,\n\t\t\ttimeout: options.timeout,\n\t\t\tstore: options.store,\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(): void {\n\t\tthis.#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): void {\n\t\tthis.#queue.abort(reason)\n\t}\n\n\tclear(): void {\n\t\tthis.#queue.clear()\n\t}\n\n\tdestroy(): void {\n\t\tif (this.#destroyed) return\n\t\tthis.#destroyed = true\n\t\tthis.#queue.destroy()\n\t\tvoid this.#pool.destroy()\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 '@src/core'\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAa,SAAb,MAA4F;CAC3F;CACA;CAIA;CACA,aAAa;CAEb,YAAY,SAAoD;EAC/D,MAAM,cAAc,KAAK,IAAI,GAAG,QAAQ,eAAe,CAAC;EACxD,KAAKE,WAAW,IAAI,mBAAA,QAAiC;GAAE,IAAI,SAAS;GAAI,OAAO,SAAS;EAAM,CAAC;EAC/F,KAAKD,QAAQ,IAAI,gBAAA,KAAgB;GAChC,GAAG,QAAQ;GACX,KAAK,QAAQ,KAAK,OAAO;EAC1B,CAAC;EACD,KAAKD,SAAS,IAAI,iBAAA,MAAuB;GACxC,SAAS,OAAO,OAAO,cAAc;IACpC,MAAM,QAAQ,MAAM,KAAKC,MAAM,QAAQ,UAAU,MAAM;IACvD,IAAI;KACH,OAAO,MAAM,QAAQ,QAAQ,OAAO,MAAM,OAAO,SAAS;IAC3D,UAAU;KACT,MAAM,QAAQ;IACf;GACD;GACA;GACA,SAAS,QAAQ;GACjB,SAAS,QAAQ;GACjB,OAAO,QAAQ;EAChB,CAAC;EACD,KAAKE,QAAQ;CACd;CAEA,IAAI,UAAqD;EACxD,OAAO,KAAKD;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,OAAa;EACZ,KAAKA,OAAO,KAAK;CAClB;CAEA,QAAc;EACb,KAAKA,OAAO,MAAM;CACnB;CAEA,SAAe;EACd,KAAKA,OAAO,OAAO;CACpB;CAEA,MAAM,QAAwB;EAC7B,KAAKA,OAAO,MAAM,MAAM;CACzB;CAEA,QAAc;EACb,KAAKA,OAAO,MAAM;CACnB;CAEA,UAAgB;EACf,IAAI,KAAKI,YAAY;EACrB,KAAKA,aAAa;EAClB,KAAKJ,OAAO,QAAQ;EACpB,KAAUC,MAAM,QAAQ;CACzB;CASA,UAAgB;EACf,MAAM,QAAQ,KAAKD,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9GA,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 { 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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"}
|
|
@@ -29,7 +29,7 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
31
31
|
* ```ts
|
|
32
|
-
* import { createWorker } from '@
|
|
32
|
+
* import { createWorker } from '@orkestrel/worker'
|
|
33
33
|
*
|
|
34
34
|
* const worker = createWorker<Query, Connection, Rows>({
|
|
35
35
|
* pool: { create: () => connect(), destroy: (connection) => connection.close() },
|
|
@@ -41,7 +41,7 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
41
41
|
* const rows = await worker.enqueue(query)
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
|
-
export declare function createWorker<TInput, TResource, TResult>(options:
|
|
44
|
+
export declare function createWorker<TInput, TResource, TResult>(options: WorkerOptions_2<TInput, TResource, TResult>): WorkerInterface<TInput, TResult>;
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* A resource-backed job worker — a thin facade composing a `Queue` (`@orkestrel/queue`)
|
|
@@ -52,16 +52,23 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
52
52
|
* `options.pool`) and a `Queue` whose handler ACQUIRES a pooled resource, runs the
|
|
53
53
|
* user handler against it, and RELEASES it in a `finally`. All concurrency, retries,
|
|
54
54
|
* timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
|
|
55
|
-
* - **Resource ↔ concurrency.** The
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
|
|
56
|
+
* safe integer after caller options are captured once. Only `undefined` defaults
|
|
57
|
+
* `concurrency` to `1` or pool `max` to that value; runtime `null` reaches the owning
|
|
58
|
+
* validator. The queue validates before the pool option is read; every declared pool member
|
|
59
|
+
* is then captured once by direct access, preserving inherited and non-enumerable structural
|
|
60
|
+
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
61
|
+
* reused across jobs.
|
|
58
62
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
59
63
|
* `execution.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
60
64
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
61
65
|
* release (the resource was never leased).
|
|
62
66
|
* - **Lifecycle (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /
|
|
63
67
|
* `abort` / `clear` delegate to the queue; `count` / `active` / `paused` / `stopped`
|
|
64
|
-
* read it. `
|
|
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
|
|
71
|
+
* identity; failures from both layers become an ordered `AggregateError`.
|
|
65
72
|
* - **Durability.** An optional `store` is passed straight through to the queue, so the
|
|
66
73
|
* worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).
|
|
67
74
|
* - **Observable (§13).** The owned {@link emitter} ({@link WorkerEventMap}) RE-EXPOSES the
|
|
@@ -77,8 +84,8 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
77
84
|
*/
|
|
78
85
|
declare class Worker_2<TInput, TResource, TResult> implements WorkerInterface<TInput, TResult> {
|
|
79
86
|
#private;
|
|
80
|
-
constructor(options:
|
|
81
|
-
get emitter(): EmitterInterface<
|
|
87
|
+
constructor(options: WorkerOptions_2<TInput, TResource, TResult>);
|
|
88
|
+
get emitter(): EmitterInterface<WorkerEventMap_2<TResult>>;
|
|
82
89
|
get count(): number;
|
|
83
90
|
get active(): number;
|
|
84
91
|
get paused(): boolean;
|
|
@@ -86,12 +93,12 @@ declare class Worker_2<TInput, TResource, TResult> implements WorkerInterface<TI
|
|
|
86
93
|
enqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult>;
|
|
87
94
|
restore(): Promise<void>;
|
|
88
95
|
start(): void;
|
|
89
|
-
stop(): void
|
|
96
|
+
stop(): Promise<void>;
|
|
90
97
|
pause(): void;
|
|
91
98
|
resume(): void;
|
|
92
|
-
abort(reason?: unknown): void
|
|
93
|
-
clear(): void
|
|
94
|
-
destroy(): void
|
|
99
|
+
abort(reason?: unknown): Promise<void>;
|
|
100
|
+
clear(): Promise<void>;
|
|
101
|
+
destroy(): Promise<void>;
|
|
95
102
|
}
|
|
96
103
|
export { Worker_2 as Worker }
|
|
97
104
|
|
|
@@ -113,7 +120,7 @@ export { Worker_2 as Worker }
|
|
|
113
120
|
* manages its own resources); a consumer who wants them observes a `Pool` directly.
|
|
114
121
|
* Declared as a `type` alias (§4.5).
|
|
115
122
|
*/
|
|
116
|
-
|
|
123
|
+
declare type WorkerEventMap_2<TResult> = {
|
|
117
124
|
/** A job was accepted — its id (delegated from the underlying queue's `enqueue`). */
|
|
118
125
|
readonly enqueue: readonly [id: string];
|
|
119
126
|
/** A job's attempt began running — its id. */
|
|
@@ -124,11 +131,12 @@ export declare type WorkerEventMap<TResult> = {
|
|
|
124
131
|
readonly success: readonly [id: string, result: TResult];
|
|
125
132
|
/** A job settled with a terminal failure — its id + the error. */
|
|
126
133
|
readonly failure: readonly [id: string, error: unknown];
|
|
127
|
-
/** The worker was aborted — the
|
|
134
|
+
/** The worker was aborted — the queue's coded abort error retaining the caller reason. */
|
|
128
135
|
readonly abort: readonly [reason: unknown];
|
|
129
136
|
/** The worker went idle — no pending jobs and none in flight. */
|
|
130
137
|
readonly drain: readonly [];
|
|
131
138
|
};
|
|
139
|
+
export { WorkerEventMap_2 as WorkerEventMap }
|
|
132
140
|
|
|
133
141
|
/** Runs one worker job with a leased pool resource. */
|
|
134
142
|
export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput, resource: TResource, execution: QueueExecution) => Promise<TResult> | TResult;
|
|
@@ -144,7 +152,7 @@ export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput,
|
|
|
144
152
|
* handler, the `error` option).
|
|
145
153
|
*/
|
|
146
154
|
export declare interface WorkerInterface<TInput, TResult> {
|
|
147
|
-
readonly emitter: EmitterInterface<
|
|
155
|
+
readonly emitter: EmitterInterface<WorkerEventMap_2<TResult>>;
|
|
148
156
|
readonly count: number;
|
|
149
157
|
readonly active: number;
|
|
150
158
|
readonly paused: boolean;
|
|
@@ -153,12 +161,26 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
153
161
|
/** Re-enqueue outstanding entries loaded from the store; no-op without a store. */
|
|
154
162
|
restore(): Promise<void>;
|
|
155
163
|
start(): void;
|
|
156
|
-
|
|
164
|
+
/** Stop the queue and await current-loop and durable cleanup quiescence. */
|
|
165
|
+
stop(): Promise<void>;
|
|
157
166
|
pause(): void;
|
|
158
167
|
resume(): void;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Cancel in-flight work, reject pending work, and await queue-owned cleanup.
|
|
170
|
+
*
|
|
171
|
+
* @param reason - Optional cause retained by the queue's coded abort error
|
|
172
|
+
* @returns The underlying queue's stable abort barrier
|
|
173
|
+
*/
|
|
174
|
+
abort(reason?: unknown): Promise<void>;
|
|
175
|
+
/** Drop pending work and await its durable cleanup. */
|
|
176
|
+
clear(): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Tear down the queue, then the pool, and finally the worker emitter.
|
|
179
|
+
*
|
|
180
|
+
* @returns One stable barrier shared by every call; it rejects with the original sole
|
|
181
|
+
* cleanup failure or an ordered `AggregateError` when both queue and pool fail
|
|
182
|
+
*/
|
|
183
|
+
destroy(): Promise<void>;
|
|
162
184
|
}
|
|
163
185
|
|
|
164
186
|
/**
|
|
@@ -169,7 +191,8 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
169
191
|
* retry while attempts remain (delegated to the underlying queue).
|
|
170
192
|
* - `pool` — the {@link PoolOptions} for the resource the handler runs against; its
|
|
171
193
|
* `max` defaults to `concurrency` so resources match the jobs in flight.
|
|
172
|
-
* - `concurrency` — the maximum jobs in flight at once; defaults to `1
|
|
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.
|
|
173
196
|
* - `retries` — the default extra attempts per job on failure; defaults to `0`.
|
|
174
197
|
* - `timeout` — the default per-attempt deadline in milliseconds; defaults to none.
|
|
175
198
|
* - `store` — durable backing; outstanding entries survive a restart; call
|
|
@@ -178,16 +201,18 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
178
201
|
* {@link WorkerEventMap} (the job lifecycle it surfaces from its underlying queue), wired
|
|
179
202
|
* at construction.
|
|
180
203
|
*/
|
|
181
|
-
|
|
182
|
-
readonly on?: EmitterHooks<
|
|
204
|
+
declare interface WorkerOptions_2<TInput, TResource, TResult> {
|
|
205
|
+
readonly on?: EmitterHooks<WorkerEventMap_2<TResult>>;
|
|
183
206
|
/** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
|
|
184
207
|
readonly error?: EmitterErrorHandler;
|
|
185
208
|
readonly handler: WorkerHandler<TInput, TResource, TResult>;
|
|
186
209
|
readonly pool: PoolOptions<TResource>;
|
|
187
210
|
readonly concurrency?: number;
|
|
188
211
|
readonly retries?: number;
|
|
212
|
+
/** Integer milliseconds in `0..2_147_483_647`; `0` disables the per-attempt deadline. */
|
|
189
213
|
readonly timeout?: number;
|
|
190
214
|
readonly store?: QueueStoreInterface<TInput>;
|
|
191
215
|
}
|
|
216
|
+
export { WorkerOptions_2 as WorkerOptions }
|
|
192
217
|
|
|
193
218
|
export { }
|
package/dist/src/core/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
31
31
|
* ```ts
|
|
32
|
-
* import { createWorker } from '@
|
|
32
|
+
* import { createWorker } from '@orkestrel/worker'
|
|
33
33
|
*
|
|
34
34
|
* const worker = createWorker<Query, Connection, Rows>({
|
|
35
35
|
* pool: { create: () => connect(), destroy: (connection) => connection.close() },
|
|
@@ -41,7 +41,7 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
41
41
|
* const rows = await worker.enqueue(query)
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
|
-
export declare function createWorker<TInput, TResource, TResult>(options:
|
|
44
|
+
export declare function createWorker<TInput, TResource, TResult>(options: WorkerOptions_2<TInput, TResource, TResult>): WorkerInterface<TInput, TResult>;
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* A resource-backed job worker — a thin facade composing a `Queue` (`@orkestrel/queue`)
|
|
@@ -52,16 +52,23 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
52
52
|
* `options.pool`) and a `Queue` whose handler ACQUIRES a pooled resource, runs the
|
|
53
53
|
* user handler against it, and RELEASES it in a `finally`. All concurrency, retries,
|
|
54
54
|
* timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
|
|
55
|
-
* - **Resource ↔ concurrency.** The
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
|
|
56
|
+
* safe integer after caller options are captured once. Only `undefined` defaults
|
|
57
|
+
* `concurrency` to `1` or pool `max` to that value; runtime `null` reaches the owning
|
|
58
|
+
* validator. The queue validates before the pool option is read; every declared pool member
|
|
59
|
+
* is then captured once by direct access, preserving inherited and non-enumerable structural
|
|
60
|
+
* options. At most one resource exists per in-flight job by default, and idle resources are
|
|
61
|
+
* reused across jobs.
|
|
58
62
|
* - **Acquire over the attempt signal.** Each job acquires using the attempt's
|
|
59
63
|
* `execution.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
60
64
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
61
65
|
* release (the resource was never leased).
|
|
62
66
|
* - **Lifecycle (§10).** `enqueue` / `restore` / `start` / `stop` / `pause` / `resume` /
|
|
63
67
|
* `abort` / `clear` delegate to the queue; `count` / `active` / `paused` / `stopped`
|
|
64
|
-
* read it. `
|
|
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
|
|
71
|
+
* identity; failures from both layers become an ordered `AggregateError`.
|
|
65
72
|
* - **Durability.** An optional `store` is passed straight through to the queue, so the
|
|
66
73
|
* worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).
|
|
67
74
|
* - **Observable (§13).** The owned {@link emitter} ({@link WorkerEventMap}) RE-EXPOSES the
|
|
@@ -77,8 +84,8 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
77
84
|
*/
|
|
78
85
|
declare class Worker_2<TInput, TResource, TResult> implements WorkerInterface<TInput, TResult> {
|
|
79
86
|
#private;
|
|
80
|
-
constructor(options:
|
|
81
|
-
get emitter(): EmitterInterface<
|
|
87
|
+
constructor(options: WorkerOptions_2<TInput, TResource, TResult>);
|
|
88
|
+
get emitter(): EmitterInterface<WorkerEventMap_2<TResult>>;
|
|
82
89
|
get count(): number;
|
|
83
90
|
get active(): number;
|
|
84
91
|
get paused(): boolean;
|
|
@@ -86,12 +93,12 @@ declare class Worker_2<TInput, TResource, TResult> implements WorkerInterface<TI
|
|
|
86
93
|
enqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult>;
|
|
87
94
|
restore(): Promise<void>;
|
|
88
95
|
start(): void;
|
|
89
|
-
stop(): void
|
|
96
|
+
stop(): Promise<void>;
|
|
90
97
|
pause(): void;
|
|
91
98
|
resume(): void;
|
|
92
|
-
abort(reason?: unknown): void
|
|
93
|
-
clear(): void
|
|
94
|
-
destroy(): void
|
|
99
|
+
abort(reason?: unknown): Promise<void>;
|
|
100
|
+
clear(): Promise<void>;
|
|
101
|
+
destroy(): Promise<void>;
|
|
95
102
|
}
|
|
96
103
|
export { Worker_2 as Worker }
|
|
97
104
|
|
|
@@ -113,7 +120,7 @@ export { Worker_2 as Worker }
|
|
|
113
120
|
* manages its own resources); a consumer who wants them observes a `Pool` directly.
|
|
114
121
|
* Declared as a `type` alias (§4.5).
|
|
115
122
|
*/
|
|
116
|
-
|
|
123
|
+
declare type WorkerEventMap_2<TResult> = {
|
|
117
124
|
/** A job was accepted — its id (delegated from the underlying queue's `enqueue`). */
|
|
118
125
|
readonly enqueue: readonly [id: string];
|
|
119
126
|
/** A job's attempt began running — its id. */
|
|
@@ -124,11 +131,12 @@ export declare type WorkerEventMap<TResult> = {
|
|
|
124
131
|
readonly success: readonly [id: string, result: TResult];
|
|
125
132
|
/** A job settled with a terminal failure — its id + the error. */
|
|
126
133
|
readonly failure: readonly [id: string, error: unknown];
|
|
127
|
-
/** The worker was aborted — the
|
|
134
|
+
/** The worker was aborted — the queue's coded abort error retaining the caller reason. */
|
|
128
135
|
readonly abort: readonly [reason: unknown];
|
|
129
136
|
/** The worker went idle — no pending jobs and none in flight. */
|
|
130
137
|
readonly drain: readonly [];
|
|
131
138
|
};
|
|
139
|
+
export { WorkerEventMap_2 as WorkerEventMap }
|
|
132
140
|
|
|
133
141
|
/** Runs one worker job with a leased pool resource. */
|
|
134
142
|
export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput, resource: TResource, execution: QueueExecution) => Promise<TResult> | TResult;
|
|
@@ -144,7 +152,7 @@ export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput,
|
|
|
144
152
|
* handler, the `error` option).
|
|
145
153
|
*/
|
|
146
154
|
export declare interface WorkerInterface<TInput, TResult> {
|
|
147
|
-
readonly emitter: EmitterInterface<
|
|
155
|
+
readonly emitter: EmitterInterface<WorkerEventMap_2<TResult>>;
|
|
148
156
|
readonly count: number;
|
|
149
157
|
readonly active: number;
|
|
150
158
|
readonly paused: boolean;
|
|
@@ -153,12 +161,26 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
153
161
|
/** Re-enqueue outstanding entries loaded from the store; no-op without a store. */
|
|
154
162
|
restore(): Promise<void>;
|
|
155
163
|
start(): void;
|
|
156
|
-
|
|
164
|
+
/** Stop the queue and await current-loop and durable cleanup quiescence. */
|
|
165
|
+
stop(): Promise<void>;
|
|
157
166
|
pause(): void;
|
|
158
167
|
resume(): void;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Cancel in-flight work, reject pending work, and await queue-owned cleanup.
|
|
170
|
+
*
|
|
171
|
+
* @param reason - Optional cause retained by the queue's coded abort error
|
|
172
|
+
* @returns The underlying queue's stable abort barrier
|
|
173
|
+
*/
|
|
174
|
+
abort(reason?: unknown): Promise<void>;
|
|
175
|
+
/** Drop pending work and await its durable cleanup. */
|
|
176
|
+
clear(): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Tear down the queue, then the pool, and finally the worker emitter.
|
|
179
|
+
*
|
|
180
|
+
* @returns One stable barrier shared by every call; it rejects with the original sole
|
|
181
|
+
* cleanup failure or an ordered `AggregateError` when both queue and pool fail
|
|
182
|
+
*/
|
|
183
|
+
destroy(): Promise<void>;
|
|
162
184
|
}
|
|
163
185
|
|
|
164
186
|
/**
|
|
@@ -169,7 +191,8 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
169
191
|
* retry while attempts remain (delegated to the underlying queue).
|
|
170
192
|
* - `pool` — the {@link PoolOptions} for the resource the handler runs against; its
|
|
171
193
|
* `max` defaults to `concurrency` so resources match the jobs in flight.
|
|
172
|
-
* - `concurrency` — the maximum jobs in flight at once; defaults to `1
|
|
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.
|
|
173
196
|
* - `retries` — the default extra attempts per job on failure; defaults to `0`.
|
|
174
197
|
* - `timeout` — the default per-attempt deadline in milliseconds; defaults to none.
|
|
175
198
|
* - `store` — durable backing; outstanding entries survive a restart; call
|
|
@@ -178,16 +201,18 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
178
201
|
* {@link WorkerEventMap} (the job lifecycle it surfaces from its underlying queue), wired
|
|
179
202
|
* at construction.
|
|
180
203
|
*/
|
|
181
|
-
|
|
182
|
-
readonly on?: EmitterHooks<
|
|
204
|
+
declare interface WorkerOptions_2<TInput, TResource, TResult> {
|
|
205
|
+
readonly on?: EmitterHooks<WorkerEventMap_2<TResult>>;
|
|
183
206
|
/** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
|
|
184
207
|
readonly error?: EmitterErrorHandler;
|
|
185
208
|
readonly handler: WorkerHandler<TInput, TResource, TResult>;
|
|
186
209
|
readonly pool: PoolOptions<TResource>;
|
|
187
210
|
readonly concurrency?: number;
|
|
188
211
|
readonly retries?: number;
|
|
212
|
+
/** Integer milliseconds in `0..2_147_483_647`; `0` disables the per-attempt deadline. */
|
|
189
213
|
readonly timeout?: number;
|
|
190
214
|
readonly store?: QueueStoreInterface<TInput>;
|
|
191
215
|
}
|
|
216
|
+
export { WorkerOptions_2 as WorkerOptions }
|
|
192
217
|
|
|
193
218
|
export { }
|