@platformatic/runtime 2.0.0-alpha.3 → 2.0.0-alpha.5

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/config.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * and run json-schema-to-typescript to regenerate this file.
6
6
  */
7
7
 
8
- export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha2Json = {
8
+ export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha5Json = {
9
9
  [k: string]: unknown;
10
10
  } & {
11
11
  $schema?: string;
@@ -201,10 +201,6 @@ export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha2Json = {
201
201
  };
202
202
  restartOnError?: boolean | number;
203
203
  services?: {
204
- id: string;
205
- path: string;
206
- config?: string;
207
- useHttp?: boolean;
208
204
  [k: string]: unknown;
209
205
  }[];
210
206
  };
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/runtime/1.52.0.json",
3
+ "entrypoint": "service-1",
4
+ "autoload": {
5
+ "path": "./services"
6
+ },
7
+ "restartOnError": 100,
8
+ "server": {
9
+ "hostname": "127.0.0.1",
10
+ "port": "0",
11
+ "logger": {
12
+ "level": "info"
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/service/1.52.0.json",
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,5 @@
1
+ 'use strict'
2
+
3
+ module.exports = async function (app) {
4
+ app.log.info('Service 1 plugin')
5
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/service/1.52.0.json",
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,5 @@
1
+ 'use strict'
2
+
3
+ module.exports = async function () {
4
+ throw new Error('Crash!')
5
+ }
@@ -1,8 +1,22 @@
1
1
  'use strict'
2
+
3
+ const { request } = require('undici')
4
+
2
5
  module.exports = async function (fastify, opts) {
3
6
  // This returns the traceId set on the span by the service
4
- fastify.get('/', async (request, reply) => {
5
- const traceId = request.span.spanContext().traceId
7
+ fastify.get('/', async (req, reply) => {
8
+ const traceId = req.span.spanContext().traceId
6
9
  return { traceId }
7
10
  })
11
+
12
+ fastify.get('/service-1/echo-headers', async (req, reply) => {
13
+ const res = await request('http://service-1.plt.local/echo-headers', {
14
+ method: 'GET',
15
+ headers: {
16
+ 'content-type': 'application/json',
17
+ },
18
+ })
19
+ const body = await res.body.json()
20
+ return body
21
+ })
8
22
  }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/service/1.52.0.json",
3
+ "server": {
4
+ "hostname": "127.0.0.1",
5
+ "port": "0",
6
+ "logger": {
7
+ "level": "info"
8
+ }
9
+ },
10
+ "service": {
11
+ "openapi": true
12
+ },
13
+ "plugins": {
14
+ "paths": [
15
+ "./routes"
16
+ ],
17
+ "typescript": false
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+ module.exports = async function (fastify, opts) {
3
+ // This returns the traceId set on the span by the service
4
+ fastify.get('/echo-headers', async (request, reply) => {
5
+ return { headers: request.headers }
6
+ })
7
+ }
package/lib/errors.js CHANGED
@@ -13,6 +13,7 @@ module.exports = {
13
13
  ServiceNotStartedError: createError(`${ERROR_PREFIX}_SERVICE_NOT_STARTED`, "Service with id '%s' is not started"),
14
14
  FailedToRetrieveOpenAPISchemaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_OPENAPI_SCHEMA`, 'Failed to retrieve OpenAPI schema for service with id "%s": %s'),
15
15
  FailedToRetrieveGraphQLSchemaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_GRAPHQL_SCHEMA`, 'Failed to retrieve GraphQL schema for service with id "%s": %s'),
16
+ FailedToRetrieveMetaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_META`, 'Failed to retrieve metadata for service with id "%s": %s'),
16
17
  ApplicationAlreadyStartedError: createError(`${ERROR_PREFIX}_APPLICATION_ALREADY_STARTED`, 'Application is already started'),
17
18
  ApplicationNotStartedError: createError(`${ERROR_PREFIX}_APPLICATION_NOT_STARTED`, 'Application has not been started'),
18
19
  ConfigPathMustBeStringError: createError(`${ERROR_PREFIX}_CONFIG_PATH_MUST_BE_STRING`, 'Config path must be a string'),
@@ -202,7 +202,7 @@ class RuntimeGenerator extends BaseGenerator {
202
202
  })
203
203
 
204
204
  if (!this.existingConfig) {
205
- this.addFile({ path: '', file: 'README.md', contents: await readFile(join(__dirname, 'README.md')) })
205
+ this.addFile({ path: '', file: 'README.md', contents: await readFile(join(__dirname, 'README.md'), 'utf-8') })
206
206
  }
207
207
 
208
208
  return {
@@ -375,7 +375,7 @@ class RuntimeGenerator extends BaseGenerator {
375
375
 
376
376
  // delete dependencies
377
377
  const servicePackageJson = JSON.parse(
378
- await readFile(join(this.targetDirectory, 'services', s.name, 'platformatic.json'))
378
+ await readFile(join(this.targetDirectory, 'services', s.name, 'platformatic.json'), 'utf-8')
379
379
  )
380
380
  if (servicePackageJson.plugins && servicePackageJson.plugins.packages) {
381
381
  servicePackageJson.plugins.packages.forEach(p => {
@@ -72,6 +72,20 @@ async function managementApiPlugin (app, opts) {
72
72
  return runtime.getServiceGraphqlSchema(id)
73
73
  })
74
74
 
75
+ app.get('/services/:id/connection-strings', async request => {
76
+ const { id } = request.params
77
+ try {
78
+ app.log.debug('get connection strings', { id })
79
+ const meta = await runtime.getServiceMeta(id)
80
+ if (meta.db) {
81
+ return meta.db
82
+ }
83
+ return null
84
+ } catch (err) {
85
+ throw new errors.FailedToRetrieveMetaError(id, err.message)
86
+ }
87
+ })
88
+
75
89
  app.post('/services/:id/start', async request => {
76
90
  const { id } = request.params
77
91
  app.log.debug('start service', { id })
package/lib/runtime.js CHANGED
@@ -28,6 +28,8 @@ const MAX_LISTENERS_COUNT = 100
28
28
  const MAX_METRICS_QUEUE_LENGTH = 5 * 60 // 5 minutes in seconds
29
29
  const COLLECT_METRICS_TIMEOUT = 1000
30
30
 
31
+ const MAX_BOOTSTRAP_ATTEMPTS = 5
32
+
31
33
  class Runtime extends EventEmitter {
32
34
  #configManager
33
35
  #runtimeTmpDir
@@ -46,7 +48,8 @@ class Runtime extends EventEmitter {
46
48
  #managementApi
47
49
  #prometheusServer
48
50
  #startedServices
49
- #crashedServicesTimers
51
+ #restartPromises
52
+ #bootstrapAttempts
50
53
 
51
54
  constructor (configManager, runtimeLogsDir, env) {
52
55
  super()
@@ -63,7 +66,8 @@ class Runtime extends EventEmitter {
63
66
  this.#interceptor = createThreadInterceptor({ domain: '.plt.local' })
64
67
  this.#status = undefined
65
68
  this.#startedServices = new Map()
66
- this.#crashedServicesTimers = new Map()
69
+ this.#restartPromises = new Map()
70
+ this.#bootstrapAttempts = new Map()
67
71
  }
68
72
 
69
73
  async init () {
@@ -126,8 +130,15 @@ class Runtime extends EventEmitter {
126
130
  this.#updateStatus('starting')
127
131
 
128
132
  // Important: do not use Promise.all here since it won't properly manage dependencies
129
- for (const service of this.#servicesIds) {
130
- await this.startService(service)
133
+ try {
134
+ for (const service of this.#servicesIds) {
135
+ await this.startService(service)
136
+ }
137
+ } catch (error) {
138
+ // Wait for the next tick so that the error is logged first
139
+ await sleep(1)
140
+ await this.close()
141
+ throw error
131
142
  }
132
143
 
133
144
  this.#updateStatus('started')
@@ -145,13 +156,7 @@ class Runtime extends EventEmitter {
145
156
  }
146
157
 
147
158
  this.#updateStatus('stopping')
148
-
149
- for (const timer of this.#crashedServicesTimers.values()) {
150
- clearTimeout(timer)
151
- }
152
-
153
159
  this.#startedServices.clear()
154
- this.#crashedServicesTimers.clear()
155
160
 
156
161
  await Promise.all(this.#servicesIds.map(service => this._stopService(service)))
157
162
 
@@ -209,12 +214,6 @@ class Runtime extends EventEmitter {
209
214
  // This is set here so that if the service fails while starting we track the status
210
215
  this.#startedServices.set(id, true)
211
216
 
212
- // Make sure we don't restart twice
213
- const crashedTimer = this.#crashedServicesTimers.get(id)
214
- if (crashedTimer) {
215
- clearTimeout(crashedTimer)
216
- }
217
-
218
217
  let service = await this.#getServiceById(id, false, false)
219
218
 
220
219
  // The service was stopped, recreate the thread
@@ -226,10 +225,39 @@ class Runtime extends EventEmitter {
226
225
  service = await this.#getServiceById(id)
227
226
  }
228
227
 
229
- const serviceUrl = await sendViaITC(service, 'start')
228
+ try {
229
+ const serviceUrl = await sendViaITC(service, 'start')
230
+ if (serviceUrl) {
231
+ this.#url = serviceUrl
232
+ }
233
+ this.#bootstrapAttempts.set(id, 0)
234
+ } catch (error) {
235
+ // TODO: handle port allocation error here
236
+ if (error.code === 'EADDRINUSE') throw error
237
+
238
+ this.logger.error({ error }, `Failed to start service "${id}".`)
239
+
240
+ const config = this.#configManager.current
241
+ const restartOnError = config.restartOnError
242
+
243
+ if (!restartOnError) {
244
+ this.logger.error(`Failed to start service "${id}".`)
245
+ throw error
246
+ }
247
+
248
+ let bootstrapAttempt = this.#bootstrapAttempts.get(id)
249
+ if (bootstrapAttempt++ >= MAX_BOOTSTRAP_ATTEMPTS || restartOnError === 0) {
250
+ this.logger.error(`Failed to start service "${id}" after ${MAX_BOOTSTRAP_ATTEMPTS} attempts.`)
251
+ throw error
252
+ }
230
253
 
231
- if (serviceUrl) {
232
- this.#url = serviceUrl
254
+ this.logger.warn(
255
+ `Starting a service "${id}" in ${restartOnError}ms. ` +
256
+ `Attempt ${bootstrapAttempt} of ${MAX_BOOTSTRAP_ATTEMPTS}...`
257
+ )
258
+
259
+ this.#bootstrapAttempts.set(id, bootstrapAttempt)
260
+ await this.#restartCrashedService(id)
233
261
  }
234
262
  }
235
263
 
@@ -243,11 +271,13 @@ class Runtime extends EventEmitter {
243
271
 
244
272
  this.#startedServices.set(id, false)
245
273
 
274
+ this.logger?.info(`Stopping service "${id}"...`)
275
+
246
276
  // Always send the stop message, it will shut down workers that only had ITC and interceptors setup
247
277
  try {
248
278
  await Promise.race([sendViaITC(service, 'stop'), sleep(10000, 'timeout', { ref: false })])
249
279
  } catch (error) {
250
- this.logger.info(`Failed to stop service "${id}". Killing a worker thread.`, error)
280
+ this.logger?.info(`Failed to stop service "${id}". Killing a worker thread.`, error)
251
281
  }
252
282
 
253
283
  // Wait for the worker thread to finish, we're going to create a new one if the service is ever restarted
@@ -638,6 +668,8 @@ class Runtime extends EventEmitter {
638
668
  }
639
669
 
640
670
  async #setupService (serviceConfig) {
671
+ if (this.#status === 'stopping' || this.#status === 'closed') return
672
+
641
673
  const config = this.#configManager.current
642
674
  const { autoload, restartOnError } = config
643
675
 
@@ -645,6 +677,10 @@ class Runtime extends EventEmitter {
645
677
  const { port1: loggerDestination, port2: loggingPort } = new MessageChannel()
646
678
  loggerDestination.on('message', this.#forwardThreadLog.bind(this))
647
679
 
680
+ if (!this.#bootstrapAttempts.has(id)) {
681
+ this.#bootstrapAttempts.set(id, 0)
682
+ }
683
+
648
684
  const service = new Worker(kWorkerFile, {
649
685
  workerData: {
650
686
  config,
@@ -677,16 +713,21 @@ class Runtime extends EventEmitter {
677
713
  loggerDestination.close()
678
714
  loggingPort.close()
679
715
 
716
+ if (this.#status === 'stopping') return
717
+
680
718
  // Wait for the next tick so that crashed from the thread are logged first
681
719
  setImmediate(() => {
682
- // If this was started, it means it crashed
683
- if (started && (this.#status === 'started' || this.#status === 'starting')) {
720
+ this.logger.warn(`Service "${id}" unexpectedly exited with code ${code}.`)
721
+
722
+ // Restart the service if it was started
723
+ if (started && this.#status === 'started') {
684
724
  if (restartOnError > 0) {
685
- this.#restartCrashedService(serviceConfig, code, started)
725
+ this.logger.warn(`Restarting a service "${id}" in ${restartOnError}ms...`)
726
+ this.#restartCrashedService(id).catch(err => {
727
+ this.logger.error({ err }, `Failed to restart service "${id}".`)
728
+ })
686
729
  } else {
687
- this.logger.warn(
688
- `Service ${id} unexpectedly exited with code ${code}. The service is no longer available ...`
689
- )
730
+ this.logger.warn(`The "${id}" service is no longer available.`)
690
731
  }
691
732
  }
692
733
  })
@@ -698,6 +739,7 @@ class Runtime extends EventEmitter {
698
739
  // Setup ITC
699
740
  service[kITC] = new ITC({ port: service })
700
741
  service[kITC].listen()
742
+ service[kITC].handle('getServiceMeta', this.getServiceMeta.bind(this))
701
743
 
702
744
  // Handle services changes
703
745
  // This is not purposely activated on when this.#configManager.current.watch === true
@@ -713,9 +755,9 @@ class Runtime extends EventEmitter {
713
755
  await this.startService(id)
714
756
  }
715
757
 
716
- this.logger.info(`Service ${id} has been successfully reloaded ...`)
758
+ this.logger?.info(`Service ${id} has been successfully reloaded ...`)
717
759
  } catch (e) {
718
- this.logger.error(e)
760
+ this.logger?.error(e)
719
761
  }
720
762
  })
721
763
 
@@ -743,26 +785,38 @@ class Runtime extends EventEmitter {
743
785
  }
744
786
  }
745
787
 
746
- async #restartCrashedService (serviceConfig, code, started) {
747
- const restartTimeout = this.#configManager.current.restartOnError
748
- const id = serviceConfig.id
788
+ async #restartCrashedService (id) {
789
+ const config = this.#configManager.current
790
+ const serviceConfig = config.services.find(s => s.id === id)
749
791
 
750
- this.logger.warn(`Service ${id} unexpectedly exited with code ${code}. Restarting in ${restartTimeout}ms ...`)
792
+ let restartPromise = this.#restartPromises.get(id)
793
+ if (restartPromise) {
794
+ await restartPromise
795
+ return
796
+ }
751
797
 
752
- const timer = setTimeout(async () => {
753
- try {
754
- await this.#setupService(serviceConfig)
798
+ restartPromise = new Promise((resolve, reject) => {
799
+ setTimeout(async () => {
800
+ this.#restartPromises.delete(id)
755
801
 
756
- if (started) {
757
- this.#startedServices.set(id, false)
758
- await this.startService(id)
802
+ try {
803
+ await this.#setupService(serviceConfig)
804
+
805
+ const started = this.#startedServices.get(id)
806
+ if (started) {
807
+ this.#startedServices.set(id, false)
808
+ await this.startService(id)
809
+ }
810
+
811
+ resolve()
812
+ } catch (err) {
813
+ reject(err)
759
814
  }
760
- } catch (err) {
761
- this.logger.error({ err }, `Failed to restart service ${id}.`)
762
- }
763
- }, restartTimeout).unref()
815
+ }, config.restartOnError)
816
+ })
764
817
 
765
- this.#crashedServicesTimers.set(id, timer)
818
+ this.#restartPromises.set(id, restartPromise)
819
+ await restartPromise
766
820
  }
767
821
 
768
822
  async #getServiceById (id, ensureStarted = false, mustExist = true) {
@@ -787,6 +841,25 @@ class Runtime extends EventEmitter {
787
841
  return service
788
842
  }
789
843
 
844
+ async getServiceMeta (id) {
845
+ const service = this.#services.get(id)
846
+
847
+ if (!service) {
848
+ throw new errors.ServiceNotFoundError(id, Array.from(this.#services.keys()).join(', '))
849
+ }
850
+
851
+ try {
852
+ return await service[kITC].send('getServiceMeta')
853
+ } catch (e) {
854
+ // The service exports no meta, return an empty object
855
+ if (e.code === 'PLT_ITC_HANDLER_NOT_FOUND') {
856
+ return {}
857
+ }
858
+
859
+ throw e
860
+ }
861
+ }
862
+
790
863
  async #getRuntimePackageJson () {
791
864
  const runtimeDir = this.#configManager.dirname
792
865
  const packageJsonPath = join(runtimeDir, 'package.json')
package/lib/schema.js CHANGED
@@ -191,7 +191,10 @@ const platformaticRuntimeSchema = {
191
191
  type: 'array',
192
192
  items: {
193
193
  type: 'object',
194
- required: ['id', 'path'],
194
+ anyOf: [
195
+ { required: ['id', 'path'] },
196
+ { required: ['id', 'url'] },
197
+ ],
195
198
  properties: {
196
199
  id: {
197
200
  type: 'string',
@@ -203,6 +206,9 @@ const platformaticRuntimeSchema = {
203
206
  config: {
204
207
  type: 'string',
205
208
  },
209
+ url: {
210
+ type: 'string',
211
+ },
206
212
  useHttp: {
207
213
  type: 'boolean',
208
214
  },
package/lib/worker/app.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { EventEmitter } = require('node:events')
4
4
  const { FileWatcher } = require('@platformatic/utils')
5
+ const { getGlobalDispatcher, setGlobalDispatcher } = require('undici')
5
6
  const debounce = require('debounce')
6
7
 
7
8
  const errors = require('../errors')
@@ -31,6 +32,7 @@ class PlatformaticApp extends EventEmitter {
31
32
  serviceId: this.appConfig.id,
32
33
  directory: this.appConfig.path,
33
34
  isEntrypoint: this.appConfig.entrypoint,
35
+ isProduction: false,
34
36
  telemetryConfig,
35
37
  metricsConfig,
36
38
  serverConfig,
@@ -90,6 +92,8 @@ class PlatformaticApp extends EventEmitter {
90
92
  context: this.#context,
91
93
  })
92
94
  this.stackable = this.#wrapStackable(stackable)
95
+
96
+ this.#updateDispatcher()
93
97
  } catch (err) {
94
98
  this.#logAndExit(err)
95
99
  }
@@ -103,7 +107,7 @@ class PlatformaticApp extends EventEmitter {
103
107
  this.#starting = true
104
108
 
105
109
  try {
106
- await this.stackable.init?.()
110
+ await this.stackable.init()
107
111
  } catch (err) {
108
112
  this.#logAndExit(err)
109
113
  }
@@ -201,24 +205,38 @@ class PlatformaticApp extends EventEmitter {
201
205
 
202
206
  #logAndExit (err) {
203
207
  // Runtime logs here with console.error because stackable is not initialized
204
- console.error(
205
- JSON.stringify({
206
- msg: err.message,
207
- name: this.appConfig.id,
208
- })
209
- )
208
+ console.error(err.message)
210
209
  process.exit(1)
211
210
  }
212
211
 
213
212
  #wrapStackable (stackable) {
214
213
  const newStackable = {}
215
214
  for (const method of Object.keys(defaultStackable)) {
216
- newStackable[method] = stackable[method]
217
- ? stackable[method].bind(stackable)
218
- : defaultStackable[method]
215
+ newStackable[method] = stackable[method] ? stackable[method].bind(stackable) : defaultStackable[method]
219
216
  }
220
217
  return newStackable
221
218
  }
219
+
220
+ #updateDispatcher () {
221
+ const telemetryConfig = this.#context.telemetryConfig
222
+ const telemetryId = telemetryConfig?.serviceName
223
+
224
+ const interceptor = dispatch => {
225
+ return function InterceptedDispatch (opts, handler) {
226
+ if (telemetryId) {
227
+ opts.headers = {
228
+ ...opts.headers,
229
+ 'x-plt-telemetry-id': telemetryId,
230
+ }
231
+ }
232
+ return dispatch(opts, handler)
233
+ }
234
+ }
235
+
236
+ const dispatcher = getGlobalDispatcher().compose(interceptor)
237
+
238
+ setGlobalDispatcher(dispatcher)
239
+ }
222
240
  }
223
241
 
224
242
  module.exports = { PlatformaticApp }
@@ -13,6 +13,7 @@ const defaultStackable = {
13
13
  getDispatchFunc: () => null,
14
14
  getOpenapiSchema: () => null,
15
15
  getGraphqlSchema: () => null,
16
+ getMeta: () => ({}),
16
17
  getMetrics: () => null,
17
18
  inject: () => {
18
19
  throw new Error('Stackable inject not implemented')
package/lib/worker/itc.js CHANGED
@@ -110,6 +110,14 @@ function setupITC (app, service, dispatcher) {
110
110
  }
111
111
  })
112
112
 
113
+ itc.handle('getServiceMeta', async () => {
114
+ try {
115
+ return app.stackable.getMeta()
116
+ } catch (err) {
117
+ throw new errors.FailedToRetrieveMetaError(service.id, err.message)
118
+ }
119
+ })
120
+
113
121
  itc.handle('getMetrics', async format => {
114
122
  return app.stackable.getMetrics({ format })
115
123
  })
@@ -13,8 +13,7 @@ const { wire } = require('undici-thread-interceptor')
13
13
  const { PlatformaticApp } = require('./app')
14
14
  const { setupITC } = require('./itc')
15
15
  const loadInterceptors = require('./interceptors')
16
- const { MessagePortWritable } = require('../streams/message-port-writable')
17
- const { createPinoWritable } = require('../streams/pino-writable')
16
+ const { MessagePortWritable, createPinoWritable } = require('@platformatic/utils')
18
17
  const { kId, kITC } = require('./symbols')
19
18
 
20
19
  process.on('uncaughtException', handleUnhandled.bind(null, 'uncaught exception'))
@@ -39,7 +38,7 @@ function handleUnhandled (type, err) {
39
38
 
40
39
  function createLogger () {
41
40
  const destination = new MessagePortWritable({ port: workerData.loggingPort })
42
- const loggerInstance = pino({ level: 'trace' }, destination)
41
+ const loggerInstance = pino({ level: 'trace', name: workerData.serviceConfig.id }, destination)
43
42
 
44
43
  Reflect.defineProperty(process, 'stdout', { value: createPinoWritable(loggerInstance, 'info') })
45
44
  Reflect.defineProperty(process, 'stderr', { value: createPinoWritable(loggerInstance, 'error') })
@@ -88,15 +87,23 @@ async function main () {
88
87
  } else if (service.useHttp) {
89
88
  serverConfig = {
90
89
  port: 0,
91
- host: '127.0.0.1',
90
+ hostname: '127.0.0.1',
92
91
  keepAliveTimeout: 5000,
93
92
  }
94
93
  }
95
94
 
95
+ let telemetryConfig = config.telemetry
96
+ if (telemetryConfig) {
97
+ telemetryConfig = {
98
+ ...telemetryConfig,
99
+ serviceName: `${telemetryConfig.serviceName}-${service.id}`,
100
+ }
101
+ }
102
+
96
103
  // Create the application
97
104
  app = new PlatformaticApp(
98
105
  service,
99
- config.telemetry,
106
+ telemetryConfig,
100
107
  serverConfig,
101
108
  !!config.managementApi,
102
109
  !!config.watch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "2.0.0-alpha.3",
3
+ "version": "2.0.0-alpha.5",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -26,6 +26,7 @@
26
26
  "express": "^4.18.3",
27
27
  "fast-jwt": "^4.0.0",
28
28
  "get-port": "^7.1.0",
29
+ "json-schema-to-typescript": "^15.0.0",
29
30
  "neostandard": "^0.11.1",
30
31
  "pino-abstract-transport": "^1.1.0",
31
32
  "split2": "^4.2.0",
@@ -33,11 +34,11 @@
33
34
  "typescript": "^5.5.4",
34
35
  "undici-oidc-interceptor": "^0.5.0",
35
36
  "why-is-node-running": "^2.2.2",
36
- "@platformatic/composer": "2.0.0-alpha.3",
37
- "@platformatic/service": "2.0.0-alpha.3",
38
- "@platformatic/sql-graphql": "2.0.0-alpha.3",
39
- "@platformatic/db": "2.0.0-alpha.3",
40
- "@platformatic/sql-mapper": "2.0.0-alpha.3"
37
+ "@platformatic/composer": "2.0.0-alpha.5",
38
+ "@platformatic/db": "2.0.0-alpha.5",
39
+ "@platformatic/sql-graphql": "2.0.0-alpha.5",
40
+ "@platformatic/sql-mapper": "2.0.0-alpha.5",
41
+ "@platformatic/service": "2.0.0-alpha.5"
41
42
  },
42
43
  "dependencies": {
43
44
  "@fastify/error": "^3.4.1",
@@ -45,7 +46,7 @@
45
46
  "@hapi/topo": "^6.0.2",
46
47
  "boring-name-generator": "^1.0.3",
47
48
  "change-case-all": "^2.1.0",
48
- "close-with-grace": "^1.3.0",
49
+ "close-with-grace": "^2.0.0",
49
50
  "commist": "^3.2.0",
50
51
  "debounce": "^2.0.0",
51
52
  "desm": "^1.3.1",
@@ -63,21 +64,22 @@
63
64
  "semgrator": "^0.3.0",
64
65
  "tail-file-stream": "^0.2.0",
65
66
  "undici": "^6.9.0",
66
- "undici-thread-interceptor": "^0.5.0",
67
+ "undici-thread-interceptor": "^0.5.1",
67
68
  "ws": "^8.16.0",
68
- "@platformatic/basic": "2.0.0-alpha.3",
69
- "@platformatic/itc": "2.0.0-alpha.3",
70
- "@platformatic/telemetry": "2.0.0-alpha.3",
71
- "@platformatic/generators": "2.0.0-alpha.3",
72
- "@platformatic/config": "2.0.0-alpha.3",
73
- "@platformatic/utils": "2.0.0-alpha.3",
74
- "@platformatic/ts-compiler": "2.0.0-alpha.3"
69
+ "@platformatic/basic": "2.0.0-alpha.5",
70
+ "@platformatic/itc": "2.0.0-alpha.5",
71
+ "@platformatic/generators": "2.0.0-alpha.5",
72
+ "@platformatic/telemetry": "2.0.0-alpha.5",
73
+ "@platformatic/ts-compiler": "2.0.0-alpha.5",
74
+ "@platformatic/utils": "2.0.0-alpha.5",
75
+ "@platformatic/config": "2.0.0-alpha.5"
75
76
  },
76
77
  "scripts": {
77
78
  "test": "npm run lint && borp --concurrency=1 --timeout=180000 && tsd",
78
79
  "coverage": "npm run lint && borp -X=fixtures -X=test -C --concurrency=1 --timeout=180000 && tsd",
79
- "lint": "eslint",
80
80
  "gen-schema": "node lib/schema.js > schema.json",
81
- "gen-types": "json2ts > config.d.ts < schema.json"
81
+ "gen-types": "json2ts > config.d.ts < schema.json",
82
+ "build": "pnpm run gen-schema && pnpm run gen-types",
83
+ "lint": "eslint"
82
84
  }
83
85
  }
package/runtime.mjs CHANGED
@@ -33,7 +33,7 @@ export async function run (argv) {
33
33
  })
34
34
 
35
35
  if (args.version) {
36
- console.log('v' + JSON.parse(await readFile(join(import.meta.url, 'package.json'))).version)
36
+ console.log('v' + JSON.parse(await readFile(join(import.meta.url, 'package.json'), 'utf-8')).version)
37
37
  process.exit(0)
38
38
  }
39
39
 
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.0.0-alpha.2.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.0.0-alpha.5.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "type": "object",
5
5
  "properties": {
@@ -766,9 +766,19 @@
766
766
  "type": "array",
767
767
  "items": {
768
768
  "type": "object",
769
- "required": [
770
- "id",
771
- "path"
769
+ "anyOf": [
770
+ {
771
+ "required": [
772
+ "id",
773
+ "path"
774
+ ]
775
+ },
776
+ {
777
+ "required": [
778
+ "id",
779
+ "url"
780
+ ]
781
+ }
772
782
  ],
773
783
  "properties": {
774
784
  "id": {
@@ -781,6 +791,9 @@
781
791
  "config": {
782
792
  "type": "string"
783
793
  },
794
+ "url": {
795
+ "type": "string"
796
+ },
784
797
  "useHttp": {
785
798
  "type": "boolean"
786
799
  }
@@ -1,44 +0,0 @@
1
- 'use strict'
2
-
3
- const { Writable } = require('node:stream')
4
-
5
- class MessagePortWritable extends Writable {
6
- #port
7
- #metadata
8
-
9
- constructor (options) {
10
- const { port, metadata, ...opts } = options
11
-
12
- super({ ...opts, decodeStrings: false })
13
- this.#port = port
14
- this.#metadata = metadata
15
- }
16
-
17
- // Since this is only invoked by pino, we only receive strings
18
- _write (chunk, encoding, callback) {
19
- this.#port.postMessage({ metadata: this.#metadata, logs: [chunk.toString(encoding ?? 'utf-8')] })
20
-
21
- // Important: do not remove nextTick otherwise _writev will never be used
22
- process.nextTick(callback)
23
- }
24
-
25
- // Since this is only invoked by pino, we only receive strings
26
- _writev (chunks, callback) {
27
- this.#port.postMessage({ metadata: this.#metadata, logs: chunks.map(c => c.chunk.toString(c.encoding ?? 'utf-8')) })
28
-
29
- // Important: do not remove nextTick otherwise _writev will never be used
30
- process.nextTick(callback)
31
- }
32
-
33
- _final (callback) {
34
- this.#port.close()
35
- callback()
36
- }
37
-
38
- _destroy (err, callback) {
39
- this.#port.close()
40
- callback(err)
41
- }
42
- }
43
-
44
- module.exports = { MessagePortWritable }
@@ -1,30 +0,0 @@
1
- 'use strict'
2
-
3
- const { Writable } = require('node:stream')
4
- const inspect = require('node:util')
5
-
6
- class PinoWritable extends Writable {
7
- #write
8
-
9
- constructor (options) {
10
- const { pino, level, ...opts } = options
11
-
12
- super({ ...opts, decodeStrings: false })
13
- this.#write = pino[level].bind(pino)
14
- }
15
-
16
- _write (chunk, encoding, callback) {
17
- this.#write({ raw: encoding === 'buffer' ? inspect(chunk) : chunk.toString(encoding ?? 'utf-8') })
18
- callback()
19
- }
20
-
21
- // We don't define _writev as we have to serialize messages one by one so batching wouldn't make any sense.
22
- }
23
-
24
- function createPinoWritable (pino, level) {
25
- const writable = new PinoWritable({ pino, level })
26
- writable.write = writable.write.bind(writable)
27
- return writable
28
- }
29
-
30
- module.exports = { PinoWritable, createPinoWritable }