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.
- package/LICENSE.md +1 -1
- package/README.md +41 -1
- package/dist/index.d.ts +22 -161
- package/dist/index.js +24 -53
- package/dist/index.js.map +1 -1
- package/package.json +11 -63
- package/src/index.ts +71 -91
- package/bin/cli.js +0 -2
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -137
- package/dist/cli.js.map +0 -1
- package/dist/codegen.d.ts +0 -5
- package/dist/codegen.js +0 -18
- package/dist/codegen.js.map +0 -1
- package/dist/config.d.ts +0 -47
- package/dist/config.js +0 -16
- package/dist/config.js.map +0 -1
- package/dist/entrypoints/cli.d.ts +0 -1
- package/dist/entrypoints/cli.js +0 -12
- package/dist/entrypoints/cli.js.map +0 -1
- package/dist/entrypoints/main.d.ts +0 -5
- package/dist/entrypoints/main.js +0 -248
- package/dist/entrypoints/main.js.map +0 -1
- package/dist/entrypoints/thread.d.ts +0 -16
- package/dist/entrypoints/thread.js +0 -182
- package/dist/entrypoints/thread.js.map +0 -1
- package/dist/entrypoints/worker.d.ts +0 -9
- package/dist/entrypoints/worker.js +0 -143
- package/dist/entrypoints/worker.js.map +0 -1
- package/dist/resolver.d.ts +0 -2
- package/dist/resolver.js +0 -7
- package/dist/resolver.js.map +0 -1
- package/dist/runtime/application/index.d.ts +0 -1
- package/dist/runtime/application/index.js +0 -2
- package/dist/runtime/application/index.js.map +0 -1
- package/dist/runtime/constants.d.ts +0 -8
- package/dist/runtime/constants.js +0 -5
- package/dist/runtime/constants.js.map +0 -1
- package/dist/runtime/enums.d.ts +0 -10
- package/dist/runtime/enums.js +0 -13
- package/dist/runtime/enums.js.map +0 -1
- package/dist/runtime/hooks.d.ts +0 -1
- package/dist/runtime/hooks.js +0 -2
- package/dist/runtime/hooks.js.map +0 -1
- package/dist/runtime/index.d.ts +0 -28
- package/dist/runtime/index.js +0 -29
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/injectables.d.ts +0 -69
- package/dist/runtime/injectables.js +0 -40
- package/dist/runtime/injectables.js.map +0 -1
- package/dist/runtime/jobs/job.d.ts +0 -146
- package/dist/runtime/jobs/job.js +0 -89
- package/dist/runtime/jobs/job.js.map +0 -1
- package/dist/runtime/jobs/manager.d.ts +0 -131
- package/dist/runtime/jobs/manager.js +0 -309
- package/dist/runtime/jobs/manager.js.map +0 -1
- package/dist/runtime/jobs/router.d.ts +0 -296
- package/dist/runtime/jobs/router.js +0 -458
- package/dist/runtime/jobs/router.js.map +0 -1
- package/dist/runtime/jobs/runner.d.ts +0 -103
- package/dist/runtime/jobs/runner.js +0 -425
- package/dist/runtime/jobs/runner.js.map +0 -1
- package/dist/runtime/jobs/step.d.ts +0 -26
- package/dist/runtime/jobs/step.js +0 -18
- package/dist/runtime/jobs/step.js.map +0 -1
- package/dist/runtime/jobs/types.d.ts +0 -86
- package/dist/runtime/jobs/types.js +0 -5
- package/dist/runtime/jobs/types.js.map +0 -1
- package/dist/runtime/metrics/metric.d.ts +0 -8
- package/dist/runtime/metrics/metric.js +0 -11
- package/dist/runtime/metrics/metric.js.map +0 -1
- package/dist/runtime/metrics/registry.d.ts +0 -3
- package/dist/runtime/metrics/registry.js +0 -5
- package/dist/runtime/metrics/registry.js.map +0 -1
- package/dist/runtime/metrics/server.d.ts +0 -7
- package/dist/runtime/metrics/server.js +0 -53
- package/dist/runtime/metrics/server.js.map +0 -1
- package/dist/runtime/plugin.d.ts +0 -2
- package/dist/runtime/plugin.js +0 -2
- package/dist/runtime/plugin.js.map +0 -1
- package/dist/runtime/runtime.d.ts +0 -27
- package/dist/runtime/runtime.js +0 -77
- package/dist/runtime/runtime.js.map +0 -1
- package/dist/runtime/scheduler/index.d.ts +0 -22
- package/dist/runtime/scheduler/index.js +0 -20
- package/dist/runtime/scheduler/index.js.map +0 -1
- package/dist/runtime/server/applications.d.ts +0 -91
- package/dist/runtime/server/applications.js +0 -171
- package/dist/runtime/server/applications.js.map +0 -1
- package/dist/runtime/server/config.d.ts +0 -148
- package/dist/runtime/server/config.js +0 -33
- package/dist/runtime/server/config.js.map +0 -1
- package/dist/runtime/server/environment.d.ts +0 -43
- package/dist/runtime/server/environment.js +0 -33
- package/dist/runtime/server/environment.js.map +0 -1
- package/dist/runtime/server/error-policy.d.ts +0 -62
- package/dist/runtime/server/error-policy.js +0 -73
- package/dist/runtime/server/error-policy.js.map +0 -1
- package/dist/runtime/server/hmr-coordinator.d.ts +0 -37
- package/dist/runtime/server/hmr-coordinator.js +0 -94
- package/dist/runtime/server/hmr-coordinator.js.map +0 -1
- package/dist/runtime/server/jobs.d.ts +0 -54
- package/dist/runtime/server/jobs.js +0 -176
- package/dist/runtime/server/jobs.js.map +0 -1
- package/dist/runtime/server/lifecycle.d.ts +0 -85
- package/dist/runtime/server/lifecycle.js +0 -177
- package/dist/runtime/server/lifecycle.js.map +0 -1
- package/dist/runtime/server/managed-worker.d.ts +0 -158
- package/dist/runtime/server/managed-worker.js +0 -420
- package/dist/runtime/server/managed-worker.js.map +0 -1
- package/dist/runtime/server/pool-manager.d.ts +0 -85
- package/dist/runtime/server/pool-manager.js +0 -129
- package/dist/runtime/server/pool-manager.js.map +0 -1
- package/dist/runtime/server/proxy.d.ts +0 -21
- package/dist/runtime/server/proxy.js +0 -76
- package/dist/runtime/server/proxy.js.map +0 -1
- package/dist/runtime/server/server.d.ts +0 -60
- package/dist/runtime/server/server.js +0 -163
- package/dist/runtime/server/server.js.map +0 -1
- package/dist/runtime/server/types.d.ts +0 -31
- package/dist/runtime/server/types.js +0 -2
- package/dist/runtime/server/types.js.map +0 -1
- package/dist/runtime/server/worker-pool.d.ts +0 -121
- package/dist/runtime/server/worker-pool.js +0 -171
- package/dist/runtime/server/worker-pool.js.map +0 -1
- package/dist/runtime/store/index.d.ts +0 -3
- package/dist/runtime/store/index.js +0 -23
- package/dist/runtime/store/index.js.map +0 -1
- package/dist/runtime/subscription/manager.d.ts +0 -49
- package/dist/runtime/subscription/manager.js +0 -170
- package/dist/runtime/subscription/manager.js.map +0 -1
- package/dist/runtime/subscription/redis.d.ts +0 -18
- package/dist/runtime/subscription/redis.js +0 -119
- package/dist/runtime/subscription/redis.js.map +0 -1
- package/dist/runtime/types.d.ts +0 -95
- package/dist/runtime/types.js +0 -2
- package/dist/runtime/types.js.map +0 -1
- package/dist/runtime/workers/application.d.ts +0 -29
- package/dist/runtime/workers/application.js +0 -77
- package/dist/runtime/workers/application.js.map +0 -1
- package/dist/runtime/workers/base.d.ts +0 -14
- package/dist/runtime/workers/base.js +0 -41
- package/dist/runtime/workers/base.js.map +0 -1
- package/dist/runtime/workers/cli.d.ts +0 -1
- package/dist/runtime/workers/cli.js +0 -2
- package/dist/runtime/workers/cli.js.map +0 -1
- package/dist/runtime/workers/job.d.ts +0 -20
- package/dist/runtime/workers/job.js +0 -170
- package/dist/runtime/workers/job.js.map +0 -1
- package/dist/vite/builder.d.ts +0 -5
- package/dist/vite/builder.js +0 -79
- package/dist/vite/builder.js.map +0 -1
- package/dist/vite/config.d.ts +0 -28
- package/dist/vite/config.js +0 -22
- package/dist/vite/config.js.map +0 -1
- package/dist/vite/plugins.d.ts +0 -2
- package/dist/vite/plugins.js +0 -43
- package/dist/vite/plugins.js.map +0 -1
- package/dist/vite/runners/worker.d.ts +0 -3
- package/dist/vite/runners/worker.js +0 -34
- package/dist/vite/runners/worker.js.map +0 -1
- package/dist/vite/server.d.ts +0 -3
- package/dist/vite/server.js +0 -36
- package/dist/vite/server.js.map +0 -1
- package/dist/vite/servers/main.d.ts +0 -8
- package/dist/vite/servers/main.js +0 -24
- package/dist/vite/servers/main.js.map +0 -1
- package/dist/vite/servers/worker.d.ts +0 -13
- package/dist/vite/servers/worker.js +0 -124
- package/dist/vite/servers/worker.js.map +0 -1
- package/src/cli.ts +0 -162
- package/src/codegen.ts +0 -30
- package/src/config.ts +0 -69
- package/src/entrypoints/cli.ts +0 -13
- package/src/entrypoints/main.ts +0 -317
- package/src/entrypoints/thread.ts +0 -223
- package/src/entrypoints/worker.ts +0 -174
- package/src/resolver.ts +0 -7
- package/src/runtime/application/index.ts +0 -1
- package/src/runtime/constants.ts +0 -13
- package/src/runtime/enums.ts +0 -12
- package/src/runtime/hooks.ts +0 -1
- package/src/runtime/index.ts +0 -29
- package/src/runtime/injectables.ts +0 -101
- package/src/runtime/jobs/job.ts +0 -467
- package/src/runtime/jobs/manager.ts +0 -454
- package/src/runtime/jobs/router.ts +0 -967
- package/src/runtime/jobs/runner.ts +0 -583
- package/src/runtime/jobs/step.ts +0 -66
- package/src/runtime/jobs/types.ts +0 -116
- package/src/runtime/metrics/metric.ts +0 -18
- package/src/runtime/metrics/registry.ts +0 -11
- package/src/runtime/metrics/server.ts +0 -69
- package/src/runtime/plugin.ts +0 -2
- package/src/runtime/runtime.ts +0 -103
- package/src/runtime/scheduler/index.ts +0 -39
- package/src/runtime/server/applications.ts +0 -264
- package/src/runtime/server/config.ts +0 -199
- package/src/runtime/server/environment.ts +0 -71
- package/src/runtime/server/error-policy.ts +0 -132
- package/src/runtime/server/hmr-coordinator.ts +0 -116
- package/src/runtime/server/jobs.ts +0 -266
- package/src/runtime/server/lifecycle.ts +0 -234
- package/src/runtime/server/managed-worker.ts +0 -546
- package/src/runtime/server/pool-manager.ts +0 -187
- package/src/runtime/server/proxy.ts +0 -119
- package/src/runtime/server/server.ts +0 -218
- package/src/runtime/server/types.ts +0 -32
- package/src/runtime/server/worker-pool.ts +0 -275
- package/src/runtime/store/index.ts +0 -30
- package/src/runtime/subscription/manager.ts +0 -307
- package/src/runtime/subscription/redis.ts +0 -159
- package/src/runtime/types.ts +0 -84
- package/src/runtime/workers/application.ts +0 -133
- package/src/runtime/workers/base.ts +0 -65
- package/src/runtime/workers/cli.ts +0 -0
- package/src/runtime/workers/job.ts +0 -142
- package/src/vite/builder.ts +0 -94
- package/src/vite/config.ts +0 -45
- package/src/vite/plugins.ts +0 -53
- package/src/vite/runners/worker.ts +0 -55
- package/src/vite/server.ts +0 -55
- package/src/vite/servers/main.ts +0 -41
- 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
|
-
}
|