@bymax-one/nest-core 1.6.2 → 1.6.4

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/CHANGELOG.md CHANGED
@@ -11,6 +11,132 @@ heading here.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [1.6.4] - 2026-10-30
15
+
16
+ A 5xx envelope no longer carries `details` unless `envelope.exposeInternals` is on, and the
17
+ message of the router's own 404 no longer carries the query string or the credentials of an
18
+ absolute-form target.
19
+
20
+ **Apply to a derived backend:**
21
+
22
+ - **`details` is absent from a response with status 500 or above, by default.** The filter used to pass the
23
+ `details` of any `HttpException` through verbatim, so a library that put a payload fragment
24
+ there (a cache preview, a query excerpt) published it in the body of a 5xx. The `code` and
25
+ `message` are unchanged, and so is every 4xx, where `details` is the contract. A backend
26
+ that read `details` from a 5xx body should log that context where the error is thrown instead.
27
+ `envelope.exposeInternals` keeps the details, for local debugging only. The filter owns no
28
+ logger and records nothing about them itself.
29
+ - **The router's 404 `message` names the path, not the URL.** For a request that matches no
30
+ route, Nest answers `Cannot GET /orders?token=…` with the raw URL. A test or a client that
31
+ matches that text for a request with a query string now reads `Cannot GET /orders`, and the
32
+ userinfo of an absolute-form target (`GET http://user:pw@host/orders`) is gone from it too. A
33
+ `NotFoundException` the application throws is not touched.
34
+
35
+ ### Security
36
+
37
+ - **The router's 404 `message` no longer carries the query string or credentials.** 1.6.3 took
38
+ the query out of `path`, "where reset codes and tokens travel", but the `message` of Nest's own
39
+ 404 (`Cannot ${method} ${url}`, built in `@nestjs/core` from the adapter's raw URL) still held
40
+ it, and held the userinfo of an absolute-form target as well. Both now read
41
+ `Cannot GET /path`, as `path` does. Only that exact framework message is rewritten, so a
42
+ message the application wrote, and a domain error's message, stay as written.
43
+
44
+ - **A server error no longer publishes its `details` by default.** Both the flat `{ code, message, details }`
45
+ and the nested `{ error: { code, message, details } }` shapes are cut for a status of 500 or
46
+ above, as is a validation-shaped 5xx, because every mapping rule shares one builder.
47
+
48
+ - **Development dependencies lifted past four advisories.** `body-parser` 2.4.0
49
+ (GHSA-27r3-jxmh-p828), `multer` 2.5.0 (GHSA-q7qp-rjg7-q26m) and `qs` 6.16.1
50
+ (GHSA-6pq8-jhm2-2vcw) are now the only resolved copies, through `pnpm`
51
+ overrides. `deepmerge` (GHSA-gcqx-w48m-w2h4) has no patched release and is
52
+ reached only through `jest-config`, so it is ignored in `osv-scanner.toml`
53
+ until 2027-01-27. All of them sit in the development tree: the package
54
+ declares no `dependencies`, so none of them reaches a consumer.
55
+
56
+ ### Changed
57
+
58
+ - Development tooling: `tsup` 9.0.0, `@opentelemetry/context-async-hooks`
59
+ 3.0.0, `@nestjs/common`, `core`, `platform-express`, `platform-fastify` and
60
+ `testing` 11.2.8, `@opentelemetry/api` 1.10.0 and the CodeQL upload action
61
+ 4.39.1. None of them changes the published JavaScript. With `tsup` 9 the
62
+ declaration files list type-only exports in a separate `export type { … }`
63
+ statement instead of inline `type` modifiers; the exported names are the
64
+ same. `SECURITY.md` and `CONTRIBUTING.md` now match the current release line
65
+ and the pinned `pnpm`.
66
+
67
+ ### Documentation
68
+
69
+ - **Support policy.** `SECURITY.md` states that only the latest minor (`1.6.x`)
70
+ receives security fixes, and that earlier versions are not supported and
71
+ get no backports: the guidance is to upgrade.
72
+ - **Issue template contact links.** The "Questions and discussion" link is
73
+ removed from the issue chooser, since the repository has GitHub Discussions
74
+ turned off. The private security contact stays.
75
+ - **Subpath lists.** `SECURITY.md` and both issue templates name the
76
+ subpaths, including `./metrics` and `./openapi`, and `SECURITY.md` calls the
77
+ request-timing component a middleware.
78
+ - **Unknown errors and the logger.** The README no longer says the message and
79
+ stack of an unknown error are "captured for your logger". They go to the
80
+ `BYMAX_UNEXPECTED_ERROR_SINK` seam when one is bound, and reach the response
81
+ only under `envelope.exposeInternals`. It also no longer says the body of that
82
+ 500 carries only the code and the correlation id: it carries the fixed
83
+ envelope fields and the generic message. The `package.json` description calls
84
+ the request-timing component a middleware, as the module registers it.
85
+
86
+ ## [1.6.3] - 2026-10-23
87
+
88
+ The error envelope's `path` carried the query string, and the documented
89
+ recipe for pairing with `@bymax-one/nest-logger` bound nothing.
90
+
91
+ **Apply to a derived backend:**
92
+
93
+ - **`path` changes in the response body.** An application that reads `path`
94
+ from the envelope now gets the request path alone. `/invoices/42?token=x`
95
+ and `http://host/invoices/42` both now read `/invoices/42`. The query string was never part of the documented
96
+ contract, and is where reset codes and tokens travel.
97
+ - **The nest-logger correlation recipe changes.** An application that
98
+ followed the previous README recipe bound
99
+ `BYMAX_CORRELATION_PROVIDER` to `LogContextService` with `useExisting`, and
100
+ got no correlation id at all. Replace it with the `useFactory` in the
101
+ README's "Integration with `@bymax-one/nest-logger`", and make sure
102
+ nest-logger's request-id middleware is mounted (`applyAccessLog(app)` or
103
+ `applyRequestIdMiddleware(consumer)`). Without it there is no id to read.
104
+
105
+ ### Fixed
106
+
107
+ - **The envelope `path` no longer carries the query string.** Both adapters
108
+ report the original URL, query included: Express returns `originalUrl`, and
109
+ Fastify the raw URL. The filter copied that into `path`, which is documented
110
+ as the request URL path. So a query-string secret reached the client and any
111
+ log that stores the envelope. An absolute-form target, as a proxy sends it,
112
+ also carried its scheme, authority and any credentials; only the path is
113
+ kept now. The `onUnexpectedError` context now receives the same path. The unexpected-error sink already reported the route
114
+ template and is unaffected.
115
+ - **The nest-logger integration recipe binds what it promises.** It imported
116
+ `NestLoggerModule`, which does not exist; the module is `BymaxLoggerModule`.
117
+ It also aliased `LogContextService` with `useExisting`, but that service
118
+ has no `getCorrelationId()`. The binding was inert, and silently so: the
119
+ filter treats a failing correlation read as "no id". The README and the
120
+ specification now adapt the `requestId` key with a factory; the README
121
+ binds `PinoUnexpectedErrorSink` (nest-logger 1.4.1) beside it. The snippet
122
+ passed the published-surface gate before only because that gate ignores
123
+ diagnostics from a peer the fixture does not install.
124
+ - **A thrown value that cannot be described no longer breaks the response.**
125
+ With `envelope.exposeInternals` on, a value with no prototype, or an `Error`
126
+ whose `stack` getter throws, threw out of the filter. The internals dump now
127
+ degrades to a fixed message, and the hooks still run.
128
+ - **The discard warning no longer claims a mismatch for identical entries.**
129
+ It now says a generated client will not match the library's own types only
130
+ where the two definitions differ.
131
+
132
+ ### Changed
133
+
134
+ - **Served component order (since 1.6.1).** Since 1.6.1, a document's own
135
+ `components.schemas` and `components.parameters` entries are listed before
136
+ this package's catalogue and before contributed entries. The 1.6.1 entry did
137
+ not say so. Order in a components map carries no meaning in OpenAPI, but it
138
+ shows in Swagger UI's model list and in a stored `openapi.json` snapshot.
139
+
14
140
  ## [1.6.2] - 2026-10-22
