nmtjs 0.16.0 → 0.17.0-beta.1

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.
Files changed (224) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +41 -1
  3. package/dist/index.d.ts +22 -121
  4. package/dist/index.js +24 -53
  5. package/dist/index.js.map +1 -1
  6. package/package.json +11 -63
  7. package/src/index.ts +71 -91
  8. package/bin/cli.js +0 -2
  9. package/dist/cli.d.ts +0 -1
  10. package/dist/cli.js +0 -137
  11. package/dist/cli.js.map +0 -1
  12. package/dist/codegen.d.ts +0 -5
  13. package/dist/codegen.js +0 -18
  14. package/dist/codegen.js.map +0 -1
  15. package/dist/config.d.ts +0 -47
  16. package/dist/config.js +0 -16
  17. package/dist/config.js.map +0 -1
  18. package/dist/entrypoints/cli.d.ts +0 -1
  19. package/dist/entrypoints/cli.js +0 -12
  20. package/dist/entrypoints/cli.js.map +0 -1
  21. package/dist/entrypoints/main.d.ts +0 -5
  22. package/dist/entrypoints/main.js +0 -248
  23. package/dist/entrypoints/main.js.map +0 -1
  24. package/dist/entrypoints/thread.d.ts +0 -16
  25. package/dist/entrypoints/thread.js +0 -182
  26. package/dist/entrypoints/thread.js.map +0 -1
  27. package/dist/entrypoints/worker.d.ts +0 -9
  28. package/dist/entrypoints/worker.js +0 -143
  29. package/dist/entrypoints/worker.js.map +0 -1
  30. package/dist/resolver.d.ts +0 -2
  31. package/dist/resolver.js +0 -7
  32. package/dist/resolver.js.map +0 -1
  33. package/dist/runtime/application/index.d.ts +0 -1
  34. package/dist/runtime/application/index.js +0 -2
  35. package/dist/runtime/application/index.js.map +0 -1
  36. package/dist/runtime/constants.d.ts +0 -8
  37. package/dist/runtime/constants.js +0 -5
  38. package/dist/runtime/constants.js.map +0 -1
  39. package/dist/runtime/enums.d.ts +0 -10
  40. package/dist/runtime/enums.js +0 -13
  41. package/dist/runtime/enums.js.map +0 -1
  42. package/dist/runtime/hooks.d.ts +0 -1
  43. package/dist/runtime/hooks.js +0 -2
  44. package/dist/runtime/hooks.js.map +0 -1
  45. package/dist/runtime/index.d.ts +0 -28
  46. package/dist/runtime/index.js +0 -29
  47. package/dist/runtime/index.js.map +0 -1
  48. package/dist/runtime/injectables.d.ts +0 -28
  49. package/dist/runtime/injectables.js +0 -24
  50. package/dist/runtime/injectables.js.map +0 -1
  51. package/dist/runtime/jobs/job.d.ts +0 -146
  52. package/dist/runtime/jobs/job.js +0 -89
  53. package/dist/runtime/jobs/job.js.map +0 -1
  54. package/dist/runtime/jobs/manager.d.ts +0 -131
  55. package/dist/runtime/jobs/manager.js +0 -309
  56. package/dist/runtime/jobs/manager.js.map +0 -1
  57. package/dist/runtime/jobs/router.d.ts +0 -296
  58. package/dist/runtime/jobs/router.js +0 -458
  59. package/dist/runtime/jobs/router.js.map +0 -1
  60. package/dist/runtime/jobs/runner.d.ts +0 -103
  61. package/dist/runtime/jobs/runner.js +0 -425
  62. package/dist/runtime/jobs/runner.js.map +0 -1
  63. package/dist/runtime/jobs/step.d.ts +0 -26
  64. package/dist/runtime/jobs/step.js +0 -18
  65. package/dist/runtime/jobs/step.js.map +0 -1
  66. package/dist/runtime/jobs/types.d.ts +0 -86
  67. package/dist/runtime/jobs/types.js +0 -5
  68. package/dist/runtime/jobs/types.js.map +0 -1
  69. package/dist/runtime/metrics/metric.d.ts +0 -8
  70. package/dist/runtime/metrics/metric.js +0 -11
  71. package/dist/runtime/metrics/metric.js.map +0 -1
  72. package/dist/runtime/metrics/registry.d.ts +0 -3
  73. package/dist/runtime/metrics/registry.js +0 -5
  74. package/dist/runtime/metrics/registry.js.map +0 -1
  75. package/dist/runtime/metrics/server.d.ts +0 -7
  76. package/dist/runtime/metrics/server.js +0 -53
  77. package/dist/runtime/metrics/server.js.map +0 -1
  78. package/dist/runtime/plugin.d.ts +0 -2
  79. package/dist/runtime/plugin.js +0 -2
  80. package/dist/runtime/plugin.js.map +0 -1
  81. package/dist/runtime/runtime.d.ts +0 -27
  82. package/dist/runtime/runtime.js +0 -77
  83. package/dist/runtime/runtime.js.map +0 -1
  84. package/dist/runtime/scheduler/index.d.ts +0 -22
  85. package/dist/runtime/scheduler/index.js +0 -20
  86. package/dist/runtime/scheduler/index.js.map +0 -1
  87. package/dist/runtime/server/applications.d.ts +0 -91
  88. package/dist/runtime/server/applications.js +0 -171
  89. package/dist/runtime/server/applications.js.map +0 -1
  90. package/dist/runtime/server/config.d.ts +0 -148
  91. package/dist/runtime/server/config.js +0 -33
  92. package/dist/runtime/server/config.js.map +0 -1
  93. package/dist/runtime/server/environment.d.ts +0 -43
  94. package/dist/runtime/server/environment.js +0 -33
  95. package/dist/runtime/server/environment.js.map +0 -1
  96. package/dist/runtime/server/error-policy.d.ts +0 -62
  97. package/dist/runtime/server/error-policy.js +0 -73
  98. package/dist/runtime/server/error-policy.js.map +0 -1
  99. package/dist/runtime/server/hmr-coordinator.d.ts +0 -37
  100. package/dist/runtime/server/hmr-coordinator.js +0 -94
  101. package/dist/runtime/server/hmr-coordinator.js.map +0 -1
  102. package/dist/runtime/server/jobs.d.ts +0 -54
  103. package/dist/runtime/server/jobs.js +0 -176
  104. package/dist/runtime/server/jobs.js.map +0 -1
  105. package/dist/runtime/server/lifecycle.d.ts +0 -85
  106. package/dist/runtime/server/lifecycle.js +0 -177
  107. package/dist/runtime/server/lifecycle.js.map +0 -1
  108. package/dist/runtime/server/managed-worker.d.ts +0 -158
  109. package/dist/runtime/server/managed-worker.js +0 -420
  110. package/dist/runtime/server/managed-worker.js.map +0 -1
  111. package/dist/runtime/server/pool-manager.d.ts +0 -85
  112. package/dist/runtime/server/pool-manager.js +0 -129
  113. package/dist/runtime/server/pool-manager.js.map +0 -1
  114. package/dist/runtime/server/proxy.d.ts +0 -21
  115. package/dist/runtime/server/proxy.js +0 -76
  116. package/dist/runtime/server/proxy.js.map +0 -1
  117. package/dist/runtime/server/server.d.ts +0 -60
  118. package/dist/runtime/server/server.js +0 -163
  119. package/dist/runtime/server/server.js.map +0 -1
  120. package/dist/runtime/server/types.d.ts +0 -31
  121. package/dist/runtime/server/types.js +0 -2
  122. package/dist/runtime/server/types.js.map +0 -1
  123. package/dist/runtime/server/worker-pool.d.ts +0 -121
  124. package/dist/runtime/server/worker-pool.js +0 -171
  125. package/dist/runtime/server/worker-pool.js.map +0 -1
  126. package/dist/runtime/store/index.d.ts +0 -3
  127. package/dist/runtime/store/index.js +0 -23
  128. package/dist/runtime/store/index.js.map +0 -1
  129. package/dist/runtime/subscription/manager.d.ts +0 -49
  130. package/dist/runtime/subscription/manager.js +0 -170
  131. package/dist/runtime/subscription/manager.js.map +0 -1
  132. package/dist/runtime/subscription/redis.d.ts +0 -18
  133. package/dist/runtime/subscription/redis.js +0 -120
  134. package/dist/runtime/subscription/redis.js.map +0 -1
  135. package/dist/runtime/types.d.ts +0 -95
  136. package/dist/runtime/types.js +0 -2
  137. package/dist/runtime/types.js.map +0 -1
  138. package/dist/runtime/workers/application.d.ts +0 -29
  139. package/dist/runtime/workers/application.js +0 -77
  140. package/dist/runtime/workers/application.js.map +0 -1
  141. package/dist/runtime/workers/base.d.ts +0 -16
  142. package/dist/runtime/workers/base.js +0 -46
  143. package/dist/runtime/workers/base.js.map +0 -1
  144. package/dist/runtime/workers/cli.d.ts +0 -1
  145. package/dist/runtime/workers/cli.js +0 -2
  146. package/dist/runtime/workers/cli.js.map +0 -1
  147. package/dist/runtime/workers/job.d.ts +0 -20
  148. package/dist/runtime/workers/job.js +0 -170
  149. package/dist/runtime/workers/job.js.map +0 -1
  150. package/dist/vite/builder.d.ts +0 -5
  151. package/dist/vite/builder.js +0 -79
  152. package/dist/vite/builder.js.map +0 -1
  153. package/dist/vite/config.d.ts +0 -28
  154. package/dist/vite/config.js +0 -22
  155. package/dist/vite/config.js.map +0 -1
  156. package/dist/vite/plugins.d.ts +0 -2
  157. package/dist/vite/plugins.js +0 -43
  158. package/dist/vite/plugins.js.map +0 -1
  159. package/dist/vite/runners/worker.d.ts +0 -3
  160. package/dist/vite/runners/worker.js +0 -34
  161. package/dist/vite/runners/worker.js.map +0 -1
  162. package/dist/vite/server.d.ts +0 -3
  163. package/dist/vite/server.js +0 -36
  164. package/dist/vite/server.js.map +0 -1
  165. package/dist/vite/servers/main.d.ts +0 -8
  166. package/dist/vite/servers/main.js +0 -24
  167. package/dist/vite/servers/main.js.map +0 -1
  168. package/dist/vite/servers/worker.d.ts +0 -13
  169. package/dist/vite/servers/worker.js +0 -124
  170. package/dist/vite/servers/worker.js.map +0 -1
  171. package/src/cli.ts +0 -162
  172. package/src/codegen.ts +0 -30
  173. package/src/config.ts +0 -69
  174. package/src/entrypoints/cli.ts +0 -13
  175. package/src/entrypoints/main.ts +0 -317
  176. package/src/entrypoints/thread.ts +0 -223
  177. package/src/entrypoints/worker.ts +0 -174
  178. package/src/resolver.ts +0 -7
  179. package/src/runtime/application/index.ts +0 -1
  180. package/src/runtime/constants.ts +0 -13
  181. package/src/runtime/enums.ts +0 -12
  182. package/src/runtime/hooks.ts +0 -1
  183. package/src/runtime/index.ts +0 -29
  184. package/src/runtime/injectables.ts +0 -72
  185. package/src/runtime/jobs/job.ts +0 -467
  186. package/src/runtime/jobs/manager.ts +0 -454
  187. package/src/runtime/jobs/router.ts +0 -967
  188. package/src/runtime/jobs/runner.ts +0 -583
  189. package/src/runtime/jobs/step.ts +0 -66
  190. package/src/runtime/jobs/types.ts +0 -116
  191. package/src/runtime/metrics/metric.ts +0 -18
  192. package/src/runtime/metrics/registry.ts +0 -11
  193. package/src/runtime/metrics/server.ts +0 -69
  194. package/src/runtime/plugin.ts +0 -2
  195. package/src/runtime/runtime.ts +0 -103
  196. package/src/runtime/scheduler/index.ts +0 -39
  197. package/src/runtime/server/applications.ts +0 -264
  198. package/src/runtime/server/config.ts +0 -199
  199. package/src/runtime/server/environment.ts +0 -71
  200. package/src/runtime/server/error-policy.ts +0 -132
  201. package/src/runtime/server/hmr-coordinator.ts +0 -116
  202. package/src/runtime/server/jobs.ts +0 -266
  203. package/src/runtime/server/lifecycle.ts +0 -234
  204. package/src/runtime/server/managed-worker.ts +0 -546
  205. package/src/runtime/server/pool-manager.ts +0 -187
  206. package/src/runtime/server/proxy.ts +0 -119
  207. package/src/runtime/server/server.ts +0 -218
  208. package/src/runtime/server/types.ts +0 -32
  209. package/src/runtime/server/worker-pool.ts +0 -275
  210. package/src/runtime/store/index.ts +0 -30
  211. package/src/runtime/subscription/manager.ts +0 -310
  212. package/src/runtime/subscription/redis.ts +0 -157
  213. package/src/runtime/types.ts +0 -84
  214. package/src/runtime/workers/application.ts +0 -133
  215. package/src/runtime/workers/base.ts +0 -71
  216. package/src/runtime/workers/cli.ts +0 -0
  217. package/src/runtime/workers/job.ts +0 -142
  218. package/src/vite/builder.ts +0 -94
  219. package/src/vite/config.ts +0 -45
  220. package/src/vite/plugins.ts +0 -53
  221. package/src/vite/runners/worker.ts +0 -55
  222. package/src/vite/server.ts +0 -55
  223. package/src/vite/servers/main.ts +0 -41
  224. package/src/vite/servers/worker.ts +0 -177
