@platformatic/runtime 1.21.1 → 1.23.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.
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://platformatic.dev/schemas/v1.21.0/runtime",
3
+ "entrypoint": "serviceApp",
4
+ "allowCycles": true,
5
+ "hotReload": false,
6
+ "autoload": {
7
+ "path": "../monorepo",
8
+ "exclude": ["docs", "composerApp"],
9
+ "mappings": {
10
+ "serviceAppWithLogger": {
11
+ "id": "with-logger",
12
+ "config": "platformatic.service.json"
13
+ },
14
+ "serviceAppWithMultiplePlugins": {
15
+ "id": "multi-plugin-service",
16
+ "config": "platformatic.service.json"
17
+ }
18
+ }
19
+ },
20
+ "managementApi": {}
21
+ }
@@ -16,5 +16,8 @@
16
16
  "plugin.js"
17
17
  ]
18
18
  },
19
- "watch": true
19
+ "watch": true,
20
+ "metrics": {
21
+ "server": "parent"
22
+ }
20
23
  }
package/lib/api-client.js CHANGED
@@ -27,8 +27,14 @@ class RuntimeApiClient extends EventEmitter {
27
27
  return this.#sendCommand('plt:start-services')
28
28
  }
29
29
 
30
+ async stop () {
31
+ await this.#sendCommand('plt:stop-services')
32
+ }
33
+
30
34
  async close () {
31
35
  await this.#sendCommand('plt:stop-services')
36
+
37
+ this.worker.postMessage({ command: 'plt:close' })
32
38
  await this.#exitPromise
33
39
  }
34
40
 