15
141
 
16
142
  An unexpected error became `500 BYMAX_INTERNAL_ERROR` and the application had no
@@ -1138,4 +1264,6 @@ have regressed from. They are kept because the reasoning is worth having.
1138
1264
  [1.6.0]: https://github.com/bymaxone/nest-core/compare/v1.5.3...v1.6.0
1139
1265
  [1.6.1]: https://github.com/bymaxone/nest-core/compare/v1.6.0...v1.6.1
1140
1266
  [1.6.2]: https://github.com/bymaxone/nest-core/compare/v1.6.1...v1.6.2
1141
- [Unreleased]: https://github.com/bymaxone/nest-core/compare/v1.6.2...HEAD
1267
+ [1.6.3]: https://github.com/bymaxone/nest-core/compare/v1.6.2...v1.6.3
1268
+ [1.6.4]: https://github.com/bymaxone/nest-core/compare/v1.6.3...v1.6.4
1269
+ [Unreleased]: https://github.com/bymaxone/nest-core/compare/v1.6.4...HEAD
package/README.md CHANGED
@@ -69,7 +69,9 @@ never loads its peer, which the release gate asserts against the packed tarball.
69
69
  - ✅ **Versioned code catalog** — `BYMAX_NOT_FOUND`, `BYMAX_CONFLICT`, `BYMAX_BAD_GATEWAY`
70
70
  and the rest, exported as constants so a client maps a `code` rather than a message string
71
71
  - ✅ **Internals stay internal** — an unknown error becomes a generic 500; its message and
72
- stack are captured for your logger, and reach the body only under `exposeInternals`
72
+ stack go to the `BYMAX_UNEXPECTED_ERROR_SINK` seam when you bind one, and reach the body
73
+ only under `exposeInternals`. A 5xx
74
+ never carries `details` either, whichever library threw it, unless `exposeInternals` is on
73
75
  - ✅ **Correlation id** — resolved through `BYMAX_CORRELATION_PROVIDER`, so the id comes from
74
76
  wherever your request context already keeps it
75
77
 
@@ -218,10 +220,10 @@ and the classification table: [Production is a closed door](#production-is-a-clo
218
220
 
219
221
  ### `envelope`
220
222
 
221
- | Option | Type | Default | Description |
222
- | ----------------- | --------- | ------- | ---------------------------------------------------------------------------------------------- |
223
- | `enabled` | `boolean` | `true` | Registers the global exception filter. |
224
- | `exposeInternals` | `boolean` | `false` | Includes the original message and stack of unknown errors. Development only, never production. |
223
+ | Option | Type | Default | Description |
224
+ | ----------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
225
+ | `enabled` | `boolean` | `true` | Registers the global exception filter. |
226
+ | `exposeInternals` | `boolean` | `false` | Includes the original message and stack of unknown errors, and the `details` of a 5xx. Development only, never production. |
225
227
 
226
228
  ### `timing`
227
229
 
@@ -557,15 +559,29 @@ follows this exact, versioned shape:
557
559
  }
558
560
  ```
559
561
 
560
- | Field | Type | Presence | Notes |
561
- | --------------- | ----------------- | -------- | -------------------------------------------------- |
562
- | `statusCode` | number | always | HTTP status. |
563
- | `code` | string | always | Stable, machine-readable code. |
564
- | `message` | string | always | Human-readable, safe for end users. |
565
- | `details` | array or object | optional | Structured context, for example validation issues. |
566
- | `correlationId` | string | optional | Present when a correlation provider is bound. |
567
- | `timestamp` | string (ISO 8601) | always | Time the error was formatted. |
568
- | `path` | string | always | Request URL path. |
562
+ | Field | Type | Presence | Notes |
563
+ | --------------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------- |
564
+ | `statusCode` | number | always | HTTP status. |
565
+ | `code` | string | always | Stable, machine-readable code. |
566
+ | `message` | string | always | Human-readable, safe for end users. |
567
+ | `details` | array or object | optional | Structured context, for example validation issues. Absent on a 5xx unless `exposeInternals` is on. |
568
+ | `correlationId` | string | optional | Present when a correlation provider is bound. |
569
+ | `timestamp` | string (ISO 8601) | always | Time the error was formatted. |
570
+ | `path` | string | always | Request URL path, never the query string. The router's own 404 `message` names the same path. |
571
+
572
+ **A server error carries no `details` by default.** For a status of 500 or above, the filter omits `details`
573
+ from the body, whichever library threw the `HttpException` and whether it wrote them flat or
574
+ under `error`; the `code` and `message` stay, because they are text the thrower chose for the
575
+ caller. `envelope.exposeInternals` keeps the details, for local debugging only. The filter owns no
576
+ logger and does not record a 5xx's `details` itself, so whatever throws such an error should log
577
+ that context on the server and must not interpolate internal data into its `message`. Client
578
+ errors are unchanged: their details are the contract. The reasoning follows the OWASP
579
+ [Error Handling Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Error_Handling_Cheat_Sheet.html)
580
+ (a generic client response, details logged server side), RFC 9457
581
+ [sections 3.1.4 and 5](https://www.rfc-editor.org/rfc/rfc9457.html) (a problem detail helps the client
582
+ correct the problem rather than debug, and no implementation details leave through the HTTP
583
+ interface), and the NestJS [exception filters](https://docs.nestjs.com/exception-filters) behaviour
584
+ of serializing an `HttpException` response object as the whole body.
569
585
 
570
586
  Codes are stable strings under a reserved `BYMAX_` prefix, derived from the
571
587
  HTTP status: `BYMAX_NOT_FOUND` for 404, `BYMAX_VALIDATION_FAILED` for the
@@ -1546,27 +1562,67 @@ your samples and your logs.
1546
1562
 
1547
1563
  ## 🔗 Integration with `@bymax-one/nest-logger`
1548
1564
 
1549
- Pairing this package with `@bymax-one/nest-logger` yields correlated logs and
1550
- error responses with one binding and no hard coupling: `LogContextService`
1551
- satisfies `ICorrelationIdProvider` out of the box, so `useExisting` aliases it
1552
- onto the shared token. Bind it from a `@Global()` module of your own so the
1553
- binding is visible outside the module that declares it:
1565
+ Pairing this package with `@bymax-one/nest-logger` gives correlated logs and
1566
+ error responses without either package importing the other. nest-logger's
1567
+ request-id middleware stores the id under the `requestId` key of
1568
+ `LogContextService`. That service is not itself an `ICorrelationIdProvider` —
1569
+ it has no `getCorrelationId()` — so adapt it with a factory. `useExisting` on
1570
+ it binds an object without that method, and the filter then reads no id and
1571
+ reports nothing. Bind both tokens from a `@Global()` module of your own so they
1572
+ are visible outside the module that declares them:
1554
1573
 
1555
1574
  ```typescript
