@bymax-one/nest-core 1.1.0 → 1.2.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 +73 -1
- package/README.md +8 -5
- package/dist/index.cjs +41 -15
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.mjs +42 -16
- package/dist/pagination/index.cjs +1 -0
- package/dist/pagination/index.mjs +1 -0
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,76 @@ heading here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.2.0] - 2026-08-08
|
|
15
|
+
|
|
16
|
+
Both entries change what a caller receives, which is why this is a minor rather than a patch: an
|
|
17
|
+
application wiring `@bymax-one/nest-auth` starts seeing that library's own error codes where it
|
|
18
|
+
previously saw one collapsed `BYMAX_BAD_REQUEST`, and a deployment with a feature disabled starts
|
|
19
|
+
answering `404` where it answered `500`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **A feature disabled on the `forRootAsync` path answers `404` instead of `500`.** Route metadata
|
|
24
|
+
is fixed before the async options resolve, so the health and metrics controllers register
|
|
25
|
+
regardless and guard at request time. That guard threw a plain `Error`, which the envelope
|
|
26
|
+
renders as `BYMAX_INTERNAL_ERROR` — so every consumer registering asynchronously with
|
|
27
|
+
`metrics: { enabled: false }`, which is the ordinary configuration and the one that keeps the
|
|
28
|
+
optional `prom-client` peer unloaded, served an unauthenticated `/metrics` that answered a
|
|
29
|
+
server error to anyone who asked. It counted as a real failure in alerting, in error budgets and
|
|
30
|
+
in any uptime check pointed at the service, describing a state nothing was wrong with.
|
|
31
|
+
|
|
32
|
+
The route now reads as absent, which is what the caller would have seen had the framework been
|
|
33
|
+
able to skip the registration. Only the feature's _absence_ is normalised: a resolved path that
|
|
34
|
+
disagrees with the route the controller was registered at is a genuine misconfiguration and
|
|
35
|
+
still throws.
|
|
36
|
+
|
|
37
|
+
- **A domain error's `details` reach the caller, and a nested `{ error: { … } }` body is read as
|
|
38
|
+
readily as a flat one.** The filter passed an explicit `code` through but dropped the structured
|
|
39
|
+
context beside it, and recognised the fields only when they sat directly on the response.
|
|
40
|
+
|
|
41
|
+
`@bymax-one/nest-auth` builds `{ error: { code, message, details } }`, so a backend wiring both
|
|
42
|
+
libraries rendered every distinct auth failure identically — a duplicate e-mail, a password below
|
|
43
|
+
the policy floor, a missing field all arrived as `BYMAX_BAD_REQUEST` / `"Auth Exception"` with no
|
|
44
|
+
details. A client could not branch on the failure, and neither could whoever was debugging it.
|
|
45
|
+
|
|
46
|
+
A nested object is followed only when it carries a string `code`, since `error` is an ordinary
|
|
47
|
+
word for a response body to use; a flat code still wins over a nested one; and a `details` value
|
|
48
|
+
that is neither an array nor an object — including the `null` `AuthException` writes to mean
|
|
49
|
+
"none" — is omitted rather than reshaped, so the field stays present only when context exists.
|
|
50
|
+
|
|
51
|
+
## [1.1.1] - 2026-08-07
|
|
52
|
+
|
|
53
|
+
**Documentation and tooling.** `dist/` differs from `1.1.0` only in the text of the comments
|
|
54
|
+
described below; no runtime code changed.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- **Equivalent mutants are documented in the source instead of only in the report.** The nine
|
|
59
|
+
now carry `// Stryker disable next-line <Mutator>: <reason>` on the line they apply to,
|
|
60
|
+
which is the convention now shared across the `@bymax-one/nest-*` libraries. The measured
|
|
61
|
+
score moves from **98.76%** to **100%** — no test and no production logic changed; Stryker
|
|
62
|
+
excludes an ignored mutant from the denominator instead of counting it as one the suite
|
|
63
|
+
failed to kill.
|
|
64
|
+
|
|
65
|
+
Two needed the block `disable`/`restore` form, because `next-line` binds to the following
|
|
66
|
+
statement and those mutants do not sit on one: the cursor-parse catch body, and
|
|
67
|
+
`setExtras({ isGlobal: true }, …)` inside the builder chain. The second was already known —
|
|
68
|
+
the note above that call said a directive does not attach there and left the mutant counted.
|
|
69
|
+
That prose is now the directive's reason, and the block brackets the builder statement alone
|
|
70
|
+
so nothing else in the file loses its `ObjectLiteral` mutants. Both were confirmed by
|
|
71
|
+
running them: the pass reports zero survivors where it reported nine.
|
|
72
|
+
|
|
73
|
+
- The README claimed **Zero suppressions** as a rule. It states what is true now: every
|
|
74
|
+
suppression carries its reason, in the grammar Stryker parses.
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- `check:mutants` gate (`scripts/check-mutation-directives.mjs`) — validates every
|
|
79
|
+
`// Stryker` comment against the parser's own regular expression, rejecting a reason
|
|
80
|
+
written after `--` instead of a colon, a reason wrapped onto a second comment line, a stray
|
|
81
|
+
comma in the mutator list, and a mutator name Stryker does not know, which matches nothing
|
|
82
|
+
and so silences nothing. Wired into CI and `prepublishOnly`.
|
|
83
|
+
|
|
14
84
|
## [1.1.0] - 2026-08-05
|
|
15
85
|
|
|
16
86
|
Four optional integrations, each off by default and each loading nothing until it
|
|
@@ -230,4 +300,6 @@ have regressed from. They are kept because the reasoning is worth having.
|
|
|
230
300
|
[1.1.0]: https://github.com/bymaxone/nest-core/compare/v1.0.1...v1.1.0
|
|
231
301
|
[1.0.1]: https://github.com/bymaxone/nest-core/compare/v1.0.0...v1.0.1
|
|
232
302
|
[1.0.0]: https://github.com/bymaxone/nest-core/releases/tag/v1.0.0
|
|
233
|
-
[
|
|
303
|
+
[1.1.1]: https://github.com/bymaxone/nest-core/compare/v1.1.0...v1.1.1
|
|
304
|
+
[1.2.0]: https://github.com/bymaxone/nest-core/compare/v1.1.1...v1.2.0
|
|
305
|
+
[Unreleased]: https://github.com/bymaxone/nest-core/compare/v1.2.0...HEAD
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<a href="https://www.npmjs.com/package/@bymax-one/nest-core"><img src="https://img.shields.io/npm/dm/@bymax-one/nest-core?style=flat-square&colorA=000000&colorB=000000" alt="npm downloads" /></a>
|
|
15
15
|
<a href="https://github.com/bymaxone/nest-core/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/bymaxone/nest-core/ci.yml?branch=main&style=flat-square&colorA=000000&label=CI" alt="CI status" /></a>
|
|
16
16
|
<a href="https://github.com/bymaxone/nest-core/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square&colorA=000000" alt="coverage" /></a>
|
|
17
|
-
<a href="https://github.com/bymaxone/nest-core/blob/main/docs/mutation_testing_results.md"><img src="https://img.shields.io/badge/mutation-
|
|
17
|
+
<a href="https://github.com/bymaxone/nest-core/blob/main/docs/mutation_testing_results.md"><img src="https://img.shields.io/badge/mutation-100%25-brightgreen?style=flat-square&colorA=000000" alt="mutation score" /></a>
|
|
18
18
|
<a href="https://scorecard.dev/viewer/?uri=github.com/bymaxone/nest-core"><img src="https://api.scorecard.dev/projects/github.com/bymaxone/nest-core/badge?style=flat-square" alt="OpenSSF Scorecard" /></a>
|
|
19
19
|
<a href="https://github.com/bymaxone/nest-core/blob/main/LICENSE"><img src="https://img.shields.io/github/license/bymaxone/nest-core?style=flat-square&colorA=000000&colorB=000000" alt="license" /></a>
|
|
20
20
|
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript" /></a>
|
|
@@ -917,17 +917,20 @@ This library sits in the path of every request and every failure of every servic
|
|
|
917
917
|
installs it, so the suite is held to a bar beyond "the tests pass".
|
|
918
918
|
|
|
919
919
|
- ✅ **100% line coverage** — statements, branches, functions and lines, enforced as a gate
|
|
920
|
-
- ✅ **
|
|
920
|
+
- ✅ **100% mutation score** — verified with [Stryker](https://stryker-mutator.io/) at
|
|
921
921
|
`break: 95`; every killable survivor was killed by a strengthened test, with no production
|
|
922
|
-
change, and the nine that
|
|
923
|
-
([report](./docs/mutation_testing_results.md))
|
|
922
|
+
change, and the nine equivalents that no test can kill each carry their reason on the line
|
|
923
|
+
they apply to ([report](./docs/mutation_testing_results.md))
|
|
924
924
|
- ✅ **End-to-end against a real application** — the filter, the interceptor, the health and
|
|
925
925
|
metrics routes, the served OpenAPI document, discovered indicators, contributed metrics and
|
|
926
926
|
trace correlation are all exercised through a booted Nest app, not against mocks of it
|
|
927
927
|
- ✅ **Published-artifact gates** — `check:exports` resolves the types the way each module
|
|
928
928
|
system does, `check:runtime` loads every subpath from the packed tarball in ESM and
|
|
929
929
|
CommonJS, and `check:published` compiles this README's snippets against `dist/`
|
|
930
|
-
- ✅ **
|
|
930
|
+
- ✅ **Every suppression carries its reason** — no coverage directives anywhere; each
|
|
931
|
+
`// Stryker disable` in the production source names, after the `:` Stryker reads it from,
|
|
932
|
+
why the mutant it silences is behaviour-preserving, and `check:mutants` proves those reasons
|
|
933
|
+
parse so they reach the mutation report rather than the `Ignored using a comment` fallback
|
|
931
934
|
|
|
932
935
|
```bash
|
|
933
936
|
pnpm test # unit suite
|
package/dist/index.cjs
CHANGED
|
@@ -287,12 +287,28 @@ function buildErrorEnvelope(input) {
|
|
|
287
287
|
var INTERNAL_ERROR_STATUS = 500;
|
|
288
288
|
var INTERNAL_ERROR_MESSAGE = "Internal server error";
|
|
289
289
|
var VALIDATION_FAILED_MESSAGE = "Validation failed";
|
|
290
|
-
function
|
|
291
|
-
if (typeof response !== "object" || response === null
|
|
290
|
+
function resolveErrorCarrier(response) {
|
|
291
|
+
if (typeof response !== "object" || response === null) {
|
|
292
292
|
return void 0;
|
|
293
293
|
}
|
|
294
|
-
|
|
295
|
-
|
|
294
|
+
if (hasStringCode(response)) {
|
|
295
|
+
return response;
|
|
296
|
+
}
|
|
297
|
+
const nested = response.error;
|
|
298
|
+
if (typeof nested === "object" && nested !== null && hasStringCode(nested)) {
|
|
299
|
+
return nested;
|
|
300
|
+
}
|
|
301
|
+
return void 0;
|
|
302
|
+
}
|
|
303
|
+
function hasStringCode(value) {
|
|
304
|
+
return "code" in value && typeof value.code === "string";
|
|
305
|
+
}
|
|
306
|
+
function extractExplicitCode(carrier) {
|
|
307
|
+
return carrier.code;
|
|
308
|
+
}
|
|
309
|
+
function extractExplicitDetails(carrier) {
|
|
310
|
+
const details = carrier.details;
|
|
311
|
+
return typeof details === "object" && details !== null ? details : void 0;
|
|
296
312
|
}
|
|
297
313
|
function isValidationResponse(response) {
|
|
298
314
|
return typeof response === "object" && response !== null && "message" in response && Array.isArray(response.message);
|
|
@@ -302,12 +318,13 @@ function toValidationDetails(violations) {
|
|
|
302
318
|
(violation) => typeof violation === "string" ? { issue: violation } : violation
|
|
303
319
|
);
|
|
304
320
|
}
|
|
305
|
-
function extractHttpMessage(response, exception) {
|
|
321
|
+
function extractHttpMessage(response, exception, carrier) {
|
|
306
322
|
if (typeof response === "string") {
|
|
307
323
|
return response;
|
|
308
324
|
}
|
|
309
|
-
|
|
310
|
-
|
|
325
|
+
const source = carrier ?? response;
|
|
326
|
+
if (typeof source === "object" && source !== null && "message" in source) {
|
|
327
|
+
const message = source.message;
|
|
311
328
|
if (typeof message === "string") {
|
|
312
329
|
return message;
|
|
313
330
|
}
|
|
@@ -414,9 +431,10 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
|
|
|
414
431
|
return this.mapUnknown(exception, context);
|
|
415
432
|
}
|
|
416
433
|
/**
|
|
417
|
-
* Map an `HttpException` to the envelope.
|
|
418
|
-
*
|
|
419
|
-
*
|
|
434
|
+
* Map an `HttpException` to the envelope. A domain error passes its own code,
|
|
435
|
+
* message and details through, whether it wrote them flat on the response or
|
|
436
|
+
* nested under `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED`
|
|
437
|
+
* with structured details; everything else derives its code from the status.
|
|
420
438
|
*
|
|
421
439
|
* @param exception - The HTTP exception to format.
|
|
422
440
|
* @param context - The neutral request context.
|
|
@@ -425,9 +443,15 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
|
|
|
425
443
|
mapHttpException(exception, context) {
|
|
426
444
|
const status = exception.getStatus();
|
|
427
445
|
const response = exception.getResponse();
|
|
428
|
-
const
|
|
429
|
-
if (
|
|
430
|
-
return this.toEnvelope(
|
|
446
|
+
const carrier = resolveErrorCarrier(response);
|
|
447
|
+
if (carrier !== void 0) {
|
|
448
|
+
return this.toEnvelope(
|
|
449
|
+
status,
|
|
450
|
+
extractExplicitCode(carrier),
|
|
451
|
+
extractHttpMessage(response, exception, carrier),
|
|
452
|
+
context,
|
|
453
|
+
extractExplicitDetails(carrier)
|
|
454
|
+
);
|
|
431
455
|
}
|
|
432
456
|
if (isValidationResponse(response)) {
|
|
433
457
|
return this.toEnvelope(
|
|
@@ -483,10 +507,12 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
|
|
|
483
507
|
path: context.path,
|
|
484
508
|
now: this.now,
|
|
485
509
|
...details !== void 0 ? { details } : {},
|
|
510
|
+
// 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.
|
|
486
511
|
...context.correlationId !== void 0 ? { correlationId: context.correlationId } : {},
|
|
487
512
|
// Gated separately from the context above: the trace id reaches the
|
|
488
513
|
// observability seam either way, and the response body only when the
|
|
489
514
|
// operator opted into publishing it.
|
|
515
|
+
// Stryker disable next-line ConditionalExpression: equivalent — same as the correlation id above. The always-spread form hands the builder `traceId: undefined`, which `buildErrorEnvelope` re-guards and omits. The call-site guard exists because `exactOptionalPropertyTypes` rejects an explicit `undefined` for an optional field, not because it changes the output.
|
|
490
516
|
...this.options.telemetry.exposeTraceId && context.traceId !== void 0 ? { traceId: context.traceId } : {}
|
|
491
517
|
});
|
|
492
518
|
}
|
|
@@ -680,8 +706,8 @@ var PassThroughInterceptor = class {
|
|
|
680
706
|
};
|
|
681
707
|
function assertAsyncFeatureEnabled(feature, enabled) {
|
|
682
708
|
if (!enabled) {
|
|
683
|
-
throw new
|
|
684
|
-
`[BymaxCoreModule] The "${feature}"
|
|
709
|
+
throw new common.NotFoundException(
|
|
710
|
+
`[BymaxCoreModule] The "${feature}" feature is disabled, so this route does not exist.`
|
|
685
711
|
);
|
|
686
712
|
}
|
|
687
713
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -464,9 +464,10 @@ declare class BymaxExceptionFilter implements ExceptionFilter {
|
|
|
464
464
|
*/
|
|
465
465
|
private buildEnvelope;
|
|
466
466
|
/**
|
|
467
|
-
* Map an `HttpException` to the envelope.
|
|
468
|
-
*
|
|
469
|
-
*
|
|
467
|
+
* Map an `HttpException` to the envelope. A domain error passes its own code,
|
|
468
|
+
* message and details through, whether it wrote them flat on the response or
|
|
469
|
+
* nested under `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED`
|
|
470
|
+
* with structured details; everything else derives its code from the status.
|
|
470
471
|
*
|
|
471
472
|
* @param exception - The HTTP exception to format.
|
|
472
473
|
* @param context - The neutral request context.
|
package/dist/index.d.ts
CHANGED
|
@@ -464,9 +464,10 @@ declare class BymaxExceptionFilter implements ExceptionFilter {
|
|
|
464
464
|
*/
|
|
465
465
|
private buildEnvelope;
|
|
466
466
|
/**
|
|
467
|
-
* Map an `HttpException` to the envelope.
|
|
468
|
-
*
|
|
469
|
-
*
|
|
467
|
+
* Map an `HttpException` to the envelope. A domain error passes its own code,
|
|
468
|
+
* message and details through, whether it wrote them flat on the response or
|
|
469
|
+
* nested under `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED`
|
|
470
|
+
* with structured details; everything else derives its code from the status.
|
|
470
471
|
*
|
|
471
472
|
* @param exception - The HTTP exception to format.
|
|
472
473
|
* @param context - The neutral request context.
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Catch, Inject, Optional, Injectable, ConfigurableModuleBuilder, Module, HttpException, Logger, Get, Res, Controller, HttpStatus } from '@nestjs/common';
|
|
1
|
+
import { Catch, Inject, Optional, Injectable, ConfigurableModuleBuilder, Module, HttpException, Logger, Get, Res, Controller, HttpStatus, NotFoundException } from '@nestjs/common';
|
|
2
2
|
import { HttpAdapterHost, DiscoveryService, Reflector, BaseExceptionFilter, APP_FILTER, APP_INTERCEPTOR, DiscoveryModule } from '@nestjs/core';
|
|
3
3
|
import { tap, catchError, throwError } from 'rxjs';
|
|
4
4
|
|
|
@@ -285,12 +285,28 @@ function buildErrorEnvelope(input) {
|
|
|
285
285
|
var INTERNAL_ERROR_STATUS = 500;
|
|
286
286
|
var INTERNAL_ERROR_MESSAGE = "Internal server error";
|
|
287
287
|
var VALIDATION_FAILED_MESSAGE = "Validation failed";
|
|
288
|
-
function
|
|
289
|
-
if (typeof response !== "object" || response === null
|
|
288
|
+
function resolveErrorCarrier(response) {
|
|
289
|
+
if (typeof response !== "object" || response === null) {
|
|
290
290
|
return void 0;
|
|
291
291
|
}
|
|
292
|
-
|
|
293
|
-
|
|
292
|
+
if (hasStringCode(response)) {
|
|
293
|
+
return response;
|
|
294
|
+
}
|
|
295
|
+
const nested = response.error;
|
|
296
|
+
if (typeof nested === "object" && nested !== null && hasStringCode(nested)) {
|
|
297
|
+
return nested;
|
|
298
|
+
}
|
|
299
|
+
return void 0;
|
|
300
|
+
}
|
|
301
|
+
function hasStringCode(value) {
|
|
302
|
+
return "code" in value && typeof value.code === "string";
|
|
303
|
+
}
|
|
304
|
+
function extractExplicitCode(carrier) {
|
|
305
|
+
return carrier.code;
|
|
306
|
+
}
|
|
307
|
+
function extractExplicitDetails(carrier) {
|
|
308
|
+
const details = carrier.details;
|
|
309
|
+
return typeof details === "object" && details !== null ? details : void 0;
|
|
294
310
|
}
|
|
295
311
|
function isValidationResponse(response) {
|
|
296
312
|
return typeof response === "object" && response !== null && "message" in response && Array.isArray(response.message);
|
|
@@ -300,12 +316,13 @@ function toValidationDetails(violations) {
|
|
|
300
316
|
(violation) => typeof violation === "string" ? { issue: violation } : violation
|
|
301
317
|
);
|
|
302
318
|
}
|
|
303
|
-
function extractHttpMessage(response, exception) {
|
|
319
|
+
function extractHttpMessage(response, exception, carrier) {
|
|
304
320
|
if (typeof response === "string") {
|
|
305
321
|
return response;
|
|
306
322
|
}
|
|
307
|
-
|
|
308
|
-
|
|
323
|
+
const source = carrier ?? response;
|
|
324
|
+
if (typeof source === "object" && source !== null && "message" in source) {
|
|
325
|
+
const message = source.message;
|
|
309
326
|
if (typeof message === "string") {
|
|
310
327
|
return message;
|
|
311
328
|
}
|
|
@@ -412,9 +429,10 @@ var BymaxExceptionFilter = class {
|
|
|
412
429
|
return this.mapUnknown(exception, context);
|
|
413
430
|
}
|
|
414
431
|
/**
|
|
415
|
-
* Map an `HttpException` to the envelope.
|
|
416
|
-
*
|
|
417
|
-
*
|
|
432
|
+
* Map an `HttpException` to the envelope. A domain error passes its own code,
|
|
433
|
+
* message and details through, whether it wrote them flat on the response or
|
|
434
|
+
* nested under `error`; the validation shape becomes `BYMAX_VALIDATION_FAILED`
|
|
435
|
+
* with structured details; everything else derives its code from the status.
|
|
418
436
|
*
|
|
419
437
|
* @param exception - The HTTP exception to format.
|
|
420
438
|
* @param context - The neutral request context.
|
|
@@ -423,9 +441,15 @@ var BymaxExceptionFilter = class {
|
|
|
423
441
|
mapHttpException(exception, context) {
|
|
424
442
|
const status = exception.getStatus();
|
|
425
443
|
const response = exception.getResponse();
|
|
426
|
-
const
|
|
427
|
-
if (
|
|
428
|
-
return this.toEnvelope(
|
|
444
|
+
const carrier = resolveErrorCarrier(response);
|
|
445
|
+
if (carrier !== void 0) {
|
|
446
|
+
return this.toEnvelope(
|
|
447
|
+
status,
|
|
448
|
+
extractExplicitCode(carrier),
|
|
449
|
+
extractHttpMessage(response, exception, carrier),
|
|
450
|
+
context,
|
|
451
|
+
extractExplicitDetails(carrier)
|
|
452
|
+
);
|
|
429
453
|
}
|
|
430
454
|
if (isValidationResponse(response)) {
|
|
431
455
|
return this.toEnvelope(
|
|
@@ -481,10 +505,12 @@ var BymaxExceptionFilter = class {
|
|
|
481
505
|
path: context.path,
|
|
482
506
|
now: this.now,
|
|
483
507
|
...details !== void 0 ? { details } : {},
|
|
508
|
+
// 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.
|
|
484
509
|
...context.correlationId !== void 0 ? { correlationId: context.correlationId } : {},
|
|
485
510
|
// Gated separately from the context above: the trace id reaches the
|
|
486
511
|
// observability seam either way, and the response body only when the
|
|
487
512
|
// operator opted into publishing it.
|
|
513
|
+
// Stryker disable next-line ConditionalExpression: equivalent — same as the correlation id above. The always-spread form hands the builder `traceId: undefined`, which `buildErrorEnvelope` re-guards and omits. The call-site guard exists because `exactOptionalPropertyTypes` rejects an explicit `undefined` for an optional field, not because it changes the output.
|
|
488
514
|
...this.options.telemetry.exposeTraceId && context.traceId !== void 0 ? { traceId: context.traceId } : {}
|
|
489
515
|
});
|
|
490
516
|
}
|
|
@@ -678,8 +704,8 @@ var PassThroughInterceptor = class {
|
|
|
678
704
|
};
|
|
679
705
|
function assertAsyncFeatureEnabled(feature, enabled) {
|
|
680
706
|
if (!enabled) {
|
|
681
|
-
throw new
|
|
682
|
-
`[BymaxCoreModule] The "${feature}"
|
|
707
|
+
throw new NotFoundException(
|
|
708
|
+
`[BymaxCoreModule] The "${feature}" feature is disabled, so this route does not exist.`
|
|
683
709
|
);
|
|
684
710
|
}
|
|
685
711
|
}
|
|
@@ -57,6 +57,7 @@ function isOrderingKeyRecord(value) {
|
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
59
|
return Object.values(value).every(
|
|
60
|
+
// Stryker disable next-line ConditionalExpression: equivalent — replacing the `typeof entry === 'number'` guard with `true` leaves `Number.isFinite(entry)`, which never coerces and so already returns `false` for every non-number. The guard is redundant and the predicate is unchanged for every value.
|
|
60
61
|
(entry) => typeof entry === "string" || typeof entry === "number" && Number.isFinite(entry)
|
|
61
62
|
);
|
|
62
63
|
}
|
|
@@ -55,6 +55,7 @@ function isOrderingKeyRecord(value) {
|
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
57
|
return Object.values(value).every(
|
|
58
|
+
// Stryker disable next-line ConditionalExpression: equivalent — replacing the `typeof entry === 'number'` guard with `true` leaves `Number.isFinite(entry)`, which never coerces and so already returns `false` for every non-number. The guard is redundant and the predicate is unchanged for every value.
|
|
58
59
|
(entry) => typeof entry === "string" || typeof entry === "number" && Number.isFinite(entry)
|
|
59
60
|
);
|
|
60
61
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
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",
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"scripts": {
|
|
79
79
|
"build": "pnpm clean && tsup",
|
|
80
80
|
"check:exports": "attw --pack . --profile strict",
|
|
81
|
+
"check:mutants": "node scripts/check-mutation-directives.mjs",
|
|
81
82
|
"check:published": "node scripts/check-published-surface.mjs",
|
|
82
83
|
"check:runtime": "node scripts/check-consumer-runtime.mjs",
|
|
83
84
|
"clean": "node -e \"const fs=require('node:fs');for(const d of ['dist','coverage'])fs.rmSync(d,{recursive:true,force:true})\"",
|
|
@@ -85,10 +86,10 @@
|
|
|
85
86
|
"lint": "eslint --no-error-on-unmatched-pattern src scripts test",
|
|
86
87
|
"lint:fix": "eslint --no-error-on-unmatched-pattern src scripts test --fix",
|
|
87
88
|
"mutation": "stryker run",
|
|
89
|
+
"mutation:full": "node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run",
|
|
88
90
|
"mutation:dry-run": "stryker run --dryRunOnly",
|
|
89
|
-
"mutation:incremental": "stryker run --incremental",
|
|
90
91
|
"prepare": "husky",
|
|
91
|
-
"prepublishOnly": "pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published",
|
|
92
|
+
"prepublishOnly": "pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published",
|
|
92
93
|
"release": "npm publish --provenance --access public",
|
|
93
94
|
"size": "node scripts/check-size.mjs",
|
|
94
95
|
"test": "jest",
|