@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 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
- service: number | string;
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 service. Defaults to the folder name if not specified.
222
+ * The name of the application. Defaults to the folder name if not specified.
220
223
  */
221
- serviceName: string;
224
+ applicationName: string;
222
225
  /**
223
- * The version of the service (optional)
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
- serviceTimeout?: number | string;
302
+ applicationTimeout?: number | string;
300
303
  messagingTimeout?: number | string;
301
304
  env?: {
302
305
  [k: string]: string;
package/eslint.config.js CHANGED
@@ -1,5 +1,3 @@
1
1
  import neostandard from 'neostandard'
2
2
 
3
- export default neostandard({
4
- ignores: ['**/.next'],
5
- })
3
+ export default neostandard()
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
- serviceId: string
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 BaseStackable<Config = Record<string, any>, Options = BaseOptions> {
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,4 @@
1
- export * from './lib/base.js'
1
+ export * from './lib/capability.js'
2
2
  export * from './lib/config.js'
3
3
  export * from './lib/creation.js'
4
4
  export * as errors from './lib/errors.js'
@@ -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'
@@ -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 BaseStackable extends EventEmitter {
17
- childManager
18
- subprocess
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.serviceId = this.context.serviceId
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.#metricsCollected = false
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
- serviceId: this.serviceId,
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
- 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) {
@@ -90,11 +140,15 @@ export class BaseStackable extends EventEmitter {
90
140
  }
91
141
 
92
142
  start () {
93
- throw new Error('BaseStackable.start must be overriden by the subclasses')
143
+ throw new Error('BaseCapability.start must be overriden by the subclasses')
94
144
  }
95
145
 
96
- stop () {
97
- throw new Error('BaseStackable.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 () {
@@ -107,7 +161,107 @@ export class BaseStackable extends EventEmitter {
107
161
  }
108
162
 
109
163
  inject () {
110
- throw new Error('BaseStackable.inject must be overriden by the subclasses')
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
- composer: {
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.service
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
- serviceId: this.serviceId,
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.composer?.wantsAbsoluteUrls ?? false,
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.serviceId,
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
- serviceId: this.serviceId,
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.serviceId, this.workerId, metricsConfig, this.metricsRegistry)
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?.serviceConfig?.configPatch
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 { importStackableAndConfig } from './modules.js'
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 { stackable } = await importStackableAndConfig(root, source, context)
6
+ const { capability } = await importCapabilityAndConfig(root, source, context)
7
7
 
8
- return stackable.create(root, source, context)
8
+ return capability.create(root, source, context)
9
9
  }
package/lib/errors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import createError from '@fastify/error'
2
2
 
3
- const ERROR_PREFIX = 'PLT_BASIC'
3
+ export const ERROR_PREFIX = 'PLT_BASIC'
4
4
 
5
5
  export const exitCodes = {
6
6
  MANAGER_MESSAGE_HANDLING_FAILED: 11,
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 importStackablePackageMarker = '__pltImportStackablePackage.js'
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(importStackablePackageMarker)
19
+ return match?.[1] === pkg || error.requireStack?.[0].endsWith(importCapabilityPackageMarker)
20
20
  }
21
21
 
22
- export async function importStackablePackage (directory, pkg) {
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 service
34
- const require = createRequire(resolve(directory, importStackablePackageMarker))
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 serviceDirectory = workerData ? relative(workerData.dirname, directory) : directory
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 ${serviceDirectory}.`
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 importStackableAndConfig (root, config, context) {
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 serviceRoot = relative(process.cwd(), root)
75
+ const applicationRoot = relative(process.cwd(), root)
76
76
 
77
- if (!hadConfig && context.serviceId && !(await findConfigurationFile(root)) && context.worker?.index === 0) {
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.serviceId })
81
+ const logger = pino({ level: context.serverConfig?.logger?.level ?? 'warn', name: context.applicationId })
82
82
 
83
- logger.warn(`We have auto-detected that service "${context.serviceId}" ${autodetectDescription}.`)
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 ${serviceRoot} with the "$schema" property set to "https://schemas.platformatic.dev/${moduleName}/${packageJson.version}.json".`
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 service dependencies.`)
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 stackable = await importStackablePackage(root, moduleName)
92
+ const capability = await importCapabilityPackage(root, moduleName)
93
93
 
94
94
  return {
95
- stackable,
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 stackables can detect if the user explicitly set them.
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 stackable to use
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 ({ serviceId, workerId, metricsConfig }) {
200
- await collectMetrics(serviceId, workerId, metricsConfig, this.#metricsRegistry)
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 BaseStackable.
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.serviceId
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 service "${globalThis.platformatic.serviceId}"`
389
- : `service "${globalThis.platformatic.serviceId}"`
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 service ${workerData.id} ${message}.`, {
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.5",
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.5",
29
- "@platformatic/metrics": "3.0.0-alpha.5",
30
- "@platformatic/foundation": "3.0.0-alpha.5",
31
- "@platformatic/telemetry": "3.0.0-alpha.5"
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
- "borp": "^0.20.0",
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": "npm run lint && borp --concurrency=1 --timeout 1200000",
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": "pnpm run gen-schema && pnpm run gen-types",
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.5.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",
@@ -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
- "service": {
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
- "service"
597
+ "application"
362
598
  ],
363
599
  "additionalProperties": false
364
600
  },
@@ -789,13 +1025,13 @@
789
1025
  }
790
1026
  ]
791
1027
  },
792
- "serviceName": {
1028
+ "applicationName": {
793
1029
  "type": "string",
794
- "description": "The name of the service. Defaults to the folder name if not specified."
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 service (optional)"
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
- "serviceName"
1141
+ "applicationName"
906
1142
  ],
907
1143
  "additionalProperties": false
908
1144
  },
@@ -923,7 +1159,7 @@
923
1159
  }
924
1160
  }
925
1161
  },
926
- "serviceTimeout": {
1162
+ "applicationTimeout": {
927
1163
  "anyOf": [
928
1164
  {
929
1165
  "type": "number",