@platformatic/runtime 3.54.0 → 3.55.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/config.d.ts CHANGED
@@ -42,6 +42,8 @@ export type PlatformaticRuntimeConfig = {
42
42
  maxHeapTotal?: number | string;
43
43
  maxYoungGeneration?: number | string;
44
44
  codeRangeSize?: number | string;
45
+ bufferPoolSize?: number | string;
46
+ defaultHighWaterMark?: number | string;
45
47
  };
46
48
  dependencies?: string[];
47
49
  arguments?: string[];
@@ -238,6 +240,8 @@ export type PlatformaticRuntimeConfig = {
238
240
  maxHeapTotal?: number | string;
239
241
  maxYoungGeneration?: number | string;
240
242
  codeRangeSize?: number | string;
243
+ bufferPoolSize?: number | string;
244
+ defaultHighWaterMark?: number | string;
241
245
  };
242
246
  undici?: {
243
247
  agentOptions?: {
@@ -340,6 +344,10 @@ export type PlatformaticRuntimeConfig = {
340
344
  * The label name to use for the application identifier in metrics (e.g., applicationId, serviceId)
341
345
  */
342
346
  applicationLabel?: string;
347
+ /**
348
+ * Enable outgoing HTTP client request duration metrics
349
+ */
350
+ httpClientMetrics?: boolean | string;
343
351
  readiness?:
344
352
  | boolean
345
353
  | {
package/index.d.ts CHANGED
@@ -142,7 +142,26 @@ export class WrappedGenerator extends BaseGenerator {}
142
142
 
143
143
  export declare const schema: JSONSchemaType<PlatformaticRuntimeConfig>
144
144
 
145
- export declare class Runtime {}
145
+ export declare class Runtime {
146
+ init (): Promise<void>
147
+ start (silent?: boolean): Promise<string | undefined>
148
+ stop (silent?: boolean): Promise<void>
149
+ close (silent?: boolean): Promise<void>
150
+ restart (applications?: string[]): Promise<string | undefined>
151
+ inject (id: string, injectParams: InjectParams): Promise<InjectResponse>
152
+ getUrl (): string | undefined
153
+ getRuntimeStatus (): string
154
+ getRuntimeMetadata (): Promise<RuntimeMetadata>
155
+ getRuntimeEnv (): Record<string, string>
156
+ getRuntimeConfig (includeMeta?: boolean): Record<string, unknown>
157
+ getApplicationsIds (): string[]
158
+ getApplicationDetails (id: string, allowUnloaded?: boolean): Promise<ApplicationDetails>
159
+ startApplication (id: string, silent?: boolean): Promise<void>
160
+ stopApplication (id: string, silent?: boolean): Promise<void>
161
+ restartApplication (id: string): Promise<void>
162
+ addApplications (applications: unknown[], start?: boolean): Promise<ApplicationDetails[]>
163
+ removeApplications (applications: string[], silent?: boolean): Promise<ApplicationDetails[]>
164
+ }
146
165
 
147
166
  export function wrapInRuntimeConfig (
148
167
  config: Configuration<unknown>,
package/lib/logger.js CHANGED
@@ -63,6 +63,7 @@ const colors = [
63
63
  function createLoggerContext () {
64
64
  const context = {
65
65
  colors: {},
66
+ closeables: [],
66
67
  maxLength: 0,
67
68
  updatePrefixes (ids) {
68
69
  context.colors = {}
@@ -179,28 +180,29 @@ export async function createLogger (config) {
179
180
  const multiStream = pino.multistream([{ stream: cliStream, level: loggerConfig.level }])
180
181
 
181
182
  if (config.telemetry && config.logger.openTelemetryExporter) {
182
- multiStream.add(
183
- pino.transport({
184
- target: 'pino-opentelemetry-transport',
185
- options: {
186
- resourceAttributes: {
187
- 'service.name': config.telemetry.applicationName,
188
- 'service.version': config.telemetry.version
189
- },
190
- logRecordProcessorOptions: [
191
- {
192
- recordProcessorType: 'simple',
193
- exporterOptions: {
194
- protocol: config.logger.openTelemetryExporter.protocol,
195
- httpExporterOptions: {
196
- url: config.logger.openTelemetryExporter.url
197
- }
183
+ const openTelemetryTransport = pino.transport({
184
+ target: 'pino-opentelemetry-transport',
185
+ options: {
186
+ resourceAttributes: {
187
+ 'service.name': config.telemetry.applicationName,
188
+ 'service.version': config.telemetry.version
189
+ },
190
+ logRecordProcessorOptions: [
191
+ {
192
+ recordProcessorType: 'simple',
193
+ exporterOptions: {
194
+ protocol: config.logger.openTelemetryExporter.protocol,
195
+ httpExporterOptions: {
196
+ url: config.logger.openTelemetryExporter.url
198
197
  }
199
198
  }
200
- ]
201
- }
202
- })
203
- )
199
+ }
200
+ ]
201
+ }
202
+ })
203
+
204
+ context.closeables.push(openTelemetryTransport)
205
+ multiStream.add(openTelemetryTransport)
204
206
  }
205
207
 
206
208
  const logsFileMb = 5
package/lib/runtime.js CHANGED
@@ -20,6 +20,7 @@ import { createRequire } from 'node:module'
20
20
  import { availableParallelism } from 'node:os'
21
21
  import { dirname, isAbsolute, join } from 'node:path'
22
22
  import { Readable } from 'node:stream'
23
+ import { finished } from 'node:stream/promises'
23
24
  import { setImmediate as immediate, setTimeout as sleep } from 'node:timers/promises'
24
25
  import { pathToFileURL } from 'node:url'
25
26
  import { Worker } from 'node:worker_threads'
@@ -400,11 +401,22 @@ export class Runtime extends EventEmitter {
400
401
  }
401
402
 
402
403
  if (this.logger) {
403
- this.#loggerDestination?.end()
404
+ const loggerDestination = this.#loggerDestination
405
+ const loggerCloseables = this.#loggerContext?.closeables ?? []
404
406
 
405
407
  this.logger = abstractLogger
406
408
  this.#loggerDestination = null
407
409
  this.#loggerContext = null
410
+
411
+ if (loggerDestination) {
412
+ loggerDestination.end()
413
+ await finished(loggerDestination).catch(() => {})
414
+ }
415
+
416
+ for (const closeable of loggerCloseables) {
417
+ closeable.end?.()
418
+ await finished(closeable).catch(() => {})
419
+ }
408
420
  }
409
421
 
410
422
  this.#updateStatus('closed')
@@ -3243,6 +3255,15 @@ export class Runtime extends EventEmitter {
3243
3255
  return argv
3244
3256
  }
3245
3257
 
3258
+ // Starting from Node.js 25 the Permission Model also gates network access
3259
+ // (dns.lookup, server.listen, outbound connections and fetch) behind
3260
+ // --allow-net. Applications always need to bind their HTTP server and reach
3261
+ // other applications through the internal mesh, so we always grant it when
3262
+ // available. On older versions the flag does not exist and must be omitted.
3263
+ if (features.node.permission.network) {
3264
+ allows.add('--allow-net')
3265
+ }
3266
+
3246
3267
  // We need to allow read access to the node_modules folder both at the runtime level and at the application level
3247
3268
  allows.add(`--allow-fs-read=${join(this.#root, 'node_modules', '*')}`)
3248
3269
  allows.add(`--allow-fs-read=${join(applicationConfig.path, 'node_modules', '*')}`)
@@ -5,7 +5,8 @@ import {
5
5
  kHandledError,
6
6
  listRecognizedConfigurationFiles,
7
7
  loadConfiguration,
8
- loadConfigurationModule
8
+ loadConfigurationModule,
9
+ mirrorGlobalDispatcherForBuiltinFetch
9
10
  } from '@platformatic/foundation'
10
11
  import debounce from 'debounce'
11
12
  import { EventEmitter } from 'node:events'
@@ -326,6 +327,7 @@ export class Controller extends EventEmitter {
326
327
  const dispatcher = getGlobalDispatcher().compose(interceptor)
327
328
 
328
329
  setGlobalDispatcher(dispatcher)
330
+ mirrorGlobalDispatcherForBuiltinFetch(dispatcher)
329
331
  }
330
332
 
331
333
  #setupHandlers () {
@@ -1,3 +1,4 @@
1
+ import { mirrorGlobalDispatcherForBuiltinFetch } from '@platformatic/foundation'
1
2
  import { createTelemetryThreadInterceptorHooks } from '@platformatic/telemetry'
2
3
  import { createRequire } from 'node:module'
3
4
  import { join } from 'node:path'
@@ -27,9 +28,11 @@ export async function setDispatcher (runtimeConfig) {
27
28
 
28
29
  const dispatcherOpts = await getDispatcherOpts(runtimeConfig.undici)
29
30
 
30
- setGlobalDispatcher(
31
- new Agent(dispatcherOpts).compose([threadInterceptor, ...userInterceptors, cacheInterceptor].filter(Boolean))
31
+ const dispatcher = new Agent(dispatcherOpts).compose(
32
+ [threadInterceptor, ...userInterceptors, cacheInterceptor].filter(Boolean)
32
33
  )
34
+ setGlobalDispatcher(dispatcher)
35
+ mirrorGlobalDispatcherForBuiltinFetch(dispatcher)
33
36
 
34
37
  return { threadDispatcher }
35
38
  }
@@ -3,15 +3,18 @@ import {
3
3
  buildPinoTimestamp,
4
4
  disablePinoDirectWrite,
5
5
  ensureLoggableError,
6
- getPrivateSymbol
6
+ getPrivateSymbol,
7
+ parseMemorySize
7
8
  } from '@platformatic/foundation'
8
9
  import { addPinoInstrumentation } from '@platformatic/telemetry'
10
+ import { Buffer } from 'node:buffer'
9
11
  import { subscribe } from 'node:diagnostics_channel'
10
12
  import { EventEmitter } from 'node:events'
11
13
  import { ServerResponse } from 'node:http'
12
14
  import inspector from 'node:inspector'
13
15
  import { hostname } from 'node:os'
14
16
  import { join, resolve } from 'node:path'
17
+ import { setDefaultHighWaterMark } from 'node:stream'
15
18
  import { pathToFileURL } from 'node:url'
16
19
  import { threadId, workerData } from 'node:worker_threads'
17
20
  import pino from 'pino'
@@ -113,6 +116,36 @@ async function performPreloading (...sources) {
113
116
  }
114
117
  }
115
118
 
119
+ function resolveHealthSize (runtimeConfig, applicationConfig, field, logger) {
120
+ const raw = applicationConfig.health?.[field] ?? runtimeConfig.health?.[field]
121
+ if (raw === undefined) {
122
+ return undefined
123
+ }
124
+
125
+ const size = typeof raw === 'string' ? parseMemorySize(raw) : raw
126
+
127
+ if (!Number.isInteger(size) || size < 0) {
128
+ logger.warn({ [field]: raw }, `Invalid health.${field}, ignoring`)
129
+ return undefined
130
+ }
131
+
132
+ return size
133
+ }
134
+
135
+ function setupBufferPool (runtimeConfig, applicationConfig, logger) {
136
+ const size = resolveHealthSize(runtimeConfig, applicationConfig, 'bufferPoolSize', logger)
137
+ if (size !== undefined) {
138
+ Buffer.poolSize = size
139
+ }
140
+ }
141
+
142
+ function setupDefaultHighWaterMark (runtimeConfig, applicationConfig, logger) {
143
+ const size = resolveHealthSize(runtimeConfig, applicationConfig, 'defaultHighWaterMark', logger)
144
+ if (size !== undefined) {
145
+ setDefaultHighWaterMark(false, size)
146
+ }
147
+ }
148
+
116
149
  // Enable compile cache if configured (Node.js 22.1.0+)
117
150
  async function setupCompileCache (runtimeConfig, applicationConfig, logger) {
118
151
  // Normalize boolean shorthand: true -> { enabled: true }
@@ -177,6 +210,9 @@ async function main () {
177
210
  const runtimeConfig = workerData.config
178
211
  const applicationConfig = workerData.applicationConfig
179
212
 
213
+ setupBufferPool(runtimeConfig, applicationConfig, globalThis.platformatic.logger)
214
+ setupDefaultHighWaterMark(runtimeConfig, applicationConfig, globalThis.platformatic.logger)
215
+
180
216
  // Enable compile cache early before loading user modules
181
217
  await setupCompileCache(runtimeConfig, applicationConfig, globalThis.platformatic.logger)
182
218
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "3.54.0",
3
+ "version": "3.55.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.54.0",
40
- "@platformatic/gateway": "3.54.0",
41
- "@platformatic/node": "3.54.0",
42
- "@platformatic/service": "3.54.0",
43
- "@platformatic/composer": "3.54.0",
44
- "@platformatic/sql-mapper": "3.54.0",
45
- "@platformatic/sql-graphql": "3.54.0",
46
- "@platformatic/wattpm-pprof-capture": "3.54.0"
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"
47
47
  },
48
48
  "dependencies": {
49
49
  "@fastify/accepts": "^5.0.0",
@@ -73,12 +73,12 @@
73
73
  "undici": "^7.0.0",
74
74
  "undici-thread-interceptor": "^1.3.1",
75
75
  "ws": "^8.16.0",
76
- "@platformatic/basic": "3.54.0",
77
- "@platformatic/foundation": "3.54.0",
78
- "@platformatic/itc": "3.54.0",
79
- "@platformatic/metrics": "3.54.0",
80
- "@platformatic/generators": "3.54.0",
81
- "@platformatic/telemetry": "3.54.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"
82
82
  },
83
83
  "engines": {
84
84
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.54.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.55.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",
@@ -208,6 +208,28 @@
208
208
  "type": "string"
209
209
  }
210
210
  ]
211
+ },
212
+ "bufferPoolSize": {
213
+ "anyOf": [
214
+ {
215
+ "type": "number",
216
+ "minimum": 0
217
+ },
218
+ {
219
+ "type": "string"
220
+ }
221
+ ]
222
+ },
223
+ "defaultHighWaterMark": {
224
+ "anyOf": [
225
+ {
226
+ "type": "number",
227
+ "minimum": 0
228
+ },
229
+ {
230
+ "type": "string"
231
+ }
232
+ ]
211
233
  }
212
234
  },
213
235
  "additionalProperties": false
@@ -567,6 +589,28 @@
567
589
  "type": "string"
568
590
  }
569
591
  ]
592
+ },
593
+ "bufferPoolSize": {
594
+ "anyOf": [
595
+ {
596
+ "type": "number",
597
+ "minimum": 0
598
+ },
599
+ {
600
+ "type": "string"
601
+ }
602
+ ]
603
+ },
604
+ "defaultHighWaterMark": {
605
+ "anyOf": [
606
+ {
607
+ "type": "number",
608
+ "minimum": 0
609
+ },
610
+ {
611
+ "type": "string"
612
+ }
613
+ ]
570
614
  }
571
615
  },
572
616
  "additionalProperties": false
@@ -924,6 +968,28 @@
924
968
  "type": "string"
925
969
  }
926
970
  ]
971
+ },
972
+ "bufferPoolSize": {
973
+ "anyOf": [
974
+ {
975
+ "type": "number",
976
+ "minimum": 0
977
+ },
978
+ {
979
+ "type": "string"
980
+ }
981
+ ]
982
+ },
983
+ "defaultHighWaterMark": {
984
+ "anyOf": [
985
+ {
986
+ "type": "number",
987
+ "minimum": 0
988
+ },
989
+ {
990
+ "type": "string"
991
+ }
992
+ ]
927
993
  }
928
994
  },
929
995
  "additionalProperties": false
@@ -1281,6 +1347,28 @@
1281
1347
  "type": "string"
1282
1348
  }
1283
1349
  ]
