nmtjs 0.15.0-beta.2 → 0.15.0-beta.20

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 (254) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +3 -2
  3. package/dist/cli.js.map +1 -0
  4. package/dist/config.d.ts +51 -0
  5. package/dist/config.js +1 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/entrypoints/cli.d.ts +1 -0
  8. package/dist/entrypoints/cli.js +1 -0
  9. package/dist/entrypoints/cli.js.map +1 -0
  10. package/dist/entrypoints/main.d.ts +5 -0
  11. package/dist/entrypoints/main.js +83 -15
  12. package/dist/entrypoints/main.js.map +1 -0
  13. package/dist/entrypoints/thread.d.ts +14 -0
  14. package/dist/entrypoints/thread.js +130 -24
  15. package/dist/entrypoints/thread.js.map +1 -0
  16. package/dist/entrypoints/worker.d.ts +3 -0
  17. package/dist/entrypoints/worker.js +4 -3
  18. package/dist/entrypoints/worker.js.map +1 -0
  19. package/dist/index.d.ts +69 -0
  20. package/dist/{_exports/index.js → index.js} +9 -5
  21. package/dist/index.js.map +1 -0
  22. package/dist/resolver.d.ts +2 -0
  23. package/dist/resolver.js +1 -0
  24. package/dist/resolver.js.map +1 -0
  25. package/dist/runtime/application/api/api.d.ts +49 -0
  26. package/dist/runtime/application/api/api.js +193 -0
  27. package/dist/runtime/application/api/api.js.map +1 -0
  28. package/dist/runtime/application/api/constants.d.ts +14 -0
  29. package/dist/runtime/application/api/constants.js +8 -0
  30. package/dist/runtime/application/api/constants.js.map +1 -0
  31. package/dist/runtime/application/api/filters.d.ts +14 -0
  32. package/dist/runtime/application/api/filters.js +11 -0
  33. package/dist/runtime/application/api/filters.js.map +1 -0
  34. package/dist/runtime/application/api/guards.d.ts +13 -0
  35. package/dist/runtime/application/api/guards.js +8 -0
  36. package/dist/runtime/application/api/guards.js.map +1 -0
  37. package/dist/runtime/application/api/index.d.ts +8 -0
  38. package/dist/runtime/application/api/index.js +9 -0
  39. package/dist/runtime/application/api/index.js.map +1 -0
  40. package/dist/runtime/application/api/middlewares.d.ts +14 -0
  41. package/dist/runtime/application/api/middlewares.js +12 -0
  42. package/dist/runtime/application/api/middlewares.js.map +1 -0
  43. package/dist/runtime/application/api/procedure.d.ts +67 -0
  44. package/dist/runtime/application/api/procedure.js +50 -0
  45. package/dist/runtime/application/api/procedure.js.map +1 -0
  46. package/dist/runtime/application/api/router.d.ts +71 -0
  47. package/dist/runtime/application/api/router.js +51 -0
  48. package/dist/runtime/application/api/router.js.map +1 -0
  49. package/dist/runtime/application/api/types.d.ts +32 -0
  50. package/dist/runtime/application/api/types.js +2 -0
  51. package/dist/runtime/application/api/types.js.map +1 -0
  52. package/dist/runtime/application/config.d.ts +26 -0
  53. package/dist/runtime/application/config.js +21 -0
  54. package/dist/runtime/application/config.js.map +1 -0
  55. package/dist/runtime/application/constants.d.ts +2 -0
  56. package/dist/runtime/application/constants.js +2 -0
  57. package/dist/runtime/application/constants.js.map +1 -0
  58. package/dist/runtime/application/hook.d.ts +19 -0
  59. package/dist/runtime/application/hook.js +11 -0
  60. package/dist/runtime/application/hook.js.map +1 -0
  61. package/dist/runtime/application/hooks.d.ts +3 -0
  62. package/dist/runtime/application/hooks.js +4 -0
  63. package/dist/runtime/application/hooks.js.map +1 -0
  64. package/dist/runtime/application/index.d.ts +5 -0
  65. package/dist/runtime/application/index.js +6 -0
  66. package/dist/runtime/application/index.js.map +1 -0
  67. package/dist/runtime/constants.d.ts +8 -0
  68. package/dist/runtime/constants.js +5 -0
  69. package/dist/runtime/constants.js.map +1 -0
  70. package/dist/runtime/core/hooks.d.ts +4 -0
  71. package/dist/runtime/core/hooks.js +4 -0
  72. package/dist/runtime/core/hooks.js.map +1 -0
  73. package/dist/runtime/core/plugin.d.ts +8 -0
  74. package/dist/runtime/core/plugin.js +4 -0
  75. package/dist/runtime/core/plugin.js.map +1 -0
  76. package/dist/runtime/core/runtime.d.ts +27 -0
  77. package/dist/runtime/core/runtime.js +81 -0
  78. package/dist/runtime/core/runtime.js.map +1 -0
  79. package/dist/runtime/enums.d.ts +21 -0
  80. package/dist/runtime/enums.js +26 -0
  81. package/dist/runtime/enums.js.map +1 -0
  82. package/dist/runtime/index.d.ts +21 -0
  83. package/dist/runtime/index.js +22 -0
  84. package/dist/runtime/index.js.map +1 -0
  85. package/dist/runtime/injectables.d.ts +23 -0
  86. package/dist/runtime/injectables.js +20 -0
  87. package/dist/runtime/injectables.js.map +1 -0
  88. package/dist/runtime/jobs/job.d.ts +132 -0
  89. package/dist/runtime/jobs/job.js +68 -0
  90. package/dist/runtime/jobs/job.js.map +1 -0
  91. package/dist/runtime/jobs/manager.d.ts +103 -0
  92. package/dist/runtime/jobs/manager.js +200 -0
  93. package/dist/runtime/jobs/manager.js.map +1 -0
  94. package/dist/runtime/jobs/router.d.ts +251 -0
  95. package/dist/runtime/jobs/router.js +396 -0
  96. package/dist/runtime/jobs/router.js.map +1 -0
  97. package/dist/runtime/jobs/runner.d.ts +64 -0
  98. package/dist/runtime/jobs/runner.js +256 -0
  99. package/dist/runtime/jobs/runner.js.map +1 -0
  100. package/dist/runtime/jobs/step.d.ts +22 -0
  101. package/dist/runtime/jobs/step.js +18 -0
  102. package/dist/runtime/jobs/step.js.map +1 -0
  103. package/dist/runtime/jobs/ui.d.ts +3 -0
  104. package/dist/runtime/jobs/ui.js +17 -0
  105. package/dist/runtime/jobs/ui.js.map +1 -0
  106. package/dist/runtime/pubsub/manager.d.ts +48 -0
  107. package/dist/runtime/pubsub/manager.js +119 -0
  108. package/dist/runtime/pubsub/manager.js.map +1 -0
  109. package/dist/runtime/pubsub/redis.d.ts +16 -0
  110. package/dist/runtime/pubsub/redis.js +98 -0
  111. package/dist/runtime/pubsub/redis.js.map +1 -0
  112. package/dist/runtime/scheduler/index.d.ts +22 -0
  113. package/dist/runtime/scheduler/index.js +20 -0
  114. package/dist/runtime/scheduler/index.js.map +1 -0
  115. package/dist/runtime/server/applications.d.ts +52 -0
  116. package/dist/runtime/server/applications.js +133 -0
  117. package/dist/runtime/server/applications.js.map +1 -0
  118. package/dist/runtime/server/config.d.ts +121 -0
  119. package/dist/runtime/server/config.js +33 -0
  120. package/dist/runtime/server/config.js.map +1 -0
  121. package/dist/runtime/server/jobs.d.ts +41 -0
  122. package/dist/runtime/server/jobs.js +181 -0
  123. package/dist/runtime/server/jobs.js.map +1 -0
  124. package/dist/runtime/server/pool.d.ts +54 -0
  125. package/dist/runtime/server/pool.js +194 -0
  126. package/dist/runtime/server/pool.js.map +1 -0
  127. package/dist/runtime/server/proxy.d.ts +21 -0
  128. package/dist/runtime/server/proxy.js +79 -0
  129. package/dist/runtime/server/proxy.js.map +1 -0
  130. package/dist/runtime/server/server.d.ts +53 -0
  131. package/dist/runtime/server/server.js +90 -0
  132. package/dist/runtime/server/server.js.map +1 -0
  133. package/dist/runtime/store/index.d.ts +3 -0
  134. package/dist/runtime/store/index.js +23 -0
  135. package/dist/runtime/store/index.js.map +1 -0
  136. package/dist/runtime/types.d.ts +103 -0
  137. package/dist/runtime/types.js +2 -0
  138. package/dist/runtime/types.js.map +1 -0
  139. package/dist/runtime/workers/application.d.ts +47 -0
  140. package/dist/runtime/workers/application.js +162 -0
  141. package/dist/runtime/workers/application.js.map +1 -0
  142. package/dist/runtime/workers/base.d.ts +16 -0
  143. package/dist/runtime/workers/base.js +46 -0
  144. package/dist/runtime/workers/base.js.map +1 -0
  145. package/dist/runtime/workers/cli.d.ts +1 -0
  146. package/dist/runtime/workers/cli.js +2 -0
  147. package/dist/runtime/workers/cli.js.map +1 -0
  148. package/dist/runtime/workers/job.d.ts +20 -0
  149. package/dist/runtime/workers/job.js +172 -0
  150. package/dist/runtime/workers/job.js.map +1 -0
  151. package/dist/typings.d.ts +5 -0
  152. package/dist/typings.js +4 -3
  153. package/dist/typings.js.map +1 -0
  154. package/dist/vite/builder.d.ts +5 -0
  155. package/dist/vite/builder.js +5 -1
  156. package/dist/vite/builder.js.map +1 -0
  157. package/dist/vite/config.d.ts +28 -0
  158. package/dist/vite/config.js +1 -0
  159. package/dist/vite/config.js.map +1 -0
  160. package/dist/vite/plugins.d.ts +2 -0
  161. package/dist/vite/plugins.js +1 -0
  162. package/dist/vite/plugins.js.map +1 -0
  163. package/dist/vite/runners/worker.d.ts +4 -0
  164. package/dist/vite/runners/worker.js +1 -0
  165. package/dist/vite/runners/worker.js.map +1 -0
  166. package/dist/vite/server.d.ts +3 -0
  167. package/dist/vite/server.js +6 -1
  168. package/dist/vite/server.js.map +1 -0
  169. package/dist/vite/servers/main.d.ts +8 -0
  170. package/dist/vite/servers/main.js +1 -0
  171. package/dist/vite/servers/main.js.map +1 -0
  172. package/dist/vite/servers/worker.d.ts +11 -0
  173. package/dist/vite/servers/worker.js +28 -0
  174. package/dist/vite/servers/worker.js.map +1 -0
  175. package/package.json +31 -18
  176. package/src/cli.ts +144 -0
  177. package/src/config.ts +64 -0
  178. package/src/entrypoints/cli.ts +13 -0
  179. package/src/entrypoints/main.ts +200 -0
  180. package/src/entrypoints/thread.ts +184 -0
  181. package/src/entrypoints/worker.ts +48 -0
  182. package/src/index.ts +82 -0
  183. package/src/resolver.ts +16 -0
  184. package/src/runtime/application/api/api.ts +265 -0
  185. package/src/runtime/application/api/constants.ts +22 -0
  186. package/src/runtime/application/api/filters.ts +39 -0
  187. package/src/runtime/application/api/guards.ts +29 -0
  188. package/src/runtime/application/api/index.ts +8 -0
  189. package/src/runtime/application/api/middlewares.ts +37 -0
  190. package/src/runtime/application/api/procedure.ts +229 -0
  191. package/src/runtime/application/api/router.ts +193 -0
  192. package/src/runtime/application/api/types.ts +124 -0
  193. package/src/runtime/application/config.ts +69 -0
  194. package/src/runtime/application/constants.ts +4 -0
  195. package/src/runtime/application/hook.ts +51 -0
  196. package/src/runtime/application/hooks.ts +3 -0
  197. package/src/runtime/application/index.ts +5 -0
  198. package/src/runtime/constants.ts +13 -0
  199. package/src/runtime/core/hooks.ts +5 -0
  200. package/src/runtime/core/plugin.ts +13 -0
  201. package/src/runtime/core/runtime.ts +109 -0
  202. package/src/runtime/enums.ts +24 -0
  203. package/src/runtime/index.ts +21 -0
  204. package/src/runtime/injectables.ts +61 -0
  205. package/src/runtime/jobs/job.ts +370 -0
  206. package/src/runtime/jobs/manager.ts +332 -0
  207. package/src/runtime/jobs/router.ts +835 -0
  208. package/src/runtime/jobs/runner.ts +320 -0
  209. package/src/runtime/jobs/step.ts +65 -0
  210. package/src/runtime/jobs/ui.ts +21 -0
  211. package/src/runtime/pubsub/manager.ts +211 -0
  212. package/src/runtime/pubsub/redis.ts +108 -0
  213. package/src/runtime/scheduler/index.ts +39 -0
  214. package/src/runtime/server/applications.ts +210 -0
  215. package/src/runtime/server/config.ts +158 -0
  216. package/src/runtime/server/jobs.ts +250 -0
  217. package/src/runtime/server/pool.ts +260 -0
  218. package/src/runtime/server/proxy.ts +118 -0
  219. package/src/runtime/server/server.ts +155 -0
  220. package/src/runtime/store/index.ts +30 -0
  221. package/src/runtime/types.ts +93 -0
  222. package/src/runtime/workers/application.ts +209 -0
  223. package/src/runtime/workers/base.ts +68 -0
  224. package/src/runtime/workers/cli.ts +0 -0
  225. package/src/runtime/workers/job.ts +153 -0
  226. package/src/typings.ts +30 -0
  227. package/src/vite/builder.ts +122 -0
  228. package/src/vite/config.ts +45 -0
  229. package/src/vite/plugins.ts +26 -0
  230. package/src/vite/runners/worker.ts +57 -0
  231. package/src/vite/server.ts +39 -0
  232. package/src/vite/servers/main.ts +34 -0
  233. package/src/vite/servers/worker.ts +143 -0
  234. package/dist/_exports/application.js +0 -1
  235. package/dist/_exports/common.js +0 -1
  236. package/dist/_exports/contract.js +0 -2
  237. package/dist/_exports/core.js +0 -1
  238. package/dist/_exports/gateway.js +0 -1
  239. package/dist/_exports/http-transport/bun.js +0 -1
  240. package/dist/_exports/http-transport/deno.js +0 -1
  241. package/dist/_exports/http-transport/node.js +0 -1
  242. package/dist/_exports/http-transport.js +0 -1
  243. package/dist/_exports/json-format.js +0 -1
  244. package/dist/_exports/protocol/client.js +0 -1
  245. package/dist/_exports/protocol/server.js +0 -1
  246. package/dist/_exports/protocol.js +0 -1
  247. package/dist/_exports/runtime/types.js +0 -1
  248. package/dist/_exports/runtime.js +0 -1
  249. package/dist/_exports/type.js +0 -2
  250. package/dist/_exports/ws-transport/bun.js +0 -1
  251. package/dist/_exports/ws-transport/deno.js +0 -1
  252. package/dist/_exports/ws-transport/node.js +0 -1
  253. package/dist/_exports/ws-transport.js +0 -1
  254. package/dist/command.js +0 -30
