@platformatic/runtime 3.71.1-alpha.0 → 4.0.0-alpha.1
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 +602 -673
- package/index.d.ts +22 -27
- package/index.js +290 -30
- package/lib/config.js +270 -367
- package/lib/errors.js +27 -13
- package/lib/generator.js +249 -127
- package/lib/logger.js +3 -3
- package/lib/management-api.js +22 -65
- package/lib/management-handlers.js +3 -5
- package/lib/memory.js +29 -0
- package/lib/policies.js +27 -0
- package/lib/predictive-scaling.js +910 -0
- package/lib/predictive-worker-scaler.js +378 -0
- package/lib/runtime.js +1081 -572
- package/lib/scheduler.js +6 -41
- package/lib/schema.js +77 -15
- package/lib/upgrade.js +16 -1
- package/lib/versions/v4.0.0.js +31 -0
- package/lib/worker/controller.js +89 -109
- package/lib/worker/interceptors.js +34 -34
- package/lib/worker/itc.js +248 -66
- package/lib/worker/main.js +50 -98
- package/lib/worker/messaging.js +42 -1
- package/lib/worker/round-robin-map.js +10 -3
- package/lib/worker/symbols.js +6 -2
- package/package.json +29 -20
- package/schema.json +252 -433
- package/lib/scaling-algorithm.js +0 -256
- package/lib/worker-scaler.js +0 -249
package/index.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface LoopbackMessagingOptions {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export namespace errors {
|
|
40
|
+
export const AddressInUseError: (port: number, firstApplication: string, secondApplication: string) => FastifyError
|
|
41
|
+
export const WorkerAddressInUseError: (port: number, application: string) => FastifyError
|
|
40
42
|
export const RuntimeExitedError: () => FastifyError
|
|
41
43
|
export const UnknownRuntimeAPICommandError: (command: string) => FastifyError
|
|
42
44
|
export const ApplicationNotFoundError: (id: string) => FastifyError
|
|
@@ -46,8 +48,6 @@ export namespace errors {
|
|
|
46
48
|
export const RuntimeNotStartedError: () => FastifyError
|
|
47
49
|
export const ConfigPathMustBeStringError: () => FastifyError
|
|
48
50
|
export const NoConfigFileFoundError: (id: string) => FastifyError
|
|
49
|
-
export const InvalidEntrypointError: (entrypoint: string) => FastifyError
|
|
50
|
-
export const MissingEntrypointError: () => FastifyError
|
|
51
51
|
export const MissingDependencyError: (dependency: string) => FastifyError
|
|
52
52
|
export const InspectAndInspectBrkError: () => FastifyError
|
|
53
53
|
export const InspectorPortError: () => FastifyError
|
|
@@ -86,9 +86,9 @@ export namespace symbols {
|
|
|
86
86
|
export const kHealthCheckTimer: unique symbol
|
|
87
87
|
export const kHealthMetricsTimer: unique symbol
|
|
88
88
|
export const kLastHealthCheckELU: unique symbol
|
|
89
|
-
export const kLastWorkerScalerELU: unique symbol
|
|
90
89
|
export const kWorkerStatus: unique symbol
|
|
91
90
|
export const kWorkerHealthSignals: unique symbol
|
|
91
|
+
export const kWorkerUrl: unique symbol
|
|
92
92
|
export const kStderrMarker: string
|
|
93
93
|
export const kWorkersBroadcast: unique symbol
|
|
94
94
|
}
|
|
@@ -110,23 +110,29 @@ export interface InjectResponse {
|
|
|
110
110
|
rawPayload: ArrayBuffer
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
export type ServingState = 'listening' | 'mesh-only' | 'background' | 'inactive'
|
|
114
|
+
|
|
113
115
|
export interface ApplicationDetails {
|
|
114
116
|
id: string
|
|
115
117
|
type?: string
|
|
118
|
+
/** v3 only: the path to the application's configuration file. v4 reports configPath. */
|
|
116
119
|
config?: string
|
|
120
|
+
/** v4 only: the configuration file the loader decided on, absent for an inline definition. */
|
|
121
|
+
configPath?: string
|
|
117
122
|
path?: string
|
|
118
123
|
status?: string
|
|
124
|
+
/** How the application serves, as opposed to whether it runs. Absent when it is not started. */
|
|
125
|
+
servingState?: ServingState
|
|
119
126
|
dependencies?: string[]
|
|
120
127
|
version?: string
|
|
121
128
|
localUrl?: string
|
|
122
|
-
entrypoint?: boolean
|
|
123
129
|
sourceMaps?: boolean
|
|
124
130
|
workers?: number
|
|
125
131
|
url?: string | null
|
|
132
|
+
urls?: string[]
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
export interface ApplicationsTopology {
|
|
129
|
-
entrypoint: string
|
|
130
136
|
production: boolean
|
|
131
137
|
applications: ApplicationDetails[]
|
|
132
138
|
}
|
|
@@ -157,14 +163,6 @@ export interface SchedulerJobBase {
|
|
|
157
163
|
nextRunAt?: string | null
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
export interface ConfiguredSchedulerJob extends SchedulerJobBase {
|
|
161
|
-
source: 'config'
|
|
162
|
-
callbackUrl: string
|
|
163
|
-
method: string
|
|
164
|
-
headers?: Record<string, string>
|
|
165
|
-
body?: string | Record<string, unknown>
|
|
166
|
-
}
|
|
167
|
-
|
|
168
166
|
export interface ApplicationSchedulerJob extends SchedulerJobBase {
|
|
169
167
|
source: 'application'
|
|
170
168
|
applicationId: string
|
|
@@ -172,7 +170,7 @@ export interface ApplicationSchedulerJob extends SchedulerJobBase {
|
|
|
172
170
|
tasks: string[]
|
|
173
171
|
}
|
|
174
172
|
|
|
175
|
-
export type SchedulerJob =
|
|
173
|
+
export type SchedulerJob = ApplicationSchedulerJob
|
|
176
174
|
|
|
177
175
|
export interface SchedulerRunResult {
|
|
178
176
|
name: string
|
|
@@ -194,10 +192,14 @@ export interface RuntimeMetadata {
|
|
|
194
192
|
execPath: string
|
|
195
193
|
nodeVersion: string
|
|
196
194
|
projectDir: string
|
|
195
|
+
/** The configuration file the loader decided on. Null when the runtime was built from an object. */
|
|
196
|
+
configPath: string | null
|
|
197
|
+
/** The autoload declaration, with its path resolved. Null when the project does not autoload. */
|
|
198
|
+
autoload: { path: string, exclude?: string[], mappings?: Record<string, unknown> } | null
|
|
197
199
|
packageName: string | null
|
|
198
200
|
packageVersion: string | null
|
|
199
|
-
url: string | null
|
|
200
201
|
platformaticVersion: string
|
|
202
|
+
urls: Record<string, string>
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
export interface WorkerLifecycleEvent {
|
|
@@ -234,9 +236,7 @@ export interface HealthMetricsEvent extends WorkerLifecycleEvent {
|
|
|
234
236
|
healthSignals: RuntimeHealthSignal[]
|
|
235
237
|
}
|
|
236
238
|
|
|
237
|
-
export
|
|
238
|
-
constructor (allowedOperations?: string[])
|
|
239
|
-
|
|
239
|
+
export interface ManagementClient {
|
|
240
240
|
getRuntimeStatus (): Promise<string>
|
|
241
241
|
getRuntimeMetadata (): Promise<RuntimeMetadata>
|
|
242
242
|
getRuntimeConfig (): Promise<Record<string, unknown>>
|
|
@@ -258,7 +258,7 @@ export declare class ManagementClient {
|
|
|
258
258
|
startApplication (id: string): Promise<void>
|
|
259
259
|
stopApplication (id: string): Promise<void>
|
|
260
260
|
restartApplication (id: string): Promise<void>
|
|
261
|
-
restart (applications?: string[]): Promise<
|
|
261
|
+
restart (applications?: string[]): Promise<void>
|
|
262
262
|
addApplications (applications: unknown[], start?: boolean): Promise<ApplicationDetails[]>
|
|
263
263
|
removeApplications (ids: string[]): Promise<ApplicationDetails[]>
|
|
264
264
|
inject (id: string, injectParams: InjectParams): Promise<InjectResponse>
|
|
@@ -370,12 +370,11 @@ export type RuntimeExtension = (
|
|
|
370
370
|
|
|
371
371
|
export declare class Runtime extends EventEmitter {
|
|
372
372
|
init (): Promise<void>
|
|
373
|
-
start (silent?: boolean): Promise<string
|
|
373
|
+
start (silent?: boolean): Promise<Record<string, string>>
|
|
374
374
|
stop (silent?: boolean): Promise<void>
|
|
375
375
|
close (silent?: boolean): Promise<void>
|
|
376
|
-
restart (applications?: string[]): Promise<
|
|
376
|
+
restart (applications?: string[]): Promise<void>
|
|
377
377
|
inject (id: string, injectParams: InjectParams): Promise<InjectResponse>
|
|
378
|
-
getUrl (): string | undefined
|
|
379
378
|
getRuntimeStatus (): string
|
|
380
379
|
getRuntimeMetadata (): Promise<RuntimeMetadata>
|
|
381
380
|
getRuntimeEnv (): Record<string, string>
|
|
@@ -386,6 +385,7 @@ export declare class Runtime extends EventEmitter {
|
|
|
386
385
|
getRuntimeConfig (includeMeta: true): RuntimeConfiguration
|
|
387
386
|
getRuntimeConfig (includeMeta?: boolean): Record<string, unknown>
|
|
388
387
|
getApplicationsIds (): string[]
|
|
388
|
+
getUrls (applicationId?: string): Record<string, string>
|
|
389
389
|
/**
|
|
390
390
|
* Returns topology for every configured application.
|
|
391
391
|
* When `allowUnloaded` is `true`, applications without running workers are
|
|
@@ -472,11 +472,6 @@ export declare class Runtime extends EventEmitter {
|
|
|
472
472
|
runApplicationScheduledTasks (id: string, scheduleId: string, scheduledTime: number): Promise<unknown>
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
export function wrapInRuntimeConfig (
|
|
476
|
-
config: Configuration<unknown>,
|
|
477
|
-
context?: ConfigurationOptions
|
|
478
|
-
): Promise<RuntimeConfiguration>
|
|
479
|
-
|
|
480
475
|
export declare const version: string
|
|
481
476
|
|
|
482
477
|
export declare function loadConfiguration (
|
package/index.js
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { importCapabilityPackage, resolve } from '@platformatic/basic'
|
|
2
2
|
import {
|
|
3
3
|
abstractLogger,
|
|
4
4
|
ensureLoggableError,
|
|
5
|
-
extractModuleFromSchemaUrl,
|
|
6
5
|
findRuntimeConfigurationFile,
|
|
7
6
|
kMetadata,
|
|
8
|
-
loadConfigurationModule
|
|
9
|
-
loadConfiguration as utilsLoadConfiguration
|
|
7
|
+
loadConfigurationModule
|
|
10
8
|
} from '@platformatic/foundation'
|
|
9
|
+
import {
|
|
10
|
+
findDecidingFile,
|
|
11
|
+
isProductionCommand,
|
|
12
|
+
LegacyConfigurationFileError,
|
|
13
|
+
loadConfiguration as loadFileConfiguration,
|
|
14
|
+
loadObjectConfiguration,
|
|
15
|
+
validateCapabilityConfiguration
|
|
16
|
+
} from '@platformatic/foundation/loader'
|
|
11
17
|
import closeWithGrace from 'close-with-grace'
|
|
18
|
+
import { stat } from 'node:fs/promises'
|
|
12
19
|
import inspector from 'node:inspector'
|
|
13
|
-
import {
|
|
20
|
+
import { dirname, resolve as resolvePath } from 'node:path'
|
|
21
|
+
import { transformConfiguration } from './lib/config.js'
|
|
14
22
|
import { NodeInspectorFlagsNotSupportedError } from './lib/errors.js'
|
|
15
23
|
import { Runtime } from './lib/runtime.js'
|
|
16
|
-
import {
|
|
17
|
-
import { upgrade } from './lib/upgrade.js'
|
|
24
|
+
import { configurationSchema } from './lib/schema.js'
|
|
18
25
|
|
|
19
26
|
async function restartRuntime (runtime) {
|
|
20
27
|
runtime.logger.info('Received SIGUSR2, restarting all applications ...')
|
|
@@ -59,34 +66,264 @@ function handleSignal (runtime, config) {
|
|
|
59
66
|
})
|
|
60
67
|
}
|
|
61
68
|
|
|
69
|
+
/*
|
|
70
|
+
The configuration is code, evaluated by the loader in foundation: it walks to the deciding file,
|
|
71
|
+
resolves both views of the env ladder, evaluates the root in a worker and fans out one worker per
|
|
72
|
+
per-app file. Everything the runtime transform used to discover — the application list, each
|
|
73
|
+
application's capability, its schema and its worker environment — arrives already resolved.
|
|
74
|
+
|
|
75
|
+
Routing by filename keeps the legacy path intact while the in-tree JSON fixtures are converted; the
|
|
76
|
+
legacy half is what a later commit deletes, not something this one has to keep working around.
|
|
77
|
+
*/
|
|
78
|
+
/*
|
|
79
|
+
`--config` names a configuration; it does not widen what a configuration may be called. The four
|
|
80
|
+
names are the format, and `resolveNamedConfigurationFile` is what refuses anything else -- it
|
|
81
|
+
separates a legacy name, which gets the migrate hint, from an unrecognised one, which gets told
|
|
82
|
+
what the four names are. Deciding here instead produced one of those answers for both.
|
|
83
|
+
|
|
84
|
+
What `--config` does change is *where*: a named file need not sit where discovery would look.
|
|
85
|
+
*/
|
|
86
|
+
function isNamedConfigurationPath (path) {
|
|
87
|
+
return /\.(json|json5|ya?ml|to?ml|js|mjs|ts|mts)$/.test(path)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function findConfigurationForSource (configOrRoot, sourceOrConfig) {
|
|
91
|
+
// A programmatic object source is not this path: the object entry point is
|
|
92
|
+
// loadObjectConfiguration, which skips the root eval worker entirely.
|
|
93
|
+
if (sourceOrConfig && typeof sourceOrConfig !== 'string') {
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof sourceOrConfig === 'string') {
|
|
98
|
+
const named = resolvePath(configOrRoot, sourceOrConfig)
|
|
99
|
+
|
|
100
|
+
// Named outright: the loader decides whether the name is one it accepts, and says which it is.
|
|
101
|
+
return isNamedConfigurationPath(named) ? named : null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof configOrRoot !== 'string') {
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isNamedConfigurationPath(configOrRoot)) {
|
|
109
|
+
return configOrRoot
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
Only an actual directory is a place to search from. findDecidingFile walks toward the env root,
|
|
114
|
+
so handing it a path that does not exist would answer with whatever project sits above it: a
|
|
115
|
+
typo'd --config would boot the parent rather than failing. Discovery looks in the named directory
|
|
116
|
+
and nowhere else, and a wrong path has to stay an error.
|
|
117
|
+
*/
|
|
118
|
+
const stats = await stat(configOrRoot).catch(() => null)
|
|
119
|
+
|
|
120
|
+
if (!stats?.isDirectory()) {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
A legacy configuration found by the walk is refused, with the error that names migrate. It used to
|
|
126
|
+
be swallowed so the legacy loader below could answer instead; that loader is gone, and the refusal
|
|
127
|
+
is the correct answer again.
|
|
128
|
+
*/
|
|
129
|
+
const deciding = await findDecidingFile(configOrRoot, { throwOnMissing: false })
|
|
130
|
+
|
|
131
|
+
return deciding?.path ?? null
|
|
132
|
+
}
|
|
133
|
+
|
|
62
134
|
export async function loadConfiguration (configOrRoot, sourceOrConfig, context) {
|
|
135
|
+
// Checked before the legacy resolver, which throws when it finds no legacy file — a project has
|
|
136
|
+
// none by construction.
|
|
137
|
+
const decidingConfigurationFile = await findConfigurationForSource(configOrRoot, sourceOrConfig)
|
|
138
|
+
|
|
139
|
+
if (decidingConfigurationFile) {
|
|
140
|
+
return loadRuntimeConfiguration(decidingConfigurationFile, context)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/*
|
|
144
|
+
Level 0. A directory with no configuration file of any kind is not an error: the loader
|
|
145
|
+
synthesizes one in memory from what the directory contains. Reaching the legacy resolver instead
|
|
146
|
+
made a configless project fail to load, which is what the removed temporary-file fallback --
|
|
147
|
+
detect the type, write a watt.json into the user's tree, then load that -- existed to paper
|
|
148
|
+
over. Nothing is written to disk now.
|
|
149
|
+
*/
|
|
150
|
+
if (!sourceOrConfig && typeof configOrRoot === 'string' && !isNamedConfigurationPath(configOrRoot)) {
|
|
151
|
+
const stats = await stat(configOrRoot).catch(() => null)
|
|
152
|
+
|
|
153
|
+
if (stats?.isDirectory()) {
|
|
154
|
+
return loadRuntimeConfiguration({ cwd: configOrRoot }, context)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
63
158
|
const { root, source } = await resolve(configOrRoot, sourceOrConfig, 'runtime')
|
|
64
159
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
root
|
|
68
|
-
|
|
69
|
-
|
|
160
|
+
/*
|
|
161
|
+
A configuration handed over as an object rather than named as a file: what an embedder builds in
|
|
162
|
+
memory, and what the ICC generates. There is nothing to evaluate, so it skips the root eval
|
|
163
|
+
worker and joins the pipeline at validation.
|
|
164
|
+
*/
|
|
165
|
+
if (source && typeof source !== 'string') {
|
|
166
|
+
return loadRuntimeConfiguration({ root, source }, context)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
A path that reaches here named a file the walk did not recognise as configuration, so the walk's
|
|
171
|
+
own refusal is the answer -- it names the file and tells the reader to run migrate, which is more
|
|
172
|
+
use than loading a legacy configuration into a runtime that no longer implements what it says.
|
|
173
|
+
*/
|
|
174
|
+
throw new LegacyConfigurationFileError(source)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/*
|
|
178
|
+
`target` is either a configuration file to evaluate or `{ root, source }` for a configuration
|
|
179
|
+
handed over as an object -- what an embedder builds in memory and what the ICC generates. The two
|
|
180
|
+
differ only in where the configuration comes from: an object skips the root eval worker, since
|
|
181
|
+
there is nothing to evaluate, and everything after that is the same pipeline.
|
|
182
|
+
*/
|
|
183
|
+
async function loadRuntimeConfiguration (target, context) {
|
|
184
|
+
/*
|
|
185
|
+
The command decides `production` when the caller did not, rather than the other way round:
|
|
186
|
+
`build` produces production artifacts, so a build that evaluated as a development boot read the
|
|
187
|
+
development env files and gave every callback `production: false` while writing what `start`
|
|
188
|
+
would later run.
|
|
189
|
+
*/
|
|
190
|
+
const diagnostics = context?.logger ?? abstractLogger
|
|
191
|
+
const explicitProduction = context?.isProduction ?? context?.production
|
|
192
|
+
const command = context?.command ?? (explicitProduction ? 'start' : 'dev')
|
|
193
|
+
const production = explicitProduction ?? isProductionCommand(command)
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
The environment the loader treats as real. An embedder can add to it, and can ask for a
|
|
197
|
+
hermetic runtime that sees none of this process's -- the loader honours both through loadEnv, and
|
|
198
|
+
a runtime that ignored them would quietly hand every worker the parent's environment.
|
|
199
|
+
*/
|
|
200
|
+
const realEnv = context?.ignoreProcessEnv ? { ...context?.env } : { ...process.env, ...context?.env }
|
|
70
201
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
202
|
+
const objectSource = typeof target !== 'string' && target.source !== undefined
|
|
203
|
+
const shared = {
|
|
204
|
+
realEnv,
|
|
205
|
+
command,
|
|
206
|
+
mode: context?.mode,
|
|
207
|
+
production,
|
|
208
|
+
customEnvFile: context?.envFile,
|
|
209
|
+
/*
|
|
210
|
+
Step 3 of the pipeline validates orchestration in the eval worker, before autoload expansion
|
|
211
|
+
acts on it -- without the schema that step is skipped, and a malformed `autoload` reaches the
|
|
212
|
+
filesystem walk as a raw TypeError instead of a validation error naming the property.
|
|
213
|
+
*/
|
|
214
|
+
schema: context?.schema ?? configurationSchema,
|
|
215
|
+
/*
|
|
216
|
+
The evaluation deadline. A configuration that never resolves — an awaited fetch to a dead
|
|
217
|
+
host, a forgotten promise — otherwise hangs the boot rather than failing it, and the default
|
|
218
|
+
is a guess about how long a reasonable one takes. A deployment that knows better says so.
|
|
219
|
+
*/
|
|
220
|
+
timeout: context?.configTimeout,
|
|
221
|
+
// The one file --debug-config evaluates in this process when an inspector is attached, so a
|
|
222
|
+
// breakpoint in it is reachable at all: a throwaway worker dies before one can attach.
|
|
223
|
+
inProcessTarget: context?.inProcessTarget,
|
|
224
|
+
/*
|
|
225
|
+
Validation imports each capability's schema, so it needs the capability installed — not
|
|
226
|
+
merely declared, which is all the detector needs. That moves the moment a capability must be
|
|
227
|
+
resolvable earlier than it once had to be: loading used to succeed and fail later, in the worker.
|
|
228
|
+
|
|
229
|
+
A caller that is loading only to discover the topology — a tool about to install those
|
|
230
|
+
dependencies, for instance — can say so rather than being made to install first.
|
|
231
|
+
*/
|
|
232
|
+
validateCapabilities: context?.validateCapabilities ?? true,
|
|
233
|
+
// The runtime is where the bundled capability copies live, so it is the fallback scope for
|
|
234
|
+
// both the schema import and the version stamp — the application's own dependencies still
|
|
235
|
+
// come first.
|
|
236
|
+
runtimeScope: import.meta.filename,
|
|
237
|
+
/*
|
|
238
|
+
These are the loader's user-facing diagnostics — the standalone-boot warning, the zero-config
|
|
239
|
+
inference notice, the capability the detector chose — and they were being handed to
|
|
240
|
+
abstractLogger, which is a no-op. Every one of them was discarded, including the warning the
|
|
241
|
+
format relies on to tell someone that nothing the root says is being applied.
|
|
242
|
+
|
|
243
|
+
A caller with a real logger passes it. The default stays silent because an embedder loading a
|
|
244
|
+
configuration has not asked for anything on its stderr.
|
|
245
|
+
*/
|
|
246
|
+
onWarning: warning => diagnostics.warn(warning.message),
|
|
247
|
+
onInfo: info => diagnostics.info(info.message)
|
|
75
248
|
}
|
|
76
249
|
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
|
|
250
|
+
let loaded
|
|
251
|
+
if (objectSource) {
|
|
252
|
+
loaded = await loadObjectConfiguration({ root: target.root, source: target.source, ...shared })
|
|
253
|
+
} else if (typeof target === 'string') {
|
|
254
|
+
loaded = await loadFileConfiguration({ cwd: dirname(target), configPath: target, ...shared })
|
|
255
|
+
} else {
|
|
256
|
+
// No file was named and none was found: the loader searches from this directory and, finding
|
|
257
|
+
// nothing, synthesizes.
|
|
258
|
+
loaded = await loadFileConfiguration({ cwd: target.cwd, ...shared })
|
|
80
259
|
}
|
|
81
260
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
261
|
+
const config = loaded.config
|
|
262
|
+
|
|
263
|
+
/*
|
|
264
|
+
The documented pipeline is validation with useDefaults, then kMetadata, then transform. The eval
|
|
265
|
+
worker's own check is a shape check that injects nothing — deliberately, so the resolve
|
|
266
|
+
projection carries authored values — so this is where the runtime schema's defaults arrive, and
|
|
267
|
+
without it the runtime reaches for settings like gracefulShutdown that nothing supplied.
|
|
268
|
+
*/
|
|
269
|
+
validateCapabilityConfiguration(config, context?.schema ?? configurationSchema, {
|
|
270
|
+
id: 'runtime',
|
|
271
|
+
module: '@platformatic/runtime',
|
|
272
|
+
root: loaded.root
|
|
89
273
|
})
|
|
274
|
+
|
|
275
|
+
// kMetadata is symbol-keyed and cannot cross a worker boundary, so each worker rebuilds it; this
|
|
276
|
+
// is the main process's copy. env is the runtime's own view — the real environment under the root
|
|
277
|
+
// env block and the root's env files — and every application worker gets the per-application
|
|
278
|
+
// environment the loader resolved instead, which this one is not a substitute for.
|
|
279
|
+
config[kMetadata] = {
|
|
280
|
+
root: loaded.root,
|
|
281
|
+
path: loaded.configPath,
|
|
282
|
+
env: loaded.env ?? process.env,
|
|
283
|
+
module: '@platformatic/runtime',
|
|
284
|
+
/*
|
|
285
|
+
The evaluation context, kept beside the envelope rather than inside it: applications added
|
|
286
|
+
after boot are evaluated with the same command, mode and production flags the boot pass used,
|
|
287
|
+
and its presence is also what tells the add path this runtime resolves configuration main-side.
|
|
288
|
+
A legacy runtime has no such context and keeps resolving configuration files worker-side.
|
|
289
|
+
*/
|
|
290
|
+
loader: {
|
|
291
|
+
command,
|
|
292
|
+
mode: loaded.mode,
|
|
293
|
+
production: loaded.production,
|
|
294
|
+
/*
|
|
295
|
+
The environment contract of the boot, carried so an application added while the runtime
|
|
296
|
+
runs is enveloped under the same one: the realEnv the boot resolved ladders against
|
|
297
|
+
(hermetic under ignoreProcessEnv, augmented by context.env) and the --env file that
|
|
298
|
+
replaces every application's env-file rung. Without them the add path defaulted to
|
|
299
|
+
process.env and the conventional .env chain -- an environment the boot says cannot exist.
|
|
300
|
+
*/
|
|
301
|
+
realEnv,
|
|
302
|
+
customEnvFile: context?.envFile,
|
|
303
|
+
/*
|
|
304
|
+
Everything the evaluation actually read: the configuration files, their import graph minus
|
|
305
|
+
node_modules, the env files consulted, and the directories whose membership decides the
|
|
306
|
+
application list. `wattpm dev` reloads on any of it -- watching only the deciding file meant
|
|
307
|
+
a config split across a helper module reloaded for none of its own edits.
|
|
308
|
+
*/
|
|
309
|
+
watchTargets: loaded.watchTargets,
|
|
310
|
+
/*
|
|
311
|
+
Every entry carrying a `url`, recorded on the way past -- before the enabled filter, which
|
|
312
|
+
is the point. `enabled` is the supported way to keep an application out of a boot, and an
|
|
313
|
+
entry it hides from `resolve` is one whose clone never arrives.
|
|
314
|
+
*/
|
|
315
|
+
resolveCandidates: loaded.resolveCandidates
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/*
|
|
320
|
+
A caller-supplied transform replaces the built-in one, exactly as it does on the legacy path where
|
|
321
|
+
it arrives as part of ...context. Ignoring it here meant a test helper or an embedder that
|
|
322
|
+
customized the configuration was silently not consulted.
|
|
323
|
+
*/
|
|
324
|
+
const apply = context?.transform ?? transformConfiguration
|
|
325
|
+
|
|
326
|
+
return apply(config, null, context)
|
|
90
327
|
}
|
|
91
328
|
|
|
92
329
|
export async function loadApplicationsCommands (executableName = '', configurationFile = null) {
|
|
@@ -111,7 +348,13 @@ export async function loadApplicationsCommands (executableName = '', configurati
|
|
|
111
348
|
throw new Error('No runtime configuration file found.')
|
|
112
349
|
}
|
|
113
350
|
|
|
114
|
-
|
|
351
|
+
/*
|
|
352
|
+
'exec' is every non-boot evaluation, and enumerating a capability's commands is one: nothing
|
|
353
|
+
starts. Without it this evaluated as a development boot — development env files, and
|
|
354
|
+
`production: false` handed to every callback — which is a context no invocation of these
|
|
355
|
+
commands actually runs in.
|
|
356
|
+
*/
|
|
357
|
+
config = await loadConfiguration(file, null, { command: 'exec' })
|
|
115
358
|
|
|
116
359
|
/* c8 ignore next 3 - Hard to test */
|
|
117
360
|
if (!config) {
|
|
@@ -123,8 +366,12 @@ export async function loadApplicationsCommands (executableName = '', configurati
|
|
|
123
366
|
|
|
124
367
|
for (const application of config.applications) {
|
|
125
368
|
try {
|
|
126
|
-
|
|
127
|
-
|
|
369
|
+
// An application arrives with its configuration already evaluated and its capability
|
|
370
|
+
// already named, so there is nothing to read and nothing to infer.
|
|
371
|
+
const applicationConfig = application.resolvedConfig
|
|
372
|
+
const pkg = application.module
|
|
373
|
+
? await importCapabilityPackage(application.path, application.module)
|
|
374
|
+
: await loadConfigurationModule(application.path, applicationConfig)
|
|
128
375
|
|
|
129
376
|
if (pkg.createCommands) {
|
|
130
377
|
const definition = await pkg.createCommands(application.id)
|
|
@@ -175,7 +422,20 @@ export async function create (configOrRoot, sourceOrConfig, context) {
|
|
|
175
422
|
return runtime
|
|
176
423
|
}
|
|
177
424
|
|
|
178
|
-
|
|
425
|
+
/*
|
|
426
|
+
The one entry point for an application added while the runtime is running -- what an extension
|
|
427
|
+
calls before `addApplications`. It replaces the exported `prepareApplication`, which is the legacy
|
|
428
|
+
half of the same job: an added application has to be evaluated the way boot evaluates
|
|
429
|
+
one, and an entry that skips that arrives without `resolvedConfig` and fails as an unhelpful
|
|
430
|
+
"unable to initialize the worker".
|
|
431
|
+
*/
|
|
432
|
+
export { prepareAddedApplications } from './lib/config.js'
|
|
433
|
+
|
|
434
|
+
// The one transform there is: the legacy one died with its last caller, the wizard's legacy-JSON
|
|
435
|
+
// branch, and the dispatch that told the two apart went with it.
|
|
436
|
+
export async function transform (config, schema, context) {
|
|
437
|
+
return transformConfiguration(config, schema, context)
|
|
438
|
+
}
|
|
179
439
|
export * as errors from './lib/errors.js'
|
|
180
440
|
export { RuntimeGenerator as Generator, WrappedGenerator } from './lib/generator.js'
|
|
181
441
|
export { setupLoopbackMessaging } from './lib/loopback-messaging.js'
|