@lokalise/fastify-extras 30.2.1 β†’ 30.3.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
@@ -1,406 +1,472 @@
1
- # fastify-extras 🧩
2
-
3
- Reusable plugins for Fastify.
4
-
5
- - [Dependency Management](#dependency-management)
6
- - [Plugins](#plugins)
7
-
8
- - [RequestContext Provider Plugin](#requestcontext-provider-plugin)
9
- - [Public Healthcheck Plugin](#public-healthcheck-plugin)
10
- - [Common Healthcheck Plugin](#common-healthcheck-plugin)
11
- - [Common Sync Healthcheck Plugin](#common-sync-healthcheck-plugin)
12
- - [Split IO Plugin](#split-io-plugin)
13
- - [BugSnag Plugin](#bugsnag-plugin)
14
- - [Metrics Plugin](#metrics-plugin)
15
- - [Bull MQ Metrics Plugin](#bullmq-metrics-plugin)
16
- - [NewRelic Transaction Manager Plugin](#newrelic-transaction-manager-plugin)
17
- - [UnhandledException Plugin](#unhandledexception-plugin)
18
-
19
- ## Dependency Management
20
-
21
- The following needs to be taken into consideration when adding new runtime dependency for the `fastify-extras` package:
22
-
23
- - If dependency is an implementation detail, and end consumer is not expected to import and use the dependency directly, it should be a `dependency`;
24
- - If dependency needs to be imported and used by consumer directly for it to function properly, it should be a `peerDependency`.
25
-
26
- ### Dependencies
27
-
28
- - `@bugsnag/js`;
29
- - `@splitsoftware/splitio`;
30
- - `fastify-metrics`;
31
- - `fastify-plugin`;
32
- - `tslib`.
33
-
34
- ### Peer Dependencies
35
-
36
- - `@fastify/jwt`;
37
- - `fastify`;
38
- - `newrelic`;
39
- - `pino`;
40
- - `bullmq`;
41
-
42
- ## Plugins
43
-
44
- ### RequestContext Provider Plugin
45
-
46
- Plugin to:
47
-
48
- - extend existing `FastifyRequest` with request context by setting the following:
49
- - `logger`, a child logger of app.log, with prepopulated header `x-request-id`;
50
- - `reqId`, the request-id;
51
-
52
- No options are required to register the plugin.
53
-
54
- The `getRequestIdFastifyAppConfig()` method is exported and returns:
55
-
56
- - `genReqId`, a function for generating the request-id;
57
- - `requestIdHeader`, the header name used to set the request-id.
58
-
59
- Which can be passed to Fastify during instantiation.
60
-
61
- ### Public Healthcheck Plugin
62
-
63
- Plugin to monitor app status through public healthcheck.
64
-
65
- Add the plugin to your Fastify instance by registering it with the following options:
66
-
67
- - `healthChecks`, a list of promises with healthcheck in the callback;
68
- - `responsePayload` (optional), the response payload that the public healthcheck should return. If no response payload is provided, the default response is:
69
- ```json
70
- { "heartbeat": "HEALTHY" }
71
- ```
72
-
73
- Your Fastify app will reply with the status of the app when hitting the `GET /` route.
74
-
75
- ### Common Healthcheck Plugin
76
-
77
- Plugin to monitor app status through public and private healthchecks using asynchronous checks.
78
-
79
- Add the plugin to your Fastify instance by registering it with the following options:
80
-
81
- - `healthChecks`, a list of promises with healthcheck in the callback;
82
- - `responsePayload` (optional), the response payload that the healthcheck should return. If no response payload is provided, the default response is:
83
- ```json
84
- { "heartbeat": "HEALTHY", "checks": {} }
85
- ```
86
-
87
- Your Fastify app will reply with the status of the app when hitting the `GET /` public route with aggregated heartbeat from healthchecks provided, example:
88
- ```json
89
- {
90
- "heartbeat": "HEALTHY"
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
-
106
- ### Common Sync Healthcheck Plugin
107
-
108
- Plugin to monitor app status through public and private healthchecks using synchronous checks. **This plugin is recommended when you have healthchecks that run synchronously or are executed in the background**, as it provides better performance for such use cases.
109
-
110
- Add the plugin to your Fastify instance by registering it with the following options:
111
-
112
- - `healthChecks`, an array of synchronous healthcheck objects, each containing:
113
- - `name`, the identifier for the healthcheck;
114
- - `isMandatory`, boolean indicating if this healthcheck is critical for service health;
115
- - `checker`, a synchronous function that returns `null` on success or an `Error` on failure;
116
- - `responsePayload` (optional), the response payload that the healthcheck should return;
117
- - `logLevel` (optional), the log level for the healthcheck routes ('fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent'), defaults to 'info';
118
- - `infoProviders` (optional), an array of info providers to include additional metadata in the `/health` response;
119
- - `isRootRouteEnabled` (optional), whether to enable the public `/` route, defaults to `true`.
120
-
121
- Example usage:
122
- ```typescript
123
- import { commonSyncHealthcheckPlugin } from '@lokalise/fastify-extras'
124
-
125
- app.register(commonSyncHealthcheckPlugin, {
126
- healthChecks: [
127
- {
128
- name: 'database',
129
- isMandatory: true,
130
- checker: (app) => {
131
- // Synchronous check - returns null if healthy, Error if not
132
- return isDatabaseConnected() ? null : new Error('Database disconnected')
133
- }
134
- },
135
- {
136
- name: 'cache',
137
- isMandatory: false, // Optional dependency
138
- checker: (app) => {
139
- return isCacheAvailable() ? null : new Error('Cache unavailable')
140
- }
141
- }
142
- ]
143
- })
144
- ```
145
-
146
- The plugin exposes the same routes as the async Common Healthcheck Plugin:
147
- - `GET /` - Public route returning aggregated health status
148
- - `GET /health` - Private route with detailed healthcheck results
149
-
150
- The key differences from the async version:
151
- - Uses synchronous healthcheck functions instead of promises
152
- - Better suited for checks that are already running in the background or are inherently synchronous
153
- - Supports mandatory vs optional healthchecks (optional failures result in `PARTIALLY_HEALTHY` status)
154
-
155
- ### Startup Healthcheck Plugin
156
-
157
- Plugin to monitor app startup status, doing potentially more expensive checks than what is reasonable through periodic healthchecks.
158
-
159
- Add the plugin to your Fastify instance by registering it with the following options:
160
-
161
- - `healthChecks`, a list of asynchronous healthchecks to run at the app startup;
162
- - `resultsLogLevel`, at what log level to report healthcheck results - default is INFO;
163
-
164
- This is the structure of the log:
165
- ```json
166
- {
167
- "heartbeat": "PARTIALLY_HEALTHY",
168
- "checks": {
169
- "check1": "HEALTHY",
170
- "check2": "HEALTHY",
171
- "check3": "FAIL"
172
- }
173
- }
174
- ```
175
-
176
- In case a non-optional healthcheck fails, an application startup will throw an error. In order to ensure that the error is thrown correctly, make sure to await the app startup:
177
-
178
- ```ts
179
- const app = fastify()
180
- await app.register(startupHealthcheckPlugin, opts)
181
- await app.ready()
182
- ```
183
-
184
- ### Split IO Plugin
185
-
186
- Plugin to handle feature flags in Split IO.
187
-
188
- Add the plugin to your Fastify instance by registering it with the following options:
189
-
190
- - `isEnabled`, if `true` the plugin will connect to [Split IO](https://split.io) using the provided `apiKey` and store data in memory with background syncing;
191
- - `apiKey`, your SDK key;
192
- - `debugMode`;
193
- - `localhostFilePath` (optional), used to utilize the SDK in [localhost mode](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#localhost-mode). It corresponds to the full path to a file with the mapping of feature flag name to treatment. `apiKey` will be automatically replaced with `localhost` if `localhostFilePath` is provided.
194
-
195
- The plugin decorates your Fastify instance with a `SplitIOFeatureManager`, which you can inject and use to leverage the following methods:
196
-
197
- - `init()`, returns a promise that resolves once the SDK has finished loading. It's called automatically when registering the plugin;
198
- - `getTreatment()`, returns the proper treatment based on the feature flag name and the key in input. Expected parameters are:
199
-
200
- - `key`, the ID of the user/account/etc. you're trying to evaluate a treatment for;
201
- - `splitName`, the Split IO feature flag name;
202
- - `attributes` (optional), a set of [Attributes](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#attribute-syntax) used in evaluation to further decide whether to show the on or off treatment;
203
-
204
- > **_NOTE:_** If `isEnabled` is false, `getTreatement()` will return `control` to signal disabled treatment.
205
-
206
- - `getTreatmentWithConfig()`, used to leverage [dynamic configurations with your treatment](https://help.split.io/hc/en-us/articles/360026943552). It accepts the same parameters as `getTreatment()`, but the response structure is as follows:
207
- ```ts
208
- type TreatmentResult = {
209
- treatment: string
210
- config: string | null
211
- }
212
- ```
213
- > **_NOTE:_** If `isEnabled` is false, `getTreatementWithConfig()` will return `control` as `treatment` and `null` as `config` to signal disabled treatment.
214
- - `track()`, used to record any actions your customers perform. Returns a boolean to indicate whether or not the SDK was able to successfully queue the event. Expected parameters are:
215
- - `key`, the ID of the user/account/etc. you're trying to evaluate a treatment for;
216
- - `trafficType`, the [traffic type](https://help.split.io/hc/en-us/articles/360019916311-Traffic-type) of the key;
217
- - `eventType`, the event type that this event should correspond to;
218
- - `value` (optional), the value to be used in creating the metric;
219
- - `properties`(optional), an object of key value pairs that represent the [properties](https://help.split.io/hc/en-us/articles/360027333612-Event-property-capture-) to be used to filter your metrics;
220
- - `shutdown()`, gracefully shuts down the client.
221
-
222
- More info about Split IO can be checked [here](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK).
223
-
224
- ### BugSnag Plugin
225
-
226
- Plugin to report errors to BugSnag.
227
-
228
- Add the plugin to your Fastify instance by registering it with the following options:
229
-
230
- - `isEnabled`;
231
- - `bugsnag`, a set of customizable [xonfiguration options](https://docs.bugsnag.com/platforms/javascript/configuration-options/).
232
-
233
- Once the plugin has been added to your Fastify instance and loaded, errors will be reported to BugSnag.
234
-
235
- ### Metrics Plugin
236
-
237
- Plugin to expose Prometheus metrics.
238
-
239
- Add the plugin to your Fastify instance by registering it with the following options:
240
-
241
- - `loggerOptions`, used to configure the internal logger instance. It can be a boolean or a set of [Pino options](https://getpino.io/#/docs/api?id=options). By default it is set to `false` and the logger is disabled;
242
- - `disablePrometheusRequestLogging` (optional). By default Fastify will issue an `info` level log message when a request is received and when the response for that request has been sent. By setting this option to `true`, these log messages will be disabled. Defaults to `true`;
243
- - `bindAddress` (optional). By default, the server will listen on the address(es) resolved by localhost when no specific host is provided. See the possible values for host when targeting localhost [here](https://fastify.dev/docs/latest/Reference/Server#listen);
244
- - `errorObjectResolver`, a resolver method that, given an `err` and optionally a `correlationID`, it will log the error if something goes wrong.
245
-
246
- The plugin exposes a `GET /metrics` route in your Fastify app to retrieve Prometheus metrics. If something goes wrong while starting the Prometheus metrics server, an `Error` is thrown. Otherwise, a success message is displayed when the plugin has been loaded.
247
-
248
- #### `PrometheusCounterTransactionManager`
249
-
250
- `PrometheusCounterTransactionManager` is an implementation of `TransactionObservabilityManager` that uses Prometheus
251
- counters to track the number of started, failed, and successful transactions. The results are automatically added to
252
- the `/metrics` endpoint exposed by the metrics plugin.
253
-
254
-
255
- ### BullMQ Metrics Plugin
256
-
257
- Plugin to auto-discover BullMQ queues which can regularly collect metrics for them and expose via `fastify-metrics` global Prometheus registry. If used together with `metricsPlugin`, it will show these metrics on `GET /metrics` route.
258
-
259
- This plugin depends on the following peer-installed packages:
260
-
261
- - `bullmq`
262
- - `ioredis`
263
-
264
- Add the plugin to your Fastify instance by registering it with the following possible options:
265
-
266
- - `redisConfigs`, Redis configurations used for BullMQ. Plugin uses them to discover the queues.
267
- - `bullMqPrefix` (optional, default: `bull`). The prefix used by BullMQ to store the queues in Redis;
268
- - `metricsPrefix` (optional, default: `bullmq`). The prefix for the metrics in Prometheus;
269
- - `queueDiscoverer` (optional, default: `BackgroundJobsBasedQueueDiscoverer`). The queue discoverer to use. The default one relies on the logic implemented by `@lokalise/background-jobs-common` where queue names are registered by the background job processors; If you are not using `@lokalise/background-jobs-common`, you can use your own queue discoverer by instantiating a `RedisBasedQueueDiscoverer` or implementing a `QueueDiscoverer` interface;
270
- - `excludedQueues` (optional, default: `[]`). An array of queue names to exclude from metrics collection;
271
- - `histogramBuckets` (optional, default: `[20, 50, 150, 400, 1000, 3000, 8000, 22000, 60000, 150000]`). Buckets for the histogram metrics (such as job completion or overall processing time).
272
- - `collectionOptions` (optional, default: `{ type: 'interval', intervalInMs: 5000 }`). Allows to configure how metrics are collected. Supports the following properties:
273
- - `type`. Can be either `interval` or `manual`.
274
- - With `interval` type, plugin automatically loops and updates metrics at the specified interval.
275
- - With `manual` type, you need to call `app.bullMqMetrics.collect()` to update the metrics; that allows you to build your own logic for scheduling the updates.
276
- - `intervalInMs` (only for `type: 'interval'`). The interval in milliseconds at which the metrics are collected;
277
-
278
- This plugin exposes `bullMqMetrics.collect()` method on the Fastify instance to manually trigger the metrics collection.
279
-
280
- If something goes wrong while starting the BullMQ metrics plugin, an `Error` is thrown.
281
-
282
- ### NewRelic Transaction Manager Plugin
283
-
284
- Plugin to create custom NewRelic spans for background jobs.
285
-
286
- Add the plugin to your Fastify instance by registering it with the following options:
287
-
288
- - `isEnabled`.
289
-
290
- The plugin decorates your Fastify instance with a `NewRelicTransactionManager`, which you can inject and use to leverage the following methods:
291
-
292
- - `start()`, which takes a `jobName`, and starts a background transaction with the provided name;
293
- - `stop()`, which takes a `jobId`, and ends the background transaction referenced by the ID;
294
- - `addCustomAttribute()`, which takes `attrName` and `attrValue` and adds the custom attribute to the current transaction. `attrValue` can be a string, a number, or a boolean.
295
- - `addCustomAttributes()`, which passes `atts` map of the custom attributes to the current transaction. `_uniqueTransactionKey` argument is not used (because New Relic doesn't support setting custom attributes directly on the transaction handle), any string can be passed.
296
-
297
- ### Amplitude Plugin
298
-
299
- This plugin facilitates the transmission of events to Amplitude.
300
-
301
- To add this plugin to your Fastify instance, register it with the following configurations:
302
-
303
- - `isEnabled`: A flag utilized to activate or de-activate the plugin.
304
- - `apiKey` (optional): This refers to the Amplitude API key which can be procured from your respective Amplitude project.
305
- - `options` (optional): To configure Amplitude, please refer to [this documentation](https://amplitude.github.io/Amplitude-TypeScript/modules/_amplitude_analytics_node.Types.html#NodeOptions).
306
- - `apiUsageTracking` (optional): You can use this callback to generate an event that will automatically be sent for tracking API usage. Non-specification of this feature will lead to disabling of API tracking.
307
- - `plugins` (optional): This feature allows you to expand the plugin's functionality, from altering event properties to relaying to third-party APIs. To learn more, visit [this link](https://www.docs.developers.amplitude.com/data/sdks/typescript-node/#plugins).
308
-
309
- The plugin decorates your Fastify instance with a `Amplitude`, which you can inject and use the `track` method on it to send events whenever you need
310
-
311
- > πŸ“˜ To ensure optimal functionality with this plugin, you may need to incorporate Amplitude types into your development dependencies.
312
- >
313
- > ```
314
- > "@amplitude/analytics-types": "*"
315
- > ```
316
-
317
- Additionally, you have the option to enhance the safety and accuracy of your events and properties by wrapping your `Amplitude` instance with `AmplitudeAdapter`.
318
-
319
- > πŸ“˜Check [`AmplitudeAdapter.spec.ts](./lib/plugins/amplitude/amplitudePlugin.spec.ts) for a practical example
320
-
321
- ### Strip Trailing Slash Plugin
322
-
323
- This plugin helps with SEO and SSR by ensuring search engines index only one version of a URL, avoiding duplicate content. It redirects URLs with a trailing slash to the version without it, making it easier for search engines to crawl your site consistently.
324
-
325
- ### UnhandledException Plugin
326
-
327
- This plugin provides a mechanism for handling uncaught exceptions within your Fastify application, ensuring that such exceptions are logged and reported. It's especially useful for capturing unforeseen exceptions and provides a controlled shutdown of the Fastify server, thereby ensuring no potential data corruption.
328
-
329
- #### Setup & Configuration
330
-
331
- To integrate this plugin into your Fastify instance, follow these steps:
332
-
333
- 1. First, import the necessary types and the plugin:
334
-
335
- ```typescript
336
- import { FastifyInstance } from 'fastify'
337
- import { unhandledExceptionPlugin, ErrorObjectResolver } from '@lokalise/fastify-extras'
338
- ```
339
-
340
- 2. Configure the plugin:
341
-
342
- Define your own `ErrorObjectResolver` to dictate how the uncaught exceptions will be structured for logging. Here's an example:
343
-
344
- ```typescript
345
- const myErrorResolver: ErrorObjectResolver = (err, correlationID) => {
346
- return {
347
- error: err,
348
- id: correlationID,
349
- }
350
- }
351
- ```
352
-
353
- You'll also need to provide an `ErrorReporter` instance. This instance should have a `report` method to handle the error reporting logic. For example:
354
-
355
- ```typescript
356
- import { ErrorReporter } from '@lokalise/node-core'
357
-
358
- const myErrorReporter = new ErrorReporter(/* initialization params */)
359
- ```
360
-
361
- 3. Register the plugin with your Fastify instance:
362
-
363
- ```typescript
364
- const fastify = Fastify()
365
-
366
- fastify.register(unhandledExceptionPlugin, {
367
- errorObjectResolver: myErrorResolver,
368
- errorReporter: myErrorReporter,
369
- })
370
- ```
371
-
372
- #### Options
373
-
374
- The plugin accepts the following options:
375
-
376
- - `errorObjectResolver` (required): This function determines the structure of the error object which will be logged in case of an uncaught exception.
377
-
378
- - `errorReporter` (required): An instance of the ErrorReporter which will handle reporting of the uncaught exceptions.
379
-
380
- #### Working Principle
381
-
382
- When an uncaught exception occurs, the plugin:
383
-
384
- - Logs the exception using the provided `errorObjectResolver`.
385
-
386
- - Reports the exception using the `ErrorReporter`.
387
-
388
- - Shuts down the Fastify server gracefully.
389
-
390
- - Exits the process with exit code `1`.
391
-
392
- #### Dependencies
393
-
394
- - `@lokalise/node-core`: For error reporting.
395
-
396
- - `fastify`: The framework this plugin is designed for.
397
-
398
- > 🚨 It's critical to note that this plugin listens to the process's 'uncaughtException' event. Multiple listeners on this event can introduce unpredictable behavior in your application. Ensure that this is the sole listener for this event or handle interactions between multiple listeners carefully.
399
-
400
- ## Utilities
401
-
402
- ### route-utilities
403
-
404
- #### authPreHandlers
405
-
406
- - `createStaticTokenAuthPreHandler` - creates pre handler tha expects a static token in the `Authorization` header. If value is different from the expected token, it will return a 401 response.
1
+ # fastify-extras 🧩
2
+
3
+ Reusable plugins for Fastify.
4
+
5
+ - [Dependency Management](#dependency-management)
6
+ - [Plugins](#plugins)
7
+
8
+ - [RequestContext Provider Plugin](#requestcontext-provider-plugin)
9
+ - [Public Healthcheck Plugin](#public-healthcheck-plugin)
10
+ - [Common Healthcheck Plugin](#common-healthcheck-plugin)
11
+ - [Common Sync Healthcheck Plugin](#common-sync-healthcheck-plugin)
12
+ - [Split IO Plugin](#split-io-plugin)
13
+ - [BugSnag Plugin](#bugsnag-plugin)
14
+ - [Metrics Plugin](#metrics-plugin)
15
+ - [Bull MQ Metrics Plugin](#bullmq-metrics-plugin)
16
+ - [NewRelic Transaction Manager Plugin](#newrelic-transaction-manager-plugin)
17
+ - [OpenTelemetry Transaction Manager Plugin](#opentelemetry-transaction-manager-plugin)
18
+ - [UnhandledException Plugin](#unhandledexception-plugin)
19
+
20
+ ## Dependency Management
21
+
22
+ The following needs to be taken into consideration when adding new runtime dependency for the `fastify-extras` package:
23
+
24
+ - If dependency is an implementation detail, and end consumer is not expected to import and use the dependency directly, it should be a `dependency`;
25
+ - If dependency needs to be imported and used by consumer directly for it to function properly, it should be a `peerDependency`.
26
+
27
+ ### Dependencies
28
+
29
+ - `@bugsnag/js`;
30
+ - `@splitsoftware/splitio`;
31
+ - `fastify-metrics`;
32
+ - `fastify-plugin`;
33
+ - `tslib`.
34
+
35
+ ### Peer Dependencies
36
+
37
+ - `@fastify/jwt`;
38
+ - `@opentelemetry/api`;
39
+ - `fastify`;
40
+ - `newrelic`;
41
+ - `pino`;
42
+ - `bullmq`;
43
+
44
+ ## Plugins
45
+
46
+ ### RequestContext Provider Plugin
47
+
48
+ Plugin to:
49
+
50
+ - extend existing `FastifyRequest` with request context by setting the following:
51
+ - `logger`, a child logger of app.log, with prepopulated header `x-request-id`;
52
+ - `reqId`, the request-id;
53
+
54
+ No options are required to register the plugin.
55
+
56
+ The `getRequestIdFastifyAppConfig()` method is exported and returns:
57
+
58
+ - `genReqId`, a function for generating the request-id;
59
+ - `requestIdHeader`, the header name used to set the request-id.
60
+
61
+ Which can be passed to Fastify during instantiation.
62
+
63
+ ### Public Healthcheck Plugin
64
+
65
+ Plugin to monitor app status through public healthcheck.
66
+
67
+ Add the plugin to your Fastify instance by registering it with the following options:
68
+
69
+ - `healthChecks`, a list of promises with healthcheck in the callback;
70
+ - `responsePayload` (optional), the response payload that the public healthcheck should return. If no response payload is provided, the default response is:
71
+ ```json
72
+ { "heartbeat": "HEALTHY" }
73
+ ```
74
+
75
+ Your Fastify app will reply with the status of the app when hitting the `GET /` route.
76
+
77
+ ### Common Healthcheck Plugin
78
+
79
+ Plugin to monitor app status through public and private healthchecks using asynchronous checks.
80
+
81
+ Add the plugin to your Fastify instance by registering it with the following options:
82
+
83
+ - `healthChecks`, a list of promises with healthcheck in the callback;
84
+ - `responsePayload` (optional), the response payload that the healthcheck should return. If no response payload is provided, the default response is:
85
+ ```json
86
+ { "heartbeat": "HEALTHY", "checks": {} }
87
+ ```
88
+
89
+ Your Fastify app will reply with the status of the app when hitting the `GET /` public route with aggregated heartbeat from healthchecks provided, example:
90
+ ```json
91
+ {
92
+ "heartbeat": "HEALTHY"
93
+ }
94
+ ```
95
+
96
+ 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:
97
+ ```json
98
+ {
99
+ "heartbeat": "PARTIALLY_HEALTHY",
100
+ "checks": {
101
+ "check1": "HEALTHY",
102
+ "check2": "HEALTHY",
103
+ "check3": "FAIL"
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Common Sync Healthcheck Plugin
109
+
110
+ Plugin to monitor app status through public and private healthchecks using synchronous checks. **This plugin is recommended when you have healthchecks that run synchronously or are executed in the background**, as it provides better performance for such use cases.
111
+
112
+ Add the plugin to your Fastify instance by registering it with the following options:
113
+
114
+ - `healthChecks`, an array of synchronous healthcheck objects, each containing:
115
+ - `name`, the identifier for the healthcheck;
116
+ - `isMandatory`, boolean indicating if this healthcheck is critical for service health;
117
+ - `checker`, a synchronous function that returns `null` on success or an `Error` on failure;
118
+ - `responsePayload` (optional), the response payload that the healthcheck should return;
119
+ - `logLevel` (optional), the log level for the healthcheck routes ('fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent'), defaults to 'info';
120
+ - `infoProviders` (optional), an array of info providers to include additional metadata in the `/health` response;
121
+ - `isRootRouteEnabled` (optional), whether to enable the public `/` route, defaults to `true`.
122
+
123
+ Example usage:
124
+ ```typescript
125
+ import { commonSyncHealthcheckPlugin } from '@lokalise/fastify-extras'
126
+
127
+ app.register(commonSyncHealthcheckPlugin, {
128
+ healthChecks: [
129
+ {
130
+ name: 'database',
131
+ isMandatory: true,
132
+ checker: (app) => {
133
+ // Synchronous check - returns null if healthy, Error if not
134
+ return isDatabaseConnected() ? null : new Error('Database disconnected')
135
+ }
136
+ },
137
+ {
138
+ name: 'cache',
139
+ isMandatory: false, // Optional dependency
140
+ checker: (app) => {
141
+ return isCacheAvailable() ? null : new Error('Cache unavailable')
142
+ }
143
+ }
144
+ ]
145
+ })
146
+ ```
147
+
148
+ The plugin exposes the same routes as the async Common Healthcheck Plugin:
149
+ - `GET /` - Public route returning aggregated health status
150
+ - `GET /health` - Private route with detailed healthcheck results
151
+
152
+ The key differences from the async version:
153
+ - Uses synchronous healthcheck functions instead of promises
154
+ - Better suited for checks that are already running in the background or are inherently synchronous
155
+ - Supports mandatory vs optional healthchecks (optional failures result in `PARTIALLY_HEALTHY` status)
156
+
157
+ ### Startup Healthcheck Plugin
158
+
159
+ Plugin to monitor app startup status, doing potentially more expensive checks than what is reasonable through periodic healthchecks.
160
+
161
+ Add the plugin to your Fastify instance by registering it with the following options:
162
+
163
+ - `healthChecks`, a list of asynchronous healthchecks to run at the app startup;
164
+ - `resultsLogLevel`, at what log level to report healthcheck results - default is INFO;
165
+
166
+ This is the structure of the log:
167
+ ```json
168
+ {
169
+ "heartbeat": "PARTIALLY_HEALTHY",
170
+ "checks": {
171
+ "check1": "HEALTHY",
172
+ "check2": "HEALTHY",
173
+ "check3": "FAIL"
174
+ }
175
+ }
176
+ ```
177
+
178
+ In case a non-optional healthcheck fails, an application startup will throw an error. In order to ensure that the error is thrown correctly, make sure to await the app startup:
179
+
180
+ ```ts
181
+ const app = fastify()
182
+ await app.register(startupHealthcheckPlugin, opts)
183
+ await app.ready()
184
+ ```
185
+
186
+ ### Split IO Plugin
187
+
188
+ Plugin to handle feature flags in Split IO.
189
+
190
+ Add the plugin to your Fastify instance by registering it with the following options:
191
+
192
+ - `isEnabled`, if `true` the plugin will connect to [Split IO](https://split.io) using the provided `apiKey` and store data in memory with background syncing;
193
+ - `apiKey`, your SDK key;
194
+ - `debugMode`;
195
+ - `localhostFilePath` (optional), used to utilize the SDK in [localhost mode](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#localhost-mode). It corresponds to the full path to a file with the mapping of feature flag name to treatment. `apiKey` will be automatically replaced with `localhost` if `localhostFilePath` is provided.
196
+
197
+ The plugin decorates your Fastify instance with a `SplitIOFeatureManager`, which you can inject and use to leverage the following methods:
198
+
199
+ - `init()`, returns a promise that resolves once the SDK has finished loading. It's called automatically when registering the plugin;
200
+ - `getTreatment()`, returns the proper treatment based on the feature flag name and the key in input. Expected parameters are:
201
+
202
+ - `key`, the ID of the user/account/etc. you're trying to evaluate a treatment for;
203
+ - `splitName`, the Split IO feature flag name;
204
+ - `attributes` (optional), a set of [Attributes](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#attribute-syntax) used in evaluation to further decide whether to show the on or off treatment;
205
+
206
+ > **_NOTE:_** If `isEnabled` is false, `getTreatement()` will return `control` to signal disabled treatment.
207
+
208
+ - `getTreatmentWithConfig()`, used to leverage [dynamic configurations with your treatment](https://help.split.io/hc/en-us/articles/360026943552). It accepts the same parameters as `getTreatment()`, but the response structure is as follows:
209
+ ```ts
210
+ type TreatmentResult = {
211
+ treatment: string
212
+ config: string | null
213
+ }
214
+ ```
215
+ > **_NOTE:_** If `isEnabled` is false, `getTreatementWithConfig()` will return `control` as `treatment` and `null` as `config` to signal disabled treatment.
216
+ - `track()`, used to record any actions your customers perform. Returns a boolean to indicate whether or not the SDK was able to successfully queue the event. Expected parameters are:
217
+ - `key`, the ID of the user/account/etc. you're trying to evaluate a treatment for;
218
+ - `trafficType`, the [traffic type](https://help.split.io/hc/en-us/articles/360019916311-Traffic-type) of the key;
219
+ - `eventType`, the event type that this event should correspond to;
220
+ - `value` (optional), the value to be used in creating the metric;
221
+ - `properties`(optional), an object of key value pairs that represent the [properties](https://help.split.io/hc/en-us/articles/360027333612-Event-property-capture-) to be used to filter your metrics;
222
+ - `shutdown()`, gracefully shuts down the client.
223
+
224
+ More info about Split IO can be checked [here](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK).
225
+
226
+ ### BugSnag Plugin
227
+
228
+ Plugin to report errors to BugSnag.
229
+
230
+ Add the plugin to your Fastify instance by registering it with the following options:
231
+
232
+ - `isEnabled`;
233
+ - `bugsnag`, a set of customizable [xonfiguration options](https://docs.bugsnag.com/platforms/javascript/configuration-options/).
234
+
235
+ Once the plugin has been added to your Fastify instance and loaded, errors will be reported to BugSnag.
236
+
237
+ ### Metrics Plugin
238
+
239
+ Plugin to expose Prometheus metrics.
240
+
241
+ Add the plugin to your Fastify instance by registering it with the following options:
242
+
243
+ - `loggerOptions`, used to configure the internal logger instance. It can be a boolean or a set of [Pino options](https://getpino.io/#/docs/api?id=options). By default it is set to `false` and the logger is disabled;
244
+ - `disablePrometheusRequestLogging` (optional). By default Fastify will issue an `info` level log message when a request is received and when the response for that request has been sent. By setting this option to `true`, these log messages will be disabled. Defaults to `true`;
245
+ - `bindAddress` (optional). By default, the server will listen on the address(es) resolved by localhost when no specific host is provided. See the possible values for host when targeting localhost [here](https://fastify.dev/docs/latest/Reference/Server#listen);
246
+ - `errorObjectResolver`, a resolver method that, given an `err` and optionally a `correlationID`, it will log the error if something goes wrong.
247
+
248
+ The plugin exposes a `GET /metrics` route in your Fastify app to retrieve Prometheus metrics. If something goes wrong while starting the Prometheus metrics server, an `Error` is thrown. Otherwise, a success message is displayed when the plugin has been loaded.
249
+
250
+ #### `PrometheusCounterTransactionManager`
251
+
252
+ `PrometheusCounterTransactionManager` is an implementation of `TransactionObservabilityManager` that uses Prometheus
253
+ counters to track the number of started, failed, and successful transactions. The results are automatically added to
254
+ the `/metrics` endpoint exposed by the metrics plugin.
255
+
256
+
257
+ ### BullMQ Metrics Plugin
258
+
259
+ Plugin to auto-discover BullMQ queues which can regularly collect metrics for them and expose via `fastify-metrics` global Prometheus registry. If used together with `metricsPlugin`, it will show these metrics on `GET /metrics` route.
260
+
261
+ This plugin depends on the following peer-installed packages:
262
+
263
+ - `bullmq`
264
+ - `ioredis`
265
+
266
+ Add the plugin to your Fastify instance by registering it with the following possible options:
267
+
268
+ - `redisConfigs`, Redis configurations used for BullMQ. Plugin uses them to discover the queues.
269
+ - `bullMqPrefix` (optional, default: `bull`). The prefix used by BullMQ to store the queues in Redis;
270
+ - `metricsPrefix` (optional, default: `bullmq`). The prefix for the metrics in Prometheus;
271
+ - `queueDiscoverer` (optional, default: `BackgroundJobsBasedQueueDiscoverer`). The queue discoverer to use. The default one relies on the logic implemented by `@lokalise/background-jobs-common` where queue names are registered by the background job processors; If you are not using `@lokalise/background-jobs-common`, you can use your own queue discoverer by instantiating a `RedisBasedQueueDiscoverer` or implementing a `QueueDiscoverer` interface;
272
+ - `excludedQueues` (optional, default: `[]`). An array of queue names to exclude from metrics collection;
273
+ - `histogramBuckets` (optional, default: `[20, 50, 150, 400, 1000, 3000, 8000, 22000, 60000, 150000]`). Buckets for the histogram metrics (such as job completion or overall processing time).
274
+ - `collectionOptions` (optional, default: `{ type: 'interval', intervalInMs: 5000 }`). Allows to configure how metrics are collected. Supports the following properties:
275
+ - `type`. Can be either `interval` or `manual`.
276
+ - With `interval` type, plugin automatically loops and updates metrics at the specified interval.
277
+ - With `manual` type, you need to call `app.bullMqMetrics.collect()` to update the metrics; that allows you to build your own logic for scheduling the updates.
278
+ - `intervalInMs` (only for `type: 'interval'`). The interval in milliseconds at which the metrics are collected;
279
+
280
+ This plugin exposes `bullMqMetrics.collect()` method on the Fastify instance to manually trigger the metrics collection.
281
+
282
+ If something goes wrong while starting the BullMQ metrics plugin, an `Error` is thrown.
283
+
284
+ ### NewRelic Transaction Manager Plugin
285
+
286
+ Plugin to create custom NewRelic spans for background jobs.
287
+
288
+ Add the plugin to your Fastify instance by registering it with the following options:
289
+
290
+ - `isEnabled`.
291
+
292
+ The plugin decorates your Fastify instance with a `NewRelicTransactionManager`, which you can inject and use to leverage the following methods:
293
+
294
+ - `start()`, which takes a `jobName`, and starts a background transaction with the provided name;
295
+ - `stop()`, which takes a `jobId`, and ends the background transaction referenced by the ID;
296
+ - `addCustomAttribute()`, which takes `attrName` and `attrValue` and adds the custom attribute to the current transaction. `attrValue` can be a string, a number, or a boolean.
297
+ - `addCustomAttributes()`, which passes `atts` map of the custom attributes to the current transaction. `_uniqueTransactionKey` argument is not used (because New Relic doesn't support setting custom attributes directly on the transaction handle), any string can be passed.
298
+
299
+ ### OpenTelemetry Transaction Manager Plugin
300
+
301
+ Plugin to create custom OpenTelemetry spans for background jobs. This is an alternative to the NewRelic Transaction Manager Plugin for applications using OpenTelemetry for observability.
302
+
303
+ Add the plugin to your Fastify instance by registering it with the following options:
304
+
305
+ - `isEnabled`, if `true` the plugin will create spans using OpenTelemetry;
306
+ - `tracerName` (optional), the instrumentation scope name for the tracer. This identifies the instrumentation library, not the service. For service identification, configure it via OpenTelemetry SDK resource attributes (e.g., `OTEL_SERVICE_NAME` environment variable). Defaults to `'unknown-tracer'`;
307
+ - `tracerVersion` (optional), the instrumentation scope version for the tracer. Defaults to `'1.0.0'`;
308
+ - `maxConcurrentSpans` (optional), maximum number of concurrent spans to track. When this limit is reached, the oldest spans will be evicted and automatically ended to prevent leaks. Defaults to `2000`.
309
+
310
+ The plugin decorates your Fastify instance with an `OpenTelemetryTransactionManager`, which implements the `TransactionObservabilityManager` interface from `@lokalise/node-core`. You can inject and use the following methods:
311
+
312
+ - `start(transactionName, uniqueTransactionKey)`, starts a background span with the provided name and stores it by the unique key;
313
+ - `startWithGroup(transactionName, uniqueTransactionKey, transactionGroup)`, starts a background span with an additional `transaction.group` attribute;
314
+ - `stop(uniqueTransactionKey, wasSuccessful?)`, ends the span referenced by the unique key. Sets status to `OK` if successful (default), or `ERROR` if not;
315
+ - `addCustomAttribute(attrName, attrValue)`, adds a custom attribute to the currently active span. `attrValue` can be a string, number, or boolean;
316
+ - `addCustomAttributes(uniqueTransactionKey, atts)`, adds multiple custom attributes to the span identified by the unique key;
317
+ - `setUserID(userId)`, sets the `enduser.id` attribute on the active span;
318
+ - `setControllerName(name, action)`, sets `code.namespace` and `code.function` attributes on the active span.
319
+
320
+ Additional OpenTelemetry-specific methods:
321
+
322
+ - `getSpan(uniqueTransactionKey)`, returns the span for advanced manipulation, or `null` if not found;
323
+ - `getTracer()`, returns the underlying OpenTelemetry tracer;
324
+ - `runInSpanContext(uniqueTransactionKey, fn)`, executes a function within the context of a specific span, useful for automatic parent-child span linking.
325
+
326
+ Example usage:
327
+
328
+ ```typescript
329
+ import { openTelemetryTransactionManagerPlugin } from '@lokalise/fastify-extras'
330
+
331
+ // Register the plugin
332
+ await app.register(openTelemetryTransactionManagerPlugin, {
333
+ isEnabled: true,
334
+ tracerName: 'my-instrumentation',
335
+ tracerVersion: '1.0.0',
336
+ // maxConcurrentSpans: 2000, // optional
337
+ })
338
+
339
+ // Use in your application
340
+ const manager = app.openTelemetryTransactionManager
341
+
342
+ // Start a transaction
343
+ manager.start('process-email-job', 'job-123')
344
+
345
+ // Add custom attributes
346
+ manager.addCustomAttributes('job-123', {
347
+ jobType: 'email',
348
+ recipient: 'user@example.com',
349
+ })
350
+
351
+ // Execute nested operations within the span context
352
+ manager.runInSpanContext('job-123', () => {
353
+ // Child spans created here will be linked to the parent
354
+ doSomeWork()
355
+ })
356
+
357
+ // End the transaction
358
+ manager.stop('job-123', true) // true = successful
359
+ ```
360
+
361
+ > **Note:** This plugin requires `@opentelemetry/api` as a peer dependency. Make sure your application has OpenTelemetry configured with appropriate exporters (e.g., OTLP exporter) to send traces to your observability backend.
362
+
363
+ ### Amplitude Plugin
364
+
365
+ This plugin facilitates the transmission of events to Amplitude.
366
+
367
+ To add this plugin to your Fastify instance, register it with the following configurations:
368
+
369
+ - `isEnabled`: A flag utilized to activate or de-activate the plugin.
370
+ - `apiKey` (optional): This refers to the Amplitude API key which can be procured from your respective Amplitude project.
371
+ - `options` (optional): To configure Amplitude, please refer to [this documentation](https://amplitude.github.io/Amplitude-TypeScript/modules/_amplitude_analytics_node.Types.html#NodeOptions).
372
+ - `apiUsageTracking` (optional): You can use this callback to generate an event that will automatically be sent for tracking API usage. Non-specification of this feature will lead to disabling of API tracking.
373
+ - `plugins` (optional): This feature allows you to expand the plugin's functionality, from altering event properties to relaying to third-party APIs. To learn more, visit [this link](https://www.docs.developers.amplitude.com/data/sdks/typescript-node/#plugins).
374
+
375
+ The plugin decorates your Fastify instance with a `Amplitude`, which you can inject and use the `track` method on it to send events whenever you need
376
+
377
+ > πŸ“˜ To ensure optimal functionality with this plugin, you may need to incorporate Amplitude types into your development dependencies.
378
+ >
379
+ > ```
380
+ > "@amplitude/analytics-types": "*"
381
+ > ```
382
+
383
+ Additionally, you have the option to enhance the safety and accuracy of your events and properties by wrapping your `Amplitude` instance with `AmplitudeAdapter`.
384
+
385
+ > πŸ“˜Check [`AmplitudeAdapter.spec.ts](./lib/plugins/amplitude/amplitudePlugin.spec.ts) for a practical example
386
+
387
+ ### Strip Trailing Slash Plugin
388
+
389
+ This plugin helps with SEO and SSR by ensuring search engines index only one version of a URL, avoiding duplicate content. It redirects URLs with a trailing slash to the version without it, making it easier for search engines to crawl your site consistently.
390
+
391
+ ### UnhandledException Plugin
392
+
393
+ This plugin provides a mechanism for handling uncaught exceptions within your Fastify application, ensuring that such exceptions are logged and reported. It's especially useful for capturing unforeseen exceptions and provides a controlled shutdown of the Fastify server, thereby ensuring no potential data corruption.
394
+
395
+ #### Setup & Configuration
396
+
397
+ To integrate this plugin into your Fastify instance, follow these steps:
398
+
399
+ 1. First, import the necessary types and the plugin:
400
+
401
+ ```typescript
402
+ import { FastifyInstance } from 'fastify'
403
+ import { unhandledExceptionPlugin, ErrorObjectResolver } from '@lokalise/fastify-extras'
404
+ ```
405
+
406
+ 2. Configure the plugin:
407
+
408
+ Define your own `ErrorObjectResolver` to dictate how the uncaught exceptions will be structured for logging. Here's an example:
409
+
410
+ ```typescript
411
+ const myErrorResolver: ErrorObjectResolver = (err, correlationID) => {
412
+ return {
413
+ error: err,
414
+ id: correlationID,
415
+ }
416
+ }
417
+ ```
418
+
419
+ You'll also need to provide an `ErrorReporter` instance. This instance should have a `report` method to handle the error reporting logic. For example:
420
+
421
+ ```typescript
422
+ import { ErrorReporter } from '@lokalise/node-core'
423
+
424
+ const myErrorReporter = new ErrorReporter(/* initialization params */)
425
+ ```
426
+
427
+ 3. Register the plugin with your Fastify instance:
428
+
429
+ ```typescript
430
+ const fastify = Fastify()
431
+
432
+ fastify.register(unhandledExceptionPlugin, {
433
+ errorObjectResolver: myErrorResolver,
434
+ errorReporter: myErrorReporter,
435
+ })
436
+ ```
437
+
438
+ #### Options
439
+
440
+ The plugin accepts the following options:
441
+
442
+ - `errorObjectResolver` (required): This function determines the structure of the error object which will be logged in case of an uncaught exception.
443
+
444
+ - `errorReporter` (required): An instance of the ErrorReporter which will handle reporting of the uncaught exceptions.
445
+
446
+ #### Working Principle
447
+
448
+ When an uncaught exception occurs, the plugin:
449
+
450
+ - Logs the exception using the provided `errorObjectResolver`.
451
+
452
+ - Reports the exception using the `ErrorReporter`.
453
+
454
+ - Shuts down the Fastify server gracefully.
455
+
456
+ - Exits the process with exit code `1`.
457
+
458
+ #### Dependencies
459
+
460
+ - `@lokalise/node-core`: For error reporting.
461
+
462
+ - `fastify`: The framework this plugin is designed for.
463
+
464
+ > 🚨 It's critical to note that this plugin listens to the process's 'uncaughtException' event. Multiple listeners on this event can introduce unpredictable behavior in your application. Ensure that this is the sole listener for this event or handle interactions between multiple listeners carefully.
465
+
466
+ ## Utilities
467
+
468
+ ### route-utilities
469
+
470
+ #### authPreHandlers
471
+
472
+ - `createStaticTokenAuthPreHandler` - creates pre handler tha expects a static token in the `Authorization` header. If value is different from the expected token, it will return a 401 response.