@platformatic/runtime 1.35.5 → 1.36.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.
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.20.0/runtime",
3
+ "entrypoint": "a",
4
+ "autoload": {
5
+ "path": "./services"
6
+ },
7
+ "server": {
8
+ "hostname": "127.0.0.1",
9
+ "port": "{PORT}",
10
+ "logger": {
11
+ "level": "info"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.3.0/service",
3
+ "server": {
4
+ "logger": {
5
+ "level": "warn"
6
+ }
7
+ },
8
+ "plugins": {
9
+ "paths": [{
10
+ "path": "plugin.js",
11
+ "encapsulate": false
12
+ }]
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ const { setTimeout: sleep } = require('timers/promises')
4
+
5
+ module.exports = async function (fastify, options) {
6
+ setImmediate(() => {
7
+ throw new Error('Crash!')
8
+ })
9
+
10
+ await sleep(1000)
11
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.20.0/runtime",
3
+ "entrypoint": "a",
4
+ "autoload": {
5
+ "path": "./services"
6
+ },
7
+ "server": {
8
+ "hostname": "127.0.0.1",
9
+ "port": "{PORT}",
10
+ "logger": {
11
+ "level": "info"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.3.0/service",
3
+ "server": {
4
+ "logger": {
5
+ "level": "warn"
6
+ }
7
+ },
8
+ "plugins": {
9
+ "paths": [{
10
+ "path": "plugin.js",
11
+ "encapsulate": false
12
+ }]
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ module.exports = async function (fastify, options) {
4
+ fastify.post('/crash', async (_, reply) => {
5
+ setImmediate(() => {
6
+ throw new Error('Crash!')
7
+ })
8
+ })
9
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.36.0/runtime",
3
+ "entrypoint": "a",
4
+ "autoload": {
5
+ "path": "./services"
6
+ },
7
+ "server": {
8
+ "hostname": "127.0.0.1",
9
+ "port": "{PORT}",
10
+ "logger": {
11
+ "level": "info"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.3.0/service",
3
+ "server": {
4
+ "logger": {
5
+ "level": "warn"
6
+ }
7
+ },
8
+ "plugins": {
9
+ "paths": [{
10
+ "path": "plugin.js",
11
+ "encapsulate": false
12
+ }]
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ module.exports = async function (fastify, options) {
4
+ fastify.post('/crash', async (_, reply) => {
5
+ setImmediate(() => {
6
+ throw new Error('Crash!')
7
+ })
8
+ })
9
+ }
package/lib/api-client.js CHANGED
@@ -28,15 +28,32 @@ class RuntimeApiClient extends EventEmitter {
28
28
  super()
29
29
  this.setMaxListeners(MAX_LISTENERS_COUNT)
30
30
 
31
- this.worker = worker
32
31
  this.#configManager = configManager
33
32
  this.#runtimeTmpDir = getRuntimeTmpDir(configManager.dirname)
33
+
34
+ this.setWorker(worker)
35
+ }
36
+
37
+ async setWorker (worker) {
38
+ if (!worker) {
39
+ throw new errors.WorkerIsRequired()
40
+ }
41
+ if (this.worker) {
42
+ this.worker.removeAllListeners('message')
43
+ // Ignore all things that happen after the worker has been replaced
44
+ this.#exitPromise.catch(() => {})
45
+ }
46
+ this.worker = worker
34
47
  this.#exitPromise = this.#exitHandler()
35
48
  this.worker.on('message', (message) => {
36
49
  if (message.operationId) {
37
50
  this.emit(message.operationId, message)
38
51
  }
39
52
  })
53
+
54
+ if (this.started) {
55
+ await this.start()
56
+ }
40
57
  }
41
58
 
42
59
  async #getRuntimePackageJson () {
@@ -72,11 +89,13 @@ class RuntimeApiClient extends EventEmitter {
72
89
 
73
90
  async start () {
74
91
  const address = await this.#sendCommand('plt:start-services')
92
+ this.started = true
75
93
  this.emit('start', address)
76
94
  return address
77
95
  }
78
96
 
79
97
  async close () {
98
+ this.started = false
80
99
  await this.#sendCommand('plt:stop-services')
81
100
 
82
101
  this.worker.postMessage({ command: 'plt:close' })
package/lib/errors.js CHANGED
@@ -25,6 +25,7 @@ module.exports = {
25
25
  FailedToUnlinkManagementApiSocket: createError(`${ERROR_PREFIX}_FAILED_TO_UNLINK_MANAGEMENT_API_SOCKET`, 'Failed to unlink management API socket "%s"'),
26
26
  LogFileNotFound: createError(`${ERROR_PREFIX}_LOG_FILE_NOT_FOUND`, 'Log file with index %s not found', 404),
27
27
  CannotFindGeneratorForTemplateError: createError(`${ERROR_PREFIX}_CANNOT_FIND_GENERATOR_FOR_TEMPLATE`, 'Cannot find a generator for template "%s"'),
28
+ WorkerIsRequired: createError(`${ERROR_PREFIX}_REQUIRED_WORKER`, 'The worker parameter is required'),
28
29
 
29
30
  // TODO: should remove next one as it's not used anymore
30
31
  CannotRemoveServiceOnUpdateError: createError(`${ERROR_PREFIX}_CANNOT_REMOVE_SERVICE_ON_UPDATE`, 'Cannot remove service "%s" when updating a Runtime')
@@ -90,7 +90,7 @@ class RuntimeGenerator extends BaseGenerator {
90
90
  this.addServicesDependencies()
91
91
 
92
92
  this.addEnvVars({
93
- PLT_SERVER_HOSTNAME: '0.0.0.0',
93
+ PLT_SERVER_HOSTNAME: '127.0.0.1',
94
94
  PORT: this.config.port || 3042,
95
95
  PLT_SERVER_LOGGER_LEVEL: this.config.logLevel || 'info',
96
96
  PLT_MANAGEMENT_API: true
package/lib/schema.js CHANGED
@@ -172,6 +172,13 @@ const platformaticRuntimeSchema = {
172
172
  }
173
173
  ]
174
174
  },
175
+ restartOnError: {
176
+ default: true,
177
+ anyOf: [
178
+ { type: 'boolean' },
179
+ { type: 'string' }
180
+ ]
181
+ },
175
182
  services: {
176
183
  type: 'array',
177
184
  items: {
package/lib/start.js CHANGED
@@ -25,6 +25,18 @@ const kWorkerExecArgv = [
25
25
  kLoaderFile
26
26
  ]
27
27
 
28
+ function startWorker ({ config, dirname, runtimeLogsDir }, env) {
29
+ const worker = new Worker(kWorkerFile, {
30
+ /* c8 ignore next */
31
+ execArgv: config.hotReload ? kWorkerExecArgv : [],
32
+ transferList: config.loggingPort ? [config.loggingPort] : [],
33
+ workerData: { config, dirname, runtimeLogsDir },
34
+ env
35
+ })
36
+
37
+ return worker
38
+ }
39
+
28
40
  async function buildRuntime (configManager, env = process.env) {
29
41
  const config = configManager.current
30
42
 
@@ -46,60 +58,48 @@ async function buildRuntime (configManager, env = process.env) {
46
58
  // The configManager cannot be transferred to the worker, so remove it.
47
59
  delete config.configManager
48
60
 
49
- const worker = new Worker(kWorkerFile, {
50
- /* c8 ignore next */
51
- execArgv: config.hotReload ? kWorkerExecArgv : [],
52
- transferList: config.loggingPort ? [config.loggingPort] : [],
53
- workerData: { config, dirname, runtimeLogsDir },
54
- env
55
- })
61
+ let worker = startWorker({ config, dirname, runtimeLogsDir }, env)
56
62
 
57
63
  let managementApi = null
58
64
 
59
- let exited = null
60
- let isWorkerAlive = true
61
- worker.on('exit', (code) => {
62
- // TODO(mcollina): refactor to not set this here
63
- process.exitCode = code
64
- isWorkerAlive = false
65
- configManager.fileWatcher?.stopWatching()
66
- managementApi?.close()
67
- if (typeof exited === 'function') {
68
- exited()
69
- }
70
- })
71
-
72
- worker.on('error', () => {
73
- // If this is the only 'error' handler, then exit the process as the default
74
- // behavior. If anything else is listening for errors, then don't exit.
75
- if (worker.listenerCount('error') === 1) {
76
- // The error is logged in the worker.
77
- process.exit(1)
78
- }
79
- })
80
-
81
65
  if (config.hotReload) {
82
66
  /* c8 ignore next 3 */
83
67
  process.on('SIGUSR2', () => {
84
68
  worker.postMessage({ signal: 'SIGUSR2' })
85
69
  })
86
70
 
87
- // TODO(mcollina): refactor to not alter globals here
88
- closeWithGrace((event, cb) => {
89
- if (isWorkerAlive) {
90
- worker.postMessage(event)
91
- exited = cb
92
- } else {
93
- setImmediate(cb)
94
- }
95
- })
96
-
97
71
  /* c8 ignore next 3 */
98
72
  configManager.on('update', () => {
99
73
  // TODO(cjihrig): Need to clean up and restart the worker.
100
74
  })
101
75
  }
102
76
 
77
+ function setupExit () {
78
+ worker.on('exit', (code) => {
79
+ // runtimeApiClient.started can be false if a stop command was issued
80
+ // via the management API.
81
+ if (config.restartOnError === false || !runtimeApiClient.started) {
82
+ // We must stop those here in case the `closeWithGrace` callback
83
+ // was not called.
84
+ configManager.fileWatcher?.stopWatching()
85
+ managementApi?.close()
86
+ return
87
+ }
88
+
89
+ worker = startWorker({ config, dirname, runtimeLogsDir }, env)
90
+ setupExit()
91
+
92
+ once(worker, 'message').then((msg) => {
93
+ runtimeApiClient.setWorker(worker).catch(() => {
94
+ // TODO: currently we restart if the worker fails to start intermitently
95
+ // should we limit this to a number of retries?
96
+ })
97
+ })
98
+ })
99
+ }
100
+
101
+ setupExit()
102
+
103
103
  await once(worker, 'message') // plt:init
104
104
 
105
105
  const runtimeApiClient = new RuntimeApiClient(
@@ -151,7 +151,16 @@ async function startCommand (args) {
151
151
  runtime = await buildRuntime(wrappedConfig)
152
152
  }
153
153
 
154
- return await runtime.start()
154
+ const res = await runtime.start()
155
+
156
+ closeWithGrace(async (event) => {
157
+ if (event.err instanceof Error) {
158
+ console.error(event.err)
159
+ }
160
+ await runtime.close()
161
+ })
162
+
163
+ return res
155
164
  } catch (err) {
156
165
  if (err.code === 'PLT_CONFIG_NO_CONFIG_FILE_FOUND' && args.length === 1) {
157
166
  const config = {
@@ -176,32 +185,24 @@ async function startCommand (args) {
176
185
  await writeFile(toWrite, JSON.stringify(config, null, 2))
177
186
  return startCommand(['--config', toWrite])
178
187
  }
179
- logErrorAndExit(err)
180
- }
181
- }
182
188
 
183
- function logErrorAndExit (err) {
184
- if (err.filenames) {
185
- console.error(`Missing config file!
186
- Be sure to have a config file with one of the following names:
189
+ if (err.filenames) {
190
+ console.error(`Missing config file!
191
+ Be sure to have a config file with one of the following names:
187
192
 
188
- ${err.filenames.map((s) => ' * ' + s).join('\n')}
193
+ ${err.filenames.map((s) => ' * ' + s).join('\n')}
189
194
 
190
- In alternative run "npm create platformatic@latest" to generate a basic plt service config.`)
191
- process.exit(1)
192
- } else if (err.validationErrors) {
193
- printConfigValidationErrors(err)
194
- process.exit(1)
195
- }
195
+ In alternative run "npm create platformatic@latest" to generate a basic plt service config.`)
196
+ process.exit(1)
197
+ } else if (err.validationErrors) {
198
+ printConfigValidationErrors(err)
199
+ process.exit(1)
200
+ }
196
201
 
197
- delete err?.stack
198
- console.error(err?.message)
202
+ console.error(err)
199
203
 
200
- if (err?.cause) {
201
- console.error(`${err.cause}`)
204
+ process.exit(1)
202
205
  }
203
-
204
- process.exit(1)
205
206
  }
206
207
 
207
208
  module.exports = { buildRuntime, start, startCommand }
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ version: '1.36.0',
5
+ up: function (config) {
6
+ if (config.restartOnError === undefined) {
7
+ config.restartOnError = false
8
+ }
9
+ return config
10
+ }
11
+ }
package/lib/worker.js CHANGED
@@ -98,10 +98,10 @@ process.on('uncaughtException', (err) => {
98
98
 
99
99
  if (stop) {
100
100
  stop().then(() => {
101
- process.exit(1)
101
+ setImmediate(process.exit.bind(process), 1)
102
102
  })
103
103
  } else {
104
- process.exit(1)
104
+ setImmediate(process.exit.bind(process), 1)
105
105
  }
106
106
  })
107
107
 
@@ -112,10 +112,10 @@ process.on('unhandledRejection', (err) => {
112
112
 
113
113
  if (stop) {
114
114
  stop().then(() => {
115
- process.exit(1)
115
+ setImmediate(process.exit.bind(process), 1)
116
116
  })
117
117
  } else {
118
- process.exit(1)
118
+ setImmediate(process.exit.bind(process), 1)
119
119
  }
120
120
  })
121
121
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "1.35.5",
3
+ "version": "1.36.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "homepage": "https://github.com/platformatic/platformatic#readme",
19
19
  "devDependencies": {
20
- "@fastify/express": "^2.3.0",
20
+ "@fastify/express": "^3.0.0",
21
21
  "@fastify/formbody": "^7.4.0",
22
22
  "@matteo.collina/tspl": "^0.1.1",
23
23
  "borp": "^0.11.0",
@@ -25,6 +25,7 @@
25
25
  "execa": "^8.0.1",
26
26
  "express": "^4.18.3",
27
27
  "fast-jwt": "^3.3.3",
28
+ "get-port": "^7.1.0",
28
29
  "pino-abstract-transport": "^1.1.0",
29
30
  "snazzy": "^9.0.0",
30
31
  "split2": "^4.2.0",
@@ -33,11 +34,10 @@
33
34
  "typescript": "^5.4.2",
34
35
  "undici-oidc-interceptor": "^0.5.0",
35
36
  "why-is-node-running": "^2.2.2",
36
- "@platformatic/sql-graphql": "1.35.5",
37
- "@platformatic/sql-mapper": "1.35.5"
37
+ "@platformatic/sql-graphql": "1.36.1",
38
+ "@platformatic/sql-mapper": "1.36.1"
38
39
  },
39
40
  "dependencies": {
40
- "ws": "^8.16.0",
41
41
  "@fastify/error": "^3.4.1",
42
42
  "@fastify/websocket": "^10.0.0",
43
43
  "@hapi/topo": "^6.0.2",
@@ -57,18 +57,19 @@
57
57
  "minimist": "^1.2.8",
58
58
  "pino": "^8.19.0",
59
59
  "pino-pretty": "^10.3.1",
60
- "semgrator": "^0.3.0",
61
60
  "pino-roll": "^1.0.0",
61
+ "semgrator": "^0.3.0",
62
62
  "tail-file-stream": "^0.1.0",
63
63
  "undici": "^6.9.0",
64
64
  "why-is-node-running": "^2.2.2",
65
- "@platformatic/config": "1.35.5",
66
- "@platformatic/generators": "1.35.5",
67
- "@platformatic/db": "1.35.5",
68
- "@platformatic/service": "1.35.5",
69
- "@platformatic/utils": "1.35.5",
70
- "@platformatic/telemetry": "1.35.5",
71
- "@platformatic/composer": "1.35.5"
65
+ "ws": "^8.16.0",
66
+ "@platformatic/composer": "1.36.1",
67
+ "@platformatic/config": "1.36.1",
68
+ "@platformatic/db": "1.36.1",
69
+ "@platformatic/generators": "1.36.1",
70
+ "@platformatic/service": "1.36.1",
71
+ "@platformatic/telemetry": "1.36.1",
72
+ "@platformatic/utils": "1.36.1"
72
73
  },
73
74
  "standard": {
74
75
  "ignore": [