@platformatic/runtime 3.8.0 → 3.9.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.
@@ -126,7 +126,8 @@ export async function managementApiPlugin (app, opts) {
126
126
  const { id } = request.params
127
127
  app.log.debug('stop profiling', { id })
128
128
 
129
- const profileData = await runtime.stopApplicationProfiling(id)
129
+ const options = request.body || {}
130
+ const profileData = await runtime.stopApplicationProfiling(id, options)
130
131
  reply.type('application/octet-stream').code(200).send(profileData)
131
132
  })
132
133
 
package/lib/runtime.js CHANGED
@@ -562,11 +562,11 @@ export class Runtime extends EventEmitter {
562
562
  return sendViaITC(service, 'startProfiling', options)
563
563
  }
564
564
 
565
- async stopApplicationProfiling (id, ensureStarted = true) {
565
+ async stopApplicationProfiling (id, options = {}, ensureStarted = true) {
566
566
  const service = await this.#getApplicationById(id, ensureStarted)
567
567
  this.#validatePprofCapturePreload()
568
568
 
569
- return sendViaITC(service, 'stopProfiling')
569
+ return sendViaITC(service, 'stopProfiling', options)
570
570
  }
571
571
 
572
572
  async updateUndiciInterceptors (undiciConfig) {
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ensureLoggableError,
3
+ executeWithTimeout,
3
4
  FileWatcher,
4
5
  kHandledError,
5
6
  listRecognizedConfigurationFiles,
@@ -24,6 +25,21 @@ function fetchApplicationUrl (application, key) {
24
25
  return getApplicationUrl(application.id)
25
26
  }
26
27
 
28
+ function handleUnhandled (app, type, err) {
29
+ const label =
30
+ workerData.worker.count > 1
31
+ ? `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
32
+ : `application "${workerData.applicationConfig.id}"`
33
+
34
+ globalThis.platformatic.logger.error({ err: ensureLoggableError(err) }, `The ${label} threw an ${type}.`)
35
+
36
+ executeWithTimeout(app?.stop(), 1000)
37
+ .catch()
38
+ .finally(() => {
39
+ process.exit(1)
40
+ })
41
+ }
42
+
27
43
  export class Controller extends EventEmitter {
28
44
  #starting
29
45
  #started
@@ -34,21 +50,13 @@ export class Controller extends EventEmitter {
34
50
  #context
35
51
  #lastELU
36
52
 
37
- constructor (
38
- appConfig,
39
- workerId,
40
- telemetryConfig,
41
- loggerConfig,
42
- serverConfig,
43
- metricsConfig,
44
- hasManagementApi,
45
- watch
46
- ) {
53
+ constructor (runtimeConfig, applicationConfig, workerId, serverConfig, metricsConfig) {
47
54
  super()
48
- this.appConfig = appConfig
49
- this.applicationId = this.appConfig.id
55
+ this.runtimeConfig = runtimeConfig
56
+ this.applicationConfig = applicationConfig
57
+ this.applicationId = this.applicationConfig.id
50
58
  this.workerId = workerId
51
- this.#watch = watch
59
+ this.#watch = !!runtimeConfig.watch
52
60
  this.#starting = false
53
61
  this.#started = false
54
62
  this.#listening = false
@@ -60,17 +68,17 @@ export class Controller extends EventEmitter {
60
68
  controller: this,
61
69
  applicationId: this.applicationId,
62
70
  workerId: this.workerId,
63
- directory: this.appConfig.path,
64
- dependencies: this.appConfig.dependencies,
65
- isEntrypoint: this.appConfig.entrypoint,
66
- isProduction: this.appConfig.isProduction,
67
- telemetryConfig,
71
+ directory: this.applicationConfig.path,
72
+ dependencies: this.applicationConfig.dependencies,
73
+ isEntrypoint: this.applicationConfig.entrypoint,
74
+ isProduction: this.applicationConfig.isProduction,
75
+ telemetryConfig: this.applicationConfig.telemetry,
76
+ loggerConfig: runtimeConfig.logger,
68
77
  metricsConfig,
69
- loggerConfig,
70
78
  serverConfig,
71
79
  worker: workerData?.worker,
72
- hasManagementApi: !!hasManagementApi,
73
- fetchApplicationUrl: fetchApplicationUrl.bind(null, appConfig)
80
+ hasManagementApi: !!runtimeConfig.managementApi,
81
+ fetchApplicationUrl: fetchApplicationUrl.bind(null, applicationConfig)
74
82
  }
75
83
  }
76
84
 
@@ -90,7 +98,7 @@ export class Controller extends EventEmitter {
90
98
  // Note: capability's init() is executed within start
91
99
  async init () {
92
100
  try {
93
- const appConfig = this.appConfig
101
+ const appConfig = this.applicationConfig
94
102
 
95
103
  if (appConfig.isProduction && !process.env.NODE_ENV) {
96
104
  process.env.NODE_ENV = 'production'
@@ -120,6 +128,10 @@ export class Controller extends EventEmitter {
120
128
  }
121
129
 
122
130
  this.#updateDispatcher()
131
+
132
+ if (this.capability.exitOnUnhandledErrors && this.runtimeConfig.exitOnUnhandledErrors) {
133
+ this.#setupHandlers()
134
+ }
123
135
  } catch (err) {
124
136
  if (err.validationErrors) {
125
137
  globalThis.platformatic.logger.error(
@@ -168,7 +180,7 @@ export class Controller extends EventEmitter {
168
180
  }
169
181
  }
170
182
 
171
- const listen = !!this.appConfig.useHttp
183
+ const listen = !!this.applicationConfig.useHttp
172
184
 
173
185
  try {
174
186
  await this.capability.start({ listen })
@@ -203,7 +215,7 @@ export class Controller extends EventEmitter {
203
215
 
204
216
  async listen () {
205
217
  // This server is not an entrypoint or already listened in start. Behave as no-op.
206
- if (!this.appConfig.entrypoint || this.appConfig.useHttp || this.#listening) {
218
+ if (!this.applicationConfig.entrypoint || this.applicationConfig.useHttp || this.#listening) {
207
219
  return
208
220
  }
209
221
 
@@ -299,4 +311,17 @@ export class Controller extends EventEmitter {
299
311
 
300
312
  setGlobalDispatcher(dispatcher)
301
313
  }
314
+
315
+ #setupHandlers () {
316
+ process.on('uncaughtException', handleUnhandled.bind(null, this, 'uncaught exception'))
317
+ process.on('unhandledRejection', handleUnhandled.bind(null, this, 'unhandled rejection'))
318
+
319
+ process.on('newListener', event => {
320
+ if (event === 'uncaughtException' || event === 'unhandledRejection') {
321
+ globalThis.platformatic.logger.warn(
322
+ `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.`
323
+ )
324
+ }
325
+ })
326
+ }
302
327
  }
package/lib/worker/itc.js CHANGED
@@ -71,7 +71,7 @@ export async function waitEventFromITC (worker, event) {
71
71
 
72
72
  export function setupITC (controller, application, dispatcher, sharedContext) {
73
73
  const logger = globalThis.platformatic.logger
74
- const messaging = new MessagingITC(controller.appConfig.id, workerData.config, logger)
74
+ const messaging = new MessagingITC(controller.applicationConfig.id, workerData.config, logger)
75
75
 
76
76
  Object.assign(globalThis.platformatic ?? {}, {
77
77
  messaging: {
@@ -82,7 +82,7 @@ export function setupITC (controller, application, dispatcher, sharedContext) {
82
82
  })
83
83
 
84
84
  const itc = new ITC({
85
- name: controller.appConfig.id + '-worker',
85
+ name: controller.applicationConfig.id + '-worker',
86
86
  port: parentPort,
87
87
  handlers: {
88
88
  async start () {
@@ -2,8 +2,6 @@ import {
2
2
  buildPinoFormatters,
3
3
  buildPinoTimestamp,
4
4
  disablePinoDirectWrite,
5
- ensureLoggableError,
6
- executeWithTimeout,
7
5
  getPrivateSymbol
8
6
  } from '@platformatic/foundation'
9
7
  import dotenv from 'dotenv'
@@ -30,21 +28,6 @@ class ForwardingEventEmitter extends EventEmitter {
30
28
  }
31
29
  }
32
30
 
33
- function handleUnhandled (app, type, err) {
34
- const label =
35
- workerData.worker.count > 1
36
- ? `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
37
- : `application "${workerData.applicationConfig.id}"`
38
-
39
- globalThis.platformatic.logger.error({ err: ensureLoggableError(err) }, `The ${label} threw an ${type}.`)
40
-
41
- executeWithTimeout(app?.stop(), 1000)
42
- .catch()
43
- .finally(() => {
44
- process.exit(1)
45
- })
46
- }
47
-
48
31
  function patchLogging () {
49
32
  disablePinoDirectWrite()
50
33
 
@@ -113,16 +96,16 @@ async function main () {
113
96
  events: new ForwardingEventEmitter()
114
97
  })
115
98
 
116
- const config = workerData.config
99
+ const runtimeConfig = workerData.config
117
100
 
118
- await performPreloading(config, workerData.applicationConfig)
101
+ await performPreloading(runtimeConfig, workerData.applicationConfig)
119
102
 
120
- const application = workerData.applicationConfig
103
+ const applicationConfig = workerData.applicationConfig
121
104
 
122
105
  // Load env file and mixin env vars from application config
123
106
  let envfile
124
- if (application.envfile) {
125
- envfile = resolve(workerData.dirname, application.envfile)
107
+ if (applicationConfig.envfile) {
108
+ envfile = resolve(workerData.dirname, applicationConfig.envfile)
126
109
  } else {
127
110
  envfile = resolve(workerData.applicationConfig.path, '.env')
128
111
  }
@@ -133,20 +116,20 @@ async function main () {
133
116
  path: envfile
134
117
  })
135
118
 
136
- if (config.env) {
137
- Object.assign(process.env, config.env)
119
+ if (runtimeConfig.env) {
120
+ Object.assign(process.env, runtimeConfig.env)
138
121
  }
139
- if (application.env) {
140
- Object.assign(process.env, application.env)
122
+ if (applicationConfig.env) {
123
+ Object.assign(process.env, applicationConfig.env)
141
124
  }
142
125
 
143
- const { threadDispatcher } = await setDispatcher(config)
126
+ const { threadDispatcher } = await setDispatcher(runtimeConfig)
144
127
 
145
128
  // If the application is an entrypoint and runtime server config is defined, use it.
146
129
  let serverConfig = null
147
- if (config.server && application.entrypoint) {
148
- serverConfig = config.server
149
- } else if (application.useHttp) {
130
+ if (runtimeConfig.server && applicationConfig.entrypoint) {
131
+ serverConfig = runtimeConfig.server
132
+ } else if (applicationConfig.useHttp) {
150
133
  serverConfig = {
151
134
  port: 0,
152
135
  hostname: '127.0.0.1',
@@ -169,48 +152,32 @@ async function main () {
169
152
  const res = await fetch(url)
170
153
  const [{ devtoolsFrontendUrl }] = await res.json()
171
154
 
172
- console.log(`For ${application.id} debugger open the following in chrome: "${devtoolsFrontendUrl}"`)
155
+ console.log(`For ${applicationConfig.id} debugger open the following in chrome: "${devtoolsFrontendUrl}"`)
173
156
  }
174
157
 
175
158
  // Create the application
176
159
  // Add idLabel to metrics config to determine which label name to use (defaults to applicationId)
177
- const metricsConfig = config.metrics
160
+ const metricsConfig = runtimeConfig.metrics
178
161
  ? {
179
- ...config.metrics,
180
- idLabel: config.metrics.applicationLabel || 'applicationId'
162
+ ...runtimeConfig.metrics,
163
+ idLabel: runtimeConfig.metrics.applicationLabel || 'applicationId'
181
164
  }
182
- : config.metrics
165
+ : runtimeConfig.metrics
183
166
 
184
167
  const controller = new Controller(
185
- application,
168
+ runtimeConfig,
169
+ applicationConfig,
186
170
  workerData.worker.count > 1 ? workerData.worker.index : undefined,
187
- application.telemetry,
188
- config.logger,
189
171
  serverConfig,
190
- metricsConfig,
191
- !!config.managementApi,
192
- !!config.watch
172
+ metricsConfig
193
173
  )
194
174
 
195
- if (config.exitOnUnhandledErrors) {
196
- process.on('uncaughtException', handleUnhandled.bind(null, controller, 'uncaught exception'))
197
- process.on('unhandledRejection', handleUnhandled.bind(null, controller, 'unhandled rejection'))
198
-
199
- process.on('newListener', event => {
200
- if (event === 'uncaughtException' || event === 'unhandledRejection') {
201
- globalThis.platformatic.logger.warn(
202
- `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.`
203
- )
204
- }
205
- })
206
- }
207
-
208
175
  await controller.init()
209
176
 
210
- if (application.entrypoint && config.basePath) {
177
+ if (applicationConfig.entrypoint && runtimeConfig.basePath) {
211
178
  const meta = await controller.capability.getMeta()
212
179
  if (!meta.gateway.wantsAbsoluteUrls) {
213
- stripBasePath(config.basePath)
180
+ stripBasePath(runtimeConfig.basePath)
214
181
  }
215
182
  }
216
183
 
@@ -222,7 +189,7 @@ async function main () {
222
189
  }
223
190
 
224
191
  // Setup interaction with parent port
225
- const itc = setupITC(controller, application, threadDispatcher, sharedContext)
192
+ const itc = setupITC(controller, applicationConfig, threadDispatcher, sharedContext)
226
193
  globalThis[kITC] = itc
227
194
  globalThis.platformatic.itc = itc
228
195
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -19,6 +19,7 @@
19
19
  "@fastify/express": "^4.0.0",
20
20
  "@fastify/formbody": "^8.0.0",
21
21
  "autocannon": "^8.0.0",
22
+ "atomic-sleep": "^1.0.0",
22
23
  "c8": "^10.0.0",
23
24
  "cleaner-spec-reporter": "^0.5.0",
24
25
  "eslint": "9",
@@ -34,14 +35,14 @@
34
35
  "typescript": "^5.5.4",
35
36
  "undici-oidc-interceptor": "^0.5.0",
36
37
  "why-is-node-running": "^2.2.2",
37
- "@platformatic/composer": "3.8.0",
38
- "@platformatic/db": "3.8.0",
39
- "@platformatic/gateway": "3.8.0",
40
- "@platformatic/node": "3.8.0",
41
- "@platformatic/sql-graphql": "3.8.0",
42
- "@platformatic/service": "3.8.0",
43
- "@platformatic/sql-mapper": "3.8.0",
44
- "@platformatic/wattpm-pprof-capture": "3.8.0"
38
+ "@platformatic/composer": "3.9.0",
39
+ "@platformatic/db": "3.9.0",
40
+ "@platformatic/gateway": "3.9.0",
41
+ "@platformatic/sql-graphql": "3.9.0",
42
+ "@platformatic/node": "3.9.0",
43
+ "@platformatic/sql-mapper": "3.9.0",
44
+ "@platformatic/service": "3.9.0",
45
+ "@platformatic/wattpm-pprof-capture": "3.9.0"
45
46
  },
46
47
  "dependencies": {
47
48
  "@fastify/accepts": "^5.0.0",
@@ -71,12 +72,12 @@
71
72
  "undici": "^7.0.0",
72
73
  "undici-thread-interceptor": "^0.14.0",
73
74
  "ws": "^8.16.0",
74
- "@platformatic/basic": "3.8.0",
75
- "@platformatic/foundation": "3.8.0",
76
- "@platformatic/itc": "3.8.0",
77
- "@platformatic/metrics": "3.8.0",
78
- "@platformatic/generators": "3.8.0",
79
- "@platformatic/telemetry": "3.8.0"
75
+ "@platformatic/foundation": "3.9.0",
76
+ "@platformatic/generators": "3.9.0",
77
+ "@platformatic/itc": "3.9.0",
78
+ "@platformatic/basic": "3.9.0",
79
+ "@platformatic/metrics": "3.9.0",
80
+ "@platformatic/telemetry": "3.9.0"
80
81
  },
81
82
  "engines": {
82
83
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.8.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.9.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",