@platformatic/basic 3.0.0-alpha.5 → 3.0.0-alpha.8
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 -5
- package/eslint.config.js +1 -3
- package/index.d.ts +2 -10
- package/index.js +1 -1
- package/lib/{base.js → capability.js} +183 -22
- package/lib/config.js +1 -1
- package/lib/creation.js +3 -3
- package/lib/errors.js +1 -1
- package/lib/modules.js +16 -16
- package/lib/schema.js +1 -1
- package/lib/worker/child-manager.js +2 -2
- package/lib/worker/child-process.js +14 -7
- package/lib/worker/child-transport.js +2 -2
- package/package.json +8 -9
- package/schema.json +244 -8
package/config.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export interface PlatformaticBasicConfig {
|
|
|
10
10
|
runtime?: {
|
|
11
11
|
preload?: string | string[];
|
|
12
12
|
basePath?: string;
|
|
13
|
+
services?: {
|
|
14
|
+
[k: string]: unknown;
|
|
15
|
+
}[];
|
|
13
16
|
workers?: number | string;
|
|
14
17
|
logger?: {
|
|
15
18
|
level: (
|
|
@@ -97,7 +100,7 @@ export interface PlatformaticBasicConfig {
|
|
|
97
100
|
restartOnError?: boolean | number;
|
|
98
101
|
gracefulShutdown?: {
|
|
99
102
|
runtime: number | string;
|
|
100
|
-
|
|
103
|
+
application: number | string;
|
|
101
104
|
};
|
|
102
105
|
health?: {
|
|
103
106
|
enabled?: boolean | string;
|
|
@@ -216,11 +219,11 @@ export interface PlatformaticBasicConfig {
|
|
|
216
219
|
telemetry?: {
|
|
217
220
|
enabled?: boolean | string;
|
|
218
221
|
/**
|
|
219
|
-
* The name of the
|
|
222
|
+
* The name of the application. Defaults to the folder name if not specified.
|
|
220
223
|
*/
|
|
221
|
-
|
|
224
|
+
applicationName: string;
|
|
222
225
|
/**
|
|
223
|
-
* The version of the
|
|
226
|
+
* The version of the application (optional)
|
|
224
227
|
*/
|
|
225
228
|
version?: string;
|
|
226
229
|
/**
|
|
@@ -296,7 +299,7 @@ export interface PlatformaticBasicConfig {
|
|
|
296
299
|
watchDisabled?: boolean;
|
|
297
300
|
[k: string]: unknown;
|
|
298
301
|
};
|
|
299
|
-
|
|
302
|
+
applicationTimeout?: number | string;
|
|
300
303
|
messagingTimeout?: number | string;
|
|
301
304
|
env?: {
|
|
302
305
|
[k: string]: string;
|
package/eslint.config.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -2,14 +2,8 @@ export interface StartOptions {
|
|
|
2
2
|
listen?: boolean
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export interface Dependency {
|
|
6
|
-
id: string
|
|
7
|
-
url?: string
|
|
8
|
-
local: boolean
|
|
9
|
-
}
|
|
10
|
-
|
|
11
5
|
export type BaseContext = Partial<{
|
|
12
|
-
|
|
6
|
+
applicationId: string
|
|
13
7
|
isEntrypoint: boolean
|
|
14
8
|
isProduction: boolean
|
|
15
9
|
isStandalone: boolean
|
|
@@ -18,7 +12,6 @@ export type BaseContext = Partial<{
|
|
|
18
12
|
metricsConfig: object
|
|
19
13
|
serverConfig: object
|
|
20
14
|
hasManagementApi: boolean
|
|
21
|
-
localServiceEnvVars: Map<string, string>
|
|
22
15
|
}>
|
|
23
16
|
|
|
24
17
|
export interface BaseOptions<Context = BaseContext> {
|
|
@@ -27,7 +20,7 @@ export interface BaseOptions<Context = BaseContext> {
|
|
|
27
20
|
|
|
28
21
|
export declare const schemaOptions: Partial<Record<string, unknown>>
|
|
29
22
|
|
|
30
|
-
export class
|
|
23
|
+
export class BaseCapability<Config = Record<string, any>, Options = BaseOptions> {
|
|
31
24
|
basePath: string
|
|
32
25
|
constructor (
|
|
33
26
|
type: string,
|
|
@@ -76,7 +69,6 @@ export class BaseStackable<Config = Record<string, any>, Options = BaseOptions>
|
|
|
76
69
|
body: object
|
|
77
70
|
}>
|
|
78
71
|
log (options: { message: string; level: string }): Promise<void>
|
|
79
|
-
getBootstrapDependencies (): Promise<Dependency[]>
|
|
80
72
|
getWatchConfig (): Promise<{
|
|
81
73
|
enabled: boolean
|
|
82
74
|
path: string
|
package/index.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildPinoOptions,
|
|
3
|
+
deepmerge,
|
|
4
|
+
executeWithTimeout,
|
|
5
|
+
kHandledError,
|
|
6
|
+
kMetadata,
|
|
7
|
+
kTimeout
|
|
8
|
+
} from '@platformatic/foundation'
|
|
2
9
|
import { client, collectMetrics, ensureMetricsGroup } from '@platformatic/metrics'
|
|
3
10
|
import { parseCommandString } from 'execa'
|
|
4
11
|
import { spawn } from 'node:child_process'
|
|
@@ -13,17 +20,45 @@ import { cleanBasePath } from './utils.js'
|
|
|
13
20
|
import { ChildManager } from './worker/child-manager.js'
|
|
14
21
|
const kITC = Symbol.for('plt.runtime.itc')
|
|
15
22
|
|
|
16
|
-
export class
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
export class BaseCapability extends EventEmitter {
|
|
24
|
+
status
|
|
25
|
+
type
|
|
26
|
+
version
|
|
27
|
+
root
|
|
28
|
+
config
|
|
29
|
+
context
|
|
30
|
+
standardStreams
|
|
31
|
+
|
|
32
|
+
applicationId
|
|
33
|
+
workerId
|
|
34
|
+
telemetryConfig
|
|
35
|
+
serverConfig
|
|
36
|
+
openapiSchema
|
|
37
|
+
graphqlSchema
|
|
38
|
+
connectionString
|
|
39
|
+
basePath
|
|
40
|
+
isEntrypoint
|
|
41
|
+
isProduction
|
|
42
|
+
dependencies
|
|
43
|
+
customHealthCheck
|
|
44
|
+
customReadinessCheck
|
|
45
|
+
clientWs
|
|
46
|
+
runtimeConfig
|
|
47
|
+
stdout
|
|
48
|
+
stderr
|
|
19
49
|
subprocessForceClose
|
|
20
50
|
subprocessTerminationSignal
|
|
51
|
+
logger
|
|
52
|
+
metricsRegistr
|
|
53
|
+
|
|
21
54
|
#subprocessStarted
|
|
22
55
|
#metricsCollected
|
|
56
|
+
#pendingDependenciesWaits
|
|
23
57
|
|
|
24
58
|
constructor (type, version, root, config, context, standardStreams = {}) {
|
|
25
59
|
super()
|
|
26
60
|
|
|
61
|
+
this.status = ''
|
|
27
62
|
this.type = type
|
|
28
63
|
this.version = version
|
|
29
64
|
this.root = root
|
|
@@ -32,7 +67,7 @@ export class BaseStackable extends EventEmitter {
|
|
|
32
67
|
this.context.worker ??= { count: 1, index: 0 }
|
|
33
68
|
this.standardStreams = standardStreams
|
|
34
69
|
|
|
35
|
-
this.
|
|
70
|
+
this.applicationId = this.context.applicationId
|
|
36
71
|
this.workerId = this.context.worker.count > 1 ? this.context.worker.index : undefined
|
|
37
72
|
this.telemetryConfig = this.context.telemetryConfig
|
|
38
73
|
this.serverConfig = deepmerge(this.context.serverConfig ?? {}, config.server ?? {})
|
|
@@ -42,7 +77,7 @@ export class BaseStackable extends EventEmitter {
|
|
|
42
77
|
this.basePath = null
|
|
43
78
|
this.isEntrypoint = this.context.isEntrypoint
|
|
44
79
|
this.isProduction = this.context.isProduction
|
|
45
|
-
this
|
|
80
|
+
this.dependencies = this.context.dependencies ?? []
|
|
46
81
|
this.customHealthCheck = null
|
|
47
82
|
this.customReadinessCheck = null
|
|
48
83
|
this.clientWs = null
|
|
@@ -51,12 +86,12 @@ export class BaseStackable extends EventEmitter {
|
|
|
51
86
|
this.stderr = standardStreams?.stderr ?? process.stderr
|
|
52
87
|
this.subprocessForceClose = false
|
|
53
88
|
this.subprocessTerminationSignal = 'SIGINT'
|
|
54
|
-
|
|
55
89
|
this.logger = this._initializeLogger()
|
|
56
90
|
|
|
57
91
|
// Setup globals
|
|
58
92
|
this.registerGlobals({
|
|
59
|
-
|
|
93
|
+
capability: this,
|
|
94
|
+
applicationId: this.applicationId,
|
|
60
95
|
workerId: this.workerId,
|
|
61
96
|
logLevel: this.logger.level,
|
|
62
97
|
// Always use URL to avoid serialization problem in Windows
|
|
@@ -79,10 +114,25 @@ export class BaseStackable extends EventEmitter {
|
|
|
79
114
|
this.metricsRegistry = new client.Registry()
|
|
80
115
|
this.registerGlobals({ prometheus: { client, registry: this.metricsRegistry } })
|
|
81
116
|
}
|
|
117
|
+
|
|
118
|
+
this.#metricsCollected = false
|
|
119
|
+
this.#pendingDependenciesWaits = new Set()
|
|
82
120
|
}
|
|
83
121
|
|
|
84
|
-
init () {
|
|
85
|
-
|
|
122
|
+
async init () {
|
|
123
|
+
if (this.status) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Wait for explicit dependencies to start
|
|
128
|
+
await this.waitForDependenciesStart(this.dependencies)
|
|
129
|
+
|
|
130
|
+
if (this.status === 'stopped') {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await this.updateContext()
|
|
135
|
+
this.status = 'init'
|
|
86
136
|
}
|
|
87
137
|
|
|
88
138
|
updateContext (_context) {
|
|
@@ -90,11 +140,15 @@ export class BaseStackable extends EventEmitter {
|
|
|
90
140
|
}
|
|
91
141
|
|
|
92
142
|
start () {
|
|
93
|
-
throw new Error('
|
|
143
|
+
throw new Error('BaseCapability.start must be overriden by the subclasses')
|
|
94
144
|
}
|
|
95
145
|
|
|
96
|
-
stop () {
|
|
97
|
-
|
|
146
|
+
async stop () {
|
|
147
|
+
if (this.#pendingDependenciesWaits.size > 0) {
|
|
148
|
+
await Promise.allSettled(this.#pendingDependenciesWaits)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.status = 'stopped'
|
|
98
152
|
}
|
|
99
153
|
|
|
100
154
|
build () {
|
|
@@ -107,7 +161,107 @@ export class BaseStackable extends EventEmitter {
|
|
|
107
161
|
}
|
|
108
162
|
|
|
109
163
|
inject () {
|
|
110
|
-
throw new Error('
|
|
164
|
+
throw new Error('BaseCapability.inject must be overriden by the subclasses')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async waitForDependenciesStart (dependencies = []) {
|
|
168
|
+
if (!globalThis[kITC]) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const pending = new Set(dependencies)
|
|
173
|
+
|
|
174
|
+
// Ask the runtime the status of the dependencies and don't wait if they are already started
|
|
175
|
+
const workers = await globalThis[kITC].send('getWorkers')
|
|
176
|
+
|
|
177
|
+
for (const worker of Object.values(workers)) {
|
|
178
|
+
if (this.dependencies.includes(worker.application) && worker.status === 'started') {
|
|
179
|
+
pending.delete(worker.application)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!pending.size) {
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.logger.info({ dependencies: Array.from(pending) }, 'Waiting for dependencies to start.')
|
|
188
|
+
|
|
189
|
+
const { promise, resolve, reject } = Promise.withResolvers()
|
|
190
|
+
|
|
191
|
+
function runtimeEventHandler ({ event, payload }) {
|
|
192
|
+
if (event !== 'application:worker:started') {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
pending.delete(payload.application)
|
|
197
|
+
|
|
198
|
+
if (pending.size === 0) {
|
|
199
|
+
cleanupEvents()
|
|
200
|
+
resolve()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function stopHandler () {
|
|
205
|
+
cleanupEvents()
|
|
206
|
+
|
|
207
|
+
const error = new Error('One of the service dependencies was unable to start.')
|
|
208
|
+
error.dependencies = dependencies
|
|
209
|
+
error[kHandledError] = true
|
|
210
|
+
reject(error)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const cleanupEvents = () => {
|
|
214
|
+
globalThis[kITC].removeListener('runtime:event', runtimeEventHandler)
|
|
215
|
+
this.context.controller.removeListener('stopping', stopHandler)
|
|
216
|
+
this.#pendingDependenciesWaits.delete(promise)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
globalThis[kITC].on('runtime:event', runtimeEventHandler)
|
|
220
|
+
this.context.controller.on('stopping', stopHandler)
|
|
221
|
+
this.#pendingDependenciesWaits.add(promise)
|
|
222
|
+
|
|
223
|
+
return promise
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async waitForDependentsStop (dependents = []) {
|
|
227
|
+
if (!globalThis[kITC]) {
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const pending = new Set(dependents)
|
|
232
|
+
|
|
233
|
+
// Ask the runtime the status of the dependencies and don't wait if they are already stopped
|
|
234
|
+
const workers = await globalThis[kITC].send('getWorkers')
|
|
235
|
+
|
|
236
|
+
for (const worker of Object.values(workers)) {
|
|
237
|
+
if (this.dependencies.includes(worker.application) && worker.status === 'started') {
|
|
238
|
+
pending.delete(worker.application)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!pending.size) {
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.logger.info({ dependents: Array.from(pending) }, 'Waiting for dependents to stop.')
|
|
247
|
+
|
|
248
|
+
const { promise, resolve } = Promise.withResolvers()
|
|
249
|
+
|
|
250
|
+
function runtimeEventHandler ({ event, payload }) {
|
|
251
|
+
if (event !== 'application:worker:stopped') {
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
pending.delete(payload.application)
|
|
256
|
+
|
|
257
|
+
if (pending.size === 0) {
|
|
258
|
+
globalThis[kITC].removeListener('runtime:event', runtimeEventHandler)
|
|
259
|
+
resolve()
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
globalThis[kITC].on('runtime:event', runtimeEventHandler)
|
|
264
|
+
return promise
|
|
111
265
|
}
|
|
112
266
|
|
|
113
267
|
getUrl () {
|
|
@@ -145,7 +299,7 @@ export class BaseStackable extends EventEmitter {
|
|
|
145
299
|
}
|
|
146
300
|
|
|
147
301
|
async getInfo () {
|
|
148
|
-
return { type: this.type, version: this.version }
|
|
302
|
+
return { type: this.type, version: this.version, dependencies: this.dependencies }
|
|
149
303
|
}
|
|
150
304
|
|
|
151
305
|
getDispatchFunc () {
|
|
@@ -158,7 +312,7 @@ export class BaseStackable extends EventEmitter {
|
|
|
158
312
|
|
|
159
313
|
getMeta () {
|
|
160
314
|
return {
|
|
161
|
-
|
|
315
|
+
gateway: {
|
|
162
316
|
wantsAbsoluteUrls: false
|
|
163
317
|
}
|
|
164
318
|
}
|
|
@@ -338,7 +492,7 @@ export class BaseStackable extends EventEmitter {
|
|
|
338
492
|
}
|
|
339
493
|
|
|
340
494
|
async stopCommand () {
|
|
341
|
-
const exitTimeout = this.runtimeConfig.gracefulShutdown.
|
|
495
|
+
const exitTimeout = this.runtimeConfig.gracefulShutdown.application
|
|
342
496
|
|
|
343
497
|
this.#subprocessStarted = false
|
|
344
498
|
const exitPromise = once(this.subprocess, 'exit')
|
|
@@ -380,7 +534,7 @@ export class BaseStackable extends EventEmitter {
|
|
|
380
534
|
return {
|
|
381
535
|
id: this.id,
|
|
382
536
|
config: this.config,
|
|
383
|
-
|
|
537
|
+
applicationId: this.applicationId,
|
|
384
538
|
workerId: this.workerId,
|
|
385
539
|
// Always use URL to avoid serialization problem in Windows
|
|
386
540
|
root: pathToFileURL(this.root).toString(),
|
|
@@ -388,7 +542,7 @@ export class BaseStackable extends EventEmitter {
|
|
|
388
542
|
logLevel: this.logger.level,
|
|
389
543
|
isEntrypoint: this.isEntrypoint,
|
|
390
544
|
runtimeBasePath: this.runtimeConfig?.basePath ?? null,
|
|
391
|
-
wantsAbsoluteUrls: meta.
|
|
545
|
+
wantsAbsoluteUrls: meta.gateway?.wantsAbsoluteUrls ?? false,
|
|
392
546
|
/* c8 ignore next 2 - else */
|
|
393
547
|
port: (this.isEntrypoint ? this.serverConfig?.port || 0 : undefined) ?? true,
|
|
394
548
|
host: (this.isEntrypoint ? this.serverConfig?.hostname : undefined) ?? true,
|
|
@@ -430,7 +584,7 @@ export class BaseStackable extends EventEmitter {
|
|
|
430
584
|
const pinoOptions = buildPinoOptions(
|
|
431
585
|
loggerOptions,
|
|
432
586
|
this.serverConfig?.logger,
|
|
433
|
-
this.
|
|
587
|
+
this.applicationId,
|
|
434
588
|
this.workerId,
|
|
435
589
|
this.context,
|
|
436
590
|
this.root
|
|
@@ -463,14 +617,14 @@ export class BaseStackable extends EventEmitter {
|
|
|
463
617
|
|
|
464
618
|
if (this.childManager && this.clientWs) {
|
|
465
619
|
await this.childManager.send(this.clientWs, 'collectMetrics', {
|
|
466
|
-
|
|
620
|
+
applicationId: this.applicationId,
|
|
467
621
|
workerId: this.workerId,
|
|
468
622
|
metricsConfig
|
|
469
623
|
})
|
|
470
624
|
return
|
|
471
625
|
}
|
|
472
626
|
|
|
473
|
-
await collectMetrics(this.
|
|
627
|
+
await collectMetrics(this.applicationId, this.workerId, metricsConfig, this.metricsRegistry)
|
|
474
628
|
}
|
|
475
629
|
|
|
476
630
|
#setHttpCacheMetrics () {
|
|
@@ -559,6 +713,13 @@ export class BaseStackable extends EventEmitter {
|
|
|
559
713
|
globalThis.platformatic.onHttpStatsSize = (url, val) => {
|
|
560
714
|
httpStatsSizeMetric.set({ dispatcher_stats_url: url }, val)
|
|
561
715
|
}
|
|
716
|
+
|
|
717
|
+
const activeResourcesEventLoopMetric = new client.Gauge({
|
|
718
|
+
name: 'active_resources_event_loop',
|
|
719
|
+
help: 'Number of active resources keeping the event loop alive',
|
|
720
|
+
registers: [registry]
|
|
721
|
+
})
|
|
722
|
+
globalThis.platformatic.onActiveResourcesEventLoop = (val) => activeResourcesEventLoopMetric.set(val)
|
|
562
723
|
}
|
|
563
724
|
|
|
564
725
|
async #invalidateHttpCache (opts = {}) {
|
package/lib/config.js
CHANGED
|
@@ -54,7 +54,7 @@ export async function resolve (fileOrDirectory, sourceOrConfig, suffixes) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export async function transform (config) {
|
|
57
|
-
const patch = workerData?.
|
|
57
|
+
const patch = workerData?.applicationConfig?.configPatch
|
|
58
58
|
|
|
59
59
|
if (!config) {
|
|
60
60
|
return config
|
package/lib/creation.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { resolve } from './config.js'
|
|
2
|
-
import {
|
|
2
|
+
import { importCapabilityAndConfig } from './modules.js'
|
|
3
3
|
|
|
4
4
|
export async function create (fileOrDirectory, sourceOrConfig, context) {
|
|
5
5
|
const { root, source } = await resolve(fileOrDirectory, sourceOrConfig)
|
|
6
|
-
const {
|
|
6
|
+
const { capability } = await importCapabilityAndConfig(root, source, context)
|
|
7
7
|
|
|
8
|
-
return
|
|
8
|
+
return capability.create(root, source, context)
|
|
9
9
|
}
|
package/lib/errors.js
CHANGED
package/lib/modules.js
CHANGED
|
@@ -7,7 +7,7 @@ import pino from 'pino'
|
|
|
7
7
|
import { packageJson } from './schema.js'
|
|
8
8
|
import { importFile } from './utils.js'
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const importCapabilityPackageMarker = '__pltImportCapabilityPackage.js'
|
|
11
11
|
|
|
12
12
|
export function isImportFailedError (error, pkg) {
|
|
13
13
|
if (error.code !== 'ERR_MODULE_NOT_FOUND' && error.code !== 'MODULE_NOT_FOUND') {
|
|
@@ -16,10 +16,10 @@ export function isImportFailedError (error, pkg) {
|
|
|
16
16
|
|
|
17
17
|
const match = error.message.match(/Cannot find package '(.+)' imported from (.+)/)
|
|
18
18
|
|
|
19
|
-
return match?.[1] === pkg || error.requireStack?.[0].endsWith(
|
|
19
|
+
return match?.[1] === pkg || error.requireStack?.[0].endsWith(importCapabilityPackageMarker)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export async function
|
|
22
|
+
export async function importCapabilityPackage (directory, pkg) {
|
|
23
23
|
let imported
|
|
24
24
|
try {
|
|
25
25
|
try {
|
|
@@ -30,8 +30,8 @@ export async function importStackablePackage (directory, pkg) {
|
|
|
30
30
|
throw e
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// Scope to the
|
|
34
|
-
const require = createRequire(resolve(directory,
|
|
33
|
+
// Scope to the application
|
|
34
|
+
const require = createRequire(resolve(directory, importCapabilityPackageMarker))
|
|
35
35
|
const toImport = require.resolve(pkg)
|
|
36
36
|
imported = await importFile(toImport)
|
|
37
37
|
}
|
|
@@ -40,16 +40,16 @@ export async function importStackablePackage (directory, pkg) {
|
|
|
40
40
|
throw e
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const
|
|
43
|
+
const applicationDirectory = workerData ? relative(workerData.dirname, directory) : directory
|
|
44
44
|
throw new Error(
|
|
45
|
-
`Unable to import package '${pkg}'. Please add it as a dependency in the package.json file in the folder ${
|
|
45
|
+
`Unable to import package '${pkg}'. Please add it as a dependency in the package.json file in the folder ${applicationDirectory}.`
|
|
46
46
|
)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
return imported.default ?? imported
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export async function
|
|
52
|
+
export async function importCapabilityAndConfig (root, config, context) {
|
|
53
53
|
let rootPackageJson
|
|
54
54
|
try {
|
|
55
55
|
rootPackageJson = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf-8'))
|
|
@@ -72,27 +72,27 @@ export async function importStackableAndConfig (root, config, context) {
|
|
|
72
72
|
const { label, name: moduleName } = appType
|
|
73
73
|
|
|
74
74
|
if (context) {
|
|
75
|
-
const
|
|
75
|
+
const applicationRoot = relative(process.cwd(), root)
|
|
76
76
|
|
|
77
|
-
if (!hadConfig && context.
|
|
77
|
+
if (!hadConfig && context.applicationId && !(await findConfigurationFile(root)) && context.worker?.index === 0) {
|
|
78
78
|
const autodetectDescription =
|
|
79
79
|
moduleName === '@platformatic/node' ? 'is a generic Node.js application' : `is using ${label}`
|
|
80
80
|
|
|
81
|
-
const logger = pino({ level: context.serverConfig?.logger?.level ?? 'warn', name: context.
|
|
81
|
+
const logger = pino({ level: context.serverConfig?.logger?.level ?? 'warn', name: context.applicationId })
|
|
82
82
|
|
|
83
|
-
logger.warn(`We have auto-detected that
|
|
83
|
+
logger.warn(`We have auto-detected that application "${context.applicationId}" ${autodetectDescription}.`)
|
|
84
84
|
logger.warn(
|
|
85
|
-
`We suggest you create a watt.json or a platformatic.json file in the folder ${
|
|
85
|
+
`We suggest you create a watt.json or a platformatic.json file in the folder ${applicationRoot} with the "$schema" property set to "https://schemas.platformatic.dev/${moduleName}/${packageJson.version}.json".`
|
|
86
86
|
)
|
|
87
|
-
logger.warn(`Also don't forget to add "${moduleName}" to the
|
|
87
|
+
logger.warn(`Also don't forget to add "${moduleName}" to the application dependencies.`)
|
|
88
88
|
logger.warn('You can also run "wattpm import" to do this automatically.\n')
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
const
|
|
92
|
+
const capability = await importCapabilityPackage(root, moduleName)
|
|
93
93
|
|
|
94
94
|
return {
|
|
95
|
-
|
|
95
|
+
capability,
|
|
96
96
|
config,
|
|
97
97
|
autodetectDescription:
|
|
98
98
|
moduleName === '@platformatic/node' ? 'is a generic Node.js application' : `is using ${label}`,
|
package/lib/schema.js
CHANGED
|
@@ -44,7 +44,7 @@ const buildableApplication = {
|
|
|
44
44
|
default: 'npm ci --omit-dev'
|
|
45
45
|
},
|
|
46
46
|
// All the following options purposely don't have a default so
|
|
47
|
-
// that
|
|
47
|
+
// that capabilities can detect if the user explicitly set them.
|
|
48
48
|
build: {
|
|
49
49
|
type: 'string'
|
|
50
50
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createDirectory, ensureLoggableError } from '@platformatic/foundation'
|
|
2
|
-
import { ITC } from '@platformatic/itc'
|
|
2
|
+
import { ITC } from '@platformatic/itc/lib/index.js'
|
|
3
3
|
import { once } from 'node:events'
|
|
4
4
|
import { rm, writeFile } from 'node:fs/promises'
|
|
5
5
|
import { createServer } from 'node:http'
|
|
@@ -137,7 +137,7 @@ export class ChildManager extends ITC {
|
|
|
137
137
|
async inject () {
|
|
138
138
|
await this.listen()
|
|
139
139
|
|
|
140
|
-
// Serialize data into a JSON file for the
|
|
140
|
+
// Serialize data into a JSON file for the capability to use
|
|
141
141
|
this.#dataPath = resolve(tmpdir(), 'platformatic', 'runtimes', `${this.#id}.json`)
|
|
142
142
|
await createDirectory(dirname(this.#dataPath))
|
|
143
143
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
ensureLoggableError,
|
|
6
6
|
features
|
|
7
7
|
} from '@platformatic/foundation'
|
|
8
|
-
import { ITC } from '@platformatic/itc'
|
|
8
|
+
import { ITC } from '@platformatic/itc/lib/index.js'
|
|
9
9
|
import { client, collectMetrics } from '@platformatic/metrics'
|
|
10
10
|
import diagnosticChannel, { tracingChannel } from 'node:diagnostics_channel'
|
|
11
11
|
import { EventEmitter, once } from 'node:events'
|
|
@@ -196,8 +196,8 @@ export class ChildProcess extends ITC {
|
|
|
196
196
|
this.#socket.close()
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
async #collectMetrics ({
|
|
200
|
-
await collectMetrics(
|
|
199
|
+
async #collectMetrics ({ applicationId, workerId, metricsConfig }) {
|
|
200
|
+
await collectMetrics(applicationId, workerId, metricsConfig, this.#metricsRegistry)
|
|
201
201
|
this.#setHttpCacheMetrics()
|
|
202
202
|
}
|
|
203
203
|
|
|
@@ -282,6 +282,13 @@ export class ChildProcess extends ITC {
|
|
|
282
282
|
globalThis.platformatic.onHttpStatsSize = (url, val) => {
|
|
283
283
|
httpStatsSizeMetric.set({ dispatcher_stats_url: url }, val)
|
|
284
284
|
}
|
|
285
|
+
|
|
286
|
+
const activeResourcesEventLoopMetric = new client.Gauge({
|
|
287
|
+
name: 'active_resources_event_loop',
|
|
288
|
+
help: 'Number of active resources keeping the event loop alive',
|
|
289
|
+
registers: [registry]
|
|
290
|
+
})
|
|
291
|
+
globalThis.platformatic.onActiveResourcesEventLoop = (val) => activeResourcesEventLoopMetric.set(val)
|
|
285
292
|
}
|
|
286
293
|
|
|
287
294
|
async #getMetrics ({ format } = {}) {
|
|
@@ -295,12 +302,12 @@ export class ChildProcess extends ITC {
|
|
|
295
302
|
disablePinoDirectWrite()
|
|
296
303
|
|
|
297
304
|
// Since this is executed by user code, make sure we only override this in the main thread
|
|
298
|
-
// The rest will be intercepted by the
|
|
305
|
+
// The rest will be intercepted by the BaseCapability.
|
|
299
306
|
const loggerOptions = globalThis.platformatic?.config?.logger ?? {}
|
|
300
307
|
const pinoOptions = {
|
|
301
308
|
...loggerOptions,
|
|
302
309
|
level: loggerOptions.level ?? 'info',
|
|
303
|
-
name: globalThis.platformatic.
|
|
310
|
+
name: globalThis.platformatic.applicationId
|
|
304
311
|
}
|
|
305
312
|
if (loggerOptions.formatters) {
|
|
306
313
|
pinoOptions.formatters = buildPinoFormatters(loggerOptions.formatters)
|
|
@@ -385,8 +392,8 @@ export class ChildProcess extends ITC {
|
|
|
385
392
|
#setupHandlers () {
|
|
386
393
|
const errorLabel =
|
|
387
394
|
typeof globalThis.platformatic.workerId !== 'undefined'
|
|
388
|
-
? `worker ${globalThis.platformatic.workerId} of the
|
|
389
|
-
: `
|
|
395
|
+
? `worker ${globalThis.platformatic.workerId} of the application "${globalThis.platformatic.applicationId}"`
|
|
396
|
+
: `application "${globalThis.platformatic.applicationId}"`
|
|
390
397
|
|
|
391
398
|
function handleUnhandled (type, err) {
|
|
392
399
|
this.#logger.error({ err: ensureLoggableError(err) }, `Child process for the ${errorLabel} threw an ${type}.`)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ensureLoggableError } from '@platformatic/foundation'
|
|
2
|
-
import { generateRequest, sanitize } from '@platformatic/itc'
|
|
2
|
+
import { generateRequest, sanitize } from '@platformatic/itc/lib/index.js'
|
|
3
3
|
import { once } from 'node:events'
|
|
4
4
|
import { platform } from 'node:os'
|
|
5
5
|
import { workerData } from 'node:worker_threads'
|
|
@@ -9,7 +9,7 @@ import { getSocketPath } from './child-manager.js'
|
|
|
9
9
|
|
|
10
10
|
/* c8 ignore next 5 */
|
|
11
11
|
function logDirectError (message, error) {
|
|
12
|
-
process._rawDebug(`Logger thread for child process of
|
|
12
|
+
process._rawDebug(`Logger thread for child process of application ${workerData.id} ${message}.`, {
|
|
13
13
|
error: ensureLoggableError(error)
|
|
14
14
|
})
|
|
15
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/basic",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.8",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"split2": "^4.2.0",
|
|
26
26
|
"undici": "^7.0.0",
|
|
27
27
|
"ws": "^8.18.0",
|
|
28
|
-
"@platformatic/itc": "3.0.0-alpha.
|
|
29
|
-
"@platformatic/
|
|
30
|
-
"@platformatic/
|
|
31
|
-
"@platformatic/
|
|
28
|
+
"@platformatic/itc": "3.0.0-alpha.8",
|
|
29
|
+
"@platformatic/foundation": "3.0.0-alpha.8",
|
|
30
|
+
"@platformatic/telemetry": "3.0.0-alpha.8",
|
|
31
|
+
"@platformatic/metrics": "3.0.0-alpha.8"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"
|
|
34
|
+
"cleaner-spec-reporter": "^0.5.0",
|
|
35
35
|
"eslint": "9",
|
|
36
36
|
"express": "^4.19.2",
|
|
37
37
|
"fastify": "^5.0.0",
|
|
@@ -44,11 +44,10 @@
|
|
|
44
44
|
"node": ">=22.18.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"test": "
|
|
48
|
-
"coverage": "npm run lint && borp -C -X test -X test/fixtures --concurrency=1 --timeout 1200000",
|
|
47
|
+
"test": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
|
|
49
48
|
"gen-schema": "node lib/schema.js > schema.json",
|
|
50
49
|
"gen-types": "json2ts > config.d.ts < schema.json",
|
|
51
|
-
"build": "
|
|
50
|
+
"build": "npm run gen-schema && npm run gen-types",
|
|
52
51
|
"lint": "eslint"
|
|
53
52
|
}
|
|
54
53
|
}
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/basic/3.0.0-alpha.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/basic/3.0.0-alpha.8.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Basic Config",
|
|
5
5
|
"type": "object",
|
|
@@ -28,6 +28,242 @@
|
|
|
28
28
|
"basePath": {
|
|
29
29
|
"type": "string"
|
|
30
30
|
},
|
|
31
|
+
"services": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"anyOf": [
|
|
36
|
+
{
|
|
37
|
+
"required": [
|
|
38
|
+
"id",
|
|
39
|
+
"path"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"required": [
|
|
44
|
+
"id",
|
|
45
|
+
"url"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"properties": {
|
|
50
|
+
"id": {
|
|
51
|
+
"type": "string"
|
|
52
|
+
},
|
|
53
|
+
"path": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"allowEmptyPaths": true,
|
|
56
|
+
"resolvePath": true
|
|
57
|
+
},
|
|
58
|
+
"config": {
|
|
59
|
+
"type": "string"
|
|
60
|
+
},
|
|
61
|
+
"url": {
|
|
62
|
+
"type": "string"
|
|
63
|
+
},
|
|
64
|
+
"gitBranch": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"default": "main"
|
|
67
|
+
},
|
|
68
|
+
"useHttp": {
|
|
69
|
+
"type": "boolean"
|
|
70
|
+
},
|
|
71
|
+
"workers": {
|
|
72
|
+
"anyOf": [
|
|
73
|
+
{
|
|
74
|
+
"type": "number",
|
|
75
|
+
"minimum": 1
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"type": "string"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
"health": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"default": {},
|
|
85
|
+
"properties": {
|
|
86
|
+
"enabled": {
|
|
87
|
+
"anyOf": [
|
|
88
|
+
{
|
|
89
|
+
"type": "boolean"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"type": "string"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"interval": {
|
|
97
|
+
"anyOf": [
|
|
98
|
+
{
|
|
99
|
+
"type": "number",
|
|
100
|
+
"minimum": 0
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"type": "string"
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
"gracePeriod": {
|
|
108
|
+
"anyOf": [
|
|
109
|
+
{
|
|
110
|
+
"type": "number",
|
|
111
|
+
"minimum": 0
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"type": "string"
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"maxUnhealthyChecks": {
|
|
119
|
+
"anyOf": [
|
|
120
|
+
{
|
|
121
|
+
"type": "number",
|
|
122
|
+
"minimum": 1
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"type": "string"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
"maxELU": {
|
|
130
|
+
"anyOf": [
|
|
131
|
+
{
|
|
132
|
+
"type": "number",
|
|
133
|
+
"minimum": 0,
|
|
134
|
+
"maximum": 1
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"type": "string"
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
"maxHeapUsed": {
|
|
142
|
+
"anyOf": [
|
|
143
|
+
{
|
|
144
|
+
"type": "number",
|
|
145
|
+
"minimum": 0,
|
|
146
|
+
"maximum": 1
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"type": "string"
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
"maxHeapTotal": {
|
|
154
|
+
"anyOf": [
|
|
155
|
+
{
|
|
156
|
+
"type": "number",
|
|
157
|
+
"minimum": 0
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"type": "string"
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
"maxYoungGeneration": {
|
|
165
|
+
"anyOf": [
|
|
166
|
+
{
|
|
167
|
+
"type": "number",
|
|
168
|
+
"minimum": 0
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"type": "string"
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"additionalProperties": false
|
|
177
|
+
},
|
|
178
|
+
"dependencies": {
|
|
179
|
+
"type": "array",
|
|
180
|
+
"items": {
|
|
181
|
+
"type": "string"
|
|
182
|
+
},
|
|
183
|
+
"default": []
|
|
184
|
+
},
|
|
185
|
+
"arguments": {
|
|
186
|
+
"type": "array",
|
|
187
|
+
"items": {
|
|
188
|
+
"type": "string"
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
"env": {
|
|
192
|
+
"type": "object",
|
|
193
|
+
"additionalProperties": {
|
|
194
|
+
"type": "string"
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"envfile": {
|
|
198
|
+
"type": "string"
|
|
199
|
+
},
|
|
200
|
+
"sourceMaps": {
|
|
201
|
+
"type": "boolean",
|
|
202
|
+
"default": false
|
|
203
|
+
},
|
|
204
|
+
"packageManager": {
|
|
205
|
+
"type": "string",
|
|
206
|
+
"enum": [
|
|
207
|
+
"npm",
|
|
208
|
+
"pnpm",
|
|
209
|
+
"yarn"
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
"preload": {
|
|
213
|
+
"anyOf": [
|
|
214
|
+
{
|
|
215
|
+
"type": "string",
|
|
216
|
+
"resolvePath": true
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"type": "array",
|
|
220
|
+
"items": {
|
|
221
|
+
"type": "string",
|
|
222
|
+
"resolvePath": true
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
"nodeOptions": {
|
|
228
|
+
"type": "string"
|
|
229
|
+
},
|
|
230
|
+
"telemetry": {
|
|
231
|
+
"type": "object",
|
|
232
|
+
"properties": {
|
|
233
|
+
"instrumentations": {
|
|
234
|
+
"type": "array",
|
|
235
|
+
"description": "An array of instrumentations loaded if telemetry is enabled",
|
|
236
|
+
"items": {
|
|
237
|
+
"oneOf": [
|
|
238
|
+
{
|
|
239
|
+
"type": "string"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"type": "object",
|
|
243
|
+
"properties": {
|
|
244
|
+
"package": {
|
|
245
|
+
"type": "string"
|
|
246
|
+
},
|
|
247
|
+
"exportName": {
|
|
248
|
+
"type": "string"
|
|
249
|
+
},
|
|
250
|
+
"options": {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"additionalProperties": true
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
"required": [
|
|
256
|
+
"package"
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
31
267
|
"workers": {
|
|
32
268
|
"anyOf": [
|
|
33
269
|
{
|
|
@@ -342,7 +578,7 @@
|
|
|
342
578
|
],
|
|
343
579
|
"default": 10000
|
|
344
580
|
},
|
|
345
|
-
"
|
|
581
|
+
"application": {
|
|
346
582
|
"anyOf": [
|
|
347
583
|
{
|
|
348
584
|
"type": "number",
|
|
@@ -358,7 +594,7 @@
|
|
|
358
594
|
"default": {},
|
|
359
595
|
"required": [
|
|
360
596
|
"runtime",
|
|
361
|
-
"
|
|
597
|
+
"application"
|
|
362
598
|
],
|
|
363
599
|
"additionalProperties": false
|
|
364
600
|
},
|
|
@@ -789,13 +1025,13 @@
|
|
|
789
1025
|
}
|
|
790
1026
|
]
|
|
791
1027
|
},
|
|
792
|
-
"
|
|
1028
|
+
"applicationName": {
|
|
793
1029
|
"type": "string",
|
|
794
|
-
"description": "The name of the
|
|
1030
|
+
"description": "The name of the application. Defaults to the folder name if not specified."
|
|
795
1031
|
},
|
|
796
1032
|
"version": {
|
|
797
1033
|
"type": "string",
|
|
798
|
-
"description": "The version of the
|
|
1034
|
+
"description": "The version of the application (optional)"
|
|
799
1035
|
},
|
|
800
1036
|
"skip": {
|
|
801
1037
|
"type": "array",
|
|
@@ -902,7 +1138,7 @@
|
|
|
902
1138
|
}
|
|
903
1139
|
},
|
|
904
1140
|
"required": [
|
|
905
|
-
"
|
|
1141
|
+
"applicationName"
|
|
906
1142
|
],
|
|
907
1143
|
"additionalProperties": false
|
|
908
1144
|
},
|
|
@@ -923,7 +1159,7 @@
|
|
|
923
1159
|
}
|
|
924
1160
|
}
|
|
925
1161
|
},
|
|
926
|
-
"
|
|
1162
|
+
"applicationTimeout": {
|
|
927
1163
|
"anyOf": [
|
|
928
1164
|
{
|
|
929
1165
|
"type": "number",
|