package/lib/api.js CHANGED
@@ -47,12 +47,13 @@ class RuntimeApi {
47
47
  parentPort.on('message', async (message) => {
48
48
  const command = message?.command
49
49
  if (command) {
50
- const res = await this.#executeCommand(message)
51
- parentPort.postMessage(res)
52
-
53
- if (command === 'plt:stop-services') {
50
+ if (command === 'plt:close') {
51
+ await this.#dispatcher.close()
54
52
  process.exit() // Exit the worker thread.
55
53
  }
54
+
55
+ const res = await this.#executeCommand(message)
56
+ parentPort.postMessage(res)
56
57
  return
57
58
  }
58
59
  await this.#handleProcessLevelEvent(message)
@@ -134,7 +135,7 @@ class RuntimeApi {
134
135
  }
135
136
 
136
137
  async stopServices () {
137
- const stopServiceReqs = [this.#dispatcher.close()]
138
+ const stopServiceReqs = []
138
139
  for (const service of this.#services.values()) {
139
140
  const serviceStatus = service.getStatus()
140
141
  if (serviceStatus === 'started') {
@@ -269,8 +270,7 @@ class RuntimeApi {
269
270
  statusCode: res.statusCode,
270
271
  statusMessage: res.statusMessage,
271
272
  headers: res.headers,
272
- body: res.body,
273
- payload: res.payload
273
+ body: res.body
274
274
  }
275
275
  }
276
276
  }
package/lib/app.js CHANGED
@@ -103,6 +103,16 @@ class PlatformaticApp {
103
103
  })
104
104
  }
105
105
 
106
+ if (!this.appConfig.entrypoint) {
107
+ configManager.update({
108
+ ...configManager.current,
109
+ server: {
110
+ ...(configManager.current.server || {}),
111
+ trustProxy: true
112
+ }
113
+ })
114
+ }
115
+
106
116
  const config = configManager.current
107
117
 
108
118
  this.#setuplogger(configManager)
package/lib/errors.js CHANGED
@@ -21,5 +21,6 @@ module.exports = {
21
21
  InspectorPortError: createError(`${ERROR_PREFIX}_INSPECTOR_PORT`, 'Inspector port must be 0 or in range 1024 to 65535'),
22
22
  InspectorHostError: createError(`${ERROR_PREFIX}_INSPECTOR_HOST`, 'Inspector host cannot be empty'),
23
23
  CannotMapSpecifierToAbsolutePathError: createError(`${ERROR_PREFIX}_CANNOT_MAP_SPECIFIER_TO_ABSOLUTE_PATH`, 'Cannot map "%s" to an absolute path'),
24
- NodeInspectorFlagsNotSupportedError: createError(`${ERROR_PREFIX}_NODE_INSPECTOR_FLAGS_NOT_SUPPORTED`, 'The Node.js inspector flags are not supported. Please use \'platformatic start --inspect\' instead.')
24
+ NodeInspectorFlagsNotSupportedError: createError(`${ERROR_PREFIX}_NODE_INSPECTOR_FLAGS_NOT_SUPPORTED`, 'The Node.js inspector flags are not supported. Please use \'platformatic start --inspect\' instead.'),
25
+ FailedToUnlinkManagementApiSocket: createError(`${ERROR_PREFIX}_FAILED_TO_UNLINK_MANAGEMENT_API_SOCKET`, 'Failed to unlink management API socket "%s"')
25
26
  }
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { BaseGenerator } = require('@platformatic/generators')
2
4
  const { NoEntryPointError, NoServiceNamedError } = require('./errors')
3
5
  const generateName = require('boring-name-generator')
@@ -78,12 +80,11 @@ class RuntimeGenerator extends BaseGenerator {
78
80
  this.setServicesConfigValues()
79
81
  this.addServicesDependencies()
80
82
 
81
- this.config.env = {
83
+ this.addEnvVars({
82
84
  PLT_SERVER_HOSTNAME: '0.0.0.0',
83
85
  PORT: this.config.port || 3042,
84
- PLT_SERVER_LOGGER_LEVEL: this.config.logLevel || 'info',
85
- ...this.config.env
86
- }
86
+ PLT_SERVER_LOGGER_LEVEL: this.config.logLevel || 'info'
87
+ }, { overwrite: false })
87
88
  }
88
89
 
89
90
  addServicesDependencies () {
@@ -167,11 +168,11 @@ class RuntimeGenerator extends BaseGenerator {
167
168
  throw new NoEntryPointError()
168
169
  }
169
170
  const servicesEnv = await this.prepareServiceFiles()
170
- this.config.env = {
171
+ this.addEnvVars({
171
172
  ...this.config.env,
172
173
  ...this.getRuntimeEnv(),
173
174
  ...servicesEnv
174
- }
175
+ })
175
176
 
176
177
  this.addFile({
177
178
  path: '',
@@ -0,0 +1,113 @@
1
+ 'use strict'
2
+
3
+ const fastify = require('fastify')
4
+ const { isatty } = require('tty')
5
+ const platformaticVersion = require('../package.json').version
6
+
7
+ async function createManagementApi (config, runtimeApiClient) {
8
+ addManagementApiLogger(config)
9
+ const app = fastify(config)
10
+ app.log.warn(
11
+ 'Runtime Management API is in the experimental stage. ' +
12
+ 'The feature is not subject to semantic versioning rules. ' +
13
+ 'Non-backward compatible changes or removal may occur in any future release. ' +
14
+ 'Use of the feature is not recommended in production environments.'
15
+ )
16
+
17
+ app.register(async (app) => {
18
+ app.get('/metadata', async () => {
19
+ return {
20
+ pid: process.pid,
21
+ cwd: process.cwd(),
22
+ execPath: process.execPath,
23
+ nodeVersion: process.version,
24
+ platformaticVersion
25
+ }
26
+ })
27
+
28
+ app.get('/services', async () => {
29
+ return runtimeApiClient.getServices()
30
+ })
31
+
32
+ app.post('/services/start', async () => {
33
+ app.log.debug('start services')
34
+ await runtimeApiClient.start()
35
+ })
36
+
37
+ app.post('/services/stop', async () => {
38
+ app.log.debug('stop services')
39
+ await runtimeApiClient.stop()
40
+ })
41
+
42
+ app.post('/services/restart', async () => {
43
+ app.log.debug('restart services')
44
+ await runtimeApiClient.restart()
45
+ })
46
+
47
+ app.get('/services/:id', async (request) => {
48
+ const { id } = request.params
49
+ app.log.debug('get service details', { id })
50
+ return runtimeApiClient.getServiceDetails(id)
51
+ })
52
+
53
+ app.get('/services/:id/config', async (request) => {
54
+ const { id } = request.params
55
+ app.log.debug('get service config', { id })
56
+ return runtimeApiClient.getServiceConfig(id)
57
+ })
58
+
59
+ app.post('/services/:id/start', async (request) => {
60
+ const { id } = request.params
61
+ app.log.debug('start service', { id })
62
+ await runtimeApiClient.startService(id)
63
+ })
64
+
65
+ app.post('/services/:id/stop', async (request) => {
66
+ const { id } = request.params
67
+ app.log.debug('stop service', { id })
68
+ await runtimeApiClient.stopService(id)
69
+ })
70
+
71
+ app.all('/services/:id/proxy/*', async (request, reply) => {
72
+ const { id, '*': requestUrl } = request.params
73
+ app.log.debug('proxy request', { id, requestUrl })
74
+
75
+ const injectParams = {
76
+ method: request.method,
77
+ url: requestUrl || '/',
78
+ headers: request.headers,
79
+ query: request.query,
80
+ body: request.body
81
+ }
82
+
83
+ const res = await runtimeApiClient.inject(id, injectParams)
84
+
85
+ reply
86
+ .code(res.statusCode)
87
+ .headers(res.headers)
88
+ .send(res.body)
89
+ })
90
+ }, { prefix: '/api' })
91
+
92
+ return app
93
+ }
94
+
95
+ function addManagementApiLogger (config) {
96
+ let logger = config.logger
97
+ if (!logger) {
98
+ config.logger = {
99
+ level: 'info',
100
+ name: 'management-api'
101
+ }
102
+ logger = config.logger
103
+ }
104
+
105
+ /* c8 ignore next 5 */
106
+ if (isatty(1) && !logger.transport) {
107
+ logger.transport = {
108
+ target: 'pino-pretty'
109
+ }
110
+ }
111
+ }
112
+
113
+ module.exports = { createManagementApi }
package/lib/schema.js CHANGED
@@ -129,6 +129,15 @@ const platformaticRuntimeSchema = {
129
129
  },
130
130
  $schema: {
131
131
  type: 'string'
132
+ },
133
+ managementApi: {
134
+ anyOf: [
135
+ { type: 'boolean' },
136
+ {
137
+ type: 'object',
138
+ properties: {}
139
+ }
140
+ ]
132
141
  }
133
142
  },
134
143
  anyOf: [
package/lib/start.js CHANGED
@@ -1,16 +1,19 @@
1
1
  'use strict'
2
+
3
+ const { tmpdir, platform } = require('node:os')
2
4
  const { once } = require('node:events')
3
5
  const inspector = require('node:inspector')
4
6
  const { join, resolve, dirname } = require('node:path')
5
- const fs = require('node:fs/promises')
7
+ const { writeFile, unlink, mkdir } = require('node:fs/promises')
6
8
  const { pathToFileURL } = require('node:url')
7
9
  const { Worker } = require('node:worker_threads')
8
- const closeWithGrace = require('close-with-grace')
9
10
  const { start: serviceStart } = require('@platformatic/service')
11
+ const { printConfigValidationErrors } = require('@platformatic/config')
12
+ const closeWithGrace = require('close-with-grace')
10
13
  const { loadConfig } = require('./load-config')
14
+ const { createManagementApi } = require('./management-api')
11
15
  const { parseInspectorOptions, wrapConfigInRuntimeConfig } = require('./config')
12
16
  const RuntimeApiClient = require('./api-client.js')
13
- const { printConfigValidationErrors } = require('@platformatic/config')
14
17
  const errors = require('./errors')
15
18
  const pkg = require('../package.json')
16
19
 
@@ -22,6 +25,8 @@ const kWorkerExecArgv = [
22
25
  kLoaderFile
23
26
  ]
24
27
 
28
+ const PLATFORMATIC_TMP_DIR = join(tmpdir(), 'platformatic', 'pids')
29
+
25
30
  async function startWithConfig (configManager, env = process.env) {
26
31
  const config = configManager.current
27
32
 
@@ -50,6 +55,8 @@ async function startWithConfig (configManager, env = process.env) {
50
55
  env
51
56
  })
52
57
 
58
+ let managementApi = null
59
+
53
60
  let exited = null
54
61
  let isWorkerAlive = true
55
62
  worker.on('exit', (code) => {
@@ -57,6 +64,7 @@ async function startWithConfig (configManager, env = process.env) {
57
64
  process.exitCode = code
58
65
  isWorkerAlive = false
59
66
  configManager.fileWatcher?.stopWatching()
67
+ managementApi?.close()
60
68
  if (typeof exited === 'function') {
61
69
  exited()
62
70
  }
@@ -96,6 +104,12 @@ async function startWithConfig (configManager, env = process.env) {
96
104
  await once(worker, 'message') // plt:init
97
105
 
98
106
  const runtimeApiClient = new RuntimeApiClient(worker)
107
+
108
+ if (config.managementApi) {
109
+ managementApi = await startManagementApi(config.managementApi, runtimeApiClient)
110
+ runtimeApiClient.managementApi = managementApi
111
+ }
112
+
99
113
  return runtimeApiClient
100
114
  }
101
115
 
@@ -112,6 +126,38 @@ async function start (args) {
112
126
  return serviceStart(config.app, args)
113
127
  }
114
128
 
129
+ async function startManagementApi (managementApiConfig, runtimeApiClient) {
130
+ const runtimePID = process.pid
131
+
132
+ let socketPath = null
133
+ if (platform() === 'win32') {
134
+ socketPath = '\\\\.\\pipe\\' + join(PLATFORMATIC_TMP_DIR, `${runtimePID}.sock`)
135
+ } else {
136
+ await mkdir(PLATFORMATIC_TMP_DIR, { recursive: true })
137
+ socketPath = join(PLATFORMATIC_TMP_DIR, `${runtimePID}.sock`)
138
+ }
139
+
140
+ try {
141
+ await mkdir(PLATFORMATIC_TMP_DIR, { recursive: true })
142
+ await unlink(socketPath).catch((err) => {
143
+ if (err.code !== 'ENOENT') {
144
+ throw new errors.FailedToUnlinkManagementApiSocket(err.message)
145
+ }
146
+ })
147
+
148
+ const managementApi = await createManagementApi(
149
+ managementApiConfig,
150
+ runtimeApiClient
151
+ )
152
+ await managementApi.listen({ path: socketPath })
153
+ return managementApi
154
+ /* c8 ignore next 4 */
155
+ } catch (err) {
156
+ console.error(err)
157
+ process.exit(1)
158
+ }
159
+ }
160
+
115
161
  async function startCommand (args) {
116
162
  try {
117
163
  const config = await loadConfig({}, args)
@@ -148,7 +194,7 @@ async function startCommand (args) {
148
194
  }
149
195
  const toWrite = join(dirname(resolve(args[0])), 'platformatic.service.json')
150
196
  console.log(`No config file found, creating ${join(dirname(args[0]), 'platformatic.service.json')}`)
151
- await fs.writeFile(toWrite, JSON.stringify(config, null, 2))
197
+ await writeFile(toWrite, JSON.stringify(config, null, 2))
152
198
  return startCommand(['--config', toWrite])
153
199
  }
154
200
  logErrorAndExit(err)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "1.21.1",
3
+ "version": "1.23.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -30,8 +30,8 @@
30
30
  "tsd": "^0.30.4",
31
31
  "typescript": "^5.3.3",
32
32
  "undici-oauth-interceptor": "^0.4.2",
33
- "@platformatic/sql-graphql": "1.21.1",
34
- "@platformatic/sql-mapper": "1.21.1"
33
+ "@platformatic/sql-graphql": "1.23.0",
34
+ "@platformatic/sql-mapper": "1.23.0"
35
35
  },
36
36
  "dependencies": {
37
37
  "@fastify/error": "^3.4.1",
@@ -52,13 +52,13 @@
52
52
  "pino-pretty": "^10.3.1",
53
53
  "undici": "^6.6.0",
54
54
  "why-is-node-running": "^2.2.2",
55
- "@platformatic/composer": "1.21.1",
56
- "@platformatic/config": "1.21.1",
57
- "@platformatic/db": "1.21.1",
58
- "@platformatic/generators": "1.21.1",
59
- "@platformatic/service": "1.21.1",
60
- "@platformatic/utils": "1.21.1",
61
- "@platformatic/telemetry": "1.21.1"
55
+ "@platformatic/composer": "1.23.0",
56
+ "@platformatic/config": "1.23.0",
57
+ "@platformatic/db": "1.23.0",
58
+ "@platformatic/service": "1.23.0",
59
+ "@platformatic/generators": "1.23.0",
60
+ "@platformatic/telemetry": "1.23.0",
61
+ "@platformatic/utils": "1.23.0"
62
62
  },
63
63
  "standard": {
64
64
  "ignore": [