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,275 +0,0 @@
|
|
|
1
|
-
import EventEmitter from 'node:events'
|
|
2
|
-
|
|
3
|
-
import type { Logger } from '@nmtjs/core'
|
|
4
|
-
|
|
5
|
-
import type { WorkerType } from '../enums.ts'
|
|
6
|
-
import type { JobTaskResult, WorkerJobTask } from '../types.ts'
|
|
7
|
-
import type { ErrorPolicy } from './error-policy.ts'
|
|
8
|
-
import type {
|
|
9
|
-
ManagedWorker,
|
|
10
|
-
ManagedWorkerConfig,
|
|
11
|
-
WorkerState,
|
|
12
|
-
} from './managed-worker.ts'
|
|
13
|
-
import type { WorkerRegistration } from './types.ts'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Health states for a worker pool.
|
|
17
|
-
*/
|
|
18
|
-
export type PoolHealth = 'healthy' | 'degraded' | 'unhealthy'
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Report on pool health status.
|
|
22
|
-
*/
|
|
23
|
-
export interface PoolHealthReport {
|
|
24
|
-
poolName: string
|
|
25
|
-
overall: PoolHealth
|
|
26
|
-
healthy: number
|
|
27
|
-
total: number
|
|
28
|
-
workers: Array<{
|
|
29
|
-
id: string
|
|
30
|
-
state: WorkerState
|
|
31
|
-
consecutiveFailures: number
|
|
32
|
-
totalFailures: number
|
|
33
|
-
}>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Configuration for creating a worker pool.
|
|
38
|
-
*/
|
|
39
|
-
export interface WorkerPoolConfig {
|
|
40
|
-
name: string
|
|
41
|
-
workerType: WorkerType
|
|
42
|
-
path: string
|
|
43
|
-
workerData?: any
|
|
44
|
-
onWorker?: (registration: WorkerRegistration) => void
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Events emitted by WorkerPool.
|
|
49
|
-
*/
|
|
50
|
-
export interface WorkerPoolEvents {
|
|
51
|
-
'health-change': [health: PoolHealth]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Factory function for creating ManagedWorker instances.
|
|
56
|
-
* This allows for dependency injection and testing.
|
|
57
|
-
*/
|
|
58
|
-
export type ManagedWorkerFactory = (
|
|
59
|
-
config: ManagedWorkerConfig,
|
|
60
|
-
policy: ErrorPolicy,
|
|
61
|
-
logger: Logger,
|
|
62
|
-
) => ManagedWorker
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Factory function for creating WorkerPool instances.
|
|
66
|
-
* This allows for dependency injection and testing.
|
|
67
|
-
*/
|
|
68
|
-
export type WorkerPoolFactory = (
|
|
69
|
-
config: WorkerPoolConfig,
|
|
70
|
-
policy: ErrorPolicy,
|
|
71
|
-
workerFactory: ManagedWorkerFactory,
|
|
72
|
-
logger: Logger,
|
|
73
|
-
) => WorkerPool
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* WorkerPool manages a collection of workers for a single application or job pool.
|
|
77
|
-
*
|
|
78
|
-
* Features:
|
|
79
|
-
* - Health tracking with three states: healthy, degraded, unhealthy
|
|
80
|
-
* - Integration with ErrorPolicy for degraded mode decisions
|
|
81
|
-
* - Worker lifecycle management (start, stop)
|
|
82
|
-
* - Health reports for debugging and monitoring
|
|
83
|
-
*
|
|
84
|
-
* Each application gets its own WorkerPool for isolated failure domains.
|
|
85
|
-
*/
|
|
86
|
-
export class WorkerPool extends EventEmitter<WorkerPoolEvents> {
|
|
87
|
-
private workers: ManagedWorker[] = []
|
|
88
|
-
private health: PoolHealth = 'healthy'
|
|
89
|
-
private logger: Logger
|
|
90
|
-
|
|
91
|
-
constructor(
|
|
92
|
-
readonly config: WorkerPoolConfig,
|
|
93
|
-
private readonly policy: ErrorPolicy,
|
|
94
|
-
private readonly workerFactory: ManagedWorkerFactory,
|
|
95
|
-
logger: Logger,
|
|
96
|
-
) {
|
|
97
|
-
super()
|
|
98
|
-
this.logger = logger.child({ component: 'WorkerPool', pool: config.name })
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Current pool health */
|
|
102
|
-
get currentHealth(): PoolHealth {
|
|
103
|
-
return this.health
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Total number of workers in the pool */
|
|
107
|
-
get workerCount(): number {
|
|
108
|
-
return this.workers.length
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Number of healthy (ready) workers */
|
|
112
|
-
get healthyCount(): number {
|
|
113
|
-
return this.workers.filter((w) => w.isHealthy).length
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Get all workers in this pool */
|
|
117
|
-
getWorkers(): readonly ManagedWorker[] {
|
|
118
|
-
return this.workers
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Add a worker to the pool.
|
|
123
|
-
*
|
|
124
|
-
* @param workerData - Additional data to pass to the worker
|
|
125
|
-
* @param index - Worker index (used for naming)
|
|
126
|
-
* @returns The created ManagedWorker
|
|
127
|
-
*/
|
|
128
|
-
add(workerData: any, index: number): ManagedWorker {
|
|
129
|
-
const { config } = this
|
|
130
|
-
|
|
131
|
-
const workerConfig: ManagedWorkerConfig = {
|
|
132
|
-
id: `${config.name}-${index + 1}`,
|
|
133
|
-
name: config.name,
|
|
134
|
-
index,
|
|
135
|
-
workerType: config.workerType,
|
|
136
|
-
path: config.path,
|
|
137
|
-
workerData: { ...config.workerData, ...workerData },
|
|
138
|
-
onWorker: config.onWorker,
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const worker = this.workerFactory(workerConfig, this.policy, this.logger)
|
|
142
|
-
|
|
143
|
-
worker.on('state-change', () => this.updateHealth())
|
|
144
|
-
|
|
145
|
-
this.workers.push(worker)
|
|
146
|
-
return worker
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Start all workers in the pool.
|
|
151
|
-
*/
|
|
152
|
-
async startAll(): Promise<void> {
|
|
153
|
-
this.logger.debug({ count: this.workers.length }, 'Starting all workers')
|
|
154
|
-
await Promise.all(this.workers.map((w) => w.start()))
|
|
155
|
-
this.logger.debug({ count: this.workers.length }, 'All workers started')
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Restart workers that are in error state.
|
|
160
|
-
* Called when HMR update comes in and there are failed workers.
|
|
161
|
-
* @returns Number of workers that were restarted
|
|
162
|
-
*/
|
|
163
|
-
async restartFailedWorkers(): Promise<number> {
|
|
164
|
-
const failedWorkers = this.workers.filter((w) => w.currentState === 'error')
|
|
165
|
-
if (failedWorkers.length === 0) return 0
|
|
166
|
-
|
|
167
|
-
this.logger.debug(
|
|
168
|
-
{ count: failedWorkers.length },
|
|
169
|
-
'Restarting failed workers after HMR update',
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
// Reset failure counts before restarting
|
|
173
|
-
for (const worker of failedWorkers) {
|
|
174
|
-
worker.resetFailureCount()
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
await Promise.all(failedWorkers.map((w) => w.start()))
|
|
178
|
-
return failedWorkers.length
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Stop all workers in the pool and clean up.
|
|
183
|
-
*/
|
|
184
|
-
async stopAll(): Promise<void> {
|
|
185
|
-
this.logger.debug({ count: this.workers.length }, 'Stopping all workers')
|
|
186
|
-
|
|
187
|
-
// Remove all event listeners from workers before stopping
|
|
188
|
-
for (const worker of this.workers) {
|
|
189
|
-
worker.removeAllListeners()
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
await Promise.all(this.workers.map((w) => w.stop()))
|
|
193
|
-
|
|
194
|
-
// Clear workers array
|
|
195
|
-
this.workers = []
|
|
196
|
-
|
|
197
|
-
// Remove all listeners from the pool itself
|
|
198
|
-
this.removeAllListeners()
|
|
199
|
-
|
|
200
|
-
this.logger.debug('All workers stopped and cleaned up')
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Get a health report for this pool.
|
|
205
|
-
*/
|
|
206
|
-
getHealthReport(): PoolHealthReport {
|
|
207
|
-
return {
|
|
208
|
-
poolName: this.config.name,
|
|
209
|
-
overall: this.health,
|
|
210
|
-
healthy: this.healthyCount,
|
|
211
|
-
total: this.workerCount,
|
|
212
|
-
workers: this.workers.map((w) => ({
|
|
213
|
-
id: w.config.id,
|
|
214
|
-
state: w.currentState,
|
|
215
|
-
consecutiveFailures: w.context.consecutiveFailures,
|
|
216
|
-
totalFailures: w.context.totalFailures,
|
|
217
|
-
})),
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Update pool health based on worker states.
|
|
223
|
-
*/
|
|
224
|
-
private updateHealth(): void {
|
|
225
|
-
const readyCount = this.healthyCount
|
|
226
|
-
const total = this.workerCount
|
|
227
|
-
|
|
228
|
-
let newHealth: PoolHealth
|
|
229
|
-
if (readyCount === total) {
|
|
230
|
-
newHealth = 'healthy'
|
|
231
|
-
} else if (readyCount > 0 && this.policy.allowDegradedMode) {
|
|
232
|
-
newHealth = 'degraded'
|
|
233
|
-
} else {
|
|
234
|
-
newHealth = 'unhealthy'
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (newHealth !== this.health) {
|
|
238
|
-
const oldHealth = this.health
|
|
239
|
-
this.health = newHealth
|
|
240
|
-
this.logger.trace(
|
|
241
|
-
{ from: oldHealth, to: newHealth, ready: readyCount, total },
|
|
242
|
-
'Pool health changed',
|
|
243
|
-
)
|
|
244
|
-
this.emit('health-change', newHealth)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* JobRunnersPool extends WorkerPool with round-robin task execution.
|
|
251
|
-
* Used for job worker pools where tasks need to be distributed across workers.
|
|
252
|
-
*/
|
|
253
|
-
export class JobRunnersPool extends WorkerPool {
|
|
254
|
-
private runIndex = 0
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Run a task on the next available worker (round-robin).
|
|
258
|
-
*/
|
|
259
|
-
async run(task: WorkerJobTask): Promise<JobTaskResult> {
|
|
260
|
-
const workers = this.getWorkers()
|
|
261
|
-
const healthyWorkers = workers.filter((w) => w.isHealthy)
|
|
262
|
-
|
|
263
|
-
if (healthyWorkers.length === 0) {
|
|
264
|
-
throw new Error('No healthy job runner threads available')
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (this.runIndex >= healthyWorkers.length) {
|
|
268
|
-
this.runIndex = 0
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const worker = healthyWorkers[this.runIndex]!
|
|
272
|
-
this.runIndex++
|
|
273
|
-
return await worker.run(task)
|
|
274
|
-
}
|
|
275
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { ServerStoreConfig } from '../server/config.ts'
|
|
2
|
-
import type { StoreTypes } from '../types.ts'
|
|
3
|
-
import { StoreType } from '../enums.ts'
|
|
4
|
-
|
|
5
|
-
export async function createStoreClient<T extends ServerStoreConfig>(
|
|
6
|
-
config: T,
|
|
7
|
-
): Promise<StoreTypes[T['type']]> {
|
|
8
|
-
if (config.type === StoreType.Redis) {
|
|
9
|
-
const { Redis } = await import('ioredis').catch(() => {
|
|
10
|
-
throw new Error(
|
|
11
|
-
'ioredis package is not installed. Please install it to use Redis store.',
|
|
12
|
-
)
|
|
13
|
-
})
|
|
14
|
-
return new Redis({
|
|
15
|
-
...config.options,
|
|
16
|
-
lazyConnect: true,
|
|
17
|
-
}) as StoreTypes[T['type']]
|
|
18
|
-
} else if (config.type === StoreType.Valkey) {
|
|
19
|
-
const { Redis } = await import('iovalkey').catch(() => {
|
|
20
|
-
throw new Error(
|
|
21
|
-
'iovalkey package is not installed. Please install it to use Valkey store.',
|
|
22
|
-
)
|
|
23
|
-
})
|
|
24
|
-
return new Redis({
|
|
25
|
-
...config.options,
|
|
26
|
-
lazyConnect: true,
|
|
27
|
-
}) as StoreTypes[T['type']]
|
|
28
|
-
}
|
|
29
|
-
throw new Error('Unsupported store')
|
|
30
|
-
}
|
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { createHash } from 'node:crypto'
|
|
3
|
-
import { PassThrough, Readable } from 'node:stream'
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
SubcriptionOptions,
|
|
7
|
-
TAnyEventContract,
|
|
8
|
-
TAnySubscriptionContract,
|
|
9
|
-
} from '@nmtjs/contract'
|
|
10
|
-
import type { Logger } from '@nmtjs/core'
|
|
11
|
-
import type { t } from '@nmtjs/type'
|
|
12
|
-
import { isAbortError } from '@nmtjs/common'
|
|
13
|
-
|
|
14
|
-
export type SubscriptionAdapterEvent = { channel: string; payload: any }
|
|
15
|
-
|
|
16
|
-
export interface SubscriptionAdapterType {
|
|
17
|
-
publish(channel: string, payload: any): Promise<boolean>
|
|
18
|
-
subscribe(
|
|
19
|
-
channel: string,
|
|
20
|
-
signal?: AbortSignal,
|
|
21
|
-
): AsyncGenerator<SubscriptionAdapterEvent>
|
|
22
|
-
initialize(): Promise<void>
|
|
23
|
-
dispose(): Promise<void>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type SubscriptionChannel = {
|
|
27
|
-
stream: Readable
|
|
28
|
-
subscription: TAnySubscriptionContract
|
|
29
|
-
event: TAnyEventContract
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export type SubscribeFn = <
|
|
33
|
-
Contract extends TAnySubscriptionContract,
|
|
34
|
-
Events extends {
|
|
35
|
-
[K in keyof Contract['events']]?: true
|
|
36
|
-
},
|
|
37
|
-
>(
|
|
38
|
-
subscription: Contract,
|
|
39
|
-
events: Events,
|
|
40
|
-
options: Contract['options'],
|
|
41
|
-
signal?: AbortSignal,
|
|
42
|
-
) => Promise<
|
|
43
|
-
Omit<Readable, typeof Symbol.asyncIterator> & {
|
|
44
|
-
[Symbol.asyncIterator]: () => AsyncIterator<
|
|
45
|
-
{} extends Events
|
|
46
|
-
? {
|
|
47
|
-
[K in keyof Contract['events']]: {
|
|
48
|
-
event: K
|
|
49
|
-
data: t.infer.decode.output<Contract['events'][K]['payload']>
|
|
50
|
-
}
|
|
51
|
-
}[keyof Contract['events']]
|
|
52
|
-
: {
|
|
53
|
-
[K in keyof Events]: K extends keyof Contract['events']
|
|
54
|
-
? {
|
|
55
|
-
event: K
|
|
56
|
-
data: t.infer.decode.output<Contract['events'][K]['payload']>
|
|
57
|
-
}
|
|
58
|
-
: never
|
|
59
|
-
}[keyof Events]
|
|
60
|
-
>
|
|
61
|
-
}
|
|
62
|
-
>
|
|
63
|
-
|
|
64
|
-
export type PublishFn = <
|
|
65
|
-
S extends TAnySubscriptionContract,
|
|
66
|
-
E extends S['events'][keyof S['events']],
|
|
67
|
-
>(
|
|
68
|
-
event: E,
|
|
69
|
-
options: S['options'],
|
|
70
|
-
data: t.infer.decode.input<E['payload']>,
|
|
71
|
-
) => Promise<boolean>
|
|
72
|
-
|
|
73
|
-
export type SubscriptionManagerOptions = {
|
|
74
|
-
logger: Logger
|
|
75
|
-
adapter: SubscriptionAdapterType
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export class SubscriptionManager {
|
|
79
|
-
readonly subscriptions = new Map<string, SubscriptionChannel>()
|
|
80
|
-
protected readonly logger: Logger
|
|
81
|
-
|
|
82
|
-
constructor(protected readonly options: SubscriptionManagerOptions) {
|
|
83
|
-
this.logger = options.logger.child({ component: SubscriptionManager.name })
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
subscribe: SubscribeFn = async (subscription, events, options, signal) => {
|
|
87
|
-
const eventKeys =
|
|
88
|
-
Object.keys(events).length === 0
|
|
89
|
-
? Object.keys(subscription.events)
|
|
90
|
-
: Object.keys(events)
|
|
91
|
-
|
|
92
|
-
this.logger.debug(
|
|
93
|
-
{ subscription: subscription.name, eventCount: eventKeys.length },
|
|
94
|
-
'Opening subscription',
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
const streams = Array(eventKeys.length)
|
|
98
|
-
|
|
99
|
-
for (const index in eventKeys) {
|
|
100
|
-
const event = subscription.events[eventKeys[index]]
|
|
101
|
-
const channel = getChannelName(event, options)
|
|
102
|
-
if (this.subscriptions.has(channel)) {
|
|
103
|
-
streams[index] = this.subscriptions.get(channel)!.stream
|
|
104
|
-
this.logger.trace(
|
|
105
|
-
{
|
|
106
|
-
channel,
|
|
107
|
-
event: event.name,
|
|
108
|
-
activeChannels: this.subscriptions.size,
|
|
109
|
-
},
|
|
110
|
-
'Reusing pubsub channel stream',
|
|
111
|
-
)
|
|
112
|
-
} else {
|
|
113
|
-
const iterable = this.options.adapter.subscribe(channel, signal)
|
|
114
|
-
const stream = this.createEventStream(iterable)
|
|
115
|
-
stream.on('close', () => {
|
|
116
|
-
this.subscriptions.delete(channel)
|
|
117
|
-
this.logger.debug(
|
|
118
|
-
{
|
|
119
|
-
channel,
|
|
120
|
-
event: event.name,
|
|
121
|
-
activeChannels: this.subscriptions.size,
|
|
122
|
-
},
|
|
123
|
-
'Pubsub channel stream closed',
|
|
124
|
-
)
|
|
125
|
-
})
|
|
126
|
-
stream.on('error', (error) => {
|
|
127
|
-
this.logger.warn(
|
|
128
|
-
{ channel, event: event.name, error },
|
|
129
|
-
'Pubsub channel stream failed',
|
|
130
|
-
)
|
|
131
|
-
})
|
|
132
|
-
streams[index] = stream
|
|
133
|
-
this.subscriptions.set(channel, { subscription, event, stream })
|
|
134
|
-
this.logger.debug(
|
|
135
|
-
{
|
|
136
|
-
channel,
|
|
137
|
-
event: event.name,
|
|
138
|
-
activeChannels: this.subscriptions.size,
|
|
139
|
-
},
|
|
140
|
-
'Creating channel stream',
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const mergedStream = mergeEventStreams(streams, signal)
|
|
146
|
-
|
|
147
|
-
mergedStream.once('close', () => {
|
|
148
|
-
this.logger.debug(
|
|
149
|
-
{ subscription: subscription.name, eventCount: eventKeys.length },
|
|
150
|
-
'Pubsub subscription stream closed',
|
|
151
|
-
)
|
|
152
|
-
})
|
|
153
|
-
mergedStream.once('error', (error) => {
|
|
154
|
-
this.logger.warn(
|
|
155
|
-
{
|
|
156
|
-
subscription: subscription.name,
|
|
157
|
-
eventCount: eventKeys.length,
|
|
158
|
-
error,
|
|
159
|
-
},
|
|
160
|
-
'Pubsub subscription stream failed',
|
|
161
|
-
)
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
return mergedStream
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
publish: PublishFn = async (event, options, data) => {
|
|
168
|
-
const channel = getChannelName(event, options)
|
|
169
|
-
|
|
170
|
-
this.logger.trace({ channel, event: event.name }, 'Publishing pubsub event')
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
const payload = event.payload.encode(data)
|
|
174
|
-
const published = await this.options.adapter.publish(channel, payload)
|
|
175
|
-
|
|
176
|
-
if (published) {
|
|
177
|
-
this.logger.trace(
|
|
178
|
-
{ channel, event: event.name },
|
|
179
|
-
'Published pubsub event',
|
|
180
|
-
)
|
|
181
|
-
} else {
|
|
182
|
-
this.logger.warn(
|
|
183
|
-
{ channel, event: event.name },
|
|
184
|
-
'Pubsub adapter reported publish failure',
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return published
|
|
189
|
-
} catch (error: any) {
|
|
190
|
-
this.logger.error(
|
|
191
|
-
{ channel, event: event.name, error },
|
|
192
|
-
'Failed to publish pubsub event',
|
|
193
|
-
)
|
|
194
|
-
throw error
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async dispose(): Promise<void> {
|
|
199
|
-
for (const { stream } of this.subscriptions.values()) {
|
|
200
|
-
stream.destroy()
|
|
201
|
-
}
|
|
202
|
-
this.subscriptions.clear()
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private createEventStream(
|
|
206
|
-
iterable: AsyncGenerator<SubscriptionAdapterEvent>,
|
|
207
|
-
): Readable {
|
|
208
|
-
const { subscriptions } = this
|
|
209
|
-
const logger = this.logger
|
|
210
|
-
|
|
211
|
-
return new Readable({
|
|
212
|
-
objectMode: true,
|
|
213
|
-
read() {
|
|
214
|
-
iterable.next().then(
|
|
215
|
-
({ value, done }) => {
|
|
216
|
-
if (done) {
|
|
217
|
-
logger.trace('Pubsub adapter stream ended')
|
|
218
|
-
this.push(null)
|
|
219
|
-
} else {
|
|
220
|
-
const subscription = subscriptions.get(value.channel)
|
|
221
|
-
if (subscription) {
|
|
222
|
-
const { event } = subscription
|
|
223
|
-
try {
|
|
224
|
-
const data = event.payload.decode(value.payload)
|
|
225
|
-
logger.trace(
|
|
226
|
-
{ channel: value.channel, event: event.name },
|
|
227
|
-
'Received event',
|
|
228
|
-
)
|
|
229
|
-
this.push({ event: event.name, data })
|
|
230
|
-
} catch (error: any) {
|
|
231
|
-
logger.warn(
|
|
232
|
-
{ channel: value.channel, event: event.name, error },
|
|
233
|
-
'Failed to decode pubsub event payload',
|
|
234
|
-
)
|
|
235
|
-
this.destroy(error)
|
|
236
|
-
}
|
|
237
|
-
} else {
|
|
238
|
-
logger.trace(
|
|
239
|
-
{ channel: value.channel },
|
|
240
|
-
'Dropped pubsub event for inactive channel',
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
(error) => {
|
|
246
|
-
if (isAbortError(error)) {
|
|
247
|
-
logger.trace('Pubsub adapter stream aborted')
|
|
248
|
-
this.push(null)
|
|
249
|
-
} else {
|
|
250
|
-
logger.warn({ error }, 'Pubsub adapter stream failed')
|
|
251
|
-
this.destroy(error)
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
)
|
|
255
|
-
},
|
|
256
|
-
})
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function concat(...args: any) {
|
|
261
|
-
return args.filter(Boolean).join('/')
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function getChannelName<T extends TAnyEventContract>(
|
|
265
|
-
contract: T,
|
|
266
|
-
options: T['options'],
|
|
267
|
-
) {
|
|
268
|
-
const key = options ? serializerOptions(options) : ''
|
|
269
|
-
assert(contract.name, 'Event contract must have a name')
|
|
270
|
-
return concat(contract.name, key)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function serializerOptions(options: Exclude<SubcriptionOptions, null>): string {
|
|
274
|
-
const hash = createHash('sha1')
|
|
275
|
-
const serialized = Object.entries(options)
|
|
276
|
-
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
277
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
278
|
-
.join(';')
|
|
279
|
-
hash.update(serialized)
|
|
280
|
-
return hash.digest('base64url')
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function mergeEventStreams(
|
|
284
|
-
streams: Readable[],
|
|
285
|
-
signal?: AbortSignal,
|
|
286
|
-
): Readable {
|
|
287
|
-
const destination = new PassThrough({
|
|
288
|
-
signal,
|
|
289
|
-
objectMode: true,
|
|
290
|
-
readableObjectMode: true,
|
|
291
|
-
writableObjectMode: true,
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
let ended = 0
|
|
295
|
-
|
|
296
|
-
for (const source of streams) {
|
|
297
|
-
source.pipe(destination, { end: false })
|
|
298
|
-
source.once('end', () => {
|
|
299
|
-
ended++
|
|
300
|
-
if (ended === streams.length) {
|
|
301
|
-
destination.end()
|
|
302
|
-
}
|
|
303
|
-
})
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return destination
|
|
307
|
-
}
|