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.
- package/LICENSE.md +1 -1
- package/README.md +41 -1
- package/dist/index.d.ts +22 -121
- 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 -28
- package/dist/runtime/injectables.js +0 -24
- 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 -120
- 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 -16
- package/dist/runtime/workers/base.js +0 -46
- 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 -72
- 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 -310
- package/src/runtime/subscription/redis.ts +0 -157
- package/src/runtime/types.ts +0 -84
- package/src/runtime/workers/application.ts +0 -133
- package/src/runtime/workers/base.ts +0 -71
- 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,266 +0,0 @@
|
|
|
1
|
-
import { inspect } from 'node:util'
|
|
2
|
-
|
|
3
|
-
import type { Logger } from '@nmtjs/core'
|
|
4
|
-
import type { RedisClient } from 'bullmq'
|
|
5
|
-
import { UnrecoverableError, Worker } from 'bullmq'
|
|
6
|
-
|
|
7
|
-
import type { AnyJob } from '../jobs/job.ts'
|
|
8
|
-
import type { Store, WorkerJobTask } from '../types.ts'
|
|
9
|
-
import type { ServerConfig } from './config.ts'
|
|
10
|
-
import type { ErrorPolicy } from './error-policy.ts'
|
|
11
|
-
import type { ApplicationServerWorkerConfig } from './types.ts'
|
|
12
|
-
import type {
|
|
13
|
-
ManagedWorkerFactory,
|
|
14
|
-
WorkerPoolConfig,
|
|
15
|
-
WorkerPoolFactory,
|
|
16
|
-
} from './worker-pool.ts'
|
|
17
|
-
import { WorkerType } from '../enums.ts'
|
|
18
|
-
import { getJobQueueName } from '../jobs/manager.ts'
|
|
19
|
-
import { JobRunnersPool } from './worker-pool.ts'
|
|
20
|
-
|
|
21
|
-
type JobsPoolConfig = Exclude<ServerConfig['jobs'], undefined>['pools']
|
|
22
|
-
|
|
23
|
-
function enrichBullMqErrorStack(error: unknown): Error {
|
|
24
|
-
const normalized =
|
|
25
|
-
error instanceof Error
|
|
26
|
-
? error
|
|
27
|
-
: new Error(
|
|
28
|
-
typeof error === 'string' ? error : inspect(error, false, 20, false),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
normalized.stack = inspect(normalized, false, 20, false)
|
|
32
|
-
|
|
33
|
-
return normalized
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function getJobsByPool(jobs: Iterable<AnyJob>): Map<string, AnyJob[]> {
|
|
37
|
-
const jobsByPool = new Map<string, AnyJob[]>()
|
|
38
|
-
|
|
39
|
-
for (const job of jobs) {
|
|
40
|
-
const poolJobs = jobsByPool.get(job.options.pool)
|
|
41
|
-
if (poolJobs) {
|
|
42
|
-
poolJobs.push(job)
|
|
43
|
-
} else {
|
|
44
|
-
jobsByPool.set(job.options.pool, [job])
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return jobsByPool
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function assertActiveJobPoolsConfigured(
|
|
52
|
-
jobsByPool: Map<string, AnyJob[]>,
|
|
53
|
-
pools: JobsPoolConfig,
|
|
54
|
-
) {
|
|
55
|
-
const missingPoolMessages = [...jobsByPool]
|
|
56
|
-
.filter(([poolName]) => !pools[poolName])
|
|
57
|
-
.flatMap(([poolName, jobs]) =>
|
|
58
|
-
jobs.map((job) => `${job.name} -> ${poolName}`),
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
if (missingPoolMessages.length > 0) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`Invalid jobs pool configuration: missing pool config for jobs: ${missingPoolMessages.join(', ')}`,
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* ApplicationServerJobs manages job worker pools and BullMQ queue workers.
|
|
70
|
-
*
|
|
71
|
-
* Changes from the old Pool-based implementation:
|
|
72
|
-
* - Uses JobRunnersPool (extends WorkerPool) for worker management
|
|
73
|
-
* - Uses ManagedWorker for restart logic and state tracking
|
|
74
|
-
* - Integrates with ErrorPolicy for restart decisions
|
|
75
|
-
* - Proper health tracking per job pool
|
|
76
|
-
*/
|
|
77
|
-
export class ApplicationServerJobs {
|
|
78
|
-
/**
|
|
79
|
-
* BullMQ workers - one per job (dedicated queues)
|
|
80
|
-
*/
|
|
81
|
-
queueWorkers = new Set<Worker>()
|
|
82
|
-
|
|
83
|
-
jobs: Map<string, AnyJob>
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Shared resource pools by configured pool name.
|
|
87
|
-
* All jobs using the same pool name share the same pool for resource management.
|
|
88
|
-
*/
|
|
89
|
-
protected pools = new Map<string, JobRunnersPool>()
|
|
90
|
-
|
|
91
|
-
constructor(
|
|
92
|
-
readonly params: {
|
|
93
|
-
logger: Logger
|
|
94
|
-
serverConfig: ServerConfig
|
|
95
|
-
workerConfig: ApplicationServerWorkerConfig
|
|
96
|
-
store: Store
|
|
97
|
-
errorPolicy: ErrorPolicy
|
|
98
|
-
workerFactory: ManagedWorkerFactory
|
|
99
|
-
poolFactory: WorkerPoolFactory
|
|
100
|
-
},
|
|
101
|
-
) {
|
|
102
|
-
this.jobs = params.serverConfig.jobs
|
|
103
|
-
? params.serverConfig.jobs.jobs
|
|
104
|
-
: new Map()
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async start() {
|
|
108
|
-
const {
|
|
109
|
-
logger,
|
|
110
|
-
serverConfig,
|
|
111
|
-
workerConfig,
|
|
112
|
-
store,
|
|
113
|
-
errorPolicy,
|
|
114
|
-
workerFactory,
|
|
115
|
-
} = this.params
|
|
116
|
-
const jobsConfig = serverConfig.jobs
|
|
117
|
-
|
|
118
|
-
if (!jobsConfig) {
|
|
119
|
-
logger.debug('Jobs are not configured, skipping')
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const jobsByPool = getJobsByPool(this.jobs.values())
|
|
124
|
-
assertActiveJobPoolsConfigured(jobsByPool, jobsConfig.pools)
|
|
125
|
-
|
|
126
|
-
// Step 1: Initialize shared resource pools used by active jobs
|
|
127
|
-
for (const poolName of jobsByPool.keys()) {
|
|
128
|
-
const poolConfig = jobsConfig.pools[poolName]!
|
|
129
|
-
|
|
130
|
-
// Create a JobRunnersPool for this pool
|
|
131
|
-
const config: WorkerPoolConfig = {
|
|
132
|
-
name: `job-pool-${poolName}`,
|
|
133
|
-
workerType: WorkerType.Job,
|
|
134
|
-
path: workerConfig.path,
|
|
135
|
-
workerData: { ...workerConfig.workerData },
|
|
136
|
-
onWorker: workerConfig.worker,
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const pool = new JobRunnersPool(
|
|
140
|
-
config,
|
|
141
|
-
errorPolicy,
|
|
142
|
-
workerFactory,
|
|
143
|
-
logger,
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
// Add workers to the pool
|
|
147
|
-
for (let i = 0; i < poolConfig.threads; i++) {
|
|
148
|
-
pool.add({ runtime: { type: 'jobs', jobWorkerPool: poolName } }, i)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
await pool.startAll()
|
|
152
|
-
this.pools.set(poolName, pool)
|
|
153
|
-
|
|
154
|
-
logger.info(
|
|
155
|
-
{
|
|
156
|
-
pool: poolName,
|
|
157
|
-
threads: poolConfig.threads,
|
|
158
|
-
jobsPerThread: poolConfig.jobs,
|
|
159
|
-
},
|
|
160
|
-
'Job runner pool started',
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Step 2: Create a dedicated BullMQ Worker for each job
|
|
165
|
-
// Calculate how many jobs use each pool for fair concurrency distribution
|
|
166
|
-
for (const job of this.jobs.values()) {
|
|
167
|
-
const queueName = getJobQueueName(job)
|
|
168
|
-
const poolName = job.options.pool
|
|
169
|
-
const pool = this.pools.get(poolName)
|
|
170
|
-
|
|
171
|
-
if (!pool) {
|
|
172
|
-
throw new Error(`Job "${job.name}" pool "${poolName}" is not started`)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const poolConfig = jobsConfig.pools[poolName]!
|
|
176
|
-
const poolCapacity = poolConfig.threads * poolConfig.jobs
|
|
177
|
-
const jobCountInPool = jobsByPool.get(poolName)?.length ?? 1
|
|
178
|
-
// Use job-specific concurrency if provided, otherwise distribute pool capacity evenly
|
|
179
|
-
const defaultConcurrency = Math.max(
|
|
180
|
-
1,
|
|
181
|
-
Math.floor(poolCapacity / jobCountInPool),
|
|
182
|
-
)
|
|
183
|
-
const concurrency = job.options.concurrency ?? defaultConcurrency
|
|
184
|
-
|
|
185
|
-
const queueWorker = new Worker(
|
|
186
|
-
queueName,
|
|
187
|
-
async (bullJob) => {
|
|
188
|
-
const task: WorkerJobTask = {
|
|
189
|
-
jobId: String(bullJob.id ?? ''),
|
|
190
|
-
jobName: bullJob.name,
|
|
191
|
-
data: bullJob.data,
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const result = await pool.run(task)
|
|
195
|
-
switch (result.type) {
|
|
196
|
-
case 'success':
|
|
197
|
-
return result.result
|
|
198
|
-
case 'unrecoverable_error': {
|
|
199
|
-
const error = enrichBullMqErrorStack(result.error)
|
|
200
|
-
const unrecoverableError = new UnrecoverableError(error.message)
|
|
201
|
-
unrecoverableError.stack = error.stack
|
|
202
|
-
throw unrecoverableError
|
|
203
|
-
}
|
|
204
|
-
case 'job_not_found':
|
|
205
|
-
case 'queue_job_not_found':
|
|
206
|
-
throw new UnrecoverableError(result.type)
|
|
207
|
-
case 'error':
|
|
208
|
-
console.error(result.error)
|
|
209
|
-
throw enrichBullMqErrorStack(result.error)
|
|
210
|
-
default:
|
|
211
|
-
throw new UnrecoverableError('Unknown job task result')
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
{ connection: store as unknown as RedisClient, concurrency },
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
this.queueWorkers.add(queueWorker)
|
|
218
|
-
logger.info(
|
|
219
|
-
{ job: job.name, queue: queueName, pool: poolName, concurrency },
|
|
220
|
-
'Job queue worker started',
|
|
221
|
-
)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async stop() {
|
|
226
|
-
const { logger } = this.params
|
|
227
|
-
|
|
228
|
-
// Force-close BullMQ workers immediately — stops accepting new jobs
|
|
229
|
-
// and doesn't wait for in-flight processor callbacks.
|
|
230
|
-
// We must use force=true on the first call because BullMQ caches the
|
|
231
|
-
// closing promise: a second call with force=true returns the original
|
|
232
|
-
// (non-force) promise, making it ineffective.
|
|
233
|
-
// Interrupted jobs will be retried via BullMQ's stalled job mechanism.
|
|
234
|
-
await Promise.all(
|
|
235
|
-
[...this.queueWorkers].map(async (worker) => {
|
|
236
|
-
try {
|
|
237
|
-
await worker.close(true)
|
|
238
|
-
} catch (error) {
|
|
239
|
-
logger.warn({ error }, 'Failed to close BullMQ worker')
|
|
240
|
-
}
|
|
241
|
-
}),
|
|
242
|
-
)
|
|
243
|
-
this.queueWorkers.clear()
|
|
244
|
-
|
|
245
|
-
// Stop job runner thread pools — sends 'stop' to threads, waits for
|
|
246
|
-
// graceful exit with timeout, force-kills if needed.
|
|
247
|
-
// Pending pool.run() calls will be rejected by ManagedWorker.stop().
|
|
248
|
-
await Promise.all(
|
|
249
|
-
Array.from(this.pools.values()).map(async (pool) => {
|
|
250
|
-
try {
|
|
251
|
-
await pool.stopAll()
|
|
252
|
-
} catch (error) {
|
|
253
|
-
logger.warn({ error }, 'Failed to stop job pool')
|
|
254
|
-
}
|
|
255
|
-
}),
|
|
256
|
-
)
|
|
257
|
-
this.pools.clear()
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Get a job pool by name.
|
|
262
|
-
*/
|
|
263
|
-
getPool(poolName: string): JobRunnersPool | undefined {
|
|
264
|
-
return this.pools.get(poolName)
|
|
265
|
-
}
|
|
266
|
-
}
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import EventEmitter from 'node:events'
|
|
2
|
-
|
|
3
|
-
import type { Logger } from '@nmtjs/core'
|
|
4
|
-
|
|
5
|
-
import type { RuntimeEnvironment } from './environment.ts'
|
|
6
|
-
import type { ApplicationServer } from './server.ts'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* States in the server lifecycle state machine.
|
|
10
|
-
*/
|
|
11
|
-
export type LifecycleState =
|
|
12
|
-
| 'idle'
|
|
13
|
-
| 'starting'
|
|
14
|
-
| 'running'
|
|
15
|
-
| 'reloading'
|
|
16
|
-
| 'stopping'
|
|
17
|
-
| 'stopped'
|
|
18
|
-
| 'failed'
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Events emitted by the ServerLifecycle.
|
|
22
|
-
*/
|
|
23
|
-
export interface LifecycleEvents {
|
|
24
|
-
'state-change': [from: LifecycleState, to: LifecycleState]
|
|
25
|
-
error: [error: Error, handled: boolean]
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Factory function type for creating ApplicationServer instances.
|
|
30
|
-
*/
|
|
31
|
-
export type ServerFactory = () => ApplicationServer | Promise<ApplicationServer>
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* ServerLifecycle manages the application server lifecycle with a clean state machine.
|
|
35
|
-
*
|
|
36
|
-
* States:
|
|
37
|
-
* - idle: Initial state, server not started
|
|
38
|
-
* - starting: Server is being started
|
|
39
|
-
* - running: Server is running normally
|
|
40
|
-
* - reloading: Server is being reloaded (stop + start)
|
|
41
|
-
* - stopping: Server is being stopped
|
|
42
|
-
* - stopped: Server has been stopped (terminal state)
|
|
43
|
-
* - failed: Server failed to start (can recover via reload in dev mode)
|
|
44
|
-
*
|
|
45
|
-
* Transitions:
|
|
46
|
-
* - idle -> starting (via start())
|
|
47
|
-
* - starting -> running (success) | failed (error)
|
|
48
|
-
* - running -> reloading (via reload()) | stopping (via stop())
|
|
49
|
-
* - reloading -> running (success) | failed (error)
|
|
50
|
-
* - failed -> reloading (via reload()) | stopping (via stop())
|
|
51
|
-
* - stopping -> stopped
|
|
52
|
-
*/
|
|
53
|
-
export class ServerLifecycle extends EventEmitter<LifecycleEvents> {
|
|
54
|
-
private state: LifecycleState = 'idle'
|
|
55
|
-
private server: ApplicationServer | null = null
|
|
56
|
-
private logger: Logger
|
|
57
|
-
|
|
58
|
-
constructor(
|
|
59
|
-
private readonly env: RuntimeEnvironment,
|
|
60
|
-
private readonly createServer: ServerFactory,
|
|
61
|
-
logger: Logger,
|
|
62
|
-
) {
|
|
63
|
-
super()
|
|
64
|
-
this.logger = logger.child({ component: 'ServerLifecycle' })
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Current lifecycle state */
|
|
68
|
-
get currentState(): LifecycleState {
|
|
69
|
-
return this.state
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Whether the server is currently running */
|
|
73
|
-
get isRunning(): boolean {
|
|
74
|
-
return this.state === 'running'
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Whether the server can be started */
|
|
78
|
-
get canStart(): boolean {
|
|
79
|
-
return this.state === 'idle' || this.state === 'failed'
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Whether the server can be stopped */
|
|
83
|
-
get canStop(): boolean {
|
|
84
|
-
return (
|
|
85
|
-
this.state === 'running' ||
|
|
86
|
-
this.state === 'failed' ||
|
|
87
|
-
this.state === 'reloading'
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Whether the server can be reloaded */
|
|
92
|
-
get canReload(): boolean {
|
|
93
|
-
return this.state === 'running' || this.state === 'failed'
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Get the current server instance (if running) */
|
|
97
|
-
getServer(): ApplicationServer | null {
|
|
98
|
-
return this.server
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Start the server.
|
|
103
|
-
* Can only be called from 'idle' or 'failed' states.
|
|
104
|
-
*/
|
|
105
|
-
async start(): Promise<void> {
|
|
106
|
-
this.assertState('idle', 'failed')
|
|
107
|
-
this.transition('starting')
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
this.server = await this.createServer()
|
|
111
|
-
await this.server.start()
|
|
112
|
-
this.transition('running')
|
|
113
|
-
} catch (error) {
|
|
114
|
-
this.transition('failed')
|
|
115
|
-
await this.handleStartupError(error as Error)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Stop the server.
|
|
121
|
-
* Can be called from 'running', 'failed', or 'reloading' states.
|
|
122
|
-
* No-op if already stopped or idle.
|
|
123
|
-
*/
|
|
124
|
-
async stop(): Promise<void> {
|
|
125
|
-
if (this.state === 'stopped' || this.state === 'idle') return
|
|
126
|
-
this.assertState('running', 'failed', 'reloading')
|
|
127
|
-
|
|
128
|
-
this.transition('stopping')
|
|
129
|
-
try {
|
|
130
|
-
await this.server?.stop()
|
|
131
|
-
} catch (error) {
|
|
132
|
-
this.logger.error(
|
|
133
|
-
new Error('Error while stopping server (continuing shutdown)', {
|
|
134
|
-
cause: error,
|
|
135
|
-
}),
|
|
136
|
-
)
|
|
137
|
-
} finally {
|
|
138
|
-
this.server = null
|
|
139
|
-
this.transition('stopped')
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Reload the server (stop current instance and start a new one).
|
|
145
|
-
* Can be called from 'running' or 'failed' states.
|
|
146
|
-
*/
|
|
147
|
-
async reload(): Promise<void> {
|
|
148
|
-
this.assertState('running', 'failed')
|
|
149
|
-
this.transition('reloading')
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
// Stop current server if exists
|
|
153
|
-
if (this.server) {
|
|
154
|
-
await this.server.stop()
|
|
155
|
-
this.server = null
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Create and start new server
|
|
159
|
-
this.server = await this.createServer()
|
|
160
|
-
await this.server.start()
|
|
161
|
-
this.transition('running')
|
|
162
|
-
} catch (error) {
|
|
163
|
-
this.server = null
|
|
164
|
-
this.transition('failed')
|
|
165
|
-
await this.handleStartupError(error as Error)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Handle startup/reload error according to the error policy.
|
|
171
|
-
*/
|
|
172
|
-
private async handleStartupError(error: Error): Promise<void> {
|
|
173
|
-
const action = this.env.errorPolicy.onStartupError(error)
|
|
174
|
-
const handled = action.type !== 'exit'
|
|
175
|
-
|
|
176
|
-
this.emit('error', error, handled)
|
|
177
|
-
|
|
178
|
-
switch (action.type) {
|
|
179
|
-
case 'exit':
|
|
180
|
-
this.logger.fatal(
|
|
181
|
-
new Error('Startup failed, exiting', { cause: error }),
|
|
182
|
-
)
|
|
183
|
-
process.exit(action.code)
|
|
184
|
-
break
|
|
185
|
-
case 'wait':
|
|
186
|
-
// Dev mode: stay alive, HMR will call reload()
|
|
187
|
-
this.logger.error(
|
|
188
|
-
new Error('Startup failed, waiting for fix via HMR', {
|
|
189
|
-
cause: error,
|
|
190
|
-
}),
|
|
191
|
-
)
|
|
192
|
-
break
|
|
193
|
-
case 'restart':
|
|
194
|
-
this.logger.warn(
|
|
195
|
-
{ error, delay: action.delay },
|
|
196
|
-
'Startup failed, scheduling restart',
|
|
197
|
-
)
|
|
198
|
-
setTimeout(() => {
|
|
199
|
-
// Only restart if still in failed state
|
|
200
|
-
if (this.state === 'failed') {
|
|
201
|
-
this.start().catch((e) => {
|
|
202
|
-
this.logger.error(new Error('Restart failed', { cause: e }))
|
|
203
|
-
})
|
|
204
|
-
}
|
|
205
|
-
}, action.delay)
|
|
206
|
-
break
|
|
207
|
-
case 'ignore':
|
|
208
|
-
this.logger.warn({ error }, 'Startup failed, ignoring')
|
|
209
|
-
break
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Transition to a new state, emitting the state-change event.
|
|
215
|
-
*/
|
|
216
|
-
private transition(to: LifecycleState): void {
|
|
217
|
-
const from = this.state
|
|
218
|
-
this.state = to
|
|
219
|
-
this.logger.debug({ from, to }, 'State transition')
|
|
220
|
-
this.emit('state-change', from, to)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Assert that the current state is one of the allowed states.
|
|
225
|
-
* Throws if not.
|
|
226
|
-
*/
|
|
227
|
-
private assertState(...allowed: LifecycleState[]): void {
|
|
228
|
-
if (!allowed.includes(this.state)) {
|
|
229
|
-
throw new Error(
|
|
230
|
-
`Invalid state: ${this.state}, expected one of: ${allowed.join(', ')}`,
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|