@platformatic/runtime 3.0.0-alpha.6 → 3.0.0-rc.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.
package/lib/worker/itc.js CHANGED
@@ -69,8 +69,8 @@ export async function waitEventFromITC (worker, event) {
69
69
  return safeHandleInITC(worker, () => once(worker[kITC], event))
70
70
  }
71
71
 
72
- export function setupITC (instance, application, dispatcher, sharedContext) {
73
- const messaging = new MessagingITC(instance.appConfig.id, workerData.config)
72
+ export function setupITC (controller, application, dispatcher, sharedContext) {
73
+ const messaging = new MessagingITC(controller.appConfig.id, workerData.config)
74
74
 
75
75
  Object.assign(globalThis.platformatic ?? {}, {
76
76
  messaging: {
@@ -80,55 +80,61 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
80
80
  })
81
81
 
82
82
  const itc = new ITC({
83
- name: instance.appConfig.id + '-worker',
83
+ name: controller.appConfig.id + '-worker',
84
84
  port: parentPort,
85
85
  handlers: {
86
86
  async start () {
87
- const status = instance.getStatus()
87
+ const status = controller.getStatus()
88
88
 
89
89
  if (status === 'starting') {
90
- await once(instance, 'start')
90
+ await once(controller, 'start')
91
91
  } else {
92
92
  // This gives a chance to a capability to perform custom logic
93
93
  globalThis.platformatic.events.emit('start')
94
94
 
95
95
  try {
96
- await instance.start()
96
+ await controller.start()
97
97
  } catch (e) {
98
- await instance.stop(true)
99
- await closeITC(dispatcher, itc, messaging)
98
+ await controller.stop(true)
99
+
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
+ })
100
104
 
101
105
  throw ensureLoggableError(e)
102
106
  }
103
107
  }
104
108
 
105
109
  if (application.entrypoint) {
106
- await instance.listen()
110
+ await controller.listen()
107
111
  }
108
112
 
109
- dispatcher.replaceServer(await instance.capability.getDispatchTarget())
110
- return application.entrypoint ? instance.capability.getUrl() : null
113
+ dispatcher.replaceServer(await controller.capability.getDispatchTarget())
114
+ return application.entrypoint ? controller.capability.getUrl() : null
111
115
  },
112
116
 
113
- async stop () {
114
- const status = instance.getStatus()
117
+ async stop ({ force, dependents }) {
118
+ const status = controller.getStatus()
115
119
 
116
- if (status === 'starting') {
117
- await once(instance, 'start')
120
+ if (!force && status === 'starting') {
121
+ await once(controller, 'start')
118
122
  }
119
123
 
120
- if (status.startsWith('start')) {
124
+ if (force || status.startsWith('start')) {
121
125
  // This gives a chance to a capability to perform custom logic
122
126
  globalThis.platformatic.events.emit('stop')
123
127
 
124
- await instance.stop()
128
+ await controller.stop(force, dependents)
125
129
  }
126
130
 
127
- await closeITC(dispatcher, itc, messaging)
131
+ once(itc, 'application:worker:stop:processed').then(() => {
132
+ closeITC(dispatcher, itc, messaging).catch(() => {})
133
+ })
128
134
  },
129
135
 
130
136
  async build () {
131
- return instance.capability.build()
137
+ return controller.capability.build()
132
138
  },
133
139
 
134
140
  async removeFromMesh () {
@@ -136,7 +142,7 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
136
142
  },
137
143
 
138
144
  inject (injectParams) {
139
- return instance.capability.inject(injectParams)
145
+ return controller.capability.inject(injectParams)
140
146
  },
141
147
 
142
148
  async updateUndiciInterceptors (undiciConfig) {
@@ -150,27 +156,27 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
150
156
  },
151
157
 
152
158
  getStatus () {
153
- return instance.getStatus()
159
+ return controller.getStatus()
154
160
  },
155
161
 
156
162
  getApplicationInfo () {
157
- return instance.capability.getInfo()
163
+ return controller.capability.getInfo()
158
164
  },
159
165
 
160
166
  async getApplicationConfig () {
161
- const current = await instance.capability.getConfig()
167
+ const current = await controller.capability.getConfig()
162
168
  // Remove all undefined keys from the config
163
169
  return JSON.parse(JSON.stringify(current))
164
170
  },
165
171
 
166
172
  async getApplicationEnv () {
167
173
  // Remove all undefined keys from the config
168
- return JSON.parse(JSON.stringify({ ...process.env, ...(await instance.capability.getEnv()) }))
174
+ return JSON.parse(JSON.stringify({ ...process.env, ...(await controller.capability.getEnv()) }))
169
175
  },
170
176
 
171
177
  async getApplicationOpenAPISchema () {
172
178
  try {
173
- return await instance.capability.getOpenapiSchema()
179
+ return await controller.capability.getOpenapiSchema()
174
180
  } catch (err) {
175
181
  throw new FailedToRetrieveOpenAPISchemaError(application.id, err.message)
176
182
  }
@@ -178,7 +184,7 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
178
184
 
179
185
  async getApplicationGraphQLSchema () {
180
186
  try {
181
- return await instance.capability.getGraphqlSchema()
187
+ return await controller.capability.getGraphqlSchema()
182
188
  } catch (err) {
183
189
  throw new FailedToRetrieveGraphQLSchemaError(application.id, err.message)
184
190
  }
@@ -186,7 +192,7 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
186
192
 
187
193
  async getApplicationMeta () {
188
194
  try {
189
- return await instance.capability.getMeta()
195
+ return await controller.capability.getMeta()
190
196
  } catch (err) {
191
197
  throw new FailedToRetrieveMetaError(application.id, err.message)
192
198
  }
@@ -194,7 +200,7 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
194
200
 
195
201
  async getMetrics (format) {
196
202
  try {
197
- return await instance.getMetrics({ format })
203
+ return await controller.getMetrics({ format })
198
204
  } catch (err) {
199
205
  throw new FailedToRetrieveMetricsError(application.id, err.message)
200
206
  }
@@ -202,7 +208,7 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
202
208
 
203
209
  async getHealth () {
204
210
  try {
205
- return await instance.getHealth()
211
+ return await controller.getHealth()
206
212
  } catch (err) {
207
213
  throw new FailedToRetrieveHealthError(application.id, err.message)
208
214
  }
@@ -210,7 +216,7 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
210
216
 
211
217
  async getCustomHealthCheck () {
212
218
  try {
213
- return await instance.capability.getCustomHealthCheck()
219
+ return await controller.capability.getCustomHealthCheck()
214
220
  } catch (err) {
215
221
  throw new FailedToPerformCustomHealthCheckError(application.id, err.message)
216
222
  }
@@ -218,7 +224,7 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
218
224
 
219
225
  async getCustomReadinessCheck () {
220
226
  try {
221
- return await instance.capability.getCustomReadinessCheck()
227
+ return await controller.capability.getCustomReadinessCheck()
222
228
  } catch (err) {
223
229
  throw new FailedToPerformCustomReadinessCheckError(application.id, err.message)
224
230
  }
@@ -234,7 +240,7 @@ export function setupITC (instance, application, dispatcher, sharedContext) {
234
240
  }
235
241
  })
236
242
 
237
- instance.on('changed', () => {
243
+ controller.on('changed', () => {
238
244
  itc.notify('changed')
239
245
  })
240
246
 
@@ -166,7 +166,7 @@ async function main () {
166
166
  }
167
167
 
168
168
  // Create the application
169
- const app = new Controller(
169
+ const controller = new Controller(
170
170
  application,
171
171
  workerData.worker.count > 1 ? workerData.worker.index : undefined,
172
172
  application.telemetry,
@@ -177,13 +177,23 @@ async function main () {
177
177
  !!config.watch
178
178
  )
179
179
 
180
- process.on('uncaughtException', handleUnhandled.bind(null, app, 'uncaught exception'))
181
- process.on('unhandledRejection', handleUnhandled.bind(null, app, 'unhandled rejection'))
180
+ if (config.exitOnUnhandledErrors) {
181
+ process.on('uncaughtException', handleUnhandled.bind(null, controller, 'uncaught exception'))
182
+ process.on('unhandledRejection', handleUnhandled.bind(null, controller, 'unhandled rejection'))
182
183
 
183
- await app.init()
184
+ process.on('newListener', event => {
185
+ if (event === 'uncaughtException' || event === 'unhandledRejection') {
186
+ globalThis.platformatic.logger.warn(
187
+ `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.`
188
+ )
189
+ }
190
+ })
191
+ }
192
+
193
+ await controller.init()
184
194
 
185
195
  if (application.entrypoint && config.basePath) {
186
- const meta = await app.capability.getMeta()
196
+ const meta = await controller.capability.getMeta()
187
197
  if (!meta.gateway.wantsAbsoluteUrls) {
188
198
  stripBasePath(config.basePath)
189
199
  }
@@ -197,12 +207,10 @@ async function main () {
197
207
  }
198
208
 
199
209
  // Setup interaction with parent port
200
- const itc = setupITC(app, application, threadDispatcher, sharedContext)
210
+ const itc = setupITC(controller, application, threadDispatcher, sharedContext)
201
211
  globalThis[kITC] = itc
202
212
 
203
- // Get the dependencies
204
- const dependencies = await app.getBootstrapDependencies()
205
- itc.notify('init', { dependencies })
213
+ itc.notify('init')
206
214
  }
207
215
 
208
216
  function stripBasePath (basePath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "3.0.0-alpha.6",
3
+ "version": "3.0.0-rc.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -34,23 +34,24 @@
34
34
  "typescript": "^5.5.4",
35
35
  "undici-oidc-interceptor": "^0.5.0",
36
36
  "why-is-node-running": "^2.2.2",
37
- "@platformatic/gateway": "3.0.0-alpha.6",
38
- "@platformatic/db": "3.0.0-alpha.6",
39
- "@platformatic/node": "3.0.0-alpha.6",
40
- "@platformatic/sql-mapper": "3.0.0-alpha.6",
41
- "@platformatic/sql-graphql": "3.0.0-alpha.6",
42
- "@platformatic/service": "3.0.0-alpha.6"
37
+ "@platformatic/db": "3.0.0-rc.1",
38
+ "@platformatic/composer": "3.0.0-rc.1",
39
+ "@platformatic/gateway": "3.0.0-rc.1",
40
+ "@platformatic/node": "3.0.0-rc.1",
41
+ "@platformatic/sql-graphql": "3.0.0-rc.1",
42
+ "@platformatic/service": "3.0.0-rc.1",
43
+ "@platformatic/sql-mapper": "3.0.0-rc.1",
44
+ "@platformatic/wattpm-pprof-capture": "3.0.0-rc.1"
43
45
  },
44
46
  "dependencies": {
45
47
  "@fastify/accepts": "^5.0.0",
46
48
  "@fastify/error": "^4.0.0",
47
49
  "@fastify/websocket": "^11.0.0",
48
- "@hapi/topo": "^6.0.2",
49
50
  "@opentelemetry/api": "^1.9.0",
50
51
  "@platformatic/undici-cache-memory": "^0.8.1",
51
52
  "@watchable/unpromise": "^1.0.2",
52
53
  "change-case-all": "^2.1.0",
53
- "close-with-grace": "^2.0.0",
54
+ "close-with-grace": "^2.2.0",
54
55
  "colorette": "^2.0.20",
55
56
  "cron": "^4.1.0",
56
57
  "debounce": "^2.0.0",
@@ -69,18 +70,18 @@
69
70
  "undici": "^7.0.0",
70
71
  "undici-thread-interceptor": "^0.14.0",
71
72
  "ws": "^8.16.0",
72
- "@platformatic/basic": "3.0.0-alpha.6",
73
- "@platformatic/itc": "3.0.0-alpha.6",
74
- "@platformatic/foundation": "3.0.0-alpha.6",
75
- "@platformatic/metrics": "3.0.0-alpha.6",
76
- "@platformatic/generators": "3.0.0-alpha.6",
77
- "@platformatic/telemetry": "3.0.0-alpha.6"
73
+ "@platformatic/basic": "3.0.0-rc.1",
74
+ "@platformatic/foundation": "3.0.0-rc.1",
75
+ "@platformatic/generators": "3.0.0-rc.1",
76
+ "@platformatic/metrics": "3.0.0-rc.1",
77
+ "@platformatic/telemetry": "3.0.0-rc.1",
78
+ "@platformatic/itc": "3.0.0-rc.1"
78
79
  },
79
80
  "engines": {
80
81
  "node": ">=22.18.0"
81
82
  },
82
83
  "scripts": {
83
- "test": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
84
+ "test": "npm run test:main && npm run test:api && npm run test:cli && npm run test:start && npm run test:multiple-workers",
84
85
  "test:main": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/versions/*.test.js",
85
86
  "test:api": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/api/*.test.js test/management-api/*.test.js",
86
87
  "test:cli": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/cli/*.test.js test/cli/**/*.test.js",
@@ -88,7 +89,7 @@
88
89
  "test:multiple-workers": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/multiple-workers/*.test.js",
89
90
  "gen-schema": "node lib/schema.js > schema.json",
90
91
  "gen-types": "json2ts > config.d.ts < schema.json",
91
- "build": "pnpm run gen-schema && pnpm run gen-types",
92
+ "build": "npm run gen-schema && npm run gen-types",
92
93
  "lint": "eslint"
93
94
  }
94
95
  }
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.0.0-alpha.6.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.0.0-rc.1.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",
@@ -186,6 +186,12 @@
186
186
  }
187
187
  ]
188
188
  },
189
+ "dependencies": {
190
+ "type": "array",
191
+ "items": {
192
+ "type": "string"
193
+ }
194
+ },
189
195
  "arguments": {
190
196
  "type": "array",
191
197
  "items": {
@@ -347,6 +353,13 @@
347
353
  },
348
354
  "additionalProperties": false
349
355
  },
356
+ "dependencies": {
357
+ "type": "array",
358
+ "items": {
359
+ "type": "string"
360
+ },
361
+ "default": []
362
+ },
350
363
  "arguments": {
351
364
  "type": "array",
352
365
  "items": {
@@ -576,6 +589,13 @@
576
589
  },
577
590
  "additionalProperties": false
578
591
  },
592
+ "dependencies": {
593
+ "type": "array",
594
+ "items": {
595
+ "type": "string"
596
+ },
597
+ "default": []
598
+ },
579
599
  "arguments": {
580
600
  "type": "array",
581
601
  "items": {
@@ -805,6 +825,13 @@
805
825
  },
806
826
  "additionalProperties": false
807
827
  },
828
+ "dependencies": {
829
+ "type": "array",
830
+ "items": {
831
+ "type": "string"
832
+ },
833
+ "default": []
834
+ },
808
835
  "arguments": {
809
836
  "type": "array",
810
837
  "items": {
@@ -1190,6 +1217,10 @@
1190
1217
  }
1191
1218
  ]
1192
1219
  },
1220
+ "exitOnUnhandledErrors": {
1221
+ "default": true,
1222
+ "type": "boolean"
1223
+ },
1193
1224
  "gracefulShutdown": {
1194
1225
  "type": "object",
1195
1226
  "properties": {
@@ -1,63 +0,0 @@
1
- import { Sorter } from '@hapi/topo'
2
- import { closest } from 'fastest-levenshtein'
3
- import { MissingDependencyError } from './errors.js'
4
- import { RoundRobinMap } from './worker/round-robin-map.js'
5
-
6
- function missingDependencyErrorMessage (clientName, application, applications) {
7
- const allNames = applications
8
- .map(s => s.id)
9
- .filter(id => id !== application.id)
10
- .sort()
11
- const closestName = closest(clientName, allNames)
12
- let errorMsg = `application '${application.id}' has unknown dependency: '${clientName}'.`
13
- if (closestName) {
14
- errorMsg += ` Did you mean '${closestName}'?`
15
- }
16
- if (allNames.length) {
17
- errorMsg += ` Known applications are: ${allNames.join(', ')}.`
18
- }
19
- return errorMsg
20
- }
21
-
22
- export function checkDependencies (applications) {
23
- const allApplications = new Set(applications.map(s => s.id))
24
-
25
- for (const application of applications) {
26
- for (const dependency of application.dependencies) {
27
- if (dependency.local && !allApplications.has(dependency.id)) {
28
- throw new MissingDependencyError(missingDependencyErrorMessage(dependency.id, application, applications))
29
- }
30
- }
31
- }
32
- }
33
-
34
- export function topologicalSort (workers, config) {
35
- const topo = new Sorter()
36
-
37
- for (const application of config.applications) {
38
- const localDependencyIds = Array.from(application.dependencies)
39
- .filter(dep => dep.local)
40
- .map(dep => dep.id)
41
-
42
- topo.add(application, {
43
- group: application.id,
44
- after: localDependencyIds,
45
- manual: true
46
- })
47
- }
48
-
49
- config.applications = topo.sort()
50
-
51
- return new RoundRobinMap(
52
- Array.from(workers.entries()).sort((a, b) => {
53
- if (a[0] === b[0]) {
54
- return 0
55
- }
56
-
57
- const aIndex = config.applications.findIndex(s => s.id === a[0])
58
- const bIndex = config.applications.findIndex(s => s.id === b[0])
59
- return aIndex - bIndex
60
- }),
61
- workers.configuration
62
- )
63
- }