nmtjs 0.16.1 → 0.17.0-beta.2

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 -161
  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 -69
  49. package/dist/runtime/injectables.js +0 -40
  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 -119
  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 -14
  142. package/dist/runtime/workers/base.js +0 -41
  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 -101
  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 -307
  212. package/src/runtime/subscription/redis.ts +0 -159
  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 -65
  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,454 +0,0 @@
1
- import assert from 'node:assert'
2
- import { randomUUID } from 'node:crypto'
3
-
4
- import type {
5
- Job,
6
- JobState,
7
- JobType,
8
- QueueEventsListener,
9
- RedisClient,
10
- } from 'bullmq'
11
- import {
12
- Queue,
13
- QueueEvents,
14
- QueueEventsProducer,
15
- UnrecoverableError,
16
- } from 'bullmq'
17
-
18
- import type { ServerStoreConfig } from '../server/config.ts'
19
- import type { AnyJob, JobBackoffOptions } from './job.ts'
20
- import type { JobDefinitionInfo, JobProgressCheckpoint } from './types.ts'
21
- import { createStoreClient } from '../store/index.ts'
22
-
23
- /**
24
- * Get the dedicated BullMQ queue name for a job
25
- */
26
- export function getJobQueueName(job: AnyJob): string {
27
- return `job.${job.options.name}`
28
- }
29
-
30
- type QueueJobResultOptions<T extends AnyJob = AnyJob> = {
31
- job: T
32
- bullJob: Job<T['_']['input'], T['_']['output'], T['options']['name']>
33
- events: QueueEvents
34
- }
35
-
36
- type QueueJobAddOptions = {
37
- jobId?: string
38
- priority?: number
39
- forceMissingWorkers?: boolean
40
- attempts?: number
41
- backoff?: JobBackoffOptions
42
- oneoff?: boolean
43
- delay?: number
44
- }
45
-
46
- export class QueueJobResult<T extends AnyJob = AnyJob> {
47
- #options: QueueJobResultOptions<T>
48
-
49
- constructor(options: QueueJobResultOptions<T>) {
50
- this.#options = options
51
- }
52
-
53
- get id() {
54
- return this.#options.bullJob.id!
55
- }
56
-
57
- get name() {
58
- return this.#options.job.options.name
59
- }
60
-
61
- async waitResult() {
62
- return await this.#options.bullJob.waitUntilFinished(this.#options.events)
63
- }
64
- }
65
-
66
- export type { JobDefinitionInfo, JobItem, JobStepInfo } from './types.ts'
67
-
68
- import type { JobItem, JobStatus } from './types.ts'
69
-
70
- /** Job item type with generic job typing for internal use */
71
- export type JobItemOf<T extends AnyJob = AnyJob> = JobItem<
72
- T['_']['input'],
73
- T['_']['output']
74
- >
75
-
76
- export interface JobManagerInstance {
77
- list<T extends AnyJob>(
78
- job: T,
79
- options?: { page?: number; limit?: number; status?: JobStatus[] },
80
- ): Promise<{
81
- items: JobItemOf<T>[]
82
- page: number
83
- limit: number
84
- pages: number
85
- total: number
86
- }>
87
- get<T extends AnyJob>(job: T, id: string): Promise<JobItemOf<T> | null>
88
- getInfo(job: AnyJob): JobDefinitionInfo
89
- add<T extends AnyJob>(
90
- job: T,
91
- data: T['_']['input'],
92
- options?: QueueJobAddOptions,
93
- ): Promise<QueueJobResult<T>>
94
- retry(
95
- job: AnyJob,
96
- id: string,
97
- options?: { clearState?: boolean },
98
- ): Promise<void>
99
- remove(job: AnyJob, id: string): Promise<void>
100
- cancel(job: AnyJob, id: string): Promise<void>
101
- }
102
-
103
- export type CustomJobsEvents = QueueEventsListener & {
104
- [K in `cancel:${string}`]: (args: {}, id: string) => void
105
- }
106
-
107
- type JobQueueEntry = {
108
- queue: Queue
109
- events: QueueEvents
110
- custom: QueueEventsProducer
111
- }
112
-
113
- /**
114
- * TODO: Consider implementing an abstract SQL storage layer for job queries.
115
- *
116
- * Current limitations with BullMQ direct queries:
117
- * - Pagination is per-type, requires complex offset calculations
118
- * - Limited filtering/sorting capabilities
119
- * - No full-text search on job data
120
- *
121
- * Proposed architecture:
122
- * 1. Shared SQLite file (WAL mode) across all workers on the same machine
123
- * 2. Each worker subscribes to BullMQ events and syncs to SQLite
124
- * 3. List/filter/sort queries run against SQLite with proper SQL semantics
125
- * 4. Mutations (add, retry, cancel, remove) still go through BullMQ directly
126
- *
127
- * Benefits:
128
- * - Proper SQL pagination, filtering, sorting
129
- * - Abstract storage layer could support different queue backends (not just BullMQ)
130
- * - No per-process memory duplication (shared file)
131
- * - Complex queries: date ranges, priority ranges, full-text search
132
- *
133
- * Implementation notes:
134
- * - Use better-sqlite3 (sync, fast for in-memory/WAL)
135
- * - Subscribe to QueueEvents BEFORE initial sync to buffer events
136
- * - Handle race conditions: initial load + event replay
137
- * - Consider leader election for sync to reduce write contention
138
- */
139
- export class JobManager {
140
- protected store!: RedisClient
141
- /**
142
- * Per-job dedicated queues. Each job has its own queue for granular control.
143
- */
144
- protected jobQueues = new Map<string, JobQueueEntry>()
145
-
146
- constructor(
147
- protected storeConfig: ServerStoreConfig,
148
- protected jobs: AnyJob[],
149
- ) {}
150
-
151
- get publicInstance(): JobManagerInstance {
152
- return {
153
- list: this.list.bind(this),
154
- add: this.add.bind(this),
155
- get: this.get.bind(this),
156
- getInfo: this.getInfo.bind(this),
157
- retry: this.retry.bind(this),
158
- remove: this.remove.bind(this),
159
- cancel: this.cancel.bind(this),
160
- }
161
- }
162
-
163
- async initialize() {
164
- this.store = (await createStoreClient(
165
- this.storeConfig,
166
- )) as unknown as RedisClient
167
-
168
- await this.store.connect()
169
-
170
- // Create a dedicated queue for each job
171
- for (const job of this.jobs) {
172
- const queueName = getJobQueueName(job)
173
- const entry: JobQueueEntry = {
174
- queue: new Queue(queueName, { connection: this.store }),
175
- events: new QueueEvents(queueName, {
176
- connection: this.store,
177
- autorun: true,
178
- }),
179
- custom: new QueueEventsProducer(queueName, { connection: this.store }),
180
- }
181
- this.jobQueues.set(job.options.name, entry)
182
- }
183
-
184
- // Wait for all queues to be ready
185
- await Promise.all(
186
- Array.from(this.jobQueues.values()).flatMap((entry) => [
187
- entry.queue.waitUntilReady(),
188
- entry.events.waitUntilReady(),
189
- entry.custom.waitUntilReady(),
190
- ]),
191
- )
192
- }
193
-
194
- async terminate() {
195
- await Promise.allSettled(
196
- Array.from(this.jobQueues.values()).flatMap((entry) => [
197
- entry.queue.close(),
198
- entry.events.close(),
199
- entry.custom.close(),
200
- ]),
201
- )
202
- this.store.disconnect(false)
203
- }
204
-
205
- protected getJobQueue(job: AnyJob): JobQueueEntry {
206
- const entry = this.jobQueues.get(job.options.name)
207
- if (!entry) {
208
- throw new Error(`Job queue for [${job.options.name}] not found`)
209
- }
210
- return entry
211
- }
212
-
213
- getInfo(job: AnyJob): JobDefinitionInfo {
214
- return {
215
- name: job.options.name,
216
- steps: job.jobSteps.map((step, index) => ({
217
- label: step.label,
218
- conditional: job.conditions.has(index),
219
- parallel: job.parallelGroupByStepIndex.has(index),
220
- })),
221
- }
222
- }
223
-
224
- async list<T extends AnyJob>(
225
- job: T,
226
- {
227
- limit = 20,
228
- page = 1,
229
- status = [],
230
- }: { page?: number; limit?: number; status?: JobStatus[] } = {},
231
- ) {
232
- const { queue } = this.getJobQueue(job)
233
- // Convert vendor-agnostic status to BullMQ JobType for querying
234
- const bullJobTypes = status.length
235
- ? status.flatMap((s) => this._mapStatusToJobType(s))
236
- : []
237
-
238
- // Get counts per type to calculate proper offsets
239
- const typeCounts = await queue.getJobCounts(...bullJobTypes)
240
- const jobsCount = Object.values(typeCounts).reduce((a, b) => a + b, 0)
241
- const totalPages = Math.ceil(jobsCount / limit)
242
- if (page > totalPages)
243
- return { items: [], page, limit, pages: totalPages, total: jobsCount }
244
-
245
- // Calculate which types we need to fetch from and with what offsets
246
- const globalStart = (page - 1) * limit
247
- const globalEnd = globalStart + limit
248
-
249
- // Determine the ordered types (use bullJobTypes if specified, otherwise use typeCounts keys)
250
- const orderedTypes =
251
- bullJobTypes.length > 0
252
- ? bullJobTypes
253
- : (Object.keys(typeCounts) as JobType[])
254
-
255
- // Calculate cumulative offsets and fetch jobs from each type as needed
256
- let cumulative = 0
257
- const jobPromises: Promise<Job[]>[] = []
258
-
259
- for (const type of orderedTypes) {
260
- const typeCount = typeCounts[type] ?? 0
261
- const typeStart = cumulative
262
- const typeEnd = cumulative + typeCount
263
-
264
- // Check if this type overlaps with our desired range
265
- if (typeEnd > globalStart && typeStart < globalEnd) {
266
- // Calculate local start/end within this type
267
- const localStart = Math.max(0, globalStart - typeStart)
268
- const localEnd = Math.min(typeCount, globalEnd - typeStart) - 1
269
-
270
- if (localEnd >= localStart) {
271
- jobPromises.push(queue.getJobs([type], localStart, localEnd))
272
- }
273
- }
274
-
275
- cumulative += typeCount
276
- }
277
-
278
- const jobArrays = await Promise.all(jobPromises)
279
- const jobs = jobArrays.flat().slice(0, limit)
280
- const items = await Promise.all(jobs.map((job) => this._mapJob(job)))
281
- return { items, page, limit, pages: totalPages, total: jobsCount }
282
- }
283
-
284
- async get<T extends AnyJob>(job: T, id: string) {
285
- const { queue } = this.getJobQueue(job)
286
- const bullJob = await queue.getJob(id)
287
- if (!bullJob) return null
288
- return await this._mapJob(bullJob)
289
- }
290
-
291
- async add<T extends AnyJob>(
292
- job: T,
293
- data: T['_']['input'],
294
- {
295
- forceMissingWorkers = false,
296
- jobId = randomUUID(),
297
- priority,
298
- attempts = job.options.attempts,
299
- backoff = job.options.backoff,
300
- oneoff = job.options.oneoff ?? true,
301
- delay,
302
- }: QueueJobAddOptions = {},
303
- ) {
304
- const { queue, events } = this.getJobQueue(job)
305
-
306
- if (!forceMissingWorkers) {
307
- if ((await queue.getWorkersCount()) === 0) {
308
- throw new Error(
309
- `No workers available for [${getJobQueueName(job)}] queue`,
310
- )
311
- }
312
- }
313
- const bullJob = await queue.add(job.options.name as any, data as any, {
314
- attempts,
315
- backoff,
316
- jobId,
317
- priority,
318
- delay,
319
- removeOnComplete: oneoff,
320
- removeOnFail: oneoff,
321
- })
322
-
323
- return new QueueJobResult({ job, bullJob, events })
324
- }
325
-
326
- async retry(job: AnyJob, id: string, options?: { clearState?: boolean }) {
327
- const { queue } = this.getJobQueue(job)
328
- const bullJob = await queue.getJob(id)
329
- if (!bullJob) throw new Error(`Job with id [${id}] not found`)
330
-
331
- const state = await bullJob.getState()
332
- // For completed jobs, clear state by default so it reruns from scratch
333
- // For failed jobs, keep state by default so it resumes from checkpoint
334
- const shouldClearState = options?.clearState ?? state === 'completed'
335
-
336
- if (shouldClearState) {
337
- await bullJob.updateProgress({})
338
- }
339
-
340
- await bullJob.retry()
341
- }
342
-
343
- async remove(job: AnyJob, id: string) {
344
- const { queue } = this.getJobQueue(job)
345
- const bullJob = await queue.getJob(id)
346
- if (!bullJob) throw new Error(`Job with id [${id}] not found`)
347
- await bullJob.remove()
348
- }
349
-
350
- async cancel(job: AnyJob, id: string) {
351
- const { custom, queue } = this.getJobQueue(job)
352
- const bullJob = await queue.getJob(id)
353
- if (!bullJob) throw new Error(`Job with id [${id}] not found`)
354
- if (bullJob.finishedOn) return
355
- if ((await bullJob.getState()) === 'waiting') {
356
- return await bullJob.remove()
357
- }
358
- if ((await bullJob.getState()) === 'active') {
359
- await custom.publishEvent({ eventName: `cancel:${id}` })
360
- }
361
- }
362
-
363
- cancellationSignal(job: AnyJob, id: string) {
364
- const { events } = this.getJobQueue(job)
365
- const controller = new AbortController()
366
- const handler = () => {
367
- controller.abort(new UnrecoverableError('Job cancelled'))
368
- }
369
- events.on<CustomJobsEvents>(`cancel:${id}`, handler)
370
- const signal = controller.signal
371
- return Object.assign(signal, {
372
- [Symbol.dispose]: () => {
373
- events.off<CustomJobsEvents>(`cancel:${id}`, handler)
374
- },
375
- })
376
- }
377
-
378
- getQueue(job: AnyJob) {
379
- return this.getJobQueue(job)
380
- }
381
-
382
- protected async _mapJob(bullJob: Job): Promise<JobItemOf> {
383
- const bullState = await bullJob.getState()
384
- const id = bullJob.id
385
- assert(typeof id === 'string', 'Expected job id to be a string')
386
-
387
- // Map BullMQ state to vendor-agnostic status
388
- const status = this._mapStatus(bullState)
389
-
390
- // Extract progress only if it's a valid checkpoint object
391
- const progress =
392
- typeof bullJob.progress === 'object' &&
393
- bullJob.progress !== null &&
394
- 'stepIndex' in bullJob.progress
395
- ? (bullJob.progress as JobProgressCheckpoint)
396
- : undefined
397
-
398
- return {
399
- id,
400
- name: bullJob.name,
401
- queue: bullJob.queueName,
402
- data: bullJob.data,
403
- output: bullJob.returnvalue,
404
- status,
405
- priority: bullJob.priority,
406
- progress,
407
- attempts: bullJob.attemptsMade,
408
- startedAt: bullJob.processedOn,
409
- completedAt: bullJob.finishedOn,
410
- error: bullJob.failedReason,
411
- stacktrace: bullJob.stacktrace,
412
- }
413
- }
414
-
415
- /** Map BullMQ JobState to vendor-agnostic JobStatus */
416
- protected _mapStatus(state: JobState | 'unknown'): JobStatus {
417
- switch (state) {
418
- case 'waiting':
419
- case 'prioritized':
420
- case 'waiting-children':
421
- return 'pending'
422
- case 'active':
423
- return 'active'
424
- case 'completed':
425
- return 'completed'
426
- case 'failed':
427
- return 'failed'
428
- case 'delayed':
429
- return 'delayed'
430
- default:
431
- return 'unknown'
432
- }
433
- }
434
-
435
- /** Map vendor-agnostic JobStatus to BullMQ JobType for queries */
436
- protected _mapStatusToJobType(status: JobStatus): JobType[] {
437
- switch (status) {
438
- case 'pending':
439
- return ['waiting', 'prioritized', 'waiting-children']
440
- case 'active':
441
- return ['active']
442
- case 'completed':
443
- return ['completed']
444
- case 'failed':
445
- return ['failed']
446
- case 'delayed':
447
- return ['delayed']
448
- case 'cancelled':
449
- return [] // BullMQ doesn't have a cancelled state
450
- default:
451
- return []
452
- }
453
- }
454
- }