@shadow-library/fastify 0.0.3 → 0.0.5

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.
@@ -2,22 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DefaultErrorHandler = void 0;
4
4
  const common_1 = require("@shadow-library/common");
5
+ const constants_1 = require("../constants.js");
5
6
  const server_error_1 = require("../server.error.js");
6
7
  const unexpectedError = new server_error_1.ServerError(server_error_1.ServerErrorCode.S001);
7
8
  const validationError = new server_error_1.ServerError(server_error_1.ServerErrorCode.S003);
8
9
  class DefaultErrorHandler {
9
- logger = common_1.Logger.getLogger(DefaultErrorHandler.name);
10
+ logger = common_1.Logger.getLogger(constants_1.NAMESPACE, 'DefaultErrorHandler');
10
11
  parseFastifyError(err) {
11
12
  if (err.statusCode === 500)
12
13
  return { statusCode: 500, error: unexpectedError.toObject() };
13
- return {
14
- statusCode: err.statusCode,
15
- error: {
16
- code: err.code,
17
- type: common_1.ErrorType.CLIENT_ERROR,
18
- message: err.message,
19
- },
20
- };
14
+ return { statusCode: err.statusCode, error: { code: err.code, type: common_1.ErrorType.CLIENT_ERROR, message: err.message } };
21
15
  }
22
16
  handle(err, _req, res) {
23
17
  this.logger.warn('Handling error', err);
@@ -1,3 +1,4 @@
1
+ export declare const NAMESPACE = "@shadow-library/fastify";
1
2
  export declare const PARAMTYPES_METADATA = "design:paramtypes";
2
3
  export declare const FASTIFY_CONFIG: unique symbol;
3
4
  export declare const FASTIFY_INSTANCE: unique symbol;
package/cjs/constants.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HTTP_CONTROLLER_INPUTS = exports.HTTP_CONTROLLER_TYPE = exports.FASTIFY_INSTANCE = exports.FASTIFY_CONFIG = exports.PARAMTYPES_METADATA = void 0;
3
+ exports.HTTP_CONTROLLER_INPUTS = exports.HTTP_CONTROLLER_TYPE = exports.FASTIFY_INSTANCE = exports.FASTIFY_CONFIG = exports.PARAMTYPES_METADATA = exports.NAMESPACE = void 0;
4
+ exports.NAMESPACE = '@shadow-library/fastify';
4
5
  exports.PARAMTYPES_METADATA = 'design:paramtypes';
5
6
  exports.FASTIFY_CONFIG = Symbol('fastify-config');
6
7
  exports.FASTIFY_INSTANCE = Symbol('fastify-instance');
@@ -3,6 +3,7 @@ import { type FastifyInstance } from 'fastify';
3
3
  import { Chain as MockRequestChain, InjectOptions as MockRequestOptions, Response as MockResponse } from 'light-my-request';
4
4
  import { JsonObject } from 'type-fest';
5
5
  import { HttpRequest, HttpResponse, ServerMetadata } from '../interfaces/index.js';
6
+ import { Context } from '../services/index.js';
6
7
  import { type FastifyConfig } from './fastify-module.interface.js';
7
8
  declare module 'fastify' {
8
9
  interface FastifyRequest {
@@ -19,13 +20,30 @@ export interface RequestContext {
19
20
  query: Record<string, string>;
20
21
  body: JsonObject;
21
22
  }
23
+ export interface RequestMetadata {
24
+ rid?: string;
25
+ srid?: string;
26
+ method?: string;
27
+ url?: string;
28
+ status?: number;
29
+ reqLen?: string;
30
+ reqIp?: string;
31
+ resLen?: string;
32
+ timeTaken?: string;
33
+ body?: any;
34
+ query?: object;
35
+ service?: string;
36
+ [key: string]: any;
37
+ }
22
38
  export declare class FastifyRouter extends Router {
23
39
  private readonly config;
24
40
  private readonly instance;
41
+ private readonly context;
25
42
  private readonly logger;
26
- constructor(config: FastifyConfig, instance: FastifyInstance);
43
+ constructor(config: FastifyConfig, instance: FastifyInstance, context: Context);
27
44
  getInstance(): FastifyInstance;
28
45
  private registerRawBody;
46
+ private getRequestLogger;
29
47
  private parseControllers;
30
48
  private getStatusCode;
31
49
  private generateRouteHandler;
@@ -14,7 +14,6 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
14
14
  var __importDefault = (this && this.__importDefault) || function (mod) {
15
15
  return (mod && mod.__esModule) ? mod : { "default": mod };
16
16
  };
17
- var FastifyRouter_1;
18
17
  Object.defineProperty(exports, "__esModule", { value: true });
19
18
  exports.FastifyRouter = void 0;
20
19
  const assert_1 = __importDefault(require("assert"));
@@ -23,15 +22,18 @@ const common_1 = require("@shadow-library/common");
23
22
  const deepmerge_1 = __importDefault(require("deepmerge"));
24
23
  const constants_1 = require("../constants.js");
25
24
  const decorators_1 = require("../decorators/index.js");
25
+ const services_1 = require("../services/index.js");
26
26
  const httpMethods = Object.values(decorators_1.HttpMethod).filter(m => m !== decorators_1.HttpMethod.ALL);
27
- let FastifyRouter = FastifyRouter_1 = class FastifyRouter extends app_1.Router {
27
+ let FastifyRouter = class FastifyRouter extends app_1.Router {
28
28
  config;
29
29
  instance;
30
- logger = common_1.Logger.getLogger(FastifyRouter_1.name);
31
- constructor(config, instance) {
30
+ context;
31
+ logger = common_1.Logger.getLogger(constants_1.NAMESPACE, 'FastifyRouter');
32
+ constructor(config, instance, context) {
32
33
  super();
33
34
  this.config = config;
34
35
  this.instance = instance;
36
+ this.context = context;
35
37
  }
36
38
  getInstance() {
37
39
  return this.instance;
@@ -46,6 +48,33 @@ let FastifyRouter = FastifyRouter_1 = class FastifyRouter extends app_1.Router {
46
48
  return parser(req, body.toString(), done);
47
49
  });
48
50
  }
51
+ getRequestLogger() {
52
+ return (req, res, done) => {
53
+ const startTime = process.hrtime();
54
+ res.raw.on('finish', () => {
55
+ const isLoggingDisabled = this.context.get('DISABLE_REQUEST_LOGGING') ?? false;
56
+ if (isLoggingDisabled)
57
+ return done();
58
+ const metadata = {};
59
+ metadata.rid = this.context.getRID();
60
+ metadata.url = req.url;
61
+ metadata.method = req.method;
62
+ metadata.status = res.statusCode;
63
+ metadata.service = req.headers['x-service'];
64
+ metadata.reqLen = req.headers['content-length'];
65
+ metadata.reqIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
66
+ metadata.resLen = res.getHeader('content-length');
67
+ const resTime = process.hrtime(startTime);
68
+ metadata.timeTaken = (resTime[0] * 1e3 + resTime[1] * 1e-6).toFixed(3);
69
+ if (req.query)
70
+ metadata.query = req.query;
71
+ if (req.body)
72
+ metadata.body = req.body;
73
+ this.logger.http('http', metadata);
74
+ });
75
+ return done();
76
+ };
77
+ }
49
78
  parseControllers(controllers) {
50
79
  const parsedControllers = { middlewares: [], routes: [] };
51
80
  for (const controller of controllers) {
@@ -123,6 +152,10 @@ let FastifyRouter = FastifyRouter_1 = class FastifyRouter extends app_1.Router {
123
152
  const hasRawBody = routes.some(r => r.metadata.rawBody);
124
153
  if (hasRawBody)
125
154
  this.registerRawBody();
155
+ this.logger.debug('Registering the global middlewares');
156
+ this.instance.addHook('onRequest', this.context.init());
157
+ this.instance.addHook('onRequest', this.getRequestLogger());
158
+ this.logger.info('Registered global middlewares');
126
159
  for (const route of routes) {
127
160
  const metadata = route.metadata;
128
161
  (0, assert_1.default)(metadata.path, 'Route path is required');
@@ -166,17 +199,18 @@ let FastifyRouter = FastifyRouter_1 = class FastifyRouter extends app_1.Router {
166
199
  this.logger.info(`server started at ${address}`);
167
200
  }
168
201
  async stop() {
169
- this.logger.info('stopping server');
202
+ this.logger.debug('stopping server');
170
203
  await this.instance.close();
204
+ this.logger.info('server stopped');
171
205
  }
172
206
  mockRequest(options) {
173
207
  return options ? this.instance.inject(options) : this.instance.inject();
174
208
  }
175
209
  };
176
210
  exports.FastifyRouter = FastifyRouter;
177
- exports.FastifyRouter = FastifyRouter = FastifyRouter_1 = __decorate([
211
+ exports.FastifyRouter = FastifyRouter = __decorate([
178
212
  (0, app_1.Injectable)(),
179
213
  __param(0, (0, app_1.Inject)(constants_1.FASTIFY_CONFIG)),
180
214
  __param(1, (0, app_1.Inject)(constants_1.FASTIFY_INSTANCE)),
181
- __metadata("design:paramtypes", [Object, Object])
215
+ __metadata("design:paramtypes", [Object, Object, services_1.Context])
182
216
  ], FastifyRouter);
@@ -22,7 +22,6 @@ class FastifyModule {
22
22
  ignoreDuplicateSlashes: true,
23
23
  requestIdLogLabel: 'rid',
24
24
  genReqId: () => (0, uuid_1.v4)(),
25
- ajv: { customOptions: { removeAdditional: true, useDefaults: true, allErrors: true } },
26
25
  };
27
26
  }
28
27
  static forRoot(options) {
@@ -1,7 +1,9 @@
1
1
  import { ValidationError } from '@shadow-library/common';
2
+ import { SchemaObject } from 'ajv';
2
3
  import { FastifyInstance } from 'fastify';
3
- import { FastifySchemaValidationError, SchemaErrorDataVar } from 'fastify/types/schema';
4
+ import { FastifyRouteSchemaDef, FastifySchemaValidationError, FastifyValidationResult, SchemaErrorDataVar } from 'fastify/types/schema';
4
5
  import { FastifyConfig, FastifyModuleOptions } from './fastify-module.interface.js';
5
6
  export declare const notFoundHandler: () => never;
7
+ export declare function compileValidator(routeSchema: FastifyRouteSchemaDef<SchemaObject>): FastifyValidationResult;
6
8
  export declare function formatSchemaErrors(errors: FastifySchemaValidationError[], dataVar: SchemaErrorDataVar): ValidationError;
7
9
  export declare function createFastifyInstance(config: FastifyConfig, fastifyFactory?: FastifyModuleOptions['fastifyFactory']): Promise<FastifyInstance>;
@@ -1,14 +1,41 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.notFoundHandler = void 0;
7
+ exports.compileValidator = compileValidator;
4
8
  exports.formatSchemaErrors = formatSchemaErrors;
5
9
  exports.createFastifyInstance = createFastifyInstance;
10
+ const assert_1 = __importDefault(require("assert"));
6
11
  const common_1 = require("@shadow-library/common");
12
+ const ajv_1 = __importDefault(require("ajv"));
7
13
  const fastify_1 = require("fastify");
8
14
  const server_error_1 = require("../server.error.js");
15
+ const allowedHttpParts = ['body', 'params', 'querystring'];
16
+ const strictValidator = new ajv_1.default({ allErrors: true, useDefaults: true, removeAdditional: true, strict: true });
17
+ const lenientValidator = new ajv_1.default({ allErrors: true, coerceTypes: true, useDefaults: true, removeAdditional: true, strict: true });
9
18
  const notFoundError = new server_error_1.ServerError(server_error_1.ServerErrorCode.S002);
10
19
  const notFoundHandler = () => (0, common_1.throwError)(notFoundError);
11
20
  exports.notFoundHandler = notFoundHandler;
21
+ function compileValidator(routeSchema) {
22
+ (0, assert_1.default)(allowedHttpParts.includes(routeSchema.httpPart), `Invalid httpPart: ${routeSchema.httpPart}`);
23
+ if (routeSchema.httpPart !== 'querystring')
24
+ return strictValidator.compile(routeSchema.schema);
25
+ const validate = lenientValidator.compile(routeSchema.schema);
26
+ return (data) => {
27
+ validate(data);
28
+ for (const error of validate.errors ?? []) {
29
+ const path = error.instancePath.substring(1);
30
+ const defaultValue = routeSchema.schema.properties?.[path]?.default;
31
+ if (defaultValue !== undefined)
32
+ data[path] = defaultValue;
33
+ else
34
+ delete data[path];
35
+ }
36
+ return { value: data };
37
+ };
38
+ }
12
39
  function formatSchemaErrors(errors, dataVar) {
13
40
  const validationError = new common_1.ValidationError();
14
41
  for (const error of errors) {
@@ -29,5 +56,6 @@ async function createFastifyInstance(config, fastifyFactory) {
29
56
  instance.setSchemaErrorFormatter(formatSchemaErrors);
30
57
  instance.setNotFoundHandler(exports.notFoundHandler);
31
58
  instance.setErrorHandler(errorHandler.handle.bind(errorHandler));
59
+ instance.setValidatorCompiler(compileValidator);
32
60
  return fastifyFactory ? await fastifyFactory(instance) : instance;
33
61
  }
@@ -3,9 +3,9 @@ type Key = string | symbol;
3
3
  export declare class Context {
4
4
  private readonly storage;
5
5
  init(): MiddlewareHandler;
6
- protected get<T>(key: Key, throwOnMissing: true): T;
7
- protected get<T>(key: Key, throwOnMissing?: false): T | null;
8
- protected set<T>(key: Key, value: T): this;
6
+ get<T>(key: Key, throwOnMissing: true): T;
7
+ get<T>(key: Key, throwOnMissing?: false): T | null;
8
+ set<T>(key: Key, value: T): this;
9
9
  getRequest(): Request;
10
10
  getResponse(): Response;
11
11
  getRID(): string;
@@ -1,20 +1,14 @@
1
1
  import { AppError, ErrorType, Logger, ValidationError } from '@shadow-library/common';
2
+ import { NAMESPACE } from '../constants.js';
2
3
  import { ServerError, ServerErrorCode } from '../server.error.js';
3
4
  const unexpectedError = new ServerError(ServerErrorCode.S001);
4
5
  const validationError = new ServerError(ServerErrorCode.S003);
5
6
  export class DefaultErrorHandler {
6
- logger = Logger.getLogger(DefaultErrorHandler.name);
7
+ logger = Logger.getLogger(NAMESPACE, 'DefaultErrorHandler');
7
8
  parseFastifyError(err) {
8
9
  if (err.statusCode === 500)
9
10
  return { statusCode: 500, error: unexpectedError.toObject() };
10
- return {
11
- statusCode: err.statusCode,
12
- error: {
13
- code: err.code,
14
- type: ErrorType.CLIENT_ERROR,
15
- message: err.message,
16
- },
17
- };
11
+ return { statusCode: err.statusCode, error: { code: err.code, type: ErrorType.CLIENT_ERROR, message: err.message } };
18
12
  }
19
13
  handle(err, _req, res) {
20
14
  this.logger.warn('Handling error', err);
@@ -1,3 +1,4 @@
1
+ export declare const NAMESPACE = "@shadow-library/fastify";
1
2
  export declare const PARAMTYPES_METADATA = "design:paramtypes";
2
3
  export declare const FASTIFY_CONFIG: unique symbol;
3
4
  export declare const FASTIFY_INSTANCE: unique symbol;
package/esm/constants.js CHANGED
@@ -1,3 +1,4 @@
1
+ export const NAMESPACE = '@shadow-library/fastify';
1
2
  export const PARAMTYPES_METADATA = 'design:paramtypes';
2
3
  export const FASTIFY_CONFIG = Symbol('fastify-config');
3
4
  export const FASTIFY_INSTANCE = Symbol('fastify-instance');
@@ -3,6 +3,7 @@ import { type FastifyInstance } from 'fastify';
3
3
  import { Chain as MockRequestChain, InjectOptions as MockRequestOptions, Response as MockResponse } from 'light-my-request';
4
4
  import { JsonObject } from 'type-fest';
5
5
  import { HttpRequest, HttpResponse, ServerMetadata } from '../interfaces/index.js';
6
+ import { Context } from '../services/index.js';
6
7
  import { type FastifyConfig } from './fastify-module.interface.js';
7
8
  declare module 'fastify' {
8
9
  interface FastifyRequest {
@@ -19,13 +20,30 @@ export interface RequestContext {
19
20
  query: Record<string, string>;
20
21
  body: JsonObject;
21
22
  }
23
+ export interface RequestMetadata {
24
+ rid?: string;
25
+ srid?: string;
26
+ method?: string;
27
+ url?: string;
28
+ status?: number;
29
+ reqLen?: string;
30
+ reqIp?: string;
31
+ resLen?: string;
32
+ timeTaken?: string;
33
+ body?: any;
34
+ query?: object;
35
+ service?: string;
36
+ [key: string]: any;
37
+ }
22
38
  export declare class FastifyRouter extends Router {
23
39
  private readonly config;
24
40
  private readonly instance;
41
+ private readonly context;
25
42
  private readonly logger;
26
- constructor(config: FastifyConfig, instance: FastifyInstance);
43
+ constructor(config: FastifyConfig, instance: FastifyInstance, context: Context);
27
44
  getInstance(): FastifyInstance;
28
45
  private registerRawBody;
46
+ private getRequestLogger;
29
47
  private parseControllers;
30
48
  private getStatusCode;
31
49
  private generateRouteHandler;
@@ -10,22 +10,24 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  return function (target, key) { decorator(target, key, paramIndex); }
12
12
  };
13
- var FastifyRouter_1;
14
13
  import assert from 'assert';
15
14
  import { Inject, Injectable, Router } from '@shadow-library/app';
16
15
  import { InternalError, Logger, utils } from '@shadow-library/common';
17
16
  import merge from 'deepmerge';
18
- import { FASTIFY_CONFIG, FASTIFY_INSTANCE, HTTP_CONTROLLER_INPUTS, HTTP_CONTROLLER_TYPE } from '../constants.js';
17
+ import { FASTIFY_CONFIG, FASTIFY_INSTANCE, HTTP_CONTROLLER_INPUTS, HTTP_CONTROLLER_TYPE, NAMESPACE } from '../constants.js';
19
18
  import { HttpMethod } from '../decorators/index.js';
19
+ import { Context } from '../services/index.js';
20
20
  const httpMethods = Object.values(HttpMethod).filter(m => m !== HttpMethod.ALL);
21
- let FastifyRouter = FastifyRouter_1 = class FastifyRouter extends Router {
21
+ let FastifyRouter = class FastifyRouter extends Router {
22
22
  config;
23
23
  instance;
24
- logger = Logger.getLogger(FastifyRouter_1.name);
25
- constructor(config, instance) {
24
+ context;
25
+ logger = Logger.getLogger(NAMESPACE, 'FastifyRouter');
26
+ constructor(config, instance, context) {
26
27
  super();
27
28
  this.config = config;
28
29
  this.instance = instance;
30
+ this.context = context;
29
31
  }
30
32
  getInstance() {
31
33
  return this.instance;
@@ -40,6 +42,33 @@ let FastifyRouter = FastifyRouter_1 = class FastifyRouter extends Router {
40
42
  return parser(req, body.toString(), done);
41
43
  });
42
44
  }
45
+ getRequestLogger() {
46
+ return (req, res, done) => {
47
+ const startTime = process.hrtime();
48
+ res.raw.on('finish', () => {
49
+ const isLoggingDisabled = this.context.get('DISABLE_REQUEST_LOGGING') ?? false;
50
+ if (isLoggingDisabled)
51
+ return done();
52
+ const metadata = {};
53
+ metadata.rid = this.context.getRID();
54
+ metadata.url = req.url;
55
+ metadata.method = req.method;
56
+ metadata.status = res.statusCode;
57
+ metadata.service = req.headers['x-service'];
58
+ metadata.reqLen = req.headers['content-length'];
59
+ metadata.reqIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
60
+ metadata.resLen = res.getHeader('content-length');
61
+ const resTime = process.hrtime(startTime);
62
+ metadata.timeTaken = (resTime[0] * 1e3 + resTime[1] * 1e-6).toFixed(3);
63
+ if (req.query)
64
+ metadata.query = req.query;
65
+ if (req.body)
66
+ metadata.body = req.body;
67
+ this.logger.http('http', metadata);
68
+ });
69
+ return done();
70
+ };
71
+ }
43
72
  parseControllers(controllers) {
44
73
  const parsedControllers = { middlewares: [], routes: [] };
45
74
  for (const controller of controllers) {
@@ -117,6 +146,10 @@ let FastifyRouter = FastifyRouter_1 = class FastifyRouter extends Router {
117
146
  const hasRawBody = routes.some(r => r.metadata.rawBody);
118
147
  if (hasRawBody)
119
148
  this.registerRawBody();
149
+ this.logger.debug('Registering the global middlewares');
150
+ this.instance.addHook('onRequest', this.context.init());
151
+ this.instance.addHook('onRequest', this.getRequestLogger());
152
+ this.logger.info('Registered global middlewares');
120
153
  for (const route of routes) {
121
154
  const metadata = route.metadata;
122
155
  assert(metadata.path, 'Route path is required');
@@ -160,17 +193,18 @@ let FastifyRouter = FastifyRouter_1 = class FastifyRouter extends Router {
160
193
  this.logger.info(`server started at ${address}`);
161
194
  }
162
195
  async stop() {
163
- this.logger.info('stopping server');
196
+ this.logger.debug('stopping server');
164
197
  await this.instance.close();
198
+ this.logger.info('server stopped');
165
199
  }
166
200
  mockRequest(options) {
167
201
  return options ? this.instance.inject(options) : this.instance.inject();
168
202
  }
169
203
  };
170
- FastifyRouter = FastifyRouter_1 = __decorate([
204
+ FastifyRouter = __decorate([
171
205
  Injectable(),
172
206
  __param(0, Inject(FASTIFY_CONFIG)),
173
207
  __param(1, Inject(FASTIFY_INSTANCE)),
174
- __metadata("design:paramtypes", [Object, Object])
208
+ __metadata("design:paramtypes", [Object, Object, Context])
175
209
  ], FastifyRouter);
176
210
  export { FastifyRouter };
@@ -19,7 +19,6 @@ export class FastifyModule {
19
19
  ignoreDuplicateSlashes: true,
20
20
  requestIdLogLabel: 'rid',
21
21
  genReqId: () => uuid(),
22
- ajv: { customOptions: { removeAdditional: true, useDefaults: true, allErrors: true } },
23
22
  };
24
23
  }
25
24
  static forRoot(options) {
@@ -1,7 +1,9 @@
1
1
  import { ValidationError } from '@shadow-library/common';
2
+ import { SchemaObject } from 'ajv';
2
3
  import { FastifyInstance } from 'fastify';
3
- import { FastifySchemaValidationError, SchemaErrorDataVar } from 'fastify/types/schema';
4
+ import { FastifyRouteSchemaDef, FastifySchemaValidationError, FastifyValidationResult, SchemaErrorDataVar } from 'fastify/types/schema';
4
5
  import { FastifyConfig, FastifyModuleOptions } from './fastify-module.interface.js';
5
6
  export declare const notFoundHandler: () => never;
7
+ export declare function compileValidator(routeSchema: FastifyRouteSchemaDef<SchemaObject>): FastifyValidationResult;
6
8
  export declare function formatSchemaErrors(errors: FastifySchemaValidationError[], dataVar: SchemaErrorDataVar): ValidationError;
7
9
  export declare function createFastifyInstance(config: FastifyConfig, fastifyFactory?: FastifyModuleOptions['fastifyFactory']): Promise<FastifyInstance>;
@@ -1,8 +1,31 @@
1
+ import assert from 'assert';
1
2
  import { ValidationError, throwError, utils } from '@shadow-library/common';
3
+ import Ajv from 'ajv';
2
4
  import { fastify } from 'fastify';
3
5
  import { ServerError, ServerErrorCode } from '../server.error.js';
6
+ const allowedHttpParts = ['body', 'params', 'querystring'];
7
+ const strictValidator = new Ajv({ allErrors: true, useDefaults: true, removeAdditional: true, strict: true });
8
+ const lenientValidator = new Ajv({ allErrors: true, coerceTypes: true, useDefaults: true, removeAdditional: true, strict: true });
4
9
  const notFoundError = new ServerError(ServerErrorCode.S002);
5
10
  export const notFoundHandler = () => throwError(notFoundError);
11
+ export function compileValidator(routeSchema) {
12
+ assert(allowedHttpParts.includes(routeSchema.httpPart), `Invalid httpPart: ${routeSchema.httpPart}`);
13
+ if (routeSchema.httpPart !== 'querystring')
14
+ return strictValidator.compile(routeSchema.schema);
15
+ const validate = lenientValidator.compile(routeSchema.schema);
16
+ return (data) => {
17
+ validate(data);
18
+ for (const error of validate.errors ?? []) {
19
+ const path = error.instancePath.substring(1);
20
+ const defaultValue = routeSchema.schema.properties?.[path]?.default;
21
+ if (defaultValue !== undefined)
22
+ data[path] = defaultValue;
23
+ else
24
+ delete data[path];
25
+ }
26
+ return { value: data };
27
+ };
28
+ }
6
29
  export function formatSchemaErrors(errors, dataVar) {
7
30
  const validationError = new ValidationError();
8
31
  for (const error of errors) {
@@ -23,5 +46,6 @@ export async function createFastifyInstance(config, fastifyFactory) {
23
46
  instance.setSchemaErrorFormatter(formatSchemaErrors);
24
47
  instance.setNotFoundHandler(notFoundHandler);
25
48
  instance.setErrorHandler(errorHandler.handle.bind(errorHandler));
49
+ instance.setValidatorCompiler(compileValidator);
26
50
  return fastifyFactory ? await fastifyFactory(instance) : instance;
27
51
  }
@@ -3,9 +3,9 @@ type Key = string | symbol;
3
3
  export declare class Context {
4
4
  private readonly storage;
5
5
  init(): MiddlewareHandler;
6
- protected get<T>(key: Key, throwOnMissing: true): T;
7
- protected get<T>(key: Key, throwOnMissing?: false): T | null;
8
- protected set<T>(key: Key, value: T): this;
6
+ get<T>(key: Key, throwOnMissing: true): T;
7
+ get<T>(key: Key, throwOnMissing?: false): T | null;
8
+ set<T>(key: Key, value: T): this;
9
9
  getRequest(): Request;
10
10
  getResponse(): Response;
11
11
  getRID(): string;
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@shadow-library/fastify",
3
3
  "type": "module",
4
- "version": "0.0.3",
4
+ "version": "0.0.5",
5
+ "sideEffects": false,
5
6
  "description": "A Fastify wrapper featuring decorator-based routing, middleware and error handling",
6
7
  "repository": {
7
8
  "type": "git",
@@ -14,17 +15,19 @@
14
15
  },
15
16
  "homepage": "https://github.com/shadow-library/fastify#readme",
16
17
  "dependencies": {
17
- "@shadow-library/class-schema": "^0.0.5",
18
+ "@fastify/middie": "^9.0.3",
19
+ "ajv": "^8.17.1",
18
20
  "deepmerge": "^4.3.1",
19
- "fastify": "^5.2.1",
21
+ "fastify": "^5.3.3",
20
22
  "jest": "^29.7.0",
21
- "type-fest": "^4.33.0",
22
- "uuid": "^11.0.5"
23
+ "type-fest": "^4.41.0",
24
+ "uuid": "^11.1.0"
23
25
  },
24
26
  "peerDependencies": {
25
27
  "@fastify/view": "^10.0.0",
26
- "@shadow-library/app": "^1.0.4",
27
- "@shadow-library/common": "^1.0.0",
28
+ "@shadow-library/app": "^1.0.7",
29
+ "@shadow-library/class-schema": "^0.0.5",
30
+ "@shadow-library/common": "^1.0.13",
28
31
  "reflect-metadata": "^0.2.2"
29
32
  },
30
33
  "peerDependenciesMeta": {