@bymax-one/nest-logger 1.0.4 → 1.0.5

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 +67 -1
  2. package/README.md +28 -11
  3. package/package.json +16 -15
package/CHANGELOG.md CHANGED
@@ -11,6 +11,71 @@ heading here.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [1.0.5] - 2026-07-30
15
+
16
+ ### Security
17
+
18
+ - **Peer floors raised to exclude known-vulnerable NestJS versions.** `@nestjs/common ^11.0.0`
19
+ admitted 11.0.0–11.0.15, carrying [GHSA-cj7v-w2c7-cp7c](https://github.com/advisories/GHSA-cj7v-w2c7-cp7c)
20
+ (remote code execution via the `Content-Type` header, patched in 11.0.16), and
21
+ `@nestjs/core ^11.0.0` admitted everything up to 11.1.17, carrying
22
+ [GHSA-36xv-jgw5-4q75](https://github.com/advisories/GHSA-36xv-jgw5-4q75) (patched in 11.1.18).
23
+
24
+ A peer range states which versions this library supports. A floor below a published
25
+ advisory told a consumer a vulnerable install was supported, and nothing in their
26
+ tooling contradicted it — the install resolved cleanly and silently. Floors are now
27
+ `^11.0.16` and `^11.1.18`.
28
+
29
+ Shipped as a patch, which is where a security fix belongs; a minor would reach the
30
+ same installs anyway, since `^1.0.4` accepts `1.1.0` as readily as `1.0.5`. No
31
+ runtime behaviour changed.
32
+
33
+ ### Fixed
34
+
35
+ - **The custom-destination examples did not compile against `ILogDestination`.**
36
+ The published interface takes a serialized line:
37
+
38
+ ```ts
39
+ write(payload: string): void | Promise<void> // newline-terminated JSON, UTF-8
40
+ ```
41
+
42
+ All three examples in the README — `LokiDestination`, `PrismaLogDestination`,
43
+ `RollingFileDestination` — and the API reference table declared
44
+ `write(entry: LogEntry): void`, so anyone following the README to build a
45
+ destination got:
46
+
47
+ ```
48
+ TS2416: Property 'write' in type 'LokiDestination' is not assignable to the
49
+ same property in base type 'ILogDestination'.
50
+ ```
51
+
52
+ `ILogDestination` is this library's extension point, so the documentation was
53
+ teaching the wrong shape for the one thing consumers are meant to implement.
54
+ `RollingFileDestination` compounded it by re-serializing a payload that already
55
+ **is** newline-terminated JSON — anyone casting past the type error would have
56
+ written double-encoded lines to disk.
57
+
58
+ Each example now does what its own job requires rather than all three doing the
59
+ same thing: Loki buffers the line verbatim, Prisma parses because it stores
60
+ individual columns, rolling-file writes it through untouched.
61
+
62
+ - **The request-id middleware typed `req` as the global DOM `Request`**, whose
63
+ `headers` is a `Headers` instance and cannot be indexed (`TS7052`). `1.0.4`
64
+ introduced `LoggableRequest` and `LoggableResponse` for exactly this and exports
65
+ them; the README never adopted them. It does now, and narrows the
66
+ `string | string[]` a repeated header produces.
67
+
68
+ ### Added
69
+
70
+ - **`pnpm check:published`** — verifies that the README's links resolve, that its
71
+ TypeScript snippets and the type tests compile against the built package, and
72
+ that every `v*.*.*` tag has a `## [x.y.z]` CHANGELOG section. It resolves the
73
+ package through its `exports` map into `dist/`, which `test:types` cannot do:
74
+ that one maps the package to `./src` through tsconfig `paths` and so never
75
+ compiles what the README claims. Both defects above were found by it.
76
+
77
+ Runs in CI, in `release.yml`, and inside `prepublishOnly`.
78
+
14
79
  ## [1.0.4] - 2026-07-29
15
80
 
16
81
  ### Fixed
@@ -187,7 +252,8 @@ published `dist/` is identical — no runtime behaviour changes for consumers.
187
252
  - Professional CI suite: `ci.yml`, `bench.yml`, `codeql.yml`, `scorecard.yml`,
188
253
  `release.yml`, Dependabot, and issue templates
189
254
 
190
- [Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.0.4...HEAD
255
+ [Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.0.5...HEAD
256
+ [1.0.5]: https://github.com/bymaxone/nest-logger/compare/v1.0.4...v1.0.5
191
257
  [1.0.4]: https://github.com/bymaxone/nest-logger/compare/v1.0.3...v1.0.4
192
258
  [1.0.3]: https://github.com/bymaxone/nest-logger/compare/v1.0.2...v1.0.3
193
259
  [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
  ```
@@ -806,7 +822,8 @@ type LogEntry = {
806
822
  interface ILogDestination {
807
823
  readonly name: string
808
824
  readonly minLevel?: LogLevel
809
- write(entry: LogEntry): void
825
+ /** `payload` is a newline-terminated JSON entry, UTF-8 encoded — not an object. */
826
+ write(payload: string): void | Promise<void>
810
827
  onInit?(): Promise<void>
811
828
  onShutdown?(): Promise<void>
812
829
  }
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.5",
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",
@@ -64,8 +64,8 @@
64
64
  },
65
65
  "dependencies": {},
66
66
  "peerDependencies": {
67
- "@nestjs/common": "^11.0.0",
68
- "@nestjs/core": "^11.0.0",
67
+ "@nestjs/common": "^11.0.16",
68
+ "@nestjs/core": "^11.1.18",
69
69
  "@opentelemetry/api": "^1.9.0",
70
70
  "pino": "^10.0.0",
71
71
  "pino-pretty": "^13.0.0",
@@ -142,23 +142,24 @@
142
142
  "registry": "https://registry.npmjs.org/"
143
143
  },
144
144
  "scripts": {
145
+ "bench": "node --expose-gc --import tsx bench/throughput.bench.ts",
145
146
  "build": "pnpm clean && tsup",
147
+ "check:exports": "attw --pack .",
148
+ "check:published": "node scripts/check-published-surface.mjs",
149
+ "clean": "rm -rf dist coverage",
146
150
  "lint": "eslint src",
147
151
  "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
152
  "mutation": "stryker run",
155
- "mutation:incremental": "stryker run --incremental",
156
153
  "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",
154
+ "mutation:incremental": "stryker run --incremental",
155
+ "release": "pnpm publish --provenance",
159
156
  "size": "node scripts/check-size.mjs",
160
- "check:exports": "attw --pack .",
161
- "clean": "rm -rf dist coverage",
162
- "release": "pnpm publish --provenance"
157
+ "test": "jest",
158
+ "test:all": "pnpm test && pnpm test:e2e",
159
+ "test:cov": "jest --coverage",
160
+ "test:cov:all": "jest --config jest.coverage.config.ts --coverage",
161
+ "test:e2e": "jest --config jest.e2e.config.ts",
162
+ "test:watch": "jest --watch",
163
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json"
163
164
  }
164
165
  }