1556
1575
  import { Global, Module } from '@nestjs/common'
1557
- import { BYMAX_CORRELATION_PROVIDER } from '@bymax-one/nest-core'
1558
- import { LogContextService, NestLoggerModule } from '@bymax-one/nest-logger'
1576
+ import { BYMAX_CORRELATION_PROVIDER, BYMAX_UNEXPECTED_ERROR_SINK } from '@bymax-one/nest-core'
1577
+ import type { ICorrelationIdProvider } from '@bymax-one/nest-core'
1578
+ import {
1579
+ BymaxLoggerModule,
1580
+ LogContextService,
1581
+ PinoUnexpectedErrorSink
1582
+ } from '@bymax-one/nest-logger'
1559
1583
 
1560
1584
  @Global()
1561
1585
  @Module({
1562
- imports: [NestLoggerModule.forRoot()],
1563
- providers: [{ provide: BYMAX_CORRELATION_PROVIDER, useExisting: LogContextService }],
1564
- exports: [BYMAX_CORRELATION_PROVIDER]
1586
+ imports: [BymaxLoggerModule.forRoot({ service: { name: 'api', version: '1.0.0' } })],
1587
+ providers: [
1588
+ {
1589
+ provide: BYMAX_CORRELATION_PROVIDER,
1590
+ inject: [LogContextService],
1591
+ useFactory: (context: LogContextService): ICorrelationIdProvider => ({
1592
+ getCorrelationId: () => context.get<string>('requestId')
1593
+ })
1594
+ },
1595
+ { provide: BYMAX_UNEXPECTED_ERROR_SINK, useClass: PinoUnexpectedErrorSink }
1596
+ ],
1597
+ exports: [BYMAX_CORRELATION_PROVIDER, BYMAX_UNEXPECTED_ERROR_SINK]
1565
1598
  })
1566
1599
  export class ObservabilityModule {}
