@platformatic/runtime 3.55.0 → 3.56.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.
package/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { FastifyError } from '@fastify/error'
2
2
  import { Configuration, ConfigurationOptions, logFatalError, parseArgs } from '@platformatic/foundation'
3
3
  import { BaseGenerator } from '@platformatic/generators'
4
+ import { PlatformaticGlobal } from '@platformatic/globals'
4
5
  import { JSONSchemaType } from 'ajv'
5
6
  import * as colorette from 'colorette'
6
7
  import { Logger } from 'pino'
@@ -27,6 +28,12 @@ export interface ApplicationsCommands {
27
28
  help: Record<string, string | (() => string)>
28
29
  }
29
30
 
31
+ export interface LoopbackMessagingOptions {
32
+ logger?: Logger
33
+ mount?: boolean
34
+ runtimeConfig?: { messagingTimeout?: number; [key: string]: unknown }
35
+ }
36
+
30
37
  export namespace errors {
31
38
  export const RuntimeExitedError: () => FastifyError
32
39
  export const UnknownRuntimeAPICommandError: (command: string) => FastifyError
@@ -61,7 +68,6 @@ export namespace symbols {
61
68
  export const kWorkerStatus: unique symbol
62
69
  export const kWorkerHealthSignals: unique symbol
63
70
  export const kStderrMarker: string
64
- export const kInterceptors: unique symbol
65
71
  export const kWorkersBroadcast: unique symbol
66
72
  }
67
73
 
@@ -191,3 +197,8 @@ export declare function transform (
191
197
  ): Promise<RuntimeConfiguration>
192
198
 
193
199
  export declare function loadApplicationsCommands (): Promise<ApplicationsCommands>
200
+
201
+ export declare function setupLoopbackMessaging (
202
+ targetId: string,
203
+ options?: LoopbackMessagingOptions
204
+ ): PlatformaticGlobal['messaging'] & { unmount: () => void }
package/index.js CHANGED
@@ -176,6 +176,7 @@ export async function create (configOrRoot, sourceOrConfig, context) {
176
176
  export { prepareApplication, transform, wrapInRuntimeConfig } from './lib/config.js'
177
177
  export * as errors from './lib/errors.js'
178
178
  export { RuntimeGenerator as Generator, WrappedGenerator } from './lib/generator.js'
179
+ export { setupLoopbackMessaging } from './lib/loopback-messaging.js'
179
180
  export { Runtime } from './lib/runtime.js'
180
181
  export { schema } from './lib/schema.js'
181
182
  export * from './lib/version.js'
package/lib/config.js CHANGED
@@ -18,8 +18,7 @@ import {
18
18
  InspectorHostError,
19
19
  InspectorPortError,
20
20
  InvalidArgumentError,
21
- InvalidEntrypointError,
22
- MissingEntrypointError
21
+ InvalidEntrypointError
23
22
  } from './errors.js'
24
23
  import { schema } from './schema.js'
25
24
  import { upgrade } from './upgrade.js'
@@ -438,15 +437,8 @@ export async function transform (config, _, context) {
438
437
  }
439
438
  }
440
439
 
441
- if (!hasValidEntrypoint && !context.allowMissingEntrypoint) {
442
- if (config.entrypoint) {
443
- throw new InvalidEntrypointError(config.entrypoint)
444
- } else if (applications.length >= 1) {
445
- throw new MissingEntrypointError()
446
- }
447
- // If there are no applications, and no entrypoint it's an empty app.
448
- // It won't start, but we should be able to parse and operate on it,
449
- // like adding other applications.
440
+ if (!hasValidEntrypoint && config.entrypoint && !context.allowMissingEntrypoint) {
441
+ throw new InvalidEntrypointError(config.entrypoint)
450
442
  }
451
443
 
452
444
  if (typeof config.metrics === 'boolean') {
package/lib/errors.js CHANGED
@@ -49,6 +49,10 @@ export const ApplicationStartTimeoutError = createError(
49
49
  `${ERROR_PREFIX}_APPLICATION_START_TIMEOUT`,
50
50
  "Application with id '%s' failed to start in %dms."
51
51
  )
52
+ export const ApplicationDependencyNotFoundError = createError(
53
+ `${ERROR_PREFIX}_APPLICATION_DEPENDENCY_NOT_FOUND`,
54
+ 'Application dependency %s not found. Available applications are: %s'
55
+ )
52
56
  export const ApplicationsDependenciesCycleError = createError(
53
57
  `${ERROR_PREFIX}_APPLICATIONS_DEPENDENCIES_CYCLE`,
54
58
  'Detected a cycle in the applications dependencies: %s'
package/lib/generator.js CHANGED
@@ -19,17 +19,6 @@ import { transform } from './config.js'
19
19
  import { schema } from './schema.js'
20
20
  import { getArrayDifference } from './utils.js'
21
21
 
22
- const wrappableProperties = {
23
- logger: {
24
- level: '{PLT_SERVER_LOGGER_LEVEL}'
25
- },
26
- server: {
27
- hostname: '{PLT_SERVER_HOSTNAME}',
28
- port: '{PORT}'
29
- },
30
- managementApi: '{PLT_MANAGEMENT_API}'
31
- }
32
-
33
22
  const engines = {
34
23
  node: '>=22.19.0'
35
24
  }
@@ -42,13 +31,36 @@ const NoApplicationNamedError = createError(
42
31
  )
43
32
  const NoEntryPointError = createError(`${ERROR_PREFIX}_NO_ENTRYPOINT`, 'No entrypoint had been defined.')
44
33
 
34
+ function getRuntimeWrappableProperties (skipServer) {
35
+ const wrappableProperties = {
36
+ logger: {
37
+ level: '{PLT_SERVER_LOGGER_LEVEL}'
38
+ },
39
+ managementApi: '{PLT_MANAGEMENT_API}'
40
+ }
41
+
42
+ if (!skipServer) {
43
+ wrappableProperties.server = {
44
+ hostname: '{PLT_SERVER_HOSTNAME}',
45
+ port: '{PORT}'
46
+ }
47
+ }
48
+
49
+ return wrappableProperties
50
+ }
51
+
45
52
  function getRuntimeBaseEnvVars (config) {
46
- return {
47
- PLT_SERVER_HOSTNAME: '127.0.0.1',
48
- PORT: config.port || 3042,
49
- PLT_SERVER_LOGGER_LEVEL: config.logLevel || 'info',
50
- PLT_MANAGEMENT_API: true
53
+ const env = {}
54
+
55
+ if (!config.skipServer) {
56
+ env.PLT_SERVER_HOSTNAME = '127.0.0.1'
57
+ env.PORT = config.port || 3042
51
58
  }
59
+
60
+ env.PLT_SERVER_LOGGER_LEVEL = config.logLevel || 'info'
61
+ env.PLT_MANAGEMENT_API = true
62
+
63
+ return env
52
64
  }
53
65
 
54
66
  export class RuntimeGenerator extends BaseGenerator {
@@ -195,7 +207,7 @@ export class RuntimeGenerator extends BaseGenerator {
195
207
  path: this.config.autoload || this.applicationsFolder,
196
208
  exclude: ['docs']
197
209
  },
198
- ...wrappableProperties
210
+ ...getRuntimeWrappableProperties(this.config.skipServer)
199
211
  }
200
212
 
201
213
  return config
@@ -251,13 +263,15 @@ export class RuntimeGenerator extends BaseGenerator {
251
263
  return
252
264
  }
253
265
 
254
- // port
255
- this.questions.push({
256
- type: 'input',
257
- name: 'port',
258
- default: 3042,
259
- message: 'What port do you want to use?'
260
- })
266
+ if (!this.config.skipServer) {
267
+ // port
268
+ this.questions.push({
269
+ type: 'input',
270
+ name: 'port',
271
+ default: 3042,
272
+ message: 'What port do you want to use?'
273
+ })
274
+ }
261
275
  }
262
276
 
263
277
  setApplicationsDirectory () {
@@ -611,7 +625,7 @@ export class WrappedGenerator extends BaseGenerator {
611
625
  async #createConfigFile () {
612
626
  const config = {
613
627
  $schema: `https://schemas.platformatic.dev/${this.module}/${this.platformaticVersion}.json`,
614
- runtime: wrappableProperties
628
+ runtime: getRuntimeWrappableProperties(this.config.skipServer)
615
629
  }
616
630
 
617
631
  this.addFile({
@@ -0,0 +1,78 @@
1
+ import { abstractLogger } from '@platformatic/foundation'
2
+ import { getMessaging, removeGlobals, updateGlobals } from '@platformatic/globals'
3
+ import { MessageChannel } from 'node:worker_threads'
4
+ import { MessagingITC } from './worker/messaging.js'
5
+
6
+ class LoopbackMessagingITC extends MessagingITC {
7
+ #application
8
+ #channel
9
+
10
+ constructor (id, application, runtimeConfig, logger, channel) {
11
+ super(id, runtimeConfig, logger)
12
+
13
+ this.#application = application
14
+ this.#channel = channel
15
+ this.addSource(channel)
16
+ }
17
+
18
+ // Force the next worker to be the channel
19
+ _getNextWorker () {
20
+ return { application: this.#application, channel: this.#channel }
21
+ }
22
+ }
23
+
24
+ class LoopbackMessaging extends LoopbackMessagingITC {
25
+ #target
26
+ #channel
27
+ #previousGlobals
28
+
29
+ constructor (targetId, config, logger) {
30
+ const channel = new MessageChannel()
31
+
32
+ super('loopback-messaging', targetId, config, logger, channel.port1)
33
+ this.#channel = channel
34
+ this.#target = new LoopbackMessagingITC(targetId, 'loopback-messaging', config, logger, channel.port2)
35
+ }
36
+
37
+ mount () {
38
+ this.#previousGlobals = getMessaging({ throwOnMissing: false })
39
+
40
+ updateGlobals({
41
+ messaging: {
42
+ handle: this.#target.handle.bind(this.#target),
43
+ send: this.#target.send.bind(this.#target),
44
+ notify: this.#target.notify.bind(this.#target)
45
+ }
46
+ })
47
+
48
+ return this
49
+ }
50
+
51
+ unmount () {
52
+ if (this.#previousGlobals) {
53
+ updateGlobals({ messaging: this.#previousGlobals })
54
+ } else {
55
+ removeGlobals(['messaging'])
56
+ }
57
+
58
+ this.close()
59
+ this.#target.close()
60
+ this.#channel.port1.close()
61
+ this.#channel.port2.close()
62
+ }
63
+ }
64
+
65
+ export function setupLoopbackMessaging (targetId, options) {
66
+ options ??= {}
67
+
68
+ const runtimeConfig = { ...(options.runtimeConfig ?? {}) }
69
+ runtimeConfig.messagingTimeout ??= 5000
70
+
71
+ const messaging = new LoopbackMessaging(targetId, runtimeConfig, options.logger ?? abstractLogger)
72
+
73
+ if (options.mount !== false) {
74
+ messaging.mount()
75
+ }
76
+
77
+ return messaging
78
+ }
@@ -9,7 +9,7 @@ import {
9
9
  } from '@platformatic/foundation'
10
10
  import fastify from 'fastify'
11
11
  import { platform, tmpdir } from 'node:os'
12
- import { join } from 'node:path'
12
+ import { dirname, join } from 'node:path'
13
13
  import { setTimeout as sleep } from 'node:timers/promises'
14
14
  import { createWebSocketStream } from 'ws'
15
15
  import { prepareApplication } from './config.js'
@@ -342,8 +342,8 @@ export async function startManagementApi (runtime, config) {
342
342
  const customSocket = typeof config === 'object' ? config?.socket : null
343
343
 
344
344
  const runtimePIDDir = join(PLATFORMATIC_TMP_DIR, runtimePID.toString())
345
- if (platform() !== 'win32' && !customSocket) {
346
- await createDirectory(runtimePIDDir, true)
345
+ if (platform() !== 'win32') {
346
+ await createDirectory(customSocket ? dirname(customSocket) : runtimePIDDir, typeof customSocket !== 'string')
347
347
  }
348
348
 
349
349
  let socketPath = null
package/lib/runtime.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  kTimeout,
10
10
  parseMemorySize
11
11
  } from '@platformatic/foundation'
12
+ import { getExecutable } from '@platformatic/globals'
12
13
  import { ITC } from '@platformatic/itc'
13
14
  import { collectProcessMetrics, client as metricsClient } from '@platformatic/metrics'
14
15
  import fastify from 'fastify'
@@ -36,7 +37,6 @@ import {
36
37
  CannotRemoveEntrypointError,
37
38
  InvalidArgumentError,
38
39
  MessagingError,
39
- MissingEntrypointError,
40
40
  MissingPprofCapture,
41
41
  RuntimeAbortedError,
42
42
  WorkerInterceptorJoinTimeoutError,
@@ -274,14 +274,18 @@ export class Runtime extends EventEmitter {
274
274
  await this.init()
275
275
  }
276
276
 
277
- if (typeof this.#config.entrypoint === 'undefined') {
278
- throw new MissingEntrypointError()
279
- }
280
277
  this.#updateStatus('starting')
281
278
  this.#createWorkersBroadcastChannel()
282
279
 
283
280
  try {
284
- await this.startApplications(this.getApplicationsIds(), silent)
281
+ const applications = this.getApplicationsIds()
282
+ await this.startApplications(applications, silent)
283
+
284
+ if (applications.length === 0) {
285
+ this.#updateStatus('started')
286
+ await this.close(silent)
287
+ return
288
+ }
285
289
 
286
290
  if (this.#config.inspectorOptions) {
287
291
  const { port } = this.#config.inspectorOptions
@@ -327,7 +331,9 @@ export class Runtime extends EventEmitter {
327
331
  this.#startHealthMetricsCollectionIfNeeded()
328
332
 
329
333
  await this.#dynamicWorkersScaler?.start()
330
- this.#showUrl()
334
+ if (this.#url) {
335
+ this.#showUrl()
336
+ }
331
337
  return this.#url
332
338
  }
333
339
 
@@ -582,7 +588,7 @@ export class Runtime extends EventEmitter {
582
588
 
583
589
  // Now, topological sort the applications based on their dependencies.
584
590
  // If circular dependencies are detected, an error with proper error code is thrown.
585
- applications = topologicalSort(dependencies)
591
+ applications = topologicalSort(applications, dependencies)
586
592
 
587
593
  // Group into dependency levels so that each level's dependencies are all
588
594
  // in previous levels. Levels are started sequentially, but applications
@@ -1122,6 +1128,10 @@ export class Runtime extends EventEmitter {
1122
1128
  }
1123
1129
 
1124
1130
  async getEntrypointDetails () {
1131
+ if (!this.#entrypointId) {
1132
+ return null
1133
+ }
1134
+
1125
1135
  return this.getApplicationDetails(this.#entrypointId)
1126
1136
  }
1127
1137
 
@@ -1168,6 +1178,10 @@ export class Runtime extends EventEmitter {
1168
1178
  }
1169
1179
 
1170
1180
  async getMetrics (format = 'json') {
1181
+ if (this.#config.metrics === false || this.#config.metrics?.enabled === false) {
1182
+ throw new Error('Metrics are disabled')
1183
+ }
1184
+
1171
1185
  let metrics = null
1172
1186
 
1173
1187
  const applicationRestartMetrics = format === 'json'
@@ -1716,6 +1730,10 @@ export class Runtime extends EventEmitter {
1716
1730
  }
1717
1731
 
1718
1732
  #showUrl () {
1733
+ if (!this.#url) {
1734
+ return
1735
+ }
1736
+
1719
1737
  this.logger.info(`Platformatic is now listening at ${this.#url}`)
1720
1738
  }
1721
1739
 
@@ -1734,7 +1752,7 @@ export class Runtime extends EventEmitter {
1734
1752
  applicationConfig.path = join(this.#root, config.resolvedApplicationsBasePath, id)
1735
1753
 
1736
1754
  if (!existsSync(applicationConfig.path)) {
1737
- const executable = globalThis.platformatic?.executable ?? 'platformatic'
1755
+ const executable = getExecutable() ?? 'platformatic'
1738
1756
  this.logger.error(
1739
1757
  `The path for application "%s" does not exist. Please run "${executable} resolve" and try again.`,
1740
1758
  id
@@ -2713,6 +2731,12 @@ export class Runtime extends EventEmitter {
2713
2731
 
2714
2732
  async #createWorkersBroadcastChannel () {
2715
2733
  this.#workersBroadcastChannel?.close()
2734
+
2735
+ if (this.#config.applications.length === 0) {
2736
+ this.#workersBroadcastChannel = undefined
2737
+ return
2738
+ }
2739
+
2716
2740
  this.#workersBroadcastChannel = new BroadcastChannel(kWorkersBroadcast)
2717
2741
  }
2718
2742
 
package/lib/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import { tmpdir } from 'node:os'
3
3
  import { join } from 'node:path'
4
- import { ApplicationsDependenciesCycleError } from './errors.js'
4
+ import { ApplicationDependencyNotFoundError, ApplicationsDependenciesCycleError } from './errors.js'
5
5
 
6
6
  export function getArrayDifference (a, b) {
7
7
  return a.filter(element => {
@@ -48,13 +48,22 @@ export function topologicalLevels (sorted, graph) {
48
48
  return levels
49
49
  }
50
50
 
51
- // Graph: Map<string, string[]>
52
- export function topologicalSort (graph) {
51
+ // valid: string[] | Map<string, string[]>, graph?: Map<string, string[]>
52
+ export function topologicalSort (valid, graph) {
53
+ if (!graph) {
54
+ graph = valid
55
+ valid = Array.from(graph.keys())
56
+ }
57
+
53
58
  const result = []
54
59
  const visited = new Set()
55
60
  const path = []
56
61
 
57
62
  function visit (node) {
63
+ if (!valid.includes(node)) {
64
+ throw new ApplicationDependencyNotFoundError(node, valid.join(', '))
65
+ }
66
+
58
67
  if (visited.has(node)) {
59
68
  return
60
69
  }
@@ -8,6 +8,16 @@ import {
8
8
  loadConfigurationModule,
9
9
  mirrorGlobalDispatcherForBuiltinFetch
10
10
  } from '@platformatic/foundation'
11
+ import {
12
+ getLogger,
13
+ getOnActiveResourcesEventLoop,
14
+ getOnHttpStatsConnected,
15
+ getOnHttpStatsFree,
16
+ getOnHttpStatsPending,
17
+ getOnHttpStatsQueued,
18
+ getOnHttpStatsRunning,
19
+ getOnHttpStatsSize
20
+ } from '@platformatic/globals'
11
21
  import debounce from 'debounce'
12
22
  import { EventEmitter } from 'node:events'
13
23
  import { existsSync } from 'node:fs'
@@ -17,6 +27,7 @@ import { workerData } from 'node:worker_threads'
17
27
  import { getGlobalDispatcher, setGlobalDispatcher } from 'undici'
18
28
  import { ApplicationAlreadyStartedError, RuntimeNotStartedError } from '../errors.js'
19
29
  import { getApplicationUrl } from '../utils.js'
30
+ import { markAsPlatformaticDispatcher, refreshGlobalDispatcher } from './interceptors.js'
20
31
 
21
32
  function fetchApplicationUrl (application, key) {
22
33
  if (!key.endsWith('_URL') || !application.id) {
@@ -29,7 +40,8 @@ function fetchApplicationUrl (application, key) {
29
40
  function handleUnhandled (app, type, err) {
30
41
  const label = `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
31
42
 
32
- globalThis.platformatic.logger.error({ err: ensureLoggableError(err) }, `The ${label} threw an ${type}.`)
43
+ const logger = getLogger()
44
+ logger.error({ err: ensureLoggableError(err) }, `The ${label} threw an ${type}.`)
33
45
 
34
46
  executeWithTimeout(app?.stop(), 1000)
35
47
  .catch()
@@ -142,7 +154,8 @@ export class Controller extends EventEmitter {
142
154
  }
143
155
  } catch (err) {
144
156
  if (err.validationErrors) {
145
- globalThis.platformatic.logger.error(
157
+ const logger = getLogger()
158
+ logger.error(
146
159
  { err: ensureLoggableError(err) },
147
160
  'The application threw a validation error.'
148
161
  )
@@ -193,6 +206,9 @@ export class Controller extends EventEmitter {
193
206
 
194
207
  try {
195
208
  await this.capability.start({ listen })
209
+ if (refreshGlobalDispatcher()) {
210
+ this.#updateDispatcher()
211
+ }
196
212
  this.#listening = listen
197
213
  /* c8 ignore next 5 */
198
214
  } catch (err) {
@@ -244,18 +260,29 @@ export class Controller extends EventEmitter {
244
260
 
245
261
  async getMetrics ({ format }) {
246
262
  const dispatcher = getGlobalDispatcher()
247
- if (globalThis.platformatic?.onHttpStatsFree && dispatcher?.stats) {
263
+ const onHttpStatsFree = getOnHttpStatsFree({ throwOnMissing: false })
264
+
265
+ if (onHttpStatsFree && dispatcher?.stats) {
266
+ const onHttpStatsConnected = getOnHttpStatsConnected()
267
+ const onHttpStatsPending = getOnHttpStatsPending()
268
+ const onHttpStatsQueued = getOnHttpStatsQueued()
269
+ const onHttpStatsRunning = getOnHttpStatsRunning()
270
+ const onHttpStatsSize = getOnHttpStatsSize()
271
+
248
272
  for (const url in dispatcher.stats) {
249
273
  const { free, connected, pending, queued, running, size } = dispatcher.stats[url]
250
- globalThis.platformatic.onHttpStatsFree(url, free || 0)
251
- globalThis.platformatic.onHttpStatsConnected(url, connected || 0)
252
- globalThis.platformatic.onHttpStatsPending(url, pending || 0)
253
- globalThis.platformatic.onHttpStatsQueued(url, queued || 0)
254
- globalThis.platformatic.onHttpStatsRunning(url, running || 0)
255
- globalThis.platformatic.onHttpStatsSize(url, size || 0)
274
+ onHttpStatsFree(url, free || 0)
275
+ onHttpStatsConnected(url, connected || 0)
276
+ onHttpStatsPending(url, pending || 0)
277
+ onHttpStatsQueued(url, queued || 0)
278
+ onHttpStatsRunning(url, running || 0)
279
+ onHttpStatsSize(url, size || 0)
256
280
  }
257
281
  }
258
- globalThis.platformatic.onActiveResourcesEventLoop(getActiveResourcesInfo().length)
282
+ const onActiveResourcesEventLoop = getOnActiveResourcesEventLoop({ throwOnMissing: false })
283
+ if (onActiveResourcesEventLoop) {
284
+ onActiveResourcesEventLoop(getActiveResourcesInfo().length)
285
+ }
259
286
  return this.capability.getMetrics({ format })
260
287
  }
261
288
 
@@ -300,7 +327,8 @@ export class Controller extends EventEmitter {
300
327
  }
301
328
 
302
329
  #logAndThrow (err) {
303
- globalThis.platformatic.logger.error(
330
+ const logger = getLogger()
331
+ logger.error(
304
332
  { err: ensureLoggableError(err) },
305
333
  err[kHandledError] ? err.message : 'The application threw an error.'
306
334
  )
@@ -326,6 +354,7 @@ export class Controller extends EventEmitter {
326
354
 
327
355
  const dispatcher = getGlobalDispatcher().compose(interceptor)
328
356
 
357
+ markAsPlatformaticDispatcher(dispatcher)
329
358
  setGlobalDispatcher(dispatcher)
330
359
  mirrorGlobalDispatcherForBuiltinFetch(dispatcher)
331
360
  }
@@ -336,7 +365,8 @@ export class Controller extends EventEmitter {
336
365
 
337
366
  process.on('newListener', event => {
338
367
  if (event === 'uncaughtException' || event === 'unhandledRejection') {
339
- globalThis.platformatic.logger.warn(
368
+ const logger = getLogger()
369
+ logger.warn(
340
370
  `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.`
341
371
  )
342
372
  }
@@ -1,3 +1,4 @@
1
+ import { getITC, updateGlobals } from '@platformatic/globals'
1
2
  import {
2
3
  HealthSignalMustBeObjectError,
3
4
  HealthSignalTypeMustBeStringError
@@ -60,7 +61,8 @@ export function initHealthSignalsApi (options = {}) {
60
61
  isSending = false
61
62
  try {
62
63
  const signals = queue.getAll()
63
- await globalThis.platformatic.itc.send('sendHealthSignals', {
64
+ const itc = getITC()
65
+ await itc.send('sendHealthSignals', {
64
66
  workerId,
65
67
  signals
66
68
  })
@@ -76,5 +78,5 @@ export function initHealthSignalsApi (options = {}) {
76
78
  return promise
77
79
  }
78
80
 
79
- globalThis.platformatic.sendHealthSignal = sendHealthSignal
81
+ updateGlobals({ sendHealthSignal })
80
82
  }
@@ -1,7 +1,7 @@
1
+ import { getClientSpansAls, getITC } from '@platformatic/globals'
1
2
  import { randomUUID } from 'node:crypto'
2
3
  import { Readable, Writable } from 'node:stream'
3
4
  import { interceptors } from 'undici'
4
- import { kITC } from './symbols.js'
5
5
 
6
6
  const kCacheIdHeader = Symbol('cacheIdHeader')
7
7
  const CACHE_ID_HEADER = 'x-plt-http-cache-id'
@@ -28,7 +28,7 @@ export class RemoteCacheStore {
28
28
  this.#logger.error(err, 'Error in onRequest http cache hook')
29
29
  }
30
30
 
31
- const itc = globalThis[kITC]
31
+ const itc = getITC()
32
32
  if (!itc) return
33
33
 
34
34
  const sanitizedRequest = this.#sanitizeRequest(request)
@@ -68,7 +68,7 @@ export class RemoteCacheStore {
68
68
  value.headers = { ...value.headers, [CACHE_ID_HEADER]: cacheEntryId }
69
69
  }
70
70
 
71
- const itc = globalThis[kITC]
71
+ const itc = getITC()
72
72
  if (!itc) throw new Error('Cannot write to cache without an ITC instance')
73
73
 
74
74
  const acc = []
@@ -96,7 +96,7 @@ export class RemoteCacheStore {
96
96
  }
97
97
 
98
98
  delete (request) {
99
- const itc = globalThis[kITC]
99
+ const itc = getITC()
100
100
  if (!itc) throw new Error('Cannot delete from cache without an ITC instance')
101
101
 
102
102
  request = this.#sanitizeRequest(request)
@@ -120,7 +120,7 @@ export function httpCacheInterceptor (interceptorOpts) {
120
120
 
121
121
  // AsyncLocalStorage that contains a client http request span
122
122
  // Exists only when the nodejs capability telemetry is enabled
123
- const clientSpansAls = globalThis.platformatic.clientSpansAls
123
+ const clientSpansAls = getClientSpansAls({ throwOnMissing: false })
124
124
 
125
125
  return originalDispatch => {
126
126
  const dispatch = (opts, handler) => {
@@ -1,14 +1,46 @@
1
- import { mirrorGlobalDispatcherForBuiltinFetch } from '@platformatic/foundation'
1
+ import { getGlobalDispatcherFromKnownUndiciSymbols, mirrorGlobalDispatcherForBuiltinFetch } from '@platformatic/foundation'
2
+ import {
3
+ getInterceptors,
4
+ getLogger,
5
+ getOnHttpCacheHit,
6
+ getOnHttpCacheMiss,
7
+ getOnHttpCacheRequest
8
+ } from '@platformatic/globals'
2
9
  import { createTelemetryThreadInterceptorHooks } from '@platformatic/telemetry'
3
10
  import { createRequire } from 'node:module'
4
11
  import { join } from 'node:path'
5
12
  import { pathToFileURL } from 'node:url'
6
13
  import { parentPort, workerData } from 'node:worker_threads'
7
- import { Agent, Client, Pool, setGlobalDispatcher } from 'undici'
14
+ import { Agent, Client, Pool } from 'undici'
8
15
  import { wire } from 'undici-thread-interceptor'
9
16
  import { createChannelCreationHook } from '../policies.js'
10
17
  import { RemoteCacheStore, httpCacheInterceptor } from './http-cache.js'
11
- import { kInterceptors } from './symbols.js'
18
+
19
+ const kPlatformaticGlobalDispatcher = Symbol.for('platformatic.undici.globalDispatcher')
20
+
21
+ let composeRuntimeDispatcher
22
+
23
+ export function markAsPlatformaticDispatcher (dispatcher) {
24
+ Object.defineProperty(dispatcher, kPlatformaticGlobalDispatcher, {
25
+ value: true,
26
+ enumerable: false,
27
+ configurable: true
28
+ })
29
+ }
30
+
31
+ export function refreshGlobalDispatcher () {
32
+ if (!composeRuntimeDispatcher) {
33
+ return false
34
+ }
35
+
36
+ const dispatcher = getGlobalDispatcherFromKnownUndiciSymbols()
37
+ if (!dispatcher || dispatcher[kPlatformaticGlobalDispatcher]) {
38
+ return false
39
+ }
40
+
41
+ composeRuntimeDispatcher(dispatcher)
42
+ return true
43
+ }
12
44
 
13
45
  export async function setDispatcher (runtimeConfig) {
14
46
  const threadDispatcher = createThreadInterceptor(runtimeConfig)
@@ -28,17 +60,39 @@ export async function setDispatcher (runtimeConfig) {
28
60
 
29
61
  const dispatcherOpts = await getDispatcherOpts(runtimeConfig.undici)
30
62
 
31
- const dispatcher = new Agent(dispatcherOpts).compose(
32
- [threadInterceptor, ...userInterceptors, cacheInterceptor].filter(Boolean)
33
- )
34
- setGlobalDispatcher(dispatcher)
35
- mirrorGlobalDispatcherForBuiltinFetch(dispatcher)
63
+ function installDispatcher (baseDispatcher) {
64
+ const dispatcher = baseDispatcher.compose(
65
+ [threadInterceptor, ...userInterceptors, cacheInterceptor].filter(Boolean)
66
+ )
67
+
68
+ markAsPlatformaticDispatcher(dispatcher)
69
+ mirrorGlobalDispatcherForBuiltinFetch(dispatcher, createLegacyDispatcher(dispatcher))
70
+ return dispatcher
71
+ }
72
+
73
+ composeRuntimeDispatcher = installDispatcher
74
+ installDispatcher(new Agent(dispatcherOpts))
36
75
 
37
76
  return { threadDispatcher }
38
77
  }
39
78
 
79
+ function createLegacyDispatcher (dispatcher) {
80
+ const legacyDispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]
81
+ const currentDispatcher = globalThis[Symbol.for('undici.globalDispatcher.2')]
82
+
83
+ if (legacyDispatcher && legacyDispatcher !== currentDispatcher && legacyDispatcher.constructor?.name === 'Dispatcher1Wrapper') {
84
+ try {
85
+ return new legacyDispatcher.constructor(dispatcher)
86
+ } catch {
87
+ return dispatcher
88
+ }
89
+ }
90
+
91
+ return dispatcher
92
+ }
93
+
40
94
  export async function updateUndiciInterceptors (undiciConfig) {
41
- const updatableInterceptors = globalThis[kInterceptors]
95
+ const updatableInterceptors = getInterceptors()
42
96
  if (!updatableInterceptors) return
43
97
 
44
98
  if (Array.isArray(undiciConfig?.interceptors)) {
@@ -98,11 +152,7 @@ async function loadInterceptors (_require, interceptorsConfigs, key) {
98
152
  }
99
153
 
100
154
  async function loadInterceptor (_require, interceptorConfig, key) {
101
- let updatableInterceptors = globalThis[kInterceptors]
102
- if (!updatableInterceptors) {
103
- updatableInterceptors = {}
104
- globalThis[kInterceptors] = updatableInterceptors
105
- }
155
+ const updatableInterceptors = getInterceptors()
106
156
 
107
157
  const { module, options } = interceptorConfig
108
158
 
@@ -199,24 +249,34 @@ function parseOrigins (origins) {
199
249
 
200
250
  function createHttpCacheInterceptor (runtimeConfig) {
201
251
  const httpCache = runtimeConfig.httpCache
252
+
202
253
  const cacheInterceptor = httpCacheInterceptor({
203
254
  store: new RemoteCacheStore({
204
255
  onRequest: opts => {
205
- globalThis.platformatic?.onHttpCacheRequest?.(opts)
256
+ const onHttpCacheRequest = getOnHttpCacheRequest({ throwOnMissing: false })
257
+ if (onHttpCacheRequest) {
258
+ onHttpCacheRequest(opts)
259
+ }
206
260
  },
207
261
  onCacheHit: opts => {
208
- globalThis.platformatic?.onHttpCacheHit?.(opts)
262
+ const onHttpCacheHit = getOnHttpCacheHit({ throwOnMissing: false })
263
+ if (onHttpCacheHit) {
264
+ onHttpCacheHit(opts)
265
+ }
209
266
  },
210
267
  onCacheMiss: opts => {
211
- globalThis.platformatic?.onHttpCacheMiss?.(opts)
268
+ const onHttpCacheMiss = getOnHttpCacheMiss({ throwOnMissing: false })
269
+ if (onHttpCacheMiss) {
270
+ onHttpCacheMiss(opts)
271
+ }
212
272
  },
213
- logger: globalThis.platformatic.logger
273
+ logger: getLogger()
214
274
  }),
215
275
  methods: httpCache.methods ?? ['GET', 'HEAD'],
216
276
  origins: parseOrigins(httpCache.origins),
217
277
  cacheByDefault: httpCache.cacheByDefault,
218
278
  type: httpCache.type,
219
- logger: globalThis.platformatic.logger
279
+ logger: getLogger()
220
280
  })
221
281
  return cacheInterceptor
222
282
  }
package/lib/worker/itc.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ensureLoggableError, executeInParallel, executeWithTimeout, kTimeout } from '@platformatic/foundation'
2
+ import { getEvents, getLogger, getMessaging, updateGlobals } from '@platformatic/globals'
2
3
  import { initializeITCTelemetry, ITC } from '@platformatic/itc'
3
4
  import { Unpromise } from '@watchable/unpromise'
4
5
  import { once } from 'node:events'
@@ -150,10 +151,10 @@ export async function waitEventFromITC (worker, event) {
150
151
  export async function setupITC (controller, application, dispatcher, sharedContext) {
151
152
  await initializeITCTelemetry()
152
153
 
153
- const logger = globalThis.platformatic.logger
154
+ const logger = getLogger()
154
155
  const messaging = new MessagingITC(controller.applicationConfig.id, workerData.config, logger)
155
156
 
156
- Object.assign(globalThis.platformatic ?? {}, {
157
+ updateGlobals({
157
158
  messaging: {
158
159
  handle: messaging.handle.bind(messaging),
159
160
  send: messaging.send.bind(messaging),
@@ -172,7 +173,8 @@ export async function setupITC (controller, application, dispatcher, sharedConte
172
173
  await once(controller, 'start')
173
174
  } else {
174
175
  // This gives a chance to a capability to perform custom logic
175
- globalThis.platformatic.events.emit('start')
176
+ const events = getEvents()
177
+ events.emit('start')
176
178
 
177
179
  try {
178
180
  await controller.start()
@@ -205,7 +207,8 @@ export async function setupITC (controller, application, dispatcher, sharedConte
205
207
 
206
208
  if (force || status.startsWith('start')) {
207
209
  // This gives a chance to a capability to perform custom logic
208
- globalThis.platformatic.events.emit('stop')
210
+ const events = getEvents()
211
+ events.emit('stop')
209
212
 
210
213
  await controller.stop(force, dependents)
211
214
  }
@@ -419,9 +422,16 @@ export async function setupITC (controller, application, dispatcher, sharedConte
419
422
  // For service-based capabilities, expose the Fastify app
420
423
  replServer.context.app = controller.capability?.getApplication?.()
421
424
  replServer.context.capability = controller.capability
422
- replServer.context.platformatic = globalThis.platformatic
425
+ replServer.context.platformatic = {
426
+ capability: controller.capability,
427
+ config: controller.config,
428
+ events: getEvents(),
429
+ itc,
430
+ logger: getLogger(),
431
+ messaging: getMessaging()
432
+ }
423
433
  replServer.context.config = controller.applicationConfig
424
- replServer.context.logger = globalThis.platformatic?.logger
434
+ replServer.context.logger = getLogger()
425
435
 
426
436
  replServer.on('exit', () => {
427
437
  port.postMessage({ type: 'exit' })
@@ -6,6 +6,7 @@ import {
6
6
  getPrivateSymbol,
7
7
  parseMemorySize
8
8
  } from '@platformatic/foundation'
9
+ import { getITC, getLogger, updateGlobals } from '@platformatic/globals'
9
10
  import { addPinoInstrumentation } from '@platformatic/telemetry'
10
11
  import { Buffer } from 'node:buffer'
11
12
  import { subscribe } from 'node:diagnostics_channel'
@@ -25,11 +26,12 @@ import { initHealthSignalsApi } from './health-signals.js'
25
26
  import { setDispatcher } from './interceptors.js'
26
27
  import { setupITC } from './itc.js'
27
28
  import { SharedContext } from './shared-context.js'
28
- import { kId, kITC, kStderrMarker } from './symbols.js'
29
+ import { kStderrMarker } from './symbols.js'
29
30
 
30
31
  function handlePreInitializationUnhandled (type, err) {
31
32
  const label = `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
32
- globalThis.platformatic.logger.error(
33
+ const logger = getLogger()
34
+ logger.error(
33
35
  { err: ensureLoggableError(err) },
34
36
  `The ${label} threw an ${type} before initialization.`
35
37
  )
@@ -38,7 +40,8 @@ function handlePreInitializationUnhandled (type, err) {
38
40
 
39
41
  class ForwardingEventEmitter extends EventEmitter {
40
42
  emitAndNotify (event, ...args) {
41
- globalThis.platformatic.itc.notify('event', { event, payload: args })
43
+ const itc = getITC()
44
+ itc.notify('event', { event, payload: args })
42
45
  return this.emit(event, ...args)
43
46
  }
44
47
  }
@@ -201,8 +204,9 @@ async function main () {
201
204
  const cleanup = setupHandlers()
202
205
 
203
206
  installUndiciGlobals(globalThis)
204
- globalThis[kId] = threadId
205
- globalThis.platformatic = Object.assign(globalThis.platformatic ?? {}, {
207
+ updateGlobals({
208
+ runtimeId: threadId,
209
+ interceptors: {},
206
210
  logger: createLogger(),
207
211
  events: new ForwardingEventEmitter()
208
212
  })
@@ -210,11 +214,11 @@ async function main () {
210
214
  const runtimeConfig = workerData.config
211
215
  const applicationConfig = workerData.applicationConfig
212
216
 
213
- setupBufferPool(runtimeConfig, applicationConfig, globalThis.platformatic.logger)
214
- setupDefaultHighWaterMark(runtimeConfig, applicationConfig, globalThis.platformatic.logger)
217
+ setupBufferPool(runtimeConfig, applicationConfig, getLogger())
218
+ setupDefaultHighWaterMark(runtimeConfig, applicationConfig, getLogger())
215
219
 
216
220
  // Enable compile cache early before loading user modules
217
- await setupCompileCache(runtimeConfig, applicationConfig, globalThis.platformatic.logger)
221
+ await setupCompileCache(runtimeConfig, applicationConfig, getLogger())
218
222
 
219
223
  await performPreloading(runtimeConfig, applicationConfig)
220
224
 
@@ -226,7 +230,8 @@ async function main () {
226
230
  envfile = resolve(workerData.applicationConfig.path, '.env')
227
231
  }
228
232
 
229
- globalThis.platformatic.logger.debug({ envfile }, 'Loading envfile...')
233
+ const logger = getLogger()
234
+ logger.debug({ envfile }, 'Loading envfile...')
230
235
 
231
236
  try {
232
237
  process.loadEnvFile(envfile)
@@ -301,15 +306,16 @@ async function main () {
301
306
 
302
307
  const sharedContext = new SharedContext()
303
308
  // Limit the amount of methods a user can call
304
- globalThis.platformatic.sharedContext = {
305
- get: () => sharedContext.get(),
306
- update: (...args) => sharedContext.update(...args)
307
- }
309
+ updateGlobals({
310
+ sharedContext: {
311
+ get: () => sharedContext.get(),
312
+ update: (...args) => sharedContext.update(...args)
313
+ }
314
+ })
308
315
 
309
316
  // Setup interaction with parent port
310
317
  const itc = await setupITC(controller, applicationConfig, threadDispatcher, sharedContext)
311
- globalThis[kITC] = itc
312
- globalThis.platformatic.itc = itc
318
+ updateGlobals({ itc })
313
319
 
314
320
  // Setup management client for privileged applications
315
321
  if (applicationConfig.management) {
@@ -322,7 +328,7 @@ async function main () {
322
328
  const ops = typeof applicationConfig.management === 'object'
323
329
  ? applicationConfig.management.operations
324
330
  : undefined
325
- globalThis.platformatic.management = new ManagementClient(ops)
331
+ updateGlobals({ management: new ManagementClient(ops) })
326
332
  }
327
333
  }
328
334
 
@@ -1,4 +1,4 @@
1
- import { kITC } from './symbols.js'
1
+ import { getITC } from '@platformatic/globals'
2
2
 
3
3
  export class ManagementClient {
4
4
  #allowedOperations
@@ -14,7 +14,8 @@ export class ManagementClient {
14
14
  throw new Error(`Operation "${operation}" is not allowed`)
15
15
  }
16
16
 
17
- return globalThis[kITC].send('management:' + operation, data)
17
+ const itc = getITC()
18
+ return itc.send('management:' + operation, data)
18
19
  }
19
20
 
20
21
  getRuntimeStatus () {
@@ -10,9 +10,10 @@ import {
10
10
  startOutgoingMessagingSpanSync,
11
11
  traceIncomingMessagingHandler
12
12
  } from '@platformatic/itc'
13
+ import { getITC } from '@platformatic/globals'
13
14
  import { MessagingError } from '../errors.js'
14
15
  import { RoundRobinMap } from './round-robin-map.js'
15
- import { kITC, kWorkersBroadcast } from './symbols.js'
16
+ import { kWorkersBroadcast } from './symbols.js'
16
17
 
17
18
  const kPendingResponses = Symbol('plt.messaging.pendingResponses')
18
19
 
@@ -76,7 +77,7 @@ export class MessagingITC extends ITC {
76
77
 
77
78
  try {
78
79
  // Get the next worker for the application
79
- const worker = this.#workers.next(application)
80
+ const worker = this._getNextWorker(application)
80
81
 
81
82
  if (!worker) {
82
83
  throw new MessagingError(application, 'No workers available')
@@ -85,8 +86,9 @@ export class MessagingITC extends ITC {
85
86
  if (!worker.channel) {
86
87
  // Use twice the value here as a fallback measure. The target handler in the main thread is forwarding
87
88
  // the request to the worker, using executeWithTimeout with the user set timeout value.
89
+ const itc = getITC()
88
90
  const channel = await executeWithTimeout(
89
- globalThis[kITC].send('getWorkerMessagingChannel', {
91
+ itc.send('getWorkerMessagingChannel', {
90
92
  id: this.#id,
91
93
  application: worker.application,
92
94
  worker: worker.worker
@@ -100,10 +102,7 @@ export class MessagingITC extends ITC {
100
102
  }
101
103
 
102
104
  worker.channel = channel
103
- this.#setupChannel(channel)
104
-
105
- channel[kPendingResponses] = new Map()
106
- channel.on('close', this.#handlePendingResponse.bind(this, channel))
105
+ this._setupChannel(channel)
107
106
  }
108
107
 
109
108
  const context = { ...options }
@@ -164,13 +163,11 @@ export class MessagingITC extends ITC {
164
163
 
165
164
  async addSource (channel) {
166
165
  this.#sources.add(channel)
167
- this.#setupChannel(channel)
166
+ this._setupChannel(channel)
167
+ }
168
168
 
169
- // This has been closed on the other side.
170
- // Pending messages will be silently discarded by Node (as postMessage does not throw) so we don't need to handle them.
171
- channel.on('close', () => {
172
- this.#sources.delete(channel)
173
- })
169
+ _getNextWorker (application) {
170
+ return this.#workers.next(application)
174
171
  }
175
172
 
176
173
  _send (request, context) {
@@ -181,7 +178,7 @@ export class MessagingITC extends ITC {
181
178
  channel[kPendingResponses].set(request.reqId, { application, request })
182
179
  }
183
180
 
184
- channel.postMessage(sanitize(request, transferList), { transferList })
181
+ channel.postMessage(sanitize(request, transferList), transferList)
185
182
  }
186
183
 
187
184
  _createClosePromise () {
@@ -209,11 +206,22 @@ export class MessagingITC extends ITC {
209
206
  this.#sources.clear()
210
207
  }
211
208
 
212
- #setupChannel (channel) {
209
+ _setupChannel (channel) {
210
+ if (channel[kPendingResponses]) {
211
+ return
212
+ }
213
+
214
+ channel[kPendingResponses] = new Map()
215
+
213
216
  // Setup the message for processing
214
217
  channel.on('message', event => {
215
218
  this.#listener(event, { channel, message: event })
216
219
  })
220
+
221
+ channel.on('close', () => {
222
+ this.#sources.delete(channel)
223
+ this.#handlePendingResponse(channel)
224
+ })
217
225
  }
218
226
 
219
227
  #updateWorkers (event) {
@@ -1,4 +1,4 @@
1
- import { kITC } from './symbols.js'
1
+ import { getITC } from '@platformatic/globals'
2
2
 
3
3
  export class SharedContext {
4
4
  constructor () {
@@ -6,12 +6,14 @@ export class SharedContext {
6
6
  }
7
7
 
8
8
  update (context, options = {}) {
9
- return globalThis[kITC].send('updateSharedContext', { ...options, context })
9
+ const itc = getITC()
10
+ return itc.send('updateSharedContext', { ...options, context })
10
11
  }
11
12
 
12
13
  get () {
13
14
  if (this.sharedContext === null) {
14
- this.sharedContext = globalThis[kITC].send('getSharedContext')
15
+ const itc = getITC()
16
+ this.sharedContext = itc.send('getSharedContext')
15
17
  }
16
18
  return this.sharedContext
17
19
  }
@@ -1,5 +1,5 @@
1
1
  export const kConfig = Symbol.for('plt.runtime.config')
2
- export const kId = Symbol.for('plt.runtime.id') // This is also used to detect if we are running in a Platformatic runtime thread
2
+ export const kId = Symbol.for('plt.runtime.id')
3
3
  export const kFullId = Symbol.for('plt.runtime.fullId')
4
4
  export const kApplicationId = Symbol.for('plt.runtime.application.id')
5
5
  export const kWorkerId = Symbol.for('plt.runtime.worker.id')
@@ -8,7 +8,6 @@ export const kHealthCheckTimer = Symbol.for('plt.runtime.worker.healthCheckTimer
8
8
  export const kWorkerStatus = Symbol('plt.runtime.worker.status')
9
9
  export const kWorkerHealthSignals = Symbol.for('plt.runtime.worker.healthSignals')
10
10
  export const kWorkerStartTime = Symbol.for('plt.runtime.worker.startTime')
11
- export const kInterceptors = Symbol.for('plt.runtime.worker.interceptors')
12
11
  export const kLastHealthCheckELU = Symbol.for('plt.runtime.worker.lastHealthCheckELU')
13
12
  export const kLastWorkerScalerELU = Symbol.for('plt.runtime.worker.lastWorkerScalerELU')
14
13
  export const kInterceptorReadyPromise = Symbol.for('plt.runtime.worker.interceptorReadyPromise')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "3.55.0",
3
+ "version": "3.56.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -36,14 +36,14 @@
36
36
  "typescript": "^5.5.4",
37
37
  "undici-oidc-interceptor": "^0.5.0",
38
38
  "why-is-node-running": "^2.2.2",
39
- "@platformatic/db": "3.55.0",
40
- "@platformatic/composer": "3.55.0",
41
- "@platformatic/node": "3.55.0",
42
- "@platformatic/service": "3.55.0",
43
- "@platformatic/sql-graphql": "3.55.0",
44
- "@platformatic/gateway": "3.55.0",
45
- "@platformatic/sql-mapper": "3.55.0",
46
- "@platformatic/wattpm-pprof-capture": "3.55.0"
39
+ "@platformatic/composer": "3.56.0",
40
+ "@platformatic/db": "3.56.0",
41
+ "@platformatic/service": "3.56.0",
42
+ "@platformatic/gateway": "3.56.0",
43
+ "@platformatic/sql-graphql": "3.56.0",
44
+ "@platformatic/sql-mapper": "3.56.0",
45
+ "@platformatic/node": "3.56.0",
46
+ "@platformatic/wattpm-pprof-capture": "3.56.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "@fastify/accepts": "^5.0.0",
@@ -70,15 +70,16 @@
70
70
  "semgrator": "^0.3.0",
71
71
  "sonic-boom": "^4.2.0",
72
72
  "systeminformation": "^5.27.11",
73
- "undici": "^7.0.0",
73
+ "undici": "^7.27.2",
74
74
  "undici-thread-interceptor": "^1.3.1",
75
75
  "ws": "^8.16.0",
76
- "@platformatic/itc": "3.55.0",
77
- "@platformatic/foundation": "3.55.0",
78
- "@platformatic/metrics": "3.55.0",
79
- "@platformatic/basic": "3.55.0",
80
- "@platformatic/generators": "3.55.0",
81
- "@platformatic/telemetry": "3.55.0"
76
+ "@platformatic/basic": "3.56.0",
77
+ "@platformatic/generators": "3.56.0",
78
+ "@platformatic/globals": "3.56.0",
79
+ "@platformatic/metrics": "3.56.0",
80
+ "@platformatic/itc": "3.56.0",
81
+ "@platformatic/telemetry": "3.56.0",
82
+ "@platformatic/foundation": "3.56.0"
82
83
  },
83
84
  "engines": {
84
85
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.55.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.56.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",