@orkestrel/worker 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -17
- package/dist/src/core/index.cjs +28 -24
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +67 -45
- package/dist/src/core/index.d.ts +67 -45
- package/dist/src/core/index.js +28 -24
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +14 -14
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +27 -27
- package/dist/src/server/index.d.ts +27 -27
- package/dist/src/server/index.js +14 -14
- package/dist/src/server/index.js.map +1 -1
- package/package.json +17 -18
package/dist/src/core/index.d.ts
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
2
|
-
import { EmitterHooks } from '@orkestrel/emitter';
|
|
3
|
-
import { EmitterInterface } from '@orkestrel/emitter';
|
|
4
|
-
import { PoolOptions } from '@orkestrel/pool';
|
|
5
|
-
import { QueueContext } from '@orkestrel/queue';
|
|
6
|
-
import { QueueEntryOptions } from '@orkestrel/queue';
|
|
7
|
-
import { QueueStoreInterface } from '@orkestrel/queue';
|
|
1
|
+
import type { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
2
|
+
import type { EmitterHooks } from '@orkestrel/emitter';
|
|
3
|
+
import type { EmitterInterface } from '@orkestrel/emitter';
|
|
4
|
+
import type { PoolOptions } from '@orkestrel/pool';
|
|
5
|
+
import type { QueueContext } from '@orkestrel/queue';
|
|
6
|
+
import type { QueueEntryOptions } from '@orkestrel/queue';
|
|
7
|
+
import type { QueueStoreInterface } from '@orkestrel/queue';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`)
|
|
11
|
-
* (`@orkestrel/pool`)
|
|
12
|
-
* automatically acquired pooled resource
|
|
13
|
-
* queue's bounded concurrency, retries, and per-attempt timeout / abort.
|
|
10
|
+
* Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) composed with a
|
|
11
|
+
* `Pool` (`@orkestrel/pool`), where each enqueued input runs through the handler against
|
|
12
|
+
* an automatically acquired pooled resource released when the job settles.
|
|
14
13
|
*
|
|
15
14
|
* @remarks
|
|
15
|
+
* Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.
|
|
16
16
|
* Default for the pool's `max`: the `concurrency` value, so resources match the jobs in flight.
|
|
17
17
|
* Resources are reused across jobs. A handler that throws still releases its resource (the
|
|
18
18
|
* acquire/release pair brackets the call in a `finally`), so a later job reuses it. The
|
|
19
19
|
* lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)
|
|
20
|
-
* delegates to the queue; `destroy` also tears the pool down.
|
|
21
|
-
*
|
|
20
|
+
* delegates to the queue; `destroy` also tears the pool down. It is observable (see the
|
|
21
|
+
* guide's `## Observing` section): a typed `emitter` surfaces the queue lifecycle
|
|
22
|
+
* (`enqueue` / `start` / `success` / `failure` / …).
|
|
22
23
|
*
|
|
23
24
|
* @typeParam TInput - The work input each job carries
|
|
24
25
|
* @typeParam TResource - The pooled resource each job runs against
|
|
@@ -27,10 +28,12 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
27
28
|
* `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})
|
|
28
29
|
* @returns A working {@link WorkerInterface}
|
|
29
30
|
*
|
|
30
|
-
* @example
|
|
31
|
+
* @example A resource-backed worker
|
|
31
32
|
* ```ts
|
|
32
33
|
* import { createWorker } from '@orkestrel/worker'
|
|
33
34
|
*
|
|
35
|
+
* // A Queue whose handler runs each job against a pooled resource (acquired before the
|
|
36
|
+
* // handler, released after it — even on throw). The pool's `max` defaults to `concurrency`.
|
|
34
37
|
* const worker = createWorker<Query, Connection, Rows>({
|
|
35
38
|
* pool: { create: () => connect(), destroy: (connection) => connection.close() },
|
|
36
39
|
* handler: (query, connection, { signal }) => connection.run(query, signal),
|
|
@@ -39,6 +42,7 @@ import { QueueStoreInterface } from '@orkestrel/queue';
|
|
|
39
42
|
* })
|
|
40
43
|
*
|
|
41
44
|
* const rows = await worker.enqueue(query)
|
|
45
|
+
* await worker.destroy() // awaits queue cleanup, pool cleanup, then emitter teardown
|
|
42
46
|
* ```
|
|
43
47
|
*/
|
|
44
48
|
export declare function createWorker<TInput, TResource, TResult>(options: WorkerOptions_2<TInput, TResource, TResult>): WorkerInterface<TInput, TResult>;
|
|
@@ -49,8 +53,8 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
49
53
|
*
|
|
50
54
|
* @remarks
|
|
51
55
|
* - **Composition, not reimplementation.** The Worker owns a `Pool` (built from
|
|
52
|
-
* `options.pool`) and a `Queue` whose handler
|
|
53
|
-
* user handler against it, and
|
|
56
|
+
* `options.pool`) and a `Queue` whose handler `acquire`s a pooled resource, runs the
|
|
57
|
+
* user handler against it, and `release`s it in a `finally`. All concurrency, retries,
|
|
54
58
|
* timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
|
|
55
59
|
* - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
|
|
56
60
|
* safe integer after caller options are captured once. Only `undefined` defaults
|
|
@@ -63,24 +67,24 @@ export declare function createWorker<TInput, TResource, TResult>(options: Worker
|
|
|
63
67
|
* `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects
|
|
64
68
|
* the acquire — the Queue then handles retry / rejection, and there is no token to
|
|
65
69
|
* release (the resource was never leased).
|
|
66
|
-
* - **Lifecycle (
|
|
67
|
-
* `abort` / `clear` delegate to the queue; `count` / `active` /
|
|
68
|
-
* read it. `stop` / `abort` / `clear` return the queue's own cleanup
|
|
69
|
-
* `destroy` returns one stable barrier while it tears down the queue, then the
|
|
70
|
-
* and destroys the worker emitter last. A sole cleanup failure is preserved by
|
|
70
|
+
* - **Lifecycle (see the guide's `## Methods` section).** `enqueue` / `restore` / `start` /
|
|
71
|
+
* `stop` / `pause` / `resume` / `abort` / `clear` delegate to the queue; `count` / `active` /
|
|
72
|
+
* `paused` / `stopped` read it. `stop` / `abort` / `clear` return the queue's own cleanup
|
|
73
|
+
* barriers. `destroy` returns one stable barrier while it tears down the queue, then the
|
|
74
|
+
* pool, and destroys the worker emitter last. A sole cleanup failure is preserved by
|
|
71
75
|
* identity; failures from both layers become an ordered `AggregateError`.
|
|
72
76
|
* - **Durability.** An optional `store` is passed straight through to the queue, so the
|
|
73
77
|
* worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).
|
|
74
|
-
* - **Observable (
|
|
75
|
-
* underlying queue's job lifecycle (`enqueue` /
|
|
76
|
-
* `abort` / `drain`) as the worker's
|
|
77
|
-
* construction — so a consumer observes the worker
|
|
78
|
-
* The bridge re-emits directly on the worker's own
|
|
79
|
-
* listener throw and routes it to its `error` handler
|
|
80
|
-
* worker observer can never corrupt the inner queue or pool
|
|
81
|
-
* throws, so the inner queue's own emit stays balanced. The
|
|
82
|
-
* release events stay the pool's internal concern (a Worker manages
|
|
83
|
-
* observe a `Pool` directly for those.
|
|
78
|
+
* - **Observable (see the guide's `## Observing` section).** The owned {@link emitter}
|
|
79
|
+
* ({@link WorkerEventMap}) re-exposes the underlying queue's job lifecycle (`enqueue` /
|
|
80
|
+
* `start` / `retry` / `success` / `failure` / `abort` / `drain`) as the worker's own events —
|
|
81
|
+
* bridged from the inner queue's emitter at construction — so a consumer observes the worker
|
|
82
|
+
* without reaching through to internals. The bridge re-emits directly on the worker's own
|
|
83
|
+
* emitter; the worker emitter isolates a listener throw and routes it to its `error` handler
|
|
84
|
+
* (the `error` option), so a buggy worker observer can never corrupt the inner queue or pool
|
|
85
|
+
* — the bridge listener never throws, so the inner queue's own emit stays balanced. The
|
|
86
|
+
* pool's create / acquire / release events stay the pool's internal concern (a Worker manages
|
|
87
|
+
* its own resources); observe a `Pool` directly for those.
|
|
84
88
|
*/
|
|
85
89
|
declare class Worker_2<TInput, TResource, TResult> implements WorkerInterface<TInput, TResult> {
|
|
86
90
|
#private;
|
|
@@ -104,21 +108,25 @@ export { Worker_2 as Worker }
|
|
|
104
108
|
|
|
105
109
|
/**
|
|
106
110
|
* Represents the push observation surface of a {@link WorkerInterface} — the job
|
|
107
|
-
* lifecycle a fire-and-forget observer subscribes to
|
|
108
|
-
* moments so a Worker consumer never reaches through to the internal `Queue`.
|
|
111
|
+
* lifecycle a fire-and-forget observer subscribes to.
|
|
109
112
|
*
|
|
110
113
|
* @typeParam TResult - The value a job resolves (the `success` payload), mirroring the
|
|
111
114
|
* {@link WorkerInterface}'s own `TResult`.
|
|
112
115
|
*
|
|
113
116
|
* @remarks
|
|
114
117
|
* A Worker is a `Queue`⨉`Pool` facade (both from their own `@orkestrel` packages); this
|
|
115
|
-
* map
|
|
116
|
-
* `success` / `failure` / `abort` / `drain`) as the worker's
|
|
117
|
-
* underlying queue's emitter at construction, so a
|
|
118
|
-
*
|
|
118
|
+
* map re-exposes the queue lifecycle the worker surfaces (`enqueue` / `start` / `retry` /
|
|
119
|
+
* `success` / `failure` / `abort` / `drain`) as the worker's own events — wired from the
|
|
120
|
+
* underlying queue's emitter at construction, so a consumer never reaches through to the
|
|
121
|
+
* internal `Queue` and a buggy observer is isolated exactly as on the queue (a throw
|
|
122
|
+
* routes to the worker emitter's `error` handler). The
|
|
119
123
|
* pool's create / acquire / release events stay the pool's internal concern (a Worker
|
|
120
124
|
* manages its own resources); a consumer who wants them observes a `Pool` directly.
|
|
121
|
-
*
|
|
125
|
+
*
|
|
126
|
+
* Declared as a `type` alias (not `interface extends EventMap` — `EventMap` is a
|
|
127
|
+
* `type` kind): a type-literal satisfies the `EventMap` constraint
|
|
128
|
+
* (`Record<string, readonly unknown[]>`) structurally, whereas an interface lacks the
|
|
129
|
+
* required index signature.
|
|
122
130
|
*/
|
|
123
131
|
declare type WorkerEventMap_2<TResult> = {
|
|
124
132
|
/** Fires when a job is accepted — its id (delegated from the underlying queue's `enqueue`). */
|
|
@@ -142,7 +150,8 @@ export { WorkerEventMap_2 as WorkerEventMap }
|
|
|
142
150
|
export declare type WorkerHandler<TInput, TResource, TResult> = (input: TInput, resource: TResource, context: QueueContext) => Promise<TResult> | TResult;
|
|
143
151
|
|
|
144
152
|
/**
|
|
145
|
-
* Represents
|
|
153
|
+
* Represents the job-worker contract a consumer holds — a `Queue` whose handler runs each
|
|
154
|
+
* job against a pooled resource.
|
|
146
155
|
*
|
|
147
156
|
* @remarks
|
|
148
157
|
* Exposes a typed {@link emitter} carrying the job lifecycle
|
|
@@ -157,25 +166,38 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
157
166
|
readonly active: number;
|
|
158
167
|
readonly paused: boolean;
|
|
159
168
|
readonly stopped: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Submits one job in FIFO order; the handler runs against an acquired resource, released
|
|
171
|
+
* when the job settles.
|
|
172
|
+
*
|
|
173
|
+
* @param input - The work payload the handler receives
|
|
174
|
+
* @param options - Optional id, retry and timeout overrides, and an entry abort signal
|
|
175
|
+
* @returns The job's settle-once execution promise
|
|
176
|
+
*/
|
|
160
177
|
enqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult>;
|
|
161
|
-
/** Re-enqueues outstanding entries
|
|
178
|
+
/** Re-enqueues the store's outstanding entries through the underlying queue; no-op without a store. */
|
|
162
179
|
restore(): Promise<void>;
|
|
180
|
+
/** Starts or restarts the underlying queue's worker loops. */
|
|
163
181
|
start(): void;
|
|
164
|
-
/** Stops the queue and awaits current-loop and durable cleanup quiescence. */
|
|
182
|
+
/** Stops the queue, rejects pending work, and awaits current-loop and durable cleanup quiescence. */
|
|
165
183
|
stop(): Promise<void>;
|
|
184
|
+
/** Suspends dequeuing through the underlying queue, leaving in-flight jobs untouched. */
|
|
166
185
|
pause(): void;
|
|
186
|
+
/** Continues a paused worker through the underlying queue. */
|
|
167
187
|
resume(): void;
|
|
168
188
|
/**
|
|
169
|
-
* Cancels in-flight work, rejects pending work, and awaits queue-owned cleanup
|
|
189
|
+
* Cancels in-flight work, rejects pending work, and awaits queue-owned cleanup; an
|
|
190
|
+
* aborted attempt is never retried.
|
|
170
191
|
*
|
|
171
192
|
* @param reason - Optional cause retained by the queue's coded abort error
|
|
172
193
|
* @returns The underlying queue's stable abort barrier
|
|
173
194
|
*/
|
|
174
195
|
abort(reason?: unknown): Promise<void>;
|
|
175
|
-
/** Drops pending
|
|
196
|
+
/** Drops pending jobs and awaits their durable cleanup, leaving in-flight jobs untouched. */
|
|
176
197
|
clear(): Promise<void>;
|
|
177
198
|
/**
|
|
178
|
-
* Tears down the queue, then the pool, and finally the worker emitter
|
|
199
|
+
* Tears down the queue, then the pool, and finally the worker emitter, behind one stable
|
|
200
|
+
* barrier.
|
|
179
201
|
*
|
|
180
202
|
* @returns One stable barrier shared by every call; it rejects with the original sole
|
|
181
203
|
* cleanup failure or an ordered `AggregateError` when both queue and pool fail
|
|
@@ -198,7 +220,7 @@ export declare interface WorkerInterface<TInput, TResult> {
|
|
|
198
220
|
* deadline.
|
|
199
221
|
* - `store` — durable backing; outstanding entries survive a restart; call
|
|
200
222
|
* `restore()` to re-run them.
|
|
201
|
-
* - `on` — the reserved {@link EmitterHooks} key
|
|
223
|
+
* - `on` — the reserved {@link EmitterHooks} key: initial listeners for the worker's
|
|
202
224
|
* {@link WorkerEventMap} (the job lifecycle it surfaces from its underlying queue), wired
|
|
203
225
|
* at construction.
|
|
204
226
|
*/
|
package/dist/src/core/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import { Queue } from "@orkestrel/queue";
|
|
|
8
8
|
*
|
|
9
9
|
* @remarks
|
|
10
10
|
* - **Composition, not reimplementation.** The Worker owns a `Pool` (built from
|
|
11
|
-
* `options.pool`) and a `Queue` whose handler
|
|
12
|
-
* user handler against it, and
|
|
11
|
+
* `options.pool`) and a `Queue` whose handler `acquire`s a pooled resource, runs the
|
|
12
|
+
* user handler against it, and `release`s it in a `finally`. All concurrency, retries,
|
|
13
13
|
* timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.
|
|
14
14
|
* - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive
|
|
15
15
|
* safe integer after caller options are captured once. Only `undefined` defaults
|
|
@@ -22,24 +22,24 @@ import { Queue } from "@orkestrel/queue";
|
|
|
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
|
-
* - **Lifecycle (
|
|
26
|
-
* `abort` / `clear` delegate to the queue; `count` / `active` /
|
|
27
|
-
* read it. `stop` / `abort` / `clear` return the queue's own cleanup
|
|
28
|
-
* `destroy` returns one stable barrier while it tears down the queue, then the
|
|
29
|
-
* and destroys the worker emitter last. A sole cleanup failure is preserved by
|
|
25
|
+
* - **Lifecycle (see the guide's `## Methods` section).** `enqueue` / `restore` / `start` /
|
|
26
|
+
* `stop` / `pause` / `resume` / `abort` / `clear` delegate to the queue; `count` / `active` /
|
|
27
|
+
* `paused` / `stopped` read it. `stop` / `abort` / `clear` return the queue's own cleanup
|
|
28
|
+
* barriers. `destroy` returns one stable barrier while it tears down the queue, then the
|
|
29
|
+
* pool, and destroys the worker emitter last. A sole cleanup failure is preserved by
|
|
30
30
|
* identity; failures from both layers become an ordered `AggregateError`.
|
|
31
31
|
* - **Durability.** An optional `store` is passed straight through to the queue, so the
|
|
32
32
|
* worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).
|
|
33
|
-
* - **Observable (
|
|
34
|
-
* underlying queue's job lifecycle (`enqueue` /
|
|
35
|
-
* `abort` / `drain`) as the worker's
|
|
36
|
-
* construction — so a consumer observes the worker
|
|
37
|
-
* The bridge re-emits directly on the worker's own
|
|
38
|
-
* listener throw and routes it to its `error` handler
|
|
39
|
-
* worker observer can never corrupt the inner queue or pool
|
|
40
|
-
* throws, so the inner queue's own emit stays balanced. The
|
|
41
|
-
* release events stay the pool's internal concern (a Worker manages
|
|
42
|
-
* observe a `Pool` directly for those.
|
|
33
|
+
* - **Observable (see the guide's `## Observing` section).** The owned {@link emitter}
|
|
34
|
+
* ({@link WorkerEventMap}) re-exposes the underlying queue's job lifecycle (`enqueue` /
|
|
35
|
+
* `start` / `retry` / `success` / `failure` / `abort` / `drain`) as the worker's own events —
|
|
36
|
+
* bridged from the inner queue's emitter at construction — so a consumer observes the worker
|
|
37
|
+
* without reaching through to internals. The bridge re-emits directly on the worker's own
|
|
38
|
+
* emitter; the worker emitter isolates a listener throw and routes it to its `error` handler
|
|
39
|
+
* (the `error` option), so a buggy worker observer can never corrupt the inner queue or pool
|
|
40
|
+
* — the bridge listener never throws, so the inner queue's own emit stays balanced. The
|
|
41
|
+
* pool's create / acquire / release events stay the pool's internal concern (a Worker manages
|
|
42
|
+
* its own resources); observe a `Pool` directly for those.
|
|
43
43
|
*/
|
|
44
44
|
var Worker = class {
|
|
45
45
|
#queue;
|
|
@@ -158,18 +158,19 @@ var Worker = class {
|
|
|
158
158
|
//#endregion
|
|
159
159
|
//#region src/core/factories.ts
|
|
160
160
|
/**
|
|
161
|
-
* Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`)
|
|
162
|
-
* (`@orkestrel/pool`)
|
|
163
|
-
* automatically acquired pooled resource
|
|
164
|
-
* queue's bounded concurrency, retries, and per-attempt timeout / abort.
|
|
161
|
+
* Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) composed with a
|
|
162
|
+
* `Pool` (`@orkestrel/pool`), where each enqueued input runs through the handler against
|
|
163
|
+
* an automatically acquired pooled resource released when the job settles.
|
|
165
164
|
*
|
|
166
165
|
* @remarks
|
|
166
|
+
* Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.
|
|
167
167
|
* Default for the pool's `max`: the `concurrency` value, so resources match the jobs in flight.
|
|
168
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
|
-
* delegates to the queue; `destroy` also tears the pool down.
|
|
172
|
-
*
|
|
171
|
+
* delegates to the queue; `destroy` also tears the pool down. It is observable (see the
|
|
172
|
+
* guide's `## Observing` section): a typed `emitter` surfaces the queue lifecycle
|
|
173
|
+
* (`enqueue` / `start` / `success` / `failure` / …).
|
|
173
174
|
*
|
|
174
175
|
* @typeParam TInput - The work input each job carries
|
|
175
176
|
* @typeParam TResource - The pooled resource each job runs against
|
|
@@ -178,10 +179,12 @@ var Worker = class {
|
|
|
178
179
|
* `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})
|
|
179
180
|
* @returns A working {@link WorkerInterface}
|
|
180
181
|
*
|
|
181
|
-
* @example
|
|
182
|
+
* @example A resource-backed worker
|
|
182
183
|
* ```ts
|
|
183
184
|
* import { createWorker } from '@orkestrel/worker'
|
|
184
185
|
*
|
|
186
|
+
* // A Queue whose handler runs each job against a pooled resource (acquired before the
|
|
187
|
+
* // handler, released after it — even on throw). The pool's `max` defaults to `concurrency`.
|
|
185
188
|
* const worker = createWorker<Query, Connection, Rows>({
|
|
186
189
|
* pool: { create: () => connect(), destroy: (connection) => connection.close() },
|
|
187
190
|
* handler: (query, connection, { signal }) => connection.run(query, signal),
|
|
@@ -190,6 +193,7 @@ var Worker = class {
|
|
|
190
193
|
* })
|
|
191
194
|
*
|
|
192
195
|
* const rows = await worker.enqueue(query)
|
|
196
|
+
* await worker.destroy() // awaits queue cleanup, pool cleanup, then emitter teardown
|
|
193
197
|
* ```
|
|
194
198
|
*/
|
|
195
199
|
function createWorker(options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","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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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/Worker.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { QueueContext, QueueEntryOptions } from '@orkestrel/queue'\nimport type { WorkerEventMap, WorkerHandler, WorkerInterface, WorkerOptions } from './types.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport { Pool } from '@orkestrel/pool'\nimport { Queue } from '@orkestrel/queue'\n\n/**\n * Represents a resource-backed job worker — a thin facade composing a `Queue`\n * (`@orkestrel/queue`) with a `Pool` (`@orkestrel/pool`).\n *\n * @remarks\n * - **Composition, not reimplementation.** The Worker owns a `Pool` (built from\n * `options.pool`) and a `Queue` whose handler `acquire`s a pooled resource, runs the\n * user handler against it, and `release`s it in a `finally`. All concurrency, retries,\n * timeout, and lifecycle are the Queue's — the Worker adds only the resource pairing.\n * - **Resource ↔ concurrency.** The queue strictly validates `concurrency` as a positive\n * safe integer after caller options are captured once. Only `undefined` defaults\n * `concurrency` to `1` or pool `max` to that value; runtime `null` reaches the owning\n * validator. The queue validates before the pool option is read; every declared pool member\n * is then captured once by direct access, preserving inherited and non-enumerable structural\n * options. At most one resource exists per in-flight job by default, and idle resources are\n * reused across jobs.\n * - **Acquire over the attempt signal.** Each job acquires using the attempt's\n * `context.signal`, so an `abort` / `timeout` while waiting for a resource rejects\n * the acquire — the Queue then handles retry / rejection, and there is no token to\n * release (the resource was never leased).\n * - **Lifecycle (see the guide's `## Methods` section).** `enqueue` / `restore` / `start` /\n * `stop` / `pause` / `resume` / `abort` / `clear` delegate to the queue; `count` / `active` /\n * `paused` / `stopped` read it. `stop` / `abort` / `clear` return the queue's own cleanup\n * barriers. `destroy` returns one stable barrier while it tears down the queue, then the\n * pool, and destroys the worker emitter last. A sole cleanup failure is preserved by\n * identity; failures from both layers become an ordered `AggregateError`.\n * - **Durability.** An optional `store` is passed straight through to the queue, so the\n * worker's outstanding jobs persist; `restore` re-runs them (delegated to the queue).\n * - **Observable (see the guide's `## Observing` section).** The owned {@link emitter}\n * ({@link WorkerEventMap}) re-exposes the underlying queue's job lifecycle (`enqueue` /\n * `start` / `retry` / `success` / `failure` / `abort` / `drain`) as the worker's own events —\n * bridged from the inner queue's emitter at construction — so a consumer observes the worker\n * without reaching through to internals. The bridge re-emits directly on the worker's own\n * emitter; the worker emitter isolates a listener throw and routes it to its `error` handler\n * (the `error` option), so a buggy worker observer can never corrupt the inner queue or pool\n * — the bridge listener never throws, so the inner queue's own emit stays balanced. The\n * pool's create / acquire / release events stay the pool's internal concern (a Worker manages\n * its own resources); observe a `Pool` directly for those.\n */\nexport class Worker<TInput, TResource, TResult> implements WorkerInterface<TInput, TResult> {\n\treadonly #queue: Queue<TInput, TResult>\n\treadonly #pool: Pool<TResource>\n\t// The push observation surface (see the guide's `## Observing` section) — the worker's own\n\t// emitter, fed by the queue→worker bridge. The emitter isolates a worker observer's throw\n\t// (routing it to the `error` handler), so it never escapes into queue or pool.\n\treadonly #emitter: Emitter<WorkerEventMap<TResult>>\n\treadonly #handler: WorkerHandler<TInput, TResource, TResult>\n\t#ending: PromiseWithResolvers<void> | undefined\n\n\tconstructor(options: WorkerOptions<TInput, TResource, TResult>) {\n\t\tconst {\n\t\t\tconcurrency: capturedConcurrency,\n\t\t\thandler,\n\t\t\ton,\n\t\t\terror,\n\t\t\tretries,\n\t\t\ttimeout,\n\t\t\tstore,\n\t\t} = options\n\t\tconst concurrency = capturedConcurrency === undefined ? 1 : capturedConcurrency\n\t\tthis.#handler = handler\n\t\tthis.#emitter = new Emitter<WorkerEventMap<TResult>>({\n\t\t\t...(on !== undefined ? { on } : {}),\n\t\t\t...(error !== undefined ? { error } : {}),\n\t\t})\n\t\tthis.#queue = new Queue<TInput, TResult>({\n\t\t\thandler: this.#handle.bind(this),\n\t\t\tconcurrency,\n\t\t\t...(retries !== undefined ? { retries } : {}),\n\t\t\t...(timeout !== undefined ? { timeout } : {}),\n\t\t\t...(store !== undefined ? { store } : {}),\n\t\t})\n\t\tconst pool = options.pool\n\t\tconst { max, on: poolOn, error: poolError, create, destroy, validate } = pool\n\t\tthis.#pool = new Pool<TResource>({\n\t\t\tcreate,\n\t\t\tmax: max === undefined ? concurrency : max,\n\t\t\t...(poolOn !== undefined ? { on: poolOn } : {}),\n\t\t\t...(poolError !== undefined ? { error: poolError } : {}),\n\t\t\t...(destroy !== undefined ? { destroy } : {}),\n\t\t\t...(validate !== undefined ? { validate } : {}),\n\t\t})\n\t\tthis.#bridge()\n\t}\n\n\tget emitter(): EmitterInterface<WorkerEventMap<TResult>> {\n\t\treturn this.#emitter\n\t}\n\n\tget count(): number {\n\t\treturn this.#queue.count\n\t}\n\n\tget active(): number {\n\t\treturn this.#queue.active\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#queue.paused\n\t}\n\n\tget stopped(): boolean {\n\t\treturn this.#queue.stopped\n\t}\n\n\tenqueue(input: TInput, options?: QueueEntryOptions): Promise<TResult> {\n\t\treturn this.#queue.enqueue(input, options)\n\t}\n\n\trestore(): Promise<void> {\n\t\treturn this.#queue.restore()\n\t}\n\n\tstart(): void {\n\t\tthis.#queue.start()\n\t}\n\n\tstop(): Promise<void> {\n\t\treturn this.#queue.stop()\n\t}\n\n\tpause(): void {\n\t\tthis.#queue.pause()\n\t}\n\n\tresume(): void {\n\t\tthis.#queue.resume()\n\t}\n\n\tabort(reason?: unknown): Promise<void> {\n\t\treturn this.#queue.abort(reason)\n\t}\n\n\tclear(): Promise<void> {\n\t\treturn this.#queue.clear()\n\t}\n\n\tdestroy(): Promise<void> {\n\t\tif (this.#ending !== undefined) return this.#ending.promise\n\t\tconst ending = Promise.withResolvers<void>()\n\t\tthis.#ending = ending\n\t\tvoid this.#teardown(ending)\n\t\treturn ending.promise\n\t}\n\n\tasync #handle(input: TInput, context: QueueContext): Promise<TResult> {\n\t\tconst token = await this.#pool.acquire(context.signal)\n\t\ttry {\n\t\t\treturn await this.#handler(input, token.value, context)\n\t\t} finally {\n\t\t\ttoken.release()\n\t\t}\n\t}\n\n\tasync #teardown(ending: PromiseWithResolvers<void>): Promise<void> {\n\t\tconst failures: unknown[] = []\n\t\ttry {\n\t\t\tawait this.#queue.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\ttry {\n\t\t\tawait this.#pool.destroy()\n\t\t} catch (error) {\n\t\t\tfailures.push(error)\n\t\t}\n\t\tthis.#emitter.destroy()\n\t\tif (failures.length === 0) ending.resolve()\n\t\telse if (failures.length === 1) ending.reject(failures[0])\n\t\telse ending.reject(new AggregateError(failures, 'worker destroy cleanup failed'))\n\t}\n\n\t// Bridge the inner queue's lifecycle onto the worker's own emitter, once at construction.\n\t// Each listener re-emits the queue event directly on the worker's emitter, which isolates a\n\t// worker observer's throw (routing it to the worker's `error` handler). Because the bridge\n\t// listener itself never throws, the queue's own `#emitter.emit` — which invoked this\n\t// listener — sees no throw, so the inner queue's engine stays balanced regardless of what a\n\t// worker observer does. The events are already post-transition (they fire from the queue's\n\t// own post-settle / post-wake emits), so this stays observation.\n\t#bridge(): void {\n\t\tconst queue = this.#queue.emitter\n\t\tqueue.on('enqueue', (id) => this.#emitter.emit('enqueue', id))\n\t\tqueue.on('start', (id) => this.#emitter.emit('start', id))\n\t\tqueue.on('retry', (id, attempt) => this.#emitter.emit('retry', id, attempt))\n\t\tqueue.on('success', (id, result) => this.#emitter.emit('success', id, result))\n\t\tqueue.on('failure', (id, error) => this.#emitter.emit('failure', id, error))\n\t\tqueue.on('abort', (reason) => this.#emitter.emit('abort', reason))\n\t\tqueue.on('drain', () => this.#emitter.emit('drain'))\n\t}\n}\n","import type { WorkerInterface, WorkerOptions } from './types.js'\nimport { Worker } from './Worker.js'\n\n/**\n * Creates a resource-backed job worker — a `Queue` (`@orkestrel/queue`) composed with a\n * `Pool` (`@orkestrel/pool`), where each enqueued input runs through the handler against\n * an automatically acquired pooled resource released when the job settles.\n *\n * @remarks\n * Bounded concurrency, retries, and the per-attempt timeout and abort are the queue's.\n * Default for the pool's `max`: the `concurrency` value, so resources match the jobs in flight.\n * Resources are reused across jobs. A handler that throws still releases its resource (the\n * acquire/release pair brackets the call in a `finally`), so a later job reuses it. The\n * lifecycle (`start` / `stop` / `pause` / `resume` / `abort` / `clear` / `destroy`)\n * delegates to the queue; `destroy` also tears the pool down. It is observable (see the\n * guide's `## Observing` section): a typed `emitter` surfaces the queue lifecycle\n * (`enqueue` / `start` / `success` / `failure` / …).\n *\n * @typeParam TInput - The work input each job carries\n * @typeParam TResource - The pooled resource each job runs against\n * @typeParam TResult - The value the handler resolves for a job\n * @param options - The `handler` and `pool` plus the optional `concurrency`, `retries`,\n * `timeout`, `store`, `on`, and `error` keys (see {@link WorkerOptions})\n * @returns A working {@link WorkerInterface}\n *\n * @example A resource-backed worker\n * ```ts\n * import { createWorker } from '@orkestrel/worker'\n *\n * // A Queue whose handler runs each job against a pooled resource (acquired before the\n * // handler, released after it — even on throw). The pool's `max` defaults to `concurrency`.\n * const worker = createWorker<Query, Connection, Rows>({\n * \tpool: { create: () => connect(), destroy: (connection) => connection.close() },\n * \thandler: (query, connection, { signal }) => connection.run(query, signal),\n * \tconcurrency: 4,\n * \tretries: 1,\n * })\n *\n * const rows = await worker.enqueue(query)\n * await worker.destroy() // awaits queue cleanup, pool cleanup, then emitter teardown\n * ```\n */\nexport function createWorker<TInput, TResource, TResult>(\n\toptions: WorkerOptions<TInput, TResource, TResult>,\n): WorkerInterface<TInput, TResult> {\n\treturn new Worker(options)\n}\n"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ let _orkestrel_queue = require("@orkestrel/queue");
|
|
|
6
6
|
let _src_core = require("../core/index.cjs");
|
|
7
7
|
//#region src/server/helpers.ts
|
|
8
8
|
/**
|
|
9
|
-
* Narrows an inbound `message` to a {@link Reply} for a given
|
|
9
|
+
* Narrows an inbound `message` to a {@link Reply} for a given correlation `id` — no assertion.
|
|
10
10
|
*
|
|
11
11
|
* @remarks
|
|
12
12
|
* A total predicate: a record whose `id` matches and whose `ok` discriminant is well-formed.
|
|
@@ -15,8 +15,8 @@ let _src_core = require("../core/index.cjs");
|
|
|
15
15
|
* correlated predicate rather than a `Guard<Reply>` and is not accepted where a `Guard` is.
|
|
16
16
|
*
|
|
17
17
|
* @param value - The inbound message to narrow
|
|
18
|
-
* @param id - The
|
|
19
|
-
* @returns True if the value is this
|
|
18
|
+
* @param id - The per-dispatch correlation id a matching reply must carry
|
|
19
|
+
* @returns True if the value is this dispatch's well-formed reply; false otherwise
|
|
20
20
|
*/
|
|
21
21
|
function isReply(value, id) {
|
|
22
22
|
const outcome = (0, _orkestrel_contract.attempt)(() => {
|
|
@@ -45,7 +45,7 @@ function isReply(value, id) {
|
|
|
45
45
|
* and restore. That job id identifies work, not a caller, and is not authentication or
|
|
46
46
|
* authorization evidence. Each attempt has its own `AbortController`, so an `abort`
|
|
47
47
|
* message for the correlation id fires the handler's `signal` (cooperative — the main
|
|
48
|
-
* side
|
|
48
|
+
* side also terminates the thread, so a handler that ignores its signal is still stopped).
|
|
49
49
|
* Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
|
|
50
50
|
* (`parentPort === null`) it is a no-op.
|
|
51
51
|
*
|
|
@@ -226,7 +226,7 @@ var Thread = class {
|
|
|
226
226
|
* authentication / authorization evidence. Per-job consumer context is explicit,
|
|
227
227
|
* structured-cloneable `input`; ambient context is not worker-thread transport. A success
|
|
228
228
|
* `value` is narrowed through `result` (a value that fails the guard rejects — the zero-`as`
|
|
229
|
-
* type bridge); a failure rejects with the thread's error string. A thread that
|
|
229
|
+
* type bridge); a failure rejects with the thread's error string. A thread that had already died
|
|
230
230
|
* rejects synchronously at construction from the latched {@link NodeThread.death} — its death
|
|
231
231
|
* events fired before this dispatch existed and will never fire again, so waiting on the
|
|
232
232
|
* listeners would dangle forever; the latch makes death total across every event ordering. If
|
|
@@ -480,13 +480,13 @@ var NodeWorker = class {
|
|
|
480
480
|
*
|
|
481
481
|
* @remarks
|
|
482
482
|
* Constructs the thread with the `script` module and the cloned `workerData`, then
|
|
483
|
-
* resolves on the thread's `online` event (rejecting on an early `error`
|
|
483
|
+
* resolves on the thread's `online` event (rejecting on an early `error` or on an `exit`
|
|
484
484
|
* that arrives before `online`, so the spawn promise is total — it can never dangle on a
|
|
485
485
|
* thread that died without erroring). The returned entity attaches persistent `error` /
|
|
486
|
-
* `exit` listeners that flip `alive` to `false`
|
|
486
|
+
* `exit` listeners that flip `alive` to `false` and latch the first terminal event on
|
|
487
487
|
* {@link NodeThread.death}: a crash is observable to an in-flight {@link Dispatch} (through
|
|
488
488
|
* its own listeners), to a pool's `validate` (through `alive`), and — crucially — to a
|
|
489
|
-
* dispatch that attaches
|
|
489
|
+
* dispatch that attaches only after the death (through the latch). A `messageerror` is terminal
|
|
490
490
|
* too, so a thread whose inbound payload could not be deserialized is never reused. The latch
|
|
491
491
|
* closes a real race: a thread can become terminal before the readiness promise continuation
|
|
492
492
|
* hands it to a {@link Dispatch}, leaving no future death event for that dispatch to observe.
|
|
@@ -518,7 +518,7 @@ function createThread(script, workerData) {
|
|
|
518
518
|
* (and reloaded from) the file at `path`, surviving a process restart. There is no new
|
|
519
519
|
* class — the store engine ({@link createDatabaseQueueStore}) is shared, and only the
|
|
520
520
|
* driver changes where the bytes live. The `input` shape must be JSON-serializable
|
|
521
|
-
* (the JSON driver round-trips it as JSON). Build a second store over the
|
|
521
|
+
* (the JSON driver round-trips it as JSON). Build a second store over the same `path` to
|
|
522
522
|
* resume the outstanding entries a prior store persisted.
|
|
523
523
|
*
|
|
524
524
|
* @typeParam TInput - The contract shape of each entry's `input` payload
|
|
@@ -543,7 +543,7 @@ function createJSONQueueStore(path, input) {
|
|
|
543
543
|
}
|
|
544
544
|
/**
|
|
545
545
|
* Creates a CPU-parallel worker over `node:worker_threads` — a thin specialization of the
|
|
546
|
-
* core `createWorker` whose pooled resource is a worker
|
|
546
|
+
* core `createWorker` whose pooled resource is a worker thread.
|
|
547
547
|
*
|
|
548
548
|
* @remarks
|
|
549
549
|
* Composition, not reimplementation: all concurrency, retries, per-attempt timeout,
|
|
@@ -554,10 +554,10 @@ function createJSONQueueStore(path, input) {
|
|
|
554
554
|
* internal handler that narrows the input through `options.input` (fail-fast before the
|
|
555
555
|
* structured-clone boundary) then runs a {@link Dispatch} against the leased thread,
|
|
556
556
|
* narrowing the reply through
|
|
557
|
-
* `options.result`.
|
|
558
|
-
* need no explicit type arguments. The boundary is crossed with
|
|
559
|
-
* reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`
|
|
560
|
-
*
|
|
557
|
+
* `options.result`. `TInput` and `TResult` infer from the `input` and `result` guards, so
|
|
558
|
+
* call sites need no explicit type arguments. The boundary is crossed with no `as`: the
|
|
559
|
+
* guards reconstruct `TInput` / `TResult` by validation. An `abort` / `timeout`
|
|
560
|
+
* terminates the in-flight thread (CPU-bound work can't honour a signal) and evicts it; a
|
|
561
561
|
* subsequent job spawns a fresh thread. The worker script's module must call
|
|
562
562
|
* `serveWorker`. Returns the plain {@link WorkerInterface} — its methods are the Worker's.
|
|
563
563
|
*
|