@platformatic/basic 3.0.0-alpha.6 → 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/index.d.ts +0 -7
- package/lib/capability.js +171 -10
- package/lib/worker/child-process.js +7 -0
- package/package.json +6 -6
- package/schema.json +8 -1
package/index.d.ts
CHANGED
|
@@ -2,12 +2,6 @@ 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
|
|
@@ -75,7 +69,6 @@ export class BaseCapability<Config = Record<string, any>, Options = BaseOptions>
|
|
|
75
69
|
body: object
|
|
76
70
|
}>
|
|
77
71
|
log (options: { message: string; level: string }): Promise<void>
|
|
78
|
-
getBootstrapDependencies (): Promise<Dependency[]>
|
|
79
72
|
getWatchConfig (): Promise<{
|
|
80
73
|
enabled: boolean
|
|
81
74
|
path: string
|
package/lib/capability.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'
|
|
@@ -14,16 +21,44 @@ import { ChildManager } from './worker/child-manager.js'
|
|
|
14
21
|
const kITC = Symbol.for('plt.runtime.itc')
|
|
15
22
|
|
|
16
23
|
export class BaseCapability extends EventEmitter {
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
@@ -42,7 +77,7 @@ export class BaseCapability 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,11 +86,11 @@ export class BaseCapability 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({
|
|
93
|
+
capability: this,
|
|
59
94
|
applicationId: this.applicationId,
|
|
60
95
|
workerId: this.workerId,
|
|
61
96
|
logLevel: this.logger.level,
|
|
@@ -79,10 +114,25 @@ export class BaseCapability 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) {
|
|
@@ -93,8 +143,12 @@ export class BaseCapability extends EventEmitter {
|
|
|
93
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 () {
|
|
@@ -110,6 +164,106 @@ export class BaseCapability extends EventEmitter {
|
|
|
110
164
|
throw new Error('BaseCapability.inject must be overriden by the subclasses')
|
|
111
165
|
}
|
|
112
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
|
|
265
|
+
}
|
|
266
|
+
|
|
113
267
|
getUrl () {
|
|
114
268
|
return this.url
|
|
115
269
|
}
|
|
@@ -145,7 +299,7 @@ export class BaseCapability 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 () {
|
|
@@ -559,6 +713,13 @@ export class BaseCapability 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 = {}) {
|
|
@@ -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 } = {}) {
|
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,10 +25,10 @@
|
|
|
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/foundation": "3.0.0-alpha.
|
|
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",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"test": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
|
|
48
48
|
"gen-schema": "node lib/schema.js > schema.json",
|
|
49
49
|
"gen-types": "json2ts > config.d.ts < schema.json",
|
|
50
|
-
"build": "
|
|
50
|
+
"build": "npm run gen-schema && npm run gen-types",
|
|
51
51
|
"lint": "eslint"
|
|
52
52
|
}
|
|
53
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",
|
|
@@ -175,6 +175,13 @@
|
|
|
175
175
|
},
|
|
176
176
|
"additionalProperties": false
|
|
177
177
|
},
|
|
178
|
+
"dependencies": {
|
|
179
|
+
"type": "array",
|
|
180
|
+
"items": {
|
|
181
|
+
"type": "string"
|
|
182
|
+
},
|
|
183
|
+
"default": []
|
|
184
|
+
},
|
|
178
185
|
"arguments": {
|
|
179
186
|
"type": "array",
|
|
180
187
|
"items": {
|