@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.
- package/LICENSE +21 -0
- package/README.md +24 -0
- package/cjs/classes/default-error-handler.d.ts +12 -0
- package/cjs/classes/default-error-handler.js +40 -0
- package/cjs/classes/index.d.ts +1 -0
- package/cjs/classes/index.js +17 -0
- package/cjs/constants.d.ts +5 -0
- package/cjs/constants.js +8 -0
- package/cjs/decorators/http-controller.decorator.d.ts +1 -0
- package/cjs/decorators/http-controller.decorator.js +8 -0
- package/cjs/decorators/http-input.decorator.d.ts +17 -0
- package/cjs/decorators/http-input.decorator.js +46 -0
- package/cjs/decorators/http-output.decorator.d.ts +11 -0
- package/cjs/decorators/http-output.decorator.js +20 -0
- package/cjs/decorators/http-route.decorator.d.ts +23 -0
- package/cjs/decorators/http-route.decorator.js +35 -0
- package/cjs/decorators/index.d.ts +5 -0
- package/cjs/decorators/index.js +21 -0
- package/cjs/decorators/middleware.decorator.d.ts +11 -0
- package/cjs/decorators/middleware.decorator.js +21 -0
- package/cjs/index.d.ts +7 -0
- package/cjs/index.js +23 -0
- package/cjs/interfaces/error-handler.interface.d.ts +4 -0
- package/cjs/interfaces/error-handler.interface.js +2 -0
- package/cjs/interfaces/index.d.ts +4 -0
- package/cjs/interfaces/index.js +20 -0
- package/cjs/interfaces/middleware.interface.d.ts +9 -0
- package/cjs/interfaces/middleware.interface.js +2 -0
- package/cjs/interfaces/route-handler.interface.d.ts +4 -0
- package/cjs/interfaces/route-handler.interface.js +2 -0
- package/cjs/interfaces/server-metadata.interface.d.ts +24 -0
- package/cjs/interfaces/server-metadata.interface.js +2 -0
- package/cjs/module/error-response.dto.d.ts +10 -0
- package/cjs/module/error-response.dto.js +55 -0
- package/cjs/module/fastify-module.interface.d.ts +19 -0
- package/cjs/module/fastify-module.interface.js +2 -0
- package/cjs/module/fastify-router.d.ts +36 -0
- package/cjs/module/fastify-router.js +182 -0
- package/cjs/module/fastify.module.d.ts +7 -0
- package/cjs/module/fastify.module.js +41 -0
- package/cjs/module/fastify.utils.d.ts +7 -0
- package/cjs/module/fastify.utils.js +33 -0
- package/cjs/module/index.d.ts +3 -0
- package/cjs/module/index.js +19 -0
- package/cjs/package.json +1 -0
- package/cjs/server.error.d.ts +16 -0
- package/cjs/server.error.js +38 -0
- package/cjs/services/context.service.d.ts +13 -0
- package/cjs/services/context.service.js +57 -0
- package/cjs/services/index.d.ts +1 -0
- package/cjs/services/index.js +17 -0
- package/esm/classes/default-error-handler.d.ts +12 -0
- package/esm/classes/default-error-handler.js +36 -0
- package/esm/classes/index.d.ts +1 -0
- package/esm/classes/index.js +1 -0
- package/esm/constants.d.ts +5 -0
- package/esm/constants.js +5 -0
- package/esm/decorators/http-controller.decorator.d.ts +1 -0
- package/esm/decorators/http-controller.decorator.js +5 -0
- package/esm/decorators/http-input.decorator.d.ts +17 -0
- package/esm/decorators/http-input.decorator.js +34 -0
- package/esm/decorators/http-output.decorator.d.ts +11 -0
- package/esm/decorators/http-output.decorator.js +12 -0
- package/esm/decorators/http-route.decorator.d.ts +23 -0
- package/esm/decorators/http-route.decorator.js +23 -0
- package/esm/decorators/index.d.ts +5 -0
- package/esm/decorators/index.js +5 -0
- package/esm/decorators/middleware.decorator.d.ts +11 -0
- package/esm/decorators/middleware.decorator.js +15 -0
- package/esm/index.d.ts +7 -0
- package/esm/index.js +7 -0
- package/esm/interfaces/error-handler.interface.d.ts +4 -0
- package/esm/interfaces/error-handler.interface.js +1 -0
- package/esm/interfaces/index.d.ts +4 -0
- package/esm/interfaces/index.js +4 -0
- package/esm/interfaces/middleware.interface.d.ts +9 -0
- package/esm/interfaces/middleware.interface.js +1 -0
- package/esm/interfaces/route-handler.interface.d.ts +4 -0
- package/esm/interfaces/route-handler.interface.js +1 -0
- package/esm/interfaces/server-metadata.interface.d.ts +24 -0
- package/esm/interfaces/server-metadata.interface.js +1 -0
- package/esm/module/error-response.dto.d.ts +10 -0
- package/esm/module/error-response.dto.js +52 -0
- package/esm/module/fastify-module.interface.d.ts +19 -0
- package/esm/module/fastify-module.interface.js +1 -0
- package/esm/module/fastify-router.d.ts +36 -0
- package/esm/module/fastify-router.js +176 -0
- package/esm/module/fastify.module.d.ts +7 -0
- package/esm/module/fastify.module.js +37 -0
- package/esm/module/fastify.utils.d.ts +7 -0
- package/esm/module/fastify.utils.js +27 -0
- package/esm/module/index.d.ts +3 -0
- package/esm/module/index.js +3 -0
- package/esm/server.error.d.ts +16 -0
- package/esm/server.error.js +33 -0
- package/esm/services/context.service.d.ts +13 -0
- package/esm/services/context.service.js +54 -0
- package/esm/services/index.d.ts +1 -0
- package/esm/services/index.js +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
var FastifyRouter_1;
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.FastifyRouter = void 0;
|
|
20
|
+
const assert_1 = __importDefault(require("assert"));
|
|
21
|
+
const app_1 = require("@shadow-library/app");
|
|
22
|
+
const common_1 = require("@shadow-library/common");
|
|
23
|
+
const deepmerge_1 = __importDefault(require("deepmerge"));
|
|
24
|
+
const constants_1 = require("../constants.js");
|
|
25
|
+
const decorators_1 = require("../decorators/index.js");
|
|
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 {
|
|
28
|
+
config;
|
|
29
|
+
instance;
|
|
30
|
+
logger = common_1.Logger.getLogger(FastifyRouter_1.name);
|
|
31
|
+
constructor(config, instance) {
|
|
32
|
+
super();
|
|
33
|
+
this.config = config;
|
|
34
|
+
this.instance = instance;
|
|
35
|
+
}
|
|
36
|
+
getInstance() {
|
|
37
|
+
return this.instance;
|
|
38
|
+
}
|
|
39
|
+
registerRawBody() {
|
|
40
|
+
const opts = { parseAs: 'buffer' };
|
|
41
|
+
const parser = this.instance.getDefaultJsonParser('error', 'error');
|
|
42
|
+
this.instance.addContentTypeParser('application/json', opts, (req, body, done) => {
|
|
43
|
+
const { metadata } = req.routeOptions.config;
|
|
44
|
+
if (metadata.rawBody)
|
|
45
|
+
req.rawBody = body;
|
|
46
|
+
return parser(req, body.toString(), done);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
parseControllers(controllers) {
|
|
50
|
+
const parsedControllers = { middlewares: [], routes: [] };
|
|
51
|
+
for (const controller of controllers) {
|
|
52
|
+
switch (controller.metadata[constants_1.HTTP_CONTROLLER_TYPE]) {
|
|
53
|
+
case 'router': {
|
|
54
|
+
const { instance, metadata, metatype } = controller;
|
|
55
|
+
const basePath = metadata.path ?? '';
|
|
56
|
+
for (const route of controller.routes) {
|
|
57
|
+
const routePath = route.metadata.path ?? '';
|
|
58
|
+
const path = basePath + routePath;
|
|
59
|
+
const parsedController = { ...route, instance, metatype };
|
|
60
|
+
parsedController.metadata.path = path || '/';
|
|
61
|
+
parsedControllers.routes.push(parsedController);
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'middleware': {
|
|
66
|
+
const metadata = controller.metadata;
|
|
67
|
+
const { instance, metatype } = controller;
|
|
68
|
+
const method = metadata.generates ? 'generate' : 'use';
|
|
69
|
+
const handler = controller.instance[method].bind(instance);
|
|
70
|
+
parsedControllers.middlewares.push({ metadata, handler, paramtypes: [], instance, metatype, handlerName: method });
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
default: {
|
|
74
|
+
throw new common_1.InternalError(`Unknown controller type: ${controller.metadata[constants_1.HTTP_CONTROLLER_TYPE]}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
parsedControllers.middlewares.sort((a, b) => b.metadata.weight - a.metadata.weight);
|
|
79
|
+
return parsedControllers;
|
|
80
|
+
}
|
|
81
|
+
getStatusCode(metadata) {
|
|
82
|
+
if (metadata.status)
|
|
83
|
+
return metadata.status;
|
|
84
|
+
const responseStatusCodes = Object.keys(metadata.schemas?.response ?? {}).map(n => parseInt(n));
|
|
85
|
+
const statusCodes = responseStatusCodes.filter(code => code >= 200 && code < 600);
|
|
86
|
+
if (statusCodes.length === 1)
|
|
87
|
+
return statusCodes[0];
|
|
88
|
+
return metadata.method === decorators_1.HttpMethod.POST ? 201 : 200;
|
|
89
|
+
}
|
|
90
|
+
generateRouteHandler(route) {
|
|
91
|
+
const metadata = route.metadata;
|
|
92
|
+
const statusCode = this.getStatusCode(metadata);
|
|
93
|
+
const argsOrder = Reflect.getMetadata(constants_1.HTTP_CONTROLLER_INPUTS, route.instance, route.handlerName) ?? [];
|
|
94
|
+
return async (request, response) => {
|
|
95
|
+
const params = request.params;
|
|
96
|
+
const query = request.query;
|
|
97
|
+
const body = request.body;
|
|
98
|
+
const context = { request, response, params, query, body };
|
|
99
|
+
response.status(statusCode);
|
|
100
|
+
for (const [key, value] of Object.entries(metadata.headers ?? {})) {
|
|
101
|
+
response.header(key, typeof value === 'function' ? value() : value);
|
|
102
|
+
}
|
|
103
|
+
const args = argsOrder.map(arg => arg && context[arg]);
|
|
104
|
+
const data = await route.handler(...args);
|
|
105
|
+
if (metadata.redirect)
|
|
106
|
+
return response.status(metadata.status ?? 301).redirect(metadata.redirect);
|
|
107
|
+
if (metadata.render) {
|
|
108
|
+
let template = metadata.render;
|
|
109
|
+
let templateData = data;
|
|
110
|
+
if (template === true) {
|
|
111
|
+
template = data.template;
|
|
112
|
+
templateData = data.data;
|
|
113
|
+
}
|
|
114
|
+
return response.viewAsync(template, templateData);
|
|
115
|
+
}
|
|
116
|
+
if (!response.sent && data)
|
|
117
|
+
return response.send(data);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async register(controllers) {
|
|
121
|
+
const { middlewares, routes } = this.parseControllers(controllers);
|
|
122
|
+
const defaultResponseSchemas = this.config.responseSchema ?? {};
|
|
123
|
+
const hasRawBody = routes.some(r => r.metadata.rawBody);
|
|
124
|
+
if (hasRawBody)
|
|
125
|
+
this.registerRawBody();
|
|
126
|
+
for (const route of routes) {
|
|
127
|
+
const metadata = route.metadata;
|
|
128
|
+
(0, assert_1.default)(metadata.path, 'Route path is required');
|
|
129
|
+
(0, assert_1.default)(metadata.method, 'Route method is required');
|
|
130
|
+
this.logger.debug(`registering route ${metadata.method} ${metadata.path}`);
|
|
131
|
+
const fastifyRouteOptions = common_1.utils.object.omitKeys(metadata, ['path', 'method', 'schemas', 'rawBody', 'status', 'headers', 'redirect', 'render']);
|
|
132
|
+
const routeOptions = { ...fastifyRouteOptions, config: { metadata } };
|
|
133
|
+
routeOptions.url = metadata.path;
|
|
134
|
+
routeOptions.method = metadata.method === decorators_1.HttpMethod.ALL ? httpMethods : [metadata.method];
|
|
135
|
+
routeOptions.handler = this.generateRouteHandler(route);
|
|
136
|
+
for (const middleware of middlewares) {
|
|
137
|
+
const name = middleware.metatype.name;
|
|
138
|
+
const { generates, type } = middleware.metadata;
|
|
139
|
+
const handler = generates ? await middleware.handler(metadata) : middleware.handler.bind(middleware);
|
|
140
|
+
if (typeof handler === 'function') {
|
|
141
|
+
this.logger.debug(`applying '${type}' middleware '${name}'`);
|
|
142
|
+
const middlewareHandler = routeOptions[type];
|
|
143
|
+
if (middlewareHandler)
|
|
144
|
+
middlewareHandler.push(handler);
|
|
145
|
+
else
|
|
146
|
+
routeOptions[type] = [handler];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
routeOptions.schema = {};
|
|
150
|
+
routeOptions.attachValidation = metadata.silentValidation ?? false;
|
|
151
|
+
routeOptions.schema.response = (0, deepmerge_1.default)(metadata.schemas?.response ?? {}, defaultResponseSchemas);
|
|
152
|
+
if (metadata.schemas?.body)
|
|
153
|
+
routeOptions.schema.body = metadata.schemas.body;
|
|
154
|
+
if (metadata.schemas?.params)
|
|
155
|
+
routeOptions.schema.params = metadata.schemas.params;
|
|
156
|
+
if (metadata.schemas?.query)
|
|
157
|
+
routeOptions.schema.querystring = metadata.schemas.query;
|
|
158
|
+
this.logger.debug('route options', { options: routeOptions });
|
|
159
|
+
this.instance.route(routeOptions);
|
|
160
|
+
this.logger.info(`registered route ${metadata.method} ${routeOptions.url}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async start() {
|
|
164
|
+
const options = common_1.utils.object.pickKeys(this.config, ['port', 'host']);
|
|
165
|
+
const address = await this.instance.listen(options);
|
|
166
|
+
this.logger.info(`server started at ${address}`);
|
|
167
|
+
}
|
|
168
|
+
async stop() {
|
|
169
|
+
this.logger.info('stopping server');
|
|
170
|
+
await this.instance.close();
|
|
171
|
+
}
|
|
172
|
+
mockRequest(options) {
|
|
173
|
+
return options ? this.instance.inject(options) : this.instance.inject();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
exports.FastifyRouter = FastifyRouter;
|
|
177
|
+
exports.FastifyRouter = FastifyRouter = FastifyRouter_1 = __decorate([
|
|
178
|
+
(0, app_1.Injectable)(),
|
|
179
|
+
__param(0, (0, app_1.Inject)(constants_1.FASTIFY_CONFIG)),
|
|
180
|
+
__param(1, (0, app_1.Inject)(constants_1.FASTIFY_INSTANCE)),
|
|
181
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
182
|
+
], 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,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FastifyModule = void 0;
|
|
4
|
+
const app_1 = require("@shadow-library/app");
|
|
5
|
+
const class_schema_1 = require("@shadow-library/class-schema");
|
|
6
|
+
const common_1 = require("@shadow-library/common");
|
|
7
|
+
const uuid_1 = require("uuid");
|
|
8
|
+
const classes_1 = require("../classes/index.js");
|
|
9
|
+
const constants_1 = require("../constants.js");
|
|
10
|
+
const error_response_dto_1 = require("./error-response.dto.js");
|
|
11
|
+
const fastify_router_1 = require("./fastify-router.js");
|
|
12
|
+
const fastify_utils_1 = require("./fastify.utils.js");
|
|
13
|
+
class FastifyModule {
|
|
14
|
+
static getDefaultConfig() {
|
|
15
|
+
const errorResponseSchema = class_schema_1.ClassSchema.generate(error_response_dto_1.ErrorResponseDto);
|
|
16
|
+
return {
|
|
17
|
+
host: 'localhost',
|
|
18
|
+
port: 8080,
|
|
19
|
+
responseSchema: { '4xx': errorResponseSchema, '5xx': errorResponseSchema },
|
|
20
|
+
errorHandler: new classes_1.DefaultErrorHandler(),
|
|
21
|
+
ignoreTrailingSlash: true,
|
|
22
|
+
requestIdLogLabel: 'rid',
|
|
23
|
+
genReqId: () => (0, uuid_1.v4)(),
|
|
24
|
+
ajv: { customOptions: { removeAdditional: true, useDefaults: true, allErrors: true } },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
static forRoot(options) {
|
|
28
|
+
const config = Object.assign({}, this.getDefaultConfig(), common_1.utils.object.omitKeys(options, ['imports', 'fastifyFactory']));
|
|
29
|
+
return this.forRootAsync({ imports: options.imports, useFactory: () => config, fastifyFactory: options.fastifyFactory });
|
|
30
|
+
}
|
|
31
|
+
static forRootAsync(options) {
|
|
32
|
+
const imports = options.imports ?? [];
|
|
33
|
+
const providers = [{ token: app_1.Router, useClass: fastify_router_1.FastifyRouter }];
|
|
34
|
+
providers.push({ token: constants_1.FASTIFY_CONFIG, useFactory: options.useFactory, inject: options.inject });
|
|
35
|
+
const fastifyFactory = (config) => (0, fastify_utils_1.createFastifyInstance)(config, options.fastifyFactory);
|
|
36
|
+
providers.push({ token: constants_1.FASTIFY_INSTANCE, useFactory: fastifyFactory, inject: [constants_1.FASTIFY_CONFIG] });
|
|
37
|
+
(0, app_1.Module)({ imports, providers, exports: [app_1.Router] })(FastifyModule);
|
|
38
|
+
return FastifyModule;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.FastifyModule = FastifyModule;
|
|
@@ -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,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.notFoundHandler = void 0;
|
|
4
|
+
exports.formatSchemaErrors = formatSchemaErrors;
|
|
5
|
+
exports.createFastifyInstance = createFastifyInstance;
|
|
6
|
+
const common_1 = require("@shadow-library/common");
|
|
7
|
+
const fastify_1 = require("fastify");
|
|
8
|
+
const server_error_1 = require("../server.error.js");
|
|
9
|
+
const notFoundError = new server_error_1.ServerError(server_error_1.ServerErrorCode.S002);
|
|
10
|
+
const notFoundHandler = () => (0, common_1.throwError)(notFoundError);
|
|
11
|
+
exports.notFoundHandler = notFoundHandler;
|
|
12
|
+
function formatSchemaErrors(errors, dataVar) {
|
|
13
|
+
const validationError = new common_1.ValidationError();
|
|
14
|
+
for (const error of errors) {
|
|
15
|
+
let key = dataVar;
|
|
16
|
+
let message = error.message ?? 'Field validation failed';
|
|
17
|
+
if (error.instancePath)
|
|
18
|
+
key += error.instancePath.replaceAll('/', '.');
|
|
19
|
+
if (Array.isArray(error.params.allowedValues))
|
|
20
|
+
message += `: ${error.params.allowedValues.join(', ')}`;
|
|
21
|
+
validationError.addFieldError(key, message);
|
|
22
|
+
}
|
|
23
|
+
return validationError;
|
|
24
|
+
}
|
|
25
|
+
async function createFastifyInstance(config, fastifyFactory) {
|
|
26
|
+
const options = common_1.utils.object.omitKeys(config, ['port', 'host', 'errorHandler', 'responseSchema']);
|
|
27
|
+
const { errorHandler } = config;
|
|
28
|
+
const instance = (0, fastify_1.fastify)(options);
|
|
29
|
+
instance.setSchemaErrorFormatter(formatSchemaErrors);
|
|
30
|
+
instance.setNotFoundHandler(exports.notFoundHandler);
|
|
31
|
+
instance.setErrorHandler(errorHandler.handle.bind(errorHandler));
|
|
32
|
+
return fastifyFactory ? await fastifyFactory(instance) : instance;
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./fastify-module.interface.js"), exports);
|
|
18
|
+
__exportStar(require("./fastify-router.js"), exports);
|
|
19
|
+
__exportStar(require("./fastify.module.js"), exports);
|
package/cjs/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -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,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ServerErrorCode = exports.ServerError = void 0;
|
|
4
|
+
const common_1 = require("@shadow-library/common");
|
|
5
|
+
const ERROR_STATUS_CODES = {
|
|
6
|
+
[common_1.ErrorType.CLIENT_ERROR]: 400,
|
|
7
|
+
[common_1.ErrorType.HTTP_ERROR]: 400,
|
|
8
|
+
[common_1.ErrorType.NOT_FOUND]: 404,
|
|
9
|
+
[common_1.ErrorType.SERVER_ERROR]: 500,
|
|
10
|
+
[common_1.ErrorType.UNAUTHENTICATED]: 401,
|
|
11
|
+
[common_1.ErrorType.UNAUTHORIZED]: 403,
|
|
12
|
+
[common_1.ErrorType.VALIDATION_ERROR]: 422,
|
|
13
|
+
[common_1.ErrorType.CONFLICT]: 409,
|
|
14
|
+
};
|
|
15
|
+
class ServerError extends common_1.AppError {
|
|
16
|
+
getStatusCode() {
|
|
17
|
+
return this.error.getStatusCode();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.ServerError = ServerError;
|
|
21
|
+
class ServerErrorCode extends common_1.ErrorCode {
|
|
22
|
+
statusCode;
|
|
23
|
+
constructor(code, type, msg, statusCode) {
|
|
24
|
+
super(code, type, msg);
|
|
25
|
+
this.statusCode = statusCode ?? ERROR_STATUS_CODES[type];
|
|
26
|
+
}
|
|
27
|
+
getStatusCode() {
|
|
28
|
+
return this.statusCode;
|
|
29
|
+
}
|
|
30
|
+
static S001 = new ServerErrorCode('S001', common_1.ErrorType.SERVER_ERROR, 'Unexpected Server Error');
|
|
31
|
+
static S002 = new ServerErrorCode('S002', common_1.ErrorType.NOT_FOUND, 'Not Found');
|
|
32
|
+
static S003 = new ServerErrorCode('S003', common_1.ErrorType.VALIDATION_ERROR, 'Invalid Input');
|
|
33
|
+
static S004 = new ServerErrorCode('S004', common_1.ErrorType.CLIENT_ERROR, 'Request body is too large', 413);
|
|
34
|
+
static S005 = new ServerErrorCode('S005', common_1.ErrorType.CLIENT_ERROR, 'Request body size did not match Content-Length');
|
|
35
|
+
static S006 = new ServerErrorCode('S006', common_1.ErrorType.CLIENT_ERROR, "Body cannot be empty when content-type is set to 'application/json'");
|
|
36
|
+
static S007 = new ServerErrorCode('S007', common_1.ErrorType.CLIENT_ERROR, 'Received media type is not supported', 415);
|
|
37
|
+
}
|
|
38
|
+
exports.ServerErrorCode = ServerErrorCode;
|
|
@@ -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 {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.Context = void 0;
|
|
10
|
+
const async_hooks_1 = require("async_hooks");
|
|
11
|
+
const app_1 = require("@shadow-library/app");
|
|
12
|
+
const common_1 = require("@shadow-library/common");
|
|
13
|
+
const REQUEST = Symbol('request');
|
|
14
|
+
const RESPONSE = Symbol('response');
|
|
15
|
+
const RID = Symbol('rid');
|
|
16
|
+
let Context = class Context {
|
|
17
|
+
storage = new async_hooks_1.AsyncLocalStorage();
|
|
18
|
+
init() {
|
|
19
|
+
return async (req, res) => {
|
|
20
|
+
const store = new Map();
|
|
21
|
+
store.set(REQUEST, req);
|
|
22
|
+
store.set(RESPONSE, res);
|
|
23
|
+
store.set(RID, req.id);
|
|
24
|
+
this.storage.enterWith(store);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
get(key, throwOnMissing) {
|
|
28
|
+
const store = this.storage.getStore();
|
|
29
|
+
if (!store)
|
|
30
|
+
throw new common_1.InternalError('Context not yet initialized');
|
|
31
|
+
const value = store.get(key);
|
|
32
|
+
if (throwOnMissing && value === undefined) {
|
|
33
|
+
throw new common_1.InternalError(`Key '${key.toString()}' not found in the context`);
|
|
34
|
+
}
|
|
35
|
+
return value ?? null;
|
|
36
|
+
}
|
|
37
|
+
set(key, value) {
|
|
38
|
+
const store = this.storage.getStore();
|
|
39
|
+
if (!store)
|
|
40
|
+
throw new common_1.InternalError('Context not yet initialized');
|
|
41
|
+
store.set(key, value);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
getRequest() {
|
|
45
|
+
return this.get(REQUEST, true);
|
|
46
|
+
}
|
|
47
|
+
getResponse() {
|
|
48
|
+
return this.get(RESPONSE, true);
|
|
49
|
+
}
|
|
50
|
+
getRID() {
|
|
51
|
+
return this.get(RID, true);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
exports.Context = Context;
|
|
55
|
+
exports.Context = Context = __decorate([
|
|
56
|
+
(0, app_1.Injectable)()
|
|
57
|
+
], Context);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './context.service.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./context.service.js"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AppErrorObject } from '@shadow-library/common';
|
|
2
|
+
import { FastifyError } from 'fastify';
|
|
3
|
+
import { ErrorHandler, HttpRequest, HttpResponse } from '../interfaces/index.js';
|
|
4
|
+
export interface ParsedFastifyError {
|
|
5
|
+
statusCode: number;
|
|
6
|
+
error: AppErrorObject;
|
|
7
|
+
}
|
|
8
|
+
export declare class DefaultErrorHandler implements ErrorHandler {
|
|
9
|
+
private readonly logger;
|
|
10
|
+
protected parseFastifyError(err: FastifyError): ParsedFastifyError;
|
|
11
|
+
handle(err: Error, _req: HttpRequest, res: HttpResponse): HttpResponse;
|
|
12
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AppError, ErrorType, Logger, ValidationError } from '@shadow-library/common';
|
|
2
|
+
import { ServerError, ServerErrorCode } from '../server.error.js';
|
|
3
|
+
const unexpectedError = new ServerError(ServerErrorCode.S001);
|
|
4
|
+
const validationError = new ServerError(ServerErrorCode.S003);
|
|
5
|
+
export class DefaultErrorHandler {
|
|
6
|
+
logger = Logger.getLogger(DefaultErrorHandler.name);
|
|
7
|
+
parseFastifyError(err) {
|
|
8
|
+
if (err.statusCode === 500)
|
|
9
|
+
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
|
+
};
|
|
18
|
+
}
|
|
19
|
+
handle(err, _req, res) {
|
|
20
|
+
this.logger.warn('Handling error', err);
|
|
21
|
+
if (err.cause)
|
|
22
|
+
this.logger.warn('Caused by:', err.cause);
|
|
23
|
+
if (err instanceof ServerError)
|
|
24
|
+
return res.status(err.getStatusCode()).send(err.toObject());
|
|
25
|
+
else if (err instanceof ValidationError)
|
|
26
|
+
return res.status(400).send({ ...err.toObject(), code: validationError.getCode() });
|
|
27
|
+
else if (err instanceof AppError)
|
|
28
|
+
return res.status(500).send(err.toObject());
|
|
29
|
+
else if (err.name === 'FastifyError') {
|
|
30
|
+
const { statusCode, error } = this.parseFastifyError(err);
|
|
31
|
+
return res.status(statusCode).send(error);
|
|
32
|
+
}
|
|
33
|
+
this.logger.error('Unhandler error has occurred:', err);
|
|
34
|
+
return res.status(500).send(unexpectedError.toObject());
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './default-error-handler.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './default-error-handler.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const PARAMTYPES_METADATA = "design:paramtypes";
|
|
2
|
+
export declare const FASTIFY_CONFIG: unique symbol;
|
|
3
|
+
export declare const FASTIFY_INSTANCE: unique symbol;
|
|
4
|
+
export declare const HTTP_CONTROLLER_TYPE: unique symbol;
|
|
5
|
+
export declare const HTTP_CONTROLLER_INPUTS: unique symbol;
|
package/esm/constants.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const PARAMTYPES_METADATA = 'design:paramtypes';
|
|
2
|
+
export const FASTIFY_CONFIG = Symbol('fastify-config');
|
|
3
|
+
export const FASTIFY_INSTANCE = Symbol('fastify-instance');
|
|
4
|
+
export const HTTP_CONTROLLER_TYPE = Symbol('http-controller-type');
|
|
5
|
+
export const HTTP_CONTROLLER_INPUTS = Symbol('http-controller-inputs');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function HttpController(path?: string): ClassDecorator;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { JSONSchema } from '@shadow-library/class-schema';
|
|
2
|
+
export declare enum RouteInputType {
|
|
3
|
+
BODY = "body",
|
|
4
|
+
PARAMS = "params",
|
|
5
|
+
QUERY = "query",
|
|
6
|
+
REQUEST = "request",
|
|
7
|
+
RESPONSE = "response"
|
|
8
|
+
}
|
|
9
|
+
export type RouteInputSchemas = Partial<Record<'body' | 'params' | 'query', JSONSchema>>;
|
|
10
|
+
export declare function HttpInput(type: RouteInputType, schema?: JSONSchema): ParameterDecorator;
|
|
11
|
+
export declare const Body: (schema?: JSONSchema) => ParameterDecorator;
|
|
12
|
+
export declare const Params: (schema?: JSONSchema) => ParameterDecorator;
|
|
13
|
+
export declare const Query: (schema?: JSONSchema) => ParameterDecorator;
|
|
14
|
+
export declare const Request: () => ParameterDecorator;
|
|
15
|
+
export declare const Req: () => ParameterDecorator;
|
|
16
|
+
export declare const Response: () => ParameterDecorator;
|
|
17
|
+
export declare const Res: () => ParameterDecorator;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import { Route } from '@shadow-library/app';
|
|
3
|
+
import { ClassSchema } from '@shadow-library/class-schema';
|
|
4
|
+
import { HTTP_CONTROLLER_INPUTS, PARAMTYPES_METADATA } from '../constants.js';
|
|
5
|
+
export var RouteInputType;
|
|
6
|
+
(function (RouteInputType) {
|
|
7
|
+
RouteInputType["BODY"] = "body";
|
|
8
|
+
RouteInputType["PARAMS"] = "params";
|
|
9
|
+
RouteInputType["QUERY"] = "query";
|
|
10
|
+
RouteInputType["REQUEST"] = "request";
|
|
11
|
+
RouteInputType["RESPONSE"] = "response";
|
|
12
|
+
})(RouteInputType || (RouteInputType = {}));
|
|
13
|
+
export function HttpInput(type, schema) {
|
|
14
|
+
return (target, propertyKey, index) => {
|
|
15
|
+
assert(propertyKey, 'Cannot apply decorator to a constructor parameter');
|
|
16
|
+
const inputs = Reflect.getMetadata(HTTP_CONTROLLER_INPUTS, target, propertyKey) ?? [];
|
|
17
|
+
Reflect.defineMetadata(HTTP_CONTROLLER_INPUTS, inputs, target, propertyKey);
|
|
18
|
+
inputs[index] = type;
|
|
19
|
+
if (!schema) {
|
|
20
|
+
const paramTypes = Reflect.getMetadata(PARAMTYPES_METADATA, target, propertyKey);
|
|
21
|
+
schema = ClassSchema.generate(paramTypes[index]);
|
|
22
|
+
}
|
|
23
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey);
|
|
24
|
+
assert(descriptor, 'Cannot apply decorator to a non-method');
|
|
25
|
+
Route({ schemas: { [type]: schema } })(target, propertyKey, descriptor);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export const Body = (schema) => HttpInput(RouteInputType.BODY, schema);
|
|
29
|
+
export const Params = (schema) => HttpInput(RouteInputType.PARAMS, schema);
|
|
30
|
+
export const Query = (schema) => HttpInput(RouteInputType.QUERY, schema);
|
|
31
|
+
export const Request = () => HttpInput(RouteInputType.REQUEST);
|
|
32
|
+
export const Req = Request;
|
|
33
|
+
export const Response = () => HttpInput(RouteInputType.RESPONSE);
|
|
34
|
+
export const Res = Response;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { JSONSchema, SchemaClass } from '@shadow-library/class-schema';
|
|
2
|
+
import { JsonObject } from 'type-fest';
|
|
3
|
+
export interface DynamicRender<T extends JsonObject> {
|
|
4
|
+
template: string;
|
|
5
|
+
data: T;
|
|
6
|
+
}
|
|
7
|
+
export declare const HttpStatus: (status: number) => MethodDecorator;
|
|
8
|
+
export declare const Header: (name: string, value: string | (() => string)) => MethodDecorator;
|
|
9
|
+
export declare const Redirect: (redirect: string, status?: number) => MethodDecorator;
|
|
10
|
+
export declare const Render: (render?: string) => MethodDecorator;
|
|
11
|
+
export declare function RespondFor(statusCode: number, schema: SchemaClass | JSONSchema): MethodDecorator;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Route } from '@shadow-library/app';
|
|
2
|
+
import { ClassSchema } from '@shadow-library/class-schema';
|
|
3
|
+
const isClass = (schema) => schema.toString().startsWith('class ');
|
|
4
|
+
export const HttpStatus = (status) => Route({ status });
|
|
5
|
+
export const Header = (name, value) => Route({ headers: { [name]: value } });
|
|
6
|
+
export const Redirect = (redirect, status = 301) => Route({ redirect, status });
|
|
7
|
+
export const Render = (render) => Route({ render: render ?? true });
|
|
8
|
+
export function RespondFor(statusCode, schema) {
|
|
9
|
+
if (isClass(schema))
|
|
10
|
+
schema = ClassSchema.generate(schema);
|
|
11
|
+
return Route({ schemas: { response: { [statusCode]: schema } } });
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare enum HttpMethod {
|
|
2
|
+
GET = "GET",
|
|
3
|
+
POST = "POST",
|
|
4
|
+
PUT = "PUT",
|
|
5
|
+
DELETE = "DELETE",
|
|
6
|
+
PATCH = "PATCH",
|
|
7
|
+
OPTIONS = "OPTIONS",
|
|
8
|
+
HEAD = "HEAD",
|
|
9
|
+
ALL = "ALL"
|
|
10
|
+
}
|
|
11
|
+
export interface RouteOptions {
|
|
12
|
+
method: HttpMethod;
|
|
13
|
+
path?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function HttpRoute(options: RouteOptions): MethodDecorator;
|
|
16
|
+
export declare const Get: (path?: string) => MethodDecorator;
|
|
17
|
+
export declare const Post: (path?: string) => MethodDecorator;
|
|
18
|
+
export declare const Put: (path?: string) => MethodDecorator;
|
|
19
|
+
export declare const Delete: (path?: string) => MethodDecorator;
|
|
20
|
+
export declare const Patch: (path?: string) => MethodDecorator;
|
|
21
|
+
export declare const Options: (path?: string) => MethodDecorator;
|
|
22
|
+
export declare const Head: (path?: string) => MethodDecorator;
|
|
23
|
+
export declare const All: (path?: string) => MethodDecorator;
|