@platformatic/basic 2.22.0 → 2.24.0
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.js +3 -3
- package/lib/base.js +25 -13
- package/lib/utils.js +1 -1
- package/lib/worker/child-manager.js +31 -6
- package/lib/worker/child-process.js +12 -7
- package/lib/worker/listeners.js +16 -2
- package/package.json +8 -7
- package/schema.json +1 -1
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createRequire } from '@platformatic/utils'
|
|
1
2
|
import { existsSync } from 'node:fs'
|
|
2
3
|
import { readFile } from 'node:fs/promises'
|
|
3
|
-
import { createRequire } from 'node:module'
|
|
4
4
|
import { relative, resolve } from 'node:path'
|
|
5
5
|
import { workerData } from 'node:worker_threads'
|
|
6
6
|
import pino from 'pino'
|
|
@@ -37,7 +37,7 @@ function isImportFailedError (error, pkg) {
|
|
|
37
37
|
return match?.[1] === pkg || error.requireStack?.[0].endsWith(importStackablePackageMarker)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
async function importStackablePackage (opts, pkg
|
|
40
|
+
async function importStackablePackage (opts, pkg) {
|
|
41
41
|
try {
|
|
42
42
|
try {
|
|
43
43
|
// Try regular import
|
|
@@ -105,7 +105,7 @@ async function buildStackable (opts) {
|
|
|
105
105
|
toImport = '@platformatic/astro'
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
const imported = await importStackablePackage(opts, toImport
|
|
108
|
+
const imported = await importStackablePackage(opts, toImport)
|
|
109
109
|
|
|
110
110
|
const serviceRoot = relative(process.cwd(), opts.context.directory)
|
|
111
111
|
if (
|
package/lib/base.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { collectMetrics } from '@platformatic/metrics'
|
|
1
|
+
import { client, collectMetrics } from '@platformatic/metrics'
|
|
2
2
|
import { deepmerge, executeWithTimeout } from '@platformatic/utils'
|
|
3
3
|
import { parseCommandString } from 'execa'
|
|
4
4
|
import { spawn } from 'node:child_process'
|
|
@@ -19,6 +19,7 @@ export class BaseStackable {
|
|
|
19
19
|
childManager
|
|
20
20
|
subprocess
|
|
21
21
|
#subprocessStarted
|
|
22
|
+
#metricsCollected
|
|
22
23
|
|
|
23
24
|
constructor (type, version, options, root, configManager) {
|
|
24
25
|
options.context.worker ??= { count: 1, index: 0 }
|
|
@@ -38,7 +39,8 @@ export class BaseStackable {
|
|
|
38
39
|
this.basePath = null
|
|
39
40
|
this.isEntrypoint = options.context.isEntrypoint
|
|
40
41
|
this.isProduction = options.context.isProduction
|
|
41
|
-
this.metricsRegistry =
|
|
42
|
+
this.metricsRegistry = new client.Registry()
|
|
43
|
+
this.#metricsCollected = false
|
|
42
44
|
this.startHttpTimer = null
|
|
43
45
|
this.endHttpTimer = null
|
|
44
46
|
this.clientWs = null
|
|
@@ -71,7 +73,8 @@ export class BaseStackable {
|
|
|
71
73
|
setConnectionString: this.setConnectionString.bind(this),
|
|
72
74
|
setBasePath: this.setBasePath.bind(this),
|
|
73
75
|
runtimeBasePath: this.runtimeConfig?.basePath ?? null,
|
|
74
|
-
invalidateHttpCache: this.#invalidateHttpCache.bind(this)
|
|
76
|
+
invalidateHttpCache: this.#invalidateHttpCache.bind(this),
|
|
77
|
+
prometheus: { client, registry: this.metricsRegistry }
|
|
75
78
|
})
|
|
76
79
|
}
|
|
77
80
|
|
|
@@ -263,7 +266,7 @@ export class BaseStackable {
|
|
|
263
266
|
|
|
264
267
|
this.#subprocessStarted = true
|
|
265
268
|
} catch (e) {
|
|
266
|
-
this.childManager.close(
|
|
269
|
+
this.childManager.close()
|
|
267
270
|
throw new Error(`Cannot execute command "${command}": executable not found`)
|
|
268
271
|
} finally {
|
|
269
272
|
await this.childManager.eject()
|
|
@@ -272,7 +275,7 @@ export class BaseStackable {
|
|
|
272
275
|
// If the process exits prematurely, terminate the thread with the same code
|
|
273
276
|
this.subprocess.on('exit', code => {
|
|
274
277
|
if (this.#subprocessStarted && typeof code === 'number' && code !== 0) {
|
|
275
|
-
this.childManager.close(
|
|
278
|
+
this.childManager.close()
|
|
276
279
|
process.exit(code)
|
|
277
280
|
}
|
|
278
281
|
})
|
|
@@ -280,10 +283,12 @@ export class BaseStackable {
|
|
|
280
283
|
const [url, clientWs] = await once(this.childManager, 'url')
|
|
281
284
|
this.url = url
|
|
282
285
|
this.clientWs = clientWs
|
|
286
|
+
|
|
287
|
+
await this._collectMetrics()
|
|
283
288
|
}
|
|
284
289
|
|
|
285
290
|
async stopCommand () {
|
|
286
|
-
const exitTimeout = this.runtimeConfig.gracefulShutdown.
|
|
291
|
+
const exitTimeout = this.runtimeConfig.gracefulShutdown.service
|
|
287
292
|
|
|
288
293
|
this.#subprocessStarted = false
|
|
289
294
|
const exitPromise = once(this.subprocess, 'exit')
|
|
@@ -307,7 +312,7 @@ export class BaseStackable {
|
|
|
307
312
|
await exitPromise
|
|
308
313
|
|
|
309
314
|
// Close the manager
|
|
310
|
-
this.childManager.close()
|
|
315
|
+
await this.childManager.close()
|
|
311
316
|
}
|
|
312
317
|
|
|
313
318
|
getChildManager () {
|
|
@@ -323,7 +328,16 @@ export class BaseStackable {
|
|
|
323
328
|
: spawn(executable, args, { cwd: this.root })
|
|
324
329
|
}
|
|
325
330
|
|
|
326
|
-
async
|
|
331
|
+
async _collectMetrics () {
|
|
332
|
+
if (this.#metricsCollected) {
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.#metricsCollected = true
|
|
337
|
+
await this.#collectMetrics()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async #collectMetrics () {
|
|
327
341
|
let metricsConfig = this.options.context.metricsConfig
|
|
328
342
|
if (metricsConfig !== false) {
|
|
329
343
|
metricsConfig = {
|
|
@@ -341,13 +355,13 @@ export class BaseStackable {
|
|
|
341
355
|
return
|
|
342
356
|
}
|
|
343
357
|
|
|
344
|
-
const {
|
|
358
|
+
const { startHttpTimer, endHttpTimer } = await collectMetrics(
|
|
345
359
|
this.serviceId,
|
|
346
360
|
this.workerId,
|
|
347
|
-
metricsConfig
|
|
361
|
+
metricsConfig,
|
|
362
|
+
this.metricsRegistry
|
|
348
363
|
)
|
|
349
364
|
|
|
350
|
-
this.metricsRegistry = registry
|
|
351
365
|
this.startHttpTimer = startHttpTimer
|
|
352
366
|
this.endHttpTimer = endHttpTimer
|
|
353
367
|
}
|
|
@@ -358,8 +372,6 @@ export class BaseStackable {
|
|
|
358
372
|
return this.childManager.send(this.clientWs, 'getMetrics', { format })
|
|
359
373
|
}
|
|
360
374
|
|
|
361
|
-
if (!this.metricsRegistry) return null
|
|
362
|
-
|
|
363
375
|
return format === 'json' ? await this.metricsRegistry.getMetricsAsJSON() : await this.metricsRegistry.metrics()
|
|
364
376
|
}
|
|
365
377
|
|
package/lib/utils.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ITC } from '@platformatic/itc'
|
|
2
|
-
import { createDirectory, ensureLoggableError } from '@platformatic/utils'
|
|
2
|
+
import { createDirectory, createRequire, ensureLoggableError } from '@platformatic/utils'
|
|
3
3
|
import { once } from 'node:events'
|
|
4
4
|
import { rm, writeFile } from 'node:fs/promises'
|
|
5
5
|
import { createServer } from 'node:http'
|
|
6
|
-
import {
|
|
6
|
+
import { register } from 'node:module'
|
|
7
7
|
import { platform, tmpdir } from 'node:os'
|
|
8
8
|
import { dirname, join, resolve } from 'node:path'
|
|
9
9
|
import { pathToFileURL } from 'node:url'
|
|
@@ -43,6 +43,7 @@ export class ChildManager extends ITC {
|
|
|
43
43
|
#scripts
|
|
44
44
|
#logger
|
|
45
45
|
#server
|
|
46
|
+
#websocketServer
|
|
46
47
|
#socketPath
|
|
47
48
|
#clients
|
|
48
49
|
#requests
|
|
@@ -93,9 +94,9 @@ export class ChildManager extends ITC {
|
|
|
93
94
|
await createDirectory(dirname(this.#socketPath))
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
this.#websocketServer = new WebSocketServer({ server: this.#server })
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
this.#websocketServer.on('connection', ws => {
|
|
99
100
|
this.#clients.add(ws)
|
|
100
101
|
|
|
101
102
|
ws.on('message', raw => {
|
|
@@ -135,8 +136,14 @@ export class ChildManager extends ITC {
|
|
|
135
136
|
await rm(this.#dataPath, { force: true })
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
this.#
|
|
139
|
-
|
|
139
|
+
for (const client of this.#clients) {
|
|
140
|
+
client.close()
|
|
141
|
+
await once(client, 'close')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await this.#closeServer(this.#websocketServer)
|
|
145
|
+
await this.#closeServer(this.#server)
|
|
146
|
+
await super.close()
|
|
140
147
|
}
|
|
141
148
|
|
|
142
149
|
async inject () {
|
|
@@ -252,4 +259,22 @@ export class ChildManager extends ITC {
|
|
|
252
259
|
this.#logger.error({ err: ensureLoggableError(error) }, message)
|
|
253
260
|
process.exit(exitCode)
|
|
254
261
|
}
|
|
262
|
+
|
|
263
|
+
#closeServer (server) {
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
if (!server || server.listening === false) {
|
|
266
|
+
resolve()
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
server.close(err => {
|
|
271
|
+
if (err) {
|
|
272
|
+
reject(err)
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
resolve()
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
}
|
|
255
280
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ITC } from '@platformatic/itc'
|
|
2
|
-
import { collectMetrics } from '@platformatic/metrics'
|
|
3
|
-
import { createPinoWritable, ensureLoggableError } from '@platformatic/utils'
|
|
2
|
+
import { client, collectMetrics } from '@platformatic/metrics'
|
|
3
|
+
import { createPinoWritable, ensureLoggableError, features } from '@platformatic/utils'
|
|
4
4
|
import diagnosticChannel, { tracingChannel } from 'node:diagnostics_channel'
|
|
5
5
|
import { EventEmitter, once } from 'node:events'
|
|
6
6
|
import { readFile } from 'node:fs/promises'
|
|
@@ -105,6 +105,7 @@ export class ChildProcess extends ITC {
|
|
|
105
105
|
const protocol = platform() === 'win32' ? 'ws+unix:' : 'ws+unix://'
|
|
106
106
|
this.#socket = new WebSocket(`${protocol}${getSocketPath(process.env.PLT_MANAGER_ID)}`)
|
|
107
107
|
this.#pendingMessages = []
|
|
108
|
+
this.#metricsRegistry = new client.Registry()
|
|
108
109
|
|
|
109
110
|
this.listen()
|
|
110
111
|
this.#setupLogger()
|
|
@@ -124,6 +125,7 @@ export class ChildProcess extends ITC {
|
|
|
124
125
|
setGraphqlSchema: this.setGraphqlSchema.bind(this),
|
|
125
126
|
setConnectionString: this.setConnectionString.bind(this),
|
|
126
127
|
setBasePath: this.setBasePath.bind(this),
|
|
128
|
+
prometheus: { client, registry: this.#metricsRegistry }
|
|
127
129
|
})
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -177,13 +179,10 @@ export class ChildProcess extends ITC {
|
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
async #collectMetrics ({ serviceId, workerId, metricsConfig }) {
|
|
180
|
-
|
|
181
|
-
this.#metricsRegistry = registry
|
|
182
|
+
await collectMetrics(serviceId, workerId, metricsConfig, this.#metricsRegistry)
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
async #getMetrics ({ format } = {}) {
|
|
185
|
-
if (!this.#metricsRegistry) return null
|
|
186
|
-
|
|
187
186
|
const res =
|
|
188
187
|
format === 'json' ? await this.#metricsRegistry.getMetricsAsJSON() : await this.#metricsRegistry.metrics()
|
|
189
188
|
|
|
@@ -232,8 +231,14 @@ export class ChildProcess extends ITC {
|
|
|
232
231
|
const host = globalThis.platformatic.host
|
|
233
232
|
|
|
234
233
|
if (port !== false) {
|
|
235
|
-
|
|
234
|
+
const hasFixedPort = typeof port === 'number'
|
|
235
|
+
options.port = hasFixedPort ? port : 0
|
|
236
|
+
|
|
237
|
+
if (hasFixedPort && features.node.reusePort) {
|
|
238
|
+
options.reusePort = true
|
|
239
|
+
}
|
|
236
240
|
}
|
|
241
|
+
|
|
237
242
|
if (typeof host === 'string') {
|
|
238
243
|
options.host = host
|
|
239
244
|
}
|
package/lib/worker/listeners.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import { withResolvers } from '@platformatic/utils'
|
|
1
|
+
import { features, withResolvers } from '@platformatic/utils'
|
|
2
2
|
import { subscribe, tracingChannel, unsubscribe } from 'node:diagnostics_channel'
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
4
|
+
|
|
5
|
+
const snapshots = new WeakMap()
|
|
6
|
+
|
|
7
|
+
export function getAsyncLocalStorageSnapshot (server) {
|
|
8
|
+
return snapshots.get(server)
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
export function createServerListener (overridePort = true, overrideHost) {
|
|
5
12
|
const { promise, resolve, reject } = withResolvers()
|
|
@@ -12,14 +19,21 @@ export function createServerListener (overridePort = true, overrideHost) {
|
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
if (overridePort !== false) {
|
|
15
|
-
|
|
22
|
+
const hasFixedPort = typeof overridePort === 'number'
|
|
23
|
+
options.port = hasFixedPort ? overridePort : 0
|
|
24
|
+
|
|
25
|
+
if (hasFixedPort && features.node.reusePort) {
|
|
26
|
+
options.reusePort = true
|
|
27
|
+
}
|
|
16
28
|
}
|
|
29
|
+
|
|
17
30
|
if (typeof overrideHost === 'string') {
|
|
18
31
|
options.host = overrideHost
|
|
19
32
|
}
|
|
20
33
|
},
|
|
21
34
|
asyncEnd ({ server }) {
|
|
22
35
|
cancel()
|
|
36
|
+
snapshots.set(server, AsyncLocalStorage.snapshot())
|
|
23
37
|
resolve(server)
|
|
24
38
|
},
|
|
25
39
|
error ({ error }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/basic",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,19 +23,20 @@
|
|
|
23
23
|
"split2": "^4.2.0",
|
|
24
24
|
"undici": "^7.0.0",
|
|
25
25
|
"ws": "^8.18.0",
|
|
26
|
-
"@platformatic/config": "2.
|
|
27
|
-
"@platformatic/itc": "2.
|
|
28
|
-
"@platformatic/
|
|
29
|
-
"@platformatic/
|
|
30
|
-
"@platformatic/
|
|
26
|
+
"@platformatic/config": "2.24.0",
|
|
27
|
+
"@platformatic/itc": "2.24.0",
|
|
28
|
+
"@platformatic/telemetry": "2.24.0",
|
|
29
|
+
"@platformatic/metrics": "2.24.0",
|
|
30
|
+
"@platformatic/utils": "2.24.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"borp": "^0.19.0",
|
|
34
34
|
"eslint": "9",
|
|
35
35
|
"express": "^4.19.2",
|
|
36
36
|
"fastify": "^5.0.0",
|
|
37
|
+
"get-port": "^7.1.0",
|
|
37
38
|
"json-schema-to-typescript": "^15.0.0",
|
|
38
|
-
"neostandard": "^0.
|
|
39
|
+
"neostandard": "^0.12.0",
|
|
39
40
|
"next": "^15.0.0",
|
|
40
41
|
"react": "^18.3.1",
|
|
41
42
|
"react-dom": "^18.3.1",
|
package/schema.json
CHANGED