nmtjs 0.15.0-beta.20 → 0.15.0-beta.22

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.
@@ -0,0 +1,110 @@
1
+ // ============================================================================
2
+ // Job Definition Types (metadata about job structure)
3
+ // ============================================================================
4
+
5
+ /** Metadata about a job step in a job definition */
6
+ export interface JobStepInfo {
7
+ /** Optional human-readable label for the step */
8
+ label?: string
9
+ /** Whether this step has a condition that may skip it */
10
+ conditional: boolean
11
+ }
12
+
13
+ /** Metadata about a job definition (not a running job instance) */
14
+ export interface JobDefinitionInfo {
15
+ /** Job name from definition */
16
+ name: string
17
+ /** Information about each step in the job */
18
+ steps: JobStepInfo[]
19
+ }
20
+
21
+ // ============================================================================
22
+ // Job Execution Types (runtime state of a running job)
23
+ // ============================================================================
24
+
25
+ /** Result of a single step execution */
26
+ export interface StepResultEntry {
27
+ /** Output data produced by the step, or null if skipped */
28
+ data: Record<string, unknown> | null
29
+ /** Duration in milliseconds */
30
+ duration: number
31
+ }
32
+
33
+ /** Checkpoint data persisted for job resume support */
34
+ export interface JobProgressCheckpoint {
35
+ /** Index of the next step to execute (0 = not started, length = completed) */
36
+ stepIndex: number
37
+ /** Label of the last completed step */
38
+ stepLabel?: string
39
+ /** Accumulated result from all completed steps */
40
+ result: Record<string, unknown>
41
+ /** Results of each individual step */
42
+ stepResults: StepResultEntry[]
43
+ /** User-defined progress state */
44
+ progress: Record<string, unknown>
45
+ }
46
+
47
+ /** Information about the currently executing job, available via injectable */
48
+ export interface JobExecutionContext {
49
+ /** Job definition name */
50
+ name: string
51
+ /** Queue job ID */
52
+ id?: string
53
+ /** Queue name */
54
+ queue?: string
55
+ /** Number of attempts made so far */
56
+ attempts?: number
57
+ /** Current step index being executed */
58
+ stepIndex?: number
59
+ }
60
+
61
+ // ============================================================================
62
+ // Job Item Types (job instances from queue)
63
+ // ============================================================================
64
+
65
+ /** Vendor-agnostic job status */
66
+ export type JobStatus =
67
+ | 'pending' // Queued, waiting to be picked up
68
+ | 'active' // Currently executing
69
+ | 'completed' // Successfully finished
70
+ | 'failed' // Failed (may retry)
71
+ | 'delayed' // Scheduled for future
72
+ | 'cancelled' // Manually cancelled
73
+ | 'unknown' // State cannot be determined
74
+
75
+ /** A job instance retrieved from the queue */
76
+ export interface JobItem<TInput = unknown, TOutput = unknown> {
77
+ /** Unique job instance ID */
78
+ id: string
79
+ /** Job definition name */
80
+ name: string
81
+ /** Queue name this job belongs to */
82
+ queue: string
83
+ /** Input data passed to the job */
84
+ data: TInput
85
+ /** Output produced by the job (if completed) */
86
+ output?: TOutput | null
87
+ /** Current job status */
88
+ status: JobStatus
89
+ /** Job priority (lower = higher priority) */
90
+ priority?: number
91
+ /** Job progress checkpoint (includes step state and user progress) */
92
+ progress?: JobProgressCheckpoint
93
+ /** Number of execution attempts */
94
+ attempts: number
95
+ /** Timestamp when job started processing (ms) */
96
+ startedAt?: number
97
+ /** Timestamp when job completed (ms) */
98
+ completedAt?: number
99
+ /** Error message if job failed */
100
+ error?: string
101
+ /** Stack trace if job failed */
102
+ stacktrace?: string[]
103
+ }
104
+
105
+ // ============================================================================
106
+ // Injectable Types
107
+ // ============================================================================
108
+
109
+ /** Function to manually trigger saving job progress state */
110
+ export type SaveJobProgress = () => Promise<void>
@@ -203,6 +203,8 @@ export class ApplicationServerJobs {
203
203
 
204
204
  async stop() {
205
205
  const { logger } = this.params
206
+ // TODO: make configurable
207
+ const closeTimeout = 10_000 // 10 seconds timeout for graceful close
206
208
 
207
209
  if (this.ui) {
208
210
  await new Promise<void>((resolve) => {
@@ -212,29 +214,43 @@ export class ApplicationServerJobs {
212
214
  })
213
215
  }
214
216
 
217
+ // Stop accepting new jobs first.
215
218
  await Promise.all(
216
- this.uiQueues.map(async (queue) => {
219
+ [...this.queueWorkers].map(async (worker) => {
217
220
  try {
218
- await queue.close()
221
+ // Try graceful close with timeout
222
+ const closePromise = worker.close()
223
+ const timeoutPromise = new Promise<'timeout'>((resolve) =>
224
+ setTimeout(() => resolve('timeout'), closeTimeout),
225
+ )
226
+
227
+ const result = await Promise.race([closePromise, timeoutPromise])
228
+
229
+ if (result === 'timeout') {
230
+ logger.warn(
231
+ { worker: worker.name },
232
+ 'Worker close timed out, forcing close',
233
+ )
234
+ await worker.close(true)
235
+ }
219
236
  } catch (error) {
220
- logger.warn({ error }, 'Failed to close Jobs UI queue')
237
+ logger.warn({ error }, 'Failed to close BullMQ worker')
221
238
  }
222
239
  }),
223
240
  )
224
- this.uiQueues = []
225
- this.ui = undefined
241
+ this.queueWorkers.clear()
226
242
 
227
- // Stop accepting new jobs first.
228
243
  await Promise.all(
229
- [...this.queueWorkers].map(async (worker) => {
244
+ this.uiQueues.map(async (queue) => {
230
245
  try {
231
- await worker.close()
246
+ await queue.close()
232
247
  } catch (error) {
233
- logger.warn({ error }, 'Failed to close BullMQ worker')
248
+ logger.warn({ error }, 'Failed to close Jobs UI queue')
234
249
  }
235
250
  }),
236
251
  )
237
- this.queueWorkers.clear()
252
+ this.uiQueues = []
253
+ this.ui = undefined
238
254
 
239
255
  await Promise.all(
240
256
  Array.from(this.pools.values()).map(async (pool) => {
@@ -3,7 +3,7 @@ import type { MessagePort } from 'node:worker_threads'
3
3
  import { UnrecoverableError } from 'bullmq'
4
4
 
5
5
  import type { JobWorkerPool } from '../enums.ts'
6
- import type { StepResultEntry } from '../jobs/runner.ts'
6
+ import type { JobProgressCheckpoint } from '../jobs/types.ts'
7
7
  import type { ServerConfig } from '../server/config.ts'
8
8
  import type { ServerPortMessage, ThreadPortMessage } from '../types.ts'
9
9
  import { LifecycleHook, WorkerType } from '../enums.ts'
@@ -95,12 +95,7 @@ export class JobWorkerRuntime extends BaseWorkerRuntime {
95
95
 
96
96
  // Load checkpoint from BullMQ progress for resume support
97
97
  const checkpoint = bullJob.progress as
98
- | {
99
- stepIndex: number
100
- result: Record<string, unknown>
101
- stepResults: StepResultEntry[]
102
- progress: Record<string, unknown>
103
- }
98
+ | JobProgressCheckpoint
104
99
  | undefined
105
100
 
106
101
  const result = await this.jobRunner.runJob(job, task.data, {