nmtjs 0.15.3 → 0.16.0-beta.10

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 (167) hide show
  1. package/dist/cli.js.map +1 -1
  2. package/dist/codegen.js.map +1 -1
  3. package/dist/config.js.map +1 -1
  4. package/dist/entrypoints/main.js.map +1 -1
  5. package/dist/entrypoints/thread.js.map +1 -1
  6. package/dist/entrypoints/worker.js.map +1 -1
  7. package/dist/index.d.ts +32 -21
  8. package/dist/index.js +8 -6
  9. package/dist/index.js.map +1 -1
  10. package/dist/runtime/application/index.d.ts +1 -5
  11. package/dist/runtime/application/index.js +1 -5
  12. package/dist/runtime/application/index.js.map +1 -1
  13. package/dist/runtime/enums.d.ts +1 -12
  14. package/dist/runtime/enums.js +3 -20
  15. package/dist/runtime/enums.js.map +1 -1
  16. package/dist/runtime/hooks.d.ts +1 -4
  17. package/dist/runtime/hooks.js +1 -3
  18. package/dist/runtime/hooks.js.map +1 -1
  19. package/dist/runtime/index.d.ts +3 -5
  20. package/dist/runtime/index.js +3 -5
  21. package/dist/runtime/index.js.map +1 -1
  22. package/dist/runtime/injectables.d.ts +10 -10
  23. package/dist/runtime/injectables.js +6 -6
  24. package/dist/runtime/injectables.js.map +1 -1
  25. package/dist/runtime/jobs/job.d.ts +4 -4
  26. package/dist/runtime/jobs/job.js.map +1 -1
  27. package/dist/runtime/jobs/manager.js.map +1 -1
  28. package/dist/runtime/jobs/router.d.ts +31 -13
  29. package/dist/runtime/jobs/router.js +14 -11
  30. package/dist/runtime/jobs/router.js.map +1 -1
  31. package/dist/runtime/jobs/runner.d.ts +1 -1
  32. package/dist/runtime/jobs/runner.js +1 -1
  33. package/dist/runtime/jobs/runner.js.map +1 -1
  34. package/dist/runtime/jobs/step.js.map +1 -1
  35. package/dist/runtime/metrics/server.js +21 -9
  36. package/dist/runtime/metrics/server.js.map +1 -1
  37. package/dist/runtime/plugin.d.ts +2 -8
  38. package/dist/runtime/plugin.js +1 -3
  39. package/dist/runtime/plugin.js.map +1 -1
  40. package/dist/runtime/runtime.d.ts +2 -2
  41. package/dist/runtime/runtime.js +1 -2
  42. package/dist/runtime/runtime.js.map +1 -1
  43. package/dist/runtime/scheduler/index.js.map +1 -1
  44. package/dist/runtime/server/applications.js.map +1 -1
  45. package/dist/runtime/server/config.d.ts +8 -19
  46. package/dist/runtime/server/config.js +2 -3
  47. package/dist/runtime/server/config.js.map +1 -1
  48. package/dist/runtime/server/environment.js.map +1 -1
  49. package/dist/runtime/server/error-policy.js.map +1 -1
  50. package/dist/runtime/server/hmr-coordinator.js.map +1 -1
  51. package/dist/runtime/server/jobs.d.ts +7 -11
  52. package/dist/runtime/server/jobs.js +45 -71
  53. package/dist/runtime/server/jobs.js.map +1 -1
  54. package/dist/runtime/server/lifecycle.js.map +1 -1
  55. package/dist/runtime/server/managed-worker.js.map +1 -1
  56. package/dist/runtime/server/pool-manager.js.map +1 -1
  57. package/dist/runtime/server/proxy.js.map +1 -1
  58. package/dist/runtime/server/server.js.map +1 -1
  59. package/dist/runtime/server/worker-pool.js.map +1 -1
  60. package/dist/runtime/store/index.js.map +1 -1
  61. package/dist/runtime/{pubsub → subscription}/manager.d.ts +15 -14
  62. package/dist/runtime/{pubsub → subscription}/manager.js +59 -8
  63. package/dist/runtime/subscription/manager.js.map +1 -0
  64. package/dist/runtime/subscription/redis.d.ts +18 -0
  65. package/dist/runtime/{pubsub → subscription}/redis.js +37 -14
  66. package/dist/runtime/subscription/redis.js.map +1 -0
  67. package/dist/runtime/types.d.ts +2 -10
  68. package/dist/runtime/workers/application.d.ts +4 -7
  69. package/dist/runtime/workers/application.js +9 -4
  70. package/dist/runtime/workers/application.js.map +1 -1
  71. package/dist/runtime/workers/base.d.ts +2 -2
  72. package/dist/runtime/workers/base.js +5 -5
  73. package/dist/runtime/workers/base.js.map +1 -1
  74. package/dist/runtime/workers/job.js +2 -1
  75. package/dist/runtime/workers/job.js.map +1 -1
  76. package/dist/vite/builder.js.map +1 -1
  77. package/dist/vite/config.js.map +1 -1
  78. package/dist/vite/plugins.js.map +1 -1
  79. package/dist/vite/runners/worker.js.map +1 -1
  80. package/dist/vite/server.js.map +1 -1
  81. package/dist/vite/servers/main.js.map +1 -1
  82. package/dist/vite/servers/worker.js.map +1 -1
  83. package/package.json +19 -23
  84. package/src/index.ts +14 -8
  85. package/src/runtime/application/index.ts +1 -5
  86. package/src/runtime/enums.ts +2 -14
  87. package/src/runtime/hooks.ts +1 -5
  88. package/src/runtime/index.ts +4 -5
  89. package/src/runtime/injectables.ts +18 -20
  90. package/src/runtime/jobs/job.ts +1 -2
  91. package/src/runtime/jobs/router.ts +52 -24
  92. package/src/runtime/jobs/runner.ts +2 -2
  93. package/src/runtime/metrics/server.ts +21 -18
  94. package/src/runtime/plugin.ts +2 -13
  95. package/src/runtime/runtime.ts +2 -4
  96. package/src/runtime/server/config.ts +12 -18
  97. package/src/runtime/server/jobs.ts +59 -88
  98. package/src/runtime/{pubsub → subscription}/manager.ts +118 -26
  99. package/src/runtime/subscription/redis.ts +157 -0
  100. package/src/runtime/types.ts +2 -11
  101. package/src/runtime/workers/application.ts +29 -17
  102. package/src/runtime/workers/base.ts +7 -7
  103. package/src/runtime/workers/job.ts +3 -6
  104. package/dist/runtime/application/api/api.d.ts +0 -50
  105. package/dist/runtime/application/api/api.js +0 -196
  106. package/dist/runtime/application/api/api.js.map +0 -1
  107. package/dist/runtime/application/api/constants.d.ts +0 -14
  108. package/dist/runtime/application/api/constants.js +0 -8
  109. package/dist/runtime/application/api/constants.js.map +0 -1
  110. package/dist/runtime/application/api/filters.d.ts +0 -14
  111. package/dist/runtime/application/api/filters.js +0 -11
  112. package/dist/runtime/application/api/filters.js.map +0 -1
  113. package/dist/runtime/application/api/guards.d.ts +0 -16
  114. package/dist/runtime/application/api/guards.js +0 -11
  115. package/dist/runtime/application/api/guards.js.map +0 -1
  116. package/dist/runtime/application/api/index.d.ts +0 -9
  117. package/dist/runtime/application/api/index.js +0 -10
  118. package/dist/runtime/application/api/index.js.map +0 -1
  119. package/dist/runtime/application/api/logging.d.ts +0 -19
  120. package/dist/runtime/application/api/logging.js +0 -77
  121. package/dist/runtime/application/api/logging.js.map +0 -1
  122. package/dist/runtime/application/api/middlewares.d.ts +0 -14
  123. package/dist/runtime/application/api/middlewares.js +0 -12
  124. package/dist/runtime/application/api/middlewares.js.map +0 -1
  125. package/dist/runtime/application/api/procedure.d.ts +0 -67
  126. package/dist/runtime/application/api/procedure.js +0 -50
  127. package/dist/runtime/application/api/procedure.js.map +0 -1
  128. package/dist/runtime/application/api/router.d.ts +0 -80
  129. package/dist/runtime/application/api/router.js +0 -50
  130. package/dist/runtime/application/api/router.js.map +0 -1
  131. package/dist/runtime/application/api/types.d.ts +0 -38
  132. package/dist/runtime/application/api/types.js +0 -2
  133. package/dist/runtime/application/api/types.js.map +0 -1
  134. package/dist/runtime/application/config.d.ts +0 -26
  135. package/dist/runtime/application/config.js +0 -21
  136. package/dist/runtime/application/config.js.map +0 -1
  137. package/dist/runtime/application/constants.d.ts +0 -2
  138. package/dist/runtime/application/constants.js +0 -2
  139. package/dist/runtime/application/constants.js.map +0 -1
  140. package/dist/runtime/application/hook.d.ts +0 -19
  141. package/dist/runtime/application/hook.js +0 -11
  142. package/dist/runtime/application/hook.js.map +0 -1
  143. package/dist/runtime/application/hooks.d.ts +0 -3
  144. package/dist/runtime/application/hooks.js +0 -4
  145. package/dist/runtime/application/hooks.js.map +0 -1
  146. package/dist/runtime/jobs/ui.d.ts +0 -3
  147. package/dist/runtime/jobs/ui.js +0 -20
  148. package/dist/runtime/jobs/ui.js.map +0 -1
  149. package/dist/runtime/pubsub/manager.js.map +0 -1
  150. package/dist/runtime/pubsub/redis.d.ts +0 -16
  151. package/dist/runtime/pubsub/redis.js.map +0 -1
  152. package/src/runtime/application/api/api.ts +0 -274
  153. package/src/runtime/application/api/constants.ts +0 -22
  154. package/src/runtime/application/api/filters.ts +0 -39
  155. package/src/runtime/application/api/guards.ts +0 -42
  156. package/src/runtime/application/api/index.ts +0 -9
  157. package/src/runtime/application/api/logging.ts +0 -110
  158. package/src/runtime/application/api/middlewares.ts +0 -37
  159. package/src/runtime/application/api/procedure.ts +0 -231
  160. package/src/runtime/application/api/router.ts +0 -220
  161. package/src/runtime/application/api/types.ts +0 -138
  162. package/src/runtime/application/config.ts +0 -69
  163. package/src/runtime/application/constants.ts +0 -4
  164. package/src/runtime/application/hook.ts +0 -51
  165. package/src/runtime/application/hooks.ts +0 -3
  166. package/src/runtime/jobs/ui.ts +0 -27
  167. package/src/runtime/pubsub/redis.ts +0 -106
