@bymax-one/nest-core 1.5.0 → 1.5.2
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 +90 -1
- package/README.md +147 -16
- package/dist/index.cjs +15 -4
- package/dist/index.d.cts +31 -3
- package/dist/index.d.ts +31 -3
- package/dist/index.mjs +15 -4
- package/dist/openapi/index.cjs +8 -1
- package/dist/openapi/index.mjs +8 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,93 @@ heading here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.5.2] - 2026-08-15
|
|
15
|
+
|
|
16
|
+
The production guard read `NODE_ENV` and nothing else, and treated an unset
|
|
17
|
+
variable as production. An application that validates its own `APP_ENV` and
|
|
18
|
+
never sets `NODE_ENV` was therefore classified as production on evidence it
|
|
19
|
+
never gave — the OpenAPI document was refused in a development deployment, with
|
|
20
|
+
no way to answer back. Two independent consumers reported the same split.
|
|
21
|
+
|
|
22
|
+
**Apply to a derived backend:** nothing to change. A deployment that sets
|
|
23
|
+
`NODE_ENV` behaves exactly as before. If yours validates its own variable
|
|
24
|
+
instead, pass it as `environment` and the document is served where that variable
|
|
25
|
+
says `development` or `test`.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **`environment`, for applications that validate their own environment
|
|
30
|
+
variable.** The production guard read `NODE_ENV` and nothing else, and treated
|
|
31
|
+
an unset variable as production. An application that parses an `APP_ENV`
|
|
32
|
+
through its config schema and never sets `NODE_ENV` was therefore classified
|
|
33
|
+
as production on evidence it never gave — the OpenAPI document was refused in
|
|
34
|
+
a development deployment, with no way to answer back. Two independent
|
|
35
|
+
consumers reported the same split between the library's view of the
|
|
36
|
+
environment and their own validated one.
|
|
37
|
+
|
|
38
|
+
A top-level `environment` option is now consulted **where the process declares
|
|
39
|
+
nothing**: `NODE_ENV` unset, or set to whitespace. `NODE_ENV` wins whenever it
|
|
40
|
+
says anything at all, so no configured value can make a runtime that named
|
|
41
|
+
itself production serve the document — asserted in both guards rather than in
|
|
42
|
+
one. The declaration enters the same fail-closed classification, so an
|
|
43
|
+
unrecognized name is production like any other: this is a second source for
|
|
44
|
+
the value, never a second set of rules.
|
|
45
|
+
|
|
46
|
+
The narrowing is stated rather than buried. Both guards previously classified
|
|
47
|
+
from the process alone; now, in the single case where the process says
|
|
48
|
+
nothing, the snapshot a consumer bound decides the answer, because there is
|
|
49
|
+
nothing else to decide it with. Replacing a guess with a declaration is not an
|
|
50
|
+
override, but it is a real change to what the second guard depends on.
|
|
51
|
+
|
|
52
|
+
**Apply to a derived backend:** nothing to change. The option is optional and
|
|
53
|
+
every existing classification is unchanged — a deployment that sets `NODE_ENV`
|
|
54
|
+
behaves exactly as before.
|
|
55
|
+
|
|
56
|
+
## [1.5.1] - 2026-08-15
|
|
57
|
+
|
|
58
|
+
Documentation only; no source change. The 1.5.0 warning's known-limit note told
|
|
59
|
+
readers that no tool could catch the one shape the warning cannot report, and
|
|
60
|
+
that rendering the document twice was therefore the only check. That is true
|
|
61
|
+
only of something reading the rendered document alone — a consumer's own suite
|
|
62
|
+
knows the intent and can assert it on every commit — so the note was arguing
|
|
63
|
+
against the better practice.
|
|
64
|
+
|
|
65
|
+
**Apply to a derived backend:** nothing to change in code. Read the revised
|
|
66
|
+
"Documenting authentication" section and write the assertion it now shows; it
|
|
67
|
+
costs one test and replaces a manual step nobody remembers to run.
|
|
68
|
+
|
|
69
|
+
### Documentation
|
|
70
|
+
|
|
71
|
+
- **The credential-free warning's known limit no longer argues against the
|
|
72
|
+
practice that covers it.** The README said no tool could distinguish a
|
|
73
|
+
document that lost its requirements from one that never had any, and that
|
|
74
|
+
rendering the document twice was therefore the only check. The first half is
|
|
75
|
+
true only of something reading the rendered document alone; a consumer's own
|
|
76
|
+
suite knows which of the two it is and can assert it on every commit. Saying
|
|
77
|
+
otherwise did not merely overstate a limit — it told readers that the standing
|
|
78
|
+
check they should write does not exist. The section now attributes the limit
|
|
79
|
+
correctly, states why the warning cannot fire on that shape (it follows from
|
|
80
|
+
the trigger, in every version), gives the assertion as the practice, and
|
|
81
|
+
leaves render-and-diff the narrower job it is genuinely good at: seeing what
|
|
82
|
+
moved when you change something, so you can turn it into an assertion.
|
|
83
|
+
|
|
84
|
+
- **A contributed scheme's presence is documented as part of the contributor's
|
|
85
|
+
configuration.** Which security schemes a library contributes can depend on
|
|
86
|
+
how that library is configured — the names are stable, their presence is not.
|
|
87
|
+
A document-level default must therefore be derived from the same configuration
|
|
88
|
+
the contributor reads. A literal is correct only for the configuration it was
|
|
89
|
+
written against: elsewhere it either resolves while describing one of two
|
|
90
|
+
credentials a route accepts (quietly incomplete) or names a scheme nobody
|
|
91
|
+
declares (a failed document build). Guarding on whether the scheme exists
|
|
92
|
+
clears the loud case and ships the quiet one.
|
|
93
|
+
|
|
94
|
+
- **What a document-level default does not let you say** is now stated. Its
|
|
95
|
+
entries are alternatives applied to every operation that says nothing, so a
|
|
96
|
+
backend with two credential families _can_ list both and nothing rejects it —
|
|
97
|
+
the result asserts that either credential works for every inheriting route,
|
|
98
|
+
which is false in the permissive direction. The minority family belongs in
|
|
99
|
+
`openapi.operationSecurity`, which outranks the default.
|
|
100
|
+
|
|
14
101
|
## [1.5.0] - 2026-08-15
|
|
15
102
|
|
|
16
103
|
An OpenAPI document could stop requiring credentials without anything saying
|
|
@@ -690,4 +777,6 @@ have regressed from. They are kept because the reasoning is worth having.
|
|
|
690
777
|
[1.2.0]: https://github.com/bymaxone/nest-core/compare/v1.1.1...v1.2.0
|
|
691
778
|
[1.4.0]: https://github.com/bymaxone/nest-core/compare/v1.3.2...v1.4.0
|
|
692
779
|
[1.5.0]: https://github.com/bymaxone/nest-core/compare/v1.4.0...v1.5.0
|
|
693
|
-
[
|
|
780
|
+
[1.5.1]: https://github.com/bymaxone/nest-core/compare/v1.5.0...v1.5.1
|
|
781
|
+
[1.5.2]: https://github.com/bymaxone/nest-core/compare/v1.5.1...v1.5.2
|
|
782
|
+
[Unreleased]: https://github.com/bymaxone/nest-core/compare/v1.5.2...HEAD
|
package/README.md
CHANGED
|
@@ -202,6 +202,20 @@ BymaxCoreModule.forRoot({ isGlobal: false })
|
|
|
202
202
|
Every block is optional; an omitted block, or an omitted field within it,
|
|
203
203
|
falls back to the documented default. Pass only what you want to change.
|
|
204
204
|
|
|
205
|
+
### `environment`
|
|
206
|
+
|
|
207
|
+
The one top-level option rather than a block, because it describes the
|
|
208
|
+
deployment rather than a feature.
|
|
209
|
+
|
|
210
|
+
| Option | Type | Default | Description |
|
|
211
|
+
| ------------- | -------- | ------- | ------------------------------------------------------------------------------------- |
|
|
212
|
+
| `environment` | `string` | unset | The environment this deployment runs in, read only where `NODE_ENV` declares nothing. |
|
|
213
|
+
|
|
214
|
+
Set it when your application validates its own environment variable and does not
|
|
215
|
+
also set `NODE_ENV`. `NODE_ENV` wins whenever it says anything, so this can never
|
|
216
|
+
serve the OpenAPI document in a runtime that named itself production. Full rules
|
|
217
|
+
and the classification table: [Production is a closed door](#production-is-a-closed-door).
|
|
218
|
+
|
|
205
219
|
### `envelope`
|
|
206
220
|
|
|
207
221
|
| Option | Type | Default | Description |
|
|
@@ -420,14 +434,84 @@ rather than by silencing output: `operationSecurity: { 'GET /examples': [] }`
|
|
|
420
434
|
marks the operation public, and it stops being reported. A library can do the
|
|
421
435
|
same for its own routes by contributing `security: []` in its fragment.
|
|
422
436
|
|
|
423
|
-
**
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
437
|
+
**There is one shape this warning cannot report.** If you remove _every_
|
|
438
|
+
requirement at once — no library describing anything, no decorator, no override,
|
|
439
|
+
no document default — the second condition above is never met: nothing states a
|
|
440
|
+
requirement, so there are no bare operations sitting _beside_ described ones.
|
|
441
|
+
That follows from the trigger and therefore holds in every version; it is not a
|
|
442
|
+
gap a later release closes, and a reader who expects one is exactly the reader
|
|
443
|
+
who stops checking.
|
|
444
|
+
|
|
445
|
+
The limit belongs to **anything reading only the rendered document**. Such a
|
|
446
|
+
document is indistinguishable from that of an API which is public on purpose —
|
|
447
|
+
both are a set of operations asking for nothing — and this package cannot tell
|
|
448
|
+
them apart without also warning at every genuinely public API, which is how a
|
|
449
|
+
warning earns the right to be ignored.
|
|
450
|
+
|
|
451
|
+
**Your own suite has no such handicap, because you know which one you are.**
|
|
452
|
+
Assert it, and the check runs on every commit rather than when somebody
|
|
453
|
+
remembers to look:
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
it('still requires a credential everywhere it should', () => {
|
|
457
|
+
const document = buildYourDocument()
|
|
458
|
+
|
|
459
|
+
// Assert the default you expect, not merely that one exists. `[]` is a
|
|
460
|
+
// defined value that requires nothing, so a "toBeDefined" check passes for
|
|
461
|
+
// a default that degraded to empty — which is the regression this test is
|
|
462
|
+
// here to catch, wearing the shape of a pass.
|
|
463
|
+
expect(document.security).toEqual([{ cookieAuth: [] }])
|
|
464
|
+
|
|
465
|
+
// An explicit `[]` is how an operation says "public". The set of operations
|
|
466
|
+
// saying it should be the set you meant — no more, no fewer.
|
|
467
|
+
expect(publicOperationsOf(document)).toEqual(['POST /auth/login'])
|
|
468
|
+
})
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Render-and-diff keeps a narrower job, and it is a good one: when you are
|
|
472
|
+
_changing_ something — adopting a library that describes its own routes, moving
|
|
473
|
+
a default — render with and without the change and compare the operations you
|
|
474
|
+
mount. It shows you what moved without your having to predict it. Then turn what
|
|
475
|
+
it showed you into an assertion, so the next change is caught rather than
|
|
476
|
+
inspected.
|
|
477
|
+
|
|
478
|
+
#### A contributed scheme's presence is part of the contributor's configuration
|
|
479
|
+
|
|
480
|
+
A library contributes security schemes, and **which** ones it contributes can
|
|
481
|
+
depend on how you configured it. Any of them, gated on any of its inputs, and
|
|
482
|
+
often on more than one — the scheme you have in mind may be the absent one, and
|
|
483
|
+
the setting you are thinking of may not be the only gate. The names are stable;
|
|
484
|
+
their presence is not.
|
|
485
|
+
|
|
486
|
+
That has one consequence worth stating as a rule, because getting it wrong
|
|
487
|
+
produces a failure at either end of the loudness scale:
|
|
488
|
+
|
|
489
|
+
> Derive a document-level default from **the same configuration the contributor
|
|
490
|
+
> reads**, never from the scheme names, and never from what the document
|
|
491
|
+
> happened to contain before you adopted the library.
|
|
492
|
+
|
|
493
|
+
Writing the name as a literal is correct only for the configuration you wrote it
|
|
494
|
+
against. Under a configuration that declares that scheme plus another, the
|
|
495
|
+
default still resolves but describes one of two credentials the route accepts —
|
|
496
|
+
**quietly incomplete**. Under one that declares it not at all, the name resolves
|
|
497
|
+
to nothing and the document build **fails** with the undeclared-scheme error
|
|
498
|
+
above. Guarding on whether the scheme exists is the tempting fix and it is the
|
|
499
|
+
wrong one: it clears the case that already announced itself and ships the one
|
|
500
|
+
that does not.
|
|
501
|
+
|
|
502
|
+
Note what a document-level default does not let you _say_. Its entries are
|
|
503
|
+
**alternatives** — any one of them satisfies an operation — and they apply to
|
|
504
|
+
every operation that states nothing. So a backend whose routes sit behind **two
|
|
505
|
+
different credential families** can certainly list both, and nothing rejects it:
|
|
506
|
+
the result is a document asserting that _either_ credential works for _every_
|
|
507
|
+
inheriting route. That is not an incomplete document, it is a false one, and it
|
|
508
|
+
is false in the permissive direction — it tells a client that a credential the
|
|
509
|
+
route will reject is one the route accepts.
|
|
510
|
+
|
|
511
|
+
Give the majority family the default and the minority explicit
|
|
512
|
+
`operationSecurity` entries. Those outrank the default, and an operation
|
|
513
|
+
carrying one is never named by the warning above, because it states a
|
|
514
|
+
requirement.
|
|
431
515
|
|
|
432
516
|
## 🔑 DI Tokens
|
|
433
517
|
|
|
@@ -889,16 +973,61 @@ can emit it once and never branch:
|
|
|
889
973
|
### Production is a closed door
|
|
890
974
|
|
|
891
975
|
`NODE_ENV` decides, and the decision is fail-closed: only `development` and
|
|
892
|
-
`test` are non-production. Any other value
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
976
|
+
`test` are non-production. Any other value is production, and in production the
|
|
977
|
+
document is never built and never mounted, whatever the configuration says. The
|
|
978
|
+
guard runs twice, independently: the option resolver forces the feature off, and
|
|
979
|
+
the bootstrap helper classifies the runtime again without trusting that
|
|
980
|
+
resolution.
|
|
981
|
+
|
|
982
|
+
**`NODE_ENV` cannot be overridden.** With it set to anything, no option serves
|
|
983
|
+
the document in a runtime it named production.
|
|
897
984
|
|
|
898
985
|
Enabling it in production is not an error, it is a no-op with a warning naming
|
|
899
986
|
the option that was ignored, so a single configuration can be shared across
|
|
900
987
|
environments.
|
|
901
988
|
|
|
989
|
+
#### When your application validates its own environment variable
|
|
990
|
+
|
|
991
|
+
Plenty of applications parse an `APP_ENV` through a config schema and never set
|
|
992
|
+
`NODE_ENV` at all. Those deployments used to be classified as production —
|
|
993
|
+
absence was the only evidence available — so the document was refused in an
|
|
994
|
+
environment that never asked for the refusal, with no way to answer back.
|
|
995
|
+
|
|
996
|
+
Declare the environment and it is used **where the process declares nothing**:
|
|
997
|
+
|
|
998
|
+
```typescript
|
|
999
|
+
BymaxCoreModule.forRootAsync({
|
|
1000
|
+
inject: [ConfigService],
|
|
1001
|
+
useFactory: (config: ConfigService) => ({
|
|
1002
|
+
// Your validated value, not a second copy of NODE_ENV.
|
|
1003
|
+
environment: config.get('APP_ENV'),
|
|
1004
|
+
openapi: { enabled: true }
|
|
1005
|
+
})
|
|
1006
|
+
})
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
| `NODE_ENV` | `environment` | Classified as |
|
|
1010
|
+
| --------------- | ------------- | -------------- |
|
|
1011
|
+
| `production` | `development` | **production** |
|
|
1012
|
+
| `development` | (anything) | development |
|
|
1013
|
+
| unset, or blank | `development` | development |
|
|
1014
|
+
| unset, or blank | `staging` | **production** |
|
|
1015
|
+
| unset, or blank | unset | **production** |
|
|
1016
|
+
|
|
1017
|
+
Two properties are worth reading off that table. A declaration never overrules a
|
|
1018
|
+
process that named its own environment — the first row is the one that matters,
|
|
1019
|
+
and it is asserted in both guards rather than in one. And the declaration enters
|
|
1020
|
+
the same fail-closed classification, so an unrecognized name is production like
|
|
1021
|
+
any other; this is a second **source** for the value, never a second set of
|
|
1022
|
+
rules.
|
|
1023
|
+
|
|
1024
|
+
The narrowing is deliberate and worth naming rather than burying: in the one
|
|
1025
|
+
case where the process declares nothing, the configuration a consumer bound does
|
|
1026
|
+
decide the answer, because there is nothing else to decide it with. Replacing a
|
|
1027
|
+
guess with a declaration is not the same as allowing an override — but it is a
|
|
1028
|
+
real change to what the second guard depends on, and you should know it before
|
|
1029
|
+
relying on either.
|
|
1030
|
+
|
|
902
1031
|
### Testing the enabled path under Jest
|
|
903
1032
|
|
|
904
1033
|
`applyBymaxOpenApi` loads `@nestjs/swagger` through a dynamic `import()` — that
|
|
@@ -1289,9 +1418,11 @@ guard you would apply to any internal endpoint, or keep it off the public listen
|
|
|
1289
1418
|
A published document is a map of every route, parameter and error shape an application has —
|
|
1290
1419
|
useful to a developer, and just as useful to anyone probing the service. So unlike the metrics
|
|
1291
1420
|
endpoint, it is not left to a guard: it is refused outright whenever the runtime is not
|
|
1292
|
-
positively `development` or `test`, in two independent layers
|
|
1293
|
-
|
|
1294
|
-
|
|
1421
|
+
positively `development` or `test`, in two independent layers. **`NODE_ENV` cannot be
|
|
1422
|
+
overridden** — with it set to anything, no option serves the document in a runtime it named
|
|
1423
|
+
production. A runtime that declares nothing is classified from the application's own
|
|
1424
|
+
[`environment`](#environment) when it supplied one, and counts as production otherwise, because
|
|
1425
|
+
the deployment nobody configured is the one most likely to be exposed.
|
|
1295
1426
|
|
|
1296
1427
|
---
|
|
1297
1428
|
|
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,13 @@ function isProductionRuntime(value = process.env["NODE_ENV"]) {
|
|
|
30
30
|
}
|
|
31
31
|
return !NON_PRODUCTION_ENVIRONMENTS.has(value.trim().toLowerCase());
|
|
32
32
|
}
|
|
33
|
+
function runtimeEnvironmentName(declared) {
|
|
34
|
+
const fromProcess = process.env["NODE_ENV"];
|
|
35
|
+
if (fromProcess !== void 0 && fromProcess.trim() !== "") {
|
|
36
|
+
return fromProcess;
|
|
37
|
+
}
|
|
38
|
+
return declared;
|
|
39
|
+
}
|
|
33
40
|
|
|
34
41
|
// src/core.options.ts
|
|
35
42
|
var DEFAULT_INDICATOR_TIMEOUT_MS = 5e3;
|
|
@@ -92,9 +99,9 @@ function cloneServers(raw) {
|
|
|
92
99
|
(server) => server.description === void 0 ? { url: server.url } : { url: server.url, description: server.description }
|
|
93
100
|
);
|
|
94
101
|
}
|
|
95
|
-
function resolveOpenApi(raw) {
|
|
102
|
+
function resolveOpenApi(raw, declaredEnvironment) {
|
|
96
103
|
const requested = raw?.enabled ?? false;
|
|
97
|
-
const production = isProductionRuntime();
|
|
104
|
+
const production = isProductionRuntime(runtimeEnvironmentName(declaredEnvironment));
|
|
98
105
|
return {
|
|
99
106
|
enabled: requested && !production,
|
|
100
107
|
suppressedInProduction: requested && production,
|
|
@@ -131,8 +138,12 @@ function normalizeCoreOptions(raw) {
|
|
|
131
138
|
timing: resolveTiming(raw?.timing),
|
|
132
139
|
health: resolveHealth(raw?.health),
|
|
133
140
|
metrics: resolveMetrics(raw?.metrics),
|
|
134
|
-
openapi: resolveOpenApi(raw?.openapi),
|
|
135
|
-
telemetry: resolveTelemetry(raw?.telemetry)
|
|
141
|
+
openapi: resolveOpenApi(raw?.openapi, raw?.environment),
|
|
142
|
+
telemetry: resolveTelemetry(raw?.telemetry),
|
|
143
|
+
// Spread rather than assigned so an application that declared nothing has
|
|
144
|
+
// no `environment` member at all, matching every other optional member on
|
|
145
|
+
// this snapshot under `exactOptionalPropertyTypes`.
|
|
146
|
+
...raw?.environment === void 0 ? {} : { environment: raw.environment }
|
|
136
147
|
});
|
|
137
148
|
}
|
|
138
149
|
normalizeCoreOptions();
|
package/dist/index.d.cts
CHANGED
|
@@ -273,6 +273,28 @@ interface BymaxCoreModuleOptions {
|
|
|
273
273
|
openapi?: OpenApiOptions;
|
|
274
274
|
/** Trace correlation. Default: disabled. */
|
|
275
275
|
telemetry?: TelemetryOptions;
|
|
276
|
+
/**
|
|
277
|
+
* The environment this deployment is running in, for the features that must
|
|
278
|
+
* never exist outside development — today, the OpenAPI document and its UI.
|
|
279
|
+
*
|
|
280
|
+
* **`NODE_ENV` always wins.** This is consulted only when the process
|
|
281
|
+
* declares nothing: `NODE_ENV` unset, or set to whitespace. It cannot make a
|
|
282
|
+
* runtime that identified itself as production serve the document, and no
|
|
283
|
+
* value here overrides one there.
|
|
284
|
+
*
|
|
285
|
+
* Set it when your application validates its own environment variable — an
|
|
286
|
+
* `APP_ENV` your config schema parses — and does not also set `NODE_ENV`.
|
|
287
|
+
* Without it, that deployment is classified as production because absence was
|
|
288
|
+
* the only evidence available, and the document is refused in an environment
|
|
289
|
+
* that never asked for the refusal.
|
|
290
|
+
*
|
|
291
|
+
* Recognized non-production values are `development` and `test`, compared
|
|
292
|
+
* case-insensitively and ignoring surrounding whitespace. Anything else,
|
|
293
|
+
* including an unrecognized name, is production.
|
|
294
|
+
*
|
|
295
|
+
* @example 'development'
|
|
296
|
+
*/
|
|
297
|
+
environment?: string;
|
|
276
298
|
}
|
|
277
299
|
/** Fully-resolved envelope options. */
|
|
278
300
|
interface ResolvedEnvelopeOptions {
|
|
@@ -334,9 +356,9 @@ interface ResolvedOpenApiOptions {
|
|
|
334
356
|
}
|
|
335
357
|
/**
|
|
336
358
|
* The effective, defaults-applied configuration exposed under
|
|
337
|
-
* `BYMAX_CORE_OPTIONS`. Fields with a documented default are always present
|
|
338
|
-
*
|
|
339
|
-
*
|
|
359
|
+
* `BYMAX_CORE_OPTIONS`. Fields with a documented default are always present.
|
|
360
|
+
* Two fields have no default and are absent unless the consumer supplies them:
|
|
361
|
+
* `timing.slowRequestThresholdMs`, and `environment`.
|
|
340
362
|
*/
|
|
341
363
|
interface ResolvedCoreOptions {
|
|
342
364
|
envelope: ResolvedEnvelopeOptions;
|
|
@@ -345,6 +367,12 @@ interface ResolvedCoreOptions {
|
|
|
345
367
|
metrics: ResolvedMetricsOptions;
|
|
346
368
|
openapi: ResolvedOpenApiOptions;
|
|
347
369
|
telemetry: ResolvedTelemetryOptions;
|
|
370
|
+
/**
|
|
371
|
+
* The environment the application declared, carried through so the bootstrap
|
|
372
|
+
* helper classifies the runtime from the same two inputs the resolver did.
|
|
373
|
+
* Absent when the application declared none.
|
|
374
|
+
*/
|
|
375
|
+
environment?: string;
|
|
348
376
|
}
|
|
349
377
|
|
|
350
378
|
/** Non-option extras accepted by `forRoot` / `forRootAsync`. */
|
package/dist/index.d.ts
CHANGED
|
@@ -273,6 +273,28 @@ interface BymaxCoreModuleOptions {
|
|
|
273
273
|
openapi?: OpenApiOptions;
|
|
274
274
|
/** Trace correlation. Default: disabled. */
|
|
275
275
|
telemetry?: TelemetryOptions;
|
|
276
|
+
/**
|
|
277
|
+
* The environment this deployment is running in, for the features that must
|
|
278
|
+
* never exist outside development — today, the OpenAPI document and its UI.
|
|
279
|
+
*
|
|
280
|
+
* **`NODE_ENV` always wins.** This is consulted only when the process
|
|
281
|
+
* declares nothing: `NODE_ENV` unset, or set to whitespace. It cannot make a
|
|
282
|
+
* runtime that identified itself as production serve the document, and no
|
|
283
|
+
* value here overrides one there.
|
|
284
|
+
*
|
|
285
|
+
* Set it when your application validates its own environment variable — an
|
|
286
|
+
* `APP_ENV` your config schema parses — and does not also set `NODE_ENV`.
|
|
287
|
+
* Without it, that deployment is classified as production because absence was
|
|
288
|
+
* the only evidence available, and the document is refused in an environment
|
|
289
|
+
* that never asked for the refusal.
|
|
290
|
+
*
|
|
291
|
+
* Recognized non-production values are `development` and `test`, compared
|
|
292
|
+
* case-insensitively and ignoring surrounding whitespace. Anything else,
|
|
293
|
+
* including an unrecognized name, is production.
|
|
294
|
+
*
|
|
295
|
+
* @example 'development'
|
|
296
|
+
*/
|
|
297
|
+
environment?: string;
|
|
276
298
|
}
|
|
277
299
|
/** Fully-resolved envelope options. */
|
|
278
300
|
interface ResolvedEnvelopeOptions {
|
|
@@ -334,9 +356,9 @@ interface ResolvedOpenApiOptions {
|
|
|
334
356
|
}
|
|
335
357
|
/**
|
|
336
358
|
* The effective, defaults-applied configuration exposed under
|
|
337
|
-
* `BYMAX_CORE_OPTIONS`. Fields with a documented default are always present
|
|
338
|
-
*
|
|
339
|
-
*
|
|
359
|
+
* `BYMAX_CORE_OPTIONS`. Fields with a documented default are always present.
|
|
360
|
+
* Two fields have no default and are absent unless the consumer supplies them:
|
|
361
|
+
* `timing.slowRequestThresholdMs`, and `environment`.
|
|
340
362
|
*/
|
|
341
363
|
interface ResolvedCoreOptions {
|
|
342
364
|
envelope: ResolvedEnvelopeOptions;
|
|
@@ -345,6 +367,12 @@ interface ResolvedCoreOptions {
|
|
|
345
367
|
metrics: ResolvedMetricsOptions;
|
|
346
368
|
openapi: ResolvedOpenApiOptions;
|
|
347
369
|
telemetry: ResolvedTelemetryOptions;
|
|
370
|
+
/**
|
|
371
|
+
* The environment the application declared, carried through so the bootstrap
|
|
372
|
+
* helper classifies the runtime from the same two inputs the resolver did.
|
|
373
|
+
* Absent when the application declared none.
|
|
374
|
+
*/
|
|
375
|
+
environment?: string;
|
|
348
376
|
}
|
|
349
377
|
|
|
350
378
|
/** Non-option extras accepted by `forRoot` / `forRootAsync`. */
|
package/dist/index.mjs
CHANGED
|
@@ -28,6 +28,13 @@ function isProductionRuntime(value = process.env["NODE_ENV"]) {
|
|
|
28
28
|
}
|
|
29
29
|
return !NON_PRODUCTION_ENVIRONMENTS.has(value.trim().toLowerCase());
|
|
30
30
|
}
|
|
31
|
+
function runtimeEnvironmentName(declared) {
|
|
32
|
+
const fromProcess = process.env["NODE_ENV"];
|
|
33
|
+
if (fromProcess !== void 0 && fromProcess.trim() !== "") {
|
|
34
|
+
return fromProcess;
|
|
35
|
+
}
|
|
36
|
+
return declared;
|
|
37
|
+
}
|
|
31
38
|
|
|
32
39
|
// src/core.options.ts
|
|
33
40
|
var DEFAULT_INDICATOR_TIMEOUT_MS = 5e3;
|
|
@@ -90,9 +97,9 @@ function cloneServers(raw) {
|
|
|
90
97
|
(server) => server.description === void 0 ? { url: server.url } : { url: server.url, description: server.description }
|
|
91
98
|
);
|
|
92
99
|
}
|
|
93
|
-
function resolveOpenApi(raw) {
|
|
100
|
+
function resolveOpenApi(raw, declaredEnvironment) {
|
|
94
101
|
const requested = raw?.enabled ?? false;
|
|
95
|
-
const production = isProductionRuntime();
|
|
102
|
+
const production = isProductionRuntime(runtimeEnvironmentName(declaredEnvironment));
|
|
96
103
|
return {
|
|
97
104
|
enabled: requested && !production,
|
|
98
105
|
suppressedInProduction: requested && production,
|
|
@@ -129,8 +136,12 @@ function normalizeCoreOptions(raw) {
|
|
|
129
136
|
timing: resolveTiming(raw?.timing),
|
|
130
137
|
health: resolveHealth(raw?.health),
|
|
131
138
|
metrics: resolveMetrics(raw?.metrics),
|
|
132
|
-
openapi: resolveOpenApi(raw?.openapi),
|
|
133
|
-
telemetry: resolveTelemetry(raw?.telemetry)
|
|
139
|
+
openapi: resolveOpenApi(raw?.openapi, raw?.environment),
|
|
140
|
+
telemetry: resolveTelemetry(raw?.telemetry),
|
|
141
|
+
// Spread rather than assigned so an application that declared nothing has
|
|
142
|
+
// no `environment` member at all, matching every other optional member on
|
|
143
|
+
// this snapshot under `exactOptionalPropertyTypes`.
|
|
144
|
+
...raw?.environment === void 0 ? {} : { environment: raw.environment }
|
|
134
145
|
});
|
|
135
146
|
}
|
|
136
147
|
normalizeCoreOptions();
|
package/dist/openapi/index.cjs
CHANGED
|
@@ -16,6 +16,13 @@ function isProductionRuntime(value = process.env["NODE_ENV"]) {
|
|
|
16
16
|
}
|
|
17
17
|
return !NON_PRODUCTION_ENVIRONMENTS.has(value.trim().toLowerCase());
|
|
18
18
|
}
|
|
19
|
+
function runtimeEnvironmentName(declared) {
|
|
20
|
+
const fromProcess = process.env["NODE_ENV"];
|
|
21
|
+
if (fromProcess !== void 0 && fromProcess.trim() !== "") {
|
|
22
|
+
return fromProcess;
|
|
23
|
+
}
|
|
24
|
+
return declared;
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
// src/discovery.ts
|
|
21
28
|
function labelFor(className, token) {
|
|
@@ -653,7 +660,7 @@ async function applyBymaxOpenApi(app) {
|
|
|
653
660
|
const logger = new common.Logger("BymaxCoreModule");
|
|
654
661
|
const resolved = resolveCoreOptions(app);
|
|
655
662
|
const options = resolved.openapi;
|
|
656
|
-
if (isProductionRuntime()) {
|
|
663
|
+
if (isProductionRuntime(runtimeEnvironmentName(resolved.environment))) {
|
|
657
664
|
if (options.suppressedInProduction || options.enabled) {
|
|
658
665
|
logger.warn(
|
|
659
666
|
'openapi.enabled was requested but the OpenAPI document is never served in production. Set NODE_ENV to "development" or "test" to serve it.'
|
package/dist/openapi/index.mjs
CHANGED
|
@@ -14,6 +14,13 @@ function isProductionRuntime(value = process.env["NODE_ENV"]) {
|
|
|
14
14
|
}
|
|
15
15
|
return !NON_PRODUCTION_ENVIRONMENTS.has(value.trim().toLowerCase());
|
|
16
16
|
}
|
|
17
|
+
function runtimeEnvironmentName(declared) {
|
|
18
|
+
const fromProcess = process.env["NODE_ENV"];
|
|
19
|
+
if (fromProcess !== void 0 && fromProcess.trim() !== "") {
|
|
20
|
+
return fromProcess;
|
|
21
|
+
}
|
|
22
|
+
return declared;
|
|
23
|
+
}
|
|
17
24
|
|
|
18
25
|
// src/discovery.ts
|
|
19
26
|
function labelFor(className, token) {
|
|
@@ -651,7 +658,7 @@ async function applyBymaxOpenApi(app) {
|
|
|
651
658
|
const logger = new Logger("BymaxCoreModule");
|
|
652
659
|
const resolved = resolveCoreOptions(app);
|
|
653
660
|
const options = resolved.openapi;
|
|
654
|
-
if (isProductionRuntime()) {
|
|
661
|
+
if (isProductionRuntime(runtimeEnvironmentName(resolved.environment))) {
|
|
655
662
|
if (options.suppressedInProduction || options.enabled) {
|
|
656
663
|
logger.warn(
|
|
657
664
|
'openapi.enabled was requested but the OpenAPI document is never served in production. Set NODE_ENV to "development" or "test" to serve it.'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-core",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
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.",
|
|
5
5
|
"author": "Bymax One <support@bymax.one>",
|
|
6
6
|
"license": "MIT",
|