@@ -0,0 +1,155 @@
1
+ import type EventEmitter from 'node:events'
2
+ import type { Worker } from 'node:worker_threads'
3
+
4
+ import type { Logger } from '@nmtjs/core'
5
+ import { createLogger } from '@nmtjs/core'
6
+
7
+ import type {
8
+ Store,
9
+ ThreadPortMessageTypes,
10
+ WorkerThreadError,
11
+ } from '../types.ts'
12
+ import type { ServerConfig } from './config.ts'
13
+ import { createStoreClient } from '../store/index.ts'
14
+ import { ApplicationServerApplications } from './applications.ts'
15
+ import { ApplicationServerJobs } from './jobs.ts'
16
+ import { ApplicationServerProxy } from './proxy.ts'
17
+
18
+ export type ApplicationServerRunOptions = {
19
+ applications: string[]
20
+ scheduler: boolean
21
+ jobs: boolean
22
+ }
23
+
24
+ export type ApplicationWorkerReadyEvent = {
25
+ application: string
26
+ threadId: number
27
+ hosts?: ThreadPortMessageTypes['ready']['hosts']
28
+ }
29
+
30
+ export type ApplicationWorkerErrorEvent = {
31
+ application: string
32
+ threadId: number
33
+ error: WorkerThreadError
34
+ }
35
+
36
+ export type ApplicationServerWorkerConfig = {
37
+ path: string
38
+ workerData?: any
39
+ worker?: (worker: Worker) => any
40
+ events?: EventEmitter<{
41
+ worker: [Worker]
42
+ 'worker-ready': [ApplicationWorkerReadyEvent]
43
+ 'worker-error': [ApplicationWorkerErrorEvent]
44
+ }>
45
+ }
46
+
47
+ export class ApplicationServer {
48
+ logger: Logger
49
+
50
+ applications?: ApplicationServerApplications
51
+ jobRunners?: ApplicationServerJobs
52
+
53
+ proxy?: ApplicationServerProxy
54
+ store?: Store
55
+
56
+ constructor(
57
+ readonly config: ServerConfig,
58
+ readonly applicationsConfig: Record<
59
+ string,
60
+ { type: 'neemata' | 'custom'; specifier: string }
61
+ >,
62
+ readonly workerConfig: ApplicationServerWorkerConfig,
63
+ readonly runOptions: ApplicationServerRunOptions = {
64
+ applications: Object.keys(config.applications),
65
+ scheduler: false,
66
+ jobs: Boolean(config.jobs?.jobs.size),
67
+ },
68
+ ) {
69
+ this.logger = createLogger(config.logger, 'Server')
70
+ this.logger.trace(
71
+ { applications: applicationsConfig, workerConfig, runOptions },
72
+ 'ApplicationServer initialized',
73
+ )
74
+ }
75
+
76
+ async start() {
77
+ const { config, logger } = this
78
+ logger.info('Starting application server...')
79
+
80
+ if (config.store) {
81
+ this.store = await createStoreClient(config.store)
82
+ await this.store.connect()
83
+ logger.debug('Store connected')
84
+ }
85
+
86
+ this.applications = new ApplicationServerApplications({
87
+ logger: this.logger,
88
+ workerConfig: this.workerConfig,
89
+ serverConfig: this.config,
90
+ applicationsConfig: this.applicationsConfig,
91
+ applications: this.runOptions.applications,
92
+ })
93
+
94
+ if (this.runOptions.jobs) {
95
+ if (!this.store) {
96
+ throw new Error(
97
+ 'Jobs feature requires a store configuration. ' +
98
+ 'Please configure `store` in your server config or disable jobs.',
99
+ )
100
+ }
101
+ this.jobRunners = new ApplicationServerJobs({
102
+ logger: this.logger,
103
+ workerConfig: this.workerConfig,
104
+ serverConfig: this.config,
105
+ store: this.store,
106
+ })
107
+ }
108
+
109
+ if (this.config.proxy) {
110
+ this.proxy = new ApplicationServerProxy({
111
+ logger: this.logger,
112
+ config: this.config.proxy,
113
+ applications: this.applications,
114
+ })
115
+ }
116
+
117
+ await this.applications.start()
118
+
119
+ if (this.runOptions.jobs) {
120
+ await this.jobRunners?.start()
121
+ }
122
+
123
+ if (this.runOptions.scheduler && config.jobs?.scheduler) {
124
+ throw new Error(
125
+ 'JobsScheduler is currently a work in progress and not available. ' +
126
+ 'Scheduled jobs will be supported in a future release.',
127
+ )
128
+ }
129
+
130
+ if (this.proxy) {
131
+ await this.proxy.start()
132
+ }
133
+
134
+ logger.info('Application server started')
135
+ }
136
+
137
+ async stop() {
138
+ this.logger.info('Stopping application server...')
139
+
140
+ // Stop proxy + stop accepting new jobs first
141
+ await this.proxy?.stop()
142
+ await this.jobRunners?.stop()
143
+
144
+ // Stop applications
145
+ await this.applications?.stop()
146
+
147
+ // Close store connection
148
+ if (this.store) {
149
+ this.logger.debug('Closing store...')
150
+ this.store.disconnect(false)
151
+ }
152
+
153
+ this.logger.info('Application server stopped')
154
+ }
155
+ }
@@ -0,0 +1,30 @@
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
+ }
@@ -0,0 +1,93 @@
1
+ import type { HookTypes } from '@nmtjs/core'
2
+ import type { ProxyableTransportType } from '@nmtjs/gateway'
3
+ import type { Redis, RedisOptions } from 'ioredis'
4
+ import type { Redis as Valkey, RedisOptions as ValkeyOptions } from 'iovalkey'
5
+
6
+ import type { ApplicationConfig } from './application/config.ts'
7
+ import type { BaseRuntime } from './core/runtime.ts'
8
+ import type { LifecycleHook, StoreType } from './enums.ts'
9
+
10
+ export type WorkerThreadErrorOrigin = 'bootstrap' | 'start' | 'runtime'
11
+
12
+ export type ThreadErrorMessage = {
13
+ message: string
14
+ name?: string
15
+ stack?: string
16
+ origin: WorkerThreadErrorOrigin
17
+ fatal: boolean
18
+ }
19
+
20
+ export type WorkerThreadError = Error & {
21
+ origin?: WorkerThreadErrorOrigin
22
+ fatal?: boolean
23
+ }
24
+
25
+ export type ServerPortMessageTypes = {
26
+ stop: undefined
27
+ task: { id: string; task: WorkerJobTask }
28
+ }
29
+
30
+ export type ThreadPortMessageTypes = {
31
+ ready: { hosts?: { type: ProxyableTransportType; url: string }[] }
32
+ error: ThreadErrorMessage
33
+ task: { id: string; task: JobTaskResult }
34
+ }
35
+
36
+ export type ServerPortMessage = {
37
+ [K in keyof ServerPortMessageTypes]: {
38
+ type: K
39
+ data: ServerPortMessageTypes[K]
40
+ }
41
+ }[keyof ServerPortMessageTypes]
42
+
43
+ export type ThreadPortMessage = {
44
+ [K in keyof ThreadPortMessageTypes]: {
45
+ type: K
46
+ data: ThreadPortMessageTypes[K]
47
+ }
48
+ }[keyof ThreadPortMessageTypes]
49
+
50
+ export interface WorkerTask {
51
+ type?: string
52
+ payload?: any
53
+ }
54
+
55
+ export type WorkerJobTask = { jobId: string; jobName: string; data: any }
56
+
57
+ export type JobTaskResult = {
58
+ [K in keyof JobTaskResultTypes]: { type: K } & JobTaskResultTypes[K]
59
+ }[keyof JobTaskResultTypes]
60
+
61
+ export type JobTaskResultTypes = {
62
+ success: { result?: unknown }
63
+ error: { error: any }
64
+ unrecoverable_error: { error: any }
65
+ job_not_found: {}
66
+ queue_job_not_found: {}
67
+ }
68
+
69
+ export interface LifecycleHookTypes extends HookTypes {
70
+ [LifecycleHook.BeforeInitialize]: (runtime: BaseRuntime) => any
71
+ [LifecycleHook.AfterInitialize]: (runtime: BaseRuntime) => any
72
+ [LifecycleHook.BeforeDispose]: (runtime: BaseRuntime) => any
73
+ [LifecycleHook.AfterDispose]: (runtime: BaseRuntime) => any
74
+ }
75
+
76
+ export type ApplicationDefinitionType =
77
+ | { type: 'neemata'; definition: ApplicationConfig<any, any> }
78
+ | { type: 'custom'; definition: any }
79
+
80
+ export interface Applications
81
+ extends Record<string, ApplicationDefinitionType> {}
82
+
83
+ export type StoreTypes = {
84
+ [StoreType.Redis]: Redis
85
+ [StoreType.Valkey]: Valkey
86
+ }
87
+ export type StoreTypeOptions = {
88
+ [StoreType.Redis]: RedisOptions
89
+ [StoreType.Valkey]: ValkeyOptions
90
+ }
91
+
92
+ export type Store = StoreTypes[StoreType]
93
+ export type StoreOptions = StoreTypeOptions[StoreType]
@@ -0,0 +1,209 @@
1
+ import type { Dependant } from '@nmtjs/core'
2
+ import type { GatewayOptions, Transport } from '@nmtjs/gateway'
3
+ import { Gateway } from '@nmtjs/gateway'
4
+ import { JsonFormat } from '@nmtjs/json-format/server'
5
+ import { ProtocolFormats } from '@nmtjs/protocol/server'
6
+
7
+ import type { ApplicationConfig } from '../application/config.ts'
8
+ import type {
9
+ AnyFilter,
10
+ AnyGuard,
11
+ AnyMiddleware,
12
+ AnyProcedure,
13
+ AnyRouter,
14
+ kDefaultProcedure as kDefaultProcedureKey,
15
+ } from '../application/index.ts'
16
+ import type { ServerConfig } from '../server/config.ts'
17
+ import { ApplicationApi } from '../application/api/api.ts'
18
+ import { ApplicationHooks } from '../application/hooks.ts'
19
+ import {
20
+ isProcedure,
21
+ isRootRouter,
22
+ isRouter,
23
+ kDefaultProcedure,
24
+ kRootRouter,
25
+ } from '../application/index.ts'
26
+ import { LifecycleHook, WorkerType } from '../enums.ts'
27
+ import { BaseWorkerRuntime } from './base.ts'
28
+
29
+ export interface ApplicationWorkerRuntimeOptions {
30
+ name: string
31
+ path: string
32
+ transports: { [key: string]: any }
33
+ }
34
+
35
+ export class ApplicationWorkerRuntime extends BaseWorkerRuntime {
36
+ api!: ApplicationApi
37
+ applicationHooks!: ApplicationHooks
38
+ gateway!: Gateway
39
+ transports!: GatewayOptions['transports']
40
+
41
+ routers = new Map<string | kRootRouter, AnyRouter>()
42
+ procedures = new Map<
43
+ string | kDefaultProcedureKey,
44
+ { procedure: AnyProcedure; path: AnyRouter[] }
45
+ >()
46
+ filters = new Set<AnyFilter>()
47
+ middlewares = new Set<AnyMiddleware>()
48
+ guards = new Set<AnyGuard>()
49
+
50
+ constructor(
51
+ readonly config: ServerConfig,
52
+ readonly runtimeOptions: ApplicationWorkerRuntimeOptions,
53
+ protected appConfig: ApplicationConfig,
54
+ ) {
55
+ super(
56
+ config,
57
+ {
58
+ logger: config.logger,
59
+ name: `Worker ${runtimeOptions.name}`,
60
+ plugins: appConfig.plugins,
61
+ },
62
+ WorkerType.Application,
63
+ )
64
+
65
+ this.applicationHooks = new ApplicationHooks()
66
+
67
+ this.api = new ApplicationApi({
68
+ timeout: this.appConfig.api.timeout,
69
+ container: this.container,
70
+ logger: this.logger,
71
+ filters: this.filters,
72
+ middlewares: this.middlewares,
73
+ guards: this.guards,
74
+ procedures: this.procedures,
75
+ })
76
+ }
77
+
78
+ async start() {
79
+ await this.initialize()
80
+
81
+ this.transports = {}
82
+
83
+ for (const key in this.runtimeOptions.transports) {
84
+ const options = this.runtimeOptions.transports[key]
85
+ const { factory, proxyable } = this.appConfig.transports[key] as Transport
86
+ this.transports[key] = { transport: await factory(options), proxyable }
87
+ }
88
+
89
+ this.gateway = new Gateway({
90
+ logger: this.logger,
91
+ container: this.container,
92
+ hooks: this.lifecycleHooks,
93
+ formats: new ProtocolFormats([new JsonFormat()]),
94
+ transports: this.transports,
95
+ api: this.api,
96
+ identity: this.appConfig.identity,
97
+ })
98
+
99
+ return await this.gateway.start().finally(async () => {
100
+ await this.lifecycleHooks.callHook(LifecycleHook.Start)
101
+ })
102
+ }
103
+
104
+ async stop() {
105
+ await this.gateway.stop()
106
+ await this.dispose()
107
+ await this.lifecycleHooks.callHook(LifecycleHook.Stop)
108
+ }
109
+
110
+ async reload(appConfig: ApplicationConfig): Promise<void> {
111
+ await this.dispose()
112
+ this.appConfig = appConfig
113
+ this.plugins = appConfig.plugins
114
+ await this.initialize()
115
+ this.gateway.options.identity =
116
+ this.appConfig.identity ?? this.gateway.options.identity
117
+ await this.gateway.reload()
118
+ }
119
+
120
+ async initialize(): Promise<void> {
121
+ this.registerApi()
122
+ this.lifecycleHooks.addHooks(this.appConfig.lifecycleHooks)
123
+ await super.initialize()
124
+ }
125
+
126
+ protected async _initialize(): Promise<void> {
127
+ await super._initialize()
128
+
129
+ for (const hook of this.appConfig.hooks) {
130
+ this.applicationHooks.hook(hook.name, async (...args: any[]) => {
131
+ const ctx = await this.container.createContext(hook.dependencies)
132
+ await hook.handler(ctx, ...args)
133
+ })
134
+ }
135
+ }
136
+
137
+ protected async _dispose(): Promise<void> {
138
+ this.applicationHooks.removeAllHooks()
139
+ await super._dispose()
140
+ this.lifecycleHooks.removeHooks(this.appConfig.lifecycleHooks)
141
+ this.filters.clear()
142
+ this.middlewares.clear()
143
+ this.guards.clear()
144
+ this.routers.clear()
145
+ this.procedures.clear()
146
+ }
147
+
148
+ protected *_dependents(): Generator<Dependant> {
149
+ yield* this.appConfig.filters
150
+ yield* this.appConfig.guards
151
+ yield* this.appConfig.middlewares
152
+ yield* this.appConfig.hooks
153
+ for (const { procedure } of this.procedures.values()) {
154
+ yield procedure
155
+ yield* procedure.guards
156
+ yield* procedure.middlewares
157
+ }
158
+ }
159
+
160
+ protected registerApi() {
161
+ const { router, filters, guards, middlewares } = this.appConfig
162
+
163
+ if (this.routers.has(kRootRouter)) {
164
+ throw new Error('Root router already registered')
165
+ }
166
+
167
+ if (!isRootRouter(router)) {
168
+ throw new Error('Root router must be a root router')
169
+ }
170
+
171
+ this.routers.set(kRootRouter, router)
172
+ this.registerRouter(router, [])
173
+
174
+ if (router.default) {
175
+ if (!isProcedure(router.default)) {
176
+ throw new Error('Root router default must be a procedure')
177
+ }
178
+ this.procedures.set(kDefaultProcedure, {
179
+ procedure: router.default,
180
+ path: [router],
181
+ })
182
+ }
183
+
184
+ for (const filter of filters) this.filters.add(filter)
185
+ for (const middleware of middlewares) this.middlewares.add(middleware)
186
+ for (const guard of guards) this.guards.add(guard)
187
+ }
188
+
189
+ protected registerRouter(router: AnyRouter, path: AnyRouter[] = []) {
190
+ for (const route of Object.values(router.routes)) {
191
+ if (isRouter(route)) {
192
+ const name = route.contract.name
193
+ if (!name) throw new Error('Nested routers must have a name')
194
+ if (this.routers.has(name)) {
195
+ throw new Error(`Router ${String(name)} already registered`)
196
+ }
197
+ this.routers.set(name, route)
198
+ this.registerRouter(route, [...path, router])
199
+ } else if (isProcedure(route)) {
200
+ const name = route.contract.name
201
+ if (!name) throw new Error('Procedures must have a name')
202
+ if (this.procedures.has(name)) {
203
+ throw new Error(`Procedure ${name} already registered`)
204
+ }
205
+ this.procedures.set(name, { procedure: route, path: [...path, router] })
206
+ }
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,68 @@
1
+ import type { Injection } from '@nmtjs/core'
2
+ import { CoreInjectables, provide } from '@nmtjs/core'
3
+
4
+ import type { BaseRuntimeOptions } from '../core/runtime.ts'
5
+ import type { WorkerType } from '../enums.ts'
6
+ import type { ServerConfig } from '../server/config.ts'
7
+ import { BaseRuntime } from '../core/runtime.ts'
8
+ import * as injectables from '../injectables.ts'
9
+ import { JobManager } from '../jobs/manager.ts'
10
+ import { PubSubManager } from '../pubsub/manager.ts'
11
+
12
+ export abstract class BaseWorkerRuntime extends BaseRuntime {
13
+ pubsub: PubSubManager
14
+ jobManager?: JobManager
15
+
16
+ constructor(
17
+ readonly config: ServerConfig,
18
+ options: BaseRuntimeOptions,
19
+ readonly workerType: WorkerType,
20
+ ) {
21
+ super(options)
22
+
23
+ this.pubsub = new PubSubManager({
24
+ logger: this.logger,
25
+ container: this.container,
26
+ })
27
+
28
+ if (this.config.store) {
29
+ this.jobManager = new JobManager(
30
+ this.config.store,
31
+ this.config.jobs ? Array.from(this.config.jobs.jobs.values()) : [],
32
+ )
33
+ }
34
+ }
35
+
36
+ async initialize(): Promise<void> {
37
+ const injections: Injection[] = [
38
+ provide(CoreInjectables.logger, this.logger),
39
+ provide(injectables.workerType, this.workerType),
40
+ provide(injectables.pubSubPublish, this.pubsub.publish.bind(this.pubsub)),
41
+ provide(
42
+ injectables.pubSubSubscribe,
43
+ this.pubsub.subscribe.bind(this.pubsub),
44
+ ),
45
+ ]
46
+
47
+ if (this.config.store) {
48
+ injections.push(provide(injectables.storeConfig, this.config.store))
49
+ }
50
+
51
+ if (this.jobManager) {
52
+ injections.push(
53
+ provide(injectables.jobManager, this.jobManager.publicInstance),
54
+ )
55
+ }
56
+
57
+ await this.container.provide(injections)
58
+ await super.initialize()
59
+ }
60
+
61
+ protected async _initialize(): Promise<void> {
62
+ await this.jobManager?.initialize()
63
+ }
64
+
65
+ protected async _dispose(): Promise<void> {
66
+ await this.jobManager?.terminate()
67
+ }
68
+ }
File without changes
@@ -0,0 +1,153 @@
1
+ import type { MessagePort } from 'node:worker_threads'
2
+
3
+ import { UnrecoverableError } from 'bullmq'
4
+
5
+ import type { JobWorkerPool } from '../enums.ts'
6
+ import type { StepResultEntry } from '../jobs/runner.ts'
7
+ import type { ServerConfig } from '../server/config.ts'
8
+ import type { ServerPortMessage, ThreadPortMessage } from '../types.ts'
9
+ import { LifecycleHook, WorkerType } from '../enums.ts'
10
+ import { jobWorkerPool } from '../injectables.ts'
11
+ import { ApplicationWorkerJobRunner } from '../jobs/runner.ts'
12
+ import { BaseWorkerRuntime } from './base.ts'
13
+
14
+ export interface JobWorkerRuntimeOptions {
15
+ poolName: string
16
+ port: MessagePort
17
+ }
18
+
19
+ export class JobWorkerRuntime extends BaseWorkerRuntime {
20
+ jobRunner!: ApplicationWorkerJobRunner
21
+
22
+ constructor(
23
+ readonly config: ServerConfig,
24
+ readonly runtimeOptions: JobWorkerRuntimeOptions,
25
+ ) {
26
+ super(
27
+ config,
28
+ { logger: config.logger, name: `Job Worker ${runtimeOptions.poolName}` },
29
+ WorkerType.Job,
30
+ )
31
+ }
32
+
33
+ async start() {
34
+ await this.initialize()
35
+ await this.lifecycleHooks.callHook(LifecycleHook.Start)
36
+ }
37
+
38
+ async stop() {
39
+ await this.lifecycleHooks.callHook(LifecycleHook.Stop)
40
+ await this.dispose()
41
+ }
42
+
43
+ async initialize(): Promise<void> {
44
+ // Validate all jobs are complete before accepting work
45
+ if (this.config.jobs) {
46
+ for (const job of this.config.jobs.jobs.values()) {
47
+ if (!job.returnHandler) {
48
+ throw new Error(
49
+ `Job "${job.name}" is incomplete. Jobs must call .return() to be finalized before use.`,
50
+ )
51
+ }
52
+ }
53
+ }
54
+
55
+ await this.container.provide(
56
+ jobWorkerPool,
57
+ this.runtimeOptions.poolName as JobWorkerPool,
58
+ )
59
+ await super.initialize()
60
+ }
61
+
62
+ protected async _initialize(): Promise<void> {
63
+ await super._initialize()
64
+
65
+ this.jobRunner = new ApplicationWorkerJobRunner({
66
+ logger: this.logger,
67
+ container: this.container,
68
+ lifecycleHooks: this.lifecycleHooks,
69
+ })
70
+
71
+ this.runtimeOptions.port.on('message', async (msg: ServerPortMessage) => {
72
+ if (msg.type === 'task') {
73
+ const { id, task } = msg.data
74
+ try {
75
+ const job = this.config.jobs?.jobs.get(task.jobName)
76
+ if (!job) {
77
+ this.runtimeOptions.port.postMessage({
78
+ type: 'task',
79
+ data: { id, task: { type: 'job_not_found' } },
80
+ } satisfies ThreadPortMessage)
81
+ return
82
+ }
83
+
84
+ using cancellationSignal = this.jobManager!.cancellationSignal(
85
+ job,
86
+ task.jobId,
87
+ )
88
+ const queue = this.jobManager!.getQueue(job).queue
89
+ const bullJob = await queue.getJob(task.jobId)
90
+ if (!bullJob) {
91
+ throw new UnrecoverableError(
92
+ `Job ${task.jobId} not found in queue (may have been removed)`,
93
+ )
94
+ }
95
+
96
+ // Load checkpoint from BullMQ progress for resume support
97
+ const checkpoint = bullJob.progress as
98
+ | {
99
+ stepIndex: number
100
+ result: Record<string, unknown>
101
+ stepResults: StepResultEntry[]
102
+ progress: Record<string, unknown>
103
+ }
104
+ | undefined
105
+
106
+ const result = await this.jobRunner.runJob(job, task.data, {
107
+ signal: cancellationSignal,
108
+ queueJob: bullJob,
109
+ result: checkpoint?.result,
110
+ stepResults: checkpoint?.stepResults,
111
+ currentStepIndex: checkpoint?.stepIndex ?? 0,
112
+ progress: checkpoint?.progress,
113
+ })
114
+ this.runtimeOptions.port.postMessage({
115
+ type: 'task',
116
+ data: { id, task: { type: 'success', result } },
117
+ })
118
+ } catch (error) {
119
+ if (error instanceof UnrecoverableError) {
120
+ this.runtimeOptions.port.postMessage({
121
+ type: 'task',
122
+ data: {
123
+ id,
124
+ task: { type: 'unrecoverable_error', error: error.message },
125
+ },
126
+ } satisfies ThreadPortMessage)
127
+ } else {
128
+ this.runtimeOptions.port.postMessage({
129
+ type: 'task',
130
+ data: { id, task: { type: 'error', error } },
131
+ } satisfies ThreadPortMessage)
132
+ }
133
+ }
134
+ }
135
+ })
136
+ }
137
+
138
+ protected async _dispose(): Promise<void> {
139
+ this.runtimeOptions.port.removeAllListeners('message')
140
+ await super._dispose()
141
+ }
142
+
143
+ protected *_dependents() {
144
+ if (this.config.jobs) {
145
+ for (const job of this.config.jobs.jobs.values()) {
146
+ yield job
147
+ // we explicitly DO NOT WANT to yield steps here, so per-job container
148
+ // resolves them independently each time — creates more isolation
149
+ // yield* job.steps
150
+ }
151
+ }
152
+ }
153
+ }