@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 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 { buildPinoOptions, deepmerge, executeWithTimeout, kMetadata, kTimeout } from '@platformatic/foundation'
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
- childManager
18
- subprocess
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.#metricsCollected = false
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
- return this.updateContext()
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
- throw new Error('BaseCapability.stop must be overriden by the subclasses')
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.6",
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.6",
29
- "@platformatic/foundation": "3.0.0-alpha.6",
30
- "@platformatic/metrics": "3.0.0-alpha.6",
31
- "@platformatic/telemetry": "3.0.0-alpha.6"
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": "pnpm run gen-schema && pnpm run gen-types",
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.6.json",
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": {