@platformatic/runtime 2.48.0 → 2.50.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/config.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * and run json-schema-to-typescript to regenerate this file.
6
6
  */
7
7
 
8
- export type HttpsSchemasPlatformaticDevPlatformaticRuntime2480Json = {
8
+ export type HttpsSchemasPlatformaticDevPlatformaticRuntime2500Json = {
9
9
  [k: string]: unknown;
10
10
  } & {
11
11
  $schema?: string;
@@ -173,6 +173,34 @@ export type HttpsSchemasPlatformaticDevPlatformaticRuntime2480Json = {
173
173
  labels?: {
174
174
  [k: string]: string;
175
175
  };
176
+ readiness?:
177
+ | boolean
178
+ | {
179
+ endpoint?: string;
180
+ success?: {
181
+ statusCode?: number;
182
+ body?: string;
183
+ };
184
+ fail?: {
185
+ statusCode?: number;
186
+ body?: string;
187
+ };
188
+ };
189
+ liveness?:
190
+ | boolean
191
+ | {
192
+ endpoint?: string;
193
+ success?: {
194
+ statusCode?: number;
195
+ body?: string;
196
+ };
197
+ fail?: {
198
+ statusCode?: number;
199
+ body?: string;
200
+ };
201
+ };
202
+ additionalProperties?: never;
203
+ [k: string]: unknown;
176
204
  };
177
205
  telemetry?: OpenTelemetry;
178
206
  inspectorOptions?: {
package/lib/errors.js CHANGED
@@ -20,6 +20,7 @@ module.exports = {
20
20
  FailedToRetrieveMetaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_META`, 'Failed to retrieve metadata for service with id "%s": %s'),
21
21
  FailedToRetrieveMetricsError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_METRICS`, 'Failed to retrieve metrics for service with id "%s": %s'),
22
22
  FailedToRetrieveHealthError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_HEALTH`, 'Failed to retrieve health for service with id "%s": %s'),
23
+ FailedToPerformCustomHealthCheckError: createError(`${ERROR_PREFIX}_FAILED_TO_PERFORM_CUSTOM_HEALTH_CHECK`, 'Failed to perform custom healthcheck for service with id "%s": %s'),
23
24
  ApplicationAlreadyStartedError: createError(`${ERROR_PREFIX}_APPLICATION_ALREADY_STARTED`, 'Application is already started'),
24
25
  ApplicationNotStartedError: createError(`${ERROR_PREFIX}_APPLICATION_NOT_STARTED`, 'Application has not been started'),
25
26
  ConfigPathMustBeStringError: createError(`${ERROR_PREFIX}_CONFIG_PATH_MUST_BE_STRING`, 'Config path must be a string'),
@@ -2,13 +2,48 @@
2
2
 
3
3
  const fastify = require('fastify')
4
4
 
5
+ const DEFAULT_HOSTNAME = '0.0.0.0'
6
+ const DEFAULT_PORT = 9090
7
+ const DEFAULT_METRICS_ENDPOINT = '/metrics'
8
+ const DEFAULT_READINESS_ENDPOINT = '/ready'
9
+ const DEFAULT_READINESS_SUCCESS_STATUS_CODE = 200
10
+ const DEFAULT_READINESS_SUCCESS_BODY = 'OK'
11
+ const DEFAULT_READINESS_FAIL_STATUS_CODE = 500
12
+ const DEFAULT_READINESS_FAIL_BODY = 'ERR'
13
+ const DEFAULT_LIVENESS_ENDPOINT = '/status'
14
+ const DEFAULT_LIVENESS_SUCCESS_STATUS_CODE = 200
15
+ const DEFAULT_LIVENESS_SUCCESS_BODY = 'OK'
16
+ const DEFAULT_LIVENESS_FAIL_STATUS_CODE = 500
17
+ const DEFAULT_LIVENESS_FAIL_BODY = 'ERR'
18
+
19
+ async function checkReadiness (runtime) {
20
+ const workers = await runtime.getWorkers()
21
+
22
+ for (const worker of Object.values(workers)) {
23
+ if (worker.status !== 'started') {
24
+ return false
25
+ }
26
+ }
27
+ return true
28
+ }
29
+
30
+ async function checkLiveness (runtime) {
31
+ if (!(await checkReadiness(runtime))) {
32
+ return false
33
+ }
34
+
35
+ const checks = await runtime.getCustomHealthChecks()
36
+
37
+ return Object.values(checks).every(check => check)
38
+ }
39
+
5
40
  async function startPrometheusServer (runtime, opts) {
6
41
  if (opts.enabled === false) {
7
42
  return
8
43
  }
9
- const host = opts.hostname ?? '0.0.0.0'
10
- const port = opts.port ?? 9090
11
- const metricsEndpoint = opts.endpoint ?? '/metrics'
44
+ const host = opts.hostname ?? DEFAULT_HOSTNAME
45
+ const port = opts.port ?? DEFAULT_PORT
46
+ const metricsEndpoint = opts.endpoint ?? DEFAULT_METRICS_ENDPOINT
12
47
  const auth = opts.auth ?? null
13
48
 
14
49
  const promServer = fastify({ name: 'Prometheus server' })
@@ -40,6 +75,54 @@ async function startPrometheusServer (runtime, opts) {
40
75
  },
41
76
  })
42
77
 
78
+ if (opts.readiness !== false) {
79
+ const successStatusCode = opts.readiness?.success?.statusCode ?? DEFAULT_READINESS_SUCCESS_STATUS_CODE
80
+ const successBody = opts.readiness?.success?.body ?? DEFAULT_READINESS_SUCCESS_BODY
81
+ const failStatusCode = opts.readiness?.fail?.statusCode ?? DEFAULT_READINESS_FAIL_STATUS_CODE
82
+ const failBody = opts.readiness?.fail?.body ?? DEFAULT_READINESS_FAIL_BODY
83
+
84
+ promServer.route({
85
+ url: opts.readiness?.endpoint ?? DEFAULT_READINESS_ENDPOINT,
86
+ method: 'GET',
87
+ logLevel: 'warn',
88
+ handler: async (req, reply) => {
89
+ reply.type('text/plain')
90
+
91
+ const ready = await checkReadiness(runtime)
92
+
93
+ if (ready) {
94
+ reply.status(successStatusCode).send(successBody)
95
+ } else {
96
+ reply.status(failStatusCode).send(failBody)
97
+ }
98
+ },
99
+ })
100
+ }
101
+
102
+ if (opts.liveness !== false) {
103
+ const successStatusCode = opts.liveness?.success?.statusCode ?? DEFAULT_LIVENESS_SUCCESS_STATUS_CODE
104
+ const successBody = opts.liveness?.success?.body ?? DEFAULT_LIVENESS_SUCCESS_BODY
105
+ const failStatusCode = opts.liveness?.fail?.statusCode ?? DEFAULT_LIVENESS_FAIL_STATUS_CODE
106
+ const failBody = opts.liveness?.fail?.body ?? DEFAULT_LIVENESS_FAIL_BODY
107
+
108
+ promServer.route({
109
+ url: opts.liveness?.endpoint ?? DEFAULT_LIVENESS_ENDPOINT,
110
+ method: 'GET',
111
+ logLevel: 'warn',
112
+ handler: async (req, reply) => {
113
+ reply.type('text/plain')
114
+
115
+ const live = await checkLiveness(runtime)
116
+
117
+ if (live) {
118
+ reply.status(successStatusCode).send(successBody)
119
+ } else {
120
+ reply.status(failStatusCode).send(failBody)
121
+ }
122
+ },
123
+ })
124
+ }
125
+
43
126
  await promServer.listen({ port, host })
44
127
  return promServer
45
128
  }
package/lib/runtime.js CHANGED
@@ -66,7 +66,7 @@ class Runtime extends EventEmitter {
66
66
  #loggerDestination
67
67
  #metrics
68
68
  #metricsTimeout
69
- #status
69
+ #status // starting, started, stopping, stopped, closed
70
70
  #meshInterceptor
71
71
  #dispatcher
72
72
  #managementApi
@@ -648,6 +648,21 @@ class Runtime extends EventEmitter {
648
648
  return status
649
649
  }
650
650
 
651
+ async getCustomHealthChecks () {
652
+ const status = {}
653
+
654
+ for (const [service, { count }] of Object.entries(this.#workers.configuration)) {
655
+ for (let i = 0; i < count; i++) {
656
+ const label = `${service}:${i}`
657
+ const worker = this.#workers.get(label)
658
+
659
+ status[label] = await sendViaITC(worker, 'getCustomHealthCheck')
660
+ }
661
+ }
662
+
663
+ return status
664
+ }
665
+
651
666
  async getServiceDetails (id, allowUnloaded = false) {
652
667
  let service
653
668
 
package/lib/schema.js CHANGED
@@ -380,9 +380,65 @@ const platformaticRuntimeSchema = {
380
380
  labels: {
381
381
  type: 'object',
382
382
  additionalProperties: { type: 'string' }
383
- }
384
- },
385
- additionalProperties: false
383
+ },
384
+ readiness: {
385
+ anyOf: [
386
+ { type: 'boolean' },
387
+ {
388
+ type: 'object',
389
+ properties: {
390
+ endpoint: { type: 'string' },
391
+ success: {
392
+ type: 'object',
393
+ properties: {
394
+ statusCode: { type: 'number' },
395
+ body: { type: 'string' }
396
+ },
397
+ additionalProperties: false
398
+ },
399
+ fail: {
400
+ type: 'object',
401
+ properties: {
402
+ statusCode: { type: 'number' },
403
+ body: { type: 'string' }
404
+ },
405
+ additionalProperties: false
406
+ }
407
+ },
408
+ additionalProperties: false
409
+ }
410
+ ]
411
+ },
412
+ liveness: {
413
+ anyOf: [
414
+ { type: 'boolean' },
415
+ {
416
+ type: 'object',
417
+ properties: {
418
+ endpoint: { type: 'string' },
419
+ success: {
420
+ type: 'object',
421
+ properties: {
422
+ statusCode: { type: 'number' },
423
+ body: { type: 'string' }
424
+ },
425
+ additionalProperties: false
426
+ },
427
+ fail: {
428
+ type: 'object',
429
+ properties: {
430
+ statusCode: { type: 'number' },
431
+ body: { type: 'string' }
432
+ },
433
+ additionalProperties: false
434
+ }
435
+ },
436
+ additionalProperties: false
437
+ }
438
+ ]
439
+ },
440
+ additionalProperties: false
441
+ }
386
442
  }
387
443
  ]
388
444
  },
@@ -16,6 +16,7 @@ const defaultStackable = {
16
16
  getDispatchTarget: () => null,
17
17
  getOpenapiSchema: () => null,
18
18
  getGraphqlSchema: () => null,
19
+ getCustomHealthCheck: () => null,
19
20
  getMeta: () => ({}),
20
21
  getMetrics: () => null,
21
22
  inject: () => {
package/lib/worker/itc.js CHANGED
@@ -166,6 +166,14 @@ function setupITC (app, service, dispatcher) {
166
166
  } catch (err) {
167
167
  throw new errors.FailedToRetrieveHealthError(service.id, err.message)
168
168
  }
169
+ },
170
+
171
+ async getCustomHealthCheck () {
172
+ try {
173
+ return await app.stackable.getCustomHealthCheck()
174
+ } catch (err) {
175
+ throw new errors.FailedToPerformCustomHealthCheckError(service.id, err.message)
176
+ }
169
177
  }
170
178
  }
171
179
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "2.48.0",
3
+ "version": "2.50.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -37,12 +37,12 @@
37
37
  "typescript": "^5.5.4",
38
38
  "undici-oidc-interceptor": "^0.5.0",
39
39
  "why-is-node-running": "^2.2.2",
40
- "@platformatic/composer": "2.48.0",
41
- "@platformatic/node": "2.48.0",
42
- "@platformatic/service": "2.48.0",
43
- "@platformatic/sql-graphql": "2.48.0",
44
- "@platformatic/sql-mapper": "2.48.0",
45
- "@platformatic/db": "2.48.0"
40
+ "@platformatic/db": "2.50.0",
41
+ "@platformatic/node": "2.50.0",
42
+ "@platformatic/service": "2.50.0",
43
+ "@platformatic/composer": "2.50.0",
44
+ "@platformatic/sql-mapper": "2.50.0",
45
+ "@platformatic/sql-graphql": "2.50.0"
46
46
  },
47
47
  "dependencies": {
48
48
  "@fastify/accepts": "^5.0.0",
@@ -76,13 +76,13 @@
76
76
  "undici": "^7.0.0",
77
77
  "undici-thread-interceptor": "^0.13.1",
78
78
  "ws": "^8.16.0",
79
- "@platformatic/config": "2.48.0",
80
- "@platformatic/basic": "2.48.0",
81
- "@platformatic/generators": "2.48.0",
82
- "@platformatic/itc": "2.48.0",
83
- "@platformatic/telemetry": "2.48.0",
84
- "@platformatic/ts-compiler": "2.48.0",
85
- "@platformatic/utils": "2.48.0"
79
+ "@platformatic/basic": "2.50.0",
80
+ "@platformatic/generators": "2.50.0",
81
+ "@platformatic/config": "2.50.0",
82
+ "@platformatic/itc": "2.50.0",
83
+ "@platformatic/telemetry": "2.50.0",
84
+ "@platformatic/ts-compiler": "2.50.0",
85
+ "@platformatic/utils": "2.50.0"
86
86
  },
87
87
  "scripts": {
88
88
  "test": "npm run lint && borp --concurrency=1 --timeout=300000 && tsd",
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.48.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.50.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1153,9 +1153,89 @@
1153
1153
  "additionalProperties": {
1154
1154
  "type": "string"
1155
1155
  }
1156
- }
1157
- },
1158
- "additionalProperties": false
1156
+ },
1157
+ "readiness": {
1158
+ "anyOf": [
1159
+ {
1160
+ "type": "boolean"
1161
+ },
1162
+ {
1163
+ "type": "object",
1164
+ "properties": {
1165
+ "endpoint": {
1166
+ "type": "string"
1167
+ },
1168
+ "success": {
1169
+ "type": "object",
1170
+ "properties": {
1171
+ "statusCode": {
1172
+ "type": "number"
1173
+ },
1174
+ "body": {
1175
+ "type": "string"
1176
+ }
1177
+ },
1178
+ "additionalProperties": false
1179
+ },
1180
+ "fail": {
1181
+ "type": "object",
1182
+ "properties": {
1183
+ "statusCode": {
1184
+ "type": "number"
1185
+ },
1186
+ "body": {
1187
+ "type": "string"
1188
+ }
1189
+ },
1190
+ "additionalProperties": false
1191
+ }
1192
+ },
1193
+ "additionalProperties": false
1194
+ }
1195
+ ]
1196
+ },
1197
+ "liveness": {
1198
+ "anyOf": [
1199
+ {
1200
+ "type": "boolean"
1201
+ },
1202
+ {
1203
+ "type": "object",
1204
+ "properties": {
1205
+ "endpoint": {
1206
+ "type": "string"
1207
+ },
1208
+ "success": {
1209
+ "type": "object",
1210
+ "properties": {
1211
+ "statusCode": {
1212
+ "type": "number"
1213
+ },
1214
+ "body": {
1215
+ "type": "string"
1216
+ }
1217
+ },
1218
+ "additionalProperties": false
1219
+ },
1220
+ "fail": {
1221
+ "type": "object",
1222
+ "properties": {
1223
+ "statusCode": {
1224
+ "type": "number"
1225
+ },
1226
+ "body": {
1227
+ "type": "string"
1228
+ }
1229
+ },
1230
+ "additionalProperties": false
1231
+ }
1232
+ },
1233
+ "additionalProperties": false
1234
+ }
1235
+ ]
1236
+ },
1237
+ "additionalProperties": false
1238
+ }
1159
1239
  }
1160
1240
  ]
1161
1241
  },