@bymax-one/nest-core 1.1.0 → 1.1.1

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,39 @@ heading here.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [1.1.1] - 2026-08-07
15
+
16
+ **Documentation and tooling.** `dist/` differs from `1.1.0` only in the text of the comments
17
+ described below; no runtime code changed.
18
+
19
+ ### Changed
20
+
21
+ - **Equivalent mutants are documented in the source instead of only in the report.** The nine
22
+ now carry `// Stryker disable next-line <Mutator>: <reason>` on the line they apply to,
23
+ which is the convention now shared across the `@bymax-one/nest-*` libraries. The measured
24
+ score moves from **98.76%** to **100%** — no test and no production logic changed; Stryker
25
+ excludes an ignored mutant from the denominator instead of counting it as one the suite
26
+ failed to kill.
27
+
28
+ Two needed the block `disable`/`restore` form, because `next-line` binds to the following
29
+ statement and those mutants do not sit on one: the cursor-parse catch body, and
30
+ `setExtras({ isGlobal: true }, …)` inside the builder chain. The second was already known —
31
+ the note above that call said a directive does not attach there and left the mutant counted.
32
+ That prose is now the directive's reason, and the block brackets the builder statement alone
33
+ so nothing else in the file loses its `ObjectLiteral` mutants. Both were confirmed by
34
+ running them: the pass reports zero survivors where it reported nine.
35
+
36
+ - The README claimed **Zero suppressions** as a rule. It states what is true now: every
37
+ suppression carries its reason, in the grammar Stryker parses.
38
+
39
+ ### Added
40
+
41
+ - `check:mutants` gate (`scripts/check-mutation-directives.mjs`) — validates every
42
+ `// Stryker` comment against the parser's own regular expression, rejecting a reason
43
+ written after `--` instead of a colon, a reason wrapped onto a second comment line, a stray
44
+ comma in the mutator list, and a mutator name Stryker does not know, which matches nothing
45
+ and so silences nothing. Wired into CI and `prepublishOnly`.
46
+
14
47
  ## [1.1.0] - 2026-08-05
15
48
 
16
49
  Four optional integrations, each off by default and each loading nothing until it
@@ -230,4 +263,5 @@ have regressed from. They are kept because the reasoning is worth having.
230
263
  [1.1.0]: https://github.com/bymaxone/nest-core/compare/v1.0.1...v1.1.0
231
264
  [1.0.1]: https://github.com/bymaxone/nest-core/compare/v1.0.0...v1.0.1
232
265
  [1.0.0]: https://github.com/bymaxone/nest-core/releases/tag/v1.0.0
233
- [Unreleased]: https://github.com/bymaxone/nest-core/compare/v1.1.0...HEAD
266
+ [1.1.1]: https://github.com/bymaxone/nest-core/compare/v1.1.0...v1.1.1
267
+ [Unreleased]: https://github.com/bymaxone/nest-core/compare/v1.1.1...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-98.76%25-brightgreen?style=flat-square&colorA=000000" alt="mutation score" /></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-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
- - ✅ **98.76% mutation score** — verified with [Stryker](https://stryker-mutator.io/) at
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 remain are documented equivalents rather than suppressions
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
- - ✅ **Zero suppressions** — no coverage or mutation directives in the production source
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
@@ -483,10 +483,12 @@ exports.BymaxExceptionFilter = class BymaxExceptionFilter {
483
483
  path: context.path,
484
484
  now: this.now,
485
485
  ...details !== void 0 ? { details } : {},
486
+ // 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
487
  ...context.correlationId !== void 0 ? { correlationId: context.correlationId } : {},
487
488
  // Gated separately from the context above: the trace id reaches the
488
489
  // observability seam either way, and the response body only when the
489
490
  // operator opted into publishing it.
491
+ // 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
492
  ...this.options.telemetry.exposeTraceId && context.traceId !== void 0 ? { traceId: context.traceId } : {}
491
493
  });
492
494
  }
package/dist/index.mjs CHANGED
@@ -481,10 +481,12 @@ var BymaxExceptionFilter = class {
481
481
  path: context.path,
482
482
  now: this.now,
483
483
  ...details !== void 0 ? { details } : {},
484
+ // 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
485
  ...context.correlationId !== void 0 ? { correlationId: context.correlationId } : {},
485
486
  // Gated separately from the context above: the trace id reaches the
486
487
  // observability seam either way, and the response body only when the
487
488
  // operator opted into publishing it.
489
+ // 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
490
  ...this.options.telemetry.exposeTraceId && context.traceId !== void 0 ? { traceId: context.traceId } : {}
489
491
  });
490
492
  }
@@ -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.1.0",
3
+ "version": "1.1.1",
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})\"",
@@ -88,7 +89,7 @@
88
89
  "mutation:dry-run": "stryker run --dryRunOnly",
89
90
  "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",