1567
1600
  ```
1568
1601
 
1569
- Every error envelope now carries the same correlation id your logs do. The
1602
+ The id exists only once nest-logger's request-id middleware runs, and
1603
+ `BymaxLoggerModule` mounts none on its own. Mount it in `main.ts`, or call
1604
+ `applyRequestIdMiddleware(consumer)` from a module's `configure()`:
1605
+
1606
+ > **Express only.** nest-logger's request-id middleware supports
1607
+ > `@nestjs/platform-express`; the Fastify adapter is unsupported, so do not
1608
+ > rely on this correlation provider there.
1609
+
1610
+ ```typescript
1611
+ import { NestFactory } from '@nestjs/core'
1612
+ import { applyAccessLog } from '@bymax-one/nest-logger'
1613
+ import { AppModule } from './app.module'
1614
+
1615
+ async function bootstrap(): Promise<void> {
1616
+ const app = await NestFactory.create(AppModule)
1617
+ applyAccessLog(app) // opens the request context that carries `requestId`
1618
+ await app.listen(3000)
1619
+ }
1620
+ void bootstrap()
1621
+ ```
1622
+
1623
+ Every error envelope now carries the same correlation id your logs do, and the
1624
+ cause of every collapsed `500` is logged with it (`PinoUnexpectedErrorSink`
1625
+ needs `@bymax-one/nest-logger` 1.4.1 or later). The
1570
1626
  same `@Global()`-module pattern is how every pluggable token in this package
1571
1627
  is overridden: `BYMAX_TIMING_SINK` and `BYMAX_HEALTH_INDICATORS` follow it
1572
1628
  identically.
@@ -1644,8 +1700,9 @@ disclose.
1644
1700
  ### An error envelope is an exfiltration surface
1645
1701
 
1646
1702
  The filter's job is to make every failure look the same to a client, so an unknown error
1647
- becomes a generic 500 whose body carries the code, the correlation id and nothing else. The
1648
- original message and stack are captured for your logger, not for the response.
1703
+ becomes a generic 500 whose body carries the fixed envelope fields and the generic message,
1704
+ and not the cause unless `exposeInternals` is on. The original message and stack go to the
1705
+ `BYMAX_UNEXPECTED_ERROR_SINK` seam when you bind one.
1649
1706
  `envelope.exposeInternals` puts them in the body and exists for local debugging — its own
1650
1707
  documentation says never to enable it in production, and it defaults to `false`.
1651
1708
 
@@ -1721,7 +1778,7 @@ the deployment nobody configured is the one most likely to be exposed.
1721
1778
  | Layer | Implementation |
1722
1779
  | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1723
1780
  | Error responses | One shape for everything; unknown errors become a generic 500 |
1724
- | Internals | Message and stack captured for logging, in the body only under `exposeInternals` (default `false`) |
1781
+ | Internals | Message and stack handed to the unexpected-error sink when bound, in the body only under `exposeInternals` (default `false`) |
1725
1782
  | Health output | The response names which indicator is down and nothing more; the reason goes to the logger. `exposeIndicatorErrors` (default `false`) puts it back in the response for debugging |
1726
1783
  | Slow indicators | Converted to `down` by the aggregator, so a probe cannot hang on one |
1727
1784
  | Correlation | Resolved through `BYMAX_CORRELATION_PROVIDER` — the app decides where the id comes from |
@@ -1737,8 +1794,9 @@ the deployment nobody configured is the one most likely to be exposed.
1737
1794
 
1738
1795
  > [!IMPORTANT]
1739
1796
  > **`exposeInternals` is a debugging switch, not a verbosity setting.** With it on,
1740
- > the body of a 500 carries the original message and stack of whatever failed —
1741
- > including anything a driver, an SDK or a template put in them.
1797
+ > the body of a 500 carries the original message and stack of whatever failed, and the
1798
+ > `details` of any 5xx `HttpException` — including anything a driver, an SDK or a
1799
+ > template put in them.
1742
1800
 
1743
1801
  ---
1744
1802
 
@@ -94,4 +94,5 @@ interface HealthResponse {
94
94
  checks: HealthCheckEntry[];
95
95
  }
96
96
 
97
- export { BYMAX_HEALTH_INDICATOR_METADATA, BymaxHealthIndicator, type HealthCheckEntry, type HealthIndicatorResult, type HealthResponse, type IHealthIndicator };
97
+ export { BYMAX_HEALTH_INDICATOR_METADATA, BymaxHealthIndicator };
98
+ export type { HealthCheckEntry, HealthIndicatorResult, HealthResponse, IHealthIndicator };
@@ -94,4 +94,5 @@ interface HealthResponse {
94
94
  checks: HealthCheckEntry[];
95
95
  }
96
96
 
97
- export { BYMAX_HEALTH_INDICATOR_METADATA, BymaxHealthIndicator, type HealthCheckEntry, type HealthIndicatorResult, type HealthResponse, type IHealthIndicator };
97
+ export { BYMAX_HEALTH_INDICATOR_METADATA, BymaxHealthIndicator };
98
+ export type { HealthCheckEntry, HealthIndicatorResult, HealthResponse, IHealthIndicator };
package/dist/index.cjs CHANGED
@@ -359,6 +359,19 @@ function extractRequestInfo(context) {
359
359
  var INTERNAL_ERROR_STATUS = 500;
360
360
  var INTERNAL_ERROR_MESSAGE = "Internal server error";
361
361
  var VALIDATION_FAILED_MESSAGE = "Validation failed";
362
+ var ABSOLUTE_FORM_PREFIX = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
363
+ function pathOf(url) {
364
+ let target = String(url);
365
+ const prefix = ABSOLUTE_FORM_PREFIX.exec(target);
366
+ if (prefix !== null) {
367
+ const afterScheme = target.slice(prefix[0].length);
368
+ const authorityEnd = afterScheme.search(/[/?#]/);
369
+ target = authorityEnd === -1 ? "" : afterScheme.slice(authorityEnd);
370
+ }
371
+ const pathEnd = target.search(/[?#]/);
372
+ const path = pathEnd === -1 ? target : target.slice(0, pathEnd);
373
+ return path === "" ? "/" : path;
374
+ }
362
375
  function resolveErrorCarrier(response) {
363
376
  if (typeof response !== "object" || response === null) {
364
377
  return void 0;
@@ -420,11 +433,16 @@ function extractHttpMessage(response, exception, carrier) {
420
433
  }
421
434
  return exception.message;
422
435
  }
436
+ var UNREPRESENTABLE_MESSAGE = "Unrepresentable thrown value";
423
437
  function buildInternalDetails(exception) {
424
- if (exception instanceof Error) {
425
- return exception.stack !== void 0 ? { message: exception.message, stack: exception.stack } : { message: exception.message };
438
+ try {
439
+ if (exception instanceof Error) {
440
+ return exception.stack !== void 0 ? { message: exception.message, stack: exception.stack } : { message: exception.message };
441
+ }
442
+ return { message: String(exception) };
443
+ } catch {
444
+ return { message: UNREPRESENTABLE_MESSAGE };
426
445
  }
427
- return { message: String(exception) };
428
446
  }
429
447
  exports.BymaxExceptionFilter = class BymaxExceptionFilter {
430
448
  /**
@@ -496,7 +514,7 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
496
514
  const traceId = this.readAnnotation(() => this.traceContext.getTraceContext()?.traceId);
497
515
  const context = {
498
516
  method: String(httpAdapter.getRequestMethod(request)),
499
- path: String(httpAdapter.getRequestUrl(request)),
517
+ path: pathOf(httpAdapter.getRequestUrl(request)),
500
518
  ...correlationId !== void 0 ? { correlationId } : {},
501
519
  ...traceId !== void 0 ? { traceId } : {}
502
520
  };
@@ -515,7 +533,7 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
515
533
  */
516
534
  buildEnvelope(exception, context, request) {
517
535
  if (exception instanceof common.HttpException) {
518
- return this.mapHttpException(exception, context);
536
+ return this.mapHttpException(exception, context, request);
519
537
  }
520
538
  const exposed = resolveExposedClientError(exception);
521
539
  if (exposed !== void 0) {
@@ -562,15 +580,16 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
562
580
  }
563
581
  /**
564
582
  * Map an `HttpException` to the envelope. A domain error passes its own code,
565
- * message and details through, whether it wrote them flat on the response or
566
- * nested under `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED`
567
- * with structured details; everything else derives its code from the status.
583
+ * message and details through (a 5xx's details are withheld by default in
584
+ * `toEnvelope`), whether it wrote them flat on the response or nested under
585
+ * `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED` with
586
+ * structured details; everything else derives its code from the status.
568
587
  *
569
588
  * @param exception - The HTTP exception to format.
570
589
  * @param context - The neutral request context.
571
590
  * @returns The formatted envelope.
572
591
  */
573
- mapHttpException(exception, context) {
592
+ mapHttpException(exception, context, request) {
574
593
  const status = exception.getStatus();
575
594
  const response = exception.getResponse();
576
595
  const carrier = resolveErrorCarrier(response);
@@ -592,10 +611,12 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
592
611
  toValidationDetails(response.message)
593
612
  );
594
613
  }
614
+ const message = extractHttpMessage(response, exception);
615
+ const rawUrl = String(this.adapterHost.httpAdapter.getRequestUrl(request));
595
616
  return this.toEnvelope(
596
617
  status,
597
618
  codeForStatus(status),
598
- extractHttpMessage(response, exception),
619
+ message === `Cannot ${context.method} ${rawUrl}` ? `Cannot ${context.method} ${context.path}` : message,
599
620
  context
600
621
  );
601
622
  }
@@ -620,7 +641,8 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
620
641
  }
621
642
  /**
622
643
  * Assemble the envelope through the pure builder, threading the shared clock
623
- * and omitting absent optional details.
644
+ * and omitting absent optional details. A server-error status carries none
645
+ * unless `exposeInternals` is on, whichever mapping rule produced them.
624
646
  *
625
647
  * @param statusCode - HTTP status for the envelope.
626
648
  * @param code - Stable machine-readable code.
@@ -630,13 +652,14 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
630
652
  * @returns The formatted envelope.
631
653
  */
632
654
  toEnvelope(statusCode, code, message, context, details) {
655
+ const withheld = statusCode >= CLIENT_ERROR_MAX2 && !this.options.envelope.exposeInternals;
633
656
  return buildErrorEnvelope({
634
657
  statusCode,
635
658
  code,
636
659
  message,
637
660
  path: context.path,
638
661
  now: this.now,
639
- ...details !== void 0 ? { details } : {},
662
+ ...details !== void 0 && !withheld ? { details } : {},
640
663
  // Stryker disable next-line ConditionalExpression: equivalent — the always-spread form hands the builder `correlationId: undefined`, and `buildErrorEnvelope` re-guards `input.correlationId !== undefined` and omits an undefined value, so the emitted envelope is byte-for-byte identical whether or not a correlation id is present.
641
664
  ...context.correlationId !== void 0 ? { correlationId: context.correlationId } : {},
642
665
  // Gated separately from the context above: the trace id reaches the
package/dist/index.d.cts CHANGED
@@ -10,7 +10,8 @@ interface EnvelopeOptions {
10
10
  enabled?: boolean;
11
11
  /**
12
12
  * Include the original message and stack of unknown errors in the envelope
13
- * details. Never enable in production. Default: `false`.
13
+ * details, and keep the `details` of a 5xx `HttpException`, which are
14
+ * otherwise withheld from the body. Never enable in production. Default: `false`.
14
15
  */
15
16
  exposeInternals?: boolean;
16
17
  }
@@ -839,9 +840,10 @@ declare class BymaxExceptionFilter implements ExceptionFilter {
839
840
  private reportUnexpected;
840
841
  /**
841
842
  * Map an `HttpException` to the envelope. A domain error passes its own code,
842
- * message and details through, whether it wrote them flat on the response or
843
- * nested under `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED`
844
- * with structured details; everything else derives its code from the status.
843
+ * message and details through (a 5xx's details are withheld by default in
844
+ * `toEnvelope`), whether it wrote them flat on the response or nested under
845
+ * `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED` with
846
+ * structured details; everything else derives its code from the status.
845
847
  *
846
848
  * @param exception - The HTTP exception to format.
847
849
  * @param context - The neutral request context.
@@ -860,7 +862,8 @@ declare class BymaxExceptionFilter implements ExceptionFilter {
860
862
  private mapUnknown;
861
863
  /**
862
864
  * Assemble the envelope through the pure builder, threading the shared clock
863
- * and omitting absent optional details.
865
+ * and omitting absent optional details. A server-error status carries none
866
+ * unless `exposeInternals` is on, whichever mapping rule produced them.
864
867
  *
865
868
  * @param statusCode - HTTP status for the envelope.
866
869
  * @param code - Stable machine-readable code.
@@ -1293,4 +1296,5 @@ declare const BYMAX_GATEWAY_TIMEOUT = "BYMAX_GATEWAY_TIMEOUT";
1293
1296
  */
1294
1297
  declare function codeForStatus(status: number): string;
1295
1298
 
1296
- export { BYMAX_BAD_GATEWAY, BYMAX_BAD_REQUEST, BYMAX_CLIENT_ERROR, BYMAX_CONFLICT, BYMAX_CORE_OPTIONS, BYMAX_CORRELATION_PROVIDER, BYMAX_FORBIDDEN, BYMAX_GATEWAY_TIMEOUT, BYMAX_HEALTH_INDICATORS, BYMAX_HEALTH_TRANSITION_SINK, BYMAX_INTERNAL_ERROR, BYMAX_METRICS_REGISTRY, BYMAX_NOT_FOUND, BYMAX_NOT_IMPLEMENTED, BYMAX_PAYLOAD_TOO_LARGE, BYMAX_SERVICE_UNAVAILABLE, BYMAX_TIMING_SINK, BYMAX_TOO_MANY_REQUESTS, BYMAX_TRACE_CONTEXT, BYMAX_UNAUTHORIZED, BYMAX_UNEXPECTED_ERROR_SINK, BYMAX_UNPROCESSABLE_ENTITY, BYMAX_UNSUPPORTED_MEDIA_TYPE, BYMAX_VALIDATION_FAILED, type BuildErrorEnvelopeInput, BymaxCoreModule, type BymaxCoreModuleOptions, BymaxExceptionFilter, BymaxTimingMiddleware, type EnvelopeOptions, type ErrorDetails, type ErrorEnvelope, type FilterErrorContext, type HealthOptions, type ICorrelationIdProvider, type ITimingSink, type ITraceContextProvider, type IUnexpectedErrorSink, type MetricsOptions, type OpenApiHttpMethod, type OpenApiOperationIdFactory, type OpenApiOperationKey, type OpenApiOptions, type OpenApiSecurityRequirement, type OpenApiSecurityScheme, type OpenApiServerDescriptor, type OperationSecurityMap, type RequestTimingSample, type ResolvedCoreOptions, type TelemetryOptions, TimingInterceptor, type TimingOptions, type TraceContext, UNMATCHED_ROUTE, type UnexpectedErrorReport, buildErrorEnvelope, codeForStatus };
1299
+ export { BYMAX_BAD_GATEWAY, BYMAX_BAD_REQUEST, BYMAX_CLIENT_ERROR, BYMAX_CONFLICT, BYMAX_CORE_OPTIONS, BYMAX_CORRELATION_PROVIDER, BYMAX_FORBIDDEN, BYMAX_GATEWAY_TIMEOUT, BYMAX_HEALTH_INDICATORS, BYMAX_HEALTH_TRANSITION_SINK, BYMAX_INTERNAL_ERROR, BYMAX_METRICS_REGISTRY, BYMAX_NOT_FOUND, BYMAX_NOT_IMPLEMENTED, BYMAX_PAYLOAD_TOO_LARGE, BYMAX_SERVICE_UNAVAILABLE, BYMAX_TIMING_SINK, BYMAX_TOO_MANY_REQUESTS, BYMAX_TRACE_CONTEXT, BYMAX_UNAUTHORIZED, BYMAX_UNEXPECTED_ERROR_SINK, BYMAX_UNPROCESSABLE_ENTITY, BYMAX_UNSUPPORTED_MEDIA_TYPE, BYMAX_VALIDATION_FAILED, BymaxCoreModule, BymaxExceptionFilter, BymaxTimingMiddleware, TimingInterceptor, UNMATCHED_ROUTE, buildErrorEnvelope, codeForStatus };
1300
+ export type { BuildErrorEnvelopeInput, BymaxCoreModuleOptions, EnvelopeOptions, ErrorDetails, ErrorEnvelope, FilterErrorContext, HealthOptions, ICorrelationIdProvider, ITimingSink, ITraceContextProvider, IUnexpectedErrorSink, MetricsOptions, OpenApiHttpMethod, OpenApiOperationIdFactory, OpenApiOperationKey, OpenApiOptions, OpenApiSecurityRequirement, OpenApiSecurityScheme, OpenApiServerDescriptor, OperationSecurityMap, RequestTimingSample, ResolvedCoreOptions, TelemetryOptions, TimingOptions, TraceContext, UnexpectedErrorReport };
package/dist/index.d.ts CHANGED
@@ -10,7 +10,8 @@ interface EnvelopeOptions {
10
10
  enabled?: boolean;
11
11
  /**
12
12
  * Include the original message and stack of unknown errors in the envelope
13
- * details. Never enable in production. Default: `false`.
13
+ * details, and keep the `details` of a 5xx `HttpException`, which are
14
+ * otherwise withheld from the body. Never enable in production. Default: `false`.
14
15
  */
15
16
  exposeInternals?: boolean;
16
17
  }
@@ -839,9 +840,10 @@ declare class BymaxExceptionFilter implements ExceptionFilter {
839
840
  private reportUnexpected;
840
841
  /**
841
842
  * Map an `HttpException` to the envelope. A domain error passes its own code,
842
- * message and details through, whether it wrote them flat on the response or
843
- * nested under `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED`
844
- * with structured details; everything else derives its code from the status.
843
+ * message and details through (a 5xx's details are withheld by default in
844
+ * `toEnvelope`), whether it wrote them flat on the response or nested under
845
+ * `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED` with
846
+ * structured details; everything else derives its code from the status.
845
847
  *
846
848
  * @param exception - The HTTP exception to format.
847
849
  * @param context - The neutral request context.
@@ -860,7 +862,8 @@ declare class BymaxExceptionFilter implements ExceptionFilter {
860
862
  private mapUnknown;
861
863
  /**
862
864
  * Assemble the envelope through the pure builder, threading the shared clock
863
- * and omitting absent optional details.
865
+ * and omitting absent optional details. A server-error status carries none
866
+ * unless `exposeInternals` is on, whichever mapping rule produced them.
864
867
  *
865
868
  * @param statusCode - HTTP status for the envelope.
866
869
  * @param code - Stable machine-readable code.
@@ -1293,4 +1296,5 @@ declare const BYMAX_GATEWAY_TIMEOUT = "BYMAX_GATEWAY_TIMEOUT";
1293
1296
  */
1294
1297
  declare function codeForStatus(status: number): string;
1295
1298
 
1296
- export { BYMAX_BAD_GATEWAY, BYMAX_BAD_REQUEST, BYMAX_CLIENT_ERROR, BYMAX_CONFLICT, BYMAX_CORE_OPTIONS, BYMAX_CORRELATION_PROVIDER, BYMAX_FORBIDDEN, BYMAX_GATEWAY_TIMEOUT, BYMAX_HEALTH_INDICATORS, BYMAX_HEALTH_TRANSITION_SINK, BYMAX_INTERNAL_ERROR, BYMAX_METRICS_REGISTRY, BYMAX_NOT_FOUND, BYMAX_NOT_IMPLEMENTED, BYMAX_PAYLOAD_TOO_LARGE, BYMAX_SERVICE_UNAVAILABLE, BYMAX_TIMING_SINK, BYMAX_TOO_MANY_REQUESTS, BYMAX_TRACE_CONTEXT, BYMAX_UNAUTHORIZED, BYMAX_UNEXPECTED_ERROR_SINK, BYMAX_UNPROCESSABLE_ENTITY, BYMAX_UNSUPPORTED_MEDIA_TYPE, BYMAX_VALIDATION_FAILED, type BuildErrorEnvelopeInput, BymaxCoreModule, type BymaxCoreModuleOptions, BymaxExceptionFilter, BymaxTimingMiddleware, type EnvelopeOptions, type ErrorDetails, type ErrorEnvelope, type FilterErrorContext, type HealthOptions, type ICorrelationIdProvider, type ITimingSink, type ITraceContextProvider, type IUnexpectedErrorSink, type MetricsOptions, type OpenApiHttpMethod, type OpenApiOperationIdFactory, type OpenApiOperationKey, type OpenApiOptions, type OpenApiSecurityRequirement, type OpenApiSecurityScheme, type OpenApiServerDescriptor, type OperationSecurityMap, type RequestTimingSample, type ResolvedCoreOptions, type TelemetryOptions, TimingInterceptor, type TimingOptions, type TraceContext, UNMATCHED_ROUTE, type UnexpectedErrorReport, buildErrorEnvelope, codeForStatus };
1299
+ export { BYMAX_BAD_GATEWAY, BYMAX_BAD_REQUEST, BYMAX_CLIENT_ERROR, BYMAX_CONFLICT, BYMAX_CORE_OPTIONS, BYMAX_CORRELATION_PROVIDER, BYMAX_FORBIDDEN, BYMAX_GATEWAY_TIMEOUT, BYMAX_HEALTH_INDICATORS, BYMAX_HEALTH_TRANSITION_SINK, BYMAX_INTERNAL_ERROR, BYMAX_METRICS_REGISTRY, BYMAX_NOT_FOUND, BYMAX_NOT_IMPLEMENTED, BYMAX_PAYLOAD_TOO_LARGE, BYMAX_SERVICE_UNAVAILABLE, BYMAX_TIMING_SINK, BYMAX_TOO_MANY_REQUESTS, BYMAX_TRACE_CONTEXT, BYMAX_UNAUTHORIZED, BYMAX_UNEXPECTED_ERROR_SINK, BYMAX_UNPROCESSABLE_ENTITY, BYMAX_UNSUPPORTED_MEDIA_TYPE, BYMAX_VALIDATION_FAILED, BymaxCoreModule, BymaxExceptionFilter, BymaxTimingMiddleware, TimingInterceptor, UNMATCHED_ROUTE, buildErrorEnvelope, codeForStatus };
1300
+ export type { BuildErrorEnvelopeInput, BymaxCoreModuleOptions, EnvelopeOptions, ErrorDetails, ErrorEnvelope, FilterErrorContext, HealthOptions, ICorrelationIdProvider, ITimingSink, ITraceContextProvider, IUnexpectedErrorSink, MetricsOptions, OpenApiHttpMethod, OpenApiOperationIdFactory, OpenApiOperationKey, OpenApiOptions, OpenApiSecurityRequirement, OpenApiSecurityScheme, OpenApiServerDescriptor, OperationSecurityMap, RequestTimingSample, ResolvedCoreOptions, TelemetryOptions, TimingOptions, TraceContext, UnexpectedErrorReport };
package/dist/index.mjs CHANGED
@@ -357,6 +357,19 @@ function extractRequestInfo(context) {
357
357
  var INTERNAL_ERROR_STATUS = 500;
358
358
  var INTERNAL_ERROR_MESSAGE = "Internal server error";
359
359
  var VALIDATION_FAILED_MESSAGE = "Validation failed";
360
+ var ABSOLUTE_FORM_PREFIX = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
361
+ function pathOf(url) {
362
+ let target = String(url);
363
+ const prefix = ABSOLUTE_FORM_PREFIX.exec(target);
364
+ if (prefix !== null) {
365
+ const afterScheme = target.slice(prefix[0].length);
366
+ const authorityEnd = afterScheme.search(/[/?#]/);
367
+ target = authorityEnd === -1 ? "" : afterScheme.slice(authorityEnd);
368
+ }
369
+ const pathEnd = target.search(/[?#]/);
370
+ const path = pathEnd === -1 ? target : target.slice(0, pathEnd);
371
+ return path === "" ? "/" : path;
372
+ }
360
373
  function resolveErrorCarrier(response) {
361
374
  if (typeof response !== "object" || response === null) {
362
375
  return void 0;
@@ -418,11 +431,16 @@ function extractHttpMessage(response, exception, carrier) {
418
431
  }
419
432
  return exception.message;
420
433
  }
434
+ var UNREPRESENTABLE_MESSAGE = "Unrepresentable thrown value";
421
435
  function buildInternalDetails(exception) {
422
- if (exception instanceof Error) {
423
- return exception.stack !== void 0 ? { message: exception.message, stack: exception.stack } : { message: exception.message };
436
+ try {
437
+ if (exception instanceof Error) {
438
+ return exception.stack !== void 0 ? { message: exception.message, stack: exception.stack } : { message: exception.message };
439
+ }
440
+ return { message: String(exception) };
441
+ } catch {
442
+ return { message: UNREPRESENTABLE_MESSAGE };
424
443
  }
425
- return { message: String(exception) };
426
444
  }
427
445
  var BymaxExceptionFilter = class {
428
446
  /**
@@ -494,7 +512,7 @@ var BymaxExceptionFilter = class {
494
512
  const traceId = this.readAnnotation(() => this.traceContext.getTraceContext()?.traceId);
495
513
  const context = {
496
514
  method: String(httpAdapter.getRequestMethod(request)),
497
- path: String(httpAdapter.getRequestUrl(request)),
515
+ path: pathOf(httpAdapter.getRequestUrl(request)),
498
516
  ...correlationId !== void 0 ? { correlationId } : {},
499
517
  ...traceId !== void 0 ? { traceId } : {}
500
518
  };
@@ -513,7 +531,7 @@ var BymaxExceptionFilter = class {
513
531
  */
514
532
  buildEnvelope(exception, context, request) {
515
533
  if (exception instanceof HttpException) {
516
- return this.mapHttpException(exception, context);
534
+ return this.mapHttpException(exception, context, request);
517
535
  }
518
536
  const exposed = resolveExposedClientError(exception);
519
537
  if (exposed !== void 0) {
@@ -560,15 +578,16 @@ var BymaxExceptionFilter = class {
560
578
  }
561
579
  /**
562
580
  * Map an `HttpException` to the envelope. A domain error passes its own code,
563
- * message and details through, whether it wrote them flat on the response or
564
- * nested under `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED`
565
- * with structured details; everything else derives its code from the status.
581
+ * message and details through (a 5xx's details are withheld by default in
582
+ * `toEnvelope`), whether it wrote them flat on the response or nested under
583
+ * `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED` with
584
+ * structured details; everything else derives its code from the status.
566
585
  *
567
586
  * @param exception - The HTTP exception to format.
568
587
  * @param context - The neutral request context.
569
588
  * @returns The formatted envelope.
570
589
  */
571
- mapHttpException(exception, context) {
590
+ mapHttpException(exception, context, request) {
572
591
  const status = exception.getStatus();
573
592
  const response = exception.getResponse();
574
593
  const carrier = resolveErrorCarrier(response);
@@ -590,10 +609,12 @@ var BymaxExceptionFilter = class {
590
609
  toValidationDetails(response.message)
591
610
  );
592
611
  }
612
+ const message = extractHttpMessage(response, exception);
613
+ const rawUrl = String(this.adapterHost.httpAdapter.getRequestUrl(request));
593
614
  return this.toEnvelope(
594
615
  status,
595
616
  codeForStatus(status),
596
- extractHttpMessage(response, exception),
617
+ message === `Cannot ${context.method} ${rawUrl}` ? `Cannot ${context.method} ${context.path}` : message,
597
618
  context
598
619
  );
599
620
  }
@@ -618,7 +639,8 @@ var BymaxExceptionFilter = class {
618
639
  }
619
640
  /**
620
641
  * Assemble the envelope through the pure builder, threading the shared clock
621
- * and omitting absent optional details.
642
+ * and omitting absent optional details. A server-error status carries none
643
+ * unless `exposeInternals` is on, whichever mapping rule produced them.
622
644
  *
623
645
  * @param statusCode - HTTP status for the envelope.
624
646
  * @param code - Stable machine-readable code.
@@ -628,13 +650,14 @@ var BymaxExceptionFilter = class {
628
650
  * @returns The formatted envelope.
629
651
  */
630
652
  toEnvelope(statusCode, code, message, context, details) {
653
+ const withheld = statusCode >= CLIENT_ERROR_MAX2 && !this.options.envelope.exposeInternals;
631
654
  return buildErrorEnvelope({
632
655
  statusCode,
633
656
  code,
634
657
  message,
635
658
  path: context.path,
636
659
  now: this.now,
637
- ...details !== void 0 ? { details } : {},
660
+ ...details !== void 0 && !withheld ? { details } : {},
638
661
  // Stryker disable next-line ConditionalExpression: equivalent — the always-spread form hands the builder `correlationId: undefined`, and `buildErrorEnvelope` re-guards `input.correlationId !== undefined` and omits an undefined value, so the emitted envelope is byte-for-byte identical whether or not a correlation id is present.
639
662
  ...context.correlationId !== void 0 ? { correlationId: context.correlationId } : {},
640
663
  // Gated separately from the context above: the trace id reaches the
@@ -54,4 +54,5 @@ declare const BYMAX_METRICS_CONTRIBUTOR_METADATA = "bymax-one:metrics-contributo
54
54
  */
55
55
  declare function BymaxMetricsContributor(): CustomDecorator<string>;
56
56
 
57
- export { BYMAX_METRICS_CONTRIBUTOR_METADATA, BymaxMetricsContributor, type IMetricsContributor, type MetricsRegistry };
57
+ export { BYMAX_METRICS_CONTRIBUTOR_METADATA, BymaxMetricsContributor };
58
+ export type { IMetricsContributor, MetricsRegistry };
@@ -54,4 +54,5 @@ declare const BYMAX_METRICS_CONTRIBUTOR_METADATA = "bymax-one:metrics-contributo
54
54
  */
55
55
  declare function BymaxMetricsContributor(): CustomDecorator<string>;
56
56
 
57
- export { BYMAX_METRICS_CONTRIBUTOR_METADATA, BymaxMetricsContributor, type IMetricsContributor, type MetricsRegistry };
57
+ export { BYMAX_METRICS_CONTRIBUTOR_METADATA, BymaxMetricsContributor };
58
+ export type { IMetricsContributor, MetricsRegistry };
@@ -737,7 +737,7 @@ function warnDiscardedComponents(logger, discarded) {
737
737
  const elided = discarded.length - MAX_WARNED_OPERATIONS;
738
738
  const listed = discarded.slice(0, MAX_WARNED_OPERATIONS).map(({ contributor, member, name }) => `${contributor} -> ${member}.${name}`).join(", ");
739
739
  logger.warn(
740
- `the served OpenAPI document already defines ${discarded.length} component entry(ies) a library also contributes: ${listed}${elided > 0 ? `, and ${elided} more` : ""}. The contributed version is not served, so a client generated from this document will not match that library's own types. What holds the name may be the application's own declaration, a core schema under openapi.includeCoreSchemas, or a contributor that sorted earlier \u2014 read the document before deciding what to remove.`
740
+ `the served OpenAPI document already defines ${discarded.length} component entry(ies) a library also contributes: ${listed}${elided > 0 ? `, and ${elided} more` : ""}. The contributed version is not served, so where the two differ a client generated from this document will not match that library's own types. What holds the name may be the application's own declaration, a core schema under openapi.includeCoreSchemas, or a contributor that sorted earlier \u2014 read the document before deciding what to remove.`
741
741
  );
742
742
  }
743
743
  function buildConfig(builder, options) {
@@ -240,4 +240,5 @@ interface OpenApiMountOutcome {
240
240
  */
241
241
  declare function applyBymaxOpenApi(app: INestApplication): Promise<OpenApiMountOutcome>;
242
242
 
243
- export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, type DiscardedComponent, type IOpenApiContributor, type OpenApiFragment, type OpenApiFragmentObject, type OpenApiHandlerKey, type OpenApiMountOutcome, type OpenApiSkipReason, applyBymaxOpenApi };
243
+ export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, applyBymaxOpenApi };
244
+ export type { DiscardedComponent, IOpenApiContributor, OpenApiFragment, OpenApiFragmentObject, OpenApiHandlerKey, OpenApiMountOutcome, OpenApiSkipReason };
@@ -240,4 +240,5 @@ interface OpenApiMountOutcome {
240
240
  */
241
241
  declare function applyBymaxOpenApi(app: INestApplication): Promise<OpenApiMountOutcome>;
242
242
 
243
- export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, type DiscardedComponent, type IOpenApiContributor, type OpenApiFragment, type OpenApiFragmentObject, type OpenApiHandlerKey, type OpenApiMountOutcome, type OpenApiSkipReason, applyBymaxOpenApi };
243
+ export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, applyBymaxOpenApi };
244
+ export type { DiscardedComponent, IOpenApiContributor, OpenApiFragment, OpenApiFragmentObject, OpenApiHandlerKey, OpenApiMountOutcome, OpenApiSkipReason };
@@ -735,7 +735,7 @@ function warnDiscardedComponents(logger, discarded) {
735
735
  const elided = discarded.length - MAX_WARNED_OPERATIONS;
736
736
  const listed = discarded.slice(0, MAX_WARNED_OPERATIONS).map(({ contributor, member, name }) => `${contributor} -> ${member}.${name}`).join(", ");
737
737
  logger.warn(
738
- `the served OpenAPI document already defines ${discarded.length} component entry(ies) a library also contributes: ${listed}${elided > 0 ? `, and ${elided} more` : ""}. The contributed version is not served, so a client generated from this document will not match that library's own types. What holds the name may be the application's own declaration, a core schema under openapi.includeCoreSchemas, or a contributor that sorted earlier \u2014 read the document before deciding what to remove.`
738
+ `the served OpenAPI document already defines ${discarded.length} component entry(ies) a library also contributes: ${listed}${elided > 0 ? `, and ${elided} more` : ""}. The contributed version is not served, so where the two differ a client generated from this document will not match that library's own types. What holds the name may be the application's own declaration, a core schema under openapi.includeCoreSchemas, or a contributor that sorted earlier \u2014 read the document before deciding what to remove.`
739
739
  );
740
740
  }
741
741
  function buildConfig(builder, options) {
@@ -175,4 +175,5 @@ declare function normalizeCursorQuery(raw: {
175
175
  */
176
176
  declare function buildCursorResult<T>(items: T[], limit: number, toCursor: (lastItem: T) => Record<string, string | number>): CursorResult<T>;
177
177
 
178
- export { type CursorQuery, type CursorResult, type PageMeta, type PageQuery, type PageResult, buildCursorResult, buildPageResult, decodeCursor, encodeCursor, normalizeCursorQuery, normalizePageQuery };
178
+ export { buildCursorResult, buildPageResult, decodeCursor, encodeCursor, normalizeCursorQuery, normalizePageQuery };
179
+ export type { CursorQuery, CursorResult, PageMeta, PageQuery, PageResult };
@@ -175,4 +175,5 @@ declare function normalizeCursorQuery(raw: {
175
175
  */
176
176
  declare function buildCursorResult<T>(items: T[], limit: number, toCursor: (lastItem: T) => Record<string, string | number>): CursorResult<T>;
177
177
 
178
- export { type CursorQuery, type CursorResult, type PageMeta, type PageQuery, type PageResult, buildCursorResult, buildPageResult, decodeCursor, encodeCursor, normalizeCursorQuery, normalizePageQuery };
178
+ export { buildCursorResult, buildPageResult, decodeCursor, encodeCursor, normalizeCursorQuery, normalizePageQuery };
179
+ export type { CursorQuery, CursorResult, PageMeta, PageQuery, PageResult };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bymax-one/nest-core",
3
- "version": "1.6.2",
4
- "description": "Zero-dependency NestJS 11 application foundation kit: error-envelope exception filter, request-timing interceptor, pagination helpers, health endpoints with indicator discovery, an optional Prometheus metrics endpoint with a contribution contract, OpenAPI documents in development, and OpenTelemetry trace correlation.",
3
+ "version": "1.6.4",
4
+ "description": "Zero-dependency NestJS 11 application foundation kit: error-envelope exception filter, request-timing middleware, pagination helpers, health endpoints with indicator discovery, an optional Prometheus metrics endpoint with a contribution contract, OpenAPI documents in development, and OpenTelemetry trace correlation.",
5
5
  "author": "Bymax One <support@bymax.one>",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/bymaxone/nest-core#readme",
@@ -140,7 +140,7 @@
140
140
  "@nestjs/swagger": "^11.4.6",
141
141
  "@nestjs/testing": "^11.1.20",
142
142
  "@opentelemetry/api": "^1.9.1",
143
- "@opentelemetry/context-async-hooks": "^2.10.0",
143
+ "@opentelemetry/context-async-hooks": "^3.0.0",
144
144
  "@stryker-mutator/core": "^9",
145
145
  "@stryker-mutator/jest-runner": "^9",
146
146
  "@stryker-mutator/typescript-checker": "^9",
@@ -168,7 +168,7 @@
168
168
  "supertest": "^7.2.2",
169
169
  "ts-jest": "^29.4.9",
170
170
  "ts-node": "^10.9.2",
171
- "tsup": "^8.5.1",
171
+ "tsup": "^9.0.0",
172
172
  "typescript": "^5.9.3"
173
173
  },
174
174
  "keywords": [