@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 +129 -1
- package/README.md +88 -30
- package/dist/health/index.d.cts +2 -1
- package/dist/health/index.d.ts +2 -1
- package/dist/index.cjs +35 -12
- package/dist/index.d.cts +10 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.mjs +35 -12
- package/dist/metrics/index.d.cts +2 -1
- package/dist/metrics/index.d.ts +2 -1
- package/dist/openapi/index.cjs +1 -1
- package/dist/openapi/index.d.cts +2 -1
- package/dist/openapi/index.d.ts +2 -1
- package/dist/openapi/index.mjs +1 -1
- package/dist/pagination/index.d.cts +2 -1
- package/dist/pagination/index.d.ts +2 -1
- package/package.json +4 -4
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
|
-
[
|
|
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
|
|
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`
|
|
1550
|
-
error responses
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
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 {
|
|
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: [
|
|
1563
|
-
providers: [
|
|
1564
|
-
|
|
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
|
-
|
|
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
|
|
1648
|
-
|
|
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
|
|
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
|
|
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
|
|
package/dist/health/index.d.cts
CHANGED
|
@@ -94,4 +94,5 @@ interface HealthResponse {
|
|
|
94
94
|
checks: HealthCheckEntry[];
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
export { BYMAX_HEALTH_INDICATOR_METADATA, BymaxHealthIndicator
|
|
97
|
+
export { BYMAX_HEALTH_INDICATOR_METADATA, BymaxHealthIndicator };
|
|
98
|
+
export type { HealthCheckEntry, HealthIndicatorResult, HealthResponse, IHealthIndicator };
|
package/dist/health/index.d.ts
CHANGED
|
@@ -94,4 +94,5 @@ interface HealthResponse {
|
|
|
94
94
|
checks: HealthCheckEntry[];
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
export { BYMAX_HEALTH_INDICATOR_METADATA, BymaxHealthIndicator
|
|
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
|
-
|
|
425
|
-
|
|
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:
|
|
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
|
|
566
|
-
*
|
|
567
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
843
|
-
*
|
|
844
|
-
*
|
|
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,
|
|
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
|
|
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
|
|
843
|
-
*
|
|
844
|
-
*
|
|
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,
|
|
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
|
-
|
|
423
|
-
|
|
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:
|
|
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
|
|
564
|
-
*
|
|
565
|
-
*
|
|
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
|
-
|
|
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
|
package/dist/metrics/index.d.cts
CHANGED
|
@@ -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
|
|
57
|
+
export { BYMAX_METRICS_CONTRIBUTOR_METADATA, BymaxMetricsContributor };
|
|
58
|
+
export type { IMetricsContributor, MetricsRegistry };
|
package/dist/metrics/index.d.ts
CHANGED
|
@@ -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
|
|
57
|
+
export { BYMAX_METRICS_CONTRIBUTOR_METADATA, BymaxMetricsContributor };
|
|
58
|
+
export type { IMetricsContributor, MetricsRegistry };
|
package/dist/openapi/index.cjs
CHANGED
|
@@ -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) {
|
package/dist/openapi/index.d.cts
CHANGED
|
@@ -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,
|
|
243
|
+
export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, applyBymaxOpenApi };
|
|
244
|
+
export type { DiscardedComponent, IOpenApiContributor, OpenApiFragment, OpenApiFragmentObject, OpenApiHandlerKey, OpenApiMountOutcome, OpenApiSkipReason };
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -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,
|
|
243
|
+
export { BYMAX_OPENAPI_CONTRACT_VERSION, BYMAX_OPENAPI_CONTRIBUTOR_METADATA, BymaxOpenApiContributor, applyBymaxOpenApi };
|
|
244
|
+
export type { DiscardedComponent, IOpenApiContributor, OpenApiFragment, OpenApiFragmentObject, OpenApiHandlerKey, OpenApiMountOutcome, OpenApiSkipReason };
|
package/dist/openapi/index.mjs
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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.
|
|
4
|
-
"description": "Zero-dependency NestJS 11 application foundation kit: error-envelope exception filter, request-timing
|
|
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": "^
|
|
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": "^
|
|
171
|
+
"tsup": "^9.0.0",
|
|
172
172
|
"typescript": "^5.9.3"
|
|
173
173
|
},
|
|
174
174
|
"keywords": [
|