@platformatic/runtime 3.4.1 → 3.5.1

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 (49) hide show
  1. package/README.md +1 -1
  2. package/config.d.ts +224 -77
  3. package/eslint.config.js +3 -5
  4. package/index.d.ts +73 -24
  5. package/index.js +173 -29
  6. package/lib/config.js +279 -197
  7. package/lib/errors.js +126 -34
  8. package/lib/generator.js +640 -0
  9. package/lib/logger.js +43 -41
  10. package/lib/management-api.js +109 -118
  11. package/lib/prom-server.js +202 -16
  12. package/lib/runtime.js +1963 -585
  13. package/lib/scheduler.js +119 -0
  14. package/lib/schema.js +22 -234
  15. package/lib/shared-http-cache.js +43 -0
  16. package/lib/upgrade.js +6 -8
  17. package/lib/utils.js +6 -61
  18. package/lib/version.js +7 -0
  19. package/lib/versions/v1.36.0.js +2 -4
  20. package/lib/versions/v1.5.0.js +2 -4
  21. package/lib/versions/v2.0.0.js +3 -5
  22. package/lib/versions/v3.0.0.js +16 -0
  23. package/lib/worker/controller.js +302 -0
  24. package/lib/worker/http-cache.js +171 -0
  25. package/lib/worker/interceptors.js +190 -10
  26. package/lib/worker/itc.js +146 -59
  27. package/lib/worker/main.js +220 -81
  28. package/lib/worker/messaging.js +182 -0
  29. package/lib/worker/round-robin-map.js +62 -0
  30. package/lib/worker/shared-context.js +22 -0
  31. package/lib/worker/symbols.js +14 -5
  32. package/package.json +47 -38
  33. package/schema.json +1383 -55
  34. package/help/compile.txt +0 -8
  35. package/help/help.txt +0 -5
  36. package/help/start.txt +0 -21
  37. package/index.test-d.ts +0 -41
  38. package/lib/build-server.js +0 -69
  39. package/lib/compile.js +0 -98
  40. package/lib/dependencies.js +0 -59
  41. package/lib/generator/README.md +0 -32
  42. package/lib/generator/errors.js +0 -10
  43. package/lib/generator/runtime-generator.d.ts +0 -37
  44. package/lib/generator/runtime-generator.js +0 -498
  45. package/lib/start.js +0 -190
  46. package/lib/worker/app.js +0 -278
  47. package/lib/worker/default-stackable.js +0 -33
  48. package/lib/worker/metrics.js +0 -122
  49. package/runtime.mjs +0 -54
