@bymax-one/nest-core 1.0.0 → 1.1.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 CHANGED
@@ -11,6 +11,160 @@ heading here.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [1.1.0] - 2026-08-05
15
+
16
+ Four optional integrations, each off by default and each loading nothing until it
17
+ is turned on: OpenAPI documents in development, health-indicator discovery, a
18
+ metrics contribution contract, and OpenTelemetry trace correlation.
19
+
20
+ ### Added
21
+
22
+ - **OpenAPI documents, development only.** A new `openapi` option block and a new
23
+ `./openapi` subpath exporting `applyBymaxOpenApi`. Enabling the block and calling
24
+ the helper once during bootstrap serves an interactive UI and the raw document,
25
+ carrying the schemas this package already owns — the error envelope and its code
26
+ catalog, the health response, and the offset and cursor page shapes with their
27
+ query parameters.
28
+
29
+ `@nestjs/swagger` is an optional peer, reached only through a lazy dynamic import,
30
+ exactly like `prom-client`: an application that leaves the feature off never loads
31
+ it, and enabling the feature without installing it fails at boot with a message
32
+ naming the package and the install command.
33
+
34
+ The schemas are contributed as plain specification objects rather than decorated
35
+ classes. A decorator runs when its class is defined, so describing these contracts
36
+ with `@ApiProperty` would load the peer in every application that imports this
37
+ package, including the ones that never enable the feature.
38
+
39
+ A contributed entry never overwrites one the document already defines.
40
+
41
+ - **Health-indicator discovery.** A new `health.autoDiscover` option and a
42
+ `@BymaxHealthIndicator()` marker, exported from the `./health` subpath alongside
43
+ the contract it belongs to. With discovery enabled, every marked provider in the
44
+ application joins readiness — so a library an application merely imports can
45
+ contribute its own check without the application registering anything.
46
+
47
+ Discovery matches the marker, never the shape of an object: a provider that
48
+ happens to expose a `name` and a `check` is not a readiness probe, and scraping
49
+ one in would let an unrelated failure take an application out of rotation. A
50
+ provider that is marked but does not implement `IHealthIndicator` fails the boot
51
+ naming the class, rather than being skipped silently.
52
+
53
+ Explicit registration still wins: an indicator bound under
54
+ `BYMAX_HEALTH_INDICATORS` keeps its name and its position, and a discovered one
55
+ with the same name is dropped. Discovered indicators are sorted by name, so the
56
+ `checks` array is stable across restarts. The provider graph is walked once, at
57
+ bootstrap.
58
+
59
+ Off by default. It changes which failures can fail a readiness probe, which is
60
+ a decision an application makes rather than one it inherits from its imports.
61
+
62
+ - **A metrics contribution contract.** A new `./metrics` subpath exporting
63
+ `IMetricsContributor` and a `@BymaxMetricsContributor()` marker. A marked provider
64
+ is handed the registry once at bootstrap and registers its own collectors, so a
65
+ library's metrics appear on the application's existing scrape endpoint with
66
+ nothing wired.
67
+
68
+ Contributors receive the registry rather than injecting `BYMAX_METRICS_REGISTRY`:
69
+ a library that injected the token would depend on this package's DI tokens, and
70
+ therefore on the module. Receiving it as an argument means the only thing a
71
+ contributing library imports is the contract and the marker.
72
+
73
+ Registration failures are rethrown with the contributor named and the original
74
+ error chained. `prom-client` reports a duplicate metric name but not who
75
+ registered it, which in an application composing several libraries is the hard
76
+ half of the question. Contributors run sorted by class name, so a collision fails
77
+ the same way on every boot.
78
+
79
+ No separate flag: contribution rides on the metrics feature. With metrics
80
+ disabled no contributor runs and `prom-client` is still never loaded.
81
+
82
+ This is the one subpath whose types name `prom-client`. Implementing the contract
83
+ means constructing `prom-client` collectors, so anyone importing it already
84
+ depends on the peer; every other subpath stays free of it.
85
+
86
+ - **Trace correlation.** A new `telemetry` option block reads the active OpenTelemetry
87
+ span and carries its identifiers into the request-timing sample, into the exception
88
+ filter's observability seam, and — behind `telemetry.exposeTraceId` — into the error
89
+ envelope served to the client.
90
+
91
+ `@opentelemetry/api` is an optional peer. Unlike the other two it is read on every
92
+ request, so it is loaded once at bootstrap rather than at the point of use, and only
93
+ when the feature is enabled.
94
+
95
+ This package reads; it never traces. No span is created, no SDK configured, no
96
+ exporter registered: all of that belongs to the instrumentation an operator already
97
+ runs, and duplicating it would produce two spans per request.
98
+
99
+ A request with nothing recording, or an all-zero span context, resolves to no trace:
100
+ the fields are absent rather than set to a sentinel. Trace identifiers are never used
101
+ as metric labels — a trace id is unbounded, and one unbounded label is enough to make
102
+ a scrape endpoint the most expensive route in a service.
103
+
104
+ ### Changed
105
+
106
+ - **The marker-based provider scan is now shared.** Readiness discovery and metrics
107
+ contribution use one scan, which reads Nest's provider graph, matches a literal
108
+ metadata key, and labels each match by class name — falling back to the provider
109
+ token for an anonymous class. Behavior is unchanged for readiness.
110
+
111
+ ### Security
112
+
113
+ - **A trace id is not published in a response body by default.** `telemetry.exposeTraceId`
114
+ is off: a trace id is not a secret, but in a response it tells a caller that a tracing
115
+ backend exists and hands them the identifier correlating their request with everything
116
+ else in that trace. With the option off the identifiers still reach the timing sample and
117
+ the logging seam.
118
+ - **The OpenAPI document is never served in production.** `NODE_ENV` decides, and
119
+ the decision is fail-closed: only `development` and `test` are non-production, so
120
+ an unset or unrecognized value is production. The guard runs in two independent
121
+ layers — the option resolver forces the feature off, and the bootstrap helper
122
+ refuses again without trusting that resolution — and there is no override. Asking
123
+ for the document in production is a no-op with a warning, not an error, so one
124
+ configuration can be shared across environments.
125
+
126
+ ### Notes
127
+
128
+ - `applyBymaxOpenApi` must be called **before** `app.listen()`. Mounting the document
129
+ re-registers routes on the HTTP adapter, and doing that against an
130
+ already-initialized Express 5 application replaces the router: every other route,
131
+ including this package's health endpoints, stops resolving.
132
+
133
+ ## [1.0.1] - 2026-08-04
134
+
135
+ **Behaviour change on the readiness endpoint.** A failing indicator's message no
136
+ longer appears in the HTTP response by default; it goes to the logger instead.
137
+
138
+ ### Security
139
+
140
+ - **The readiness response no longer publishes an indicator's failure message.**
141
+ `GET /health/ready` returned `details.error` carrying the rejecting indicator's
142
+ `Error#message`. That endpoint is typically unauthenticated and reachable by
143
+ whatever probes it — and an indicator rarely authors its own failure text: it
144
+ writes `await this.redis.ping()` and lets the driver's error propagate. Driver
145
+ errors carry hosts, ports and, for a connection string, credentials. An
146
+ indicator failing with `connection refused: postgres://user:PASSWORD@db:5432`
147
+ served that string to anyone who could reach the probe.
148
+
149
+ A failing check is now `{ name, status: 'down' }` and nothing more. The message
150
+ is written to Nest's `Logger` instead, so the diagnostic survives in a channel
151
+ that already has access control rather than being lost.
152
+
153
+ `health.exposeIndicatorErrors` (default `false`) puts it back in the response
154
+ for local debugging — the same shape, and the same warning, as
155
+ `envelope.exposeInternals`. The library made opposite choices about the same
156
+ risk in two places; they now agree.
157
+
158
+ `timedOutAfterMs` is unaffected: that number is one this library chose, not text
159
+ an indicator produced.
160
+
161
+ ### Changed
162
+
163
+ - **The Health and Security Model sections describe the split**, and the
164
+ configuration table documents the new option. The previous text said an
165
+ indicator "cannot leak more than it already chose to put in a message", which
166
+ assigned a choice the indicator's author usually never makes.
167
+
14
168
  ## [1.0.0] - 2026-08-03
15
169
 
16
170
  First published release. Everything below ships in it.
@@ -73,5 +227,7 @@ have regressed from. They are kept because the reasoning is worth having.
73
227
  cleanly and silently. Corrected before the first publish, so no released version
74
228
  ever carried the permissive range. No runtime behaviour changed.
75
229
 
230
+ [1.1.0]: https://github.com/bymaxone/nest-core/compare/v1.0.1...v1.1.0
231
+ [1.0.1]: https://github.com/bymaxone/nest-core/compare/v1.0.0...v1.0.1
76
232
  [1.0.0]: https://github.com/bymaxone/nest-core/releases/tag/v1.0.0
77
- [Unreleased]: https://github.com/bymaxone/nest-core/compare/v1.0.0...HEAD
233
+ [Unreleased]: https://github.com/bymaxone/nest-core/compare/v1.1.0...HEAD