@camcima/nestjs-rfc9457 0.2.0 → 0.3.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
@@ -77,6 +77,7 @@ Extension members (arbitrary key-value pairs) are allowed for problem-type-speci
77
77
  - Four `instance` strategies: `'request-uri'`, `'uuid'`, `'none'`, or a custom callback
78
78
  - Optional catch-all mode for non-`HttpException` throwables (produces 500 Problem Details)
79
79
  - Custom `exceptionMapper` callback for full control over any exception
80
+ - Default `error`-level logging of unhandled exceptions when `catchAllExceptions: true` (override via `onUnhandled` callback)
80
81
  - `ProblemDetailsFactory` is injectable — use it directly in GraphQL, microservices, or custom filters
81
82
  - Optional `@nestjs/swagger` integration: `ProblemDetailDto` and `ValidationProblemDetailDto` for OpenAPI documentation, plus a `applyProblemDetailResponses()` helper that auto-applies `@ApiResponse` decorators to all controllers under `application/problem+json`
82
83
  - Works with both Express and Fastify adapters
@@ -260,6 +261,8 @@ When `false` (default), exceptions that are not `HttpException` instances are pa
260
261
  Rfc9457Module.forRoot({ catchAllExceptions: true });
261
262
  ```
262
263
 
264
+ **Observability:** when this branch fires (a non-`HttpException` reaches the filter and no `exceptionMapper` claims it), the library logs the exception at `error` level via NestJS's built-in `Logger` (context `Rfc9457ExceptionFilter`) before sending the generic 500. This keeps unexpected throwables visible in server logs even though the response body is intentionally bland. To redirect or replace this logging, use the [`onUnhandled`](#onunhandled) callback described below.
265
+
263
266
  ### `exceptionMapper`
264
267
 
265
268
  **Type**: `(exception: unknown, request: Rfc9457Request) => ProblemDetail | null`
@@ -284,6 +287,28 @@ Rfc9457Module.forRoot({
284
287
 
285
288
  If the returned `ProblemDetail` omits `status`, the factory falls back to `exception.getStatus()` (if it is an `HttpException`) or `500`.
286
289
 
290
+ ### `onUnhandled`
291
+
292
+ **Type**: `(exception: unknown, request: Rfc9457Request) => void` | **Default**: built-in `Logger.error(...)` (context `Rfc9457ExceptionFilter`)
293
+
294
+ Called when a non-`HttpException` reaches the catch-all branch (i.e. `catchAllExceptions: true` AND the `exceptionMapper` returned `null`). Use this to send unhandled exceptions to a structured sink (Sentry, Datadog, a custom pino child logger) or to suppress the default log entirely.
295
+
296
+ ```typescript
297
+ Rfc9457Module.forRoot({
298
+ catchAllExceptions: true,
299
+ onUnhandled: (exception, request) => {
300
+ // Route to Sentry, Datadog, etc.
301
+ sentry.captureException(exception, {
302
+ tags: { method: request.method, url: request.url },
303
+ });
304
+ },
305
+ });
306
+ ```
307
+
308
+ **The filter still sends the generic 500 Problem Details response after invoking `onUnhandled`.** This callback exists purely for observability — it never changes the HTTP response.
309
+
310
+ When `onUnhandled` is **not** provided, the library calls `Logger.error(...)` with either the exception's `stack` string or a `{ exception }` structured context (for non-`Error` values). The log context is `Rfc9457ExceptionFilter` so it can be filtered or silenced via NestJS's logger configuration.
311
+
287
312
  ### `validationExceptionMapper`
288
313
 
289
314
  **Type**: `(messages: string[], request: Rfc9457Request) => ProblemDetail`
@@ -930,13 +955,13 @@ Gitleaks must be [installed locally](https://github.com/gitleaks/gitleaks#instal
930
955
 
931
956
  ```bash
932
957
  # Dependency audit (production only)
933
- npm run audit:deps
958
+ pnpm run audit:deps
934
959
 
