Skip to content

Commit c4cd92b

Browse files
committed
feat: Add Fastify v4 compatibility
BREAKING CHANGE: Fastify is now a peer-dependency. BREAKING CHANGE: Removed deprecated options
1 parent 4d40d6b commit c4cd92b

14 files changed

+1700
-1680
lines changed

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,6 @@ createServer({
491491
If for some reason you wish to disable service health monitoring, you can set
492492
the `FASTIFY_MICRO_DISABLE_SERVICE_HEALTH_MONITORING` environment variable to `true`.
493493

494-
## Deprecated APIs
495-
496-
- `configure` _(will be removed in v4.x)_: Use `plugins` with full `fastify-autoload` options.
497-
- `routesDir` _(will be removed in v4.x)_: Use `routes` with full `fastify-autoload` options.
498-
499494
## License
500495

501496
[MIT](https://github.com/47ng/fastify-micro/blob/master/LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com)

package.json

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,48 +24,62 @@
2424
"scripts": {
2525
"test": "jest --coverage --runInBand",
2626
"test:watch": "jest --watch --runInBand",
27-
"build:clean": "rm -rf ./dist",
27+
"prebuild": "rm -rf ./dist && mkdir -p ./dist",
28+
"build": "run-p build:*",
2829
"build:ts": "tsc",
29-
"build": "run-s build:clean build:ts",
30+
"build:copy-worker": "cp -f ./src/logRedactionWorker.mjs ./dist/",
3031
"ci": "run-s build test",
3132
"test:integration": "NODE_ENV=production ts-node ./tests/integration/main.ts",
3233
"prepare": "husky install"
3334
},
35+
"peerDependencies": {
36+
"fastify": "^4"
37+
},
3438
"dependencies": {
35-
"@47ng/check-env": "^2.1.0",
36-
"@sentry/node": "^6.18.1",
37-
"fastify": "^3.27.2",
38-
"fastify-autoload": "^3.11.0",
39-
"fastify-plugin": "^3.0.1",
40-
"fastify-sensible": "^3.1.2",
39+
"@47ng/check-env": "^3.0.0",
40+
"@fastify/autoload": "^5.4.1",
41+
"@fastify/sensible": "^5.1.1",
42+
"@fastify/under-pressure": "^8.1.0",
43+
"@sentry/node": "^7.16.0",
44+
"fastify-plugin": "^4.3.0",
4145
"get-port": "^6.1.2",
42-
"nanoid": "^3.3.1",
43-
"redact-env": "^0.3.1",
44-
"sonic-boom": "^2.6.0",
45-
"under-pressure": "^5.8.0"
46+
"pino-abstract-transport": "^1.0.0",
47+
"redact-env": "^1.0.0",
48+
"sonic-boom": "^3.2.0"
4649
},
4750
"devDependencies": {
48-
"@commitlint/config-conventional": "^16.2.1",
49-
"@types/jest": "^27.4.1",
50-
"@types/node": "^17.0.21",
51+
"@commitlint/config-conventional": "^17.1.0",
52+
"@swc/cli": "^0.1.57",
53+
"@swc/core": "^1.3.11",
54+
"@swc/helpers": "^0.4.12",
55+
"@swc/jest": "^0.2.23",
56+
"@types/jest": "^29.2.0",
57+
"@types/node": "^18.11.5",
5158
"@types/pino": "7.0.5",
52-
"@types/sonic-boom": "^2.1.1",
53-
"axios": "^0.26.0",
54-
"commitlint": "^16.2.1",
55-
"husky": "^7.0.4",
56-
"jest": "^27.5.1",
59+
"axios": "^1.1.3",
60+
"commitlint": "^17.1.2",
61+
"fastify": "^4",
62+
"husky": "^8.0.1",
63+
"jest": "^29.2.2",
5764
"npm-run-all": "^4.1.5",
58-
"regenerator-runtime": "^0.13.9",
59-
"sentry-testkit": "^3.3.7",
60-
"ts-jest": "^27.1.3",
61-
"ts-node": "^10.6.0",
62-
"typescript": "^4.6.2",
65+
"regenerator-runtime": "^0.13.10",
66+
"sentry-testkit": "^5.0.3",
67+
"ts-jest": "^29.0.3",
68+
"ts-node": "^10.9.1",
69+
"typescript": "^4.8.4",
6370
"wait-for-expect": "^3.0.2"
6471
},
6572
"jest": {
6673
"verbose": true,
67-
"preset": "ts-jest/presets/js-with-ts",
74+
"transform": {
75+
"^.+\\.(t|j)sx?$": [
76+
"@swc/jest"
77+
]
78+
},
6879
"testEnvironment": "node",
80+
"testMatch": [
81+
"<rootDir>/tests/**/*.test.ts"
82+
],
6983
"testPathIgnorePatterns": [
7084
"/node_modules/",
7185
"<rootDir>/tests/integration/"

src/graceful-shutdown.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// - await async onClose hooks
55
// - add some options
66

7-
import { FastifyPluginAsync } from 'fastify'
7+
import type { FastifyPluginAsync } from 'fastify'
88
import fp from 'fastify-plugin'
99
import { performance } from 'node:perf_hooks'
1010

@@ -85,6 +85,6 @@ const gracefulShutdownPlugin: FastifyPluginAsync<GracefulShutdownOptions> =
8585
}
8686

8787
export default fp(gracefulShutdownPlugin, {
88-
fastify: '3.x',
88+
fastify: '4.x',
8989
name: 'fastify-micro:graceful-shutdown'
9090
})

src/index.ts

Lines changed: 5 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import checkEnv from '@47ng/check-env'
1+
import { checkEnv } from '@47ng/check-env'
2+
import { AutoloadPluginOptions, fastifyAutoload } from '@fastify/autoload'
3+
import sensible from '@fastify/sensible'
4+
import underPressurePlugin from '@fastify/under-pressure'
25
import Fastify, { FastifyInstance, FastifyServerOptions } from 'fastify'
3-
import { AutoloadPluginOptions, fastifyAutoload } from 'fastify-autoload'
4-
import 'fastify-sensible'
5-
import sensible from 'fastify-sensible'
6-
import underPressurePlugin from 'under-pressure'
76
import gracefulShutdown, { GracefulShutdownOptions } from './graceful-shutdown'
87
import { getLoggerOptions, makeReqIdGenerator } from './logger'
98
import sentry, { SentryOptions } from './sentry'
@@ -44,19 +43,6 @@ export type Options = FastifyServerOptions & {
4443
*/
4544
redactLogPaths?: string[]
4645

47-
/**
48-
* @deprecated - Use `plugins` instead to load plugins from the filesystem.
49-
*
50-
* Add your own plugins in this callback.
51-
*
52-
* It's called after most built-in plugins have run,
53-
* but before loading your routes (if enabled with routesDir).
54-
*
55-
* This is where we recommend registering interfaces
56-
* to your service's data stores.
57-
*/
58-
configure?: (server: FastifyInstance) => void
59-
6046
/**
6147
* Add custom options for under-pressure
6248
*/
@@ -89,19 +75,6 @@ export type Options = FastifyServerOptions & {
8975
*/
9076
routes?: AutoloadPluginOptions
9177

92-
/**
93-
* @deprecated - Use `routes` instead, with full `fastify-autoload` options.
94-
*
95-
* Path to a directory where to load routes.
96-
*
97-
* This directory will be walked recursively and any file encountered
98-
* will be registered as a fastify plugin.
99-
* Routes are loaded after `configure` has run (if specified).
100-
*
101-
* Pass `false` to disable (it is disabled by default).
102-
*/
103-
routesDir?: string | false
104-
10578
/**
10679
* Run cleanup tasks before exiting.
10780
*
@@ -125,12 +98,10 @@ export type Options = FastifyServerOptions & {
12598

12699
export function createServer(
127100
options: Options = {
128-
printRoutes: 'auto',
129-
routesDir: false
101+
printRoutes: 'auto'
130102
}
131103
) {
132104
checkEnv({ required: ['NODE_ENV'] })
133-
134105
const server = Fastify({
135106
logger: getLoggerOptions(options),
136107
genReqId: makeReqIdGenerator(),
@@ -148,14 +119,6 @@ export function createServer(
148119
if (options.plugins) {
149120
server.register(fastifyAutoload, options.plugins)
150121
}
151-
if (options.configure) {
152-
if (process.env.NODE_ENV === 'development') {
153-
console.warn(
154-
'[fastify-micro] Option `configure` is deprecated. Use `plugins` instead with full fastify-autoload options.'
155-
)
156-
}
157-
options.configure(server)
158-
}
159122

160123
const afterPlugins = server.after(error => {
161124
if (error) {
@@ -196,16 +159,6 @@ export function createServer(
196159
if (options.routes) {
197160
server.register(fastifyAutoload, options.routes)
198161
}
199-
if (options.routesDir) {
200-
if (process.env.NODE_ENV === 'development') {
201-
console.warn(
202-
'[fastify-micro] Option `routesDir` is deprecated. Use `routes` instead with full fastify-autoload options.'
203-
)
204-
}
205-
server.register(fastifyAutoload, {
206-
dir: options.routesDir
207-
})
208-
}
209162

210163
if (options.cleanupOnExit) {
211164
server.addHook('onClose', options.cleanupOnExit)

src/logRedactionWorker.mjs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import build from 'pino-abstract-transport'
2+
import * as redactEnv from 'redact-env'
3+
4+
export default async function redactEnvInLogsWorker(options) {
5+
// todo: Build the regexp on the main thread?
6+
const regexp = redactEnv.build(options.redactEnv)
7+
return build(
8+
async function redactEnvInLogs(source) {
9+
for await (let line of source) {
10+
console.log('• ' + redactEnv.redact(line, regexp))
11+
}
12+
},
13+
{
14+
parse: 'lines'
15+
}
16+
)
17+
}

src/logger.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { FastifyLoggerOptions, FastifyRequest } from 'fastify'
2-
import { nanoid } from 'nanoid'
1+
import type { FastifyReply, FastifyRequest } from 'fastify'
32
import crypto from 'node:crypto'
43
import pino from 'pino'
54
import redactEnv from 'redact-env'
65
import SonicBoom from 'sonic-boom'
76
import type { Options } from './index'
7+
import { randomID } from './randomID'
88

99
function createRedactedStream(
1010
pipeTo: SonicBoom,
@@ -23,10 +23,11 @@ export function getLoggerOptions({
2323
name,
2424
redactEnv = [],
2525
redactLogPaths = []
26-
}: Options): FastifyLoggerOptions & pino.LoggerOptions & { stream: any } {
26+
}: Options) {
27+
// todo: Move env redaction to a Pino v7+ Transport
2728
return {
2829
level:
29-
process.env.LOG_LEVEL ||
30+
process.env.LOG_LEVEL ??
3031
(process.env.DEBUG === 'true' ? 'debug' : 'info'),
3132
redact: [
3233
// Security redactions
@@ -47,14 +48,14 @@ export function getLoggerOptions({
4748
commit: process.env.COMMIT_ID?.slice(0, 8)
4849
},
4950
serializers: {
50-
req(req) {
51+
req(req: FastifyRequest) {
5152
return {
5253
method: req.method,
5354
url: req.url,
5455
headers: req.headers
5556
}
5657
},
57-
res(res) {
58+
res(res: FastifyReply) {
5859
// Response has already be sent at time of logging,
5960
// so we need to parse the headers to log them.
6061
// Trying to collect them earlier to show them here
@@ -91,7 +92,7 @@ export function getLoggerOptions({
9192
}
9293
}
9394

94-
export const makeReqIdGenerator = (defaultSalt: string = nanoid()) =>
95+
export const makeReqIdGenerator = (defaultSalt: string = randomID()) =>
9596
function genReqId(req: FastifyRequest): string {
9697
let ipAddress: string = ''
9798
const xForwardedFor = req.headers['x-forwarded-for']
@@ -101,17 +102,12 @@ export const makeReqIdGenerator = (defaultSalt: string = nanoid()) =>
101102
? xForwardedFor.split(',')[0]
102103
: xForwardedFor[0].split(',')[0]
103104
} else {
104-
ipAddress = req.socket?.remoteAddress || ''
105+
ipAddress = req.socket?.remoteAddress ?? ''
105106
}
106107
const hash = crypto.createHash('sha256')
107108
hash.update(ipAddress)
108-
hash.update(req.headers['user-agent'] || '')
109-
hash.update(process.env.LOG_FINGERPRINT_SALT || defaultSalt)
110-
const fingerprint = hash
111-
.digest('base64')
112-
.slice(0, 16)
113-
.replace(/\+/g, '-')
114-
.replace(/\//g, '_')
115-
116-
return [fingerprint, nanoid(16)].join('.')
109+
hash.update(req.headers['user-agent'] ?? '')
110+
hash.update(process.env.LOG_FINGERPRINT_SALT ?? defaultSalt)
111+
const fingerprint = hash.digest('base64url').slice(0, 16)
112+
return [fingerprint, randomID(12)].join('.')
117113
}

src/randomID.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import crypto from 'node:crypto'
2+
3+
export function randomID(bytes: number = 12) {
4+
return crypto.randomBytes(bytes).toString('base64url')
5+
}

src/sentry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,6 @@ function sentryPlugin(
148148
}
149149

150150
export default fp(sentryPlugin as FastifyPluginCallback<SentryOptions>, {
151-
fastify: '3.x',
151+
fastify: '4.x',
152152
name: 'fastify-micro:sentry'
153153
})

tests/basics.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import axios from 'axios'
2-
import { nanoid } from 'nanoid'
32
import { createServer, startServer } from '../src'
3+
import { randomID } from '../src/randomID'
44

55
describe('Basics', () => {
66
beforeEach(() => {
@@ -15,7 +15,7 @@ describe('Basics', () => {
1515
})
1616

1717
test('Default port should be 3000', async () => {
18-
const key = nanoid()
18+
const key = randomID()
1919
const server = createServer()
2020
server.get('/', (_, res) => {
2121
res.send({ key })
@@ -28,7 +28,7 @@ describe('Basics', () => {
2828

2929
test('Port should be configurable via the environment', async () => {
3030
process.env.PORT = '3001'
31-
const key = nanoid()
31+
const key = randomID()
3232
const server = createServer()
3333
server.get('/', (_, res) => {
3434
res.send({ key })
@@ -42,7 +42,7 @@ describe('Basics', () => {
4242

4343
test('Port can be passed as a second argument to `startServer`', async () => {
4444
const server = createServer()
45-
const key = nanoid()
45+
const key = randomID()
4646
server.get('/', (_, res) => {
4747
res.send({ key })
4848
})

0 commit comments

Comments
 (0)