fastify 3.11.0 → 3.14.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.
- package/README.md +15 -10
- package/SECURITY.md +2 -2
- package/build/build-validation.js +25 -1
- package/docs/Benchmarking.md +3 -3
- package/docs/ContentTypeParser.md +21 -0
- package/docs/Decorators.md +1 -1
- package/docs/Ecosystem.md +38 -26
- package/docs/Encapsulation.md +4 -1
- package/docs/Errors.md +41 -41
- package/docs/Getting-Started.md +2 -2
- package/docs/HTTP2.md +1 -1
- package/docs/Hooks.md +35 -4
- package/docs/LTS.md +1 -1
- package/docs/Lifecycle.md +1 -1
- package/docs/Logging.md +4 -4
- package/docs/Middleware.md +3 -3
- package/docs/Migration-Guide-V3.md +3 -3
- package/docs/Plugins-Guide.md +15 -15
- package/docs/Plugins.md +7 -7
- package/docs/Recommendations.md +3 -4
- package/docs/Reply.md +4 -4
- package/docs/Request.md +1 -1
- package/docs/Routes.md +65 -10
- package/docs/Server.md +80 -14
- package/docs/Serverless.md +43 -64
- package/docs/Style-Guide.md +24 -19
- package/docs/Testing.md +5 -5
- package/docs/TypeScript.md +159 -17
- package/docs/Validation-and-Serialization.md +62 -6
- package/docs/Write-Plugin.md +3 -3
- package/fastify.d.ts +14 -3
- package/fastify.js +52 -18
- package/lib/configValidator.js +288 -53
- package/lib/contentTypeParser.js +28 -7
- package/lib/errors.js +1 -1
- package/lib/pluginOverride.js +5 -5
- package/lib/reply.js +35 -28
- package/lib/reqIdGenFactory.js +2 -1
- package/lib/request.js +1 -1
- package/lib/route.js +20 -27
- package/lib/schema-compilers.js +5 -3
- package/lib/schema-controller.js +106 -0
- package/lib/schemas.js +13 -23
- package/lib/symbols.js +1 -3
- package/lib/warnings.js +4 -0
- package/package.json +22 -17
- package/test/constrained-routes.test.js +184 -0
- package/test/content-parser.test.js +179 -7
- package/test/context-config.test.js +52 -0
- package/test/custom-parser.test.js +262 -2
- package/test/hooks-async.test.js +46 -0
- package/test/hooks.test.js +47 -0
- package/test/internals/initialConfig.test.js +30 -5
- package/test/internals/reply.test.js +2 -2
- package/test/internals/request.test.js +3 -9
- package/test/pretty-print.test.js +28 -0
- package/test/route.test.js +0 -2
- package/test/schema-feature.test.js +134 -4
- package/test/schema-serialization.test.js +42 -0
- package/test/schema-special-usage.test.js +234 -0
- package/test/schema-validation.test.js +1 -1
- package/test/stream.test.js +90 -0
- package/test/throw.test.js +1 -1
- package/test/types/content-type-parser.test-d.ts +8 -2
- package/test/types/fastify.test-d.ts +27 -0
- package/test/types/instance.test-d.ts +43 -1
- package/test/types/logger.test-d.ts +8 -3
- package/test/types/reply.test-d.ts +2 -1
- package/test/types/schema.test-d.ts +52 -1
- package/test/versioned-routes.test.js +99 -18
- package/types/content-type-parser.d.ts +10 -4
- package/types/instance.d.ts +57 -7
- package/types/logger.d.ts +1 -1
- package/types/reply.d.ts +2 -1
- package/types/route.d.ts +15 -11
- package/types/schema.d.ts +5 -4
package/docs/TypeScript.md
CHANGED
|
@@ -37,7 +37,7 @@ This example will get you up and running with Fastify and TypeScript. It results
|
|
|
37
37
|
```
|
|
38
38
|
3. Initialize a TypeScript configuration file:
|
|
39
39
|
```bash
|
|
40
|
-
npx
|
|
40
|
+
npx tsc --init
|
|
41
41
|
```
|
|
42
42
|
or use one of the [recommended ones](https://github.com/tsconfig/bases#node-10-tsconfigjson).
|
|
43
43
|
|
|
@@ -82,7 +82,7 @@ The type system heavily relies on generic properties to provide the most accurat
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
interface IHeaders {
|
|
85
|
-
'
|
|
85
|
+
'h-Custom': string;
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
3. Using the two interfaces, define a new API route and pass them as generics. The shorthand route methods (i.e. `.get`) accept a generic object `RequestGenericInterface` containing four named properties: `Body`, `Querystring`, `Params`, and `Headers`. The interfaces will be passed down through the route method into the route method handler `request` instance.
|
|
@@ -92,12 +92,13 @@ The type system heavily relies on generic properties to provide the most accurat
|
|
|
92
92
|
Headers: IHeaders
|
|
93
93
|
}>('/auth', async (request, reply) => {
|
|
94
94
|
const { username, password } = request.query
|
|
95
|
-
const customerHeader = request.headers['
|
|
95
|
+
const customerHeader = request.headers['h-Custom']
|
|
96
96
|
// do something with request data
|
|
97
97
|
|
|
98
98
|
return `logged in!`
|
|
99
99
|
})
|
|
100
100
|
```
|
|
101
|
+
|
|
101
102
|
4. Build and run the server code with `npm run build` and `npm run start`
|
|
102
103
|
5. Query the api
|
|
103
104
|
```bash
|
|
@@ -115,7 +116,7 @@ The type system heavily relies on generic properties to provide the most accurat
|
|
|
115
116
|
done(username !== 'admin' ? new Error('Must be admin') : undefined) // only validate `admin` account
|
|
116
117
|
}
|
|
117
118
|
}, async (request, reply) => {
|
|
118
|
-
const customerHeader = request.headers['
|
|
119
|
+
const customerHeader = request.headers['h-Custom']
|
|
119
120
|
// do something with request data
|
|
120
121
|
return `logged in!`
|
|
121
122
|
})
|
|
@@ -126,25 +127,92 @@ The type system heavily relies on generic properties to provide the most accurat
|
|
|
126
127
|
|
|
127
128
|
### JSON Schema
|
|
128
129
|
|
|
130
|
+
To validate your requests and responses you can use JSON Schema files. If you didn't know already, defining schemas for your Fastify routes can increase their throughput! Check out the [Validation and Serialization](Validation-and-Serialization.md) documentation for more info.
|
|
131
|
+
|
|
132
|
+
Also it has the advantage to use the defined type within your handlers (including pre-validation, etc.).
|
|
133
|
+
|
|
134
|
+
Here are some options how to achieve this.
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
#### typebox
|
|
138
|
+
|
|
139
|
+
A useful library for building types and a schema at once is [typebox](https://www.npmjs.com/package/@sinclair/typebox).
|
|
140
|
+
With typebox you define your schema within your code and use them directly as types or schemas as you need them.
|
|
141
|
+
|
|
142
|
+
When you want to use it for validation of some payload in a fastify route you can do it as follows:
|
|
143
|
+
|
|
144
|
+
1. Install `typebox` in your project.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm i @sinclair/typebox
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
2. Define the schema you need with `Type` and create the respective type with `Static`.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { Static, Type } from '@sinclair/typebox'
|
|
154
|
+
|
|
155
|
+
const User = Type.Object({
|
|
156
|
+
name: Type.String(),
|
|
157
|
+
mail: Type.Optional(Type.String({ format: "email" })),
|
|
158
|
+
});
|
|
159
|
+
type UserType = Static<typeof User>;
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
3. Use the defined type and schema during the definition of your route
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const app = fastify();
|
|
166
|
+
|
|
167
|
+
app.post<{ Body: UserType; Response: UserType }>(
|
|
168
|
+
"/",
|
|
169
|
+
{
|
|
170
|
+
schema: {
|
|
171
|
+
body: User,
|
|
172
|
+
response: {
|
|
173
|
+
200: User,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
(req, rep) => {
|
|
178
|
+
const { body: user } = req;
|
|
179
|
+
/* user has type
|
|
180
|
+
* const user: StaticProperties<{
|
|
181
|
+
* name: TString;
|
|
182
|
+
* mail: TOptional<TString>;
|
|
183
|
+
* }>
|
|
184
|
+
*/
|
|
185
|
+
//...
|
|
186
|
+
rep.status(200).send(user);
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Schemas in JSON Files
|
|
192
|
+
|
|
129
193
|
In the last example we used interfaces to define the types for the request querystring and headers. Many users will already be using JSON Schemas to define these properties, and luckily there is a way to transform existing JSON Schemas into TypeScript interfaces!
|
|
130
194
|
|
|
131
195
|
1. If you did not complete the 'Getting Started' example, go back and follow steps 1-4 first.
|
|
132
196
|
2. Install the `json-schema-to-typescript` module:
|
|
133
|
-
|
|
197
|
+
|
|
198
|
+
```bash
|
|
134
199
|
npm i -D json-schema-to-typescript
|
|
135
200
|
```
|
|
201
|
+
|
|
136
202
|
3. Create a new folder called `schemas` and add two files `headers.json` and `querystring.json`. Copy and paste the following schema definitions into the respective files:
|
|
203
|
+
|
|
137
204
|
```json
|
|
138
205
|
{
|
|
139
206
|
"title": "Headers Schema",
|
|
140
207
|
"type": "object",
|
|
141
208
|
"properties": {
|
|
142
|
-
"
|
|
209
|
+
"h-Custom": { "type": "string" }
|
|
143
210
|
},
|
|
144
211
|
"additionalProperties": false,
|
|
145
|
-
"required": ["
|
|
212
|
+
"required": ["h-Custom"]
|
|
146
213
|
}
|
|
147
214
|
```
|
|
215
|
+
|
|
148
216
|
```json
|
|
149
217
|
{
|
|
150
218
|
"title": "Querystring Schema",
|
|
@@ -157,18 +225,22 @@ In the last example we used interfaces to define the types for the request query
|
|
|
157
225
|
"required": ["username", "password"]
|
|
158
226
|
}
|
|
159
227
|
```
|
|
228
|
+
|
|
160
229
|
4. Add a `compile-schemas` script to the package.json:
|
|
161
|
-
|
|
230
|
+
|
|
231
|
+
```json
|
|
162
232
|
{
|
|
163
233
|
"scripts": {
|
|
164
234
|
"compile-schemas": "json2ts -i schemas -o types"
|
|
165
235
|
}
|
|
166
236
|
}
|
|
167
|
-
|
|
237
|
+
```
|
|
238
|
+
|
|
168
239
|
`json2ts` is a CLI utility included in `json-schema-to-typescript`. `schemas` is the input path, and `types` is the output path.
|
|
169
240
|
5. Run `npm run compile-schemas`. Two new files should have been created in the `types` directory.
|
|
170
241
|
6. Update `index.ts` to have the following code:
|
|
171
|
-
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
172
244
|
import fastify from 'fastify'
|
|
173
245
|
|
|
174
246
|
// import json schemas as normal
|
|
@@ -194,7 +266,7 @@ In the last example we used interfaces to define the types for the request query
|
|
|
194
266
|
done(username !== 'admin' ? new Error('Must be admin') : undefined)
|
|
195
267
|
}
|
|
196
268
|
}, async (request, reply) => {
|
|
197
|
-
const customerHeader = request.headers['
|
|
269
|
+
const customerHeader = request.headers['h-Custom']
|
|
198
270
|
// do something with request data
|
|
199
271
|
return `logged in!`
|
|
200
272
|
})
|
|
@@ -211,11 +283,11 @@ In the last example we used interfaces to define the types for the request query
|
|
|
211
283
|
},
|
|
212
284
|
preHandler: (request, reply) => {
|
|
213
285
|
const { username, password } = request.query
|
|
214
|
-
const customerHeader = request.headers['
|
|
286
|
+
const customerHeader = request.headers['h-Custom']
|
|
215
287
|
},
|
|
216
288
|
handler: (request, reply) => {
|
|
217
289
|
const { username, password } = request.query
|
|
218
|
-
const customerHeader = request.headers['
|
|
290
|
+
const customerHeader = request.headers['h-Custom']
|
|
219
291
|
}
|
|
220
292
|
})
|
|
221
293
|
|
|
@@ -229,10 +301,67 @@ In the last example we used interfaces to define the types for the request query
|
|
|
229
301
|
```
|
|
230
302
|
Pay special attention to the imports at the top of this file. It might seem redundant, but you need to import both the schema files and the generated interfaces.
|
|
231
303
|
|
|
232
|
-
Great work! Now you can make use of both JSON Schemas and TypeScript definitions.
|
|
304
|
+
Great work! Now you can make use of both JSON Schemas and TypeScript definitions.
|
|
305
|
+
|
|
306
|
+
#### json-schema-to-ts
|
|
307
|
+
|
|
308
|
+
If you do not want to generate types from your schemas, but want to use them diretly from your code, you can use the package
|
|
309
|
+
[json-schema-to-ts](https://www.npmjs.com/package/json-schema-to-ts).
|
|
310
|
+
|
|
311
|
+
You can install it as dev-dependency.
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
npm install -D json-schema-to-ts
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
In your code you can define your schema like a normal object. But be aware of making it *const* like explained in the docs of the module.
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const todo = {
|
|
321
|
+
type: 'object',
|
|
322
|
+
properties: {
|
|
323
|
+
name: { type: 'string' },
|
|
324
|
+
description: { type: 'string' },
|
|
325
|
+
done: { type: 'boolean' },
|
|
326
|
+
},
|
|
327
|
+
required: ['name'],
|
|
328
|
+
} as const;
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
With the provided type `FromSchema` you can build a type from your schema and use it in your handler.
|
|
233
332
|
|
|
234
|
-
|
|
235
|
-
|
|
333
|
+
```typescript
|
|
334
|
+
fastify.post<{ Body: FromSchema<typeof todo> }>(
|
|
335
|
+
'/todo',
|
|
336
|
+
{
|
|
337
|
+
schema: {
|
|
338
|
+
body: todo,
|
|
339
|
+
response: {
|
|
340
|
+
201: {
|
|
341
|
+
type: 'string',
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
async (request, reply): Promise<void> => {
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
request.body has type
|
|
350
|
+
{
|
|
351
|
+
[x: string]: unknown;
|
|
352
|
+
description?: string;
|
|
353
|
+
done?: boolean;
|
|
354
|
+
name: string;
|
|
355
|
+
}
|
|
356
|
+
*/
|
|
357
|
+
|
|
358
|
+
request.body.name // will not throw type error
|
|
359
|
+
request.body.notthere // will throw type error
|
|
360
|
+
|
|
361
|
+
reply.status(201).send();
|
|
362
|
+
},
|
|
363
|
+
);
|
|
364
|
+
```
|
|
236
365
|
|
|
237
366
|
### Plugins
|
|
238
367
|
|
|
@@ -393,6 +522,19 @@ However, there are a couple of suggestions to help improve this experience:
|
|
|
393
522
|
- Make sure the `no-unused-vars` rule is enabled in [ESLint](https://eslint.org/docs/rules/no-unused-vars) and any imported plugin are actually being loaded.
|
|
394
523
|
- Use a module such as [depcheck](https://www.npmjs.com/package/depcheck) or [npm-check](https://www.npmjs.com/package/npm-check) to verify plugin dependencies are being used somewhere in your project.
|
|
395
524
|
|
|
525
|
+
## Code Completion In Vanilla JavaScript
|
|
526
|
+
|
|
527
|
+
Vanilla JavaScript can use the published types to provide code completion (e.g. [Intellisense](https://code.visualstudio.com/docs/editor/intellisense)) by following the [TypeScript JSDoc Reference](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html).
|
|
528
|
+
|
|
529
|
+
For example:
|
|
530
|
+
|
|
531
|
+
```js
|
|
532
|
+
/** @type {import('fastify').FastifyPluginAsync<{ optionA: boolean, optionB: string }>} */
|
|
533
|
+
module.exports = async function (fastify, { optionA, optionB }) {
|
|
534
|
+
fastify.get('/look', () => 'at me');
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
396
538
|
## API Type System Documentation
|
|
397
539
|
|
|
398
540
|
This section is a detailed account of all the types available to you in Fastify version 3.x
|
|
@@ -704,7 +846,7 @@ import fastify, { RequestGenericInterface } from 'fastify'
|
|
|
704
846
|
|
|
705
847
|
const server = fastify()
|
|
706
848
|
|
|
707
|
-
|
|
849
|
+
interface requestGeneric extends RequestGenericInterface {
|
|
708
850
|
Querystring: {
|
|
709
851
|
name: string
|
|
710
852
|
}
|
|
@@ -101,7 +101,7 @@ As usual, the function `getSchemas` is encapsulated and returns the shared schem
|
|
|
101
101
|
```js
|
|
102
102
|
fastify.addSchema({ $id: 'one', my: 'hello' })
|
|
103
103
|
// will return only `one` schema
|
|
104
|
-
fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })
|
|
104
|
+
fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) })
|
|
105
105
|
|
|
106
106
|
fastify.register((instance, opts, done) => {
|
|
107
107
|
instance.addSchema({ $id: 'two', my: 'ciao' })
|
|
@@ -198,6 +198,62 @@ fastify.post('/the/url', { schema }, handler)
|
|
|
198
198
|
|
|
199
199
|
*Note that Ajv will try to [coerce](https://github.com/epoberezkin/ajv#coercing-data-types) the values to the types specified in your schema `type` keywords, both to pass the validation and to use the correctly typed data afterwards.*
|
|
200
200
|
|
|
201
|
+
The Ajv default configuration in Fastify doesn't support coercing array parameters in querystring. However, Fastify allows [`customOptions`](Server.md#ajv) in Ajv instance. The `coerceTypes: 'array'` will coerce one parameter to a single element in array. Example:
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
const opts = {
|
|
205
|
+
schema: {
|
|
206
|
+
querystring: {
|
|
207
|
+
type: 'object',
|
|
208
|
+
properties: {
|
|
209
|
+
ids: {
|
|
210
|
+
type: 'array',
|
|
211
|
+
default: []
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fastify.get('/', opts, (request, reply) => {
|
|
219
|
+
reply.send({ params: request.query })
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
fastify.listen(3000, (err) => {
|
|
223
|
+
if (err) throw err
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Using Fastify defaults the following request will result in `400` status code:
|
|
228
|
+
|
|
229
|
+
```sh
|
|
230
|
+
curl -X GET "http://localhost:3000/?ids=1
|
|
231
|
+
|
|
232
|
+
{"statusCode":400,"error":"Bad Request","message":"querystring/hello should be array"}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Using `coerceTypes` as 'array' should fix it:
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
const ajv = new Ajv({
|
|
239
|
+
removeAdditional: true,
|
|
240
|
+
useDefaults: true,
|
|
241
|
+
coerceTypes: 'array', // This line
|
|
242
|
+
allErrors: true
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
|
|
246
|
+
return ajv.compile(schema)
|
|
247
|
+
})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
curl -X GET "http://localhost:3000/?ids=1
|
|
252
|
+
|
|
253
|
+
{"params":{"hello":["1"]}}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
For further information see [here](https://ajv.js.org/docs/coercion.html)
|
|
201
257
|
|
|
202
258
|
<a name="ajv-plugins"></a>
|
|
203
259
|
#### Ajv Plugins
|
|
@@ -488,10 +544,10 @@ const schema = {
|
|
|
488
544
|
and fail to satisfy it, the route will immediately return a response with the following payload
|
|
489
545
|
|
|
490
546
|
```js
|
|
491
|
-
{
|
|
547
|
+
{
|
|
492
548
|
"statusCode": 400,
|
|
493
549
|
"error": "Bad Request",
|
|
494
|
-
"message": "body should have required property 'name'"
|
|
550
|
+
"message": "body should have required property 'name'"
|
|
495
551
|
}
|
|
496
552
|
```
|
|
497
553
|
|
|
@@ -519,7 +575,7 @@ The context function will be the Fastify server instance.
|
|
|
519
575
|
```js
|
|
520
576
|
const fastify = Fastify({
|
|
521
577
|
schemaErrorFormatter: (errors, dataVar) => {
|
|
522
|
-
// ... my formatting logic
|
|
578
|
+
// ... my formatting logic
|
|
523
579
|
return new Error(myErrorMessage)
|
|
524
580
|
}
|
|
525
581
|
})
|
|
@@ -527,7 +583,7 @@ const fastify = Fastify({
|
|
|
527
583
|
// or
|
|
528
584
|
fastify.setSchemaErrorFormatter(function (errors, dataVar) {
|
|
529
585
|
this.log.error({ err: errors }, 'Validation failed')
|
|
530
|
-
// ... my formatting logic
|
|
586
|
+
// ... my formatting logic
|
|
531
587
|
return new Error(myErrorMessage)
|
|
532
588
|
})
|
|
533
589
|
```
|
|
@@ -542,7 +598,7 @@ fastify.setErrorHandler(function (error, request, reply) {
|
|
|
542
598
|
})
|
|
543
599
|
```
|
|
544
600
|
|
|
545
|
-
If you want custom error response in schema without headaches and quickly, you can take a look at [`ajv-errors`](https://github.com/epoberezkin/ajv-errors). Checkout the [example](https://github.com/fastify/example/blob/
|
|
601
|
+
If you want custom error response in schema without headaches and quickly, you can take a look at [`ajv-errors`](https://github.com/epoberezkin/ajv-errors). Checkout the [example](https://github.com/fastify/example/blob/HEAD/validation-messages/custom-errors-messages.js) usage.
|
|
546
602
|
|
|
547
603
|
Below is an example showing how to add **custom error messages for each property** of a schema by supplying custom AJV options.
|
|
548
604
|
Inline comments in the schema below describe how to configure it to show a different error message for each case:
|
package/docs/Write-Plugin.md
CHANGED
|
@@ -41,12 +41,12 @@ It is not mandatory, but we highly recommend you use a code linter in your plugi
|
|
|
41
41
|
We use [`standard`](https://standardjs.com/) since it works without the need to configure it and is very easy integrate in a test suite.
|
|
42
42
|
|
|
43
43
|
## Continuous Integration
|
|
44
|
-
It is not mandatory, but if you release your code as open source it helps to use Continuous Integration to ensure contributions do not break your plugin and to show that the plugin works as intended. Both [CircleCI](https://circleci.com/) and [GitHub Actions](https://github.com/features/actions) are free for open source projects and easy to setup.<br>
|
|
45
|
-
In addition you can enable services like [Dependabot](https://dependabot.com/) or [Snyk](https://snyk.io/),
|
|
44
|
+
It is not mandatory, but if you release your code as open source, it helps to use Continuous Integration to ensure contributions do not break your plugin and to show that the plugin works as intended. Both [CircleCI](https://circleci.com/) and [GitHub Actions](https://github.com/features/actions) are free for open source projects and easy to setup.<br>
|
|
45
|
+
In addition, you can enable services like [Dependabot](https://dependabot.com/) or [Snyk](https://snyk.io/), which will help you keep your dependencies up to date and discover if a new release of Fastify has some issues with your plugin.
|
|
46
46
|
|
|
47
47
|
## Let's start!
|
|
48
48
|
Awesome, now you know everything you need to know about how to write a good plugin for Fastify!
|
|
49
|
-
After you
|
|
49
|
+
After you have built one (or more!) let us know! We will add it to the [ecosystem](https://github.com/fastify/fastify#ecosystem) section of our documentation!
|
|
50
50
|
|
|
51
51
|
If you want to see some real world examples, checkout:
|
|
52
52
|
- [`point-of-view`](https://github.com/fastify/point-of-view)
|
package/fastify.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as http from 'http'
|
|
|
2
2
|
import * as http2 from 'http2'
|
|
3
3
|
import * as https from 'https'
|
|
4
4
|
import * as LightMyRequest from 'light-my-request'
|
|
5
|
+
import { ConstraintStrategy, HTTPVersion } from 'find-my-way'
|
|
5
6
|
|
|
6
7
|
import { FastifyRequest, RequestGenericInterface } from './types/request'
|
|
7
8
|
import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression } from './types/utils'
|
|
@@ -12,6 +13,7 @@ import * as ajv from 'ajv'
|
|
|
12
13
|
import { FastifyError } from 'fastify-error'
|
|
13
14
|
import { FastifyReply } from './types/reply'
|
|
14
15
|
import { FastifySchemaValidationError } from './types/schema'
|
|
16
|
+
import { ConstructorAction, ProtoAction } from "./types/content-type-parser";
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Fastify factory function for the standard fastify http, https, or http2 server instance.
|
|
@@ -69,6 +71,9 @@ export type FastifyHttpsOptions<
|
|
|
69
71
|
> = FastifyServerOptions<Server, Logger> & {
|
|
70
72
|
https: https.ServerOptions
|
|
71
73
|
}
|
|
74
|
+
|
|
75
|
+
type FindMyWayVersion<RawServer extends RawServerBase> = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2
|
|
76
|
+
|
|
72
77
|
/**
|
|
73
78
|
* Options for a fastify server instance. Utilizes conditional logic on the generic server parameter to enforce certain https and http2
|
|
74
79
|
*/
|
|
@@ -84,8 +89,8 @@ export type FastifyServerOptions<
|
|
|
84
89
|
maxParamLength?: number,
|
|
85
90
|
disableRequestLogging?: boolean,
|
|
86
91
|
exposeHeadRoutes?: boolean,
|
|
87
|
-
onProtoPoisoning?:
|
|
88
|
-
onConstructorPoisoning?:
|
|
92
|
+
onProtoPoisoning?: ProtoAction,
|
|
93
|
+
onConstructorPoisoning?: ConstructorAction,
|
|
89
94
|
logger?: boolean | FastifyLoggerOptions<RawServer> | Logger,
|
|
90
95
|
serverFactory?: FastifyServerFactory<RawServer>,
|
|
91
96
|
caseSensitive?: boolean,
|
|
@@ -94,6 +99,9 @@ export type FastifyServerOptions<
|
|
|
94
99
|
genReqId?: <RequestGeneric extends RequestGenericInterface = RequestGenericInterface>(req: FastifyRequest<RequestGeneric, RawServer, RawRequestDefaultExpression<RawServer>>) => string,
|
|
95
100
|
trustProxy?: boolean | string | string[] | number | TrustProxyFunction,
|
|
96
101
|
querystringParser?: (str: string) => { [key: string]: unknown },
|
|
102
|
+
/**
|
|
103
|
+
* @deprecated Prefer using the `constraints.version` property
|
|
104
|
+
*/
|
|
97
105
|
versioning?: {
|
|
98
106
|
storage(): {
|
|
99
107
|
get(version: string): string | null,
|
|
@@ -103,6 +111,9 @@ export type FastifyServerOptions<
|
|
|
103
111
|
},
|
|
104
112
|
deriveVersion<Context>(req: Object, ctx?: Context): string // not a fan of using Object here. Also what is Context? Can either of these be better defined?
|
|
105
113
|
},
|
|
114
|
+
constraints?: {
|
|
115
|
+
[name: string]: ConstraintStrategy<FindMyWayVersion<RawServer>>,
|
|
116
|
+
},
|
|
106
117
|
return503OnClosing?: boolean,
|
|
107
118
|
ajv?: {
|
|
108
119
|
customOptions?: ajv.Options,
|
|
@@ -142,7 +153,7 @@ export { FastifyLoggerOptions, FastifyLoggerInstance, FastifyLogFn, LogLevel } f
|
|
|
142
153
|
export { FastifyContext } from './types/context'
|
|
143
154
|
export { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route'
|
|
144
155
|
export * from './types/register'
|
|
145
|
-
export { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser } from './types/content-type-parser'
|
|
156
|
+
export { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser'
|
|
146
157
|
export { FastifyError } from 'fastify-error'
|
|
147
158
|
export { FastifySchema, FastifySchemaCompiler } from './types/schema'
|
|
148
159
|
export { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './types/utils'
|
package/fastify.js
CHANGED
|
@@ -15,9 +15,7 @@ const {
|
|
|
15
15
|
kLogLevel,
|
|
16
16
|
kLogSerializers,
|
|
17
17
|
kHooks,
|
|
18
|
-
|
|
19
|
-
kValidatorCompiler,
|
|
20
|
-
kSerializerCompiler,
|
|
18
|
+
kSchemaController,
|
|
21
19
|
kReplySerializerDefault,
|
|
22
20
|
kContentTypeParser,
|
|
23
21
|
kReply,
|
|
@@ -36,8 +34,8 @@ const Request = require('./lib/request')
|
|
|
36
34
|
const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
|
|
37
35
|
const decorator = require('./lib/decorate')
|
|
38
36
|
const ContentTypeParser = require('./lib/contentTypeParser')
|
|
37
|
+
const SchemaController = require('./lib/schema-controller')
|
|
39
38
|
const { Hooks, hookRunnerApplication } = require('./lib/hooks')
|
|
40
|
-
const { Schemas } = require('./lib/schemas')
|
|
41
39
|
const { createLogger } = require('./lib/logger')
|
|
42
40
|
const pluginUtils = require('./lib/pluginUtils')
|
|
43
41
|
const reqIdGenFactory = require('./lib/reqIdGenFactory')
|
|
@@ -45,6 +43,7 @@ const { buildRouting, validateBodyLimitOption } = require('./lib/route')
|
|
|
45
43
|
const build404 = require('./lib/fourOhFour')
|
|
46
44
|
const getSecuredInitialConfig = require('./lib/initialConfigValidation')
|
|
47
45
|
const override = require('./lib/pluginOverride')
|
|
46
|
+
const warning = require('./lib/warnings')
|
|
48
47
|
const { defaultInitOptions } = getSecuredInitialConfig
|
|
49
48
|
|
|
50
49
|
const {
|
|
@@ -86,6 +85,10 @@ function fastify (options) {
|
|
|
86
85
|
throw new Error(`querystringParser option should be a function, instead got '${typeof options.querystringParser}'`)
|
|
87
86
|
}
|
|
88
87
|
|
|
88
|
+
if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
|
|
89
|
+
throw new Error(`schemaController.bucket option should be a function, instead got '${typeof options.schemaController.bucket}'`)
|
|
90
|
+
}
|
|
91
|
+
|
|
89
92
|
validateBodyLimitOption(options.bodyLimit)
|
|
90
93
|
|
|
91
94
|
const requestIdHeader = options.requestIdHeader || defaultInitOptions.requestIdHeader
|
|
@@ -132,15 +135,34 @@ function fastify (options) {
|
|
|
132
135
|
|
|
133
136
|
const initialConfig = getSecuredInitialConfig(options)
|
|
134
137
|
|
|
138
|
+
let constraints = options.constraints
|
|
139
|
+
if (options.versioning) {
|
|
140
|
+
warning.emit('FSTDEP009')
|
|
141
|
+
constraints = {
|
|
142
|
+
...constraints,
|
|
143
|
+
version: {
|
|
144
|
+
name: 'version',
|
|
145
|
+
mustMatchWhenDerived: true,
|
|
146
|
+
storage: options.versioning.storage,
|
|
147
|
+
deriveConstraint: options.versioning.deriveVersion,
|
|
148
|
+
validate (value) {
|
|
149
|
+
if (typeof value !== 'string') {
|
|
150
|
+
throw new Error('Version constraint should be a string.')
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
135
157
|
// Default router
|
|
136
158
|
const router = buildRouting({
|
|
137
159
|
config: {
|
|
138
160
|
defaultRoute: defaultRoute,
|
|
139
161
|
onBadUrl: onBadUrl,
|
|
162
|
+
constraints: constraints,
|
|
140
163
|
ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash,
|
|
141
164
|
maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
|
|
142
|
-
caseSensitive: options.caseSensitive
|
|
143
|
-
versioning: options.versioning
|
|
165
|
+
caseSensitive: options.caseSensitive
|
|
144
166
|
}
|
|
145
167
|
})
|
|
146
168
|
|
|
@@ -155,7 +177,7 @@ function fastify (options) {
|
|
|
155
177
|
const { server, listen } = createServer(options, httpHandler)
|
|
156
178
|
|
|
157
179
|
const setupResponseListeners = Reply.setupResponseListeners
|
|
158
|
-
const
|
|
180
|
+
const schemaController = SchemaController.buildSchemaController(null, options.schemaController)
|
|
159
181
|
|
|
160
182
|
// Public API
|
|
161
183
|
const fastify = {
|
|
@@ -172,11 +194,9 @@ function fastify (options) {
|
|
|
172
194
|
[kLogLevel]: '',
|
|
173
195
|
[kLogSerializers]: null,
|
|
174
196
|
[kHooks]: new Hooks(),
|
|
175
|
-
[
|
|
176
|
-
[kValidatorCompiler]: null,
|
|
197
|
+
[kSchemaController]: schemaController,
|
|
177
198
|
[kSchemaErrorFormatter]: null,
|
|
178
199
|
[kErrorHandler]: defaultErrorHandler,
|
|
179
|
-
[kSerializerCompiler]: null,
|
|
180
200
|
[kReplySerializerDefault]: null,
|
|
181
201
|
[kContentTypeParser]: new ContentTypeParser(
|
|
182
202
|
bodyLimit,
|
|
@@ -230,10 +250,11 @@ function fastify (options) {
|
|
|
230
250
|
addHook: addHook,
|
|
231
251
|
// schemas
|
|
232
252
|
addSchema: addSchema,
|
|
233
|
-
getSchema:
|
|
234
|
-
getSchemas:
|
|
253
|
+
getSchema: schemaController.getSchema.bind(schemaController),
|
|
254
|
+
getSchemas: schemaController.getSchemas.bind(schemaController),
|
|
235
255
|
setValidatorCompiler: setValidatorCompiler,
|
|
236
256
|
setSerializerCompiler: setSerializerCompiler,
|
|
257
|
+
setSchemaController: setSchemaController,
|
|
237
258
|
setReplySerializer: setReplySerializer,
|
|
238
259
|
setSchemaErrorFormatter: setSchemaErrorFormatter,
|
|
239
260
|
// custom parsers
|
|
@@ -247,6 +268,7 @@ function fastify (options) {
|
|
|
247
268
|
ready: null,
|
|
248
269
|
onClose: null,
|
|
249
270
|
close: null,
|
|
271
|
+
printPlugins: null,
|
|
250
272
|
// http server
|
|
251
273
|
listen: listen,
|
|
252
274
|
server: server,
|
|
@@ -281,10 +303,10 @@ function fastify (options) {
|
|
|
281
303
|
get () { return this[kRoutePrefix] }
|
|
282
304
|
},
|
|
283
305
|
validatorCompiler: {
|
|
284
|
-
get () { return this[
|
|
306
|
+
get () { return this[kSchemaController].getValidatorCompiler() }
|
|
285
307
|
},
|
|
286
308
|
serializerCompiler: {
|
|
287
|
-
get () { return this[
|
|
309
|
+
get () { return this[kSchemaController].getSerializerCompiler() }
|
|
288
310
|
},
|
|
289
311
|
version: {
|
|
290
312
|
get () {
|
|
@@ -330,6 +352,8 @@ function fastify (options) {
|
|
|
330
352
|
avvio.on('start', () => (fastify[kState].started = true))
|
|
331
353
|
fastify[kAvvioBoot] = fastify.ready // the avvio ready function
|
|
332
354
|
fastify.ready = ready // overwrite the avvio ready function
|
|
355
|
+
fastify.printPlugins = avvio.prettyPrint.bind(avvio)
|
|
356
|
+
|
|
333
357
|
// cache the closing value, since we are checking it in an hot path
|
|
334
358
|
avvio.once('preReady', () => {
|
|
335
359
|
fastify.onClose((instance, done) => {
|
|
@@ -495,7 +519,7 @@ function fastify (options) {
|
|
|
495
519
|
// wrapper that we expose to the user for schemas handling
|
|
496
520
|
function addSchema (schema) {
|
|
497
521
|
throwIfAlreadyStarted('Cannot call "addSchema" when fastify instance is already started!')
|
|
498
|
-
this[
|
|
522
|
+
this[kSchemaController].add(schema)
|
|
499
523
|
this[kChildren].forEach(child => child.addSchema(schema))
|
|
500
524
|
return this
|
|
501
525
|
}
|
|
@@ -516,7 +540,7 @@ function fastify (options) {
|
|
|
516
540
|
|
|
517
541
|
// Most devs do not know what to do with this error.
|
|
518
542
|
// In the vast majority of cases, it's a network error and/or some
|
|
519
|
-
// config issue on the
|
|
543
|
+
// config issue on the load balancer side.
|
|
520
544
|
this.log.trace({ err }, 'client error')
|
|
521
545
|
|
|
522
546
|
// If the socket is not writable, there is no reason to try to send data.
|
|
@@ -562,7 +586,7 @@ function fastify (options) {
|
|
|
562
586
|
|
|
563
587
|
function setValidatorCompiler (validatorCompiler) {
|
|
564
588
|
throwIfAlreadyStarted('Cannot call "setValidatorCompiler" when fastify instance is already started!')
|
|
565
|
-
this[
|
|
589
|
+
this[kSchemaController].setValidatorCompiler(validatorCompiler)
|
|
566
590
|
return this
|
|
567
591
|
}
|
|
568
592
|
|
|
@@ -575,7 +599,17 @@ function fastify (options) {
|
|
|
575
599
|
|
|
576
600
|
function setSerializerCompiler (serializerCompiler) {
|
|
577
601
|
throwIfAlreadyStarted('Cannot call "setSerializerCompiler" when fastify instance is already started!')
|
|
578
|
-
this[
|
|
602
|
+
this[kSchemaController].setSerializerCompiler(serializerCompiler)
|
|
603
|
+
return this
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function setSchemaController (schemaControllerOpts) {
|
|
607
|
+
throwIfAlreadyStarted('Cannot call "setSchemaController" when fastify instance is already started!')
|
|
608
|
+
const old = this[kSchemaController]
|
|
609
|
+
const schemaController = SchemaController.buildSchemaController(old.parent, Object.assign({}, old.opts, schemaControllerOpts))
|
|
610
|
+
this[kSchemaController] = schemaController
|
|
611
|
+
this.getSchema = schemaController.getSchema.bind(schemaController)
|
|
612
|
+
this.getSchemas = schemaController.getSchemas.bind(schemaController)
|
|
579
613
|
return this
|
|
580
614
|
}
|
|
581
615
|
|