@orkestrel/worker 0.0.4 → 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 +55 -24
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +38 -15
- package/dist/src/core/index.d.ts +38 -15
- package/dist/src/core/index.js +55 -24
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +140 -67
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +39 -50
- package/dist/src/server/index.d.ts +39 -50
- package/dist/src/server/index.js +140 -67
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -9
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContractShape } from '@orkestrel/contract';
|
|
2
|
+
import { Guard } from '@orkestrel/contract';
|
|
2
3
|
import { Infer } from '@orkestrel/contract';
|
|
3
4
|
import { QueueExecution } from '@orkestrel/queue';
|
|
4
5
|
import { QueueStoreInterface } from '@orkestrel/queue';
|
|
@@ -25,8 +26,8 @@ import { WorkerInterface } from '../core/index.ts';
|
|
|
25
26
|
*
|
|
26
27
|
* @example
|
|
27
28
|
* ```ts
|
|
28
|
-
* import { stringShape } from '@
|
|
29
|
-
* import { createJSONQueueStore } from '@
|
|
29
|
+
* import { stringShape } from '@orkestrel/contract'
|
|
30
|
+
* import { createJSONQueueStore } from '@orkestrel/worker/server'
|
|
30
31
|
*
|
|
31
32
|
* const store = createJSONQueueStore('data/queue.json', stringShape())
|
|
32
33
|
* await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })
|
|
@@ -65,7 +66,7 @@ export declare function createJSONQueueStore<TInput extends ContractShape>(path:
|
|
|
65
66
|
*
|
|
66
67
|
* @example
|
|
67
68
|
* ```ts
|
|
68
|
-
* import { createNodeWorker } from '@
|
|
69
|
+
* import { createNodeWorker } from '@orkestrel/worker/server'
|
|
69
70
|
*
|
|
70
71
|
* const worker = createNodeWorker({
|
|
71
72
|
* script: new URL('./double.js', import.meta.url),
|
|
@@ -75,7 +76,7 @@ export declare function createJSONQueueStore<TInput extends ContractShape>(path:
|
|
|
75
76
|
* })
|
|
76
77
|
*
|
|
77
78
|
* const doubled = await worker.enqueue(21) // 42, computed on a worker thread
|
|
78
|
-
* worker.destroy() // terminates every thread
|
|
79
|
+
* await worker.destroy() // terminates every thread
|
|
79
80
|
* ```
|
|
80
81
|
*/
|
|
81
82
|
export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOptions<TInput, TResult>): WorkerInterface<TInput, TResult>;
|
|
@@ -88,15 +89,15 @@ export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOpt
|
|
|
88
89
|
* that id: a success `value` is narrowed through `result` (a value that fails the guard
|
|
89
90
|
* rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.
|
|
90
91
|
* A thread that ALREADY died rejects synchronously at entry from the latched
|
|
91
|
-
* {@link NodeThread.death} — its death events fired before this dispatch existed
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* job rejects.
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
92
|
+
* {@link NodeThread.death} — its death events fired before this dispatch existed and will
|
|
93
|
+
* never fire again, so waiting on the listeners below would dangle forever; the latch makes
|
|
94
|
+
* death total across every event ordering. If the thread `error`s / `exit`s mid-flight it is
|
|
95
|
+
* marked dead and the
|
|
96
|
+
* job rejects. An inbound `messageerror` also evicts and terminates the thread before
|
|
97
|
+
* rejection. On `execution.signal` abort it contains the cooperative `abort` post,
|
|
98
|
+
* evicts the thread, and observes `terminate()` settlement because CPU-bound work cannot
|
|
99
|
+
* honour the signal; the freed pool slot then gets a fresh thread. Every per-job listener
|
|
100
|
+
* (`message` / `messageerror` / `error` / `exit` / `abort`) is removed on settle.
|
|
100
101
|
*
|
|
101
102
|
* @typeParam TResult - The reply type the `result` guard narrows to
|
|
102
103
|
* @param thread - The leased thread to run the job on
|
|
@@ -107,23 +108,6 @@ export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOpt
|
|
|
107
108
|
*/
|
|
108
109
|
export declare function dispatch<TResult>(thread: NodeThread, input: unknown, execution: QueueExecution, result: Guard<TResult>): Promise<TResult>;
|
|
109
110
|
|
|
110
|
-
/**
|
|
111
|
-
* A runtime type predicate used to narrow a wire payload with no assertion.
|
|
112
|
-
*
|
|
113
|
-
* @remarks
|
|
114
|
-
* Mirrors the core `Guard<T>` (a total `(value: unknown) => value is T` predicate,
|
|
115
|
-
* AGENTS §14) but is re-declared here so the server workers surface is self-describing
|
|
116
|
-
* and the worker-side `serve.ts` — which may not import from `@src/core` (it loads as
|
|
117
|
-
* raw `.ts` inside a spawned thread) — shares the same vocabulary. A `Guard` NEVER
|
|
118
|
-
* throws; adversarial input returns `false`. It is the zero-`as` bridge across the
|
|
119
|
-
* structured-clone boundary: the main side narrows each reply value through the
|
|
120
|
-
* `result` guard and the input through the `input` guard, so a generic `TInput` /
|
|
121
|
-
* `TResult` is reconstructed by validation rather than asserted.
|
|
122
|
-
*
|
|
123
|
-
* @typeParam T - The type a value is narrowed to when the predicate holds
|
|
124
|
-
*/
|
|
125
|
-
export declare type Guard<T> = (value: unknown) => value is T;
|
|
126
|
-
|
|
127
111
|
/**
|
|
128
112
|
* Narrow an inbound `message` to a {@link Reply} for a given job `id` — no assertion.
|
|
129
113
|
*
|
|
@@ -142,16 +126,16 @@ export declare function isReply(value: unknown, id: string): value is Reply;
|
|
|
142
126
|
* {@link createNodeWorker} leases per job.
|
|
143
127
|
*
|
|
144
128
|
* @remarks
|
|
145
|
-
* `alive` starts `true` and flips to `false`
|
|
146
|
-
* is evicted on abort; the pool's `validate` reads
|
|
129
|
+
* `alive` starts `true` and flips to `false` when the thread `error`s, reports a
|
|
130
|
+
* `messageerror`, exits, or is evicted on abort; the pool's `validate` reads
|
|
131
|
+
* `alive && worker.threadId > 0`, so a
|
|
147
132
|
* dead thread is destroyed and replaced rather than reused. `death` LATCHES the first
|
|
148
|
-
* terminal event (`error`
|
|
133
|
+
* terminal event (`error` / `messageerror`, or a synthesized error on `exit`) — the death-signal
|
|
149
134
|
* record a `dispatch` checks at entry, so a job dispatched AFTER the thread died (its
|
|
150
135
|
* death events already fired and will never fire again) rejects immediately instead of
|
|
151
|
-
* awaiting events that already happened.
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* the latch is what makes that ordering safe. `worker` is the underlying
|
|
136
|
+
* awaiting events that already happened. A thread can become terminal before the readiness
|
|
137
|
+
* promise continuation attaches dispatch listeners; the latch is what makes that ordering
|
|
138
|
+
* safe. `worker` is the underlying
|
|
155
139
|
* `node:worker_threads` thread (its `postMessage` / `terminate` drive the protocol).
|
|
156
140
|
*/
|
|
157
141
|
export declare interface NodeThread {
|
|
@@ -165,8 +149,9 @@ export declare interface NodeThread {
|
|
|
165
149
|
*
|
|
166
150
|
* @remarks
|
|
167
151
|
* - `script` — the worker module each pooled thread runs; its module must call
|
|
168
|
-
* `serveWorker(...)`.
|
|
169
|
-
*
|
|
152
|
+
* `serveWorker(...)`. Raw TypeScript is unflagged on Node 22.18+ and Node 23.6+;
|
|
153
|
+
* Node 22.12–22.17 and Node 23.0–23.5 require `--experimental-strip-types`. A built
|
|
154
|
+
* `.js` / `.mjs` script remains an alternative across supported Node versions.
|
|
170
155
|
* - `input` — narrows the work payload BEFORE it crosses the structured-clone boundary
|
|
171
156
|
* (fail-fast) and supplies the `TInput` inference, so call sites need no type argument.
|
|
172
157
|
* - `result` — narrows every reply value coming back from a thread; an invalid reply
|
|
@@ -174,7 +159,8 @@ export declare interface NodeThread {
|
|
|
174
159
|
* - `workerData` — opaque data cloned to every thread once at spawn (read there via
|
|
175
160
|
* `serveWorker`'s host `workerData`); must be structured-cloneable.
|
|
176
161
|
* - `concurrency` — the maximum jobs in flight at once; the thread pool's `max` matches
|
|
177
|
-
* it, so at most this many threads exist. Defaults to `1
|
|
162
|
+
* it, so at most this many threads exist. Defaults to `1` and must be a positive safe
|
|
163
|
+
* integer, as validated by the underlying queue.
|
|
178
164
|
* - `retries` — the default extra attempts per job on failure / timeout; defaults to `0`.
|
|
179
165
|
* - `timeout` — the default per-attempt deadline in milliseconds; defaults to none.
|
|
180
166
|
* - `store` — durable backing for outstanding jobs (survives a restart; `restore()`
|
|
@@ -202,11 +188,11 @@ export declare interface NodeWorkerOptions<TInput, TResult> {
|
|
|
202
188
|
* @remarks
|
|
203
189
|
* Internal plumbing rather than public call surface, but centralized here per AGENTS §5 (an
|
|
204
190
|
* impl file holds only its class / functions). A reply is a discriminated union on `ok`: a
|
|
205
|
-
* `true` carries any opaque `value` (narrowed at the boundary by the `result`
|
|
191
|
+
* `true` carries any opaque `value` (narrowed at the boundary by the `result` guard,
|
|
206
192
|
* with no `as`); a `false` carries a string `error`. The worker-side `serve.ts` cannot import
|
|
207
193
|
* this (it loads as raw source in a spawned thread, AGENTS §5 exception) and posts the same
|
|
208
|
-
* shape structurally. The `id` ties a reply to its job
|
|
209
|
-
*
|
|
194
|
+
* shape structurally. The `id` ties a reply to its job: id-less / foreign-id chatter is ignored,
|
|
195
|
+
* while a matching-id malformed envelope taints the thread and causes dispatch to terminate it.
|
|
210
196
|
*/
|
|
211
197
|
export declare type Reply = {
|
|
212
198
|
readonly id: string;
|
|
@@ -226,7 +212,10 @@ export declare type Reply = {
|
|
|
226
212
|
* run/abort protocol: a `run` message narrows its `input` through `options.input` (an
|
|
227
213
|
* invalid payload replies with an error envelope, never running the handler), then runs
|
|
228
214
|
* `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or
|
|
229
|
-
* `{ id, ok: false, error }` on throw.
|
|
215
|
+
* `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a
|
|
216
|
+
* success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
|
|
217
|
+
* fails, the parent port closes so the main side observes thread exit instead of waiting forever.
|
|
218
|
+
* Each in-flight job has its own `AbortController`,
|
|
230
219
|
* so an `abort` message for that id fires the handler's `signal` (cooperative — the main
|
|
231
220
|
* side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
|
|
232
221
|
* Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
|
|
@@ -239,7 +228,7 @@ export declare type Reply = {
|
|
|
239
228
|
* @example
|
|
240
229
|
* ```ts
|
|
241
230
|
* // double.ts — a worker script
|
|
242
|
-
* import { serveWorker } from '@
|
|
231
|
+
* import { serveWorker } from '@orkestrel/worker/server'
|
|
243
232
|
*
|
|
244
233
|
* serveWorker<number, number>({
|
|
245
234
|
* input: (value): value is number => typeof value === 'number',
|
|
@@ -281,11 +270,11 @@ export declare interface ServeWorkerOptions<TInput, TResult> {
|
|
|
281
270
|
* listeners that flip `alive` to `false` AND latch the first terminal event on
|
|
282
271
|
* {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via
|
|
283
272
|
* its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a
|
|
284
|
-
* dispatch that attaches AFTER the death (via the latch).
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
273
|
+
* dispatch that attaches AFTER the death (via the latch). A `messageerror` is terminal too,
|
|
274
|
+
* so a thread whose inbound payload could not be deserialized is never reused. The latch
|
|
275
|
+
* closes a real race: a thread can become terminal before the readiness promise continuation
|
|
276
|
+
* hands it to `dispatch`, leaving no future death event for that dispatch to observe. Without
|
|
277
|
+
* the latch, that job would wait forever. The pool's `create` hook calls this.
|
|
289
278
|
*
|
|
290
279
|
* @param script - The worker module each thread runs (must call `serveWorker`)
|
|
291
280
|
* @param workerData - Opaque, structured-cloneable data handed to the thread at spawn
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContractShape } from '@orkestrel/contract';
|
|
2
|
+
import { Guard } from '@orkestrel/contract';
|
|
2
3
|
import { Infer } from '@orkestrel/contract';
|
|
3
4
|
import { QueueExecution } from '@orkestrel/queue';
|
|
4
5
|
import { QueueStoreInterface } from '@orkestrel/queue';
|
|
@@ -25,8 +26,8 @@ import { WorkerInterface } from '../core/index.ts';
|
|
|
25
26
|
*
|
|
26
27
|
* @example
|
|
27
28
|
* ```ts
|
|
28
|
-
* import { stringShape } from '@
|
|
29
|
-
* import { createJSONQueueStore } from '@
|
|
29
|
+
* import { stringShape } from '@orkestrel/contract'
|
|
30
|
+
* import { createJSONQueueStore } from '@orkestrel/worker/server'
|
|
30
31
|
*
|
|
31
32
|
* const store = createJSONQueueStore('data/queue.json', stringShape())
|
|
32
33
|
* await store.save({ id: 'job-1', input: 'https://example.com', attempts: 0 })
|
|
@@ -65,7 +66,7 @@ export declare function createJSONQueueStore<TInput extends ContractShape>(path:
|
|
|
65
66
|
*
|
|
66
67
|
* @example
|
|
67
68
|
* ```ts
|
|
68
|
-
* import { createNodeWorker } from '@
|
|
69
|
+
* import { createNodeWorker } from '@orkestrel/worker/server'
|
|
69
70
|
*
|
|
70
71
|
* const worker = createNodeWorker({
|
|
71
72
|
* script: new URL('./double.js', import.meta.url),
|
|
@@ -75,7 +76,7 @@ export declare function createJSONQueueStore<TInput extends ContractShape>(path:
|
|
|
75
76
|
* })
|
|
76
77
|
*
|
|
77
78
|
* const doubled = await worker.enqueue(21) // 42, computed on a worker thread
|
|
78
|
-
* worker.destroy() // terminates every thread
|
|
79
|
+
* await worker.destroy() // terminates every thread
|
|
79
80
|
* ```
|
|
80
81
|
*/
|
|
81
82
|
export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOptions<TInput, TResult>): WorkerInterface<TInput, TResult>;
|
|
@@ -88,15 +89,15 @@ export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOpt
|
|
|
88
89
|
* that id: a success `value` is narrowed through `result` (a value that fails the guard
|
|
89
90
|
* rejects — the zero-`as` type bridge), a failure rejects with the thread's error string.
|
|
90
91
|
* A thread that ALREADY died rejects synchronously at entry from the latched
|
|
91
|
-
* {@link NodeThread.death} — its death events fired before this dispatch existed
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* job rejects.
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
92
|
+
* {@link NodeThread.death} — its death events fired before this dispatch existed and will
|
|
93
|
+
* never fire again, so waiting on the listeners below would dangle forever; the latch makes
|
|
94
|
+
* death total across every event ordering. If the thread `error`s / `exit`s mid-flight it is
|
|
95
|
+
* marked dead and the
|
|
96
|
+
* job rejects. An inbound `messageerror` also evicts and terminates the thread before
|
|
97
|
+
* rejection. On `execution.signal` abort it contains the cooperative `abort` post,
|
|
98
|
+
* evicts the thread, and observes `terminate()` settlement because CPU-bound work cannot
|
|
99
|
+
* honour the signal; the freed pool slot then gets a fresh thread. Every per-job listener
|
|
100
|
+
* (`message` / `messageerror` / `error` / `exit` / `abort`) is removed on settle.
|
|
100
101
|
*
|
|
101
102
|
* @typeParam TResult - The reply type the `result` guard narrows to
|
|
102
103
|
* @param thread - The leased thread to run the job on
|
|
@@ -107,23 +108,6 @@ export declare function createNodeWorker<TInput, TResult>(options: NodeWorkerOpt
|
|
|
107
108
|
*/
|
|
108
109
|
export declare function dispatch<TResult>(thread: NodeThread, input: unknown, execution: QueueExecution, result: Guard<TResult>): Promise<TResult>;
|
|
109
110
|
|
|
110
|
-
/**
|
|
111
|
-
* A runtime type predicate used to narrow a wire payload with no assertion.
|
|
112
|
-
*
|
|
113
|
-
* @remarks
|
|
114
|
-
* Mirrors the core `Guard<T>` (a total `(value: unknown) => value is T` predicate,
|
|
115
|
-
* AGENTS §14) but is re-declared here so the server workers surface is self-describing
|
|
116
|
-
* and the worker-side `serve.ts` — which may not import from `@src/core` (it loads as
|
|
117
|
-
* raw `.ts` inside a spawned thread) — shares the same vocabulary. A `Guard` NEVER
|
|
118
|
-
* throws; adversarial input returns `false`. It is the zero-`as` bridge across the
|
|
119
|
-
* structured-clone boundary: the main side narrows each reply value through the
|
|
120
|
-
* `result` guard and the input through the `input` guard, so a generic `TInput` /
|
|
121
|
-
* `TResult` is reconstructed by validation rather than asserted.
|
|
122
|
-
*
|
|
123
|
-
* @typeParam T - The type a value is narrowed to when the predicate holds
|
|
124
|
-
*/
|
|
125
|
-
export declare type Guard<T> = (value: unknown) => value is T;
|
|
126
|
-
|
|
127
111
|
/**
|
|
128
112
|
* Narrow an inbound `message` to a {@link Reply} for a given job `id` — no assertion.
|
|
129
113
|
*
|
|
@@ -142,16 +126,16 @@ export declare function isReply(value: unknown, id: string): value is Reply;
|
|
|
142
126
|
* {@link createNodeWorker} leases per job.
|
|
143
127
|
*
|
|
144
128
|
* @remarks
|
|
145
|
-
* `alive` starts `true` and flips to `false`
|
|
146
|
-
* is evicted on abort; the pool's `validate` reads
|
|
129
|
+
* `alive` starts `true` and flips to `false` when the thread `error`s, reports a
|
|
130
|
+
* `messageerror`, exits, or is evicted on abort; the pool's `validate` reads
|
|
131
|
+
* `alive && worker.threadId > 0`, so a
|
|
147
132
|
* dead thread is destroyed and replaced rather than reused. `death` LATCHES the first
|
|
148
|
-
* terminal event (`error`
|
|
133
|
+
* terminal event (`error` / `messageerror`, or a synthesized error on `exit`) — the death-signal
|
|
149
134
|
* record a `dispatch` checks at entry, so a job dispatched AFTER the thread died (its
|
|
150
135
|
* death events already fired and will never fire again) rejects immediately instead of
|
|
151
|
-
* awaiting events that already happened.
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* the latch is what makes that ordering safe. `worker` is the underlying
|
|
136
|
+
* awaiting events that already happened. A thread can become terminal before the readiness
|
|
137
|
+
* promise continuation attaches dispatch listeners; the latch is what makes that ordering
|
|
138
|
+
* safe. `worker` is the underlying
|
|
155
139
|
* `node:worker_threads` thread (its `postMessage` / `terminate` drive the protocol).
|
|
156
140
|
*/
|
|
157
141
|
export declare interface NodeThread {
|
|
@@ -165,8 +149,9 @@ export declare interface NodeThread {
|
|
|
165
149
|
*
|
|
166
150
|
* @remarks
|
|
167
151
|
* - `script` — the worker module each pooled thread runs; its module must call
|
|
168
|
-
* `serveWorker(...)`.
|
|
169
|
-
*
|
|
152
|
+
* `serveWorker(...)`. Raw TypeScript is unflagged on Node 22.18+ and Node 23.6+;
|
|
153
|
+
* Node 22.12–22.17 and Node 23.0–23.5 require `--experimental-strip-types`. A built
|
|
154
|
+
* `.js` / `.mjs` script remains an alternative across supported Node versions.
|
|
170
155
|
* - `input` — narrows the work payload BEFORE it crosses the structured-clone boundary
|
|
171
156
|
* (fail-fast) and supplies the `TInput` inference, so call sites need no type argument.
|
|
172
157
|
* - `result` — narrows every reply value coming back from a thread; an invalid reply
|
|
@@ -174,7 +159,8 @@ export declare interface NodeThread {
|
|
|
174
159
|
* - `workerData` — opaque data cloned to every thread once at spawn (read there via
|
|
175
160
|
* `serveWorker`'s host `workerData`); must be structured-cloneable.
|
|
176
161
|
* - `concurrency` — the maximum jobs in flight at once; the thread pool's `max` matches
|
|
177
|
-
* it, so at most this many threads exist. Defaults to `1
|
|
162
|
+
* it, so at most this many threads exist. Defaults to `1` and must be a positive safe
|
|
163
|
+
* integer, as validated by the underlying queue.
|
|
178
164
|
* - `retries` — the default extra attempts per job on failure / timeout; defaults to `0`.
|
|
179
165
|
* - `timeout` — the default per-attempt deadline in milliseconds; defaults to none.
|
|
180
166
|
* - `store` — durable backing for outstanding jobs (survives a restart; `restore()`
|
|
@@ -202,11 +188,11 @@ export declare interface NodeWorkerOptions<TInput, TResult> {
|
|
|
202
188
|
* @remarks
|
|
203
189
|
* Internal plumbing rather than public call surface, but centralized here per AGENTS §5 (an
|
|
204
190
|
* impl file holds only its class / functions). A reply is a discriminated union on `ok`: a
|
|
205
|
-
* `true` carries any opaque `value` (narrowed at the boundary by the `result`
|
|
191
|
+
* `true` carries any opaque `value` (narrowed at the boundary by the `result` guard,
|
|
206
192
|
* with no `as`); a `false` carries a string `error`. The worker-side `serve.ts` cannot import
|
|
207
193
|
* this (it loads as raw source in a spawned thread, AGENTS §5 exception) and posts the same
|
|
208
|
-
* shape structurally. The `id` ties a reply to its job
|
|
209
|
-
*
|
|
194
|
+
* shape structurally. The `id` ties a reply to its job: id-less / foreign-id chatter is ignored,
|
|
195
|
+
* while a matching-id malformed envelope taints the thread and causes dispatch to terminate it.
|
|
210
196
|
*/
|
|
211
197
|
export declare type Reply = {
|
|
212
198
|
readonly id: string;
|
|
@@ -226,7 +212,10 @@ export declare type Reply = {
|
|
|
226
212
|
* run/abort protocol: a `run` message narrows its `input` through `options.input` (an
|
|
227
213
|
* invalid payload replies with an error envelope, never running the handler), then runs
|
|
228
214
|
* `options.handler(input, { signal })` and replies `{ id, ok: true, value }` on success or
|
|
229
|
-
* `{ id, ok: false, error }` on throw.
|
|
215
|
+
* `{ id, ok: false, error }` on throw. Input-guard throws use the same failure envelope. If a
|
|
216
|
+
* success value cannot be cloned, the post is retried as a clone-safe failure; if that post also
|
|
217
|
+
* fails, the parent port closes so the main side observes thread exit instead of waiting forever.
|
|
218
|
+
* Each in-flight job has its own `AbortController`,
|
|
230
219
|
* so an `abort` message for that id fires the handler's `signal` (cooperative — the main
|
|
231
220
|
* side ALSO terminates the thread, so a handler that ignores its signal is still stopped).
|
|
232
221
|
* Every inbound message is narrowed with the inlined guards — no `as`. On the main thread
|
|
@@ -239,7 +228,7 @@ export declare type Reply = {
|
|
|
239
228
|
* @example
|
|
240
229
|
* ```ts
|
|
241
230
|
* // double.ts — a worker script
|
|
242
|
-
* import { serveWorker } from '@
|
|
231
|
+
* import { serveWorker } from '@orkestrel/worker/server'
|
|
243
232
|
*
|
|
244
233
|
* serveWorker<number, number>({
|
|
245
234
|
* input: (value): value is number => typeof value === 'number',
|
|
@@ -281,11 +270,11 @@ export declare interface ServeWorkerOptions<TInput, TResult> {
|
|
|
281
270
|
* listeners that flip `alive` to `false` AND latch the first terminal event on
|
|
282
271
|
* {@link NodeThread.death}: a crash is observable to an in-flight {@link dispatch} (via
|
|
283
272
|
* its own listeners), to the pool's `validate` (via `alive`), and — crucially — to a
|
|
284
|
-
* dispatch that attaches AFTER the death (via the latch).
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
273
|
+
* dispatch that attaches AFTER the death (via the latch). A `messageerror` is terminal too,
|
|
274
|
+
* so a thread whose inbound payload could not be deserialized is never reused. The latch
|
|
275
|
+
* closes a real race: a thread can become terminal before the readiness promise continuation
|
|
276
|
+
* hands it to `dispatch`, leaving no future death event for that dispatch to observe. Without
|
|
277
|
+
* the latch, that job would wait forever. The pool's `create` hook calls this.
|
|
289
278
|
*
|
|
290
279
|
* @param script - The worker module each thread runs (must call `serveWorker`)
|
|
291
280
|
* @param workerData - Opaque, structured-cloneable data handed to the thread at spawn
|