@bymax-one/nest-logger 1.0.4 → 1.0.6

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +86 -1
  2. package/README.md +33 -12
  3. package/package.json +28 -24
package/CHANGELOG.md CHANGED
@@ -11,6 +11,89 @@ heading here.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [1.0.6] - 2026-08-06
15
+
16
+ **Tests and documentation only.** `dist/` is byte-identical to `1.0.5`.
17
+
18
+ An earlier revision of this branch added inline `// Stryker disable` comments and reported a
19
+ 99.73% score off the back of them. That is against this package's own plan, which forbids them
20
+ because they ship in the unminified bundle and eat the server subpath's brotli budget. They are
21
+ gone; the score is the measured 97.42% with the equivalents documented instead.
22
+
23
+ ### Tests
24
+
25
+ - `detectOtelTraceApi` resolves `@opentelemetry/api`, and the specifier was pinned by nothing —
26
+ the spec mocks `createRequire`, so the resolver ignored what it was handed. Renaming the module
27
+ would have kept the suite green while every deployment silently lost trace correlation, because
28
+ a failed resolve is swallowed by design.
29
+ - `useNestLogger` gained a feature-module case, the shape where the provider sits outside the host
30
+ module's own injector.
31
+
32
+ ## [1.0.5] - 2026-07-30
33
+
34
+ ### Security
35
+
36
+ - **Peer floors raised to exclude known-vulnerable NestJS versions.** `@nestjs/common ^11.0.0`
37
+ admitted 11.0.0–11.0.15, carrying [GHSA-cj7v-w2c7-cp7c](https://github.com/advisories/GHSA-cj7v-w2c7-cp7c)
38
+ (remote code execution via the `Content-Type` header, patched in 11.0.16), and
39
+ `@nestjs/core ^11.0.0` admitted everything up to 11.1.17, carrying
40
+ [GHSA-36xv-jgw5-4q75](https://github.com/advisories/GHSA-36xv-jgw5-4q75) (patched in 11.1.18).
41
+
42
+ A peer range states which versions this library supports. A floor below a published
43
+ advisory told a consumer a vulnerable install was supported, and nothing in their
44
+ tooling contradicted it — the install resolved cleanly and silently. Floors are now
45
+ `^11.0.16` and `^11.1.18`.
46
+
47
+ Shipped as a patch, which is where a security fix belongs; a minor would reach the
48
+ same installs anyway, since `^1.0.4` accepts `1.1.0` as readily as `1.0.5`. No
49
+ runtime behaviour changed.
50
+
51
+ ### Fixed
52
+
53
+ - **The custom-destination examples did not compile against `ILogDestination`.**
54
+ The published interface takes a serialized line:
55
+
56
+ ```ts
57
+ write(payload: string): void | Promise<void> // newline-terminated JSON, UTF-8
58
+ ```
59
+
60
+ All three examples in the README — `LokiDestination`, `PrismaLogDestination`,
61
+ `RollingFileDestination` — and the API reference table declared
62
+ `write(entry: LogEntry): void`, so anyone following the README to build a
63
+ destination got:
64
+
65
+ ```
66
+ TS2416: Property 'write' in type 'LokiDestination' is not assignable to the
67
+ same property in base type 'ILogDestination'.
68
+ ```
69
+
70
+ `ILogDestination` is this library's extension point, so the documentation was
71
+ teaching the wrong shape for the one thing consumers are meant to implement.
72
+ `RollingFileDestination` compounded it by re-serializing a payload that already
73
+ **is** newline-terminated JSON — anyone casting past the type error would have
74
+ written double-encoded lines to disk.
75
+
76
+ Each example now does what its own job requires rather than all three doing the
77
+ same thing: Loki buffers the line verbatim, Prisma parses because it stores
78
+ individual columns, rolling-file writes it through untouched.
79
+
80
+ - **The request-id middleware typed `req` as the global DOM `Request`**, whose
81
+ `headers` is a `Headers` instance and cannot be indexed (`TS7052`). `1.0.4`
82
+ introduced `LoggableRequest` and `LoggableResponse` for exactly this and exports
83
+ them; the README never adopted them. It does now, and narrows the
84
+ `string | string[]` a repeated header produces.
85
+
86
+ ### Added
87
+
88
+ - **`pnpm check:published`** — verifies that the README's links resolve, that its
89
+ TypeScript snippets and the type tests compile against the built package, and
90
+ that every `v*.*.*` tag has a `## [x.y.z]` CHANGELOG section. It resolves the
91
+ package through its `exports` map into `dist/`, which `test:types` cannot do:
92
+ that one maps the package to `./src` through tsconfig `paths` and so never
93
+ compiles what the README claims. Both defects above were found by it.
94
+
95
+ Runs in CI, in `release.yml`, and inside `prepublishOnly`.
96
+
14
97
  ## [1.0.4] - 2026-07-29
15
98
 
16
99
  ### Fixed
@@ -187,7 +270,9 @@ published `dist/` is identical — no runtime behaviour changes for consumers.
187
270
  - Professional CI suite: `ci.yml`, `bench.yml`, `codeql.yml`, `scorecard.yml`,
188
271
  `release.yml`, Dependabot, and issue templates
189
272
 
190
- [Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.0.4...HEAD
273
+ [Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.0.5...HEAD
274
+ [1.0.6]: https://github.com/bymaxone/nest-logger/compare/v1.0.5...v1.0.6
275
+ [1.0.5]: https://github.com/bymaxone/nest-logger/compare/v1.0.4...v1.0.5
191
276
  [1.0.4]: https://github.com/bymaxone/nest-logger/compare/v1.0.3...v1.0.4
192
277
  [1.0.3]: https://github.com/bymaxone/nest-logger/compare/v1.0.2...v1.0.3
193
278
  [1.0.2]: https://github.com/bymaxone/nest-logger/compare/v1.0.1...v1.0.2
package/README.md CHANGED
@@ -337,15 +337,21 @@ Once active, every log entry automatically carries `traceId`, `spanId`, and `tra
337
337
  ```typescript
338
338
  // request-id.middleware.ts
339
339
  import { Injectable, NestMiddleware } from '@nestjs/common'
340
- import { LogContextService } from '@bymax-one/nest-logger'
340
+ import {
341
+ LogContextService,
342
+ type LoggableRequest,
343
+ type LoggableResponse
344
+ } from '@bymax-one/nest-logger'
341
345
  import { randomUUID } from 'node:crypto'
342
346
 
343
347
  @Injectable()
344
348
  export class RequestIdMiddleware implements NestMiddleware {
345
349
  constructor(private readonly logContext: LogContextService) {}
346
350
 
347
- use(req: Request, res: Response, next: () => void) {
348
- const requestId = req.headers['x-request-id'] ?? `r_${randomUUID()}`
351
+ use(req: LoggableRequest, res: LoggableResponse, next: () => void) {
352
+ // A header can arrive repeated, so its type is `string | string[]`.
353
+ const raw = req.headers['x-request-id']
354
+ const requestId = (Array.isArray(raw) ? raw[0] : raw) ?? `r_${randomUUID()}`
349
355
  this.logContext.run({ requestId }, next)
350
356
  }
351
357
  }
@@ -439,7 +445,8 @@ export class LokiDestination implements ILogDestination {
439
445
  private readonly url: string
440
446
  private readonly headers: Record<string, string>
441
447
  private readonly labels: Record<string, string>
442
- private buffer: LogEntry[] = []
448
+ // Serialized lines, not objects — `write` receives the payload already encoded.
449
+ private buffer: string[] = []
443
450
  private flushTimer?: NodeJS.Timeout
444
451
 
445
452
  constructor(opts: {
@@ -466,8 +473,10 @@ export class LokiDestination implements ILogDestination {
466
473
  await this.flush()
467
474
  }
468
475
 
469
- write(entry: LogEntry): void {
470
- this.buffer.push(entry)
476
+ // `payload` already IS the serialized entry, newline-terminated. Buffer it as
477
+ // it arrives; parse only where a field is genuinely needed, as `flush` does.
478
+ write(payload: string): void {
479
+ this.buffer.push(payload)
471
480
  if (this.buffer.length >= 100) void this.flush()
472
481
  }
473
482
 
@@ -478,7 +487,10 @@ export class LokiDestination implements ILogDestination {
478
487
  streams: [
479
488
  {
480
489
  stream: this.labels,
481
- values: batch.map((e) => [String(BigInt(e.time) * 1_000_000n), JSON.stringify(e)])
490
+ values: batch.map((line) => [
491
+ String(BigInt((JSON.parse(line) as LogEntry).time) * 1_000_000n),
492
+ line.trimEnd()
493
+ ])
482
494
  }
483
495
  ]
484
496
  })
@@ -520,7 +532,10 @@ export class PrismaLogDestination implements ILogDestination {
520
532
 
521
533
  constructor(private readonly prisma: PrismaClient) {}
522
534
 
523
- write(entry: LogEntry): void {
535
+ write(payload: string): void {
536
+ // This destination stores individual columns, so it is one of the few that
537
+ // has to parse. A sink that forwards the line verbatim should not.
538
+ const entry = JSON.parse(payload) as LogEntry
524
539
  void this.prisma.applicationLog.create({
525
540
  data: {
526
541
  level: entry.level,
@@ -557,8 +572,9 @@ export class RollingFileDestination implements ILogDestination {
557
572
  await new Promise<void>((resolve) => this.stream?.end(resolve))
558
573
  }
559
574
 
560
- write(entry: LogEntry): void {
561
- this.stream?.write(JSON.stringify(entry) + '\n')
575
+ write(payload: string): void {
576
+ // Already newline-terminated JSON — re-serializing would double-encode it.
577
+ this.stream?.write(payload)
562
578
  }
563
579
  }
564
580
  ```
@@ -729,7 +745,11 @@ When integrating `@bymax-one/nest-logger` in production, verify each of the foll
729
745
  A logger runs on every request of every service that installs it, so the suite is held to a bar beyond "it runs" — every behavior is pinned so that a regression **fails a test**.
730
746
 
731
747
  - ✅ **100% line coverage** — statements, branches, functions, and lines, enforced as a release gate across unit + e2e
732
- - ✅ **97.42% mutation score** — verified with [Stryker](https://stryker-mutator.io/) against a `break` threshold of 95; 97.42% is the theoretical maximum for this codebase
748
+ - ✅ **97.42% mutation score** — verified with [Stryker](https://stryker-mutator.io/) against a
749
+ `break` threshold of 95. The ten survivors are documented equivalents rather than coverage gaps,
750
+ and they stay uncounted-against rather than silenced: this package's plan forbids inline
751
+ `// Stryker disable` comments because they ship in the unminified bundle (see the
752
+ [report](./docs/mutation_testing_results.md))
733
753
  - ✅ **Every survivor documented** — the 10 remaining mutants are equivalents, each recorded in [docs/mutation_testing_results.md](./docs/mutation_testing_results.md) rather than silenced with an inline comment, so the score is an accounting rather than a number
734
754
  - ✅ **No real I/O in unit tests** — the Pino instance and every destination are mocked; e2e tests exercise the wired module through `@nestjs/testing` and supertest
735
755
 
@@ -806,7 +826,8 @@ type LogEntry = {
806
826
  interface ILogDestination {
807
827
  readonly name: string
808
828
  readonly minLevel?: LogLevel
809
- write(entry: LogEntry): void
829
+ /** `payload` is a newline-terminated JSON entry, UTF-8 encoded — not an object. */
830
+ write(payload: string): void | Promise<void>
810
831
  onInit?(): Promise<void>
811
832
  onShutdown?(): Promise<void>
812
833
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bymax-one/nest-logger",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Structured JSON logging for NestJS based on Pino 10, with optional OpenTelemetry correlation.",
5
5
  "author": "Bymax One <support@bymax.one>",
6
6
  "license": "MIT",
@@ -53,6 +53,29 @@
53
53
  ]
54
54
  }
55
55
  },
56
+ "scripts": {
57
+ "bench": "node --expose-gc --import tsx bench/throughput.bench.ts",
58
+ "build": "pnpm clean && tsup",
59
+ "check:exports": "attw --pack .",
60
+ "check:published": "node scripts/check-published-surface.mjs",
61
+ "clean": "rm -rf dist coverage",
62
+ "lint": "eslint src",
63
+ "lint:fix": "eslint src --fix",
64
+ "mutation": "stryker run",
65
+ "mutation:dry-run": "stryker run --dryRunOnly",
66
+ "mutation:incremental": "stryker run --incremental",
67
+ "prepare": "husky",
68
+ "prepublishOnly": "pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published",
69
+ "release": "npm publish --provenance --access public",
70
+ "size": "node scripts/check-size.mjs",
71
+ "test": "jest",
72
+ "test:all": "pnpm test && pnpm test:e2e",
73
+ "test:cov": "jest --coverage",
74
+ "test:cov:all": "jest --config jest.coverage.config.ts --coverage",
75
+ "test:e2e": "jest --config jest.e2e.config.ts",
76
+ "test:watch": "jest --watch",
77
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json"
78
+ },
56
79
  "lint-staged": {
57
80
  "*.{ts,tsx,js,mjs,cjs}": [
58
81
  "eslint --fix",
@@ -64,8 +87,8 @@
64
87
  },
65
88
  "dependencies": {},
66
89
  "peerDependencies": {
67
- "@nestjs/common": "^11.0.0",
68
- "@nestjs/core": "^11.0.0",
90
+ "@nestjs/common": "^11.0.16",
91
+ "@nestjs/core": "^11.1.18",
69
92
  "@opentelemetry/api": "^1.9.0",
70
93
  "pino": "^10.0.0",
71
94
  "pino-pretty": "^13.0.0",
@@ -134,31 +157,12 @@
134
157
  "request-id",
135
158
  "pii-redaction"
136
159
  ],
160
+ "packageManager": "pnpm@11.20.0",
137
161
  "engines": {
138
162
  "node": ">=24.0.0"
139
163
  },
140
164
  "publishConfig": {
141
165
  "access": "public",
142
166
  "registry": "https://registry.npmjs.org/"
143
- },
144
- "scripts": {
145
- "build": "pnpm clean && tsup",
146
- "lint": "eslint src",
147
- "lint:fix": "eslint src --fix",
148
- "test": "jest",
149
- "test:cov": "jest --coverage",
150
- "test:watch": "jest --watch",
151
- "test:e2e": "jest --config jest.e2e.config.ts",
152
- "test:all": "pnpm test && pnpm test:e2e",
153
- "test:cov:all": "jest --config jest.coverage.config.ts --coverage",
154
- "mutation": "stryker run",
155
- "mutation:incremental": "stryker run --incremental",
156
- "mutation:dry-run": "stryker run --dryRunOnly",
157
- "bench": "node --expose-gc --import tsx bench/throughput.bench.ts",
158
- "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json",
159
- "size": "node scripts/check-size.mjs",
160
- "check:exports": "attw --pack .",
161
- "clean": "rm -rf dist coverage",
162
- "release": "pnpm publish --provenance"
163
167
  }
164
- }
168
+ }