@@ -1,9 +1,11 @@
1
+ import type { RuntimePlugin } from '@nmtjs/application'
1
2
  import type {
2
3
  AnyInjectable,
3
4
  Dependant,
4
5
  Logger,
5
6
  LoggingOptions,
6
7
  } from '@nmtjs/core'
8
+ import { LifecycleHook, LifecycleHooks } from '@nmtjs/application'
7
9
  import {
8
10
  Container,
9
11
  createLogger,
@@ -11,10 +13,6 @@ import {
11
13
  Scope,
12
14
  } from '@nmtjs/core'
13
15
 
14
- import type { RuntimePlugin } from './plugin.ts'
15
- import { LifecycleHook } from './enums.ts'
16
- import { LifecycleHooks } from './hooks.ts'
17
-
18
16
  export type BaseRuntimeOptions = {
19
17
  logger?: LoggingOptions
20
18
  container?: Container
@@ -1,13 +1,15 @@
1
+ import type {
2
+ ApplicationConfig,
3
+ ApplicationTransport,
4
+ } from '@nmtjs/application'
1
5
  import type { LoggingOptions } from '@nmtjs/core'
2
- import type { Transport } from '@nmtjs/gateway'
3
6
  import type { ApplicationOptions } from '@nmtjs/proxy'
4
7
  import type { Applications } from 'nmtjs/runtime/types'
5
8
 
6
- import type { ApplicationConfig } from '../application/config.ts'
7
- import type { JobWorkerPool, StoreType } from '../enums.ts'
9
+ import type { StoreType } from '../enums.ts'
8
10
  import type { AnyJob } from '../jobs/job.ts'
9
- import type { PubSubAdapterType } from '../pubsub/manager.ts'
10
11
  import type { JobsSchedulerOptions } from '../scheduler/index.ts'
12
+ import type { SubscriptionAdapterType } from '../subscription/manager.ts'
11
13
  import type { StoreTypeOptions } from '../types.ts'
12
14
  import { kServerConfig } from '../constants.ts'
13
15
 
@@ -30,7 +32,7 @@ export type ServerApplicationConfig<T = ApplicationConfig> =
30
32
  T extends ApplicationConfig<any, infer Transports>
31
33
  ? {
32
34
  threads: {
33
- [K in keyof Transports]: Transports[K] extends Transport<
35
+ [K in keyof Transports]: Transports[K] extends ApplicationTransport<
34
36
  any,
35
37
  infer Options
36
38
  >
@@ -41,17 +43,13 @@ export type ServerApplicationConfig<T = ApplicationConfig> =
41
43
  : any
42
44
 
43
45
  export type ServerJobsConfig = {
44
- pools: {
45
- [JobWorkerPool.Io]: ServerPoolOptions
46
- [JobWorkerPool.Compute]: ServerPoolOptions
47
- }
46
+ pools: Record<string, ServerPoolOptions>
48
47
  jobs?: AnyJob[]
49
48
  /**
50
49
  * @deprecated Scheduler is currently being refactored and is not available.
51
50
  * Using this option will throw an error at startup.
52
51
  */
53
52
  scheduler?: JobsSchedulerOptions
54
- ui?: { hostname?: string; port?: number }
55
53
  }
56
54
 
57
55
  export interface ServerConfigInit {
@@ -86,7 +84,7 @@ export interface ServerConfigInit {
86
84
  }
87
85
  jobs?: ServerJobsConfig
88
86
  commands?: {}
89
- pubsub?: { adapter: PubSubAdapterType }
87
+ subscription?: { adapter: SubscriptionAdapterType }
90
88
  deploymentId?: string
91
89
  metrics?: {
92
90
  /**
@@ -133,8 +131,6 @@ export interface ServerConfig {
133
131
  * The applications will be accessible via `<hostname>:<port>/<application-name>/**`
134
132
  *
135
133
  * Requires adding `@nmtjs/proxy` package to your dependencies
136
- * and [`cargo`](https://doc.rust-lang.org/cargo/getting-started/installation.html) alongside with
137
- * [`rustc`](https://www.rust-lang.org/tools/install) to be available globally for building native modules.
138
134
  */
139
135
  proxy: ServerConfigInit['proxy']
140
136
  jobs?: {
@@ -145,10 +141,9 @@ export interface ServerConfig {
145
141
  * Using this option will throw an error at startup.
146
142
  */
147
143
  scheduler?: JobsSchedulerOptions
148
- ui?: ServerJobsConfig['ui']
149
144
  }
150
145
  commands?: {}
151
- pubsub: ServerConfigInit['pubsub']
146
+ subscription: ServerConfigInit['subscription']
152
147
  deploymentId: ServerConfigInit['deploymentId']
153
148
  metrics?: ServerConfigInit['metrics']
154
149
  }
@@ -161,7 +156,7 @@ export function defineServer(options: ServerConfigInit): ServerConfig {
161
156
  proxy,
162
157
  store,
163
158
  applications,
164
- pubsub,
159
+ subscription,
165
160
  metrics,
166
161
  } = options
167
162
 
@@ -175,7 +170,6 @@ export function defineServer(options: ServerConfigInit): ServerConfig {
175
170
  pools: options.jobs.pools,
176
171
  jobs: map,
177
172
  scheduler: options.jobs.scheduler,
178
- ui: options.jobs.ui,
179
173
  }
180
174
  }
181
175
 
@@ -187,7 +181,7 @@ export function defineServer(options: ServerConfigInit): ServerConfig {
187
181
  proxy,
188
182
  store,
189
183
  applications,
190
- pubsub,
184
+ subscription,
191
185
  jobs,
192
186
  metrics,
193
187
  } as const)
@@ -2,10 +2,9 @@ import { inspect } from 'node:util'
2
2
 
3
3
  import type { Logger } from '@nmtjs/core'
4
4
  import type { RedisClient } from 'bullmq'
5
- import { Queue, UnrecoverableError, Worker } from 'bullmq'
5
+ import { UnrecoverableError, Worker } from 'bullmq'
6
6
 
7
7
  import type { AnyJob } from '../jobs/job.ts'
8
- import type { JobsUI } from '../jobs/ui.ts'
9
8
  import type { Store, WorkerJobTask } from '../types.ts'
10
9
  import type { ServerConfig } from './config.ts'
11
10
  import type { ErrorPolicy } from './error-policy.ts'
@@ -15,11 +14,12 @@ import type {
15
14
  WorkerPoolConfig,
16
15
  WorkerPoolFactory,
17
16
  } from './worker-pool.ts'
18
- import { JobWorkerPool, WorkerType } from '../enums.ts'
17
+ import { WorkerType } from '../enums.ts'
19
18
  import { getJobQueueName } from '../jobs/manager.ts'
20
- import { createJobsUI } from '../jobs/ui.ts'
21
19
  import { JobRunnersPool } from './worker-pool.ts'
22
20
 
21
+ type JobsPoolConfig = Exclude<ServerConfig['jobs'], undefined>['pools']
22
+
23
23
  function enrichBullMqErrorStack(error: unknown): Error {
24
24
  const normalized =
25
25
  error instanceof Error
@@ -33,6 +33,38 @@ function enrichBullMqErrorStack(error: unknown): Error {
33
33
  return normalized
34
34
  }
35
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
+
36
68
  /**
37
69
  * ApplicationServerJobs manages job worker pools and BullMQ queue workers.
38
70
  *
@@ -40,23 +72,21 @@ function enrichBullMqErrorStack(error: unknown): Error {
40
72
  * - Uses JobRunnersPool (extends WorkerPool) for worker management
41
73
  * - Uses ManagedWorker for restart logic and state tracking
42
74
  * - Integrates with ErrorPolicy for restart decisions
43
- * - Proper health tracking per job pool type
75
+ * - Proper health tracking per job pool
44
76
  */
45
77
  export class ApplicationServerJobs {
46
78
  /**
47
79
  * BullMQ workers - one per job (dedicated queues)
48
80
  */
49
81
  queueWorkers = new Set<Worker>()
50
- ui?: JobsUI
51
- protected uiQueues: Queue[] = []
52
82
 
53
83
  jobs: Map<string, AnyJob>
54
84
 
55
85
  /**
56
- * Shared resource pools by pool type (Io, Compute).
57
- * All jobs of a given pool type share the same pool for resource management.
86
+ * Shared resource pools by configured pool name.
87
+ * All jobs using the same pool name share the same pool for resource management.
58
88
  */
59
- protected pools = new Map<JobWorkerPool, JobRunnersPool>()
89
+ protected pools = new Map<string, JobRunnersPool>()
60
90
 
61
91
  constructor(
62
92
  readonly params: {
@@ -90,44 +120,16 @@ export class ApplicationServerJobs {
90
120
  return
91
121
  }
92
122
 
93
- if (jobsConfig.ui) {
94
- const hostname = jobsConfig.ui.hostname ?? '127.0.0.1'
95
- const port = jobsConfig.ui.port ?? 3000
96
-
97
- this.uiQueues = [...this.jobs.values()].map(
98
- (job) =>
99
- new Queue(getJobQueueName(job), {
100
- connection: store as unknown as RedisClient,
101
- }),
102
- )
103
-
104
- this.ui = createJobsUI(this.uiQueues)
105
-
106
- await new Promise<void>((resolve, reject) => {
107
- if (!this.ui) return reject(new Error('Jobs UI server is missing'))
108
- this.ui.once('error', reject)
109
- this.ui.listen(port, hostname, resolve)
110
- })
111
-
112
- const address = this.ui.address()
113
- const resolved =
114
- address && typeof address !== 'string'
115
- ? { hostname: address.address, port: address.port }
116
- : { hostname, port }
117
-
118
- logger.info({ ...resolved }, 'Jobs UI started')
119
- }
120
-
121
- // Step 1: Initialize shared resource pools (Io, Compute)
122
- const poolTypes = Object.values(JobWorkerPool)
123
+ const jobsByPool = getJobsByPool(this.jobs.values())
124
+ assertActiveJobPoolsConfigured(jobsByPool, jobsConfig.pools)
123
125
 
124
- for (const poolType of poolTypes) {
125
- const poolConfig = jobsConfig.pools[poolType]
126
- if (!poolConfig) continue
126
+ // Step 1: Initialize shared resource pools used by active jobs
127
+ for (const poolName of jobsByPool.keys()) {
128
+ const poolConfig = jobsConfig.pools[poolName]!
127
129
 
128
- // Create a JobRunnersPool for this pool type
130
+ // Create a JobRunnersPool for this pool
129
131
  const config: WorkerPoolConfig = {
130
- name: `job-pool-${poolType}`,
132
+ name: `job-pool-${poolName}`,
131
133
  workerType: WorkerType.Job,
132
134
  path: workerConfig.path,
133
135
  workerData: { ...workerConfig.workerData },
@@ -143,15 +145,15 @@ export class ApplicationServerJobs {
143
145
 
144
146
  // Add workers to the pool
145
147
  for (let i = 0; i < poolConfig.threads; i++) {
146
- pool.add({ runtime: { type: 'jobs', jobWorkerPool: poolType } }, i)
148
+ pool.add({ runtime: { type: 'jobs', jobWorkerPool: poolName } }, i)
147
149
  }
148
150
 
149
151
  await pool.startAll()
150
- this.pools.set(poolType, pool)
152
+ this.pools.set(poolName, pool)
151
153
 
152
154
  logger.info(
153
155
  {
154
- pool: poolType,
156
+ pool: poolName,
155
157
  threads: poolConfig.threads,
156
158
  jobsPerThread: poolConfig.jobs,
157
159
  },
@@ -161,28 +163,18 @@ export class ApplicationServerJobs {
161
163
 
162
164
  // Step 2: Create a dedicated BullMQ Worker for each job
163
165
  // Calculate how many jobs use each pool for fair concurrency distribution
164
- const jobsPerPool = new Map<JobWorkerPool, number>()
165
- for (const job of this.jobs.values()) {
166
- const count = jobsPerPool.get(job.options.pool) ?? 0
167
- jobsPerPool.set(job.options.pool, count + 1)
168
- }
169
-
170
166
  for (const job of this.jobs.values()) {
171
167
  const queueName = getJobQueueName(job)
172
- const poolType = job.options.pool
173
- const pool = this.pools.get(poolType)
168
+ const poolName = job.options.pool
169
+ const pool = this.pools.get(poolName)
174
170
 
175
171
  if (!pool) {
176
- logger.warn(
177
- { job: job.name, pool: poolType },
178
- 'No pool configured for job, skipping worker creation',
179
- )
180
- continue
172
+ throw new Error(`Job "${job.name}" pool "${poolName}" is not started`)
181
173
  }
182
174
 
183
- const poolConfig = jobsConfig.pools[poolType]
175
+ const poolConfig = jobsConfig.pools[poolName]!
184
176
  const poolCapacity = poolConfig.threads * poolConfig.jobs
185
- const jobCountInPool = jobsPerPool.get(poolType) ?? 1
177
+ const jobCountInPool = jobsByPool.get(poolName)?.length ?? 1
186
178
  // Use job-specific concurrency if provided, otherwise distribute pool capacity evenly
187
179
  const defaultConcurrency = Math.max(
188
180
  1,
@@ -224,7 +216,7 @@ export class ApplicationServerJobs {
224
216
 
225
217
  this.queueWorkers.add(queueWorker)
226
218
  logger.info(
227
- { job: job.name, queue: queueName, pool: poolType, concurrency },
219
+ { job: job.name, queue: queueName, pool: poolName, concurrency },
228
220
  'Job queue worker started',
229
221
  )
230
222
  }
@@ -233,14 +225,6 @@ export class ApplicationServerJobs {
233
225
  async stop() {
234
226
  const { logger } = this.params
235
227
 
236
- if (this.ui) {
237
- await new Promise<void>((resolve) => {
238
- this.ui?.close(() => resolve())
239
- }).catch((error) => {
240
- logger.warn({ error }, 'Failed to stop Jobs UI server')
241
- })
242
- }
243
-
244
228
  // Force-close BullMQ workers immediately — stops accepting new jobs
245
229
  // and doesn't wait for in-flight processor callbacks.
246
230
  // We must use force=true on the first call because BullMQ caches the
@@ -271,25 +255,12 @@ export class ApplicationServerJobs {
271
255
  }),
272
256
  )
273
257
  this.pools.clear()
274
-
275
- // Close UI queues last (non-critical)
276
- await Promise.all(
277
- this.uiQueues.map(async (queue) => {
278
- try {
279
- await queue.close()
280
- } catch (error) {
281
- logger.warn({ error }, 'Failed to close Jobs UI queue')
282
- }
283
- }),
284
- )
285
- this.uiQueues = []
286
- this.ui = undefined
287
258
  }
288
259
 
289
260
  /**
290
- * Get a job pool by type.
261
+ * Get a job pool by name.
291
262
  */
292
- getPool(poolType: JobWorkerPool): JobRunnersPool | undefined {
293
- return this.pools.get(poolType)
263
+ getPool(poolName: string): JobRunnersPool | undefined {
264
+ return this.pools.get(poolName)
294
265
  }
295
266
  }
@@ -11,27 +11,27 @@ import type { Container, Logger } from '@nmtjs/core'
11
11
  import type { t } from '@nmtjs/type'
12
12
  import { isAbortError } from '@nmtjs/common'
13
13
 
14
- import { pubSubAdapter } from '../injectables.ts'
14
+ import { subscriptionAdapter } from '../injectables.ts'
15
15
 
16
- export type PubSubAdapterEvent = { channel: string; payload: any }
16
+ export type SubscriptionAdapterEvent = { channel: string; payload: any }
17
17
 
18
- export interface PubSubAdapterType {
18
+ export interface SubscriptionAdapterType {
19
19
  publish(channel: string, payload: any): Promise<boolean>
20
20
  subscribe(
21
21
  channel: string,
22
22
  signal?: AbortSignal,
23
- ): AsyncGenerator<PubSubAdapterEvent>
23
+ ): AsyncGenerator<SubscriptionAdapterEvent>
24
24
  initialize(): Promise<void>
25
25
  dispose(): Promise<void>
26
26
  }
27
27
 
28
- export type PubSubChannel = {
28
+ export type SubscriptionChannel = {
29
29
  stream: Readable
30
30
  subscription: TAnySubscriptionContract
31
31
  event: TAnyEventContract
32
32
  }
33
33
 
34
- export type PubSubSubscribe = <
34
+ export type SubscribeFn = <
35
35
  Contract extends TAnySubscriptionContract,
36
36
  Events extends {
37
37
  [K in keyof Contract['events']]?: true
@@ -63,7 +63,7 @@ export type PubSubSubscribe = <
63
63
  }
64
64
  >
65
65
 
66
- export type PubSubPublish = <
66
+ export type PublishFn = <
67
67
  S extends TAnySubscriptionContract,
68
68
  E extends S['events'][keyof S['events']],
69
69
  >(
@@ -72,23 +72,24 @@ export type PubSubPublish = <
72
72
  data: t.infer.decode.input<E['payload']>,
73
73
  ) => Promise<boolean>
74
74
 
75
- export type PubSubManagerOptions = { logger: Logger; container: Container }
75
+ export type SubscriptionManagerOptions = {
76
+ logger: Logger
77
+ container: Container
78
+ }
76
79
 
77
- export class PubSubManager {
78
- readonly subscriptions = new Map<string, PubSubChannel>()
80
+ export class SubscriptionManager {
81
+ readonly subscriptions = new Map<string, SubscriptionChannel>()
82
+ protected readonly logger: Logger
79
83
 
80
- constructor(protected readonly options: PubSubManagerOptions) {}
84
+ constructor(protected readonly options: SubscriptionManagerOptions) {
85
+ this.logger = options.logger.child({ component: SubscriptionManager.name })
86
+ }
81
87
 
82
88
  protected get adapter() {
83
- return this.options.container.resolve(pubSubAdapter)
89
+ return this.options.container.resolve(subscriptionAdapter)
84
90
  }
85
91
 
86
- subscribe: PubSubSubscribe = async (
87
- subscription,
88
- events,
89
- options,
90
- signal,
91
- ) => {
92
+ subscribe: SubscribeFn = async (subscription, events, options, signal) => {
92
93
  const adapter = await this.adapter
93
94
 
94
95
  const eventKeys =
@@ -96,6 +97,11 @@ export class PubSubManager {
96
97
  ? Object.keys(subscription.events)
97
98
  : Object.keys(events)
98
99
 
100
+ this.logger.debug(
101
+ { subscription: subscription.name, eventCount: eventKeys.length },
102
+ 'Opening subscription',
103
+ )
104
+
99
105
  const streams = Array(eventKeys.length)
100
106
 
101
107
  for (const index in eventKeys) {
@@ -103,44 +109,115 @@ export class PubSubManager {
103
109
  const channel = getChannelName(event, options)
104
110
  if (this.subscriptions.has(channel)) {
105
111
  streams[index] = this.subscriptions.get(channel)!.stream
112
+ this.logger.trace(
113
+ {
114
+ channel,
115
+ event: event.name,
116
+ activeChannels: this.subscriptions.size,
117
+ },
118
+ 'Reusing pubsub channel stream',
119
+ )
106
120
  } else {
107
121
  const iterable = adapter.subscribe(channel, signal)
108
122
  const stream = this.createEventStream(iterable)
109
- stream.on('close', () => this.subscriptions.delete(channel))
123
+ stream.on('close', () => {
124
+ this.subscriptions.delete(channel)
125
+ this.logger.debug(
126
+ {
127
+ channel,
128
+ event: event.name,
129
+ activeChannels: this.subscriptions.size,
130
+ },
131
+ 'Pubsub channel stream closed',
132
+ )
133
+ })
134
+ stream.on('error', (error) => {
135
+ this.logger.warn(
136
+ { channel, event: event.name, error },
137
+ 'Pubsub channel stream failed',
138
+ )
139
+ })
110
140
  streams[index] = stream
111
141
  this.subscriptions.set(channel, { subscription, event, stream })
142
+ this.logger.debug(
143
+ {
144
+ channel,
145
+ event: event.name,
146
+ activeChannels: this.subscriptions.size,
147
+ },
148
+ 'Creating channel stream',
149
+ )
112
150
  }
113
151
  }
114
152
 
115
- return mergeEventStreams(streams, signal)
153
+ const mergedStream = mergeEventStreams(streams, signal)
154
+
155
+ mergedStream.once('close', () => {
156
+ this.logger.debug(
157
+ { subscription: subscription.name, eventCount: eventKeys.length },
158
+ 'Pubsub subscription stream closed',
159
+ )
160
+ })
161
+ mergedStream.once('error', (error) => {
162
+ this.logger.warn(
163
+ {
164
+ subscription: subscription.name,
165
+ eventCount: eventKeys.length,
166
+ error,
167
+ },
168
+ 'Pubsub subscription stream failed',
169
+ )
170
+ })
171
+
172
+ return mergedStream
116
173
  }
117
174
 
118
- publish: PubSubPublish = async (event, options, data) => {
175
+ publish: PublishFn = async (event, options, data) => {
119
176
  const adapter = await this.adapter
120
177
 
121
178
  const channel = getChannelName(event, options)
122
179
 
180
+ this.logger.trace({ channel, event: event.name }, 'Publishing pubsub event')
181
+
123
182
  try {
124
183
  const payload = event.payload.encode(data)
125
- return await adapter.publish(channel, payload)
184
+ const published = await adapter.publish(channel, payload)
185
+
186
+ if (published) {
187
+ this.logger.trace(
188
+ { channel, event: event.name },
189
+ 'Published pubsub event',
190
+ )
191
+ } else {
192
+ this.logger.warn(
193
+ { channel, event: event.name },
194
+ 'Pubsub adapter reported publish failure',
195
+ )
196
+ }
197
+
198
+ return published
126
199
  } catch (error: any) {
127
- this.options.logger.error(
128
- `Failed to publish event "${event.name}" on channel "${channel}": ${error.message}`,
200
+ this.logger.error(
201
+ { channel, event: event.name, error },
202
+ 'Failed to publish pubsub event',
129
203
  )
130
- return Promise.reject(error)
204
+ throw error
131
205
  }
132
206
  }
133
207
 
134
208
  private createEventStream(
135
- iterable: AsyncGenerator<PubSubAdapterEvent>,
209
+ iterable: AsyncGenerator<SubscriptionAdapterEvent>,
136
210
  ): Readable {
137
211
  const { subscriptions } = this
212
+ const logger = this.logger
213
+
138
214
  return new Readable({
139
215
  objectMode: true,
140
216
  read() {
141
217
  iterable.next().then(
142
218
  ({ value, done }) => {
143
219
  if (done) {
220
+ logger.trace('Pubsub adapter stream ended')
144
221
  this.push(null)
145
222
  } else {
146
223
  const subscription = subscriptions.get(value.channel)
@@ -148,17 +225,32 @@ export class PubSubManager {
148
225
  const { event } = subscription
149
226
  try {
150
227
  const data = event.payload.decode(value.payload)
228
+ logger.trace(
229
+ { channel: value.channel, event: event.name },
230
+ 'Received event',
231
+ )
151
232
  this.push({ event: event.name, data })
152
233
  } catch (error: any) {
234
+ logger.warn(
235
+ { channel: value.channel, event: event.name, error },
236
+ 'Failed to decode pubsub event payload',
237
+ )
153
238
  this.destroy(error)
154
239
  }
240
+ } else {
241
+ logger.trace(
242
+ { channel: value.channel },
243
+ 'Dropped pubsub event for inactive channel',
244
+ )
155
245
  }
156
246
  }
157
247
  },
158
248
  (error) => {
159
249
  if (isAbortError(error)) {
250
+ logger.trace('Pubsub adapter stream aborted')
160
251
  this.push(null)
161
252
  } else {
253
+ logger.warn({ error }, 'Pubsub adapter stream failed')
162
254
  this.destroy(error)
163
255
  }
164
256
  },