@platformatic/runtime 3.4.1 → 3.5.0
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/README.md +1 -1
- package/config.d.ts +224 -77
- package/eslint.config.js +3 -5
- package/index.d.ts +73 -24
- package/index.js +173 -29
- package/lib/config.js +279 -197
- package/lib/errors.js +126 -34
- package/lib/generator.js +640 -0
- package/lib/logger.js +43 -41
- package/lib/management-api.js +109 -118
- package/lib/prom-server.js +202 -16
- package/lib/runtime.js +1963 -585
- package/lib/scheduler.js +119 -0
- package/lib/schema.js +22 -234
- package/lib/shared-http-cache.js +43 -0
- package/lib/upgrade.js +6 -8
- package/lib/utils.js +6 -61
- package/lib/version.js +7 -0
- package/lib/versions/v1.36.0.js +2 -4
- package/lib/versions/v1.5.0.js +2 -4
- package/lib/versions/v2.0.0.js +3 -5
- package/lib/versions/v3.0.0.js +16 -0
- package/lib/worker/controller.js +302 -0
- package/lib/worker/http-cache.js +171 -0
- package/lib/worker/interceptors.js +190 -10
- package/lib/worker/itc.js +146 -59
- package/lib/worker/main.js +220 -81
- package/lib/worker/messaging.js +182 -0
- package/lib/worker/round-robin-map.js +62 -0
- package/lib/worker/shared-context.js +22 -0
- package/lib/worker/symbols.js +14 -5
- package/package.json +47 -38
- package/schema.json +1383 -55
- package/help/compile.txt +0 -8
- package/help/help.txt +0 -5
- package/help/start.txt +0 -21
- package/index.test-d.ts +0 -41
- package/lib/build-server.js +0 -69
- package/lib/compile.js +0 -98
- package/lib/dependencies.js +0 -59
- package/lib/generator/README.md +0 -32
- package/lib/generator/errors.js +0 -10
- package/lib/generator/runtime-generator.d.ts +0 -37
- package/lib/generator/runtime-generator.js +0 -498
- package/lib/start.js +0 -190
- package/lib/worker/app.js +0 -278
- package/lib/worker/default-stackable.js +0 -33
- package/lib/worker/metrics.js +0 -122
- package/runtime.mjs +0 -54
package/lib/worker/itc.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { ensureLoggableError } from '@platformatic/foundation'
|
|
2
|
+
import { ITC } from '@platformatic/itc'
|
|
3
|
+
import { Unpromise } from '@watchable/unpromise'
|
|
4
|
+
import { once } from 'node:events'
|
|
5
|
+
import { parentPort, workerData } from 'node:worker_threads'
|
|
6
|
+
import {
|
|
7
|
+
ApplicationExitedError,
|
|
8
|
+
FailedToPerformCustomHealthCheckError,
|
|
9
|
+
FailedToPerformCustomReadinessCheckError,
|
|
10
|
+
FailedToRetrieveGraphQLSchemaError,
|
|
11
|
+
FailedToRetrieveHealthError,
|
|
12
|
+
FailedToRetrieveMetaError,
|
|
13
|
+
FailedToRetrieveMetricsError,
|
|
14
|
+
FailedToRetrieveOpenAPISchemaError,
|
|
15
|
+
WorkerExitedError
|
|
16
|
+
} from '../errors.js'
|
|
17
|
+
import { updateUndiciInterceptors } from './interceptors.js'
|
|
18
|
+
import { MessagingITC } from './messaging.js'
|
|
19
|
+
import { kApplicationId, kITC, kId, kWorkerId } from './symbols.js'
|
|
11
20
|
|
|
12
21
|
async function safeHandleInITC (worker, fn) {
|
|
13
22
|
try {
|
|
@@ -23,7 +32,11 @@ async function safeHandleInITC (worker, fn) {
|
|
|
23
32
|
])
|
|
24
33
|
|
|
25
34
|
if (typeof exitCode === 'number') {
|
|
26
|
-
|
|
35
|
+
if (typeof worker[kWorkerId] !== 'undefined') {
|
|
36
|
+
throw new WorkerExitedError(worker[kWorkerId], worker[kApplicationId], exitCode)
|
|
37
|
+
} else {
|
|
38
|
+
throw new ApplicationExitedError(worker[kId], exitCode)
|
|
39
|
+
}
|
|
27
40
|
} else {
|
|
28
41
|
ac.abort()
|
|
29
42
|
}
|
|
@@ -42,121 +55,195 @@ async function safeHandleInITC (worker, fn) {
|
|
|
42
55
|
}
|
|
43
56
|
}
|
|
44
57
|
|
|
45
|
-
async function
|
|
46
|
-
|
|
58
|
+
async function closeITC (dispatcher, itc, messaging) {
|
|
59
|
+
await dispatcher.interceptor.close()
|
|
60
|
+
itc.close()
|
|
61
|
+
messaging.close()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function sendViaITC (worker, name, message, transferList) {
|
|
65
|
+
return safeHandleInITC(worker, () => worker[kITC].send(name, message, { transferList }))
|
|
47
66
|
}
|
|
48
67
|
|
|
49
|
-
async function waitEventFromITC (worker, event) {
|
|
68
|
+
export async function waitEventFromITC (worker, event) {
|
|
50
69
|
return safeHandleInITC(worker, () => once(worker[kITC], event))
|
|
51
70
|
}
|
|
52
71
|
|
|
53
|
-
function setupITC (
|
|
72
|
+
export function setupITC (controller, application, dispatcher, sharedContext) {
|
|
73
|
+
const messaging = new MessagingITC(controller.appConfig.id, workerData.config)
|
|
74
|
+
|
|
75
|
+
Object.assign(globalThis.platformatic ?? {}, {
|
|
76
|
+
messaging: {
|
|
77
|
+
handle: messaging.handle.bind(messaging),
|
|
78
|
+
send: messaging.send.bind(messaging)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
54
82
|
const itc = new ITC({
|
|
55
|
-
name:
|
|
83
|
+
name: controller.appConfig.id + '-worker',
|
|
56
84
|
port: parentPort,
|
|
57
85
|
handlers: {
|
|
58
86
|
async start () {
|
|
59
|
-
const status =
|
|
87
|
+
const status = controller.getStatus()
|
|
60
88
|
|
|
61
89
|
if (status === 'starting') {
|
|
62
|
-
await once(
|
|
90
|
+
await once(controller, 'start')
|
|
63
91
|
} else {
|
|
64
|
-
|
|
65
|
-
|
|
92
|
+
// This gives a chance to a capability to perform custom logic
|
|
93
|
+
globalThis.platformatic.events.emit('start')
|
|
66
94
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
95
|
+
try {
|
|
96
|
+
await controller.start()
|
|
97
|
+
} catch (e) {
|
|
98
|
+
await controller.stop(true)
|
|
70
99
|
|
|
71
|
-
|
|
100
|
+
// Reply to the runtime that the start failed, so it can cleanup
|
|
101
|
+
once(itc, 'application:worker:start:processed').then(() => {
|
|
102
|
+
closeITC(dispatcher, itc, messaging).catch(() => {})
|
|
103
|
+
})
|
|
72
104
|
|
|
73
|
-
|
|
74
|
-
|
|
105
|
+
throw ensureLoggableError(e)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (application.entrypoint) {
|
|
110
|
+
await controller.listen()
|
|
111
|
+
}
|
|
75
112
|
|
|
76
|
-
|
|
113
|
+
dispatcher.replaceServer(await controller.capability.getDispatchTarget())
|
|
114
|
+
return application.entrypoint ? controller.capability.getUrl() : null
|
|
77
115
|
},
|
|
78
116
|
|
|
79
|
-
async stop () {
|
|
80
|
-
const status =
|
|
117
|
+
async stop ({ force, dependents }) {
|
|
118
|
+
const status = controller.getStatus()
|
|
81
119
|
|
|
82
|
-
if (status === 'starting') {
|
|
83
|
-
await once(
|
|
120
|
+
if (!force && status === 'starting') {
|
|
121
|
+
await once(controller, 'start')
|
|
84
122
|
}
|
|
85
123
|
|
|
86
|
-
if (
|
|
87
|
-
|
|
124
|
+
if (force || status.startsWith('start')) {
|
|
125
|
+
// This gives a chance to a capability to perform custom logic
|
|
126
|
+
globalThis.platformatic.events.emit('stop')
|
|
127
|
+
|
|
128
|
+
await controller.stop(force, dependents)
|
|
88
129
|
}
|
|
89
130
|
|
|
90
|
-
|
|
91
|
-
|
|
131
|
+
once(itc, 'application:worker:stop:processed').then(() => {
|
|
132
|
+
closeITC(dispatcher, itc, messaging).catch(() => {})
|
|
133
|
+
})
|
|
92
134
|
},
|
|
93
135
|
|
|
94
136
|
async build () {
|
|
95
|
-
return
|
|
137
|
+
return controller.capability.build()
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
async removeFromMesh () {
|
|
141
|
+
return dispatcher.interceptor.close()
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
inject (injectParams) {
|
|
145
|
+
return controller.capability.inject(injectParams)
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
async updateUndiciInterceptors (undiciConfig) {
|
|
149
|
+
await updateUndiciInterceptors(undiciConfig)
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async updateWorkersCount (data) {
|
|
153
|
+
const { workers } = data
|
|
154
|
+
workerData.applicationConfig.workers = workers
|
|
155
|
+
workerData.worker.count = workers
|
|
96
156
|
},
|
|
97
157
|
|
|
98
158
|
getStatus () {
|
|
99
|
-
return
|
|
159
|
+
return controller.getStatus()
|
|
100
160
|
},
|
|
101
161
|
|
|
102
|
-
|
|
103
|
-
return
|
|
162
|
+
getApplicationInfo () {
|
|
163
|
+
return controller.capability.getInfo()
|
|
104
164
|
},
|
|
105
165
|
|
|
106
|
-
async
|
|
107
|
-
const current = await
|
|
166
|
+
async getApplicationConfig () {
|
|
167
|
+
const current = await controller.capability.getConfig()
|
|
108
168
|
// Remove all undefined keys from the config
|
|
109
169
|
return JSON.parse(JSON.stringify(current))
|
|
110
170
|
},
|
|
111
171
|
|
|
112
|
-
async
|
|
172
|
+
async getApplicationEnv () {
|
|
113
173
|
// Remove all undefined keys from the config
|
|
114
|
-
return JSON.parse(JSON.stringify({ ...process.env, ...(await
|
|
174
|
+
return JSON.parse(JSON.stringify({ ...process.env, ...(await controller.capability.getEnv()) }))
|
|
115
175
|
},
|
|
116
176
|
|
|
117
|
-
async
|
|
177
|
+
async getApplicationOpenAPISchema () {
|
|
118
178
|
try {
|
|
119
|
-
return await
|
|
179
|
+
return await controller.capability.getOpenapiSchema()
|
|
120
180
|
} catch (err) {
|
|
121
|
-
throw new
|
|
181
|
+
throw new FailedToRetrieveOpenAPISchemaError(application.id, err.message)
|
|
122
182
|
}
|
|
123
183
|
},
|
|
124
184
|
|
|
125
|
-
async
|
|
185
|
+
async getApplicationGraphQLSchema () {
|
|
126
186
|
try {
|
|
127
|
-
return await
|
|
187
|
+
return await controller.capability.getGraphqlSchema()
|
|
128
188
|
} catch (err) {
|
|
129
|
-
throw new
|
|
189
|
+
throw new FailedToRetrieveGraphQLSchemaError(application.id, err.message)
|
|
130
190
|
}
|
|
131
191
|
},
|
|
132
192
|
|
|
133
|
-
async
|
|
193
|
+
async getApplicationMeta () {
|
|
134
194
|
try {
|
|
135
|
-
return await
|
|
195
|
+
return await controller.capability.getMeta()
|
|
136
196
|
} catch (err) {
|
|
137
|
-
throw new
|
|
197
|
+
throw new FailedToRetrieveMetaError(application.id, err.message)
|
|
138
198
|
}
|
|
139
199
|
},
|
|
140
200
|
|
|
141
201
|
async getMetrics (format) {
|
|
142
202
|
try {
|
|
143
|
-
return await
|
|
203
|
+
return await controller.getMetrics({ format })
|
|
144
204
|
} catch (err) {
|
|
145
|
-
throw new
|
|
205
|
+
throw new FailedToRetrieveMetricsError(application.id, err.message)
|
|
146
206
|
}
|
|
147
207
|
},
|
|
148
208
|
|
|
149
|
-
|
|
150
|
-
|
|
209
|
+
async getHealth () {
|
|
210
|
+
try {
|
|
211
|
+
return await controller.getHealth()
|
|
212
|
+
} catch (err) {
|
|
213
|
+
throw new FailedToRetrieveHealthError(application.id, err.message)
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
async getCustomHealthCheck () {
|
|
218
|
+
try {
|
|
219
|
+
return await controller.capability.getCustomHealthCheck()
|
|
220
|
+
} catch (err) {
|
|
221
|
+
throw new FailedToPerformCustomHealthCheckError(application.id, err.message)
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
async getCustomReadinessCheck () {
|
|
226
|
+
try {
|
|
227
|
+
return await controller.capability.getCustomReadinessCheck()
|
|
228
|
+
} catch (err) {
|
|
229
|
+
throw new FailedToPerformCustomReadinessCheckError(application.id, err.message)
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
setSharedContext (context) {
|
|
234
|
+
sharedContext._set(context)
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
saveMessagingChannel (channel) {
|
|
238
|
+
messaging.addSource(channel)
|
|
151
239
|
}
|
|
152
240
|
}
|
|
153
241
|
})
|
|
154
242
|
|
|
155
|
-
|
|
243
|
+
controller.on('changed', () => {
|
|
156
244
|
itc.notify('changed')
|
|
157
245
|
})
|
|
158
246
|
|
|
247
|
+
itc.listen()
|
|
159
248
|
return itc
|
|
160
249
|
}
|
|
161
|
-
|
|
162
|
-
module.exports = { sendViaITC, setupITC, waitEventFromITC }
|
package/lib/worker/main.js
CHANGED
|
@@ -1,40 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const { pathToFileURL } = require('node:url')
|
|
7
|
-
|
|
8
|
-
const pino = require('pino')
|
|
9
|
-
const { fetch, setGlobalDispatcher, Agent } = require('undici')
|
|
10
|
-
const { wire } = require('undici-thread-interceptor')
|
|
11
|
-
|
|
12
|
-
const { PlatformaticApp } = require('./app')
|
|
13
|
-
const { setupITC } = require('./itc')
|
|
14
|
-
const loadInterceptors = require('./interceptors')
|
|
15
|
-
const {
|
|
16
|
-
MessagePortWritable,
|
|
17
|
-
createPinoWritable,
|
|
1
|
+
import {
|
|
2
|
+
buildPinoFormatters,
|
|
3
|
+
buildPinoTimestamp,
|
|
4
|
+
disablePinoDirectWrite,
|
|
5
|
+
ensureLoggableError,
|
|
18
6
|
executeWithTimeout,
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
7
|
+
getPrivateSymbol
|
|
8
|
+
} from '@platformatic/foundation'
|
|
9
|
+
import dotenv from 'dotenv'
|
|
10
|
+
import { subscribe } from 'node:diagnostics_channel'
|
|
11
|
+
import { EventEmitter } from 'node:events'
|
|
12
|
+
import { ServerResponse } from 'node:http'
|
|
13
|
+
import inspector from 'node:inspector'
|
|
14
|
+
import { hostname } from 'node:os'
|
|
15
|
+
import { resolve } from 'node:path'
|
|
16
|
+
import { pathToFileURL } from 'node:url'
|
|
17
|
+
import { threadId, workerData } from 'node:worker_threads'
|
|
18
|
+
import pino from 'pino'
|
|
19
|
+
import { fetch } from 'undici'
|
|
20
|
+
import { Controller } from './controller.js'
|
|
21
|
+
import { setDispatcher } from './interceptors.js'
|
|
22
|
+
import { setupITC } from './itc.js'
|
|
23
|
+
import { SharedContext } from './shared-context.js'
|
|
24
|
+
import { kId, kITC, kStderrMarker } from './symbols.js'
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
function handleUnhandled (app, type, err) {
|
|
27
|
+
const label =
|
|
28
|
+
workerData.worker.count > 1
|
|
29
|
+
? `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
|
|
30
|
+
: `application "${workerData.applicationConfig.id}"`
|
|
25
31
|
|
|
26
|
-
globalThis.
|
|
27
|
-
globalThis[kId] = threadId
|
|
28
|
-
|
|
29
|
-
let app
|
|
30
|
-
const config = workerData.config
|
|
31
|
-
globalThis.platformatic = Object.assign(globalThis.platformatic ?? {}, { logger: createLogger() })
|
|
32
|
-
|
|
33
|
-
function handleUnhandled (type, err) {
|
|
34
|
-
globalThis.platformatic.logger.error(
|
|
35
|
-
{ err: ensureLoggableError(err) },
|
|
36
|
-
`Service ${workerData.serviceConfig.id} threw an ${type}.`
|
|
37
|
-
)
|
|
32
|
+
globalThis.platformatic.logger.error({ err: ensureLoggableError(err) }, `The ${label} threw an ${type}.`)
|
|
38
33
|
|
|
39
34
|
executeWithTimeout(app?.stop(), 1000)
|
|
40
35
|
.catch()
|
|
@@ -43,55 +38,108 @@ function handleUnhandled (type, err) {
|
|
|
43
38
|
})
|
|
44
39
|
}
|
|
45
40
|
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
function patchLogging () {
|
|
42
|
+
disablePinoDirectWrite()
|
|
43
|
+
|
|
44
|
+
const kFormatForStderr = getPrivateSymbol(console, 'kFormatForStderr')
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
// To avoid out of order printing on the main thread, instruct console to only print to the stdout.
|
|
47
|
+
console._stderr = console._stdout
|
|
48
|
+
console._stderrErrorHandler = console._stdoutErrorHandler
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
// To recognize stderr in the main thread, each line is prepended with a special private Unicode character.
|
|
51
|
+
const originalFormatter = console[kFormatForStderr]
|
|
52
|
+
console[kFormatForStderr] = function (args) {
|
|
53
|
+
let string = kStderrMarker + originalFormatter(args).replaceAll('\n', '\n' + kStderrMarker)
|
|
54
|
+
|
|
55
|
+
if (string.endsWith(kStderrMarker)) {
|
|
56
|
+
string = string.slice(0, -1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return string
|
|
60
|
+
}
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
function createLogger () {
|
|
64
|
+
// Do not propagate runtime transports to the worker
|
|
65
|
+
if (workerData.config.logger) {
|
|
66
|
+
delete workerData.config.logger.transport
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const pinoOptions = {
|
|
70
|
+
level: 'trace',
|
|
71
|
+
name: workerData.applicationConfig.id,
|
|
72
|
+
...workerData.config.logger
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (workerData.worker?.count > 1) {
|
|
76
|
+
pinoOptions.base = { pid: process.pid, hostname: hostname(), worker: workerData.worker.index }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (pinoOptions.formatters) {
|
|
80
|
+
pinoOptions.formatters = buildPinoFormatters(pinoOptions.formatters)
|
|
81
|
+
}
|
|
82
|
+
if (pinoOptions.timestamp) {
|
|
83
|
+
pinoOptions.timestamp = buildPinoTimestamp(pinoOptions.timestamp)
|
|
59
84
|
}
|
|
60
85
|
|
|
61
|
-
|
|
86
|
+
return pino(pinoOptions)
|
|
87
|
+
}
|
|
62
88
|
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
89
|
+
async function performPreloading (...sources) {
|
|
90
|
+
for (const source of sources) {
|
|
91
|
+
const preload = typeof source.preload === 'string' ? [source.preload] : source.preload
|
|
66
92
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (config.undici.interceptors[key]) {
|
|
71
|
-
interceptors[key] = await loadInterceptors(_require, config.undici.interceptors[key])
|
|
93
|
+
if (Array.isArray(preload)) {
|
|
94
|
+
for (const file of preload) {
|
|
95
|
+
await import(pathToFileURL(file))
|
|
72
96
|
}
|
|
73
97
|
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
74
100
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
101
|
+
async function main () {
|
|
102
|
+
globalThis.fetch = fetch
|
|
103
|
+
globalThis[kId] = threadId
|
|
104
|
+
globalThis.platformatic = Object.assign(globalThis.platformatic ?? {}, {
|
|
105
|
+
logger: createLogger(),
|
|
106
|
+
events: new EventEmitter()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const config = workerData.config
|
|
110
|
+
|
|
111
|
+
await performPreloading(config, workerData.applicationConfig)
|
|
112
|
+
|
|
113
|
+
const application = workerData.applicationConfig
|
|
114
|
+
|
|
115
|
+
// Load env file and mixin env vars from application config
|
|
116
|
+
let envfile
|
|
117
|
+
if (application.envfile) {
|
|
118
|
+
envfile = resolve(workerData.dirname, application.envfile)
|
|
119
|
+
} else {
|
|
120
|
+
envfile = resolve(workerData.applicationConfig.path, '.env')
|
|
78
121
|
}
|
|
79
122
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
123
|
+
globalThis.platformatic.logger.debug({ envfile }, 'Loading envfile...')
|
|
124
|
+
|
|
125
|
+
dotenv.config({
|
|
126
|
+
path: envfile
|
|
127
|
+
})
|
|
84
128
|
|
|
85
|
-
|
|
129
|
+
if (config.env) {
|
|
130
|
+
Object.assign(process.env, config.env)
|
|
131
|
+
}
|
|
132
|
+
if (application.env) {
|
|
133
|
+
Object.assign(process.env, application.env)
|
|
134
|
+
}
|
|
86
135
|
|
|
87
|
-
|
|
88
|
-
const threadDispatcher = wire({ port: parentPort, useNetwork: service.useHttp, timeout: true })
|
|
136
|
+
const { threadDispatcher } = await setDispatcher(config)
|
|
89
137
|
|
|
90
|
-
// If the
|
|
138
|
+
// If the application is an entrypoint and runtime server config is defined, use it.
|
|
91
139
|
let serverConfig = null
|
|
92
|
-
if (config.server &&
|
|
140
|
+
if (config.server && application.entrypoint) {
|
|
93
141
|
serverConfig = config.server
|
|
94
|
-
} else if (
|
|
142
|
+
} else if (application.useHttp) {
|
|
95
143
|
serverConfig = {
|
|
96
144
|
port: 0,
|
|
97
145
|
hostname: '127.0.0.1',
|
|
@@ -99,37 +147,128 @@ async function main () {
|
|
|
99
147
|
}
|
|
100
148
|
}
|
|
101
149
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
150
|
+
const inspectorOptions = workerData.inspectorOptions
|
|
151
|
+
|
|
152
|
+
if (inspectorOptions) {
|
|
153
|
+
for (let i = 0; !inspector.url(); i++) {
|
|
154
|
+
inspector.open(inspectorOptions.port + i, inspectorOptions.host, inspectorOptions.breakFirstLine)
|
|
107
155
|
}
|
|
156
|
+
|
|
157
|
+
const url = new URL(inspector.url())
|
|
158
|
+
|
|
159
|
+
url.protocol = 'http'
|
|
160
|
+
url.pathname = '/json/list'
|
|
161
|
+
|
|
162
|
+
const res = await fetch(url)
|
|
163
|
+
const [{ devtoolsFrontendUrl }] = await res.json()
|
|
164
|
+
|
|
165
|
+
console.log(`For ${application.id} debugger open the following in chrome: "${devtoolsFrontendUrl}"`)
|
|
108
166
|
}
|
|
109
167
|
|
|
110
168
|
// Create the application
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
169
|
+
// Add idLabel to metrics config to determine which label name to use (defaults to applicationId)
|
|
170
|
+
const metricsConfig = config.metrics
|
|
171
|
+
? {
|
|
172
|
+
...config.metrics,
|
|
173
|
+
idLabel: config.metrics.applicationLabel || 'applicationId'
|
|
174
|
+
}
|
|
175
|
+
: config.metrics
|
|
176
|
+
|
|
177
|
+
const controller = new Controller(
|
|
178
|
+
application,
|
|
179
|
+
workerData.worker.count > 1 ? workerData.worker.index : undefined,
|
|
180
|
+
application.telemetry,
|
|
114
181
|
config.logger,
|
|
115
182
|
serverConfig,
|
|
116
|
-
|
|
183
|
+
metricsConfig,
|
|
117
184
|
!!config.managementApi,
|
|
118
185
|
!!config.watch
|
|
119
186
|
)
|
|
120
187
|
|
|
121
|
-
|
|
188
|
+
if (config.exitOnUnhandledErrors) {
|
|
189
|
+
process.on('uncaughtException', handleUnhandled.bind(null, controller, 'uncaught exception'))
|
|
190
|
+
process.on('unhandledRejection', handleUnhandled.bind(null, controller, 'unhandled rejection'))
|
|
122
191
|
|
|
123
|
-
|
|
124
|
-
|
|
192
|
+
process.on('newListener', event => {
|
|
193
|
+
if (event === 'uncaughtException' || event === 'unhandledRejection') {
|
|
194
|
+
globalThis.platformatic.logger.warn(
|
|
195
|
+
`A listener has been added for the "process.${event}" event. This listener will be never triggered as Watt default behavior will kill the process before.\n To disable this behavior, set "exitOnUnhandledErrors" to false in the runtime config.`
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
}
|
|
125
200
|
|
|
126
|
-
|
|
127
|
-
const dependencies = config.autoload ? await app.getBootstrapDependencies() : []
|
|
128
|
-
itc.notify('init', { dependencies })
|
|
129
|
-
itc.listen()
|
|
201
|
+
await controller.init()
|
|
130
202
|
|
|
203
|
+
if (application.entrypoint && config.basePath) {
|
|
204
|
+
const meta = await controller.capability.getMeta()
|
|
205
|
+
if (!meta.gateway.wantsAbsoluteUrls) {
|
|
206
|
+
stripBasePath(config.basePath)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const sharedContext = new SharedContext()
|
|
211
|
+
// Limit the amount of methods a user can call
|
|
212
|
+
globalThis.platformatic.sharedContext = {
|
|
213
|
+
get: () => sharedContext.get(),
|
|
214
|
+
update: (...args) => sharedContext.update(...args)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Setup interaction with parent port
|
|
218
|
+
const itc = setupITC(controller, application, threadDispatcher, sharedContext)
|
|
131
219
|
globalThis[kITC] = itc
|
|
220
|
+
|
|
221
|
+
itc.notify('init')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function stripBasePath (basePath) {
|
|
225
|
+
const kBasePath = Symbol('kBasePath')
|
|
226
|
+
|
|
227
|
+
subscribe('http.server.request.start', ({ request, response }) => {
|
|
228
|
+
if (request.url.startsWith(basePath)) {
|
|
229
|
+
request.url = request.url.slice(basePath.length)
|
|
230
|
+
|
|
231
|
+
if (request.url.charAt(0) !== '/') {
|
|
232
|
+
request.url = '/' + request.url
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
response[kBasePath] = basePath
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const originWriteHead = ServerResponse.prototype.writeHead
|
|
240
|
+
const originSetHeader = ServerResponse.prototype.setHeader
|
|
241
|
+
|
|
242
|
+
ServerResponse.prototype.writeHead = function (statusCode, statusMessage, headers) {
|
|
243
|
+
if (this[kBasePath] !== undefined) {
|
|
244
|
+
if (headers === undefined && typeof statusMessage === 'object') {
|
|
245
|
+
headers = statusMessage
|
|
246
|
+
statusMessage = undefined
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (headers) {
|
|
250
|
+
for (const key in headers) {
|
|
251
|
+
if (key.toLowerCase() === 'location' && !headers[key].startsWith(basePath)) {
|
|
252
|
+
headers[key] = basePath + headers[key]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return originWriteHead.call(this, statusCode, statusMessage, headers)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
ServerResponse.prototype.setHeader = function (name, value) {
|
|
262
|
+
if (this[kBasePath]) {
|
|
263
|
+
if (name.toLowerCase() === 'location' && !value.startsWith(basePath)) {
|
|
264
|
+
value = basePath + value
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
originSetHeader.call(this, name, value)
|
|
268
|
+
}
|
|
132
269
|
}
|
|
133
270
|
|
|
271
|
+
patchLogging()
|
|
272
|
+
|
|
134
273
|
// No need to catch this because there is the unhadledRejection handler on top.
|
|
135
274
|
main()
|