@shadow-library/fastify 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +436 -0
- package/cjs/decorators/http-input.decorator.d.ts +2 -3
- package/cjs/decorators/http-output.decorator.d.ts +3 -0
- package/cjs/decorators/http-output.decorator.js +0 -4
- package/cjs/decorators/index.d.ts +1 -0
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/transform.decorator.d.ts +27 -0
- package/cjs/decorators/transform.decorator.js +17 -0
- package/cjs/interfaces/server-metadata.interface.d.ts +2 -2
- package/cjs/module/data-transformers.d.ts +14 -0
- package/cjs/module/data-transformers.js +19 -0
- package/cjs/module/fastify-module.interface.d.ts +5 -0
- package/cjs/module/fastify-router.d.ts +17 -5
- package/cjs/module/fastify-router.js +136 -30
- package/cjs/module/fastify.utils.d.ts +9 -2
- package/cjs/module/fastify.utils.js +15 -10
- package/esm/decorators/http-input.decorator.d.ts +2 -3
- package/esm/decorators/http-output.decorator.d.ts +3 -0
- package/esm/decorators/http-output.decorator.js +0 -4
- package/esm/decorators/index.d.ts +1 -0
- package/esm/decorators/index.js +1 -0
- package/esm/decorators/transform.decorator.d.ts +27 -0
- package/esm/decorators/transform.decorator.js +14 -0
- package/esm/interfaces/server-metadata.interface.d.ts +2 -2
- package/esm/module/data-transformers.d.ts +14 -0
- package/esm/module/data-transformers.js +16 -0
- package/esm/module/fastify-module.interface.d.ts +5 -0
- package/esm/module/fastify-router.d.ts +17 -5
- package/esm/module/fastify-router.js +136 -30
- package/esm/module/fastify.utils.d.ts +9 -2
- package/esm/module/fastify.utils.js +15 -10
- package/package.json +1 -1
|
@@ -23,7 +23,7 @@ const node_assert_1 = __importDefault(require("node:assert"));
|
|
|
23
23
|
const app_1 = require("@shadow-library/app");
|
|
24
24
|
const class_schema_1 = require("@shadow-library/class-schema");
|
|
25
25
|
const common_1 = require("@shadow-library/common");
|
|
26
|
-
const deepmerge_1 =
|
|
26
|
+
const deepmerge_1 = require("deepmerge");
|
|
27
27
|
const find_my_way_1 = __importDefault(require("find-my-way"));
|
|
28
28
|
const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
|
|
29
29
|
/**
|
|
@@ -32,11 +32,13 @@ const json_stable_stringify_1 = __importDefault(require("json-stable-stringify")
|
|
|
32
32
|
const constants_1 = require("../constants.js");
|
|
33
33
|
const decorators_1 = require("../decorators/index.js");
|
|
34
34
|
const services_1 = require("../services/index.js");
|
|
35
|
+
const data_transformers_1 = require("./data-transformers.js");
|
|
35
36
|
/**
|
|
36
37
|
* Declaring the constants
|
|
37
38
|
*/
|
|
38
39
|
const httpMethods = Object.values(decorators_1.HttpMethod).filter(m => m !== decorators_1.HttpMethod.ALL);
|
|
39
|
-
const DEFAULT_ARTIFACTS = {
|
|
40
|
+
const DEFAULT_ARTIFACTS = { masks: {}, transformers: {} };
|
|
41
|
+
const isClassSchema = (schema) => typeof schema === 'function' || (Array.isArray(schema) && typeof schema[0] === 'function');
|
|
40
42
|
let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
41
43
|
config;
|
|
42
44
|
instance;
|
|
@@ -45,12 +47,17 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
45
47
|
logger = common_1.Logger.getLogger(constants_1.NAMESPACE, 'FastifyRouter');
|
|
46
48
|
cachedDynamicMiddlewares = new Map();
|
|
47
49
|
childRouter = null;
|
|
50
|
+
transformers = { ...data_transformers_1.INBUILT_TRANSFORMERS };
|
|
48
51
|
sensitiveTransformer = new class_schema_1.TransformerFactory(s => s['x-fastify']?.sensitive === true);
|
|
52
|
+
inputDataTransformer = new class_schema_1.TransformerFactory(s => this.isTransformable('input', s));
|
|
53
|
+
outputDataTransformer = new class_schema_1.TransformerFactory(s => this.isTransformable('output', s));
|
|
49
54
|
constructor(config, instance, context) {
|
|
50
55
|
super();
|
|
51
56
|
this.config = config;
|
|
52
57
|
this.instance = instance;
|
|
53
58
|
this.context = context;
|
|
59
|
+
if (config.transformers)
|
|
60
|
+
Object.assign(this.transformers, config.transformers);
|
|
54
61
|
if (config.enableChildRoutes) {
|
|
55
62
|
const options = common_1.utils.object.pickKeys(config, ['ignoreTrailingSlash', 'ignoreDuplicateSlashes', 'allowUnsafeRegex', 'caseSensitive', 'maxParamLength', 'querystringParser']);
|
|
56
63
|
this.childRouter = (0, find_my_way_1.default)(options);
|
|
@@ -59,6 +66,10 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
59
66
|
getInstance() {
|
|
60
67
|
return this.instance;
|
|
61
68
|
}
|
|
69
|
+
isTransformable(type, schema) {
|
|
70
|
+
const transformerType = schema['x-fastify']?.transform?.[type];
|
|
71
|
+
return typeof transformerType === 'string' && transformerType in this.transformers;
|
|
72
|
+
}
|
|
62
73
|
joinPaths(...parts) {
|
|
63
74
|
const path = parts
|
|
64
75
|
.filter(p => typeof p === 'string')
|
|
@@ -67,6 +78,13 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
67
78
|
.join('/');
|
|
68
79
|
return `/${path}`;
|
|
69
80
|
}
|
|
81
|
+
addRouteHandler(routeOptions, hook, handler, action = 'append') {
|
|
82
|
+
const existingHandlers = Array.isArray(routeOptions[hook]) ? routeOptions[hook] : routeOptions[hook] ? [routeOptions[hook]] : [];
|
|
83
|
+
if (action === 'prepend')
|
|
84
|
+
routeOptions[hook] = [handler, ...existingHandlers];
|
|
85
|
+
else
|
|
86
|
+
routeOptions[hook] = [...existingHandlers, handler];
|
|
87
|
+
}
|
|
70
88
|
registerRawBody() {
|
|
71
89
|
const opts = { parseAs: 'buffer' };
|
|
72
90
|
const parser = this.instance.getDefaultJsonParser('error', 'error');
|
|
@@ -88,7 +106,16 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
88
106
|
return common_1.utils.string.maskWords(stringified);
|
|
89
107
|
return '****';
|
|
90
108
|
}
|
|
109
|
+
generateDataTransformer(type) {
|
|
110
|
+
return (value, schema) => {
|
|
111
|
+
const transformType = schema['x-fastify']?.transform?.[type];
|
|
112
|
+
const transformer = this.transformers[transformType];
|
|
113
|
+
(0, node_assert_1.default)(transformer, `transformer '${transformType}' not found`);
|
|
114
|
+
return transformer(value);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
91
117
|
getRequestLogger() {
|
|
118
|
+
const mask = this.maskField.bind(this);
|
|
92
119
|
return (req, res, done) => {
|
|
93
120
|
const startTime = process.hrtime();
|
|
94
121
|
res.raw.on('finish', () => {
|
|
@@ -96,7 +123,7 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
96
123
|
if (isLoggingDisabled)
|
|
97
124
|
return;
|
|
98
125
|
const { url, config } = req.routeOptions;
|
|
99
|
-
const {
|
|
126
|
+
const { masks } = config.artifacts ?? DEFAULT_ARTIFACTS;
|
|
100
127
|
const metadata = {};
|
|
101
128
|
metadata.rid = this.context.getRID();
|
|
102
129
|
metadata.url = url ?? req.raw.url;
|
|
@@ -109,11 +136,11 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
109
136
|
const resTime = process.hrtime(startTime);
|
|
110
137
|
metadata.timeTaken = (resTime[0] * 1e3 + resTime[1] * 1e-6).toFixed(3); // Converting time to milliseconds
|
|
111
138
|
if (req.body)
|
|
112
|
-
metadata.body =
|
|
139
|
+
metadata.body = masks.body ? masks.body(structuredClone(req.body), mask) : req.body;
|
|
113
140
|
if (req.query)
|
|
114
|
-
metadata.query =
|
|
141
|
+
metadata.query = masks.query ? masks.query(structuredClone(req.query), mask) : req.query;
|
|
115
142
|
if (req.params)
|
|
116
|
-
metadata.params =
|
|
143
|
+
metadata.params = masks.params ? masks.params(structuredClone(req.params), mask) : req.params;
|
|
117
144
|
this.logger.http(`${req.method} ${metadata.url} -> ${res.statusCode} (${metadata.timeTaken}ms)`, metadata);
|
|
118
145
|
});
|
|
119
146
|
done();
|
|
@@ -206,6 +233,52 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
206
233
|
this.cachedDynamicMiddlewares.set(cacheKey, handler);
|
|
207
234
|
return handler;
|
|
208
235
|
}
|
|
236
|
+
transformResponseHandler() {
|
|
237
|
+
const transform = this.generateDataTransformer('output');
|
|
238
|
+
return async (request, reply, payload) => {
|
|
239
|
+
const statusCode = String(reply.statusCode);
|
|
240
|
+
const responseTransformers = request.routeOptions.config.artifacts?.transformers.response;
|
|
241
|
+
if (!responseTransformers)
|
|
242
|
+
return payload;
|
|
243
|
+
let transformer = responseTransformers[statusCode];
|
|
244
|
+
if (!transformer) {
|
|
245
|
+
const fallbackStatus = statusCode.charAt(0) + 'xx';
|
|
246
|
+
transformer = responseTransformers[fallbackStatus];
|
|
247
|
+
this.logger.debug(`using fallback response transformer for status code ${statusCode}`, { fallbackStatus });
|
|
248
|
+
}
|
|
249
|
+
if (transformer) {
|
|
250
|
+
this.logger.debug(`transforming response for status code ${statusCode}`);
|
|
251
|
+
const cloned = (0, deepmerge_1.all)([{}, payload]);
|
|
252
|
+
const data = transformer(cloned, transform);
|
|
253
|
+
this.logger.debug(`transformed response for status code ${statusCode}`, { data });
|
|
254
|
+
return data;
|
|
255
|
+
}
|
|
256
|
+
return payload;
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
transformRequestHandler() {
|
|
260
|
+
const transform = this.generateDataTransformer('input');
|
|
261
|
+
return async (request) => {
|
|
262
|
+
const transformers = request.routeOptions.config.artifacts?.transformers;
|
|
263
|
+
if (!transformers)
|
|
264
|
+
return;
|
|
265
|
+
if (transformers.body && request.body) {
|
|
266
|
+
this.logger.debug('transforming request body', { body: request.body });
|
|
267
|
+
request.body = transformers.body(request.body, transform);
|
|
268
|
+
this.logger.debug('transformed request body', { body: request.body });
|
|
269
|
+
}
|
|
270
|
+
if (transformers.query && request.query) {
|
|
271
|
+
this.logger.debug('transforming request query', { query: request.query });
|
|
272
|
+
request.query = transformers.query(request.query, transform);
|
|
273
|
+
this.logger.debug('transformed request query', { query: request.query });
|
|
274
|
+
}
|
|
275
|
+
if (transformers.params && request.params) {
|
|
276
|
+
this.logger.debug('transforming request params', { params: request.params });
|
|
277
|
+
request.params = transformers.params(request.params, transform);
|
|
278
|
+
this.logger.debug('transformed request params', { params: request.params });
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
209
282
|
async register(controllers) {
|
|
210
283
|
const { middlewares, routes } = this.parseControllers(controllers);
|
|
211
284
|
const defaultResponseSchemas = this.config.responseSchema ?? {};
|
|
@@ -222,7 +295,7 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
222
295
|
(0, node_assert_1.default)(metadata.method, 'Route method is required');
|
|
223
296
|
this.logger.debug(`registering route ${metadata.method} ${metadata.path}`);
|
|
224
297
|
const fastifyRouteOptions = common_1.utils.object.omitKeys(metadata, ['path', 'method', 'schemas', 'rawBody', 'status', 'headers', 'redirect', 'render']);
|
|
225
|
-
const artifacts = {
|
|
298
|
+
const artifacts = { masks: {}, transformers: {} };
|
|
226
299
|
const routeOptions = { ...fastifyRouteOptions, config: { metadata, artifacts } };
|
|
227
300
|
routeOptions.url = metadata.path;
|
|
228
301
|
routeOptions.method = metadata.method === decorators_1.HttpMethod.ALL ? httpMethods : [metadata.method];
|
|
@@ -234,45 +307,78 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
|
|
|
234
307
|
const handler = await this.getMiddlewareHandler(middleware, metadata);
|
|
235
308
|
if (typeof handler === 'function') {
|
|
236
309
|
this.logger.debug(`applying '${type}' middleware '${name}'`);
|
|
237
|
-
|
|
238
|
-
if (middlewareHandler)
|
|
239
|
-
middlewareHandler.push(handler);
|
|
240
|
-
else
|
|
241
|
-
routeOptions[type] = [handler];
|
|
310
|
+
this.addRouteHandler(routeOptions, type, handler);
|
|
242
311
|
}
|
|
243
312
|
}
|
|
244
|
-
|
|
313
|
+
const responseSchemas = { ...defaultResponseSchemas };
|
|
314
|
+
routeOptions.schema = { response: responseSchemas };
|
|
245
315
|
routeOptions.attachValidation = metadata.silentValidation ?? false;
|
|
246
|
-
|
|
247
|
-
const { body: bodySchema, params: paramsSchema, query: querySchema } = metadata.schemas ?? {};
|
|
316
|
+
const { body: bodySchema, params: paramsSchema, query: querySchema, response: responseSchema } = metadata.schemas ?? {};
|
|
248
317
|
const isMaskEnabled = this.config.maskSensitiveData ?? true;
|
|
249
318
|
if (bodySchema) {
|
|
250
|
-
const schema =
|
|
319
|
+
const schema = isClassSchema(bodySchema) ? class_schema_1.ClassSchema.generate(bodySchema) : bodySchema;
|
|
251
320
|
routeOptions.schema.body = schema;
|
|
252
|
-
if (class_schema_1.ClassSchema.isBranded(schema)
|
|
253
|
-
const
|
|
254
|
-
if (
|
|
255
|
-
artifacts.
|
|
321
|
+
if (class_schema_1.ClassSchema.isBranded(schema)) {
|
|
322
|
+
const bodyTransformer = this.inputDataTransformer.maybeCompile(schema);
|
|
323
|
+
if (bodyTransformer)
|
|
324
|
+
artifacts.transformers.body = bodyTransformer;
|
|
325
|
+
if (isMaskEnabled) {
|
|
326
|
+
const transformer = this.sensitiveTransformer.maybeCompile(schema);
|
|
327
|
+
if (transformer)
|
|
328
|
+
artifacts.masks.body = transformer;
|
|
329
|
+
}
|
|
256
330
|
}
|
|
257
331
|
}
|
|
258
332
|
if (paramsSchema) {
|
|
259
|
-
const schema =
|
|
333
|
+
const schema = isClassSchema(paramsSchema) ? class_schema_1.ClassSchema.generate(paramsSchema) : paramsSchema;
|
|
260
334
|
routeOptions.schema.params = schema;
|
|
261
|
-
if (class_schema_1.ClassSchema.isBranded(schema)
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
artifacts.
|
|
335
|
+
if (class_schema_1.ClassSchema.isBranded(schema)) {
|
|
336
|
+
const paramsTransformer = this.inputDataTransformer.maybeCompile(schema);
|
|
337
|
+
if (paramsTransformer)
|
|
338
|
+
artifacts.transformers.params = paramsTransformer;
|
|
339
|
+
if (isMaskEnabled) {
|
|
340
|
+
const transformer = this.sensitiveTransformer.maybeCompile(schema);
|
|
341
|
+
if (transformer)
|
|
342
|
+
artifacts.masks.params = transformer;
|
|
343
|
+
}
|
|
265
344
|
}
|
|
266
345
|
}
|
|
267
346
|
if (querySchema) {
|
|
268
|
-
const schema =
|
|
347
|
+
const schema = isClassSchema(querySchema) ? class_schema_1.ClassSchema.generate(querySchema) : querySchema;
|
|
269
348
|
routeOptions.schema.querystring = schema;
|
|
270
|
-
if (class_schema_1.ClassSchema.isBranded(schema)
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
273
|
-
artifacts.
|
|
349
|
+
if (class_schema_1.ClassSchema.isBranded(schema)) {
|
|
350
|
+
const queryTransformer = this.inputDataTransformer.maybeCompile(schema);
|
|
351
|
+
if (queryTransformer)
|
|
352
|
+
artifacts.transformers.query = queryTransformer;
|
|
353
|
+
if (isMaskEnabled) {
|
|
354
|
+
const transformer = this.sensitiveTransformer.maybeCompile(schema);
|
|
355
|
+
if (transformer)
|
|
356
|
+
artifacts.masks.query = transformer;
|
|
357
|
+
}
|
|
274
358
|
}
|
|
275
359
|
}
|
|
360
|
+
if (responseSchema) {
|
|
361
|
+
const responseTransformers = {};
|
|
362
|
+
artifacts.transformers.response = responseTransformers;
|
|
363
|
+
for (const [code, schemaDef] of Object.entries(responseSchema)) {
|
|
364
|
+
const statusCode = code.toLowerCase();
|
|
365
|
+
const schema = isClassSchema(schemaDef) ? class_schema_1.ClassSchema.generate(schemaDef) : schemaDef;
|
|
366
|
+
responseSchemas[statusCode] = schema;
|
|
367
|
+
if (class_schema_1.ClassSchema.isBranded(schema)) {
|
|
368
|
+
const transformer = this.outputDataTransformer.maybeCompile(schema);
|
|
369
|
+
if (transformer)
|
|
370
|
+
responseTransformers[statusCode] = transformer;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (Object.keys(responseTransformers).length > 0) {
|
|
374
|
+
const handler = this.transformResponseHandler();
|
|
375
|
+
this.addRouteHandler(routeOptions, 'preSerialization', handler);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if ('body' in artifacts.transformers || 'query' in artifacts.transformers || 'params' in artifacts.transformers) {
|
|
379
|
+
const handler = this.transformRequestHandler();
|
|
380
|
+
this.addRouteHandler(routeOptions, 'preHandler', handler, 'prepend');
|
|
381
|
+
}
|
|
276
382
|
this.logger.debug('route options', { options: routeOptions });
|
|
277
383
|
this.instance.route(routeOptions);
|
|
278
384
|
this.logger.info(`registered route ${metadata.method} ${routeOptions.url}`);
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { ValidationError } from '@shadow-library/common';
|
|
2
|
-
import { SchemaObject } from 'ajv';
|
|
2
|
+
import Ajv, { SchemaObject } from 'ajv';
|
|
3
3
|
import { FastifyInstance } from 'fastify';
|
|
4
4
|
import { FastifyRouteSchemaDef, FastifySchemaValidationError, FastifyValidationResult, SchemaErrorDataVar } from 'fastify/types/schema';
|
|
5
5
|
import { FastifyConfig, FastifyModuleOptions } from './fastify-module.interface.js';
|
|
6
|
+
/**
|
|
7
|
+
* Defining types
|
|
8
|
+
*/
|
|
9
|
+
export interface AjvValidators {
|
|
10
|
+
strictValidator: Ajv;
|
|
11
|
+
lenientValidator: Ajv;
|
|
12
|
+
}
|
|
6
13
|
export declare const notFoundHandler: () => never;
|
|
7
|
-
export declare function compileValidator(routeSchema: FastifyRouteSchemaDef<SchemaObject
|
|
14
|
+
export declare function compileValidator(routeSchema: FastifyRouteSchemaDef<SchemaObject>, validators: AjvValidators): FastifyValidationResult;
|
|
8
15
|
export declare function formatSchemaErrors(errors: FastifySchemaValidationError[], dataVar: SchemaErrorDataVar): ValidationError;
|
|
9
16
|
export declare function createFastifyInstance(config: FastifyConfig, fastifyFactory?: FastifyModuleOptions['fastifyFactory']): Promise<FastifyInstance>;
|
|
@@ -18,17 +18,13 @@ const fastify_1 = require("fastify");
|
|
|
18
18
|
* Importing user defined packages
|
|
19
19
|
*/
|
|
20
20
|
const server_error_1 = require("../server.error.js");
|
|
21
|
-
/**
|
|
22
|
-
* Defining types
|
|
23
|
-
*/
|
|
24
21
|
/**
|
|
25
22
|
* Declaring the constants
|
|
26
23
|
*/
|
|
27
24
|
const keywords = ['x-fastify'];
|
|
28
25
|
const allowedHttpParts = ['body', 'params', 'querystring'];
|
|
29
|
-
const strictValidator = new ajv_1.default({ allErrors: true, useDefaults: true, removeAdditional: true, strict: true, keywords });
|
|
30
|
-
const lenientValidator = new ajv_1.default({ allErrors: true, coerceTypes: true, useDefaults: true, removeAdditional: true, strict: true, keywords });
|
|
31
26
|
const notFoundError = new server_error_1.ServerError(server_error_1.ServerErrorCode.S002);
|
|
27
|
+
const defaultAjvOptions = { allErrors: true, useDefaults: true, removeAdditional: true, strict: true, keywords };
|
|
32
28
|
const notFoundHandler = () => (0, common_1.throwError)(notFoundError);
|
|
33
29
|
exports.notFoundHandler = notFoundHandler;
|
|
34
30
|
function compileSchema(ajv, schema) {
|
|
@@ -41,13 +37,13 @@ function compileSchema(ajv, schema) {
|
|
|
41
37
|
}
|
|
42
38
|
return ajv.getSchema(schema.$id);
|
|
43
39
|
}
|
|
44
|
-
function compileValidator(routeSchema) {
|
|
40
|
+
function compileValidator(routeSchema, validators) {
|
|
45
41
|
(0, node_assert_1.default)(allowedHttpParts.includes(routeSchema.httpPart), `Invalid httpPart: ${routeSchema.httpPart}`);
|
|
46
42
|
if (routeSchema.httpPart === 'body')
|
|
47
|
-
return compileSchema(strictValidator, routeSchema.schema);
|
|
43
|
+
return compileSchema(validators.strictValidator, routeSchema.schema);
|
|
48
44
|
if (routeSchema.httpPart === 'params')
|
|
49
|
-
return compileSchema(lenientValidator, routeSchema.schema);
|
|
50
|
-
const validate = compileSchema(lenientValidator, routeSchema.schema);
|
|
45
|
+
return compileSchema(validators.lenientValidator, routeSchema.schema);
|
|
46
|
+
const validate = compileSchema(validators.lenientValidator, routeSchema.schema);
|
|
51
47
|
return (data) => {
|
|
52
48
|
validate(data);
|
|
53
49
|
for (const error of validate.errors ?? []) {
|
|
@@ -78,10 +74,19 @@ function formatSchemaErrors(errors, dataVar) {
|
|
|
78
74
|
async function createFastifyInstance(config, fastifyFactory) {
|
|
79
75
|
const options = common_1.utils.object.omitKeys(config, ['port', 'host', 'errorHandler', 'responseSchema']);
|
|
80
76
|
const { errorHandler } = config;
|
|
77
|
+
const strictValidator = new ajv_1.default({ ...defaultAjvOptions, ...config.ajv?.customOptions });
|
|
78
|
+
const lenientValidator = new ajv_1.default({ ...defaultAjvOptions, coerceTypes: true, ...config.ajv?.customOptions });
|
|
79
|
+
for (let plugin of config.ajv?.plugins ?? []) {
|
|
80
|
+
if (typeof plugin === 'function')
|
|
81
|
+
plugin = [plugin, {}];
|
|
82
|
+
const [ajvPlugin, options] = plugin;
|
|
83
|
+
ajvPlugin(strictValidator, options);
|
|
84
|
+
ajvPlugin(lenientValidator, options);
|
|
85
|
+
}
|
|
81
86
|
const instance = (0, fastify_1.fastify)(options);
|
|
82
87
|
instance.setSchemaErrorFormatter(formatSchemaErrors);
|
|
83
88
|
instance.setNotFoundHandler(exports.notFoundHandler);
|
|
84
89
|
instance.setErrorHandler(errorHandler.handle.bind(errorHandler));
|
|
85
|
-
instance.setValidatorCompiler(compileValidator);
|
|
90
|
+
instance.setValidatorCompiler(routeSchema => compileValidator(routeSchema, { strictValidator, lenientValidator }));
|
|
86
91
|
return fastifyFactory ? await fastifyFactory(instance) : instance;
|
|
87
92
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { JSONSchema } from '@shadow-library/class-schema';
|
|
2
|
-
import { Class } from 'type-fest';
|
|
1
|
+
import { JSONSchema, SchemaClass } from '@shadow-library/class-schema';
|
|
3
2
|
/**
|
|
4
3
|
* Defining types
|
|
5
4
|
*/
|
|
@@ -10,7 +9,7 @@ export declare enum RouteInputType {
|
|
|
10
9
|
REQUEST = "request",
|
|
11
10
|
RESPONSE = "response"
|
|
12
11
|
}
|
|
13
|
-
export type RouteInputSchemas = Partial<Record<'body' | 'params' | 'query', JSONSchema |
|
|
12
|
+
export type RouteInputSchemas = Partial<Record<'body' | 'params' | 'query', JSONSchema | SchemaClass>>;
|
|
14
13
|
/**
|
|
15
14
|
* Declaring the constants
|
|
16
15
|
*/
|
|
@@ -13,6 +13,9 @@ export interface DynamicRender<T extends JsonObject> {
|
|
|
13
13
|
template: string;
|
|
14
14
|
data: T;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Declaring the constants
|
|
18
|
+
*/
|
|
16
19
|
export declare const HttpStatus: (status: number) => MethodDecorator;
|
|
17
20
|
export declare const Header: (name: string, value: string | (() => string)) => MethodDecorator;
|
|
18
21
|
export declare const Redirect: (redirect: string, status?: number) => MethodDecorator;
|
|
@@ -2,17 +2,13 @@
|
|
|
2
2
|
* Importing npm packages
|
|
3
3
|
*/
|
|
4
4
|
import { Route } from '@shadow-library/app';
|
|
5
|
-
import { ClassSchema } from '@shadow-library/class-schema';
|
|
6
5
|
/**
|
|
7
6
|
* Declaring the constants
|
|
8
7
|
*/
|
|
9
|
-
const isClass = (schema) => schema.toString().startsWith('class ');
|
|
10
8
|
export const HttpStatus = (status) => Route({ status });
|
|
11
9
|
export const Header = (name, value) => Route({ headers: { [name]: value } });
|
|
12
10
|
export const Redirect = (redirect, status = 301) => Route({ redirect, status });
|
|
13
11
|
export const Render = (render) => Route({ render: render ?? true });
|
|
14
12
|
export function RespondFor(statusCode, schema) {
|
|
15
|
-
if (isClass(schema))
|
|
16
|
-
schema = ClassSchema.generate(schema);
|
|
17
13
|
return Route({ schemas: { response: { [statusCode]: schema } } });
|
|
18
14
|
}
|
package/esm/decorators/index.js
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing npm packages
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Importing user defined packages
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Defining types
|
|
9
|
+
*/
|
|
10
|
+
export interface TransformOptions {
|
|
11
|
+
input?: TransformTypes;
|
|
12
|
+
output?: TransformTypes;
|
|
13
|
+
}
|
|
14
|
+
export interface CustomTransformers {
|
|
15
|
+
}
|
|
16
|
+
export interface InbuiltTransformers {
|
|
17
|
+
'email:normalize': (value: string) => string;
|
|
18
|
+
'string:trim': (value: string) => string;
|
|
19
|
+
'int:parse': (value: string) => number;
|
|
20
|
+
'float:parse': (value: string) => number;
|
|
21
|
+
'bigint:parse': (value: string) => bigint;
|
|
22
|
+
}
|
|
23
|
+
export type TransformTypes = keyof CustomTransformers | keyof InbuiltTransformers;
|
|
24
|
+
/**
|
|
25
|
+
* Declaring the constants
|
|
26
|
+
*/
|
|
27
|
+
export declare function Transform(type: TransformTypes | TransformOptions): PropertyDecorator;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing npm packages
|
|
3
|
+
*/
|
|
4
|
+
import { FieldMetadata } from '@shadow-library/class-schema';
|
|
5
|
+
/**
|
|
6
|
+
* Declaring the constants
|
|
7
|
+
*/
|
|
8
|
+
export function Transform(type) {
|
|
9
|
+
const options = typeof type === 'string' ? { input: type, output: type } : type;
|
|
10
|
+
return (target, propertyKey) => {
|
|
11
|
+
const decorator = FieldMetadata({ 'x-fastify': { transform: options } });
|
|
12
|
+
decorator(target, propertyKey);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Importing npm packages
|
|
3
3
|
*/
|
|
4
4
|
import { RouteMetadata } from '@shadow-library/app';
|
|
5
|
-
import { JSONSchema } from '@shadow-library/class-schema';
|
|
5
|
+
import { JSONSchema, SchemaClass } from '@shadow-library/class-schema';
|
|
6
6
|
import { FastifyInstance, RouteShorthandOptions } from 'fastify';
|
|
7
7
|
/**
|
|
8
8
|
* Importing user defined packages
|
|
@@ -18,7 +18,7 @@ declare module '@shadow-library/app' {
|
|
|
18
18
|
path?: string;
|
|
19
19
|
version?: number;
|
|
20
20
|
schemas?: RouteInputSchemas & {
|
|
21
|
-
response?: Record<number | string, JSONSchema>;
|
|
21
|
+
response?: Record<number | string, JSONSchema | SchemaClass>;
|
|
22
22
|
};
|
|
23
23
|
rawBody?: boolean;
|
|
24
24
|
silentValidation?: boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing npm packages
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Importing user defined packages
|
|
6
|
+
*/
|
|
7
|
+
import { InbuiltTransformers } from '../decorators/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Defining types
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Declaring the constants
|
|
13
|
+
*/
|
|
14
|
+
export declare const INBUILT_TRANSFORMERS: InbuiltTransformers;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing npm packages
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Defining types
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Declaring the constants
|
|
9
|
+
*/
|
|
10
|
+
export const INBUILT_TRANSFORMERS = {
|
|
11
|
+
'email:normalize': value => value.trim().toLowerCase(),
|
|
12
|
+
'string:trim': value => value.trim(),
|
|
13
|
+
'int:parse': value => parseInt(value, 10),
|
|
14
|
+
'float:parse': value => parseFloat(value),
|
|
15
|
+
'bigint:parse': value => BigInt(value),
|
|
16
|
+
};
|
|
@@ -8,6 +8,7 @@ import { Promisable } from 'type-fest';
|
|
|
8
8
|
/**
|
|
9
9
|
* Importing user defined packages
|
|
10
10
|
*/
|
|
11
|
+
import { CustomTransformers } from '../decorators/index.js';
|
|
11
12
|
import { ErrorHandler } from '../interfaces/index.js';
|
|
12
13
|
import { ContextService } from '../services/index.js';
|
|
13
14
|
/**
|
|
@@ -59,6 +60,10 @@ export interface FastifyConfig extends FastifyServerOptions {
|
|
|
59
60
|
* The global route prefix for all routes in the Fastify instance
|
|
60
61
|
*/
|
|
61
62
|
routePrefix?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Object defining custom transformers for request and response data transformation
|
|
65
|
+
*/
|
|
66
|
+
transformers?: Record<keyof CustomTransformers, (value: any) => any>;
|
|
62
67
|
}
|
|
63
68
|
export interface FastifyModuleOptions extends Partial<FastifyConfig> {
|
|
64
69
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ControllerRouteMetadata, Router } from '@shadow-library/app';
|
|
2
|
+
import { Transformer } from '@shadow-library/class-schema';
|
|
2
3
|
import { type FastifyInstance } from 'fastify';
|
|
3
4
|
import { Chain as MockRequestChain, InjectOptions as MockRequestOptions, Response as MockResponse } from 'light-my-request';
|
|
4
5
|
import { JsonObject, JsonValue } from 'type-fest';
|
|
@@ -49,12 +50,15 @@ export interface ChildRouteRequest {
|
|
|
49
50
|
params: Record<string, string>;
|
|
50
51
|
query: Record<string, string>;
|
|
51
52
|
}
|
|
53
|
+
interface Transformers {
|
|
54
|
+
body?: Transformer;
|
|
55
|
+
query?: Transformer;
|
|
56
|
+
params?: Transformer;
|
|
57
|
+
response?: Record<string, Transformer>;
|
|
58
|
+
}
|
|
52
59
|
interface RouteArtifacts {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
maskQuery?(query: object): object;
|
|
56
|
-
maskParams?(params: object): object;
|
|
57
|
-
};
|
|
60
|
+
masks: Partial<Record<'body' | 'query' | 'params', Transformer>>;
|
|
61
|
+
transformers: Transformers;
|
|
58
62
|
}
|
|
59
63
|
export declare class FastifyRouter extends Router {
|
|
60
64
|
private readonly config;
|
|
@@ -64,17 +68,25 @@ export declare class FastifyRouter extends Router {
|
|
|
64
68
|
private readonly logger;
|
|
65
69
|
private readonly cachedDynamicMiddlewares;
|
|
66
70
|
private readonly childRouter;
|
|
71
|
+
private readonly transformers;
|
|
67
72
|
private readonly sensitiveTransformer;
|
|
73
|
+
private readonly inputDataTransformer;
|
|
74
|
+
private readonly outputDataTransformer;
|
|
68
75
|
constructor(config: FastifyConfig, instance: FastifyInstance, context: ContextService);
|
|
69
76
|
getInstance(): FastifyInstance;
|
|
77
|
+
private isTransformable;
|
|
70
78
|
private joinPaths;
|
|
79
|
+
private addRouteHandler;
|
|
71
80
|
private registerRawBody;
|
|
72
81
|
private maskField;
|
|
82
|
+
private generateDataTransformer;
|
|
73
83
|
private getRequestLogger;
|
|
74
84
|
private parseControllers;
|
|
75
85
|
private getStatusCode;
|
|
76
86
|
private generateRouteHandler;
|
|
77
87
|
private getMiddlewareHandler;
|
|
88
|
+
private transformResponseHandler;
|
|
89
|
+
private transformRequestHandler;
|
|
78
90
|
register(controllers: ControllerRouteMetadata[]): Promise<void>;
|
|
79
91
|
start(): Promise<void>;
|
|
80
92
|
stop(): Promise<void>;
|