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.
- package/dist/index.d.ts +2 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/injectables.d.ts +5 -0
- package/dist/runtime/injectables.js +4 -0
- package/dist/runtime/injectables.js.map +1 -1
- package/dist/runtime/jobs/manager.d.ts +19 -16
- package/dist/runtime/jobs/manager.js +74 -18
- package/dist/runtime/jobs/manager.js.map +1 -1
- package/dist/runtime/jobs/router.d.ts +36 -10
- package/dist/runtime/jobs/router.js +64 -13
- package/dist/runtime/jobs/router.js.map +1 -1
- package/dist/runtime/jobs/runner.d.ts +27 -4
- package/dist/runtime/jobs/runner.js +74 -11
- package/dist/runtime/jobs/runner.js.map +1 -1
- package/dist/runtime/jobs/step.d.ts +1 -0
- package/dist/runtime/jobs/step.js.map +1 -1
- package/dist/runtime/jobs/types.d.ts +80 -0
- package/dist/runtime/jobs/types.js +5 -0
- package/dist/runtime/jobs/types.js.map +1 -0
- package/dist/runtime/server/jobs.js +19 -10
- package/dist/runtime/server/jobs.js.map +1 -1
- package/dist/runtime/workers/job.js.map +1 -1
- package/package.json +12 -12
- package/src/runtime/index.ts +1 -0
- package/src/runtime/injectables.ts +13 -0
- package/src/runtime/jobs/manager.ts +97 -37
- package/src/runtime/jobs/router.ts +117 -23
- package/src/runtime/jobs/runner.ts +120 -15
- package/src/runtime/jobs/step.ts +1 -0
- package/src/runtime/jobs/types.ts +110 -0
- package/src/runtime/server/jobs.ts +26 -10
- package/src/runtime/workers/job.ts +2 -7
|
@@ -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.
|
|
219
|
+
[...this.queueWorkers].map(async (worker) => {
|
|
217
220
|
try {
|
|
218
|
-
|
|
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
|
|
237
|
+
logger.warn({ error }, 'Failed to close BullMQ worker')
|
|
221
238
|
}
|
|
222
239
|
}),
|
|
223
240
|
)
|
|
224
|
-
this.
|
|
225
|
-
this.ui = undefined
|
|
241
|
+
this.queueWorkers.clear()
|
|
226
242
|
|
|
227
|
-
// Stop accepting new jobs first.
|
|
228
243
|
await Promise.all(
|
|
229
|
-
|
|
244
|
+
this.uiQueues.map(async (queue) => {
|
|
230
245
|
try {
|
|
231
|
-
await
|
|
246
|
+
await queue.close()
|
|
232
247
|
} catch (error) {
|
|
233
|
-
logger.warn({ error }, 'Failed to close
|
|
248
|
+
logger.warn({ error }, 'Failed to close Jobs UI queue')
|
|
234
249
|
}
|
|
235
250
|
}),
|
|
236
251
|
)
|
|
237
|
-
this.
|
|
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 {
|
|
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, {
|