935
960
  # Secret scanning
936
- npm run audit:secrets
961
+ pnpm run audit:secrets
937
962
 
938
- # Full npm audit (all dependencies)
939
- npm audit
963
+ # Full pnpm audit (all dependencies)
964
+ pnpm audit
940
965
  ```
941
966
 
942
967
  ---
@@ -951,19 +976,19 @@ git clone https://github.com/camcima/nestjs-rfc9457.git
951
976
  cd nestjs-rfc9457
952
977
 
953
978
  # Install dependencies
954
- npm install
979
+ pnpm install
955
980
 
956
981
  # Run unit tests
957
- npm run test:unit
982
+ pnpm run test:unit
958
983
 
959
984
  # Run e2e tests
960
- npm run test:e2e
985
+ pnpm run test:e2e
961
986
 
962
987
  # Run all tests with coverage
963
- npm run test:cov
988
+ pnpm run test:cov
964
989
 
965
990
  # Build
966
- npm run build
991
+ pnpm run build
967
992
  ```
968
993
 
969
994
  This project uses [Conventional Commits](https://www.conventionalcommits.org/) enforced by commitlint, and [Lefthook](https://github.com/evilmartians/lefthook) for pre-commit hooks (lint + format on staged files).
@@ -6,6 +6,7 @@ export declare class Rfc9457ExceptionFilter extends BaseExceptionFilter {
6
6
  private readonly factory;
7
7
  private readonly options;
8
8
  private readonly adapterHost;
9
+ private readonly logger;
9
10
  constructor(factory: ProblemDetailsFactory, options: Rfc9457ModuleOptions, adapterHost: HttpAdapterHost);
10
11
  catch(exception: unknown, host: ArgumentsHost): void;
11
12
  }
@@ -11,18 +11,20 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  return function (target, key) { decorator(target, key, paramIndex); }
13
13
  };
14
+ var Rfc9457ExceptionFilter_1;
14
15
  Object.defineProperty(exports, "__esModule", { value: true });
15
16
  exports.Rfc9457ExceptionFilter = void 0;
16
17
  const common_1 = require("@nestjs/common");
17
18
  const core_1 = require("@nestjs/core");
18
19
  const problem_details_factory_1 = require("./problem-details.factory");
19
20
  const rfc9457_constants_1 = require("./rfc9457.constants");
