@lokalise/fastify-extras 25.2.0 → 25.4.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/README.md
CHANGED
|
@@ -70,6 +70,39 @@ Add the plugin to your Fastify instance by registering it with the following opt
|
|
|
70
70
|
|
|
71
71
|
Your Fastify app will reply with the status of the app when hitting the `GET /` route.
|
|
72
72
|
|
|
73
|
+
### Common Healthcheck Plugin
|
|
74
|
+
|
|
75
|
+
Plugin to monitor app status through public and private healthchecks.
|
|
76
|
+
|
|
77
|
+
Add the plugin to your Fastify instance by registering it with the following options:
|
|
78
|
+
|
|
79
|
+
- `healthChecks`, a list of promises with healthcheck in the callback;
|
|
80
|
+
- `responsePayload` (optional), the response payload that the healthcheck should return. If no response payload is provided, the default response is:
|
|
81
|
+
```json
|
|
82
|
+
{ "heartbeat": "HEALTHY", "checks": {} }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Your Fastify app will reply with the status of the app when hitting the `GET /` public route with aggregated heartbeat from healthchecks provided, example:
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"heartbeat": "HEALTHY"
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
Your Fastify app will reply with the status of the app when hitting the `GET /health` private route with detailed results from healthchecks provided, example:
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"heartbeat": "PARTIALLY_HEALTHY",
|
|
98
|
+
"checks": {
|
|
99
|
+
"check1": "HEALTHY",
|
|
100
|
+
"check2": "HEALTHY",
|
|
101
|
+
"check3": "FAIL"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
73
106
|
### Split IO Plugin
|
|
74
107
|
|
|
75
108
|
Plugin to handle feature flags in Split IO.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FastifyPluginCallback } from 'fastify';
|
|
2
|
+
import type { HealthChecker } from './healthcheckCommons';
|
|
3
|
+
export interface CommonHealthcheckPluginOptions {
|
|
4
|
+
responsePayload?: Record<string, unknown>;
|
|
5
|
+
logLevel?: 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent';
|
|
6
|
+
healthChecks: readonly HealthCheck[];
|
|
7
|
+
infoProviders?: readonly InfoProvider[];
|
|
8
|
+
}
|
|
9
|
+
export type InfoProvider = {
|
|
10
|
+
name: string;
|
|
11
|
+
dataResolver: () => Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
export type HealthCheck = {
|
|
14
|
+
name: string;
|
|
15
|
+
isMandatory: boolean;
|
|
16
|
+
checker: HealthChecker;
|
|
17
|
+
};
|
|
18
|
+
export declare const commonHealthcheckPlugin: FastifyPluginCallback<CommonHealthcheckPluginOptions>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.commonHealthcheckPlugin = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fastify_plugin_1 = tslib_1.__importDefault(require("fastify-plugin"));
|
|
6
|
+
function resolveHealthcheckResults(results, opts) {
|
|
7
|
+
const healthChecks = {};
|
|
8
|
+
let isFullyHealthy = true;
|
|
9
|
+
let isPartiallyHealthy = false;
|
|
10
|
+
// Return detailed healthcheck results
|
|
11
|
+
for (let i = 0; i < results.length; i++) {
|
|
12
|
+
const entry = results[i];
|
|
13
|
+
healthChecks[entry.name] = entry.result.error ? 'FAIL' : 'HEALTHY';
|
|
14
|
+
if (entry.result.error && opts.healthChecks[i].isMandatory) {
|
|
15
|
+
isFullyHealthy = false;
|
|
16
|
+
isPartiallyHealthy = false;
|
|
17
|
+
}
|
|
18
|
+
// Check if we are only partially healthy (only optional dependencies are failing)
|
|
19
|
+
if (isFullyHealthy && entry.result.error && !opts.healthChecks[i].isMandatory) {
|
|
20
|
+
isFullyHealthy = false;
|
|
21
|
+
isPartiallyHealthy = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
isFullyHealthy,
|
|
26
|
+
isPartiallyHealthy,
|
|
27
|
+
healthChecks,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function addRoute(app, opts, routeOpts) {
|
|
31
|
+
const responsePayload = opts.responsePayload ?? {};
|
|
32
|
+
app.route({
|
|
33
|
+
url: routeOpts.url,
|
|
34
|
+
method: 'GET',
|
|
35
|
+
logLevel: opts.logLevel ?? 'info',
|
|
36
|
+
schema: {
|
|
37
|
+
// hide route from swagger plugins
|
|
38
|
+
// @ts-expect-error
|
|
39
|
+
hide: true,
|
|
40
|
+
},
|
|
41
|
+
handler: async (_, reply) => {
|
|
42
|
+
let isFullyHealthy = true;
|
|
43
|
+
let isPartiallyHealthy = false;
|
|
44
|
+
let healthChecks = {};
|
|
45
|
+
if (opts.healthChecks.length) {
|
|
46
|
+
const results = await Promise.all(opts.healthChecks.map((healthcheck) => {
|
|
47
|
+
return healthcheck.checker(app).then((result) => {
|
|
48
|
+
if (result.error) {
|
|
49
|
+
app.log.error(result.error, `${healthcheck.name} healthcheck has failed`);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
name: healthcheck.name,
|
|
53
|
+
result,
|
|
54
|
+
isMandatory: healthcheck.isMandatory,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}));
|
|
58
|
+
const resolvedHealthcheckResponse = resolveHealthcheckResults(results, opts);
|
|
59
|
+
healthChecks = resolvedHealthcheckResponse.healthChecks;
|
|
60
|
+
isFullyHealthy = resolvedHealthcheckResponse.isFullyHealthy;
|
|
61
|
+
isPartiallyHealthy = resolvedHealthcheckResponse.isPartiallyHealthy;
|
|
62
|
+
}
|
|
63
|
+
const extraInfo = opts.infoProviders
|
|
64
|
+
? opts.infoProviders.map((infoProvider) => {
|
|
65
|
+
return {
|
|
66
|
+
name: infoProvider.name,
|
|
67
|
+
value: infoProvider.dataResolver(),
|
|
68
|
+
};
|
|
69
|
+
})
|
|
70
|
+
: undefined;
|
|
71
|
+
const heartbeat = isFullyHealthy
|
|
72
|
+
? 'HEALTHY'
|
|
73
|
+
: isPartiallyHealthy
|
|
74
|
+
? 'PARTIALLY_HEALTHY'
|
|
75
|
+
: 'FAIL';
|
|
76
|
+
const response = {
|
|
77
|
+
...responsePayload,
|
|
78
|
+
heartbeat,
|
|
79
|
+
...(routeOpts.isPublicRoute
|
|
80
|
+
? {}
|
|
81
|
+
: { checks: healthChecks, ...(extraInfo && { extraInfo }) }),
|
|
82
|
+
};
|
|
83
|
+
return reply.status(isFullyHealthy || isPartiallyHealthy ? 200 : 500).send(response);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function plugin(app, opts, done) {
|
|
88
|
+
addRoute(app, opts, {
|
|
89
|
+
url: '/',
|
|
90
|
+
isPublicRoute: true,
|
|
91
|
+
});
|
|
92
|
+
addRoute(app, opts, {
|
|
93
|
+
url: '/health',
|
|
94
|
+
isPublicRoute: false,
|
|
95
|
+
});
|
|
96
|
+
done();
|
|
97
|
+
}
|
|
98
|
+
exports.commonHealthcheckPlugin = (0, fastify_plugin_1.default)(plugin, {
|
|
99
|
+
fastify: '5.x',
|
|
100
|
+
name: 'common-healthcheck-plugin',
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=commonHealthcheckPlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commonHealthcheckPlugin.js","sourceRoot":"","sources":["../../../lib/plugins/healthcheck/commonHealthcheckPlugin.ts"],"names":[],"mappings":";;;;AAEA,4EAA+B;AAuC/B,SAAS,yBAAyB,CAChC,OAA4B,EAC5B,IAAoC;IAEpC,MAAM,YAAY,GAA2B,EAAE,CAAA;IAC/C,IAAI,cAAc,GAAG,IAAI,CAAA;IACzB,IAAI,kBAAkB,GAAG,KAAK,CAAA;IAE9B,sCAAsC;IACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;QACxB,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;QAClE,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAC3D,cAAc,GAAG,KAAK,CAAA;YACtB,kBAAkB,GAAG,KAAK,CAAA;QAC5B,CAAC;QAED,kFAAkF;QAClF,IAAI,cAAc,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAC9E,cAAc,GAAG,KAAK,CAAA;YACtB,kBAAkB,GAAG,IAAI,CAAA;QAC3B,CAAC;IACH,CAAC;IAED,OAAO;QACL,cAAc;QACd,kBAAkB;QAClB,YAAY;KACb,CAAA;AACH,CAAC;AAED,SAAS,QAAQ,CACf,GAAuB,EACvB,IAAoC,EACpC,SAAkC;IAElC,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,IAAI,EAAE,CAAA;IAElD,GAAG,CAAC,KAAK,CAAC;QACR,GAAG,EAAE,SAAS,CAAC,GAAG;QAClB,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,MAAM;QACjC,MAAM,EAAE;YACN,kCAAkC;YAClC,mBAAmB;YACnB,IAAI,EAAE,IAAI;SACX;QAED,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE;YAC1B,IAAI,cAAc,GAAG,IAAI,CAAA;YACzB,IAAI,kBAAkB,GAAG,KAAK,CAAA;YAC9B,IAAI,YAAY,GAA2B,EAAE,CAAA;YAE7C,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;gBAC7B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE;oBACpC,OAAO,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;wBAC9C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;4BACjB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,WAAW,CAAC,IAAI,yBAAyB,CAAC,CAAA;wBAC3E,CAAC;wBACD,OAAO;4BACL,IAAI,EAAE,WAAW,CAAC,IAAI;4BACtB,MAAM;4BACN,WAAW,EAAE,WAAW,CAAC,WAAW;yBACrC,CAAA;oBACH,CAAC,CAAC,CAAA;gBACJ,CAAC,CAAC,CACH,CAAA;gBAED,MAAM,2BAA2B,GAAG,yBAAyB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;gBAC5E,YAAY,GAAG,2BAA2B,CAAC,YAAY,CAAA;gBACvD,cAAc,GAAG,2BAA2B,CAAC,cAAc,CAAA;gBAC3D,kBAAkB,GAAG,2BAA2B,CAAC,kBAAkB,CAAA;YACrE,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa;gBAClC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE;oBACtC,OAAO;wBACL,IAAI,EAAE,YAAY,CAAC,IAAI;wBACvB,KAAK,EAAE,YAAY,CAAC,YAAY,EAAE;qBACnC,CAAA;gBACH,CAAC,CAAC;gBACJ,CAAC,CAAC,SAAS,CAAA;YAEb,MAAM,SAAS,GAAG,cAAc;gBAC9B,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,kBAAkB;oBAClB,CAAC,CAAC,mBAAmB;oBACrB,CAAC,CAAC,MAAM,CAAA;YAEZ,MAAM,QAAQ,GAAG;gBACf,GAAG,eAAe;gBAClB,SAAS;gBACT,GAAG,CAAC,SAAS,CAAC,aAAa;oBACzB,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;aAC/D,CAAA;YAED,OAAO,KAAK,CAAC,MAAM,CAAC,cAAc,IAAI,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACtF,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,MAAM,CACb,GAAuB,EACvB,IAAoC,EACpC,IAAgB;IAEhB,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE;QAClB,GAAG,EAAE,GAAG;QACR,aAAa,EAAE,IAAI;KACpB,CAAC,CAAA;IACF,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE;QAClB,GAAG,EAAE,SAAS;QACd,aAAa,EAAE,KAAK;KACrB,CAAC,CAAA;IAEF,IAAI,EAAE,CAAA;AACR,CAAC;AAEY,QAAA,uBAAuB,GAA0D,IAAA,wBAAE,EAC9F,MAAM,EACN;IACE,OAAO,EAAE,KAAK;IACd,IAAI,EAAE,2BAA2B;CAClC,CACF,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokalise/fastify-extras",
|
|
3
|
-
"version": "25.
|
|
3
|
+
"version": "25.4.0",
|
|
4
4
|
"description": "Opinionated set of fastify plugins, commonly used in Lokalise",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Lokalise",
|
|
@@ -34,9 +34,8 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@amplitude/analytics-node": "^1.3.6",
|
|
36
36
|
"@bugsnag/js": "^8.1.2",
|
|
37
|
-
"@lokalise/background-jobs-common": "^8.0.0",
|
|
38
37
|
"@lokalise/error-utils": "^2.2.0",
|
|
39
|
-
"@splitsoftware/splitio": "^
|
|
38
|
+
"@splitsoftware/splitio": "^11.0.1",
|
|
40
39
|
"@supercharge/promise-pool": "^3.2.0",
|
|
41
40
|
"fastify-metrics": "^12.1.0",
|
|
42
41
|
"fastify-plugin": "^5.0.1",
|
|
@@ -47,6 +46,7 @@
|
|
|
47
46
|
},
|
|
48
47
|
"peerDependencies": {
|
|
49
48
|
"@fastify/jwt": "^9.0.1",
|
|
49
|
+
"@lokalise/background-jobs-common": ">=8.0.0",
|
|
50
50
|
"@lokalise/node-core": ">=12.0.0",
|
|
51
51
|
"bullmq": "^5.19.0",
|
|
52
52
|
"fastify": "^5.0.0",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"@amplitude/analytics-types": "^2.8.4",
|
|
60
60
|
"@biomejs/biome": "^1.9.4",
|
|
61
61
|
"@lokalise/backend-http-client": "^2.4.0",
|
|
62
|
+
"@lokalise/background-jobs-common": "^9.0.0",
|
|
62
63
|
"@lokalise/biome-config": "^1.5.0",
|
|
63
64
|
"@lokalise/node-core": "^13.1.0",
|
|
64
65
|
"@types/newrelic": "^9.14.5",
|
|
@@ -69,9 +70,9 @@
|
|
|
69
70
|
"fastify": "^5.1.0",
|
|
70
71
|
"fastify-type-provider-zod": "^4.0.2",
|
|
71
72
|
"ioredis": "^5.4.1",
|
|
72
|
-
"newrelic": "12.
|
|
73
|
+
"newrelic": "12.8.0",
|
|
73
74
|
"pino": "^9.4.0",
|
|
74
|
-
"pino-pretty": "^
|
|
75
|
+
"pino-pretty": "^13.0.0",
|
|
75
76
|
"shx": "^0.3.4",
|
|
76
77
|
"typescript": "^5.6.3",
|
|
77
78
|
"vitest": "^2.1.4",
|