@shadow-library/fastify 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +206 -0
- package/cjs/decorators/http-controller.decorator.js +0 -2
- package/cjs/module/fastify-module.interface.d.ts +4 -0
- package/cjs/module/fastify-router.d.ts +1 -0
- package/cjs/module/fastify-router.js +11 -9
- package/cjs/module/fastify.utils.d.ts +9 -2
- package/cjs/module/fastify.utils.js +15 -10
- package/esm/decorators/http-controller.decorator.js +0 -2
- package/esm/module/fastify-module.interface.d.ts +4 -0
- package/esm/module/fastify-router.d.ts +1 -0
- package/esm/module/fastify-router.js +11 -9
- package/esm/module/fastify.utils.d.ts +9 -2
- package/esm/module/fastify.utils.js +15 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -384,6 +384,160 @@ FastifyModule.forRoot({
|
|
|
384
384
|
// Routes will be: GET /api/users (no version prefix)
|
|
385
385
|
```
|
|
386
386
|
|
|
387
|
+
### Global Route Prefix
|
|
388
|
+
|
|
389
|
+
The `routePrefix` configuration option allows you to add a global prefix to all routes in your application. This is useful for:
|
|
390
|
+
|
|
391
|
+
- **API Namespacing**: Prefix all routes with `/api` to clearly distinguish API endpoints from other routes
|
|
392
|
+
- **Multi-tenant Applications**: Add tenant-specific prefixes
|
|
393
|
+
- **Microservices**: Namespace your service routes (e.g., `/user-service`, `/payment-service`)
|
|
394
|
+
- **API Gateways**: Organize routes by domain or service boundaries
|
|
395
|
+
|
|
396
|
+
#### Basic Usage
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
@Module({
|
|
400
|
+
imports: [
|
|
401
|
+
FastifyModule.forRoot({
|
|
402
|
+
controllers: [UserController, ProductController],
|
|
403
|
+
routePrefix: 'api',
|
|
404
|
+
port: 3000,
|
|
405
|
+
}),
|
|
406
|
+
],
|
|
407
|
+
})
|
|
408
|
+
export class AppModule {}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Example Controllers:**
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
@HttpController('/users')
|
|
415
|
+
export class UserController {
|
|
416
|
+
@Get()
|
|
417
|
+
async getUsers() {
|
|
418
|
+
return [];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@Get('/:id')
|
|
422
|
+
async getUser(@Params() params: { id: string }) {
|
|
423
|
+
return { id: params.id };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@HttpController('/products')
|
|
428
|
+
export class ProductController {
|
|
429
|
+
@Get()
|
|
430
|
+
async getProducts() {
|
|
431
|
+
return [];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Result**:
|
|
437
|
+
|
|
438
|
+
- `GET /api/users`
|
|
439
|
+
- `GET /api/users/:id`
|
|
440
|
+
- `GET /api/products`
|
|
441
|
+
|
|
442
|
+
#### Combining with Versioning
|
|
443
|
+
|
|
444
|
+
You can use `routePrefix` together with `prefixVersioning` for a well-organized API structure:
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
@Module({
|
|
448
|
+
imports: [
|
|
449
|
+
FastifyModule.forRoot({
|
|
450
|
+
controllers: [UserV1Controller, UserV2Controller],
|
|
451
|
+
routePrefix: 'api',
|
|
452
|
+
prefixVersioning: true,
|
|
453
|
+
port: 3000,
|
|
454
|
+
}),
|
|
455
|
+
],
|
|
456
|
+
})
|
|
457
|
+
export class AppModule {}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**Result**:
|
|
461
|
+
|
|
462
|
+
- `GET /api/v1/users`
|
|
463
|
+
- `GET /api/v2/users`
|
|
464
|
+
|
|
465
|
+
The order is always: `/{routePrefix}/{version}/{controller-path}/{route-path}`
|
|
466
|
+
|
|
467
|
+
#### Multi-Service Example
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
// User Service Module
|
|
471
|
+
@Module({
|
|
472
|
+
imports: [
|
|
473
|
+
FastifyModule.forRoot({
|
|
474
|
+
controllers: [UserController, AuthController],
|
|
475
|
+
routePrefix: 'user-service',
|
|
476
|
+
port: 3001,
|
|
477
|
+
}),
|
|
478
|
+
],
|
|
479
|
+
})
|
|
480
|
+
export class UserServiceModule {}
|
|
481
|
+
|
|
482
|
+
// Payment Service Module
|
|
483
|
+
@Module({
|
|
484
|
+
imports: [
|
|
485
|
+
FastifyModule.forRoot({
|
|
486
|
+
controllers: [PaymentController, InvoiceController],
|
|
487
|
+
routePrefix: 'payment-service',
|
|
488
|
+
port: 3002,
|
|
489
|
+
}),
|
|
490
|
+
],
|
|
491
|
+
})
|
|
492
|
+
export class PaymentServiceModule {}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Result**:
|
|
496
|
+
|
|
497
|
+
- User Service: `POST /user-service/auth/login`, `GET /user-service/users`
|
|
498
|
+
- Payment Service: `POST /payment-service/payments`, `GET /payment-service/invoices`
|
|
499
|
+
|
|
500
|
+
#### Dynamic Route Prefix
|
|
501
|
+
|
|
502
|
+
You can also set the prefix dynamically using `forRootAsync`:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
@Module({
|
|
506
|
+
imports: [
|
|
507
|
+
FastifyModule.forRootAsync({
|
|
508
|
+
useFactory: (configService: ConfigService) => ({
|
|
509
|
+
controllers: [UserController],
|
|
510
|
+
routePrefix: configService.get('API_PREFIX'), // e.g., 'api/v1'
|
|
511
|
+
port: configService.get('PORT'),
|
|
512
|
+
}),
|
|
513
|
+
inject: [ConfigService],
|
|
514
|
+
}),
|
|
515
|
+
],
|
|
516
|
+
})
|
|
517
|
+
export class AppModule {}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
#### Best Practices
|
|
521
|
+
|
|
522
|
+
1. **Use Consistent Prefixes**: Keep your route prefix consistent across your application
|
|
523
|
+
2. **Avoid Deep Nesting**: Keep prefixes shallow for better readability (prefer `/api` over `/api/v1/public`)
|
|
524
|
+
3. **Combine with Versioning**: Use `routePrefix` for namespace and `prefixVersioning` for versions
|
|
525
|
+
4. **Document Your Routes**: Clearly document the prefix structure for API consumers
|
|
526
|
+
5. **Environment-based Prefixes**: Consider different prefixes for different environments if needed
|
|
527
|
+
|
|
528
|
+
#### Without Route Prefix
|
|
529
|
+
|
|
530
|
+
If you don't need a global prefix, simply omit the `routePrefix` option:
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
FastifyModule.forRoot({
|
|
534
|
+
controllers: [UserController],
|
|
535
|
+
// No routePrefix specified
|
|
536
|
+
port: 3000,
|
|
537
|
+
});
|
|
538
|
+
// Routes will be: GET /users (no prefix)
|
|
539
|
+
```
|
|
540
|
+
|
|
387
541
|
### Error Handling
|
|
388
542
|
|
|
389
543
|
```typescript
|
|
@@ -537,6 +691,9 @@ The module provides two configuration methods:
|
|
|
537
691
|
// Security
|
|
538
692
|
maskSensitiveData: true,
|
|
539
693
|
|
|
694
|
+
// Global route prefix
|
|
695
|
+
routePrefix: 'api',
|
|
696
|
+
|
|
540
697
|
// API Versioning
|
|
541
698
|
prefixVersioning: true,
|
|
542
699
|
|
|
@@ -693,6 +850,55 @@ export class AppModule {}
|
|
|
693
850
|
- **Database**: Add database connection decorators
|
|
694
851
|
- **Caching**: Configure caching plugins
|
|
695
852
|
|
|
853
|
+
### Customizing AJV Validation
|
|
854
|
+
|
|
855
|
+
The module uses [AJV](https://ajv.js.org/) for JSON Schema validation. You can customize the AJV instance by providing custom options or plugins through the `ajv` configuration:
|
|
856
|
+
|
|
857
|
+
```typescript
|
|
858
|
+
import { Module } from '@shadow-library/app';
|
|
859
|
+
import { FastifyModule } from '@shadow-library/fastify';
|
|
860
|
+
import ajvFormats from 'ajv-formats';
|
|
861
|
+
import ajvKeywords from 'ajv-keywords';
|
|
862
|
+
|
|
863
|
+
@Module({
|
|
864
|
+
imports: [
|
|
865
|
+
FastifyModule.forRoot({
|
|
866
|
+
controllers: [UserController],
|
|
867
|
+
ajv: {
|
|
868
|
+
// Custom AJV options
|
|
869
|
+
customOptions: {
|
|
870
|
+
removeAdditional: 'all',
|
|
871
|
+
coerceTypes: 'array',
|
|
872
|
+
useDefaults: true,
|
|
873
|
+
},
|
|
874
|
+
// AJV plugins - supports both formats:
|
|
875
|
+
// 1. Just the plugin function (uses empty options)
|
|
876
|
+
// 2. Tuple of [plugin, options]
|
|
877
|
+
plugins: [
|
|
878
|
+
ajvFormats, // Plugin without options
|
|
879
|
+
[ajvKeywords, { keywords: ['typeof', 'instanceof'] }], // Plugin with options
|
|
880
|
+
],
|
|
881
|
+
},
|
|
882
|
+
}),
|
|
883
|
+
],
|
|
884
|
+
})
|
|
885
|
+
export class AppModule {}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
#### AJV Configuration Options
|
|
889
|
+
|
|
890
|
+
| Option | Type | Description |
|
|
891
|
+
| --------------- | ------------------------------------------ | ----------------------------------------------------- |
|
|
892
|
+
| `customOptions` | `object` | Custom AJV options to merge with the default settings |
|
|
893
|
+
| `plugins` | `Array<Plugin \| [Plugin, PluginOptions]>` | Array of AJV plugins to register with both validators |
|
|
894
|
+
|
|
895
|
+
#### Common Use Cases for AJV Customization:
|
|
896
|
+
|
|
897
|
+
- **Format Validation**: Add `ajv-formats` for email, uri, date-time validation
|
|
898
|
+
- **Custom Keywords**: Use `ajv-keywords` for additional validation keywords
|
|
899
|
+
- **Strict Mode**: Configure strict schema validation settings
|
|
900
|
+
- **Type Coercion**: Customize how types are coerced during validation
|
|
901
|
+
|
|
696
902
|
## Middleware
|
|
697
903
|
|
|
698
904
|
Create custom middleware by implementing the `Middleware` decorator:
|
|
@@ -16,7 +16,5 @@ const constants_1 = require("../constants.js");
|
|
|
16
16
|
* Declaring the constants
|
|
17
17
|
*/
|
|
18
18
|
function HttpController(path = '') {
|
|
19
|
-
if (path.charAt(0) !== '/')
|
|
20
|
-
path = `/${path}`;
|
|
21
19
|
return target => (0, app_1.Controller)({ [constants_1.HTTP_CONTROLLER_TYPE]: 'router', path })(target);
|
|
22
20
|
}
|
|
@@ -55,6 +55,10 @@ export interface FastifyConfig extends FastifyServerOptions {
|
|
|
55
55
|
* @default false
|
|
56
56
|
*/
|
|
57
57
|
prefixVersioning?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The global route prefix for all routes in the Fastify instance
|
|
60
|
+
*/
|
|
61
|
+
routePrefix?: string;
|
|
58
62
|
}
|
|
59
63
|
export interface FastifyModuleOptions extends Partial<FastifyConfig> {
|
|
60
64
|
/**
|
|
@@ -67,6 +67,7 @@ export declare class FastifyRouter extends Router {
|
|
|
67
67
|
private readonly sensitiveTransformer;
|
|
68
68
|
constructor(config: FastifyConfig, instance: FastifyInstance, context: ContextService);
|
|
69
69
|
getInstance(): FastifyInstance;
|
|
70
|
+
private joinPaths;
|
|
70
71
|
private registerRawBody;
|
|
71
72
|
private maskField;
|
|
72
73
|
private getRequestLogger;
|
|
@@ -59,6 +59,14 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
59
59
|
getInstance() {
|
|
60
60
|
return this.instance;
|
|
61
61
|
}
|
|
62
|
+
joinPaths(...parts) {
|
|
63
|
+
const path = parts
|
|
64
|
+
.filter(p => typeof p === 'string')
|
|
65
|
+
.map(p => p.replace(/^\/+|\/+$/g, ''))
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join('/');
|
|
68
|
+
return `/${path}`;
|
|
69
|
+
}
|
|
62
70
|
registerRawBody() {
|
|
63
71
|
const opts = { parseAs: 'buffer' };
|
|
64
72
|
const parser = this.instance.getDefaultJsonParser('error', 'error');
|
|
@@ -117,16 +125,10 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
117
125
|
switch (controller.metadata[constants_1.HTTP_CONTROLLER_TYPE]) {
|
|
118
126
|
case 'router': {
|
|
119
127
|
const { instance, metadata, metatype } = controller;
|
|
120
|
-
const basePath = metadata.path ?? '/';
|
|
121
128
|
for (const route of controller.routes) {
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
if (this.config.prefixVersioning) {
|
|
126
|
-
const version = route.metadata.version ?? 1;
|
|
127
|
-
const connector = path.startsWith('/') ? '' : '/';
|
|
128
|
-
path = '/v' + version + connector + path;
|
|
129
|
-
}
|
|
129
|
+
const version = route.metadata.version ?? 1;
|
|
130
|
+
const versionPrefix = this.config.prefixVersioning ? `/v${version}` : '';
|
|
131
|
+
const path = this.joinPaths(this.config.routePrefix, versionPrefix, metadata.path, route.metadata.path);
|
|
130
132
|
const parsedController = { ...route, instance, metatype };
|
|
131
133
|
parsedController.metadata.path = path;
|
|
132
134
|
parsedControllers.routes.push(parsedController);
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { ValidationError } from '@shadow-library/common';
|
|
2
|
-
import { SchemaObject } from 'ajv';
|
|
2
|
+
import Ajv, { SchemaObject } from 'ajv';
|
|
3
3
|
import { FastifyInstance } from 'fastify';
|
|
4
4
|
import { FastifyRouteSchemaDef, FastifySchemaValidationError, FastifyValidationResult, SchemaErrorDataVar } from 'fastify/types/schema';
|
|
5
5
|
import { FastifyConfig, FastifyModuleOptions } from './fastify-module.interface.js';
|
|
6
|
+
/**
|
|
7
|
+
* Defining types
|
|
8
|
+
*/
|
|
9
|
+
export interface AjvValidators {
|
|
10
|
+
strictValidator: Ajv;
|
|
11
|
+
lenientValidator: Ajv;
|
|
12
|
+
}
|
|
6
13
|
export declare const notFoundHandler: () => never;
|
|
7
|
-
export declare function compileValidator(routeSchema: FastifyRouteSchemaDef<SchemaObject
|
|
14
|
+
export declare function compileValidator(routeSchema: FastifyRouteSchemaDef<SchemaObject>, validators: AjvValidators): FastifyValidationResult;
|
|
8
15
|
export declare function formatSchemaErrors(errors: FastifySchemaValidationError[], dataVar: SchemaErrorDataVar): ValidationError;
|
|
9
16
|
export declare function createFastifyInstance(config: FastifyConfig, fastifyFactory?: FastifyModuleOptions['fastifyFactory']): Promise<FastifyInstance>;
|
|
@@ -18,17 +18,13 @@ const fastify_1 = require("fastify");
|
|
|
18
18
|
* Importing user defined packages
|
|
19
19
|
*/
|
|
20
20
|
const server_error_1 = require("../server.error.js");
|
|
21
|
-
/**
|
|
22
|
-
* Defining types
|
|
23
|
-
*/
|
|
24
21
|
/**
|
|
25
22
|
* Declaring the constants
|
|
26
23
|
*/
|
|
27
24
|
const keywords = ['x-fastify'];
|
|
28
25
|
const allowedHttpParts = ['body', 'params', 'querystring'];
|
|
29
|
-
const strictValidator = new ajv_1.default({ allErrors: true, useDefaults: true, removeAdditional: true, strict: true, keywords });
|
|
30
|
-
const lenientValidator = new ajv_1.default({ allErrors: true, coerceTypes: true, useDefaults: true, removeAdditional: true, strict: true, keywords });
|
|
31
26
|
const notFoundError = new server_error_1.ServerError(server_error_1.ServerErrorCode.S002);
|
|
27
|
+
const defaultAjvOptions = { allErrors: true, useDefaults: true, removeAdditional: true, strict: true, keywords };
|
|
32
28
|
const notFoundHandler = () => (0, common_1.throwError)(notFoundError);
|
|
33
29
|
exports.notFoundHandler = notFoundHandler;
|
|
34
30
|
function compileSchema(ajv, schema) {
|
|
@@ -41,13 +37,13 @@ function compileSchema(ajv, schema) {
|
|
|
41
37
|
}
|
|
42
38
|
return ajv.getSchema(schema.$id);
|
|
43
39
|
}
|
|
44
|
-
function compileValidator(routeSchema) {
|
|
40
|
+
function compileValidator(routeSchema, validators) {
|
|
45
41
|
(0, node_assert_1.default)(allowedHttpParts.includes(routeSchema.httpPart), `Invalid httpPart: ${routeSchema.httpPart}`);
|
|
46
42
|
if (routeSchema.httpPart === 'body')
|
|
47
|
-
return compileSchema(strictValidator, routeSchema.schema);
|
|
43
|
+
return compileSchema(validators.strictValidator, routeSchema.schema);
|
|
48
44
|
if (routeSchema.httpPart === 'params')
|
|
49
|
-
return compileSchema(lenientValidator, routeSchema.schema);
|
|
50
|
-
const validate = compileSchema(lenientValidator, routeSchema.schema);
|
|
45
|
+
return compileSchema(validators.lenientValidator, routeSchema.schema);
|
|
46
|
+
const validate = compileSchema(validators.lenientValidator, routeSchema.schema);
|
|
51
47
|
return (data) => {
|
|
52
48
|
validate(data);
|
|
53
49
|
for (const error of validate.errors ?? []) {
|
|
@@ -78,10 +74,19 @@ function formatSchemaErrors(errors, dataVar) {
|
|
|
78
74
|
async function createFastifyInstance(config, fastifyFactory) {
|
|
79
75
|
const options = common_1.utils.object.omitKeys(config, ['port', 'host', 'errorHandler', 'responseSchema']);
|
|
80
76
|
const { errorHandler } = config;
|
|
77
|
+
const strictValidator = new ajv_1.default({ ...defaultAjvOptions, ...config.ajv?.customOptions });
|
|
78
|
+
const lenientValidator = new ajv_1.default({ ...defaultAjvOptions, coerceTypes: true, ...config.ajv?.customOptions });
|
|
79
|
+
for (let plugin of config.ajv?.plugins ?? []) {
|
|
80
|
+
if (typeof plugin === 'function')
|
|
81
|
+
plugin = [plugin, {}];
|
|
82
|
+
const [ajvPlugin, options] = plugin;
|
|
83
|
+
ajvPlugin(strictValidator, options);
|
|
84
|
+
ajvPlugin(lenientValidator, options);
|
|
85
|
+
}
|
|
81
86
|
const instance = (0, fastify_1.fastify)(options);
|
|
82
87
|
instance.setSchemaErrorFormatter(formatSchemaErrors);
|
|
83
88
|
instance.setNotFoundHandler(exports.notFoundHandler);
|
|
84
89
|
instance.setErrorHandler(errorHandler.handle.bind(errorHandler));
|
|
85
|
-
instance.setValidatorCompiler(compileValidator);
|
|
90
|
+
instance.setValidatorCompiler(routeSchema => compileValidator(routeSchema, { strictValidator, lenientValidator }));
|
|
86
91
|
return fastifyFactory ? await fastifyFactory(instance) : instance;
|
|
87
92
|
}
|
|
@@ -13,7 +13,5 @@ import { HTTP_CONTROLLER_TYPE } from '../constants.js';
|
|
|
13
13
|
* Declaring the constants
|
|
14
14
|
*/
|
|
15
15
|
export function HttpController(path = '') {
|
|
16
|
-
if (path.charAt(0) !== '/')
|
|
17
|
-
path = `/${path}`;
|
|
18
16
|
return target => Controller({ [HTTP_CONTROLLER_TYPE]: 'router', path })(target);
|
|
19
17
|
}
|
|
@@ -55,6 +55,10 @@ export interface FastifyConfig extends FastifyServerOptions {
|
|
|
55
55
|
* @default false
|
|
56
56
|
*/
|
|
57
57
|
prefixVersioning?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The global route prefix for all routes in the Fastify instance
|
|
60
|
+
*/
|
|
61
|
+
routePrefix?: string;
|
|
58
62
|
}
|
|
59
63
|
export interface FastifyModuleOptions extends Partial<FastifyConfig> {
|
|
60
64
|
/**
|
|
@@ -67,6 +67,7 @@ export declare class FastifyRouter extends Router {
|
|
|
67
67
|
private readonly sensitiveTransformer;
|
|
68
68
|
constructor(config: FastifyConfig, instance: FastifyInstance, context: ContextService);
|
|
69
69
|
getInstance(): FastifyInstance;
|
|
70
|
+
private joinPaths;
|
|
70
71
|
private registerRawBody;
|
|
71
72
|
private maskField;
|
|
72
73
|
private getRequestLogger;
|
|
@@ -53,6 +53,14 @@ let FastifyRouter = class FastifyRouter extends Router {
|
|
|
53
53
|
getInstance() {
|
|
54
54
|
return this.instance;
|
|
55
55
|
}
|
|
56
|
+
joinPaths(...parts) {
|
|
57
|
+
const path = parts
|
|
58
|
+
.filter(p => typeof p === 'string')
|
|
59
|
+
.map(p => p.replace(/^\/+|\/+$/g, ''))
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join('/');
|
|
62
|
+
return `/${path}`;
|
|
63
|
+
}
|
|
56
64
|
registerRawBody() {
|
|
57
65
|
const opts = { parseAs: 'buffer' };
|
|
58
66
|
const parser = this.instance.getDefaultJsonParser('error', 'error');
|
|
@@ -111,16 +119,10 @@ let FastifyRouter = class FastifyRouter extends Router {
|
|
|
111
119
|
switch (controller.metadata[HTTP_CONTROLLER_TYPE]) {
|
|
112
120
|
case 'router': {
|
|
113
121
|
const { instance, metadata, metatype } = controller;
|
|
114
|
-
const basePath = metadata.path ?? '/';
|
|
115
122
|
for (const route of controller.routes) {
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
if (this.config.prefixVersioning) {
|
|
120
|
-
const version = route.metadata.version ?? 1;
|
|
121
|
-
const connector = path.startsWith('/') ? '' : '/';
|
|
122
|
-
path = '/v' + version + connector + path;
|
|
123
|
-
}
|
|
123
|
+
const version = route.metadata.version ?? 1;
|
|
124
|
+
const versionPrefix = this.config.prefixVersioning ? `/v${version}` : '';
|
|
125
|
+
const path = this.joinPaths(this.config.routePrefix, versionPrefix, metadata.path, route.metadata.path);
|
|
124
126
|
const parsedController = { ...route, instance, metatype };
|
|
125
127
|
parsedController.metadata.path = path;
|
|
126
128
|
parsedControllers.routes.push(parsedController);
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { ValidationError } from '@shadow-library/common';
|
|
2
|
-
import { SchemaObject } from 'ajv';
|
|
2
|
+
import Ajv, { SchemaObject } from 'ajv';
|
|
3
3
|
import { FastifyInstance } from 'fastify';
|
|
4
4
|
import { FastifyRouteSchemaDef, FastifySchemaValidationError, FastifyValidationResult, SchemaErrorDataVar } from 'fastify/types/schema';
|
|
5
5
|
import { FastifyConfig, FastifyModuleOptions } from './fastify-module.interface.js';
|
|
6
|
+
/**
|
|
7
|
+
* Defining types
|
|
8
|
+
*/
|
|
9
|
+
export interface AjvValidators {
|
|
10
|
+
strictValidator: Ajv;
|
|
11
|
+
lenientValidator: Ajv;
|
|
12
|
+
}
|
|
6
13
|
export declare const notFoundHandler: () => never;
|
|
7
|
-
export declare function compileValidator(routeSchema: FastifyRouteSchemaDef<SchemaObject
|
|
14
|
+
export declare function compileValidator(routeSchema: FastifyRouteSchemaDef<SchemaObject>, validators: AjvValidators): FastifyValidationResult;
|
|
8
15
|
export declare function formatSchemaErrors(errors: FastifySchemaValidationError[], dataVar: SchemaErrorDataVar): ValidationError;
|
|
9
16
|
export declare function createFastifyInstance(config: FastifyConfig, fastifyFactory?: FastifyModuleOptions['fastifyFactory']): Promise<FastifyInstance>;
|
|
@@ -9,17 +9,13 @@ import { fastify } from 'fastify';
|
|
|
9
9
|
* Importing user defined packages
|
|
10
10
|
*/
|
|
11
11
|
import { ServerError, ServerErrorCode } from '../server.error.js';
|
|
12
|
-
/**
|
|
13
|
-
* Defining types
|
|
14
|
-
*/
|
|
15
12
|
/**
|
|
16
13
|
* Declaring the constants
|
|
17
14
|
*/
|
|
18
15
|
const keywords = ['x-fastify'];
|
|
19
16
|
const allowedHttpParts = ['body', 'params', 'querystring'];
|
|
20
|
-
const strictValidator = new Ajv({ allErrors: true, useDefaults: true, removeAdditional: true, strict: true, keywords });
|
|
21
|
-
const lenientValidator = new Ajv({ allErrors: true, coerceTypes: true, useDefaults: true, removeAdditional: true, strict: true, keywords });
|
|
22
17
|
const notFoundError = new ServerError(ServerErrorCode.S002);
|
|
18
|
+
const defaultAjvOptions = { allErrors: true, useDefaults: true, removeAdditional: true, strict: true, keywords };
|
|
23
19
|
export const notFoundHandler = () => throwError(notFoundError);
|
|
24
20
|
function compileSchema(ajv, schema) {
|
|
25
21
|
if (!schema.$id)
|
|
@@ -31,13 +27,13 @@ function compileSchema(ajv, schema) {
|
|
|
31
27
|
}
|
|
32
28
|
return ajv.getSchema(schema.$id);
|
|
33
29
|
}
|
|
34
|
-
export function compileValidator(routeSchema) {
|
|
30
|
+
export function compileValidator(routeSchema, validators) {
|
|
35
31
|
assert(allowedHttpParts.includes(routeSchema.httpPart), `Invalid httpPart: ${routeSchema.httpPart}`);
|
|
36
32
|
if (routeSchema.httpPart === 'body')
|
|
37
|
-
return compileSchema(strictValidator, routeSchema.schema);
|
|
33
|
+
return compileSchema(validators.strictValidator, routeSchema.schema);
|
|
38
34
|
if (routeSchema.httpPart === 'params')
|
|
39
|
-
return compileSchema(lenientValidator, routeSchema.schema);
|
|
40
|
-
const validate = compileSchema(lenientValidator, routeSchema.schema);
|
|
35
|
+
return compileSchema(validators.lenientValidator, routeSchema.schema);
|
|
36
|
+
const validate = compileSchema(validators.lenientValidator, routeSchema.schema);
|
|
41
37
|
return (data) => {
|
|
42
38
|
validate(data);
|
|
43
39
|
for (const error of validate.errors ?? []) {
|
|
@@ -68,10 +64,19 @@ export function formatSchemaErrors(errors, dataVar) {
|
|
|
68
64
|
export async function createFastifyInstance(config, fastifyFactory) {
|
|
69
65
|
const options = utils.object.omitKeys(config, ['port', 'host', 'errorHandler', 'responseSchema']);
|
|
70
66
|
const { errorHandler } = config;
|
|
67
|
+
const strictValidator = new Ajv({ ...defaultAjvOptions, ...config.ajv?.customOptions });
|
|
68
|
+
const lenientValidator = new Ajv({ ...defaultAjvOptions, coerceTypes: true, ...config.ajv?.customOptions });
|
|
69
|
+
for (let plugin of config.ajv?.plugins ?? []) {
|
|
70
|
+
if (typeof plugin === 'function')
|
|
71
|
+
plugin = [plugin, {}];
|
|
72
|
+
const [ajvPlugin, options] = plugin;
|
|
73
|
+
ajvPlugin(strictValidator, options);
|
|
74
|
+
ajvPlugin(lenientValidator, options);
|
|
75
|
+
}
|
|
71
76
|
const instance = fastify(options);
|
|
72
77
|
instance.setSchemaErrorFormatter(formatSchemaErrors);
|
|
73
78
|
instance.setNotFoundHandler(notFoundHandler);
|
|
74
79
|
instance.setErrorHandler(errorHandler.handle.bind(errorHandler));
|
|
75
|
-
instance.setValidatorCompiler(compileValidator);
|
|
80
|
+
instance.setValidatorCompiler(routeSchema => compileValidator(routeSchema, { strictValidator, lenientValidator }));
|
|
76
81
|
return fastifyFactory ? await fastifyFactory(instance) : instance;
|
|
77
82
|
}
|
package/package.json
CHANGED