1350
+ },
1351
+ "bufferPoolSize": {
1352
+ "anyOf": [
1353
+ {
1354
+ "type": "number",
1355
+ "minimum": 0
1356
+ },
1357
+ {
1358
+ "type": "string"
1359
+ }
1360
+ ]
1361
+ },
1362
+ "defaultHighWaterMark": {
1363
+ "anyOf": [
1364
+ {
1365
+ "type": "number",
1366
+ "minimum": 0
1367
+ },
1368
+ {
1369
+ "type": "string"
1370
+ }
1371
+ ]
1284
1372
  }
1285
1373
  },
1286
1374
  "additionalProperties": false
@@ -1994,6 +2082,30 @@
1994
2082
  }
1995
2083
  ],
1996
2084
  "default": 268435456
2085
+ },
2086
+ "bufferPoolSize": {
2087
+ "anyOf": [
2088
+ {
2089
+ "type": "number",
2090
+ "minimum": 0
2091
+ },
2092
+ {
2093
+ "type": "string"
2094
+ }
2095
+ ],
2096
+ "default": 262144
2097
+ },
2098
+ "defaultHighWaterMark": {
2099
+ "anyOf": [
2100
+ {
2101
+ "type": "number",
2102
+ "minimum": 0
2103
+ },
2104
+ {
2105
+ "type": "string"
2106
+ }
2107
+ ],
2108
+ "default": 262144
1997
2109
  }
1998
2110
  },
1999
2111
  "additionalProperties": false
@@ -2276,6 +2388,18 @@
2276
2388
  "default": "applicationId",
2277
2389
  "description": "The label name to use for the application identifier in metrics (e.g., applicationId, serviceId)"
2278
2390
  },
2391
+ "httpClientMetrics": {
2392
+ "anyOf": [
2393
+ {
2394
+ "type": "boolean"
2395
+ },
2396
+ {
2397
+ "type": "string"
2398
+ }
2399
+ ],
2400
+ "default": false,
2401
+ "description": "Enable outgoing HTTP client request duration metrics"
2402
+ },
2279
2403
  "readiness": {
2280
2404
  "anyOf": [
2281
2405
  {