@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.
- package/config.d.ts +8 -0
- package/index.d.ts +32 -2
- package/index.js +1 -0
- package/lib/config.js +3 -11
- package/lib/errors.js +4 -0
- package/lib/generator.js +39 -25
- package/lib/logger.js +22 -20
- package/lib/loopback-messaging.js +78 -0
- package/lib/management-api.js +3 -3
- package/lib/runtime.js +54 -9
- package/lib/utils.js +12 -3
- package/lib/worker/controller.js +45 -13
- package/lib/worker/health-signals.js +4 -2
- package/lib/worker/http-cache.js +5 -5
- package/lib/worker/interceptors.js +79 -16
- package/lib/worker/itc.js +16 -6
- package/lib/worker/main.js +57 -15
- package/lib/worker/management.js +3 -2
- package/lib/worker/messaging.js +23 -15
- package/lib/worker/shared-context.js +5 -3
- package/lib/worker/symbols.js +1 -2
- package/package.json +17 -16
- package/schema.json +125 -1
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
|
@@ -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
|
|
|
@@ -142,7 +148,26 @@ export class WrappedGenerator extends BaseGenerator {}
|
|
|
142
148
|
|
|
143
149
|
export declare const schema: JSONSchemaType<PlatformaticRuntimeConfig>
|
|
144
150
|
|
|
145
|
-
export declare class Runtime {
|
|
151
|
+
export declare class Runtime {
|
|
152
|
+
init (): Promise<void>
|
|
153
|
+
start (silent?: boolean): Promise<string | undefined>
|
|
154
|
+
stop (silent?: boolean): Promise<void>
|
|
155
|
+
close (silent?: boolean): Promise<void>
|
|
156
|
+
restart (applications?: string[]): Promise<string | undefined>
|
|
157
|
+
inject (id: string, injectParams: InjectParams): Promise<InjectResponse>
|
|
158
|
+
getUrl (): string | undefined
|
|
159
|
+
getRuntimeStatus (): string
|
|
160
|
+
getRuntimeMetadata (): Promise<RuntimeMetadata>
|
|
161
|
+
getRuntimeEnv (): Record<string, string>
|
|
162
|
+
getRuntimeConfig (includeMeta?: boolean): Record<string, unknown>
|
|
163
|
+
getApplicationsIds (): string[]
|
|
164
|
+
getApplicationDetails (id: string, allowUnloaded?: boolean): Promise<ApplicationDetails>
|
|
165
|
+
startApplication (id: string, silent?: boolean): Promise<void>
|
|
166
|
+
stopApplication (id: string, silent?: boolean): Promise<void>
|
|
167
|
+
restartApplication (id: string): Promise<void>
|
|
168
|
+
addApplications (applications: unknown[], start?: boolean): Promise<ApplicationDetails[]>
|
|
169
|
+
removeApplications (applications: string[], silent?: boolean): Promise<ApplicationDetails[]>
|
|
170
|
+
}
|
|
146
171
|
|
|
147
172
|
export function wrapInRuntimeConfig (
|
|
148
173
|
config: Configuration<unknown>,
|
|
@@ -172,3 +197,8 @@ export declare function transform (
|
|
|
172
197
|
): Promise<RuntimeConfiguration>
|
|
173
198
|
|
|
174
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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:
|
|
628
|
+
runtime: getRuntimeWrappableProperties(this.config.skipServer)
|
|
615
629
|
}
|
|
616
630
|
|
|
617
631
|
this.addFile({
|
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
|
-
|
|
183
|
-
pino
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
@@ -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
|
+
}
|
package/lib/management-api.js
CHANGED
|
@@ -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'
|
|
346
|
-
await createDirectory(runtimePIDDir,
|
|
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'
|
|
@@ -20,6 +21,7 @@ import { createRequire } from 'node:module'
|
|
|
20
21
|
import { availableParallelism } from 'node:os'
|
|
21
22
|
import { dirname, isAbsolute, join } from 'node:path'
|
|
22
23
|
import { Readable } from 'node:stream'
|
|
24
|
+
import { finished } from 'node:stream/promises'
|
|
23
25
|
import { setImmediate as immediate, setTimeout as sleep } from 'node:timers/promises'
|
|
24
26
|
import { pathToFileURL } from 'node:url'
|
|
25
27
|
import { Worker } from 'node:worker_threads'
|
|
@@ -35,7 +37,6 @@ import {
|
|
|
35
37
|
CannotRemoveEntrypointError,
|
|
36
38
|
InvalidArgumentError,
|
|
37
39
|
MessagingError,
|
|
38
|
-
MissingEntrypointError,
|
|
39
40
|
MissingPprofCapture,
|
|
40
41
|
RuntimeAbortedError,
|
|
41
42
|
WorkerInterceptorJoinTimeoutError,
|
|
@@ -273,14 +274,18 @@ export class Runtime extends EventEmitter {
|
|
|
273
274
|
await this.init()
|
|
274
275
|
}
|
|
275
276
|
|
|
276
|
-
if (typeof this.#config.entrypoint === 'undefined') {
|
|
277
|
-
throw new MissingEntrypointError()
|
|
278
|
-
}
|
|
279
277
|
this.#updateStatus('starting')
|
|
280
278
|
this.#createWorkersBroadcastChannel()
|
|
281
279
|
|
|
282
280
|
try {
|
|
283
|
-
|
|
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
|
+
}
|
|
284
289
|
|
|
285
290
|
if (this.#config.inspectorOptions) {
|
|
286
291
|
const { port } = this.#config.inspectorOptions
|
|
@@ -326,7 +331,9 @@ export class Runtime extends EventEmitter {
|
|
|
326
331
|
this.#startHealthMetricsCollectionIfNeeded()
|
|
327
332
|
|
|
328
333
|
await this.#dynamicWorkersScaler?.start()
|
|
329
|
-
this.#
|
|
334
|
+
if (this.#url) {
|
|
335
|
+
this.#showUrl()
|
|
336
|
+
}
|
|
330
337
|
return this.#url
|
|
331
338
|
}
|
|
332
339
|
|
|
@@ -400,11 +407,22 @@ export class Runtime extends EventEmitter {
|
|
|
400
407
|
}
|
|
401
408
|
|
|
402
409
|
if (this.logger) {
|
|
403
|
-
this.#loggerDestination
|
|
410
|
+
const loggerDestination = this.#loggerDestination
|
|
411
|
+
const loggerCloseables = this.#loggerContext?.closeables ?? []
|
|
404
412
|
|
|
405
413
|
this.logger = abstractLogger
|
|
406
414
|
this.#loggerDestination = null
|
|
407
415
|
this.#loggerContext = null
|
|
416
|
+
|
|
417
|
+
if (loggerDestination) {
|
|
418
|
+
loggerDestination.end()
|
|
419
|
+
await finished(loggerDestination).catch(() => {})
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
for (const closeable of loggerCloseables) {
|
|
423
|
+
closeable.end?.()
|
|
424
|
+
await finished(closeable).catch(() => {})
|
|
425
|
+
}
|
|
408
426
|
}
|
|
409
427
|
|
|
410
428
|
this.#updateStatus('closed')
|
|
@@ -570,7 +588,7 @@ export class Runtime extends EventEmitter {
|
|
|
570
588
|
|
|
571
589
|
// Now, topological sort the applications based on their dependencies.
|
|
572
590
|
// If circular dependencies are detected, an error with proper error code is thrown.
|
|
573
|
-
applications = topologicalSort(dependencies)
|
|
591
|
+
applications = topologicalSort(applications, dependencies)
|
|
574
592
|
|
|
575
593
|
// Group into dependency levels so that each level's dependencies are all
|
|
576
594
|
// in previous levels. Levels are started sequentially, but applications
|
|
@@ -1110,6 +1128,10 @@ export class Runtime extends EventEmitter {
|
|
|
1110
1128
|
}
|
|
1111
1129
|
|
|
1112
1130
|
async getEntrypointDetails () {
|
|
1131
|
+
if (!this.#entrypointId) {
|
|
1132
|
+
return null
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1113
1135
|
return this.getApplicationDetails(this.#entrypointId)
|
|
1114
1136
|
}
|
|
1115
1137
|
|
|
@@ -1156,6 +1178,10 @@ export class Runtime extends EventEmitter {
|
|
|
1156
1178
|
}
|
|
1157
1179
|
|
|
1158
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
|
+
|
|
1159
1185
|
let metrics = null
|
|
1160
1186
|
|
|
1161
1187
|
const applicationRestartMetrics = format === 'json'
|
|
@@ -1704,6 +1730,10 @@ export class Runtime extends EventEmitter {
|
|
|
1704
1730
|
}
|
|
1705
1731
|
|
|
1706
1732
|
#showUrl () {
|
|
1733
|
+
if (!this.#url) {
|
|
1734
|
+
return
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1707
1737
|
this.logger.info(`Platformatic is now listening at ${this.#url}`)
|
|
1708
1738
|
}
|
|
1709
1739
|
|
|
@@ -1722,7 +1752,7 @@ export class Runtime extends EventEmitter {
|
|
|
1722
1752
|
applicationConfig.path = join(this.#root, config.resolvedApplicationsBasePath, id)
|
|
1723
1753
|
|
|
1724
1754
|
if (!existsSync(applicationConfig.path)) {
|
|
1725
|
-
const executable =
|
|
1755
|
+
const executable = getExecutable() ?? 'platformatic'
|
|
1726
1756
|
this.logger.error(
|
|
1727
1757
|
`The path for application "%s" does not exist. Please run "${executable} resolve" and try again.`,
|
|
1728
1758
|
id
|
|
@@ -2701,6 +2731,12 @@ export class Runtime extends EventEmitter {
|
|
|
2701
2731
|
|
|
2702
2732
|
async #createWorkersBroadcastChannel () {
|
|
2703
2733
|
this.#workersBroadcastChannel?.close()
|
|
2734
|
+
|
|
2735
|
+
if (this.#config.applications.length === 0) {
|
|
2736
|
+
this.#workersBroadcastChannel = undefined
|
|
2737
|
+
return
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2704
2740
|
this.#workersBroadcastChannel = new BroadcastChannel(kWorkersBroadcast)
|
|
2705
2741
|
}
|
|
2706
2742
|
|
|
@@ -3243,6 +3279,15 @@ export class Runtime extends EventEmitter {
|
|
|
3243
3279
|
return argv
|
|
3244
3280
|
}
|
|
3245
3281
|
|
|
3282
|
+
// Starting from Node.js 25 the Permission Model also gates network access
|
|
3283
|
+
// (dns.lookup, server.listen, outbound connections and fetch) behind
|
|
3284
|
+
// --allow-net. Applications always need to bind their HTTP server and reach
|
|
3285
|
+
// other applications through the internal mesh, so we always grant it when
|
|
3286
|
+
// available. On older versions the flag does not exist and must be omitted.
|
|
3287
|
+
if (features.node.permission.network) {
|
|
3288
|
+
allows.add('--allow-net')
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3246
3291
|
// We need to allow read access to the node_modules folder both at the runtime level and at the application level
|
|
3247
3292
|
allows.add(`--allow-fs-read=${join(this.#root, 'node_modules', '*')}`)
|
|
3248
3293
|
allows.add(`--allow-fs-read=${join(applicationConfig.path, 'node_modules', '*')}`)
|
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
|
-
//
|
|
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
|
}
|