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
|
@@ -6,9 +6,11 @@ import { MessageChannel, Worker } from 'node:worker_threads'
|
|
|
6
6
|
import type {
|
|
7
7
|
JobTaskResult,
|
|
8
8
|
ServerPortMessageTypes,
|
|
9
|
+
ThreadErrorMessage,
|
|
9
10
|
ThreadPortMessage,
|
|
10
11
|
ThreadPortMessageTypes,
|
|
11
12
|
WorkerJobTask,
|
|
13
|
+
WorkerThreadError,
|
|
12
14
|
} from '../types.ts'
|
|
13
15
|
|
|
14
16
|
const omitExecArgv = ['--expose-gc']
|
|
@@ -22,7 +24,7 @@ export type ThreadState =
|
|
|
22
24
|
|
|
23
25
|
export class Thread extends EventEmitter<
|
|
24
26
|
{
|
|
25
|
-
error: [error:
|
|
27
|
+
error: [error: WorkerThreadError]
|
|
26
28
|
ready: [ThreadPortMessageTypes['ready']]
|
|
27
29
|
task: [ThreadPortMessageTypes['task']]
|
|
28
30
|
terminate: []
|
|
@@ -34,6 +36,8 @@ export class Thread extends EventEmitter<
|
|
|
34
36
|
> {
|
|
35
37
|
worker: Worker
|
|
36
38
|
state: ThreadState = 'pending'
|
|
39
|
+
protected readyMessage?: ThreadPortMessageTypes['ready']
|
|
40
|
+
protected startPromise?: Promise<void>
|
|
37
41
|
|
|
38
42
|
constructor(
|
|
39
43
|
readonly port: MessagePort,
|
|
@@ -46,40 +50,99 @@ export class Thread extends EventEmitter<
|
|
|
46
50
|
execArgv: process.execArgv.filter((f) => !omitExecArgv.includes(f)),
|
|
47
51
|
})
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
this.port.on('message', (msg: ThreadPortMessage) => {
|
|
50
54
|
const { type, data } = msg
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
switch (type) {
|
|
56
|
+
case 'ready': {
|
|
57
|
+
this.state = 'ready'
|
|
58
|
+
this.readyMessage = data
|
|
59
|
+
this.emit('ready', data)
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
case 'error': {
|
|
63
|
+
const error = createWorkerThreadError(data as ThreadErrorMessage)
|
|
64
|
+
this.state = 'error'
|
|
65
|
+
this.emit('error', error)
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
case 'task': {
|
|
69
|
+
this.emit('task', data as ThreadPortMessageTypes['task'])
|
|
70
|
+
const { id, task } = data as ThreadPortMessageTypes['task']
|
|
71
|
+
this.emit(`task-${id}`, task)
|
|
72
|
+
break
|
|
73
|
+
}
|
|
55
74
|
}
|
|
56
|
-
}
|
|
75
|
+
})
|
|
57
76
|
|
|
58
|
-
this.
|
|
77
|
+
this.worker.once('exit', (code) => {
|
|
78
|
+
if (this.state === 'terminating') return
|
|
79
|
+
const error = createWorkerThreadError(
|
|
80
|
+
{
|
|
81
|
+
message: `Worker thread ${this.worker.threadId} exited unexpectedly with code ${code}`,
|
|
82
|
+
name: 'WorkerThreadExitError',
|
|
83
|
+
origin: 'runtime',
|
|
84
|
+
fatal: code !== 0,
|
|
85
|
+
},
|
|
86
|
+
false,
|
|
87
|
+
)
|
|
88
|
+
this.state = 'error'
|
|
89
|
+
this.emit('error', error)
|
|
90
|
+
})
|
|
59
91
|
}
|
|
60
92
|
|
|
61
93
|
async start() {
|
|
94
|
+
if (this.state === 'ready') return
|
|
95
|
+
if (this.startPromise) return this.startPromise
|
|
62
96
|
switch (this.state) {
|
|
63
97
|
case 'error':
|
|
64
98
|
case 'terminating':
|
|
65
99
|
case 'starting':
|
|
66
100
|
throw new Error('Cannot start worker thread in current state')
|
|
67
|
-
case 'pending':
|
|
68
|
-
|
|
69
|
-
const signal = AbortSignal.timeout(15000)
|
|
70
|
-
try {
|
|
71
|
-
await once(this, 'ready', { signal })
|
|
72
|
-
} catch (err) {
|
|
73
|
-
const error = new Error(
|
|
74
|
-
'Worker thread did not become ready in time',
|
|
75
|
-
{ cause: err },
|
|
76
|
-
)
|
|
77
|
-
this.emit('error', error)
|
|
78
|
-
this.stop()
|
|
79
|
-
throw error
|
|
80
|
-
}
|
|
81
|
-
}
|
|
101
|
+
case 'pending':
|
|
102
|
+
break
|
|
82
103
|
}
|
|
104
|
+
this.state = 'starting'
|
|
105
|
+
this.startPromise = new Promise<void>((resolve, reject) => {
|
|
106
|
+
let settled = false
|
|
107
|
+
let timer: NodeJS.Timeout
|
|
108
|
+
const cleanup = () => {
|
|
109
|
+
if (settled) return
|
|
110
|
+
settled = true
|
|
111
|
+
if (timer) clearTimeout(timer)
|
|
112
|
+
this.off('ready', handleReady)
|
|
113
|
+
this.off('error', handleError)
|
|
114
|
+
this.startPromise = undefined
|
|
115
|
+
}
|
|
116
|
+
const handleReady = () => {
|
|
117
|
+
cleanup()
|
|
118
|
+
resolve()
|
|
119
|
+
}
|
|
120
|
+
const handleError = (error: WorkerThreadError) => {
|
|
121
|
+
cleanup()
|
|
122
|
+
reject(error)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.once('ready', handleReady)
|
|
126
|
+
this.once('error', handleError)
|
|
127
|
+
|
|
128
|
+
timer = setTimeout(() => {
|
|
129
|
+
const error = createWorkerThreadError(
|
|
130
|
+
{
|
|
131
|
+
message: 'Worker thread did not become ready in time',
|
|
132
|
+
name: 'WorkerStartupTimeoutError',
|
|
133
|
+
origin: 'start',
|
|
134
|
+
fatal: true,
|
|
135
|
+
},
|
|
136
|
+
false,
|
|
137
|
+
)
|
|
138
|
+
cleanup()
|
|
139
|
+
this.state = 'error'
|
|
140
|
+
this.emit('error', error)
|
|
141
|
+
reject(error)
|
|
142
|
+
}, 15000)
|
|
143
|
+
})
|
|
144
|
+
await this.startPromise
|
|
145
|
+
this.startPromise = undefined
|
|
83
146
|
}
|
|
84
147
|
|
|
85
148
|
async stop() {
|
|
@@ -126,6 +189,20 @@ export class Thread extends EventEmitter<
|
|
|
126
189
|
}
|
|
127
190
|
}
|
|
128
191
|
|
|
192
|
+
function createWorkerThreadError(
|
|
193
|
+
message: ThreadErrorMessage,
|
|
194
|
+
includeStack = true,
|
|
195
|
+
): WorkerThreadError {
|
|
196
|
+
const error = new Error(message.message) as WorkerThreadError
|
|
197
|
+
if (message.name) error.name = message.name
|
|
198
|
+
if (includeStack && message.stack) {
|
|
199
|
+
error.stack = message.stack
|
|
200
|
+
}
|
|
201
|
+
error.origin = message.origin
|
|
202
|
+
error.fatal = message.fatal
|
|
203
|
+
return error
|
|
204
|
+
}
|
|
205
|
+
|
|
129
206
|
export class Pool extends EventEmitter<{
|
|
130
207
|
workerReady: [worker: Worker]
|
|
131
208
|
workerTaskResult: [worker: Worker]
|
|
@@ -49,7 +49,13 @@ export class ApplicationServerProxy {
|
|
|
49
49
|
tls: config.tls
|
|
50
50
|
? { keyPath: config.tls.key, certPath: config.tls.cert }
|
|
51
51
|
: undefined,
|
|
52
|
-
applications:
|
|
52
|
+
applications: Object.entries(config.applications)
|
|
53
|
+
.filter(([_, options]) => options !== undefined)
|
|
54
|
+
.map(([app, options]) => ({
|
|
55
|
+
name: app,
|
|
56
|
+
routing: options!.routing,
|
|
57
|
+
sni: options!.sni,
|
|
58
|
+
})),
|
|
53
59
|
})
|
|
54
60
|
|
|
55
61
|
this.onAdd = (application, upstream) => {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import type EventEmitter from 'node:events'
|
|
1
2
|
import type { Worker } from 'node:worker_threads'
|
|
2
3
|
|
|
3
4
|
import type { Logger } from '@nmtjs/core'
|
|
4
5
|
import { createLogger } from '@nmtjs/core'
|
|
5
6
|
|
|
6
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
Store,
|
|
9
|
+
ThreadPortMessageTypes,
|
|
10
|
+
WorkerThreadError,
|
|
11
|
+
} from '../types.ts'
|
|
7
12
|
import type { ServerConfig } from './config.ts'
|
|
8
13
|
import { createStoreClient } from '../store/index.ts'
|
|
9
14
|
import { ApplicationServerApplications } from './applications.ts'
|
|
@@ -16,10 +21,27 @@ export type ApplicationServerRunOptions = {
|
|
|
16
21
|
jobs: boolean
|
|
17
22
|
}
|
|
18
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
|
+
|
|
19
36
|
export type ApplicationServerWorkerConfig = {
|
|
20
37
|
path: string
|
|
21
38
|
workerData?: any
|
|
22
39
|
worker?: (worker: Worker) => any
|
|
40
|
+
events?: EventEmitter<{
|
|
41
|
+
worker: [Worker]
|
|
42
|
+
'worker-ready': [ApplicationWorkerReadyEvent]
|
|
43
|
+
'worker-error': [ApplicationWorkerErrorEvent]
|
|
44
|
+
}>
|
|
23
45
|
}
|
|
24
46
|
|
|
25
47
|
export class ApplicationServer {
|
package/src/runtime/types.ts
CHANGED
|
@@ -7,6 +7,21 @@ import type { ApplicationConfig } from './application/config.ts'
|
|
|
7
7
|
import type { BaseRuntime } from './core/runtime.ts'
|
|
8
8
|
import type { LifecycleHook, StoreType } from './enums.ts'
|
|
9
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
|
+
|
|
10
25
|
export type ServerPortMessageTypes = {
|
|
11
26
|
stop: undefined
|
|
12
27
|
task: { id: string; task: WorkerJobTask }
|
|
@@ -14,7 +29,7 @@ export type ServerPortMessageTypes = {
|
|
|
14
29
|
|
|
15
30
|
export type ThreadPortMessageTypes = {
|
|
16
31
|
ready: { hosts?: { type: ProxyableTransportType; url: string }[] }
|
|
17
|
-
error:
|
|
32
|
+
error: ThreadErrorMessage
|
|
18
33
|
task: { id: string; task: JobTaskResult }
|
|
19
34
|
}
|
|
20
35
|
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
AnyMiddleware,
|
|
12
12
|
AnyProcedure,
|
|
13
13
|
AnyRouter,
|
|
14
|
+
kDefaultProcedure as kDefaultProcedureKey,
|
|
14
15
|
} from '../application/index.ts'
|
|
15
16
|
import type { ServerConfig } from '../server/config.ts'
|
|
16
17
|
import { ApplicationApi } from '../application/api/api.ts'
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
isProcedure,
|
|
20
21
|
isRootRouter,
|
|
21
22
|
isRouter,
|
|
23
|
+
kDefaultProcedure,
|
|
22
24
|
kRootRouter,
|
|
23
25
|
} from '../application/index.ts'
|
|
24
26
|
import { LifecycleHook, WorkerType } from '../enums.ts'
|
|
@@ -37,7 +39,10 @@ export class ApplicationWorkerRuntime extends BaseWorkerRuntime {
|
|
|
37
39
|
transports!: GatewayOptions['transports']
|
|
38
40
|
|
|
39
41
|
routers = new Map<string | kRootRouter, AnyRouter>()
|
|
40
|
-
procedures = new Map<
|
|
42
|
+
procedures = new Map<
|
|
43
|
+
string | kDefaultProcedureKey,
|
|
44
|
+
{ procedure: AnyProcedure; path: AnyRouter[] }
|
|
45
|
+
>()
|
|
41
46
|
filters = new Set<AnyFilter>()
|
|
42
47
|
middlewares = new Set<AnyMiddleware>()
|
|
43
48
|
guards = new Set<AnyGuard>()
|
|
@@ -166,6 +171,16 @@ export class ApplicationWorkerRuntime extends BaseWorkerRuntime {
|
|
|
166
171
|
this.routers.set(kRootRouter, router)
|
|
167
172
|
this.registerRouter(router, [])
|
|
168
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
|
+
|
|
169
184
|
for (const filter of filters) this.filters.add(filter)
|
|
170
185
|
for (const middleware of middlewares) this.middlewares.add(middleware)
|
|
171
186
|
for (const guard of guards) this.guards.add(guard)
|
|
@@ -17,11 +17,17 @@ import { buildPlugins } from '../plugins.ts'
|
|
|
17
17
|
import { createBroadcastChannel } from '../runners/worker.ts'
|
|
18
18
|
import { createServer } from '../server.ts'
|
|
19
19
|
|
|
20
|
+
export type WorkerServerEventMap = {
|
|
21
|
+
worker: [Worker]
|
|
22
|
+
'worker-error': [unknown]
|
|
23
|
+
'worker-ready': [unknown]
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
export async function createWorkerServer(
|
|
21
27
|
options: ViteConfigOptions,
|
|
22
28
|
mode: 'development' | 'production',
|
|
23
29
|
neemataConfig: NeemataConfig,
|
|
24
|
-
events: EventEmitter<
|
|
30
|
+
events: EventEmitter<WorkerServerEventMap>,
|
|
25
31
|
): Promise<ViteDevServer> {
|
|
26
32
|
const config = createConfig(options)
|
|
27
33
|
const applicationEntries = Object.values(options.applicationImports).map(
|
|
@@ -94,6 +100,36 @@ export async function createWorkerServer(
|
|
|
94
100
|
},
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
let lastError: any
|
|
104
|
+
|
|
105
|
+
events.on('worker-error', (payload: any) => {
|
|
106
|
+
lastError = payload?.error ?? payload
|
|
107
|
+
const error =
|
|
108
|
+
lastError instanceof Error
|
|
109
|
+
? lastError
|
|
110
|
+
: new Error(String(lastError))
|
|
111
|
+
const message = {
|
|
112
|
+
type: 'error',
|
|
113
|
+
err: {
|
|
114
|
+
message: error.message,
|
|
115
|
+
stack: error.stack ?? '',
|
|
116
|
+
plugin: 'neemata:worker',
|
|
117
|
+
},
|
|
118
|
+
} satisfies HotPayload
|
|
119
|
+
for (const client of clients.values()) {
|
|
120
|
+
client.send(message)
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
events.on('worker-ready', () => {
|
|
125
|
+
if (!lastError) return
|
|
126
|
+
lastError = undefined
|
|
127
|
+
const message: HotPayload = { type: 'full-reload' }
|
|
128
|
+
for (const client of clients.values()) {
|
|
129
|
+
client.send(message)
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
97
133
|
const environment = new DevEnvironment(name, config, {
|
|
98
134
|
hot: mode === 'development',
|
|
99
135
|
transport,
|