@platformatic/runtime 3.4.1 → 3.5.1

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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/config.d.ts +224 -77
  3. package/eslint.config.js +3 -5
  4. package/index.d.ts +73 -24
  5. package/index.js +173 -29
  6. package/lib/config.js +279 -197
  7. package/lib/errors.js +126 -34
  8. package/lib/generator.js +640 -0
  9. package/lib/logger.js +43 -41
  10. package/lib/management-api.js +109 -118
  11. package/lib/prom-server.js +202 -16
  12. package/lib/runtime.js +1963 -585
  13. package/lib/scheduler.js +119 -0
  14. package/lib/schema.js +22 -234
  15. package/lib/shared-http-cache.js +43 -0
  16. package/lib/upgrade.js +6 -8
  17. package/lib/utils.js +6 -61
  18. package/lib/version.js +7 -0
  19. package/lib/versions/v1.36.0.js +2 -4
  20. package/lib/versions/v1.5.0.js +2 -4
  21. package/lib/versions/v2.0.0.js +3 -5
  22. package/lib/versions/v3.0.0.js +16 -0
  23. package/lib/worker/controller.js +302 -0
  24. package/lib/worker/http-cache.js +171 -0
  25. package/lib/worker/interceptors.js +190 -10
  26. package/lib/worker/itc.js +146 -59
  27. package/lib/worker/main.js +220 -81
  28. package/lib/worker/messaging.js +182 -0
  29. package/lib/worker/round-robin-map.js +62 -0
  30. package/lib/worker/shared-context.js +22 -0
  31. package/lib/worker/symbols.js +14 -5
  32. package/package.json +47 -38
  33. package/schema.json +1383 -55
  34. package/help/compile.txt +0 -8
  35. package/help/help.txt +0 -5
  36. package/help/start.txt +0 -21
  37. package/index.test-d.ts +0 -41
  38. package/lib/build-server.js +0 -69
  39. package/lib/compile.js +0 -98
  40. package/lib/dependencies.js +0 -59
  41. package/lib/generator/README.md +0 -32
  42. package/lib/generator/errors.js +0 -10
  43. package/lib/generator/runtime-generator.d.ts +0 -37
  44. package/lib/generator/runtime-generator.js +0 -498
  45. package/lib/start.js +0 -190
  46. package/lib/worker/app.js +0 -278
  47. package/lib/worker/default-stackable.js +0 -33
  48. package/lib/worker/metrics.js +0 -122
  49. package/runtime.mjs +0 -54
package/lib/logger.js CHANGED
@@ -1,55 +1,57 @@
1
- 'use strict'
1
+ import { buildPinoFormatters, buildPinoTimestamp } from '@platformatic/foundation'
2
+ import { isatty } from 'node:tty'
3
+ import pino from 'pino'
4
+ import pretty from 'pino-pretty'
5
+
6
+ export { abstractLogger } from '@platformatic/foundation'
7
+
8
+ const customPrettifiers = {
9
+ name (name, _, obj) {
10
+ if (typeof obj.worker !== 'undefined') {
11
+ name += ':' + obj.worker
12
+ obj.worker = undefined // Do not show the worker in a separate line
13
+ }
2
14
 
3
- const { join } = require('node:path')
4
- const { isatty } = require('node:tty')
15
+ return name
16
+ }
17
+ }
5
18
 
