@bymax-one/nest-core 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +183 -2
- package/README.md +272 -36
- package/dist/index.cjs +335 -161
- package/dist/index.d.cts +242 -16
- package/dist/index.d.ts +242 -16
- package/dist/index.mjs +336 -163
- package/dist/openapi/index.cjs +240 -45
- package/dist/openapi/index.d.cts +147 -2
- package/dist/openapi/index.d.ts +147 -2
- package/dist/openapi/index.mjs +240 -48
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,7 +11,186 @@ heading here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
-
## [1.
|
|
14
|
+
## [1.5.0] - 2026-08-15
|
|
15
|
+
|
|
16
|
+
An OpenAPI document could stop requiring credentials without anything saying
|
|
17
|
+
so. Deleting a document-level `security` default — typically alongside the
|
|
18
|
+
per-operation entries a library has taken over describing — leaves every route
|
|
19
|
+
the backend itself owns with no requirement from any source, and the document
|
|
20
|
+
stays valid, no requirement dangles, and the runtime still answers `401`. The
|
|
21
|
+
only observable change is that a client generated from the document stops
|
|
22
|
+
sending credentials.
|
|
23
|
+
|
|
24
|
+
**Apply to a derived backend:** bump the dependency. No code change is needed.
|
|
25
|
+
If the boot log now names operations, they are the ones a generated client will
|
|
26
|
+
call without credentials — set `openapi.security`, or mark each public with an
|
|
27
|
+
explicit `[]` in `openapi.operationSecurity`.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **The document build warns when an operation ends up requiring no credential
|
|
32
|
+
at all.** Deleting a document-level `security` default — typically alongside
|
|
33
|
+
the per-operation entries a library has taken over describing — leaves every
|
|
34
|
+
route the backend itself owns with no requirement from any source. Nothing
|
|
35
|
+
catches it today: the document is valid, no requirement dangles so
|
|
36
|
+
`assertSchemesDeclared` is satisfied, the runtime still answers `401` so a
|
|
37
|
+
status-code probe finds nothing, and a consumer's document test stays green if
|
|
38
|
+
it asserts only the operations it enumerated. The only observable change is
|
|
39
|
+
that a client generated from the document sends no credentials.
|
|
40
|
+
`applyBymaxOpenApi` now emits one warning per build naming the affected
|
|
41
|
+
operations, capped at ten with a count of the rest.
|
|
42
|
+
|
|
43
|
+
It warns and never throws — an API that is public on purpose is legitimate —
|
|
44
|
+
and the trigger is narrow so the line stays worth reading: only when the
|
|
45
|
+
document declares no top-level `security`, **and** at least one other
|
|
46
|
+
operation does state a requirement, **and** the operation is not one of the
|
|
47
|
+
three this package registers. An explicit `[]` — from `operationSecurity`, a
|
|
48
|
+
decorator, or a library's fragment — states the intent and stops the report.
|
|
49
|
+
The known limit is documented rather than closed: a document with nothing
|
|
50
|
+
explicit anywhere is indistinguishable from an API that is public on purpose,
|
|
51
|
+
so removing _every_ requirement at once is not warned. Render the document
|
|
52
|
+
with and without your libraries and diff the operations you mount.
|
|
53
|
+
|
|
54
|
+
## [1.4.0] - 2026-08-13
|
|
55
|
+
|
|
56
|
+
HTTP metrics were blind to every request that did not reach a handler. Nest runs
|
|
57
|
+
**middleware → guards → interceptors → pipes → handler**, and the recorder was an
|
|
58
|
+
interceptor, so authentication failures, authorization failures, throttled
|
|
59
|
+
requests and unknown paths were never counted. Measured on a running
|
|
60
|
+
application, three requests — a handler success, a guard rejection, an unknown
|
|
61
|
+
path — produced **one** sample. A deployment could be under a credential-stuffing
|
|
62
|
+
run, a privilege probe or route enumeration with a flat error graph, which makes
|
|
63
|
+
this a security fix rather than an observability improvement.
|
|
64
|
+
|
|
65
|
+
**Apply to a derived backend:** bump the dependency. No code change is needed.
|
|
66
|
+
Expect new `401`/`403`/`429` series on routes that already existed, a new
|
|
67
|
+
`route="<unmatched>"` series for `404`s, and a **lower success rate** — the
|
|
68
|
+
denominator finally includes the rejections. Existing `status_code="200"` series
|
|
69
|
+
keep their values.
|
|
70
|
+
|
|
71
|
+
### Security
|
|
72
|
+
|
|
73
|
+
- **Requests rejected before a handler are now counted.** The recorder moved
|
|
74
|
+
from `APP_INTERCEPTOR` to middleware (`BymaxTimingMiddleware`), applied to
|
|
75
|
+
every route when `timing.enabled` is `true`. Nest runs middleware, then
|
|
76
|
+
guards, then interceptors: a request a guard rejects never reached
|
|
77
|
+
`intercept()`, and a request matching no route never reached a controller.
|
|
78
|
+
Measured on a real application, three requests — a handler success, a guard
|
|
79
|
+
rejection and an unknown path — produced exactly **one** sample. `401`, `403`,
|
|
80
|
+
`429` and `404` were all invisible, which is to say a deployment could be under
|
|
81
|
+
a credential-stuffing run, a privilege probe or route enumeration with a flat
|
|
82
|
+
error graph. All six cases are now counted, and the sample is emitted on the
|
|
83
|
+
response's `'close'` event rather than `'finish'`, so a client that hangs up
|
|
84
|
+
mid-request — what a scanner does — is counted too.
|
|
85
|
+
- **The root path is recorded.** On Express the middleware is mounted at `'/'`
|
|
86
|
+
rather than through a wildcard pattern. The unbraced `'*splat'` skips the root outright,
|
|
87
|
+
and the braced `'{*splat}'` that Nest 11's migration guide prescribes stops
|
|
88
|
+
matching the _prefixed_ root once an application calls `setGlobalPrefix` —
|
|
89
|
+
which production applications almost always do. That was reported as
|
|
90
|
+
nest#14520 and fixed by nest#14522, whose regression test covers Fastify;
|
|
91
|
+
measured on `@nestjs/core` 11.1.28 with the Express adapter, the prefixed root
|
|
92
|
+
still reaches no middleware while the route itself answers `200`. Both
|
|
93
|
+
patterns were measured against the mount, which matched every path in both
|
|
94
|
+
configurations. Fastify needs the opposite choice — see the next entry. One
|
|
95
|
+
limit remains and is documented: module middleware is scoped to the global
|
|
96
|
+
prefix, so a request outside it entirely reaches no middleware.
|
|
97
|
+
- **Fastify records the same labels as Express**, which the documented support
|
|
98
|
+
for both platforms had been promising without any test behind it. Nest runs
|
|
99
|
+
middleware on Fastify through `@fastify/middie`, whose `runMiddie` calls
|
|
100
|
+
`run(req.raw, reply.raw, next)` and copies only `id`, `hostname`, `protocol`,
|
|
101
|
+
`ip`, `ips`, `log`, `query` and `body` onto that raw request — never
|
|
102
|
+
`routeOptions`. The recorder therefore saw no route metadata at all and would
|
|
103
|
+
have labelled every Fastify request `<unmatched>`, destroying the per-route
|
|
104
|
+
breakdown and making a scan indistinguishable from ordinary traffic. Worse,
|
|
105
|
+
`forRoutes('/')` is a mount on Express but an **exact match** on Fastify, so
|
|
106
|
+
most requests produced no sample whatsoever. The module now selects the mount
|
|
107
|
+
per adapter and registers an `onRequest` hook on Fastify that carries the
|
|
108
|
+
resolved template to the recorder. Covered by a new Fastify end-to-end suite.
|
|
109
|
+
- **Unmatched requests record a bounded label.** A request that matched no route
|
|
110
|
+
is recorded as `<unmatched>` (exported as `UNMATCHED_ROUTE`), never the
|
|
111
|
+
requested path. The previous raw-URL fallback would have let anyone mint one
|
|
112
|
+
Prometheus time series per probe, so counting scanner traffic under it would
|
|
113
|
+
have turned this fix into a memory-exhaustion vector.
|
|
114
|
+
|
|
115
|
+
### Changed
|
|
116
|
+
|
|
117
|
+
- **`ITimingSink` implementations now receive more samples**, including requests
|
|
118
|
+
that never reached a handler. Sinks that assumed "one sample per completed
|
|
119
|
+
request" should expect "one sample per closed request". The built-in metrics
|
|
120
|
+
bridge needs no change; a dashboard filtering on `2xx` sees its numbers
|
|
121
|
+
unchanged and its error rates become correct.
|
|
122
|
+
- **No status is relabelled**, and that is deliberate. A client that hangs up
|
|
123
|
+
mid-handler was already counted — destroying the socket does not cancel the
|
|
124
|
+
JavaScript already running, so the handler finished and the interceptor
|
|
125
|
+
recorded an ordinary `200` — and it still is, once, under the same `200`.
|
|
126
|
+
Introducing a sentinel status for aborts would rewrite the value of
|
|
127
|
+
`status_code="200"` series that already exist in every deployment, moving
|
|
128
|
+
error-rate panels with no change in traffic. Whether an abort deserves its own
|
|
129
|
+
status is a separate decision from whether the request is counted at all, and
|
|
130
|
+
this release makes only the second one.
|
|
131
|
+
- **The timing recorder is registered once, not twice.** The middleware
|
|
132
|
+
**replaced** the interceptor rather than joining it — two recorders would
|
|
133
|
+
double every rate an alert threshold is tuned against, which is a quieter
|
|
134
|
+
failure than the one being fixed.
|
|
135
|
+
|
|
136
|
+
### Deprecated
|
|
137
|
+
|
|
138
|
+
- **`TimingInterceptor`** is superseded by `BymaxTimingMiddleware` and is no
|
|
139
|
+
longer registered by `BymaxCoreModule`. It stays exported so an application
|
|
140
|
+
that wired it by hand keeps compiling; registering it alongside the middleware
|
|
141
|
+
records a second sample for every request that reaches a handler.
|
|
142
|
+
|
|
143
|
+
### Added
|
|
144
|
+
|
|
145
|
+
- **`BymaxTimingMiddleware` and `UNMATCHED_ROUTE`** are exported from the package
|
|
146
|
+
root.
|
|
147
|
+
- **A library can describe its own routes in a consumer's document.** A provider
|
|
148
|
+
marked `@BymaxOpenApiContributor()` returns OpenAPI fragments keyed by handler
|
|
149
|
+
identity — `'AuthController.login'` — and they are merged onto the operations
|
|
150
|
+
those handlers produced. It exists because the two obvious alternatives do
|
|
151
|
+
not work: decorating a library's controllers with `@nestjs/swagger` would load
|
|
152
|
+
that peer in every application importing the library, and a consumer-side map
|
|
153
|
+
keyed by path cannot be written by a library mounted through
|
|
154
|
+
`RouterModule.register`, which does not know its own final paths.
|
|
155
|
+
- **`openapi.operationIdFactory`**, plus the exported `OpenApiOperationIdFactory`
|
|
156
|
+
type. This package now always installs a factory so it can learn which handler
|
|
157
|
+
produced which operation, and **delegates the id string** — to this option when
|
|
158
|
+
set, to the format `@nestjs/swagger` itself produces otherwise. Choosing the id
|
|
159
|
+
instead would have renamed every operation in every published document and
|
|
160
|
+
broken any client generated from one; a test compares both documents to keep
|
|
161
|
+
that true if the peer's format ever changes.
|
|
162
|
+
- **The contract types** `IOpenApiContributor`, `OpenApiFragment`,
|
|
163
|
+
`OpenApiFragmentObject` and `OpenApiHandlerKey`, exported from `./openapi` so a
|
|
164
|
+
sibling library can target them at its own compile time.
|
|
165
|
+
- **`BYMAX_OPENAPI_CONTRACT_VERSION`**, and a required `contractVersion` on every
|
|
166
|
+
fragment. A fragment crosses a boundary between independently released
|
|
167
|
+
packages, and on that boundary compile-time types protect nothing: each side
|
|
168
|
+
type-checks against its own installed copy, so only the value travelling at
|
|
169
|
+
runtime can say which shape it is. A revision this package does not speak fails
|
|
170
|
+
the build naming both. Required rather than inferred from absence, following
|
|
171
|
+
the pattern Kubernetes objects use for `apiVersion` — an optional discriminator
|
|
172
|
+
is unambiguous only while exactly one revision exists, which is precisely when
|
|
173
|
+
nobody checks it.
|
|
174
|
+
|
|
175
|
+
### Fixed
|
|
176
|
+
|
|
177
|
+
- **`DiscoveryModule` is imported when only the document is enabled.** It was
|
|
178
|
+
imported on the synchronous registration path only when readiness discovery or
|
|
179
|
+
metrics could scan, so an application enabling nothing but OpenAPI had no
|
|
180
|
+
scanner — and a library's description of its own routes would have been
|
|
181
|
+
silently dropped.
|
|
182
|
+
|
|
183
|
+
### Notes
|
|
184
|
+
|
|
185
|
+
- Deriving fragments from validation decorators is deliberately **not** in this
|
|
186
|
+
package: it takes no dependency on any validation library. A library that
|
|
187
|
+
wants its schemas to track its own decorators generates them in its own build,
|
|
188
|
+
where that dependency already exists, and commits the result with a test
|
|
189
|
+
asserting generated matches committed — so drift fails in the repository that
|
|
190
|
+
caused it. An application's own DTOs need none of this; `@nestjs/swagger`'s CLI
|
|
191
|
+
plugin already derives them, which is the route a precompiled library lacks.
|
|
192
|
+
|
|
193
|
+
## [1.3.2] - 2026-08-12
|
|
15
194
|
|
|
16
195
|
A consumer audit of the served document found that it described the library's
|
|
17
196
|
promises rather than the deployment: routes of features that were switched off
|
|
@@ -509,4 +688,6 @@ have regressed from. They are kept because the reasoning is worth having.
|
|
|
509
688
|
[1.2.2]: https://github.com/bymaxone/nest-core/compare/v1.2.1...v1.2.2
|
|
510
689
|
[1.2.1]: https://github.com/bymaxone/nest-core/compare/v1.2.0...v1.2.1
|
|
511
690
|
[1.2.0]: https://github.com/bymaxone/nest-core/compare/v1.1.1...v1.2.0
|
|
512
|
-
[
|
|
691
|
+
[1.4.0]: https://github.com/bymaxone/nest-core/compare/v1.3.2...v1.4.0
|
|
692
|
+
[1.5.0]: https://github.com/bymaxone/nest-core/compare/v1.4.0...v1.5.0
|
|
693
|
+
[Unreleased]: https://github.com/bymaxone/nest-core/compare/v1.5.0...HEAD
|