nmtjs 0.15.0-beta.13 → 0.15.0-beta.15
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/dist/entrypoints/main.js +79 -10
- package/dist/entrypoints/main.js.map +1 -1
- package/dist/entrypoints/thread.js +129 -27
- package/dist/entrypoints/thread.js.map +1 -1
- package/dist/runtime/application/api/api.d.ts +2 -1
- package/dist/runtime/application/api/api.js +7 -3
- package/dist/runtime/application/api/api.js.map +1 -1
- package/dist/runtime/application/api/constants.d.ts +2 -0
- package/dist/runtime/application/api/constants.js +1 -0
- package/dist/runtime/application/api/constants.js.map +1 -1
- package/dist/runtime/application/api/router.d.ts +4 -1
- package/dist/runtime/application/api/router.js +6 -2
- package/dist/runtime/application/api/router.js.map +1 -1
- package/dist/runtime/server/applications.d.ts +4 -1
- package/dist/runtime/server/applications.js +38 -15
- package/dist/runtime/server/applications.js.map +1 -1
- package/dist/runtime/server/config.d.ts +4 -0
- package/dist/runtime/server/config.js.map +1 -1
- package/dist/runtime/server/pool.d.ts +4 -2
- package/dist/runtime/server/pool.js +91 -20
- package/dist/runtime/server/pool.js.map +1 -1
- package/dist/runtime/server/proxy.js +7 -1
- package/dist/runtime/server/proxy.js.map +1 -1
- package/dist/runtime/server/server.d.ts +17 -1
- package/dist/runtime/server/server.js.map +1 -1
- package/dist/runtime/types.d.ts +13 -3
- package/dist/runtime/workers/application.d.ts +2 -2
- package/dist/runtime/workers/application.js +10 -1
- package/dist/runtime/workers/application.js.map +1 -1
- package/dist/vite/servers/worker.d.ts +5 -2
- package/dist/vite/servers/worker.js +27 -0
- package/dist/vite/servers/worker.js.map +1 -1
- package/package.json +12 -12
- package/src/entrypoints/main.ts +103 -15
- package/src/entrypoints/thread.ts +145 -34
- package/src/runtime/application/api/api.ts +12 -3
- package/src/runtime/application/api/constants.ts +5 -0
- package/src/runtime/application/api/router.ts +13 -3
- package/src/runtime/server/applications.ts +53 -14
- package/src/runtime/server/config.ts +4 -0
- package/src/runtime/server/pool.ts +100 -23
- package/src/runtime/server/proxy.ts +7 -1
- package/src/runtime/server/server.ts +23 -1
- package/src/runtime/types.ts +16 -1
- package/src/runtime/workers/application.ts +16 -1
- package/src/vite/servers/worker.ts +37 -1
package/src/entrypoints/main.ts
CHANGED
|
@@ -2,10 +2,16 @@ import type { Worker } from 'node:worker_threads'
|
|
|
2
2
|
import EventEmitter from 'node:events'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
ApplicationWorkerErrorEvent,
|
|
7
|
+
ApplicationWorkerReadyEvent,
|
|
8
|
+
ServerConfig,
|
|
9
|
+
} from 'nmtjs/runtime'
|
|
6
10
|
import type { ViteDevServer } from 'vite'
|
|
7
11
|
import { ApplicationServer, isServerConfig } from 'nmtjs/runtime'
|
|
8
12
|
|
|
13
|
+
import type { WorkerServerEventMap as BaseWorkerServerEventMap } from '../vite/servers/worker.ts'
|
|
14
|
+
|
|
9
15
|
declare global {
|
|
10
16
|
const __VITE_CONFIG__: string
|
|
11
17
|
const __APPLICATIONS_CONFIG__: string
|
|
@@ -27,15 +33,22 @@ const applicationsConfig: Record<
|
|
|
27
33
|
{ type: 'neemata' | 'custom'; specifier: string }
|
|
28
34
|
> = __APPLICATIONS_CONFIG__ ? JSON.parse(__APPLICATIONS_CONFIG__) : {}
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
type WorkerEventMap = BaseWorkerServerEventMap & {
|
|
37
|
+
'worker-error': [ApplicationWorkerErrorEvent]
|
|
38
|
+
'worker-ready': [ApplicationWorkerReadyEvent]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let _viteServerEvents: EventEmitter<WorkerEventMap> | undefined
|
|
31
42
|
let _viteWorkerServer: ViteDevServer | undefined
|
|
32
43
|
|
|
33
|
-
let server: ApplicationServer
|
|
44
|
+
let server: ApplicationServer | undefined
|
|
45
|
+
let hasActiveWorkerError = false
|
|
46
|
+
const isDev = _vite?.mode === 'development'
|
|
34
47
|
|
|
35
48
|
if (import.meta.env.DEV && import.meta.hot) {
|
|
36
49
|
import.meta.hot.accept('#server', async (module) => {
|
|
37
|
-
await
|
|
38
|
-
await
|
|
50
|
+
await shutdownServer()
|
|
51
|
+
await bootWithHandling(module?.default)
|
|
39
52
|
})
|
|
40
53
|
}
|
|
41
54
|
|
|
@@ -45,7 +58,9 @@ if (_vite) {
|
|
|
45
58
|
/* @vite-ignore */
|
|
46
59
|
_vite.options.configPath
|
|
47
60
|
).then((m) => m.default as import('../config.ts').NeemataConfig)
|
|
48
|
-
_viteServerEvents = new EventEmitter<
|
|
61
|
+
_viteServerEvents = new EventEmitter<WorkerEventMap>()
|
|
62
|
+
_viteServerEvents.on('worker-error', handleWorkerError)
|
|
63
|
+
_viteServerEvents.on('worker-ready', handleWorkerReady)
|
|
49
64
|
_viteWorkerServer = await createWorkerServer(
|
|
50
65
|
_vite.options,
|
|
51
66
|
_vite.mode,
|
|
@@ -54,18 +69,41 @@ if (_vite) {
|
|
|
54
69
|
)
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
async function
|
|
58
|
-
if (!isServerConfig(
|
|
59
|
-
|
|
72
|
+
async function bootServer(configValue: ServerConfig | undefined) {
|
|
73
|
+
if (!isServerConfig(configValue)) throw new InvalidServerConfigError()
|
|
74
|
+
const workerConfig = {
|
|
60
75
|
path: fileURLToPath(import.meta.resolve(`./thread${_ext}`)),
|
|
61
76
|
workerData: { vite: _vite?.mode },
|
|
62
77
|
worker: _viteServerEvents
|
|
63
|
-
? (worker) => {
|
|
78
|
+
? (worker: Worker) => {
|
|
64
79
|
_viteServerEvents.emit('worker', worker)
|
|
65
80
|
}
|
|
66
81
|
: undefined,
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
events: _viteServerEvents,
|
|
83
|
+
}
|
|
84
|
+
const appServer = new ApplicationServer(
|
|
85
|
+
configValue,
|
|
86
|
+
applicationsConfig,
|
|
87
|
+
workerConfig,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await appServer.start()
|
|
92
|
+
server = appServer
|
|
93
|
+
clearWorkerErrorOverlay()
|
|
94
|
+
} catch (error) {
|
|
95
|
+
await appServer.stop().catch(() => {})
|
|
96
|
+
throw error
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function bootWithHandling(configValue: ServerConfig | undefined) {
|
|
101
|
+
try {
|
|
102
|
+
await bootServer(configValue)
|
|
103
|
+
} catch (error) {
|
|
104
|
+
handleStartupError(error)
|
|
105
|
+
if (!isDev) throw error
|
|
106
|
+
}
|
|
69
107
|
}
|
|
70
108
|
|
|
71
109
|
let isTerminating = false
|
|
@@ -73,7 +111,7 @@ let isTerminating = false
|
|
|
73
111
|
async function handleTermination() {
|
|
74
112
|
if (isTerminating) return
|
|
75
113
|
isTerminating = true
|
|
76
|
-
await
|
|
114
|
+
await shutdownServer()
|
|
77
115
|
_viteWorkerServer?.close()
|
|
78
116
|
process.exit(0)
|
|
79
117
|
}
|
|
@@ -82,17 +120,66 @@ function handleUnexpectedError(error: Error) {
|
|
|
82
120
|
console.error(new Error('Unexpected Error:', { cause: error }))
|
|
83
121
|
}
|
|
84
122
|
|
|
123
|
+
async function shutdownServer() {
|
|
124
|
+
if (!server) return
|
|
125
|
+
try {
|
|
126
|
+
await server.stop()
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(
|
|
129
|
+
new Error('Failed to stop application server', { cause: error as Error }),
|
|
130
|
+
)
|
|
131
|
+
} finally {
|
|
132
|
+
server = undefined
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function handleWorkerError(event: ApplicationWorkerErrorEvent) {
|
|
137
|
+
hasActiveWorkerError = true
|
|
138
|
+
console.error(
|
|
139
|
+
new Error(`Worker ${event.application} thread ${event.threadId} error`, {
|
|
140
|
+
cause: event.error,
|
|
141
|
+
}),
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function handleWorkerReady(_: ApplicationWorkerReadyEvent) {
|
|
146
|
+
clearWorkerErrorOverlay()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function handleStartupError(error: unknown) {
|
|
150
|
+
const normalized = error instanceof Error ? error : new Error(String(error))
|
|
151
|
+
if (_viteServerEvents) {
|
|
152
|
+
_viteServerEvents.emit('worker-error', {
|
|
153
|
+
application: 'server',
|
|
154
|
+
threadId: -1,
|
|
155
|
+
error: normalized,
|
|
156
|
+
} as ApplicationWorkerErrorEvent)
|
|
157
|
+
} else {
|
|
158
|
+
hasActiveWorkerError = true
|
|
159
|
+
console.error(
|
|
160
|
+
new Error('Failed to start application server', { cause: normalized }),
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function clearWorkerErrorOverlay() {
|
|
166
|
+
if (!hasActiveWorkerError) return
|
|
167
|
+
hasActiveWorkerError = false
|
|
168
|
+
}
|
|
169
|
+
|
|
85
170
|
process.once('SIGTERM', handleTermination)
|
|
86
171
|
process.once('SIGINT', handleTermination)
|
|
87
172
|
process.on('uncaughtException', handleUnexpectedError)
|
|
88
173
|
process.on('unhandledRejection', handleUnexpectedError)
|
|
89
174
|
|
|
90
|
-
await
|
|
175
|
+
await bootWithHandling(
|
|
91
176
|
await import(
|
|
92
177
|
// @ts-expect-error
|
|
93
178
|
'#server'
|
|
94
179
|
).then((m) => m.default),
|
|
95
|
-
)
|
|
180
|
+
).catch(() => {
|
|
181
|
+
if (!isDev) process.exit(1)
|
|
182
|
+
})
|
|
96
183
|
|
|
97
184
|
const { format } = Intl.NumberFormat('en', {
|
|
98
185
|
notation: 'compact',
|
|
@@ -108,5 +195,6 @@ const printMem = () => {
|
|
|
108
195
|
`Memory Usage: RSS=${format(memoryUsage.rss)}, HeapTotal=${format(memoryUsage.heapTotal)}, HeapUsed=${format(memoryUsage.heapUsed)}, External=${format(memoryUsage.external)}, ArrayBuffers=${format(memoryUsage.arrayBuffers)}`,
|
|
109
196
|
)
|
|
110
197
|
}
|
|
198
|
+
void printMem
|
|
111
199
|
// printMem()
|
|
112
200
|
// setInterval(printMem, 5000)
|
|
@@ -2,7 +2,11 @@ import type { MessagePort } from 'node:worker_threads'
|
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { workerData as _workerData } from 'node:worker_threads'
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
ThreadErrorMessage,
|
|
7
|
+
ThreadPortMessage,
|
|
8
|
+
WorkerThreadErrorOrigin,
|
|
9
|
+
} from 'nmtjs/runtime'
|
|
6
10
|
import type { ModuleRunner } from 'vite/module-runner'
|
|
7
11
|
|
|
8
12
|
export type RunWorkerOptions = {
|
|
@@ -18,56 +22,163 @@ const workerData = _workerData as RunWorkerOptions
|
|
|
18
22
|
const ext = new URL(import.meta.url).pathname.endsWith('.ts') ? '.ts' : '.js'
|
|
19
23
|
const workerPath = fileURLToPath(import.meta.resolve(`./worker${ext}`))
|
|
20
24
|
|
|
25
|
+
type WorkerModule = typeof import('./worker.ts')
|
|
26
|
+
type WorkerRuntime = Awaited<ReturnType<WorkerModule['run']>>
|
|
27
|
+
|
|
28
|
+
const kReportedError = Symbol.for('nmtjs.worker.reported-error')
|
|
29
|
+
|
|
30
|
+
let runner: ModuleRunner | undefined
|
|
31
|
+
let workerModule: WorkerModule
|
|
32
|
+
let runtime: WorkerRuntime | undefined
|
|
33
|
+
let runtimeStarted = false
|
|
34
|
+
|
|
21
35
|
process.on('uncaughtException', (error) => {
|
|
22
|
-
|
|
36
|
+
reportError(error, 'runtime', { fatal: true })
|
|
23
37
|
})
|
|
24
38
|
|
|
25
39
|
process.on('unhandledRejection', (error) => {
|
|
26
|
-
|
|
40
|
+
reportError(error, 'runtime', { fatal: true })
|
|
27
41
|
})
|
|
28
42
|
|
|
29
|
-
process.on('exit',
|
|
30
|
-
|
|
43
|
+
process.on('exit', () => {
|
|
44
|
+
void cleanup()
|
|
31
45
|
})
|
|
32
46
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
runner = undefined
|
|
46
|
-
workerModule = await import(
|
|
47
|
-
/* @vite-ignore */
|
|
48
|
-
workerPath
|
|
49
|
-
)
|
|
47
|
+
async function cleanup() {
|
|
48
|
+
await stopRuntime()
|
|
49
|
+
await closeRunner()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function closeRunner() {
|
|
53
|
+
if (!runner) return
|
|
54
|
+
try {
|
|
55
|
+
await runner.close()
|
|
56
|
+
} catch (error) {
|
|
57
|
+
reportError(error, 'runtime', { fatal: false })
|
|
58
|
+
} finally {
|
|
59
|
+
runner = undefined
|
|
50
60
|
}
|
|
61
|
+
}
|
|
51
62
|
|
|
52
|
-
|
|
63
|
+
async function stopRuntime() {
|
|
64
|
+
if (!runtime) return
|
|
65
|
+
try {
|
|
66
|
+
if (runtimeStarted) {
|
|
67
|
+
await runtime.stop()
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
reportError(error, 'runtime', { fatal: false })
|
|
71
|
+
} finally {
|
|
72
|
+
runtimeStarted = false
|
|
73
|
+
}
|
|
74
|
+
}
|
|
53
75
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
76
|
+
function normalizeError(value: unknown): Error {
|
|
77
|
+
if (value instanceof Error) return value
|
|
78
|
+
if (typeof value === 'string') return new Error(value)
|
|
79
|
+
if (value && typeof value === 'object') {
|
|
80
|
+
try {
|
|
81
|
+
return new Error(JSON.stringify(value))
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
return new Error(String(value))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function serializeError(
|
|
88
|
+
error: Error,
|
|
89
|
+
origin: WorkerThreadErrorOrigin,
|
|
90
|
+
fatal: boolean,
|
|
91
|
+
): ThreadErrorMessage {
|
|
92
|
+
return {
|
|
93
|
+
message: error.message,
|
|
94
|
+
name: error.name,
|
|
95
|
+
stack: error.stack,
|
|
96
|
+
origin,
|
|
97
|
+
fatal,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function reportError(
|
|
102
|
+
value: unknown,
|
|
103
|
+
origin: WorkerThreadErrorOrigin,
|
|
104
|
+
options: { fatal?: boolean } = {},
|
|
105
|
+
): Error {
|
|
106
|
+
const fatal = options.fatal ?? origin !== 'runtime'
|
|
107
|
+
const error = normalizeError(value)
|
|
108
|
+
if (!(error as any)[kReportedError]) {
|
|
109
|
+
try {
|
|
110
|
+
workerData.port.postMessage({
|
|
111
|
+
type: 'error',
|
|
112
|
+
data: serializeError(error, origin, fatal),
|
|
113
|
+
} satisfies ThreadPortMessage)
|
|
114
|
+
} catch {}
|
|
115
|
+
console.error(new Error(`Worker thread ${origin} error`, { cause: error }))
|
|
116
|
+
;(error as any)[kReportedError] = true
|
|
117
|
+
}
|
|
118
|
+
return error
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function loadWorkerModule() {
|
|
122
|
+
try {
|
|
123
|
+
if (workerData.vite) {
|
|
124
|
+
const { createModuleRunner } = (await import(
|
|
125
|
+
'../vite/runners/worker.ts'
|
|
126
|
+
)) as typeof import('../vite/runners/worker.ts')
|
|
127
|
+
|
|
128
|
+
runner = createModuleRunner(workerData.vite)
|
|
129
|
+
workerModule = await runner.import(workerPath)
|
|
130
|
+
} else {
|
|
131
|
+
runner = undefined
|
|
132
|
+
workerModule = await import(
|
|
133
|
+
/* @vite-ignore */
|
|
134
|
+
workerPath
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw reportError(error, 'bootstrap')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function initializeRuntime() {
|
|
143
|
+
try {
|
|
144
|
+
runtime = await workerModule.run(workerData.runtime)
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw reportError(error, 'bootstrap')
|
|
147
|
+
}
|
|
57
148
|
|
|
58
149
|
workerData.port.on('message', async (msg) => {
|
|
59
150
|
if (msg.type === 'stop') {
|
|
60
|
-
await
|
|
151
|
+
await cleanup()
|
|
61
152
|
process.exit(0)
|
|
62
153
|
}
|
|
63
154
|
})
|
|
155
|
+
}
|
|
64
156
|
|
|
65
|
-
|
|
157
|
+
async function startRuntime() {
|
|
158
|
+
if (!runtime) return
|
|
159
|
+
try {
|
|
160
|
+
const hosts = (await runtime.start()) || undefined
|
|
161
|
+
runtimeStarted = true
|
|
162
|
+
workerData.port.postMessage({
|
|
163
|
+
type: 'ready',
|
|
164
|
+
data: { hosts },
|
|
165
|
+
} satisfies ThreadPortMessage)
|
|
166
|
+
} catch (error) {
|
|
167
|
+
throw reportError(error, 'start')
|
|
168
|
+
}
|
|
169
|
+
}
|
|
66
170
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} catch (error) {
|
|
72
|
-
console.error(new Error('Worker thread error:', { cause: error }))
|
|
171
|
+
async function main() {
|
|
172
|
+
await loadWorkerModule()
|
|
173
|
+
await initializeRuntime()
|
|
174
|
+
await startRuntime()
|
|
73
175
|
}
|
|
176
|
+
|
|
177
|
+
main().catch(async (error) => {
|
|
178
|
+
const normalized = error instanceof Error ? error : normalizeError(error)
|
|
179
|
+
if (!(normalized as any)[kReportedError]) {
|
|
180
|
+
reportError(normalized, 'bootstrap')
|
|
181
|
+
}
|
|
182
|
+
await cleanup()
|
|
183
|
+
process.exit(1)
|
|
184
|
+
})
|
|
@@ -17,12 +17,14 @@ import { ProtocolError } from '@nmtjs/protocol/server'
|
|
|
17
17
|
import { NeemataTypeError, registerDefaultLocale, type } from '@nmtjs/type'
|
|
18
18
|
import { prettifyError } from 'zod/mini'
|
|
19
19
|
|
|
20
|
+
import type { kDefaultProcedure as kDefaultProcedureKey } from './constants.ts'
|
|
20
21
|
import type { AnyFilter } from './filters.ts'
|
|
21
22
|
import type { AnyGuard } from './guards.ts'
|
|
22
23
|
import type { AnyMiddleware } from './middlewares.ts'
|
|
23
24
|
import type { AnyProcedure } from './procedure.ts'
|
|
24
25
|
import type { AnyRouter } from './router.ts'
|
|
25
26
|
import type { ApiCallContext } from './types.ts'
|
|
27
|
+
import { kDefaultProcedure } from './constants.ts'
|
|
26
28
|
|
|
27
29
|
registerDefaultLocale()
|
|
28
30
|
|
|
@@ -39,7 +41,10 @@ export type ApiOptions = {
|
|
|
39
41
|
timeout?: number
|
|
40
42
|
container: Container
|
|
41
43
|
logger: Logger
|
|
42
|
-
procedures: Map<
|
|
44
|
+
procedures: Map<
|
|
45
|
+
string | kDefaultProcedureKey,
|
|
46
|
+
{ procedure: AnyProcedure; path: AnyRouter[] }
|
|
47
|
+
>
|
|
43
48
|
guards: Set<AnyGuard>
|
|
44
49
|
middlewares: Set<AnyMiddleware>
|
|
45
50
|
filters: Set<AnyFilter>
|
|
@@ -57,8 +62,12 @@ export class ApplicationApi implements GatewayApi {
|
|
|
57
62
|
constructor(public options: ApiOptions) {}
|
|
58
63
|
|
|
59
64
|
find(procedureName: string) {
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
65
|
+
const procedure = this.options.procedures.get(procedureName)
|
|
66
|
+
if (procedure) return procedure
|
|
67
|
+
|
|
68
|
+
const fallback = this.options.procedures.get(kDefaultProcedure)
|
|
69
|
+
if (fallback) return fallback
|
|
70
|
+
|
|
62
71
|
throw NotFound()
|
|
63
72
|
}
|
|
64
73
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export const kProcedure: unique symbol = Symbol.for('neemata:ProcedureKey')
|
|
2
2
|
export type kProcedure = typeof kProcedure
|
|
3
3
|
|
|
4
|
+
export const kDefaultProcedure: unique symbol = Symbol.for(
|
|
5
|
+
'neemata:DefaultProcedureKey',
|
|
6
|
+
)
|
|
7
|
+
export type kDefaultProcedure = typeof kDefaultProcedure
|
|
8
|
+
|
|
4
9
|
export const kRouter: unique symbol = Symbol.for('neemata:RouterKey')
|
|
5
10
|
export type kRouter = typeof kRouter
|
|
6
11
|
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
TRouteContract,
|
|
7
7
|
TRouterContract,
|
|
8
8
|
} from '@nmtjs/contract'
|
|
9
|
+
import type { AnyType } from '@nmtjs/type/any'
|
|
9
10
|
import { c, IsRouterContract } from '@nmtjs/contract'
|
|
10
11
|
|
|
11
12
|
import type { AnyGuard } from './guards.ts'
|
|
@@ -25,6 +26,7 @@ export interface AnyRouter {
|
|
|
25
26
|
export interface AnyRootRouter extends AnyRouter {
|
|
26
27
|
[kRootRouter]: any
|
|
27
28
|
contract: TAnyRouterContract<Record<string, TRouteContract>, undefined>
|
|
29
|
+
default?: AnyProcedure<any>
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface Router<Contract extends TAnyRouterContract> extends AnyRouter {
|
|
@@ -56,6 +58,7 @@ export interface RootRouter<
|
|
|
56
58
|
>,
|
|
57
59
|
> extends Router<Contract> {
|
|
58
60
|
[kRootRouter]: any
|
|
61
|
+
default?: AnyProcedure<any>
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
export type MergeRoutersRoutesContracts<
|
|
@@ -79,17 +82,24 @@ export type ExtractRouterContracts<
|
|
|
79
82
|
: []
|
|
80
83
|
|
|
81
84
|
export function createRootRouter<Routers extends readonly AnyRouter[]>(
|
|
82
|
-
|
|
85
|
+
routers: Routers,
|
|
86
|
+
defaultProcedure?: AnyProcedure<
|
|
87
|
+
TProcedureContract<AnyType, AnyType, true | undefined, string | undefined>
|
|
88
|
+
>,
|
|
83
89
|
): RootRouter<
|
|
84
90
|
TRouterContract<
|
|
85
|
-
MergeRoutersRoutesContracts<ExtractRouterContracts<Routers>>,
|
|
91
|
+
MergeRoutersRoutesContracts<ExtractRouterContracts<[...Routers]>>,
|
|
86
92
|
undefined
|
|
87
93
|
>
|
|
88
94
|
> {
|
|
89
95
|
const routes: Record<string, any> = {}
|
|
90
96
|
for (const router of routers) Object.assign(routes, router.routes)
|
|
91
97
|
const router = createRouter({ name: undefined, routes: routes })
|
|
92
|
-
return Object.freeze({
|
|
98
|
+
return Object.freeze({
|
|
99
|
+
...router,
|
|
100
|
+
default: defaultProcedure,
|
|
101
|
+
[kRootRouter]: true,
|
|
102
|
+
}) as any
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
export function createRouter<
|
|
@@ -3,9 +3,14 @@ import { EventEmitter } from 'node:events'
|
|
|
3
3
|
import type { Logger } from '@nmtjs/core'
|
|
4
4
|
import type { ProxyableTransportType } from '@nmtjs/gateway'
|
|
5
5
|
|
|
6
|
+
import type { ThreadPortMessageTypes, WorkerThreadError } from '../types.ts'
|
|
6
7
|
import type { ServerApplicationConfig, ServerConfig } from './config.ts'
|
|
7
8
|
import type { Thread } from './pool.ts'
|
|
8
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
ApplicationServerWorkerConfig,
|
|
11
|
+
ApplicationWorkerErrorEvent,
|
|
12
|
+
ApplicationWorkerReadyEvent,
|
|
13
|
+
} from './server.ts'
|
|
9
14
|
import { Pool } from './pool.ts'
|
|
10
15
|
|
|
11
16
|
export type ApplicationProxyUpstream = {
|
|
@@ -48,6 +53,10 @@ export class ApplicationServerApplications extends EventEmitter<{
|
|
|
48
53
|
})
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
get appsNames() {
|
|
57
|
+
return this.params.applications
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
async start() {
|
|
52
61
|
const { logger, applications, applicationsConfig, serverConfig } =
|
|
53
62
|
this.params
|
|
@@ -88,29 +97,45 @@ export class ApplicationServerApplications extends EventEmitter<{
|
|
|
88
97
|
})
|
|
89
98
|
|
|
90
99
|
thread.on('ready', ({ hosts }) => {
|
|
91
|
-
|
|
100
|
+
this.removeThreadUpstreams(thread)
|
|
92
101
|
|
|
93
102
|
const keys: Array<{ application: string; key: string }> = []
|
|
103
|
+
const sanitizedHosts: ThreadPortMessageTypes['ready']['hosts'] = []
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
if (hosts?.length) {
|
|
106
|
+
for (const host of hosts) {
|
|
107
|
+
const url = new URL(host.url)
|
|
108
|
+
if (url.hostname === '0.0.0.0') url.hostname = '127.0.0.1'
|
|
99
109
|
|
|
100
|
-
|
|
101
|
-
type: host.type,
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
const normalizedUrl = url.toString()
|
|
111
|
+
sanitizedHosts.push({ type: host.type, url: normalizedUrl })
|
|
112
|
+
|
|
113
|
+
const upstream: ApplicationProxyUpstream = {
|
|
114
|
+
type: host.type,
|
|
115
|
+
url: normalizedUrl,
|
|
116
|
+
}
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
const key = `${upstream.type}:${upstream.url}`
|
|
119
|
+
keys.push({ application: applicationName, key })
|
|
120
|
+
this.addUpstream(applicationName, key, upstream)
|
|
121
|
+
}
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
this.upstreamsByThread.set(thread, keys)
|
|
125
|
+
|
|
126
|
+
this.emitWorkerReady({
|
|
127
|
+
application: applicationName,
|
|
128
|
+
threadId: thread.worker.threadId,
|
|
129
|
+
hosts: sanitizedHosts.length ? sanitizedHosts : undefined,
|
|
130
|
+
})
|
|
111
131
|
})
|
|
112
132
|
|
|
113
|
-
thread.
|
|
133
|
+
thread.on('error', (error: WorkerThreadError) => {
|
|
134
|
+
this.emitWorkerError({
|
|
135
|
+
application: applicationName,
|
|
136
|
+
threadId: thread.worker.threadId,
|
|
137
|
+
error,
|
|
138
|
+
})
|
|
114
139
|
this.removeThreadUpstreams(thread)
|
|
115
140
|
})
|
|
116
141
|
|
|
@@ -168,4 +193,18 @@ export class ApplicationServerApplications extends EventEmitter<{
|
|
|
168
193
|
}
|
|
169
194
|
}
|
|
170
195
|
}
|
|
196
|
+
|
|
197
|
+
protected emitWorkerReady(event: ApplicationWorkerReadyEvent) {
|
|
198
|
+
this.params.workerConfig.events?.emit('worker-ready', event)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
protected emitWorkerError(event: ApplicationWorkerErrorEvent) {
|
|
202
|
+
this.params.logger.error(
|
|
203
|
+
new Error(
|
|
204
|
+
`Worker [${event.application}] thread ${event.threadId} error`,
|
|
205
|
+
{ cause: event.error },
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
this.params.workerConfig.events?.emit('worker-error', event)
|
|
209
|
+
}
|
|
171
210
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TSError } from '@nmtjs/common'
|
|
2
2
|
import type { LoggingOptions } from '@nmtjs/core'
|
|
3
3
|
import type { Transport } from '@nmtjs/gateway'
|
|
4
|
+
import type { ApplicationOptions, PortUpstreamOptions } from '@nmtjs/proxy'
|
|
4
5
|
import type { Applications } from 'nmtjs/runtime/types'
|
|
5
6
|
|
|
6
7
|
import type { ApplicationConfig } from '../application/config.ts'
|
|
@@ -64,6 +65,9 @@ export interface ServerConfig {
|
|
|
64
65
|
proxy?: {
|
|
65
66
|
port: number
|
|
66
67
|
hostname: string
|
|
68
|
+
applications: {
|
|
69
|
+
[K in keyof Applications]?: Omit<ApplicationOptions, 'name'>
|
|
70
|
+
}
|
|
67
71
|
threads?: number
|
|
68
72
|
healthChecks?: { interval?: number }
|
|
69
73
|
tls?: { key: string; cert: string }
|