6
- const pino = require('pino')
7
- const pretty = require('pino-pretty')
19
+ // Create the runtime logger
20
+ export async function createLogger (config) {
21
+ const loggerConfig = { ...config.logger, transport: undefined }
22
+ if (config.logger.base === null) {
23
+ loggerConfig.base = undefined
24
+ }
8
25
 
9
- function createLogger (config, runtimeLogsDir) {
10
- const loggerConfig = { ...config.logger }
11
- const cliStream = isatty(1) ? pretty() : pino.destination(1)
26
+ let cliStream
12
27
 
13
- if (!config.managementApi) {
14
- return [pino(loggerConfig, cliStream), cliStream]
28
+ if (config.logger.transport) {
29
+ cliStream = pino.transport(config.logger.transport)
30
+ } else {
31
+ cliStream = isatty(1) ? pretty({ customPrettifiers }) : pino.destination(1)
15
32
  }
16
33
 
17
- const multiStream = pino.multistream([
18
- { stream: cliStream, level: loggerConfig.level || 'info' },
19
- ])
34
+ if (loggerConfig.formatters) {
35
+ loggerConfig.formatters = buildPinoFormatters(loggerConfig.formatters)
36
+ }
20
37
 
21
- if (loggerConfig.transport) {
22
- const transport = pino.transport(loggerConfig.transport)
23
- multiStream.add({ level: loggerConfig.level || 'info', stream: transport })
38
+ if (loggerConfig.timestamp) {
39
+ loggerConfig.timestamp = buildPinoTimestamp(loggerConfig.timestamp)
24
40
  }
25
41
 
26
- if (config.managementApi) {
27
- const logsFileMb = 5
28
- const logsLimitMb = config.managementApi?.logs?.maxSize || 200
42
+ if (!config.managementApi) {
43
+ return [pino(loggerConfig, cliStream), cliStream]
44
+ }
29
45
 
30
- let logsLimitCount = Math.ceil(logsLimitMb / logsFileMb) - 1
31
- if (logsLimitCount < 1) {
32
- logsLimitCount = 1
33
- }
46
+ const multiStream = pino.multistream([{ stream: cliStream, level: loggerConfig.level }])
47
+
48
+ const logsFileMb = 5
49
+ const logsLimitMb = config.managementApi?.logs?.maxSize || 200
34
50
 
35
- const pinoRoll = pino.transport({
36
- target: 'pino-roll',
37
- options: {
38
- file: join(runtimeLogsDir, 'logs'),
39
- mode: 0o600,
40
- size: logsFileMb + 'm',
41
- mkdir: true,
42
- fsync: true,
43
- limit: {
44
- count: logsLimitCount,
45
- },
46
- },
47
- })
48
-
49
- multiStream.add({ level: 'trace', stream: pinoRoll })
51
+ let logsLimitCount = Math.ceil(logsLimitMb / logsFileMb) - 1
52
+ if (logsLimitCount < 1) {
53
+ logsLimitCount = 1
50
54
  }
51
55
 
52
- return [pino({ level: 'trace' }, multiStream), multiStream]
56
+ return [pino(loggerConfig, multiStream), multiStream]
53
57
  }
54
-
55
- module.exports = { createLogger }
@@ -1,27 +1,24 @@
1
- 'use strict'
2
-
3
- const { platform, tmpdir } = require('node:os')
4
- const { join } = require('node:path')
5
- const { createDirectory, safeRemove } = require('@platformatic/utils')
6
-
7
- const fastify = require('fastify')
8
- const ws = require('ws')
9
-
10
- const errors = require('./errors')
11
- const { getRuntimeLogsDir } = require('./utils')
1
+ import fastifyAccepts from '@fastify/accepts'
2
+ import fastifyWebsocket from '@fastify/websocket'
3
+ import { createDirectory, safeRemove } from '@platformatic/foundation'
4
+ import fastify from 'fastify'
5
+ import { platform, tmpdir } from 'node:os'
6
+ import { join } from 'node:path'
7
+ import { setTimeout as sleep } from 'node:timers/promises'
8
+ import { createWebSocketStream } from 'ws'
12
9
 
13
10
  const PLATFORMATIC_TMP_DIR = join(tmpdir(), 'platformatic', 'runtimes')
14
11
 
15
- async function managementApiPlugin (app, opts) {
16
- app.log.warn(
17
- 'Runtime Management API is in the experimental stage. ' +
18
- 'The feature is not subject to semantic versioning rules. ' +
19
- 'Non-backward compatible changes or removal may occur in any future release. ' +
20
- 'Use of the feature is not recommended in production environments.'
21
- )
12
+ export async function managementApiPlugin (app, opts) {
13
+ app.register(fastifyAccepts)
22
14
 
23
15
  const runtime = opts.runtime
24
16
 
17
+ app.get('/status', async () => {
18
+ const status = runtime.getRuntimeStatus()
19
+ return { status }
20
+ })
21
+
25
22
  app.get('/metadata', async () => {
26
23
  return runtime.getRuntimeMetadata()
27
24
  })
@@ -35,62 +32,62 @@ async function managementApiPlugin (app, opts) {
35
32
  })
36
33
 
37
34
  app.post('/stop', async () => {
38
- app.log.debug('stop services')
39
- await runtime.close(true)
35
+ app.log.debug('stop applications')
36
+ await runtime.close()
40
37
  })
41
38
 
42
39
  app.post('/restart', async () => {
43
- app.log.debug('restart services')
40
+ app.log.debug('restart applications')
44
41
  await runtime.restart()
45
42
  })
46
43
 
47
- app.get('/services', async () => {
48
- return runtime.getServices()
44
+ app.get('/applications', async () => {
45
+ return runtime.getApplications()
49
46
  })
50
47
 
51
- app.get('/services/:id', async request => {
48
+ app.get('/applications/:id', async request => {
52
49
  const { id } = request.params
53
- app.log.debug('get service details', { id })
54
- return runtime.getServiceDetails(id)
50
+ app.log.debug('get application details', { id })
51
+ return runtime.getApplicationDetails(id)
55
52
  })
56
53
 
57
- app.get('/services/:id/config', async request => {
54
+ app.get('/applications/:id/config', async request => {
58
55
  const { id } = request.params
59
- app.log.debug('get service config', { id })
60
- return runtime.getServiceConfig(id)
56
+ app.log.debug('get application config', { id })
57
+ return runtime.getApplicationConfig(id)
61
58
  })
62
59
 
63
- app.get('/services/:id/env', async request => {
60
+ app.get('/applications/:id/env', async request => {
64
61
  const { id } = request.params
65
- app.log.debug('get service config', { id })
66
- return runtime.getServiceEnv(id)
62
+ app.log.debug('get application config', { id })
63
+ return runtime.getApplicationEnv(id)
67
64
  })
68
65
 
69
- app.get('/services/:id/openapi-schema', async request => {
66
+ app.get('/applications/:id/openapi-schema', async request => {
70
67
  const { id } = request.params
71
68
  app.log.debug('get openapi-schema', { id })
72
- return runtime.getServiceOpenapiSchema(id)
69
+ return runtime.getApplicationOpenapiSchema(id)
73
70
  })
74
71
 
75
- app.get('/services/:id/graphql-schema', async request => {
72
+ app.get('/applications/:id/graphql-schema', async request => {
76
73
  const { id } = request.params
77
74
  app.log.debug('get graphql-schema', { id })
78
- return runtime.getServiceGraphqlSchema(id)
75
+ return runtime.getApplicationGraphqlSchema(id)
79
76
  })
80
77
 
81
- app.post('/services/:id/start', async request => {
78
+ app.post('/applications/:id/start', async request => {
82
79
  const { id } = request.params
83
- app.log.debug('start service', { id })
84
- await runtime.startService(id)
80
+ app.log.debug('start application', { id })
81
+ await runtime.startApplication(id)
85
82
  })
86
83
 
87
- app.post('/services/:id/stop', async request => {
84
+ app.post('/applications/:id/stop', async request => {
88
85
  const { id } = request.params
89
- app.log.debug('stop service', { id })
90
- await runtime._stopService(id)
86
+ app.log.debug('stop application', { id })
87
+ await runtime.stopApplication(id)
91
88
  })
92
89
 
93
- app.all('/services/:id/proxy/*', async (request, reply) => {
90
+ app.all('/applications/:id/proxy/*', async (request, reply) => {
94
91
  const { id, '*': requestUrl } = request.params
95
92
  app.log.debug('proxy request', { id, requestUrl })
96
93
 
@@ -115,6 +112,36 @@ async function managementApiPlugin (app, opts) {
115
112
  reply.code(res.statusCode).headers(res.headers).send(res.body)
116
113
  })
117
114
 
115
+ app.post('/applications/:id/pprof/start', async (request, reply) => {
116
+ const { id } = request.params
117
+ app.log.debug('start profiling', { id })
118
+
119
+ const options = request.body || {}
120
+ await runtime.startApplicationProfiling(id, options)
121
+ reply.code(200).send({})
122
+ })
123
+
124
+ app.post('/applications/:id/pprof/stop', async (request, reply) => {
125
+ const { id } = request.params
126
+ app.log.debug('stop profiling', { id })
127
+
128
+ const profileData = await runtime.stopApplicationProfiling(id)
129
+ reply.type('application/octet-stream').code(200).send(profileData)
130
+ })
131
+
132
+ app.get('/metrics', { logLevel: 'debug' }, async (req, reply) => {
133
+ const accepts = req.accepts()
134
+
135
+ if (!accepts.type('text/plain') && accepts.type('application/json')) {
136
+ const { metrics } = await runtime.getMetrics('json')
137
+ return metrics
138
+ }
139
+
140
+ reply.type('text/plain')
141
+ const { metrics } = await runtime.getMetrics('text')
142
+ return metrics
143
+ })
144
+
118
145
  app.get('/metrics/live', { websocket: true }, async socket => {
119
146
  const cachedMetrics = runtime.getCachedMetrics()
120
147
  if (cachedMetrics.length > 0) {
@@ -139,93 +166,57 @@ async function managementApiPlugin (app, opts) {
139
166
  })
140
167
 
141
168
  app.get('/logs/live', { websocket: true }, async (socket, req) => {
142
- const startLogId = req.query.start ? parseInt(req.query.start) : null
143
-
144
- if (startLogId) {
145
- const logIds = await runtime.getLogIds()
146
- if (!logIds.includes(startLogId)) {
147
- throw new errors.LogFileNotFound(startLogId)
148
- }
149
- }
150
-
151
- const stream = ws.createWebSocketStream(socket)
152
- runtime.pipeLogsStream(stream, req.log, startLogId)
153
- })
154
-
155
- app.get('/logs/indexes', async req => {
156
- const returnAllIds = req.query.all === 'true'
157
-
158
- if (returnAllIds) {
159
- const runtimesLogsIds = await runtime.getAllLogIds()
160
- return runtimesLogsIds
161
- }
162
-
163
- const runtimeLogsIds = await runtime.getLogIds()
164
- return { indexes: runtimeLogsIds }
165
- })
166
-
167
- app.get('/logs/all', async (req, reply) => {
168
- const runtimePID = parseInt(req.query.pid) || process.pid
169
-
170
- const logsIds = await runtime.getLogIds(runtimePID)
171
- const startLogId = logsIds.at(0)
172
- const endLogId = logsIds.at(-1)
173
-
174
- reply.hijack()
175
-
176
- runtime.pipeLogsStream(reply.raw, req.log, startLogId, endLogId, runtimePID)
169
+ runtime.addLoggerDestination(createWebSocketStream(socket))
177
170
  })
171
+ }
178
172
 
179
- app.get('/logs/:id', async req => {
180
- const logId = parseInt(req.params.id)
181
- const runtimePID = parseInt(req.query.pid) || process.pid
173
+ export async function startManagementApi (runtime) {
174
+ const runtimePID = process.pid
182
175
 
183
- const logIds = await runtime.getLogIds(runtimePID)
184
- if (!logIds || !logIds.includes(logId)) {
185
- throw new errors.LogFileNotFound(logId)
186
- }
176
+ const runtimePIDDir = join(PLATFORMATIC_TMP_DIR, runtimePID.toString())
177
+ if (platform() !== 'win32') {
178
+ await createDirectory(runtimePIDDir, true)
179
+ }
187
180
 
188
- const logFileStream = await runtime.getLogFileStream(logId, runtimePID)
189
- return logFileStream
190
- })
191
- }
181
+ let socketPath = null
182
+ if (platform() === 'win32') {
183
+ socketPath = '\\\\.\\pipe\\platformatic-' + runtimePID.toString()
184
+ } else {
185
+ socketPath = join(runtimePIDDir, 'socket')
186
+ }
192
187
 
193
- async function startManagementApi (runtime, configManager) {
194
- const runtimePID = process.pid
188
+ const managementApi = fastify()
189
+ managementApi.register(fastifyWebsocket)
190
+ managementApi.register(managementApiPlugin, { runtime, prefix: '/api/v1' })
195
191
 
196
- try {
197
- const runtimePIDDir = join(PLATFORMATIC_TMP_DIR, runtimePID.toString())
192
+ managementApi.addHook('onClose', async () => {
198
193
  if (platform() !== 'win32') {
199
- await createDirectory(runtimePIDDir, true)
194
+ await safeRemove(runtimePIDDir)
200
195
  }
196
+ })
201
197
 
202
- const runtimeLogsDir = getRuntimeLogsDir(configManager.dirname, process.pid)
203
- await createDirectory(runtimeLogsDir, true)
198
+ // When the runtime closes, close the management API as well
199
+ runtime.on('closed', managementApi.close.bind(managementApi))
204
200
 
205
- let socketPath = null
206
- if (platform() === 'win32') {
207
- socketPath = '\\\\.\\pipe\\platformatic-' + runtimePID.toString()
208
- } else {
209
- socketPath = join(runtimePIDDir, 'socket')
210
- }
201
+ /*
202
+ If runtime are started multiple times in a short
203
+ period of time (like in tests), there is a chance that the pipe is still in use
204
+ as the manament API server is closed after the runtime is closed (see event handler above).
211
205
 
212
- const managementApi = fastify()
213
- managementApi.register(require('@fastify/websocket'))
214
- managementApi.register(managementApiPlugin, { runtime, prefix: '/api/v1' })
215
-
216
- managementApi.addHook('onClose', async () => {
217
- if (platform() !== 'win32') {
218
- await safeRemove(runtimePIDDir)
206
+ Since it's a very rare case, we simply retry couple of times.
207
+ */
208
+ for (let i = 0; i < 5; i++) {
209
+ try {
210
+ await managementApi.listen({ path: socketPath })
211
+ break
212
+ } catch (e) {
213
+ if (i === 5) {
214
+ throw e
219
215
  }
220
- })
221
216
 
222
- await managementApi.listen({ path: socketPath })
223
- return managementApi
224
- /* c8 ignore next 4 */
225
- } catch (err) {
226
- console.error(err)
227
- process.exit(1)
217
+ await sleep(100)
218
+ }
228
219
  }
229
- }
230
220
 
231
- module.exports = { startManagementApi, managementApiPlugin }
221
+ return managementApi
222
+ }
@@ -1,46 +1,232 @@
1
- 'use strict'
1
+ import fastifyAccepts from '@fastify/accepts'
2
+ import fastifyBasicAuth from '@fastify/basic-auth'
3
+ import { loadModule } from '@platformatic/foundation'
4
+ import fastify from 'fastify'
5
+ import { createRequire } from 'node:module'
6
+ import { resolve } from 'node:path'
2
7
 
3
- const fastify = require('fastify')
8
+ const DEFAULT_HOSTNAME = '0.0.0.0'
9
+ const DEFAULT_PORT = 9090
10
+ const DEFAULT_METRICS_ENDPOINT = '/metrics'
11
+ const DEFAULT_READINESS_ENDPOINT = '/ready'
12
+ const DEFAULT_READINESS_SUCCESS_STATUS_CODE = 200
13
+ const DEFAULT_READINESS_SUCCESS_BODY = 'OK'
14
+ const DEFAULT_READINESS_FAIL_STATUS_CODE = 500
15
+ const DEFAULT_READINESS_FAIL_BODY = 'ERR'
16
+ const DEFAULT_LIVENESS_ENDPOINT = '/status'
17
+ const DEFAULT_LIVENESS_SUCCESS_STATUS_CODE = 200
18
+ const DEFAULT_LIVENESS_SUCCESS_BODY = 'OK'
19
+ const DEFAULT_LIVENESS_FAIL_STATUS_CODE = 500
20
+ const DEFAULT_LIVENESS_FAIL_BODY = 'ERR'
4
21
 
5
- async function startPrometheusServer (runtime, opts) {
6
- const host = opts.hostname ?? '0.0.0.0'
7
- const port = opts.port ?? 9090
8
- const metricsEndpoint = opts.endpoint ?? '/metrics'
22
+ async function checkReadiness (runtime) {
23
+ const workers = await runtime.getWorkers()
24
+
25
+ // Make sure there is at least one started worker
26
+ const applications = new Set()
27
+ const started = new Set()
28
+ for (const worker of Object.values(workers)) {
29
+ applications.add(worker.application)
30
+
31
+ if (worker.status === 'started') {
32
+ started.add(worker.application)
33
+ }
34
+ }
35
+
36
+ if (started.size !== applications.size) {
37
+ return { status: false }
38
+ }
39
+
40
+ // perform custom readiness checks, get custom response content if any
41
+ const checks = await runtime.getCustomReadinessChecks()
42
+
43
+ let response
44
+ const status = Object.values(checks).every(check => {
45
+ if (typeof check === 'boolean') {
46
+ return check
47
+ } else if (typeof check === 'object') {
48
+ response = check
49
+ return check.status
50
+ }
51
+ return false
52
+ })
53
+
54
+ return { response, status }
55
+ }
56
+
57
+ async function checkLiveness (runtime) {
58
+ const { status: ready, response: readinessResponse } = await checkReadiness(runtime)
59
+ if (!ready) {
60
+ return { status: false, readiness: readinessResponse }
61
+ }
62
+ // TODO test, doc
63
+ // in case of readiness check failure, if custom readiness response is set, we return the readiness check response on health check endpoint
64
+
65
+ const checks = await runtime.getCustomHealthChecks()
66
+
67
+ let response
68
+ const status = Object.values(checks).every(check => {
69
+ if (typeof check === 'boolean') {
70
+ return check
71
+ } else if (typeof check === 'object') {
72
+ response = check
73
+ return check.status
74
+ }
75
+ return false
76
+ })
77
+
78
+ return { response, status }
79
+ }
80
+
81
+ export async function startPrometheusServer (runtime, opts) {
82
+ if (opts.enabled === false) {
83
+ return
84
+ }
85
+ const host = opts.hostname ?? DEFAULT_HOSTNAME
86
+ const port = opts.port ?? DEFAULT_PORT
87
+ const metricsEndpoint = opts.endpoint ?? DEFAULT_METRICS_ENDPOINT
9
88
  const auth = opts.auth ?? null
10
89
 
11
90
  const promServer = fastify({ name: 'Prometheus server' })
91
+ promServer.register(fastifyAccepts)
12
92
 
13
93
  let onRequestHook
14
94
  if (auth) {
15
95
  const { username, password } = auth
16
96
 
17
- await promServer.register(require('@fastify/basic-auth'), {
97
+ await promServer.register(fastifyBasicAuth, {
18
98
  validate: function (user, pass, req, reply, done) {
19
99
  if (username !== user || password !== pass) {
20
100
  return reply.code(401).send({ message: 'Unauthorized' })
21
101
  }
22
102
  return done()
23
- },
103
+ }
24
104
  })
25
105
  onRequestHook = promServer.basicAuth
26
106
  }
27
107
 
108
+ const readinessEndpoint = opts.readiness?.endpoint ?? DEFAULT_READINESS_ENDPOINT
109
+ const livenessEndpoint = opts.liveness?.endpoint ?? DEFAULT_LIVENESS_ENDPOINT
110
+
111
+ promServer.route({
112
+ url: '/',
113
+ method: 'GET',
114
+ logLevel: 'warn',
115
+ handler (req, reply) {
116
+ reply.type('text/plain')
117
+ let response = `Hello from Platformatic Prometheus Server!\nThe metrics are available at ${metricsEndpoint}.`
118
+
119
+ if (opts.readiness !== false) {
120
+ response += `\nThe readiness endpoint is available at ${readinessEndpoint}.`
121
+ }
122
+
123
+ if (opts.liveness !== false) {
124
+ response += `\nThe liveness endpoint is available at ${livenessEndpoint}.`
125
+ }
126
+
127
+ return response
128
+ }
129
+ })
130
+
28
131
  promServer.route({
29
132
  url: metricsEndpoint,
30
133
  method: 'GET',
31
134
  logLevel: 'warn',
32
135
  onRequest: onRequestHook,
33
136
  handler: async (req, reply) => {
34
- reply.type('text/plain')
35
- const { metrics } = await runtime.getMetrics('text')
36
- return metrics
37
- },
137
+ const accepts = req.accepts()
138
+ const reqType = !accepts.type('text/plain') && accepts.type('application/json') ? 'json' : 'text'
139
+ if (reqType === 'text') {
140
+ reply.type('text/plain')
141
+ }
142
+ return (await runtime.getMetrics(reqType)).metrics
143
+ }
38
144
  })
39
145
 
146
+ if (opts.readiness !== false) {
147
+ const successStatusCode = opts.readiness?.success?.statusCode ?? DEFAULT_READINESS_SUCCESS_STATUS_CODE
148
+ const successBody = opts.readiness?.success?.body ?? DEFAULT_READINESS_SUCCESS_BODY
149
+ const failStatusCode = opts.readiness?.fail?.statusCode ?? DEFAULT_READINESS_FAIL_STATUS_CODE
150
+ const failBody = opts.readiness?.fail?.body ?? DEFAULT_READINESS_FAIL_BODY
151
+
152
+ promServer.route({
153
+ url: readinessEndpoint,
154
+ method: 'GET',
155
+ logLevel: 'warn',
156
+ handler: async (req, reply) => {
157
+ reply.type('text/plain')
158
+
159
+ const { status, response } = await checkReadiness(runtime)
160
+
161
+ if (typeof response === 'boolean') {
162
+ if (status) {
163
+ reply.status(successStatusCode).send(successBody)
164
+ } else {
165
+ reply.status(failStatusCode).send(failBody)
166
+ }
167
+ } else if (typeof response === 'object') {
168
+ const { status, body, statusCode } = response
169
+ if (status) {
170
+ reply.status(statusCode || successStatusCode).send(body || successBody)
171
+ } else {
172
+ reply.status(statusCode || failStatusCode).send(body || failBody)
173
+ }
174
+ } else if (!response) {
175
+ if (status) {
176
+ reply.status(successStatusCode).send(successBody)
177
+ } else {
178
+ reply.status(failStatusCode).send(failBody)
179
+ }
180
+ }
181
+ }
182
+ })
183
+ }
184
+
185
+ if (opts.liveness !== false) {
186
+ const successStatusCode = opts.liveness?.success?.statusCode ?? DEFAULT_LIVENESS_SUCCESS_STATUS_CODE
187
+ const successBody = opts.liveness?.success?.body ?? DEFAULT_LIVENESS_SUCCESS_BODY
188
+ const failStatusCode = opts.liveness?.fail?.statusCode ?? DEFAULT_LIVENESS_FAIL_STATUS_CODE
189
+ const failBody = opts.liveness?.fail?.body ?? DEFAULT_LIVENESS_FAIL_BODY
190
+
191
+ promServer.route({
192
+ url: livenessEndpoint,
193
+ method: 'GET',
194
+ logLevel: 'warn',
195
+ handler: async (req, reply) => {
196
+ reply.type('text/plain')
197
+
198
+ const { status, response, readiness } = await checkLiveness(runtime)
199
+
200
+ if (typeof response === 'boolean') {
201
+ if (status) {
202
+ reply.status(successStatusCode).send(successBody)
203
+ } else {
204
+ reply.status(failStatusCode).send(readiness?.body || failBody)
205
+ }
206
+ } else if (typeof response === 'object') {
207
+ const { status, body, statusCode } = response
208
+ if (status) {
209
+ reply.status(statusCode || successStatusCode).send(body || successBody)
210
+ } else {
211
+ reply.status(statusCode || failStatusCode).send(body || readiness?.body || failBody)
212
+ }
213
+ } else if (!response) {
214
+ if (status) {
215
+ reply.status(successStatusCode).send(successBody)
216
+ } else {
217
+ reply.status(failStatusCode).send(readiness?.body || failBody)
218
+ }
219
+ }
220
+ }
221
+ })
222
+ }
223
+
224
+ const require = createRequire(resolve(import.meta.filename))
225
+ for (const pluginPath of opts.plugins ?? []) {
226
+ const plugin = await loadModule(require, pluginPath)
227
+ await promServer.register(plugin)
228
+ }
229
+
40
230
  await promServer.listen({ port, host })
41
231
  return promServer
42
232
  }
43
-
44
- module.exports = {
45
- startPrometheusServer,
46
- }