@@ -1,546 +0,0 @@
1
- import type { MessagePort, WorkerOptions } from 'node:worker_threads'
2
- import EventEmitter, { once } from 'node:events'
3
- import { MessageChannel, Worker } from 'node:worker_threads'
4
-
5
- import type { Logger } from '@nmtjs/core'
6
-
7
- import type { WorkerType } from '../enums.ts'
8
- import type {
9
- JobTaskResult,
10
- ServerPortMessageTypes,
11
- ThreadErrorMessage,
12
- ThreadPortMessage,
13
- ThreadPortMessageTypes,
14
- WorkerJobTask,
15
- WorkerThreadError,
16
- } from '../types.ts'
17
- import type { ErrorPolicy, WorkerErrorContext } from './error-policy.ts'
18
-
19
- const omitExecArgv = ['--expose-gc']
20
-
21
- /**
22
- * States in the worker state machine.
23
- */
24
- export type WorkerState =
25
- | 'idle'
26
- | 'starting'
27
- | 'ready'
28
- | 'error'
29
- | 'restarting'
30
- | 'stopping'
31
- | 'stopped'
32
-
33
- /**
34
- * Context tracked for each managed worker.
35
- */
36
- export interface ManagedWorkerContext {
37
- consecutiveFailures: number
38
- totalFailures: number
39
- lastStableTime: number | null
40
- lastError: Error | null
41
- }
42
-
43
- /**
44
- * Configuration for creating a managed worker.
45
- */
46
- export interface ManagedWorkerConfig {
47
- id: string
48
- name: string
49
- index: number
50
- workerType: WorkerType
51
- path: string
52
- workerData?: any
53
- workerOptions?: Partial<WorkerOptions>
54
- onWorker?: (registration: { worker: Worker; vitePort?: MessagePort }) => void
55
- }
56
-
57
- /**
58
- * Events emitted by ManagedWorker.
59
- */
60
- export interface ManagedWorkerEvents {
61
- 'state-change': [from: WorkerState, to: WorkerState]
62
- ready: [hosts?: ThreadPortMessageTypes['ready']['hosts']]
63
- error: [error: WorkerThreadError]
64
- task: [data: ThreadPortMessageTypes['task']]
65
- }
66
-
67
- /**
68
- * Time window (ms) after which a worker is considered "stable".
69
- * Used to reset consecutive failure count.
70
- */
71
- const STABILITY_WINDOW_MS = 30_000
72
-
73
- /**
74
- * Worker startup timeout (ms).
75
- */
76
- const STARTUP_TIMEOUT_MS = 15_000
77
-
78
- /**
79
- * Worker stop timeout (ms).
80
- */
81
- const STOP_TIMEOUT_MS = 10_000
82
-
83
- /**
84
- * ManagedWorker wraps a Node.js Worker thread with:
85
- * - Explicit state machine
86
- * - Automatic restart with exponential backoff
87
- * - Error tracking and stability detection
88
- * - Integration with ErrorPolicy for restart decisions
89
- *
90
- * State Machine:
91
- * - idle: Initial state, worker not created
92
- * - starting: Worker created, waiting for ready message
93
- * - ready: Worker is running and healthy
94
- * - error: Worker crashed or failed to start
95
- * - restarting: Worker is scheduled for restart (waiting for delay)
96
- * - stopping: Worker is being terminated
97
- * - stopped: Worker has been terminated (terminal state)
98
- */
99
- export class ManagedWorker extends EventEmitter<ManagedWorkerEvents> {
100
- protected state: WorkerState = 'idle'
101
- protected ctx: ManagedWorkerContext = {
102
- consecutiveFailures: 0,
103
- totalFailures: 0,
104
- lastStableTime: null,
105
- lastError: null,
106
- }
107
- protected worker: Worker | null = null
108
- protected port: MessagePort | null = null
109
- protected vitePort: MessagePort | null = null
110
- protected restartTimer: ReturnType<typeof setTimeout> | null = null
111
- protected startPromise: Promise<void> | null = null
112
- protected pendingTaskIds = new Set<string>()
113
- protected logger: Logger
114
-
115
- constructor(
116
- readonly config: ManagedWorkerConfig,
117
- protected readonly policy: ErrorPolicy,
118
- logger: Logger,
119
- ) {
120
- super()
121
- this.logger = logger.child({
122
- component: 'ManagedWorker',
123
- workerId: config.id,
124
- })
125
- }
126
-
127
- /** Current worker state */
128
- get currentState(): WorkerState {
129
- return this.state
130
- }
131
-
132
- /** Whether the worker is healthy (ready state) */
133
- get isHealthy(): boolean {
134
- return this.state === 'ready'
135
- }
136
-
137
- /** Get a copy of the worker context */
138
- get context(): ManagedWorkerContext {
139
- return { ...this.ctx }
140
- }
141
-
142
- /**
143
- * Reset failure counts. Called when HMR update comes in to give the worker
144
- * a fresh start after code has been fixed.
145
- */
146
- resetFailureCount(): void {
147
- this.ctx.consecutiveFailures = 0
148
- this.ctx.lastError = null
149
- }
150
-
151
- /** Get the underlying Worker instance (if exists) */
152
- getWorker(): Worker | null {
153
- return this.worker
154
- }
155
-
156
- /**
157
- * Start the worker.
158
- * Can only be called from 'idle' or 'error' states.
159
- */
160
- async start(): Promise<void> {
161
- if (this.state === 'ready') return
162
- if (this.startPromise) return this.startPromise
163
- this.assertState('idle', 'error')
164
-
165
- this.startPromise = this.doStart()
166
- try {
167
- await this.startPromise
168
- } finally {
169
- this.startPromise = null
170
- }
171
- }
172
-
173
- /**
174
- * Stop the worker.
175
- * Can be called from any state except 'stopped'.
176
- * Clears any pending restart timers and rejects pending run() tasks.
177
- */
178
- async stop(): Promise<void> {
179
- this.clearRestartTimer()
180
-
181
- if (this.state === 'stopped') return
182
- this.transition('stopping')
183
-
184
- // Reject any pending run() calls that are waiting for task results.
185
- // Without this, callers of pool.run() would hang forever since the
186
- // port is about to be closed and task-* events will never fire.
187
- this.rejectPendingTasks()
188
-
189
- try {
190
- await this.terminateWorker()
191
- } finally {
192
- this.cleanup()
193
- this.transition('stopped')
194
- }
195
- }
196
-
197
- /**
198
- * Run a task on this worker (for job workers).
199
- * The worker must be in 'ready' state.
200
- */
201
- async run(task: WorkerJobTask): Promise<JobTaskResult> {
202
- if (this.state !== 'ready' || !this.port) {
203
- throw new Error('Worker is not ready to run tasks')
204
- }
205
-
206
- const id = crypto.randomUUID()
207
- this.pendingTaskIds.add(id)
208
- this.send('task', { id, task })
209
-
210
- try {
211
- const [result] = (await once(this, `task-${id}` as any)) as [
212
- JobTaskResult,
213
- ]
214
- return result
215
- } finally {
216
- this.pendingTaskIds.delete(id)
217
- }
218
- }
219
-
220
- /**
221
- * Send a message to the worker.
222
- */
223
- send<T extends keyof ServerPortMessageTypes>(
224
- type: T,
225
- ...[data]: ServerPortMessageTypes[T] extends undefined
226
- ? []
227
- : [data: ServerPortMessageTypes[T]]
228
- ): void {
229
- this.port?.postMessage({ type, data })
230
- }
231
-
232
- /**
233
- * Internal: Start the worker and wait for it to be ready.
234
- */
235
- protected async doStart(): Promise<void> {
236
- this.transition('starting')
237
-
238
- try {
239
- const { port1, port2 } = new MessageChannel()
240
- this.port = port1
241
-
242
- const viteChannel = this.config.onWorker ? new MessageChannel() : null
243
- this.vitePort = viteChannel?.port1 ?? null
244
-
245
- const { config } = this
246
- this.worker = new Worker(config.path, {
247
- ...config.workerOptions,
248
- execArgv: process.execArgv.filter((f) => !omitExecArgv.includes(f)),
249
- workerData: {
250
- ...config.workerData,
251
- port: port2,
252
- vitePort: viteChannel?.port2,
253
- },
254
- name: `${config.name}-${config.index + 1}`,
255
- transferList: viteChannel ? [port2, viteChannel.port2] : [port2],
256
- })
257
-
258
- // Notify callback if provided
259
- config.onWorker?.({
260
- worker: this.worker,
261
- vitePort: this.vitePort ?? undefined,
262
- })
263
-
264
- // Set up message handling
265
- this.attachListeners()
266
-
267
- // Wait for ready with timeout
268
- await this.waitForReady()
269
- } catch (error) {
270
- this.handleError(error as Error)
271
- throw error
272
- }
273
- }
274
-
275
- /**
276
- * Wait for the worker to emit a 'ready' message or timeout.
277
- */
278
- protected waitForReady(): Promise<void> {
279
- return new Promise((resolve, reject) => {
280
- let settled = false
281
- let timer: ReturnType<typeof setTimeout>
282
-
283
- const cleanup = () => {
284
- if (settled) return
285
- settled = true
286
- clearTimeout(timer)
287
- this.off('ready', handleReady)
288
- this.off('error', handleError)
289
- }
290
-
291
- const handleReady = () => {
292
- cleanup()
293
- resolve()
294
- }
295
-
296
- const handleError = (error: WorkerThreadError) => {
297
- cleanup()
298
- reject(error)
299
- }
300
-
301
- this.once('ready', handleReady)
302
- this.once('error', handleError)
303
-
304
- timer = setTimeout(() => {
305
- const error = createWorkerThreadError({
306
- message: `Worker thread ${this.config.id} did not become ready in time`,
307
- name: 'WorkerStartupTimeoutError',
308
- origin: 'start',
309
- fatal: true,
310
- })
311
- cleanup()
312
- this.handleError(error)
313
- reject(error)
314
- }, STARTUP_TIMEOUT_MS)
315
- })
316
- }
317
-
318
- /**
319
- * Attach listeners for port and worker events.
320
- */
321
- protected attachListeners(): void {
322
- if (!this.port || !this.worker) return
323
-
324
- this.port.on('message', (msg: ThreadPortMessage) => {
325
- const { type, data } = msg
326
- switch (type) {
327
- case 'ready':
328
- this.handleReady(data.hosts)
329
- break
330
- case 'error': {
331
- const error = createWorkerThreadError(data as ThreadErrorMessage)
332
- this.handleError(error)
333
- break
334
- }
335
- case 'task': {
336
- const taskData = data as ThreadPortMessageTypes['task']
337
- this.emit('task', taskData)
338
- this.emit(`task-${taskData.id}` as any, taskData.task)
339
- break
340
- }
341
- }
342
- })
343
-
344
- this.worker.once('exit', (code) => {
345
- // Ignore if we're already stopping/stopped
346
- if (this.state === 'stopping' || this.state === 'stopped') return
347
-
348
- const error = createWorkerThreadError({
349
- message: `Worker thread ${this.config.id} exited unexpectedly with code ${code}`,
350
- name: 'WorkerThreadExitError',
351
- origin: 'runtime',
352
- fatal: code !== 0,
353
- })
354
- this.handleError(error)
355
- })
356
- }
357
-
358
- /**
359
- * Handle worker ready event.
360
- */
361
- protected handleReady(
362
- hosts?: ThreadPortMessageTypes['ready']['hosts'],
363
- ): void {
364
- // Check if worker was stable before this (for failure count reset)
365
- if (
366
- this.ctx.lastStableTime !== null &&
367
- Date.now() - this.ctx.lastStableTime > STABILITY_WINDOW_MS
368
- ) {
369
- this.ctx.consecutiveFailures = 0
370
- }
371
-
372
- this.ctx.lastStableTime = Date.now()
373
- this.transition('ready')
374
- this.emit('ready', hosts)
375
- }
376
-
377
- /**
378
- * Handle worker error.
379
- */
380
- protected handleError(error: Error): void {
381
- const workerError = error as WorkerThreadError
382
- this.ctx.lastError = error
383
- this.ctx.consecutiveFailures++
384
- this.ctx.totalFailures++
385
-
386
- this.transition('error')
387
- if (this.listenerCount('error') > 0) {
388
- this.emit('error', workerError)
389
- } else {
390
- this.logger.error(workerError)
391
- }
392
-
393
- // Unblock any pending run() callers waiting for task-* events.
394
- // On runtime errors the current worker may never emit task completion.
395
- this.rejectPendingTasks()
396
-
397
- // Cleanup old worker
398
- this.terminateWorker().catch(() => {})
399
- this.cleanup()
400
-
401
- // Consult policy for action
402
- const errorContext: WorkerErrorContext = {
403
- workerId: this.config.id,
404
- workerType: this.config.workerType,
405
- consecutiveFailures: this.ctx.consecutiveFailures,
406
- totalFailures: this.ctx.totalFailures,
407
- lastStableTime: this.ctx.lastStableTime,
408
- }
409
-
410
- const action = this.policy.onWorkerError(workerError, errorContext)
411
-
412
- switch (action.type) {
413
- case 'restart':
414
- this.scheduleRestart(action.delay)
415
- break
416
- case 'exit':
417
- this.logger.fatal(
418
- new Error('Worker failed, exiting process', { cause: error }),
419
- )
420
- process.exit(action.code)
421
- break
422
- case 'wait':
423
- this.logger.debug('Worker failed, waiting for fix via HMR')
424
- break
425
- case 'ignore':
426
- this.logger.warn({ error }, 'Worker error ignored')
427
- break
428
- }
429
- }
430
-
431
- /**
432
- * Schedule a restart after the given delay.
433
- */
434
- protected scheduleRestart(delay: number): void {
435
- this.transition('restarting')
436
- this.logger.debug(
437
- { delay, attempt: this.ctx.consecutiveFailures },
438
- 'Scheduling worker restart',
439
- )
440
-
441
- this.restartTimer = setTimeout(() => {
442
- this.restartTimer = null
443
- // Reset to idle before starting
444
- this.transition('idle')
445
- this.start().catch((error) => {
446
- this.logger.error(new Error('Restart failed', { cause: error }))
447
- })
448
- }, delay)
449
- }
450
-
451
- /**
452
- * Clear any pending restart timer.
453
- */
454
- protected clearRestartTimer(): void {
455
- if (this.restartTimer) {
456
- clearTimeout(this.restartTimer)
457
- this.restartTimer = null
458
- }
459
- }
460
-
461
- /**
462
- * Terminate the underlying worker thread.
463
- */
464
- protected async terminateWorker(): Promise<void> {
465
- if (!this.worker) return
466
-
467
- const timeout = STOP_TIMEOUT_MS
468
- try {
469
- const signal = AbortSignal.timeout(timeout)
470
- const exitPromise = once(this.worker, 'exit', { signal })
471
- this.send('stop')
472
- this.port?.close()
473
- await exitPromise
474
- } catch {
475
- this.logger.warn(
476
- `Worker thread ${this.config.id} did not terminate in time, forcing termination`,
477
- )
478
- await this.worker.terminate()
479
- }
480
- }
481
-
482
- /**
483
- * Cleanup internal state.
484
- */
485
- protected cleanup(): void {
486
- this.port?.close()
487
- this.vitePort?.close()
488
- this.worker = null
489
- this.port = null
490
- this.vitePort = null
491
- }
492
-
493
- /**
494
- * Reject all pending run() calls by emitting an error result
495
- * for each tracked task id. This unblocks callers waiting on
496
- * `once(this, 'task-<id>')` so they don't hang during shutdown.
497
- */
498
- protected rejectPendingTasks(): void {
499
- for (const id of this.pendingTaskIds) {
500
- const result: JobTaskResult = {
501
- type: 'error',
502
- error: new Error('Worker is shutting down'),
503
- }
504
- this.emit(`task-${id}` as any, result)
505
- }
506
- this.pendingTaskIds.clear()
507
- }
508
-
509
- /**
510
- * Transition to a new state, emitting the state-change event.
511
- */
512
- protected transition(to: WorkerState): void {
513
- const from = this.state
514
- this.state = to
515
- this.logger.trace({ from, to }, 'Worker state transition')
516
- this.emit('state-change', from, to)
517
- }
518
-
519
- /**
520
- * Assert that the current state is one of the allowed states.
521
- */
522
- protected assertState(...allowed: WorkerState[]): void {
523
- if (!allowed.includes(this.state)) {
524
- throw new Error(
525
- `Invalid worker state: ${this.state}, expected one of: ${allowed.join(', ')}`,
526
- )
527
- }
528
- }
529
- }
530
-
531
- /**
532
- * Create a WorkerThreadError from a message.
533
- */
534
- function createWorkerThreadError(
535
- message: ThreadErrorMessage,
536
- includeStack = true,
537
- ): WorkerThreadError {
538
- const error = new Error(message.message) as WorkerThreadError
539
- if (message.name) error.name = message.name
540
- if (includeStack && message.stack) {
541
- error.stack = message.stack
542
- }
543
- error.origin = message.origin
544
- error.fatal = message.fatal
545
- return error
546
- }