@shadow-library/fastify 0.0.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.
Files changed (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/cjs/classes/default-error-handler.d.ts +12 -0
  4. package/cjs/classes/default-error-handler.js +40 -0
  5. package/cjs/classes/index.d.ts +1 -0
  6. package/cjs/classes/index.js +17 -0
  7. package/cjs/constants.d.ts +5 -0
  8. package/cjs/constants.js +8 -0
  9. package/cjs/decorators/http-controller.decorator.d.ts +1 -0
  10. package/cjs/decorators/http-controller.decorator.js +8 -0
  11. package/cjs/decorators/http-input.decorator.d.ts +17 -0
  12. package/cjs/decorators/http-input.decorator.js +46 -0
  13. package/cjs/decorators/http-output.decorator.d.ts +11 -0
  14. package/cjs/decorators/http-output.decorator.js +20 -0
  15. package/cjs/decorators/http-route.decorator.d.ts +23 -0
  16. package/cjs/decorators/http-route.decorator.js +35 -0
  17. package/cjs/decorators/index.d.ts +5 -0
  18. package/cjs/decorators/index.js +21 -0
  19. package/cjs/decorators/middleware.decorator.d.ts +11 -0
  20. package/cjs/decorators/middleware.decorator.js +21 -0
  21. package/cjs/index.d.ts +7 -0
  22. package/cjs/index.js +23 -0
  23. package/cjs/interfaces/error-handler.interface.d.ts +4 -0
  24. package/cjs/interfaces/error-handler.interface.js +2 -0
  25. package/cjs/interfaces/index.d.ts +4 -0
  26. package/cjs/interfaces/index.js +20 -0
  27. package/cjs/interfaces/middleware.interface.d.ts +9 -0
  28. package/cjs/interfaces/middleware.interface.js +2 -0
  29. package/cjs/interfaces/route-handler.interface.d.ts +4 -0
  30. package/cjs/interfaces/route-handler.interface.js +2 -0
  31. package/cjs/interfaces/server-metadata.interface.d.ts +24 -0
  32. package/cjs/interfaces/server-metadata.interface.js +2 -0
  33. package/cjs/module/error-response.dto.d.ts +10 -0
  34. package/cjs/module/error-response.dto.js +55 -0
  35. package/cjs/module/fastify-module.interface.d.ts +19 -0
  36. package/cjs/module/fastify-module.interface.js +2 -0
  37. package/cjs/module/fastify-router.d.ts +36 -0
  38. package/cjs/module/fastify-router.js +182 -0
  39. package/cjs/module/fastify.module.d.ts +7 -0
  40. package/cjs/module/fastify.module.js +41 -0
  41. package/cjs/module/fastify.utils.d.ts +7 -0
  42. package/cjs/module/fastify.utils.js +33 -0
  43. package/cjs/module/index.d.ts +3 -0
  44. package/cjs/module/index.js +19 -0
  45. package/cjs/package.json +1 -0
  46. package/cjs/server.error.d.ts +16 -0
  47. package/cjs/server.error.js +38 -0
  48. package/cjs/services/context.service.d.ts +13 -0
  49. package/cjs/services/context.service.js +57 -0
  50. package/cjs/services/index.d.ts +1 -0
  51. package/cjs/services/index.js +17 -0
  52. package/esm/classes/default-error-handler.d.ts +12 -0
  53. package/esm/classes/default-error-handler.js +36 -0
  54. package/esm/classes/index.d.ts +1 -0
  55. package/esm/classes/index.js +1 -0
  56. package/esm/constants.d.ts +5 -0
  57. package/esm/constants.js +5 -0
  58. package/esm/decorators/http-controller.decorator.d.ts +1 -0
  59. package/esm/decorators/http-controller.decorator.js +5 -0
  60. package/esm/decorators/http-input.decorator.d.ts +17 -0
  61. package/esm/decorators/http-input.decorator.js +34 -0
  62. package/esm/decorators/http-output.decorator.d.ts +11 -0
  63. package/esm/decorators/http-output.decorator.js +12 -0
  64. package/esm/decorators/http-route.decorator.d.ts +23 -0
  65. package/esm/decorators/http-route.decorator.js +23 -0
  66. package/esm/decorators/index.d.ts +5 -0
  67. package/esm/decorators/index.js +5 -0
  68. package/esm/decorators/middleware.decorator.d.ts +11 -0
  69. package/esm/decorators/middleware.decorator.js +15 -0
  70. package/esm/index.d.ts +7 -0
  71. package/esm/index.js +7 -0
  72. package/esm/interfaces/error-handler.interface.d.ts +4 -0
  73. package/esm/interfaces/error-handler.interface.js +1 -0
  74. package/esm/interfaces/index.d.ts +4 -0
  75. package/esm/interfaces/index.js +4 -0
  76. package/esm/interfaces/middleware.interface.d.ts +9 -0
  77. package/esm/interfaces/middleware.interface.js +1 -0
  78. package/esm/interfaces/route-handler.interface.d.ts +4 -0
  79. package/esm/interfaces/route-handler.interface.js +1 -0
  80. package/esm/interfaces/server-metadata.interface.d.ts +24 -0
  81. package/esm/interfaces/server-metadata.interface.js +1 -0
  82. package/esm/module/error-response.dto.d.ts +10 -0
  83. package/esm/module/error-response.dto.js +52 -0
  84. package/esm/module/fastify-module.interface.d.ts +19 -0
  85. package/esm/module/fastify-module.interface.js +1 -0
  86. package/esm/module/fastify-router.d.ts +36 -0
  87. package/esm/module/fastify-router.js +176 -0
  88. package/esm/module/fastify.module.d.ts +7 -0
  89. package/esm/module/fastify.module.js +37 -0
  90. package/esm/module/fastify.utils.d.ts +7 -0
  91. package/esm/module/fastify.utils.js +27 -0
  92. package/esm/module/index.d.ts +3 -0
  93. package/esm/module/index.js +3 -0
  94. package/esm/server.error.d.ts +16 -0
  95. package/esm/server.error.js +33 -0
  96. package/esm/services/context.service.d.ts +13 -0
  97. package/esm/services/context.service.js +54 -0
  98. package/esm/services/index.d.ts +1 -0
  99. package/esm/services/index.js +1 -0
  100. package/package.json +44 -0
@@ -0,0 +1,23 @@
1
+ import { Route } from '@shadow-library/app';
2
+ export var HttpMethod;
3
+ (function (HttpMethod) {
4
+ HttpMethod["GET"] = "GET";
5
+ HttpMethod["POST"] = "POST";
6
+ HttpMethod["PUT"] = "PUT";
7
+ HttpMethod["DELETE"] = "DELETE";
8
+ HttpMethod["PATCH"] = "PATCH";
9
+ HttpMethod["OPTIONS"] = "OPTIONS";
10
+ HttpMethod["HEAD"] = "HEAD";
11
+ HttpMethod["ALL"] = "ALL";
12
+ })(HttpMethod || (HttpMethod = {}));
13
+ export function HttpRoute(options) {
14
+ return Route(options);
15
+ }
16
+ export const Get = (path) => HttpRoute({ method: HttpMethod.GET, path });
17
+ export const Post = (path) => HttpRoute({ method: HttpMethod.POST, path });
18
+ export const Put = (path) => HttpRoute({ method: HttpMethod.PUT, path });
19
+ export const Delete = (path) => HttpRoute({ method: HttpMethod.DELETE, path });
20
+ export const Patch = (path) => HttpRoute({ method: HttpMethod.PATCH, path });
21
+ export const Options = (path) => HttpRoute({ method: HttpMethod.OPTIONS, path });
22
+ export const Head = (path) => HttpRoute({ method: HttpMethod.HEAD, path });
23
+ export const All = (path) => HttpRoute({ method: HttpMethod.ALL, path });
@@ -0,0 +1,5 @@
1
+ export * from './http-controller.decorator.js';
2
+ export * from './http-input.decorator.js';
3
+ export * from './http-output.decorator.js';
4
+ export * from './http-route.decorator.js';
5
+ export * from './middleware.decorator.js';
@@ -0,0 +1,5 @@
1
+ export * from './http-controller.decorator.js';
2
+ export * from './http-input.decorator.js';
3
+ export * from './http-output.decorator.js';
4
+ export * from './http-route.decorator.js';
5
+ export * from './middleware.decorator.js';
@@ -0,0 +1,11 @@
1
+ import { HTTP_CONTROLLER_TYPE } from '../constants.js';
2
+ export type MiddlewareType = 'onRequest' | 'preParsing' | 'preValidation' | 'preHandler' | 'preSerialization' | 'onSend' | 'onResponse' | 'onError';
3
+ export interface MiddlewareOptions {
4
+ type: MiddlewareType;
5
+ weight: number;
6
+ }
7
+ export interface MiddlewareMetadata extends MiddlewareOptions {
8
+ [HTTP_CONTROLLER_TYPE]: 'middleware';
9
+ generates: boolean;
10
+ }
11
+ export declare function Middleware(options?: Partial<MiddlewareOptions>): ClassDecorator;
@@ -0,0 +1,15 @@
1
+ import assert from 'assert';
2
+ import { Controller } from '@shadow-library/app';
3
+ import { HTTP_CONTROLLER_TYPE } from '../constants.js';
4
+ const propertyKeys = ['generate', 'use'];
5
+ export function Middleware(options = {}) {
6
+ if (!options.type)
7
+ options.type = 'preHandler';
8
+ if (!options.weight)
9
+ options.weight = 0;
10
+ return target => {
11
+ const key = propertyKeys.find(key => key in target.prototype);
12
+ assert(key, `Cannot apply @Middleware to a class without a 'generate()' or 'use()' method`);
13
+ Controller({ ...options, [HTTP_CONTROLLER_TYPE]: 'middleware', generates: key === 'generate' })(target);
14
+ };
15
+ }
package/esm/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import 'reflect-metadata';
2
+ export * from './classes/index.js';
3
+ export * from './decorators/index.js';
4
+ export * from './interfaces/index.js';
5
+ export * from './module/index.js';
6
+ export * from './services/index.js';
7
+ export * from './server.error.js';
package/esm/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import 'reflect-metadata';
2
+ export * from './classes/index.js';
3
+ export * from './decorators/index.js';
4
+ export * from './interfaces/index.js';
5
+ export * from './module/index.js';
6
+ export * from './services/index.js';
7
+ export * from './server.error.js';
@@ -0,0 +1,4 @@
1
+ import { HttpRequest, HttpResponse } from './route-handler.interface.js';
2
+ export interface ErrorHandler {
3
+ handle(err: Error, req: HttpRequest, res: HttpResponse): any | Promise<any>;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export * from './error-handler.interface.js';
2
+ export * from './middleware.interface.js';
3
+ export * from './route-handler.interface.js';
4
+ export * from './server-metadata.interface.js';
@@ -0,0 +1,4 @@
1
+ export * from './error-handler.interface.js';
2
+ export * from './middleware.interface.js';
3
+ export * from './route-handler.interface.js';
4
+ export * from './server-metadata.interface.js';
@@ -0,0 +1,9 @@
1
+ import { RouteMetdata } from '@shadow-library/app';
2
+ import { HttpRequest, HttpResponse } from './route-handler.interface.js';
3
+ export type MiddlewareHandler = (request: HttpRequest, response: HttpResponse) => Promise<any>;
4
+ export interface MiddlewareGenerator {
5
+ generate(metadata: RouteMetdata): MiddlewareHandler | undefined | Promise<MiddlewareHandler | undefined>;
6
+ }
7
+ export interface HttpMiddleware {
8
+ use(request: HttpRequest, response: HttpResponse): Promise<any>;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { FastifyReply, FastifyRequest } from 'fastify';
2
+ export type HttpRequest = FastifyRequest;
3
+ export type HttpResponse = FastifyReply;
4
+ export type RouteHandler = (req: HttpRequest, res: HttpResponse) => unknown | Promise<unknown>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { RouteMetdata } from '@shadow-library/app';
2
+ import { JSONSchema } from '@shadow-library/class-schema';
3
+ import { RouteShorthandOptions } from 'fastify';
4
+ import { HTTP_CONTROLLER_TYPE } from '../constants.js';
5
+ import { HttpMethod, RouteInputSchemas } from '../decorators/index.js';
6
+ declare module '@shadow-library/app' {
7
+ interface RouteMetdata extends Omit<RouteShorthandOptions, 'config'> {
8
+ method?: HttpMethod;
9
+ path?: string;
10
+ schemas?: RouteInputSchemas & {
11
+ response?: Record<number | string, JSONSchema>;
12
+ };
13
+ rawBody?: boolean;
14
+ status?: number;
15
+ headers?: Record<string, string | (() => string)>;
16
+ redirect?: string;
17
+ render?: string | true;
18
+ }
19
+ interface ControllerMetdata {
20
+ [HTTP_CONTROLLER_TYPE]?: 'router' | 'middleware';
21
+ path?: string;
22
+ }
23
+ }
24
+ export type ServerMetadata = RouteMetdata;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ export declare class ErrorFieldDto {
2
+ field: string;
3
+ msg: string;
4
+ }
5
+ export declare class ErrorResponseDto {
6
+ code: string;
7
+ type: string;
8
+ message: string;
9
+ fields: ErrorFieldDto[];
10
+ }
@@ -0,0 +1,52 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Field, Schema } from '@shadow-library/class-schema';
11
+ let ErrorFieldDto = class ErrorFieldDto {
12
+ field;
13
+ msg;
14
+ };
15
+ __decorate([
16
+ Field(),
17
+ __metadata("design:type", String)
18
+ ], ErrorFieldDto.prototype, "field", void 0);
19
+ __decorate([
20
+ Field(),
21
+ __metadata("design:type", String)
22
+ ], ErrorFieldDto.prototype, "msg", void 0);
23
+ ErrorFieldDto = __decorate([
24
+ Schema()
25
+ ], ErrorFieldDto);
26
+ export { ErrorFieldDto };
27
+ let ErrorResponseDto = class ErrorResponseDto {
28
+ code;
29
+ type;
30
+ message;
31
+ fields;
32
+ };
33
+ __decorate([
34
+ Field(),
35
+ __metadata("design:type", String)
36
+ ], ErrorResponseDto.prototype, "code", void 0);
37
+ __decorate([
38
+ Field(),
39
+ __metadata("design:type", String)
40
+ ], ErrorResponseDto.prototype, "type", void 0);
41
+ __decorate([
42
+ Field(),
43
+ __metadata("design:type", String)
44
+ ], ErrorResponseDto.prototype, "message", void 0);
45
+ __decorate([
46
+ Field(() => [ErrorFieldDto], { required: false }),
47
+ __metadata("design:type", Array)
48
+ ], ErrorResponseDto.prototype, "fields", void 0);
49
+ ErrorResponseDto = __decorate([
50
+ Schema()
51
+ ], ErrorResponseDto);
52
+ export { ErrorResponseDto };
@@ -0,0 +1,19 @@
1
+ import { FactoryProvider, ModuleMetadata } from '@shadow-library/app';
2
+ import { JSONSchema } from '@shadow-library/class-schema';
3
+ import { FastifyInstance, FastifyServerOptions } from 'fastify';
4
+ import { Promisable } from 'type-fest';
5
+ import { ErrorHandler } from '../interfaces/index.js';
6
+ export interface FastifyConfig extends FastifyServerOptions {
7
+ host: string;
8
+ port: number;
9
+ errorHandler: ErrorHandler;
10
+ responseSchema?: Record<string | number, JSONSchema>;
11
+ }
12
+ export interface FastifyModuleOptions extends Partial<FastifyConfig> {
13
+ imports?: ModuleMetadata['imports'];
14
+ fastifyFactory?: (instance: FastifyInstance) => Promisable<FastifyInstance>;
15
+ }
16
+ export interface FastifyModuleAsyncOptions extends Pick<FastifyModuleOptions, 'imports' | 'fastifyFactory'> {
17
+ useFactory: (...args: any[]) => Promisable<FastifyConfig>;
18
+ inject?: FactoryProvider['inject'];
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { ControllerRouteMetadata, Router } from '@shadow-library/app';
2
+ import { type FastifyInstance } from 'fastify';
3
+ import { Chain as MockRequestChain, InjectOptions as MockRequestOptions, Response as MockResponse } from 'light-my-request';
4
+ import { JsonObject } from 'type-fest';
5
+ import { HttpRequest, HttpResponse, ServerMetadata } from '../interfaces/index.js';
6
+ import { type FastifyConfig } from './fastify-module.interface.js';
7
+ declare module 'fastify' {
8
+ interface FastifyRequest {
9
+ rawBody?: Buffer;
10
+ }
11
+ interface FastifyContextConfig {
12
+ metadata: ServerMetadata;
13
+ }
14
+ }
15
+ export interface RequestContext {
16
+ request: HttpRequest;
17
+ response: HttpResponse;
18
+ params: Record<string, string>;
19
+ query: Record<string, string>;
20
+ body: JsonObject;
21
+ }
22
+ export declare class FastifyRouter extends Router {
23
+ private readonly config;
24
+ private readonly instance;
25
+ private readonly logger;
26
+ constructor(config: FastifyConfig, instance: FastifyInstance);
27
+ getInstance(): FastifyInstance;
28
+ private registerRawBody;
29
+ private parseControllers;
30
+ private getStatusCode;
31
+ private generateRouteHandler;
32
+ register(controllers: ControllerRouteMetadata[]): Promise<void>;
33
+ start(): Promise<void>;
34
+ stop(): Promise<void>;
35
+ mockRequest(options?: MockRequestOptions): MockRequestChain | Promise<MockResponse>;
36
+ }
@@ -0,0 +1,176 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ var FastifyRouter_1;
14
+ import assert from 'assert';
15
+ import { Inject, Injectable, Router } from '@shadow-library/app';
16
+ import { InternalError, Logger, utils } from '@shadow-library/common';
17
+ import merge from 'deepmerge';
18
+ import { FASTIFY_CONFIG, FASTIFY_INSTANCE, HTTP_CONTROLLER_INPUTS, HTTP_CONTROLLER_TYPE } from '../constants.js';
19
+ import { HttpMethod } from '../decorators/index.js';
20
+ const httpMethods = Object.values(HttpMethod).filter(m => m !== HttpMethod.ALL);
21
+ let FastifyRouter = FastifyRouter_1 = class FastifyRouter extends Router {
22
+ config;
23
+ instance;
24
+ logger = Logger.getLogger(FastifyRouter_1.name);
25
+ constructor(config, instance) {
26
+ super();
27
+ this.config = config;
28
+ this.instance = instance;
29
+ }
30
+ getInstance() {
31
+ return this.instance;
32
+ }
33
+ registerRawBody() {
34
+ const opts = { parseAs: 'buffer' };
35
+ const parser = this.instance.getDefaultJsonParser('error', 'error');
36
+ this.instance.addContentTypeParser('application/json', opts, (req, body, done) => {
37
+ const { metadata } = req.routeOptions.config;
38
+ if (metadata.rawBody)
39
+ req.rawBody = body;
40
+ return parser(req, body.toString(), done);
41
+ });
42
+ }
43
+ parseControllers(controllers) {
44
+ const parsedControllers = { middlewares: [], routes: [] };
45
+ for (const controller of controllers) {
46
+ switch (controller.metadata[HTTP_CONTROLLER_TYPE]) {
47
+ case 'router': {
48
+ const { instance, metadata, metatype } = controller;
49
+ const basePath = metadata.path ?? '';
50
+ for (const route of controller.routes) {
51
+ const routePath = route.metadata.path ?? '';
52
+ const path = basePath + routePath;
53
+ const parsedController = { ...route, instance, metatype };
54
+ parsedController.metadata.path = path || '/';
55
+ parsedControllers.routes.push(parsedController);
56
+ }
57
+ break;
58
+ }
59
+ case 'middleware': {
60
+ const metadata = controller.metadata;
61
+ const { instance, metatype } = controller;
62
+ const method = metadata.generates ? 'generate' : 'use';
63
+ const handler = controller.instance[method].bind(instance);
64
+ parsedControllers.middlewares.push({ metadata, handler, paramtypes: [], instance, metatype, handlerName: method });
65
+ break;
66
+ }
67
+ default: {
68
+ throw new InternalError(`Unknown controller type: ${controller.metadata[HTTP_CONTROLLER_TYPE]}`);
69
+ }
70
+ }
71
+ }
72
+ parsedControllers.middlewares.sort((a, b) => b.metadata.weight - a.metadata.weight);
73
+ return parsedControllers;
74
+ }
75
+ getStatusCode(metadata) {
76
+ if (metadata.status)
77
+ return metadata.status;
78
+ const responseStatusCodes = Object.keys(metadata.schemas?.response ?? {}).map(n => parseInt(n));
79
+ const statusCodes = responseStatusCodes.filter(code => code >= 200 && code < 600);
80
+ if (statusCodes.length === 1)
81
+ return statusCodes[0];
82
+ return metadata.method === HttpMethod.POST ? 201 : 200;
83
+ }
84
+ generateRouteHandler(route) {
85
+ const metadata = route.metadata;
86
+ const statusCode = this.getStatusCode(metadata);
87
+ const argsOrder = Reflect.getMetadata(HTTP_CONTROLLER_INPUTS, route.instance, route.handlerName) ?? [];
88
+ return async (request, response) => {
89
+ const params = request.params;
90
+ const query = request.query;
91
+ const body = request.body;
92
+ const context = { request, response, params, query, body };
93
+ response.status(statusCode);
94
+ for (const [key, value] of Object.entries(metadata.headers ?? {})) {
95
+ response.header(key, typeof value === 'function' ? value() : value);
96
+ }
97
+ const args = argsOrder.map(arg => arg && context[arg]);
98
+ const data = await route.handler(...args);
99
+ if (metadata.redirect)
100
+ return response.status(metadata.status ?? 301).redirect(metadata.redirect);
101
+ if (metadata.render) {
102
+ let template = metadata.render;
103
+ let templateData = data;
104
+ if (template === true) {
105
+ template = data.template;
106
+ templateData = data.data;
107
+ }
108
+ return response.viewAsync(template, templateData);
109
+ }
110
+ if (!response.sent && data)
111
+ return response.send(data);
112
+ };
113
+ }
114
+ async register(controllers) {
115
+ const { middlewares, routes } = this.parseControllers(controllers);
116
+ const defaultResponseSchemas = this.config.responseSchema ?? {};
117
+ const hasRawBody = routes.some(r => r.metadata.rawBody);
118
+ if (hasRawBody)
119
+ this.registerRawBody();
120
+ for (const route of routes) {
121
+ const metadata = route.metadata;
122
+ assert(metadata.path, 'Route path is required');
123
+ assert(metadata.method, 'Route method is required');
124
+ this.logger.debug(`registering route ${metadata.method} ${metadata.path}`);
125
+ const fastifyRouteOptions = utils.object.omitKeys(metadata, ['path', 'method', 'schemas', 'rawBody', 'status', 'headers', 'redirect', 'render']);
126
+ const routeOptions = { ...fastifyRouteOptions, config: { metadata } };
127
+ routeOptions.url = metadata.path;
128
+ routeOptions.method = metadata.method === HttpMethod.ALL ? httpMethods : [metadata.method];
129
+ routeOptions.handler = this.generateRouteHandler(route);
130
+ for (const middleware of middlewares) {
131
+ const name = middleware.metatype.name;
132
+ const { generates, type } = middleware.metadata;
133
+ const handler = generates ? await middleware.handler(metadata) : middleware.handler.bind(middleware);
134
+ if (typeof handler === 'function') {
135
+ this.logger.debug(`applying '${type}' middleware '${name}'`);
136
+ const middlewareHandler = routeOptions[type];
137
+ if (middlewareHandler)
138
+ middlewareHandler.push(handler);
139
+ else
140
+ routeOptions[type] = [handler];
141
+ }
142
+ }
143
+ routeOptions.schema = {};
144
+ routeOptions.attachValidation = metadata.silentValidation ?? false;
145
+ routeOptions.schema.response = merge(metadata.schemas?.response ?? {}, defaultResponseSchemas);
146
+ if (metadata.schemas?.body)
147
+ routeOptions.schema.body = metadata.schemas.body;
148
+ if (metadata.schemas?.params)
149
+ routeOptions.schema.params = metadata.schemas.params;
150
+ if (metadata.schemas?.query)
151
+ routeOptions.schema.querystring = metadata.schemas.query;
152
+ this.logger.debug('route options', { options: routeOptions });
153
+ this.instance.route(routeOptions);
154
+ this.logger.info(`registered route ${metadata.method} ${routeOptions.url}`);
155
+ }
156
+ }
157
+ async start() {
158
+ const options = utils.object.pickKeys(this.config, ['port', 'host']);
159
+ const address = await this.instance.listen(options);
160
+ this.logger.info(`server started at ${address}`);
161
+ }
162
+ async stop() {
163
+ this.logger.info('stopping server');
164
+ await this.instance.close();
165
+ }
166
+ mockRequest(options) {
167
+ return options ? this.instance.inject(options) : this.instance.inject();
168
+ }
169
+ };
170
+ FastifyRouter = FastifyRouter_1 = __decorate([
171
+ Injectable(),
172
+ __param(0, Inject(FASTIFY_CONFIG)),
173
+ __param(1, Inject(FASTIFY_INSTANCE)),
174
+ __metadata("design:paramtypes", [Object, Object])
175
+ ], FastifyRouter);
176
+ export { FastifyRouter };
@@ -0,0 +1,7 @@
1
+ import { Class } from 'type-fest';
2
+ import { FastifyModuleAsyncOptions, FastifyModuleOptions } from './fastify-module.interface.js';
3
+ export declare class FastifyModule {
4
+ private static getDefaultConfig;
5
+ static forRoot(options: FastifyModuleOptions): Class<FastifyModule>;
6
+ static forRootAsync(options: FastifyModuleAsyncOptions): Class<FastifyModule>;
7
+ }
@@ -0,0 +1,37 @@
1
+ import { Module, Router } from '@shadow-library/app';
2
+ import { ClassSchema } from '@shadow-library/class-schema';
3
+ import { utils } from '@shadow-library/common';
4
+ import { v4 as uuid } from 'uuid';
5
+ import { DefaultErrorHandler } from '../classes/index.js';
6
+ import { FASTIFY_CONFIG, FASTIFY_INSTANCE } from '../constants.js';
7
+ import { ErrorResponseDto } from './error-response.dto.js';
8
+ import { FastifyRouter } from './fastify-router.js';
9
+ import { createFastifyInstance } from './fastify.utils.js';
10
+ export class FastifyModule {
11
+ static getDefaultConfig() {
12
+ const errorResponseSchema = ClassSchema.generate(ErrorResponseDto);
13
+ return {
14
+ host: 'localhost',
15
+ port: 8080,
16
+ responseSchema: { '4xx': errorResponseSchema, '5xx': errorResponseSchema },
17
+ errorHandler: new DefaultErrorHandler(),
18
+ ignoreTrailingSlash: true,
19
+ requestIdLogLabel: 'rid',
20
+ genReqId: () => uuid(),
21
+ ajv: { customOptions: { removeAdditional: true, useDefaults: true, allErrors: true } },
22
+ };
23
+ }
24
+ static forRoot(options) {
25
+ const config = Object.assign({}, this.getDefaultConfig(), utils.object.omitKeys(options, ['imports', 'fastifyFactory']));
26
+ return this.forRootAsync({ imports: options.imports, useFactory: () => config, fastifyFactory: options.fastifyFactory });
27
+ }
28
+ static forRootAsync(options) {
29
+ const imports = options.imports ?? [];
30
+ const providers = [{ token: Router, useClass: FastifyRouter }];
31
+ providers.push({ token: FASTIFY_CONFIG, useFactory: options.useFactory, inject: options.inject });
32
+ const fastifyFactory = (config) => createFastifyInstance(config, options.fastifyFactory);
33
+ providers.push({ token: FASTIFY_INSTANCE, useFactory: fastifyFactory, inject: [FASTIFY_CONFIG] });
34
+ Module({ imports, providers, exports: [Router] })(FastifyModule);
35
+ return FastifyModule;
36
+ }
37
+ }
@@ -0,0 +1,7 @@
1
+ import { ValidationError } from '@shadow-library/common';
2
+ import { FastifyInstance } from 'fastify';
3
+ import { FastifySchemaValidationError, SchemaErrorDataVar } from 'fastify/types/schema';
4
+ import { FastifyConfig, FastifyModuleOptions } from './fastify-module.interface.js';
5
+ export declare const notFoundHandler: () => never;
6
+ export declare function formatSchemaErrors(errors: FastifySchemaValidationError[], dataVar: SchemaErrorDataVar): ValidationError;
7
+ export declare function createFastifyInstance(config: FastifyConfig, fastifyFactory?: FastifyModuleOptions['fastifyFactory']): Promise<FastifyInstance>;
@@ -0,0 +1,27 @@
1
+ import { ValidationError, throwError, utils } from '@shadow-library/common';
2
+ import { fastify } from 'fastify';
3
+ import { ServerError, ServerErrorCode } from '../server.error.js';
4
+ const notFoundError = new ServerError(ServerErrorCode.S002);
5
+ export const notFoundHandler = () => throwError(notFoundError);
6
+ export function formatSchemaErrors(errors, dataVar) {
7
+ const validationError = new ValidationError();
8
+ for (const error of errors) {
9
+ let key = dataVar;
10
+ let message = error.message ?? 'Field validation failed';
11
+ if (error.instancePath)
12
+ key += error.instancePath.replaceAll('/', '.');
13
+ if (Array.isArray(error.params.allowedValues))
14
+ message += `: ${error.params.allowedValues.join(', ')}`;
15
+ validationError.addFieldError(key, message);
16
+ }
17
+ return validationError;
18
+ }
19
+ export async function createFastifyInstance(config, fastifyFactory) {
20
+ const options = utils.object.omitKeys(config, ['port', 'host', 'errorHandler', 'responseSchema']);
21
+ const { errorHandler } = config;
22
+ const instance = fastify(options);
23
+ instance.setSchemaErrorFormatter(formatSchemaErrors);
24
+ instance.setNotFoundHandler(notFoundHandler);
25
+ instance.setErrorHandler(errorHandler.handle.bind(errorHandler));
26
+ return fastifyFactory ? await fastifyFactory(instance) : instance;
27
+ }
@@ -0,0 +1,3 @@
1
+ export * from './fastify-module.interface.js';
2
+ export * from './fastify-router.js';
3
+ export * from './fastify.module.js';
@@ -0,0 +1,3 @@
1
+ export * from './fastify-module.interface.js';
2
+ export * from './fastify-router.js';
3
+ export * from './fastify.module.js';
@@ -0,0 +1,16 @@
1
+ import { AppError, ErrorCode, ErrorType } from '@shadow-library/common';
2
+ export declare class ServerError extends AppError<ServerErrorCode> {
3
+ getStatusCode(): number;
4
+ }
5
+ export declare class ServerErrorCode extends ErrorCode {
6
+ private readonly statusCode;
7
+ protected constructor(code: string, type: ErrorType, msg: string, statusCode?: number);
8
+ getStatusCode(): number;
9
+ static readonly S001: ServerErrorCode;
10
+ static readonly S002: ServerErrorCode;
11
+ static readonly S003: ServerErrorCode;
12
+ static readonly S004: ServerErrorCode;
13
+ static readonly S005: ServerErrorCode;
14
+ static readonly S006: ServerErrorCode;
15
+ static readonly S007: ServerErrorCode;
16
+ }
@@ -0,0 +1,33 @@
1
+ import { AppError, ErrorCode, ErrorType } from '@shadow-library/common';
2
+ const ERROR_STATUS_CODES = {
3
+ [ErrorType.CLIENT_ERROR]: 400,
4
+ [ErrorType.HTTP_ERROR]: 400,
5
+ [ErrorType.NOT_FOUND]: 404,
6
+ [ErrorType.SERVER_ERROR]: 500,
7
+ [ErrorType.UNAUTHENTICATED]: 401,
8
+ [ErrorType.UNAUTHORIZED]: 403,
9
+ [ErrorType.VALIDATION_ERROR]: 422,
10
+ [ErrorType.CONFLICT]: 409,
11
+ };
12
+ export class ServerError extends AppError {
13
+ getStatusCode() {
14
+ return this.error.getStatusCode();
15
+ }
16
+ }
17
+ export class ServerErrorCode extends ErrorCode {
18
+ statusCode;
19
+ constructor(code, type, msg, statusCode) {
20
+ super(code, type, msg);
21
+ this.statusCode = statusCode ?? ERROR_STATUS_CODES[type];
22
+ }
23
+ getStatusCode() {
24
+ return this.statusCode;
25
+ }
26
+ static S001 = new ServerErrorCode('S001', ErrorType.SERVER_ERROR, 'Unexpected Server Error');
27
+ static S002 = new ServerErrorCode('S002', ErrorType.NOT_FOUND, 'Not Found');
28
+ static S003 = new ServerErrorCode('S003', ErrorType.VALIDATION_ERROR, 'Invalid Input');
29
+ static S004 = new ServerErrorCode('S004', ErrorType.CLIENT_ERROR, 'Request body is too large', 413);
30
+ static S005 = new ServerErrorCode('S005', ErrorType.CLIENT_ERROR, 'Request body size did not match Content-Length');
31
+ static S006 = new ServerErrorCode('S006', ErrorType.CLIENT_ERROR, "Body cannot be empty when content-type is set to 'application/json'");
32
+ static S007 = new ServerErrorCode('S007', ErrorType.CLIENT_ERROR, 'Received media type is not supported', 415);
33
+ }
@@ -0,0 +1,13 @@
1
+ import { MiddlewareHandler } from '../interfaces/index.js';
2
+ type Key = string | symbol;
3
+ export declare class Context {
4
+ private readonly storage;
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;
9
+ getRequest(): Request;
10
+ getResponse(): Response;
11
+ getRID(): string;
12
+ }
13
+ export {};