package/lib/worker/itc.js CHANGED
@@ -1,13 +1,22 @@
1
- 'use strict'
2
-
3
- const { once } = require('node:events')
4
- const { parentPort } = require('node:worker_threads')
5
-
6
- const { ITC } = require('@platformatic/itc')
7
- const { Unpromise } = require('@watchable/unpromise')
8
-
9
- const errors = require('../errors')
10
- const { kITC, kId } = require('./symbols')
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
- throw new errors.ServiceExitedError(worker[kId], exitCode)
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 sendViaITC (worker, name, message) {
46
- return safeHandleInITC(worker, () => worker[kITC].send(name, message))
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 (app, service, dispatcher) {
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: app.appConfig.id + '-worker',
83
+ name: controller.appConfig.id + '-worker',
56
84
  port: parentPort,
57
85
  handlers: {
58
86
  async start () {
59
- const status = app.getStatus()
87
+ const status = controller.getStatus()
60
88
 
61
89
  if (status === 'starting') {
62
- await once(app, 'start')
90
+ await once(controller, 'start')
63
91
  } else {
64
- await app.start()
65
- }
92
+ // This gives a chance to a capability to perform custom logic
93
+ globalThis.platformatic.events.emit('start')
66
94
 
67
- if (service.entrypoint) {
68
- await app.listen()
69
- }
95
+ try {
96
+ await controller.start()
97
+ } catch (e) {
98
+ await controller.stop(true)
70
99
 
71
- const url = app.stackable.getUrl()
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
- const dispatchFunc = await app.stackable.getDispatchFunc()
74
- dispatcher.replaceServer(url ?? dispatchFunc)
105
+ throw ensureLoggableError(e)
106
+ }
107
+ }
108
+
109
+ if (application.entrypoint) {
110
+ await controller.listen()
111
+ }
75
112
 
76
- return service.entrypoint ? url : null
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 = app.getStatus()
117
+ async stop ({ force, dependents }) {
118
+ const status = controller.getStatus()
81
119
 
82
- if (status === 'starting') {
83
- await once(app, 'start')
120
+ if (!force && status === 'starting') {
121
+ await once(controller, 'start')
84
122
  }
85
123
 
86
- if (status !== 'stopped') {
87
- await app.stop()
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
- dispatcher.interceptor.close()
91
- itc.close()
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 app.stackable.build()
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 app.getStatus()
159
+ return controller.getStatus()
100
160
  },
101
161
 
102
- getServiceInfo () {
103
- return app.stackable.getInfo()
162
+ getApplicationInfo () {
163
+ return controller.capability.getInfo()
104
164
  },
105
165
 
106
- async getServiceConfig () {
107
- const current = await app.stackable.getConfig()
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 getServiceEnv () {
172
+ async getApplicationEnv () {
113
173
  // Remove all undefined keys from the config
114
- return JSON.parse(JSON.stringify({ ...process.env, ...(await app.stackable.getEnv()) }))
174
+ return JSON.parse(JSON.stringify({ ...process.env, ...(await controller.capability.getEnv()) }))
115
175
  },
116
176
 
117
- async getServiceOpenAPISchema () {
177
+ async getApplicationOpenAPISchema () {
118
178
  try {
119
- return await app.stackable.getOpenapiSchema()
179
+ return await controller.capability.getOpenapiSchema()
120
180
  } catch (err) {
121
- throw new errors.FailedToRetrieveOpenAPISchemaError(service.id, err.message)
181
+ throw new FailedToRetrieveOpenAPISchemaError(application.id, err.message)
122
182
  }
123
183
  },
124
184
 
125
- async getServiceGraphQLSchema () {
185
+ async getApplicationGraphQLSchema () {
126
186
  try {
127
- return await app.stackable.getGraphqlSchema()
187
+ return await controller.capability.getGraphqlSchema()
128
188
  } catch (err) {
129
- throw new errors.FailedToRetrieveGraphQLSchemaError(service.id, err.message)
189
+ throw new FailedToRetrieveGraphQLSchemaError(application.id, err.message)
130
190
  }
131
191
  },
132
192
 
133
- async getServiceMeta () {
193
+ async getApplicationMeta () {
134
194
  try {
135
- return await app.stackable.getMeta()
195
+ return await controller.capability.getMeta()
136
196
  } catch (err) {
137
- throw new errors.FailedToRetrieveMetaError(service.id, err.message)
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 app.getMetrics({ format })
203
+ return await controller.getMetrics({ format })
144
204
  } catch (err) {
145
- throw new errors.FailedToRetrieveMetricsError(service.id, err.message)
205
+ throw new FailedToRetrieveMetricsError(application.id, err.message)
146
206
  }
147
207
  },
148
208
 
149
- inject (injectParams) {
150
- return app.stackable.inject(injectParams)
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
- app.on('changed', () => {
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 }
@@ -1,40 +1,35 @@
1
- 'use strict'
2
-
3
- const { createRequire } = require('node:module')
4
- const { join } = require('node:path')
5
- const { parentPort, workerData, threadId } = require('node:worker_threads')
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
- ensureLoggableError
20
- } = require('@platformatic/utils')
21
- const { kId, kITC } = require('./symbols')
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
- process.on('uncaughtException', handleUnhandled.bind(null, 'uncaught exception'))
24
- process.on('unhandledRejection', handleUnhandled.bind(null, 'unhandled rejection'))
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.fetch = fetch
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 createLogger () {
47
- const destination = new MessagePortWritable({ port: workerData.loggingPort })
48
- const loggerInstance = pino({ level: 'trace', name: workerData.serviceConfig.id }, destination)
41
+ function patchLogging () {
42
+ disablePinoDirectWrite()
43
+
44
+ const kFormatForStderr = getPrivateSymbol(console, 'kFormatForStderr')
49
45
 
50
- Reflect.defineProperty(process, 'stdout', { value: createPinoWritable(loggerInstance, 'info') })
51
- Reflect.defineProperty(process, 'stderr', { value: createPinoWritable(loggerInstance, 'error') })
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
- return loggerInstance
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
- async function main () {
57
- if (config.preload) {
58
- await import(pathToFileURL(config.preload))
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
- const service = workerData.serviceConfig
86
+ return pino(pinoOptions)
87
+ }
62
88
 
63
- // Setup undici
64
- const interceptors = {}
65
- const composedInterceptors = []
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
- if (config.undici?.interceptors) {
68
- const _require = createRequire(join(workerData.dirname, 'package.json'))
69
- for (const key of ['Agent', 'Pool', 'Client']) {
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
- if (Array.isArray(config.undici.interceptors)) {
76
- composedInterceptors.push(...(await loadInterceptors(_require, config.undici.interceptors)))
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
- const globalDispatcher = new Agent({
81
- ...config.undici,
82
- interceptors
83
- }).compose(composedInterceptors)
123
+ globalThis.platformatic.logger.debug({ envfile }, 'Loading envfile...')
124
+
125
+ dotenv.config({
126
+ path: envfile
127
+ })
84
128
 
85
- setGlobalDispatcher(globalDispatcher)
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
- // Setup mesh networker
88
- const threadDispatcher = wire({ port: parentPort, useNetwork: service.useHttp, timeout: true })
136
+ const { threadDispatcher } = await setDispatcher(config)
89
137
 
90
- // If the service is an entrypoint and runtime server config is defined, use it.
138
+ // If the application is an entrypoint and runtime server config is defined, use it.
91
139
  let serverConfig = null
92
- if (config.server && service.entrypoint) {
140
+ if (config.server && application.entrypoint) {
93
141
  serverConfig = config.server
94
- } else if (service.useHttp) {
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
- let telemetryConfig = config.telemetry
103
- if (telemetryConfig) {
104
- telemetryConfig = {
105
- ...telemetryConfig,
106
- serviceName: `${telemetryConfig.serviceName}-${service.id}`
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
- app = new PlatformaticApp(
112
- service,
113
- telemetryConfig,
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
- config.metrics,
183
+ metricsConfig,
117
184
  !!config.managementApi,
118
185
  !!config.watch
119
186
  )
120
187
 
121
- await app.init()
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
- // Setup interaction with parent port
124
- const itc = setupITC(app, service, threadDispatcher)
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
- // Get the dependencies
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()