20
- let Rfc9457ExceptionFilter = class Rfc9457ExceptionFilter extends core_1.BaseExceptionFilter {
21
+ let Rfc9457ExceptionFilter = Rfc9457ExceptionFilter_1 = class Rfc9457ExceptionFilter extends core_1.BaseExceptionFilter {
21
22
  constructor(factory, options, adapterHost) {
22
23
  super(adapterHost.httpAdapter);
23
24
  this.factory = factory;
24
25
  this.options = options;
25
26
  this.adapterHost = adapterHost;
27
+ this.logger = new common_1.Logger(Rfc9457ExceptionFilter_1.name);
26
28
  }
27
29
  catch(exception, host) {
28
30
  if (host.getType() !== 'http') {
@@ -47,6 +49,18 @@ let Rfc9457ExceptionFilter = class Rfc9457ExceptionFilter extends core_1.BaseExc
47
49
  super.catch(exception, host);
48
50
  return;
49
51
  }
52
+ if (!isHttpException) {
53
+ if (this.options.onUnhandled) {
54
+ const ctx = host.switchToHttp();
55
+ this.options.onUnhandled(exception, ctx.getRequest());
56
+ }
57
+ else if (exception instanceof Error) {
58
+ this.logger.error(`Unhandled non-HTTP exception: ${exception.message}`, exception.stack);
59
+ }
60
+ else {
61
+ this.logger.error('Unhandled non-HTTP exception (non-Error value thrown)', exception);
62
+ }
63
+ }
50
64
  const ctx = host.switchToHttp();
51
65
  const request = ctx.getRequest();
52
66
  const response = ctx.getResponse();
@@ -57,7 +71,7 @@ let Rfc9457ExceptionFilter = class Rfc9457ExceptionFilter extends core_1.BaseExc
57
71
  }
58
72
  };
59
73
  exports.Rfc9457ExceptionFilter = Rfc9457ExceptionFilter;
60
- exports.Rfc9457ExceptionFilter = Rfc9457ExceptionFilter = __decorate([
74
+ exports.Rfc9457ExceptionFilter = Rfc9457ExceptionFilter = Rfc9457ExceptionFilter_1 = __decorate([
61
75
  (0, common_1.Catch)(),
62
76
  __param(1, (0, common_1.Inject)(rfc9457_constants_1.RFC9457_MODULE_OPTIONS)),
63
77
  __metadata("design:paramtypes", [problem_details_factory_1.ProblemDetailsFactory, Object, core_1.HttpAdapterHost])
@@ -1 +1 @@
1
- {"version":3,"file":"rfc9457.exception-filter.js","sourceRoot":"","sources":["../src/rfc9457.exception-filter.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA6E;AAC7E,uCAAoE;AACpE,uEAAkE;AAClE,2DAAmF;AAI5E,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,0BAAmB;IAC7D,YACmB,OAA8B,EACE,OAA6B,EAC7D,WAA4B;QAE7C,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QAJd,YAAO,GAAP,OAAO,CAAuB;QACE,YAAO,GAAP,OAAO,CAAsB;QAC7D,gBAAW,GAAX,WAAW,CAAiB;IAG/C,CAAC;IAED,KAAK,CAAC,SAAkB,EAAE,IAAmB;QAE3C,IAAI,IAAI,CAAC,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;YAC9B,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,MAAM,eAAe,GAAG,SAAS,YAAY,sBAAa,CAAC;QAK3D,IAAI,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YAChC,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAChE,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;gBACnC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;gBACnF,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC;gBACjD,WAAW,CAAC,SAAS,CAAC,QAAQ,EAAE,cAAc,EAAE,wCAAoB,CAAC,CAAC;gBACtE,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;gBAC1C,OAAO;YACT,CAAC;QACH,CAAC;QAGD,IAAI,CAAC,eAAe,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;YACzD,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAEnC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QACvF,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC;QACjD,WAAW,CAAC,SAAS,CAAC,QAAQ,EAAE,cAAc,EAAE,wCAAoB,CAAC,CAAC;QACtE,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;CACF,CAAA;AAlDY,wDAAsB;iCAAtB,sBAAsB;IADlC,IAAA,cAAK,GAAE;IAIH,WAAA,IAAA,eAAM,EAAC,0CAAsB,CAAC,CAAA;qCADL,+CAAqB,UAEjB,sBAAe;GAJpC,sBAAsB,CAkDlC"}
1
+ {"version":3,"file":"rfc9457.exception-filter.js","sourceRoot":"","sources":["../src/rfc9457.exception-filter.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,7 @@ export interface Rfc9457ModuleOptions {
24
24
  catchAllExceptions?: boolean;
25
25
  exceptionMapper?: (exception: unknown, request: Rfc9457Request) => ProblemDetail | null;
26
26
  validationExceptionMapper?: (messages: string[], request: Rfc9457Request) => ProblemDetail;
27
+ onUnhandled?: (exception: unknown, request: Rfc9457Request) => void;
27
28
  }
28
29
  export interface Rfc9457OptionsFactory {
29
30
  createRfc9457Options(): Promise<Rfc9457ModuleOptions> | Rfc9457ModuleOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camcima/nestjs-rfc9457",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "NestJS library for RFC 9457 Problem Details responses",
5
5
  "author": "Carlos Cima",
6
6
  "license": "MIT",
@@ -22,6 +22,7 @@
22
22
  "engines": {
23
23
  "node": ">=18"
24
24
  },
25
+ "packageManager": "pnpm@9.15.0",
25
26
  "scripts": {
26
27
  "build": "tsc -p tsconfig.build.json",
27
28
  "lint": "eslint 'src/**/*.ts' 'test/**/*.ts'",
@@ -31,17 +32,17 @@
31
32
  "test:unit": "jest --testPathPattern=test/unit",
32
33
  "test:e2e": "jest --testPathPattern=test/e2e",
33
34
  "test:cov": "jest --coverage",
34
- "prepublishOnly": "npm run build",
35
+ "prepublishOnly": "pnpm run build",
35
36
  "release": "release-it",
36
- "audit:deps": "npm audit --omit=dev",
37
+ "audit:deps": "pnpm audit --prod",
37
38
  "audit:secrets": "gitleaks git --no-banner --redact -v"
38
39
  },
39
40
  "peerDependencies": {
40
41
  "@nestjs/common": "^10.0.0 || ^11.0.0",
41
42
  "@nestjs/core": "^10.0.0 || ^11.0.0",
43
+ "@nestjs/swagger": "^7.0.0 || ^8.0.0 || ^11.0.0",
42
44
  "class-validator": "^0.14.0",
43
- "reflect-metadata": "^0.1.13 || ^0.2.0",
44
- "@nestjs/swagger": "^7.0.0 || ^8.0.0 || ^11.0.0"
45
+ "reflect-metadata": "^0.1.13 || ^0.2.0"
45
46
  },
46
47
  "peerDependenciesMeta": {
47
48
  "class-validator": {
@@ -54,14 +55,14 @@
54
55
  "devDependencies": {
55
56
  "@commitlint/cli": "^20.5.0",
56
57
  "@commitlint/config-conventional": "^20.5.0",
57
- "@nestjs/common": "^11.1.18",
58
- "@nestjs/core": "^11.1.18",
59
- "@nestjs/swagger": "^11.1.18",
60
- "@nestjs/platform-express": "^11.1.18",
61
- "@nestjs/platform-fastify": "^11.1.18",
62
- "@nestjs/testing": "^11.1.18",
58
+ "@nestjs/common": "^11.1.19",
59
+ "@nestjs/core": "^11.1.19",
60
+ "@nestjs/platform-express": "^11.1.19",
61
+ "@nestjs/platform-fastify": "^11.1.19",
62
+ "@nestjs/swagger": "^11.2.7",
63
+ "@nestjs/testing": "^11.1.19",
63
64
  "@types/jest": "^30.0.0",
64
- "@types/node": "^22.19.17",
65
+ "@types/node": "^25.6.0",
65
66
  "@types/supertest": "^7.2.0",
66
67
  "@typescript-eslint/eslint-plugin": "^7.0.0",
67
68
  "@typescript-eslint/parser": "^7.0.0",
@@ -70,10 +71,9 @@
70
71
  "eslint": "^8.0.0",
71
72
  "eslint-config-prettier": "^9.0.0",
72
73
  "eslint-plugin-prettier": "^5.0.0",
73
- "fastify": "^4.0.0",
74
74
  "jest": "^30.3.0",
75
75
  "lefthook": "^1.0.0",
76
- "prettier": "^3.0.0",
76
+ "prettier": "^3.8.3",
77
77
  "reflect-metadata": "^0.2.0",
78
78
  "release-it": "^18.0.0",
79
79
  "rxjs": "^7.0.0",
@@ -82,6 +82,15 @@
82
82
  "ts-node": "^10.9.2",
83
83
  "typescript": "^6.0.2"
84
84
  },
85
+ "pnpm": {
86
+ "overrides": {
87
+ "basic-ftp@<5.3.0": ">=5.3.0",
88
+ "fastify@<5.8.5": ">=5.8.5",
89
+ "lodash@<4.18.0": ">=4.18.0",
90
+ "path-to-regexp@<8.4.0": ">=8.4.0",
91
+ "undici@<6.24.0": ">=6.24.0"
92
+ }
93
+ },
85
94
  "repository": {
86
95
  "type": "git",
87
96
  "url": "git+https://github.com/camcima/nestjs-rfc9457.git"