@platformatic/runtime 3.54.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.
@@ -5,8 +5,19 @@ import {
5
5
  kHandledError,
6
6
  listRecognizedConfigurationFiles,
7
7
  loadConfiguration,
8
- loadConfigurationModule
8
+ loadConfigurationModule,
9
+ mirrorGlobalDispatcherForBuiltinFetch
9
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'
10
21
  import debounce from 'debounce'
11
22
  import { EventEmitter } from 'node:events'
12
23
  import { existsSync } from 'node:fs'
@@ -16,6 +27,7 @@ import { workerData } from 'node:worker_threads'
16
27
  import { getGlobalDispatcher, setGlobalDispatcher } from 'undici'
17
28
  import { ApplicationAlreadyStartedError, RuntimeNotStartedError } from '../errors.js'
18
29
  import { getApplicationUrl } from '../utils.js'
30
+ import { markAsPlatformaticDispatcher, refreshGlobalDispatcher } from './interceptors.js'
19
31
 
20
32
  function fetchApplicationUrl (application, key) {
21
33
  if (!key.endsWith('_URL') || !application.id) {
@@ -28,7 +40,8 @@ function fetchApplicationUrl (application, key) {
28
40
  function handleUnhandled (app, type, err) {
29
41
  const label = `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
30
42
 
31
- 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}.`)
32
45
 
33
46
  executeWithTimeout(app?.stop(), 1000)
34
47
  .catch()
@@ -141,7 +154,8 @@ export class Controller extends EventEmitter {
141
154
  }
142
155
  } catch (err) {
143
156
  if (err.validationErrors) {
144
- globalThis.platformatic.logger.error(
157
+ const logger = getLogger()
158
+ logger.error(
145
159
  { err: ensureLoggableError(err) },
146
160
  'The application threw a validation error.'
147
161
  )
@@ -192,6 +206,9 @@ export class Controller extends EventEmitter {
192
206
 
193
207
  try {
194
208
  await this.capability.start({ listen })
209
+ if (refreshGlobalDispatcher()) {
210
+ this.#updateDispatcher()
211
+ }
195
212
  this.#listening = listen
196
213
  /* c8 ignore next 5 */
197
214
  } catch (err) {
@@ -243,18 +260,29 @@ export class Controller extends EventEmitter {
243
260
 
244
261
  async getMetrics ({ format }) {
245
262
  const dispatcher = getGlobalDispatcher()
246
- 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
+
247
272
  for (const url in dispatcher.stats) {
248
273
  const { free, connected, pending, queued, running, size } = dispatcher.stats[url]
249
- globalThis.platformatic.onHttpStatsFree(url, free || 0)
250
- globalThis.platformatic.onHttpStatsConnected(url, connected || 0)
251
- globalThis.platformatic.onHttpStatsPending(url, pending || 0)
252
- globalThis.platformatic.onHttpStatsQueued(url, queued || 0)
253
- globalThis.platformatic.onHttpStatsRunning(url, running || 0)
254
- 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)
255
280
  }
256
281
  }
257
- globalThis.platformatic.onActiveResourcesEventLoop(getActiveResourcesInfo().length)
282
+ const onActiveResourcesEventLoop = getOnActiveResourcesEventLoop({ throwOnMissing: false })
283
+ if (onActiveResourcesEventLoop) {
284
+ onActiveResourcesEventLoop(getActiveResourcesInfo().length)
285
+ }
258
286
  return this.capability.getMetrics({ format })
259
287
  }
260
288
 
@@ -299,7 +327,8 @@ export class Controller extends EventEmitter {
299
327
  }
300
328
 
301
329
  #logAndThrow (err) {
302
- globalThis.platformatic.logger.error(
330
+ const logger = getLogger()
331
+ logger.error(
303
332
  { err: ensureLoggableError(err) },
304
333
  err[kHandledError] ? err.message : 'The application threw an error.'
305
334
  )
@@ -325,7 +354,9 @@ export class Controller extends EventEmitter {
325
354
 
326
355
  const dispatcher = getGlobalDispatcher().compose(interceptor)
327
356
 
357
+ markAsPlatformaticDispatcher(dispatcher)
328
358
  setGlobalDispatcher(dispatcher)
359
+ mirrorGlobalDispatcherForBuiltinFetch(dispatcher)
329
360
  }
330
361
 
331
362
  #setupHandlers () {
@@ -334,7 +365,8 @@ export class Controller extends EventEmitter {
334
365
 
335
366
  process.on('newListener', event => {
336
367
  if (event === 'uncaughtException' || event === 'unhandledRejection') {
337
- globalThis.platformatic.logger.warn(
368
+ const logger = getLogger()
369
+ logger.warn(
338
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.`
339
371
  )
340
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,13 +1,46 @@
1
+ import { getGlobalDispatcherFromKnownUndiciSymbols, mirrorGlobalDispatcherForBuiltinFetch } from '@platformatic/foundation'
2
+ import {
3
+ getInterceptors,
4
+ getLogger,
5
+ getOnHttpCacheHit,
6
+ getOnHttpCacheMiss,
7
+ getOnHttpCacheRequest
8
+ } from '@platformatic/globals'
1
9
  import { createTelemetryThreadInterceptorHooks } from '@platformatic/telemetry'
2
10
  import { createRequire } from 'node:module'
3
11
  import { join } from 'node:path'
4
12
  import { pathToFileURL } from 'node:url'
5
13
  import { parentPort, workerData } from 'node:worker_threads'
6
- import { Agent, Client, Pool, setGlobalDispatcher } from 'undici'
14
+ import { Agent, Client, Pool } from 'undici'
7
15
  import { wire } from 'undici-thread-interceptor'
8
16
  import { createChannelCreationHook } from '../policies.js'
9
17
  import { RemoteCacheStore, httpCacheInterceptor } from './http-cache.js'
10
- 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
+ }
11
44
 
12
45
  export async function setDispatcher (runtimeConfig) {
13
46
  const threadDispatcher = createThreadInterceptor(runtimeConfig)
@@ -27,15 +60,39 @@ export async function setDispatcher (runtimeConfig) {
27
60
 
28
61
  const dispatcherOpts = await getDispatcherOpts(runtimeConfig.undici)
29
62
 
30
- setGlobalDispatcher(
31
- new Agent(dispatcherOpts).compose([threadInterceptor, ...userInterceptors, cacheInterceptor].filter(Boolean))
32
- )
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))
33
75
 
34
76
  return { threadDispatcher }
35
77
  }
36
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
+
37
94
  export async function updateUndiciInterceptors (undiciConfig) {
38
- const updatableInterceptors = globalThis[kInterceptors]
95
+ const updatableInterceptors = getInterceptors()
39
96
  if (!updatableInterceptors) return
40
97
 
41
98
  if (Array.isArray(undiciConfig?.interceptors)) {
@@ -95,11 +152,7 @@ async function loadInterceptors (_require, interceptorsConfigs, key) {
95
152
  }
96
153
 
97
154
  async function loadInterceptor (_require, interceptorConfig, key) {
98
- let updatableInterceptors = globalThis[kInterceptors]
99
- if (!updatableInterceptors) {
100
- updatableInterceptors = {}
101
- globalThis[kInterceptors] = updatableInterceptors
102
- }
155
+ const updatableInterceptors = getInterceptors()
103
156
 
104
157
  const { module, options } = interceptorConfig
105
158
 
@@ -196,24 +249,34 @@ function parseOrigins (origins) {
196
249
 
197
250
  function createHttpCacheInterceptor (runtimeConfig) {
198
251
  const httpCache = runtimeConfig.httpCache
252
+
199
253
  const cacheInterceptor = httpCacheInterceptor({
200
254
  store: new RemoteCacheStore({
201
255
  onRequest: opts => {
202
- globalThis.platformatic?.onHttpCacheRequest?.(opts)
256
+ const onHttpCacheRequest = getOnHttpCacheRequest({ throwOnMissing: false })
257
+ if (onHttpCacheRequest) {
258
+ onHttpCacheRequest(opts)
259
+ }
203
260
  },
204
261
  onCacheHit: opts => {
205
- globalThis.platformatic?.onHttpCacheHit?.(opts)
262
+ const onHttpCacheHit = getOnHttpCacheHit({ throwOnMissing: false })
263
+ if (onHttpCacheHit) {
264
+ onHttpCacheHit(opts)
265
+ }
206
266
  },
207
267
  onCacheMiss: opts => {
208
- globalThis.platformatic?.onHttpCacheMiss?.(opts)
268
+ const onHttpCacheMiss = getOnHttpCacheMiss({ throwOnMissing: false })
269
+ if (onHttpCacheMiss) {
270
+ onHttpCacheMiss(opts)
271
+ }
209
272
  },
210
- logger: globalThis.platformatic.logger
273
+ logger: getLogger()
211
274
  }),
212
275
  methods: httpCache.methods ?? ['GET', 'HEAD'],
213
276
  origins: parseOrigins(httpCache.origins),
214
277
  cacheByDefault: httpCache.cacheByDefault,
215
278
  type: httpCache.type,
216
- logger: globalThis.platformatic.logger
279
+ logger: getLogger()
217
280
  })
218
281
  return cacheInterceptor
219
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' })
@@ -3,15 +3,19 @@ import {
3
3
  buildPinoTimestamp,
4
4
  disablePinoDirectWrite,
5
5
  ensureLoggableError,
6
- getPrivateSymbol
6
+ getPrivateSymbol,
7
+ parseMemorySize
7
8
  } from '@platformatic/foundation'
9
+ import { getITC, getLogger, updateGlobals } from '@platformatic/globals'
8
10
  import { addPinoInstrumentation } from '@platformatic/telemetry'
11
+ import { Buffer } from 'node:buffer'
9
12
  import { subscribe } from 'node:diagnostics_channel'
10
13
  import { EventEmitter } from 'node:events'
11
14
  import { ServerResponse } from 'node:http'
12
15
  import inspector from 'node:inspector'
13
16
  import { hostname } from 'node:os'
14
17
  import { join, resolve } from 'node:path'
18
+ import { setDefaultHighWaterMark } from 'node:stream'
15
19
  import { pathToFileURL } from 'node:url'
16
20
  import { threadId, workerData } from 'node:worker_threads'
17
21
  import pino from 'pino'
@@ -22,11 +26,12 @@ import { initHealthSignalsApi } from './health-signals.js'
22
26
  import { setDispatcher } from './interceptors.js'
23
27
  import { setupITC } from './itc.js'
24
28
  import { SharedContext } from './shared-context.js'
25
- import { kId, kITC, kStderrMarker } from './symbols.js'
29
+ import { kStderrMarker } from './symbols.js'
26
30
 
27
31
  function handlePreInitializationUnhandled (type, err) {
28
32
  const label = `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
29
- globalThis.platformatic.logger.error(
33
+ const logger = getLogger()
34
+ logger.error(
30
35
  { err: ensureLoggableError(err) },
31
36
  `The ${label} threw an ${type} before initialization.`
32
37
  )
@@ -35,7 +40,8 @@ function handlePreInitializationUnhandled (type, err) {
35
40
 
36
41
  class ForwardingEventEmitter extends EventEmitter {
37
42
  emitAndNotify (event, ...args) {
38
- globalThis.platformatic.itc.notify('event', { event, payload: args })
43
+ const itc = getITC()
44
+ itc.notify('event', { event, payload: args })
39
45
  return this.emit(event, ...args)
40
46
  }
41
47
  }
@@ -113,6 +119,36 @@ async function performPreloading (...sources) {
113
119
  }
114
120
  }
115
121
 
122
+ function resolveHealthSize (runtimeConfig, applicationConfig, field, logger) {
123
+ const raw = applicationConfig.health?.[field] ?? runtimeConfig.health?.[field]
124
+ if (raw === undefined) {
125
+ return undefined
126
+ }
127
+
128
+ const size = typeof raw === 'string' ? parseMemorySize(raw) : raw
129
+
130
+ if (!Number.isInteger(size) || size < 0) {
131
+ logger.warn({ [field]: raw }, `Invalid health.${field}, ignoring`)
132
+ return undefined
133
+ }
134
+
135
+ return size
136
+ }
137
+
138
+ function setupBufferPool (runtimeConfig, applicationConfig, logger) {
139
+ const size = resolveHealthSize(runtimeConfig, applicationConfig, 'bufferPoolSize', logger)
140
+ if (size !== undefined) {
141
+ Buffer.poolSize = size
142
+ }
143
+ }
144
+
145
+ function setupDefaultHighWaterMark (runtimeConfig, applicationConfig, logger) {
146
+ const size = resolveHealthSize(runtimeConfig, applicationConfig, 'defaultHighWaterMark', logger)
147
+ if (size !== undefined) {
148
+ setDefaultHighWaterMark(false, size)
149
+ }
150
+ }
151
+
116
152
  // Enable compile cache if configured (Node.js 22.1.0+)
117
153
  async function setupCompileCache (runtimeConfig, applicationConfig, logger) {
118
154
  // Normalize boolean shorthand: true -> { enabled: true }
@@ -168,8 +204,9 @@ async function main () {
168
204
  const cleanup = setupHandlers()
169
205
 
170
206
  installUndiciGlobals(globalThis)
171
- globalThis[kId] = threadId
172
- globalThis.platformatic = Object.assign(globalThis.platformatic ?? {}, {
207
+ updateGlobals({
208
+ runtimeId: threadId,
209
+ interceptors: {},
173
210
  logger: createLogger(),
174
211
  events: new ForwardingEventEmitter()
175
212
  })
@@ -177,8 +214,11 @@ async function main () {
177
214
  const runtimeConfig = workerData.config
178
215
  const applicationConfig = workerData.applicationConfig
179
216
 
217
+ setupBufferPool(runtimeConfig, applicationConfig, getLogger())
218
+ setupDefaultHighWaterMark(runtimeConfig, applicationConfig, getLogger())
219
+
180
220
  // Enable compile cache early before loading user modules
181
- await setupCompileCache(runtimeConfig, applicationConfig, globalThis.platformatic.logger)
221
+ await setupCompileCache(runtimeConfig, applicationConfig, getLogger())
182
222
 
183
223
  await performPreloading(runtimeConfig, applicationConfig)
184
224
 
@@ -190,7 +230,8 @@ async function main () {
190
230
  envfile = resolve(workerData.applicationConfig.path, '.env')
191
231
  }
192
232
 
193
- globalThis.platformatic.logger.debug({ envfile }, 'Loading envfile...')
233
+ const logger = getLogger()
234
+ logger.debug({ envfile }, 'Loading envfile...')
194
235
 
195
236
  try {
196
237
  process.loadEnvFile(envfile)
@@ -265,15 +306,16 @@ async function main () {
265
306
 
266
307
  const sharedContext = new SharedContext()
267
308
  // Limit the amount of methods a user can call
268
- globalThis.platformatic.sharedContext = {
269
- get: () => sharedContext.get(),
270
- update: (...args) => sharedContext.update(...args)
271
- }
309
+ updateGlobals({
310
+ sharedContext: {
311
+ get: () => sharedContext.get(),
312
+ update: (...args) => sharedContext.update(...args)
313
+ }
314
+ })
272
315
 
273
316
  // Setup interaction with parent port
274
317
  const itc = await setupITC(controller, applicationConfig, threadDispatcher, sharedContext)
275
- globalThis[kITC] = itc
276
- globalThis.platformatic.itc = itc
318
+ updateGlobals({ itc })
277
319
 
278
320
  // Setup management client for privileged applications
279
321
  if (applicationConfig.management) {
@@ -286,7 +328,7 @@ async function main () {
286
328
  const ops = typeof applicationConfig.management === 'object'
287
329
  ? applicationConfig.management.operations
288
330
  : undefined
289
- globalThis.platformatic.management = new ManagementClient(ops)
331
+ updateGlobals({ management: new ManagementClient(ops) })
290
332
  }
291
333
  }
292
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
  }