@shadow-library/fastify 1.6.4 → 1.7.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 CHANGED
@@ -684,6 +684,45 @@ FastifyModule.forRoot({
684
684
 
685
685
  > **Note:** When using `forRootAsync`, the defaults are automatically merged with the config returned by your factory function, so you only need to specify the properties you want to override.
686
686
 
687
+ ### Developer Options
688
+
689
+ The library registers additional config keys under the `app.dev` namespace for development and debugging convenience. These are controlled via environment variables (or any config source) and require no changes to your module setup.
690
+
691
+ | Config Key | Type | Default | Description |
692
+ | --------------------- | --------- | ------------------------------ | ------------------------------------------------- |
693
+ | `app.dev.delay` | `integer` | — (disabled) | Adds an artificial delay (in ms) to every request |
694
+ | `app.dev.stack-trace` | `boolean` | `true` in dev, `false` in prod | Appends the error `stack` to error responses |
695
+
696
+ #### `app.dev.stack-trace`
697
+
698
+ When enabled, the `DefaultErrorHandler` includes the error's `stack` property in the response body alongside the standard error fields. This is automatically enabled in non-production environments and disabled in production.
699
+
700
+ A warning is logged if stack trace logging is active in a production environment.
701
+
702
+ The response shape when enabled matches `DevErrorResponseDto`:
703
+
704
+ ```typescript
705
+ import { DevErrorResponseDto } from '@shadow-library/fastify';
706
+
707
+ // Error response shape with stack trace enabled:
708
+ // {
709
+ // code: "S001",
710
+ // message: "An unexpected error has occurred",
711
+ // stack: "Error: ...\n at ..."
712
+ // }
713
+ ```
714
+
715
+ #### `app.dev.delay`
716
+
717
+ Adds an artificial delay in milliseconds to every incoming request via an `onRequest` hook. Useful for simulating slow networks or testing loading states in your frontend without modifying application code.
718
+
719
+ A warning is logged if a dev delay is active in a production environment.
720
+
721
+ ```bash
722
+ # Add a 500ms delay to every request
723
+ APP_DEV_DELAY=500 node dist/main.js
724
+ ```
725
+
687
726
  ### Dynamic Module Configuration
688
727
 
689
728
  `FastifyModule` is a **dynamic module** that configures itself based on the options you provide. Unlike static modules, dynamic modules return a module configuration object at runtime, allowing for flexible dependency injection and configuration.
@@ -13,6 +13,9 @@ export interface ParsedFastifyError {
13
13
  }
14
14
  export declare class DefaultErrorHandler implements ErrorHandler {
15
15
  private readonly logger;
16
+ private readonly isStackTraceEnabled;
17
+ constructor();
16
18
  protected parseFastifyError(err: FastifyError): ParsedFastifyError;
19
+ private handleError;
17
20
  handle(err: Error, _req: HttpRequest, res: HttpResponse): HttpResponse;
18
21
  }
@@ -18,27 +18,35 @@ const validationError = new server_error_1.ServerError(server_error_1.ServerErro
18
18
  const invalidRequestError = new server_error_1.ServerError(server_error_1.ServerErrorCode.S006);
19
19
  class DefaultErrorHandler {
20
20
  logger = common_1.Logger.getLogger(constants_1.NAMESPACE, 'DefaultErrorHandler');
21
+ isStackTraceEnabled = common_1.Config.get('app.dev.stack-trace');
22
+ constructor() {
23
+ if (this.isStackTraceEnabled && common_1.Config.isProd())
24
+ this.logger.warn('Stack trace logging is enabled in production');
25
+ }
21
26
  parseFastifyError(err) {
22
27
  if (err.statusCode === 500)
23
28
  return { statusCode: 500, error: unexpectedError.toObject() };
24
29
  return { statusCode: err.statusCode, error: { ...invalidRequestError.toObject(), message: err.message } };
25
30
  }
26
- handle(err, _req, res) {
27
- this.logger.warn('Handling error', err);
28
- if (err.cause)
29
- this.logger.warn('Caused by', err.cause);
31
+ handleError(err) {
30
32
  if (err instanceof server_error_1.ServerError)
31
- return res.status(err.getStatusCode()).send(err.toObject());
33
+ return { statusCode: err.getStatusCode(), error: err.toObject() };
32
34
  else if (err instanceof common_1.ValidationError)
33
- return res.status(validationError.getStatusCode()).send({ ...err.toObject(), ...validationError.toObject() });
35
+ return { statusCode: validationError.getStatusCode(), error: { ...err.toObject(), ...validationError.toObject() } };
34
36
  else if (err instanceof common_1.AppError)
35
- return res.status(500).send(err.toObject());
36
- else if (err.name === 'FastifyError') {
37
- const { statusCode, error } = this.parseFastifyError(err);
38
- return res.status(statusCode).send(error);
39
- }
37
+ return { statusCode: 500, error: err.toObject() };
38
+ else if (err.name === 'FastifyError')
39
+ return this.parseFastifyError(err);
40
40
  this.logger.error('Unhandled error has occurred', err);
41
- return res.status(500).send(unexpectedError.toObject());
41
+ return { statusCode: unexpectedError.getStatusCode(), error: unexpectedError.toObject() };
42
+ }
43
+ handle(err, _req, res) {
44
+ this.logger.warn('Handling error', err);
45
+ if (err.cause)
46
+ this.logger.warn('Caused by', err.cause);
47
+ const { statusCode, error } = this.handleError(err);
48
+ const payload = this.isStackTraceEnabled ? { ...error, stack: err.stack } : error;
49
+ return res.status(statusCode).send(payload);
42
50
  }
43
51
  }
44
52
  exports.DefaultErrorHandler = DefaultErrorHandler;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Importing npm packages
3
+ */
4
+ /**
5
+ * Importing user defined packages
6
+ */
7
+ /**
8
+ * Defining types
9
+ */
10
+ declare module '@shadow-library/common' {
11
+ interface ConfigRecords {
12
+ 'app.port': number;
13
+ 'app.host': string;
14
+ 'app.dev.delay': number;
15
+ 'app.dev.stack-trace': boolean;
16
+ }
17
+ }
18
+ export {};
package/cjs/config.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /**
3
+ * Importing npm packages
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const common_1 = require("@shadow-library/common");
7
+ /**
8
+ * Declaring the constants
9
+ */
10
+ const isDevValue = String(common_1.Config.isDev());
11
+ common_1.Config.load('app.host', { defaultValue: 'localhost' });
12
+ common_1.Config.load('app.port', { defaultValue: '8080', validateType: 'integer' });
13
+ common_1.Config.load('app.dev.delay', { validateType: 'integer' });
14
+ common_1.Config.load('app.dev.stack-trace', { defaultValue: isDevValue, validateType: 'boolean' });
package/cjs/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Importing npm packages
2
+ * Importing side-effect packages
3
3
  */
4
4
  import 'reflect-metadata';
5
+ import './config.js';
5
6
  /**
6
7
  * exporting modules
7
8
  */
package/cjs/index.js CHANGED
@@ -16,9 +16,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.FASTIFY_INSTANCE = void 0;
18
18
  /**
19
- * Importing npm packages
19
+ * Importing side-effect packages
20
20
  */
21
21
  require("reflect-metadata");
22
+ require("./config.js");
22
23
  /**
23
24
  * exporting modules
24
25
  */
@@ -17,3 +17,6 @@ export declare class ErrorResponseDto {
17
17
  message: string;
18
18
  fields?: ErrorFieldDto[];
19
19
  }
20
+ export declare class DevErrorResponseDto extends ErrorResponseDto {
21
+ stack?: string;
22
+ }
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.ErrorResponseDto = exports.ErrorFieldDto = void 0;
12
+ exports.DevErrorResponseDto = exports.ErrorResponseDto = exports.ErrorFieldDto = void 0;
13
13
  /**
14
14
  * Importing npm packages
15
15
  */
@@ -65,3 +65,14 @@ __decorate([
65
65
  exports.ErrorResponseDto = ErrorResponseDto = __decorate([
66
66
  (0, class_schema_1.Schema)()
67
67
  ], ErrorResponseDto);
68
+ let DevErrorResponseDto = class DevErrorResponseDto extends ErrorResponseDto {
69
+ stack;
70
+ };
71
+ exports.DevErrorResponseDto = DevErrorResponseDto;
72
+ __decorate([
73
+ (0, class_schema_1.Field)({ optional: true }),
74
+ __metadata("design:type", String)
75
+ ], DevErrorResponseDto.prototype, "stack", void 0);
76
+ exports.DevErrorResponseDto = DevErrorResponseDto = __decorate([
77
+ (0, class_schema_1.Schema)()
78
+ ], DevErrorResponseDto);
@@ -14,12 +14,6 @@ import { ContextService } from '../services/index.js';
14
14
  /**
15
15
  * Defining types
16
16
  */
17
- declare module '@shadow-library/common' {
18
- interface ConfigRecords {
19
- 'app.port': number;
20
- 'app.host': string;
21
- }
22
- }
23
17
  export interface FastifyConfig extends FastifyServerOptions {
24
18
  /**
25
19
  * The host on which the Fastify instance is to be started
@@ -291,6 +291,12 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
291
291
  this.instance.addHook('onRequest', this.context.init());
292
292
  this.instance.addHook('onRequest', this.getRequestLogger());
293
293
  this.logger.info('Registered global middlewares');
294
+ const delay = common_1.Config.get('app.dev.delay');
295
+ if (delay) {
296
+ if (common_1.Config.isProd())
297
+ this.logger.warn('Dev delay is enabled in production');
298
+ this.instance.addHook('onRequest', (_req, _res, done) => setTimeout(done, delay));
299
+ }
294
300
  for (const route of routes) {
295
301
  const metadata = route.metadata;
296
302
  (0, node_assert_1.default)(metadata.path, 'Route path is required');
@@ -32,9 +32,9 @@ const fastify_utils_1 = require("./fastify.utils.js");
32
32
  */
33
33
  let FastifyModule = FastifyModule_1 = class FastifyModule {
34
34
  static getDefaultConfig() {
35
- common_1.Config.load('app.host', { defaultValue: 'localhost' });
36
- common_1.Config.load('app.port', { defaultValue: '8080', validateType: 'number' });
37
- const errorResponseSchema = class_schema_1.ClassSchema.generate(error_response_dto_1.ErrorResponseDto);
35
+ const stackTrace = common_1.Config.get('app.dev.stack-trace');
36
+ const errorResponseClass = stackTrace ? error_response_dto_1.DevErrorResponseDto : error_response_dto_1.ErrorResponseDto;
37
+ const errorResponseSchema = class_schema_1.ClassSchema.generate(errorResponseClass);
38
38
  return {
39
39
  host: common_1.Config.get('app.host'),
40
40
  port: common_1.Config.get('app.port'),
@@ -13,6 +13,9 @@ export interface ParsedFastifyError {
13
13
  }
14
14
  export declare class DefaultErrorHandler implements ErrorHandler {
15
15
  private readonly logger;
16
+ private readonly isStackTraceEnabled;
17
+ constructor();
16
18
  protected parseFastifyError(err: FastifyError): ParsedFastifyError;
19
+ private handleError;
17
20
  handle(err: Error, _req: HttpRequest, res: HttpResponse): HttpResponse;
18
21
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Importing npm packages
3
3
  */
4
- import { AppError, Logger, ValidationError } from '@shadow-library/common';
4
+ import { AppError, Config, Logger, ValidationError } from '@shadow-library/common';
5
5
  /**
6
6
  * Importing user defined packages
7
7
  */
@@ -15,26 +15,34 @@ const validationError = new ServerError(ServerErrorCode.S003);
15
15
  const invalidRequestError = new ServerError(ServerErrorCode.S006);
16
16
  export class DefaultErrorHandler {
17
17
  logger = Logger.getLogger(NAMESPACE, 'DefaultErrorHandler');
18
+ isStackTraceEnabled = Config.get('app.dev.stack-trace');
19
+ constructor() {
20
+ if (this.isStackTraceEnabled && Config.isProd())
21
+ this.logger.warn('Stack trace logging is enabled in production');
22
+ }
18
23
  parseFastifyError(err) {
19
24
  if (err.statusCode === 500)
20
25
  return { statusCode: 500, error: unexpectedError.toObject() };
21
26
  return { statusCode: err.statusCode, error: { ...invalidRequestError.toObject(), message: err.message } };
22
27
  }
23
- handle(err, _req, res) {
24
- this.logger.warn('Handling error', err);
25
- if (err.cause)
26
- this.logger.warn('Caused by', err.cause);
28
+ handleError(err) {
27
29
  if (err instanceof ServerError)
28
- return res.status(err.getStatusCode()).send(err.toObject());
30
+ return { statusCode: err.getStatusCode(), error: err.toObject() };
29
31
  else if (err instanceof ValidationError)
30
- return res.status(validationError.getStatusCode()).send({ ...err.toObject(), ...validationError.toObject() });
32
+ return { statusCode: validationError.getStatusCode(), error: { ...err.toObject(), ...validationError.toObject() } };
31
33
  else if (err instanceof AppError)
32
- return res.status(500).send(err.toObject());
33
- else if (err.name === 'FastifyError') {
34
- const { statusCode, error } = this.parseFastifyError(err);
35
- return res.status(statusCode).send(error);
36
- }
34
+ return { statusCode: 500, error: err.toObject() };
35
+ else if (err.name === 'FastifyError')
36
+ return this.parseFastifyError(err);
37
37
  this.logger.error('Unhandled error has occurred', err);
38
- return res.status(500).send(unexpectedError.toObject());
38
+ return { statusCode: unexpectedError.getStatusCode(), error: unexpectedError.toObject() };
39
+ }
40
+ handle(err, _req, res) {
41
+ this.logger.warn('Handling error', err);
42
+ if (err.cause)
43
+ this.logger.warn('Caused by', err.cause);
44
+ const { statusCode, error } = this.handleError(err);
45
+ const payload = this.isStackTraceEnabled ? { ...error, stack: err.stack } : error;
46
+ return res.status(statusCode).send(payload);
39
47
  }
40
48
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Importing npm packages
3
+ */
4
+ /**
5
+ * Importing user defined packages
6
+ */
7
+ /**
8
+ * Defining types
9
+ */
10
+ declare module '@shadow-library/common' {
11
+ interface ConfigRecords {
12
+ 'app.port': number;
13
+ 'app.host': string;
14
+ 'app.dev.delay': number;
15
+ 'app.dev.stack-trace': boolean;
16
+ }
17
+ }
18
+ export {};
package/esm/config.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Importing npm packages
3
+ */
4
+ import { Config } from '@shadow-library/common';
5
+ /**
6
+ * Declaring the constants
7
+ */
8
+ const isDevValue = String(Config.isDev());
9
+ Config.load('app.host', { defaultValue: 'localhost' });
10
+ Config.load('app.port', { defaultValue: '8080', validateType: 'integer' });
11
+ Config.load('app.dev.delay', { validateType: 'integer' });
12
+ Config.load('app.dev.stack-trace', { defaultValue: isDevValue, validateType: 'boolean' });
package/esm/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Importing npm packages
2
+ * Importing side-effect packages
3
3
  */
4
4
  import 'reflect-metadata';
5
+ import './config.js';
5
6
  /**
6
7
  * exporting modules
7
8
  */
package/esm/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Importing npm packages
2
+ * Importing side-effect packages
3
3
  */
4
4
  import 'reflect-metadata';
5
+ import './config.js';
5
6
  /**
6
7
  * exporting modules
7
8
  */
@@ -17,3 +17,6 @@ export declare class ErrorResponseDto {
17
17
  message: string;
18
18
  fields?: ErrorFieldDto[];
19
19
  }
20
+ export declare class DevErrorResponseDto extends ErrorResponseDto {
21
+ stack?: string;
22
+ }
@@ -62,3 +62,14 @@ ErrorResponseDto = __decorate([
62
62
  Schema()
63
63
  ], ErrorResponseDto);
64
64
  export { ErrorResponseDto };
65
+ let DevErrorResponseDto = class DevErrorResponseDto extends ErrorResponseDto {
66
+ stack;
67
+ };
68
+ __decorate([
69
+ Field({ optional: true }),
70
+ __metadata("design:type", String)
71
+ ], DevErrorResponseDto.prototype, "stack", void 0);
72
+ DevErrorResponseDto = __decorate([
73
+ Schema()
74
+ ], DevErrorResponseDto);
75
+ export { DevErrorResponseDto };
@@ -14,12 +14,6 @@ import { ContextService } from '../services/index.js';
14
14
  /**
15
15
  * Defining types
16
16
  */
17
- declare module '@shadow-library/common' {
18
- interface ConfigRecords {
19
- 'app.port': number;
20
- 'app.host': string;
21
- }
22
- }
23
17
  export interface FastifyConfig extends FastifyServerOptions {
24
18
  /**
25
19
  * The host on which the Fastify instance is to be started
@@ -16,7 +16,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
16
16
  import assert from 'node:assert';
17
17
  import { Inject, Injectable, Router } from '@shadow-library/app';
18
18
  import { ClassSchema, TransformerFactory } from '@shadow-library/class-schema';
19
- import { InternalError, Logger, utils } from '@shadow-library/common';
19
+ import { Config, InternalError, Logger, utils } from '@shadow-library/common';
20
20
  import { all as deepmerge } from 'deepmerge';
21
21
  import findMyWay from 'find-my-way';
22
22
  import stringify from 'json-stable-stringify';
@@ -285,6 +285,12 @@ let FastifyRouter = class FastifyRouter extends Router {
285
285
  this.instance.addHook('onRequest', this.context.init());
286
286
  this.instance.addHook('onRequest', this.getRequestLogger());
287
287
  this.logger.info('Registered global middlewares');
288
+ const delay = Config.get('app.dev.delay');
289
+ if (delay) {
290
+ if (Config.isProd())
291
+ this.logger.warn('Dev delay is enabled in production');
292
+ this.instance.addHook('onRequest', (_req, _res, done) => setTimeout(done, delay));
293
+ }
288
294
  for (const route of routes) {
289
295
  const metadata = route.metadata;
290
296
  assert(metadata.path, 'Route path is required');
@@ -18,7 +18,7 @@ import { v4 as uuid } from 'uuid';
18
18
  import { DefaultErrorHandler } from '../classes/index.js';
19
19
  import { FASTIFY_CONFIG, FASTIFY_INSTANCE } from '../constants.js';
20
20
  import { ContextService } from '../services/index.js';
21
- import { ErrorResponseDto } from './error-response.dto.js';
21
+ import { DevErrorResponseDto, ErrorResponseDto } from './error-response.dto.js';
22
22
  import { FastifyRouter } from './fastify-router.js';
23
23
  import { createFastifyInstance } from './fastify.utils.js';
24
24
  /**
@@ -29,9 +29,9 @@ import { createFastifyInstance } from './fastify.utils.js';
29
29
  */
30
30
  let FastifyModule = FastifyModule_1 = class FastifyModule {
31
31
  static getDefaultConfig() {
32
- Config.load('app.host', { defaultValue: 'localhost' });
33
- Config.load('app.port', { defaultValue: '8080', validateType: 'number' });
34
- const errorResponseSchema = ClassSchema.generate(ErrorResponseDto);
32
+ const stackTrace = Config.get('app.dev.stack-trace');
33
+ const errorResponseClass = stackTrace ? DevErrorResponseDto : ErrorResponseDto;
34
+ const errorResponseSchema = ClassSchema.generate(errorResponseClass);
35
35
  return {
36
36
  host: Config.get('app.host'),
37
37
  port: Config.get('app.port'),
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@shadow-library/fastify",
3
3
  "type": "module",
4
- "version": "1.6.4",
5
- "sideEffects": false,
4
+ "version": "1.7.0",
5
+ "sideEffects": [
6
+ "./esm/config.js",
7
+ "./cjs/config.js"
8
+ ],
6
9
  "description": "A Fastify wrapper featuring decorator-based routing, middleware and error handling",
7
10
  "repository": {
8
11
  "type": "git",
@@ -27,9 +30,9 @@
27
30
  },
28
31
  "peerDependencies": {
29
32
  "@fastify/view": "^10.0.0",
30
- "@shadow-library/app": "^1.3.1",
31
- "@shadow-library/class-schema": "^0.2.0",
32
- "@shadow-library/common": "^1.5.2",
33
+ "@shadow-library/app": "^1.3.2",
34
+ "@shadow-library/class-schema": "^0.4.2",
35
+ "@shadow-library/common": "^1.6.0",
33
36
  "reflect-metadata": "^0.2.2"
34
37
  },
35
38
  "peerDependenciesMeta": {