@evanion/nestjs-correlation-id 1.1.0 → 2.0.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +59 -1
  2. package/README.md +100 -34
  3. package/dist/constants.d.ts +16 -1
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +16 -5
  6. package/dist/correlation-id.middleware.d.ts +5 -4
  7. package/dist/correlation-id.middleware.d.ts.map +1 -0
  8. package/dist/correlation-id.middleware.js +37 -31
  9. package/dist/correlation.module.d.ts +2 -1
  10. package/dist/correlation.module.d.ts.map +1 -0
  11. package/dist/correlation.module.js +18 -20
  12. package/dist/correlation.service.d.ts +34 -3
  13. package/dist/correlation.service.d.ts.map +1 -0
  14. package/dist/correlation.service.js +51 -27
  15. package/dist/index.d.ts +7 -5
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +6 -21
  18. package/dist/interfaces/correlation-config.interface.d.ts +2 -0
  19. package/dist/interfaces/correlation-config.interface.d.ts.map +1 -0
  20. package/dist/interfaces/correlation-config.interface.js +1 -2
  21. package/dist/withCorrelation.function.d.ts +16 -74
  22. package/dist/withCorrelation.function.d.ts.map +1 -0
  23. package/dist/withCorrelation.function.js +41 -22
  24. package/package.json +76 -62
  25. package/src/constants.ts +19 -0
  26. package/src/correlation-id.middleware.ts +55 -0
  27. package/src/correlation.module.ts +32 -0
  28. package/src/correlation.service.ts +69 -0
  29. package/src/index.ts +6 -0
  30. package/src/interfaces/correlation-config.interface.ts +5 -0
  31. package/src/withCorrelation.function.ts +60 -0
  32. package/CONTRIBUTING.md +0 -31
  33. package/dist/correlation-id.middleware.spec.d.ts +0 -1
  34. package/dist/correlation-id.middleware.spec.js +0 -62
  35. package/dist/correlation.service.spec.d.ts +0 -1
  36. package/dist/correlation.service.spec.js +0 -35
package/CHANGELOG.md CHANGED
@@ -1,3 +1,61 @@
1
- RELEASE 1.0.0
1
+ # Changelog
2
+
3
+ ## 2.0.0 (2026-08-27)
4
+
5
+ First release from the [Evanion/libraries](https://github.com/Evanion/libraries)
6
+ monorepo. The library moved here from the standalone
7
+ `Evanion/nestjs-correlation-id` repo; 1.1.0 was the last release from there.
8
+
9
+ ### ⚠️ Breaking Changes
10
+
11
+ - **NestJS 6 through 9 are no longer supported.** The peer range is now
12
+ `^10 || ^11 || ^12`. Those majors are long past end of life, and claiming
13
+ support for versions that are never tested is worse than declaring the range
14
+ honestly.
15
+ - **An `exports` map was added.** Deep imports into `dist/` no longer resolve.
16
+ They were never a documented entry point.
17
+ - **`uuid` is no longer a dependency.** Ids now come from `node:crypto`'s
18
+ `randomUUID`, which has been built into every supported Node version. The
19
+ generated format is unchanged (RFC 4122 v4), and a custom `generator` is
20
+ unaffected. This library now has no runtime dependencies beyond `tslib`.
21
+ - **Node 20 or newer is required**, declared via `engines`.
22
+
23
+ ### 🚀 Features
24
+
25
+ - **Ships both ESM and CommonJS.** NestJS 12 is ESM-only while 10 and 11 are
26
+ CommonJS, so both formats are needed to cover the supported range. `import`
27
+ and `require()` both work, and both are verified against a packed tarball on
28
+ every release.
29
+ - **NestJS 12 support.**
30
+ - **`CorrelationConfig` is now exported** from the package root. It is the type
31
+ `CorrelationModule.forRoot()` takes, and it was never exported — so callers
32
+ could configure the module without being able to name the type.
33
+ - **`@nestjs/axios` is now an optional peer dependency.** It is only used for a
34
+ type in `withCorrelation`, and the import is type-only, so it is erased at
35
+ runtime. Consumers that do not use `withCorrelation` no longer need it
36
+ installed.
37
+
38
+ ### 🩹 Fixes
39
+
40
+ - **The `@nestjs/axios` peer range was malformed and matched nothing.** It read
41
+ `^0.1.0 || ^1.0.0 || ^2.0.0 || ^3.00` — that `^3.00` is not valid semver, and
42
+ one invalid comparator invalidates the _entire_ range, so even the `^2.0.0`
43
+ entry failed to match. This is why
44
+ [#1 "Add support for @nestjs/axios 2x"](https://github.com/Evanion/nestjs-correlation-id/issues/1)
45
+ stayed broken after 2.x was ostensibly added: a single missing zero.
46
+ - The README imported from `@nestjs-common` rather than `@nestjs/common`.
47
+
48
+ ### 🏗️ Internal
49
+
50
+ - Tests migrated from jest to vitest, and expanded from 5 to 12.
51
+ - Releases now go through `nx release` with npm trusted publishing and
52
+ provenance, replacing release-it.
53
+
54
+ ## 1.0.0
2
55
 
3
56
  - Initial release
57
+
58
+ > Releases 1.0.3 through 1.1.0 were published from the standalone repository
59
+ > without changelog entries. See the
60
+ > [commit history](https://github.com/Evanion/nestjs-correlation-id/commits/main)
61
+ > there for what changed.
package/README.md CHANGED
@@ -8,6 +8,31 @@
8
8
  </a>
9
9
  </div>
10
10
 
11
+ ### Requirements
12
+
13
+ | | |
14
+ | ---------- | ----------- |
15
+ | **NestJS** | 12 |
16
+ | **Node** | 20 or newer |
17
+
18
+ Ships ESM only, matching NestJS 12. There is no CommonJS build, so
19
+ `require('@evanion/nestjs-correlation-id')` will not work — use `import`.
20
+
21
+ One build means one module graph and one `CorrelationService` class object, so
22
+ injecting by class token is always safe. The dual build this package used to
23
+ ship could hand Nest two unrelated copies of the same class.
24
+
25
+ The middleware is typed against `node:http`'s `IncomingMessage` and
26
+ `ServerResponse` and reads and writes raw headers, so it works under
27
+ `@nestjs/platform-express` and `@nestjs/platform-fastify` alike. `express` is
28
+ not a peer dependency.
29
+
30
+ `@nestjs/axios` is an optional peer dependency, needed only if you use
31
+ [`withCorrelation`](#how-to-use). It is a
32
+ type-only import, so it is not pulled in at runtime.
33
+
34
+ This package has no runtime dependencies beyond `tslib`.
35
+
11
36
  ### Why?
12
37
 
13
38
  When debugging an issue in your applications logs, it helps to be able to follow a specific request up and down your whole stack. This is usually done by including a `correlation-id` (aka `Request-id`) header in all your requests, and forwarding the same id across all your microservices.
@@ -43,7 +68,14 @@ export class AppModule implements NestModule {
43
68
  }
44
69
  ```
45
70
 
46
- And then just inject the correlation middleware in your HttpService by calling the `registerAsync` method with the `withCorrelation` function.
71
+ `CorrelationIdMiddleware` opens an
72
+ [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html) context for the
73
+ request. Everything downstream of it — guards, interceptors, controllers, and
74
+ anything they await — sees that request's id, and concurrent requests stay
75
+ isolated.
76
+
77
+ Then forward the id on outgoing HTTP calls by passing `withCorrelation()` to
78
+ `HttpModule.registerAsync`.
47
79
 
48
80
  ```ts
49
81
  import { HttpModule } from '@nestjs/axios';
@@ -57,7 +89,49 @@ import { withCorrelation } from '@evanion/nestjs-correlation-id';
57
89
  export class UsersModule {}
58
90
  ```
59
91
 
60
- You can now use the `HttpService` as usual in your `UsersService` and `UsersController`
92
+ Use `HttpService` as usual in `UsersService` and `UsersController`. It stays a
93
+ singleton: the correlation header is attached by an axios request interceptor
94
+ that reads the current context when the request is made.
95
+
96
+ `withCorrelation()` needs `CorrelationModule.forRoot()` to have been called
97
+ somewhere in the application — it is a global module, so once in the root module
98
+ is enough. Without it, Nest fails at boot with
99
+ `Nest can't resolve dependencies of the HTTP_MODULE_OPTIONS (?)`.
100
+
101
+ ### Working outside a request
102
+
103
+ `CorrelationService` is a singleton, so it is injected like any other provider
104
+ and resolved with `module.get(CorrelationService)`. Outside a correlation
105
+ context `getCorrelationId()` returns `undefined`, and outgoing calls carry no
106
+ correlation header.
107
+
108
+ For work with no request behind it — queue consumers, cron jobs, scripts — open
109
+ a context yourself:
110
+
111
+ ```ts
112
+ await this.correlationService.run(this.correlationService.generate(), () =>
113
+ this.processJob(job),
114
+ );
115
+ ```
116
+
117
+ ### Configuration
118
+
119
+ `CorrelationModule.forRoot()` accepts a `CorrelationConfig`, exported from the
120
+ package root.
121
+
122
+ ```ts
123
+ import {
124
+ CorrelationModule,
125
+ type CorrelationConfig,
126
+ } from '@evanion/nestjs-correlation-id';
127
+
128
+ const config: Partial<CorrelationConfig> = {
129
+ header: 'X-Request-Id', // defaults to 'X-Correlation-Id'
130
+ generator: () => myId(), // defaults to node:crypto randomUUID
131
+ };
132
+
133
+ CorrelationModule.forRoot(config);
134
+ ```
61
135
 
62
136
  ### Customize
63
137
 
@@ -79,64 +153,56 @@ export class AppModule implements NestModule {
79
153
 
80
154
  #### Add `correlationId` to logs
81
155
 
82
- In order to add the correlation ID to your logs, you can use the `CorrelationService` service to get the current correlationId.
83
-
84
- In the following example, we are using the [@ntegral/nestjs-sentry](https://github.com/ntegral/nestjs-sentry) package, but you can use any package or provider you like.
156
+ Inject `CorrelationService` wherever you build log context and read the current
157
+ id. It is a singleton, so nothing about injecting it changes the scope of the
158
+ provider holding it.
85
159
 
86
160
  ```ts
87
161
  import { CorrelationService } from '@evanion/nestjs-correlation-id';
88
162
  import { Injectable, NestMiddleware } from '@nestjs/common';
89
- import { InjectSentry, SentryService } from '@ntegral/nestjs-sentry';
90
- import { NextFunction, Request, Response } from 'express';
163
+ import type { IncomingMessage, ServerResponse } from 'node:http';
164
+ import * as Sentry from '@sentry/node';
91
165
 
92
166
  @Injectable()
93
- export class SentryMiddleware implements NestMiddleware {
94
- constructor(
95
- private readonly correlationService: CorrelationService,
96
- @InjectSentry() private readonly sentryService: SentryService,
97
- ) {}
98
-
99
- async use(_req: Request, _res: Response, next: NextFunction) {
100
- const correlationId = await this.correlationService.getCorrelationId();
101
- this.sentryService.instance().configureScope((scope) => {
102
- scope.setTag('correlationId', correlationId);
103
- });
167
+ export class SentryTagMiddleware implements NestMiddleware {
168
+ constructor(private readonly correlationService: CorrelationService) {}
169
+
170
+ use(_req: IncomingMessage, _res: ServerResponse, next: () => void) {
171
+ const correlationId = this.correlationService.getCorrelationId();
172
+ if (correlationId) Sentry.setTag('correlationId', correlationId);
104
173
  next();
105
174
  }
106
175
  }
107
176
  ```
108
177
 
109
- Then add it to your `AppModule`
178
+ `getCorrelationId()` is synchronous — it never returned a promise — and gives
179
+ `undefined` when there is no correlation context, so apply this after
180
+ `CorrelationIdMiddleware`, which is what opens one.
110
181
 
111
182
  ```ts
112
- import { Module } from '@nestjs-common';
113
- import { SentryModule } from '@ntegral/nestjs-sentry';
114
- import { CorrelationModule } from '@evanion/nestjs-correlation-id';
115
- import { SentryMiddleware } from './middleware/sentry.middleware';
116
-
117
183
  @Module({
118
- imports: [
119
- CorrelationModule.forRoot(),
120
- SentryModule.forRoot({
121
- // ... your config
122
- }),
123
- ],
184
+ imports: [CorrelationModule.forRoot()],
124
185
  })
125
186
  export class AppModule implements NestModule {
126
187
  configure(consumer: MiddlewareConsumer) {
127
188
  consumer.apply(CorrelationIdMiddleware).forRoutes('*');
128
- consumer.apply(SentryMiddleware).forRoutes('*');
189
+ consumer.apply(SentryTagMiddleware).forRoutes('*');
129
190
  }
130
191
  }
131
192
  ```
132
193
 
133
- If you need to manually set the correlationId anywhere in your application. You can use the `CorrelationService` service to set the correlationId.
194
+ To replace the id of the current context:
134
195
 
135
196
  ```ts
136
197
  this.correlationService.setCorrelationId('some_correlation_id');
137
198
  ```
138
199
 
139
- see [e2e tests](/test) for a fully working example
200
+ It throws outside a correlation context, rather than writing somewhere nothing
201
+ will read.
202
+
203
+ See the [specs on GitHub](https://github.com/Evanion/libraries/tree/main/nest/correlation-id/src)
204
+ for fully worked examples, including an end-to-end one that stands up a real
205
+ Nest application.
140
206
 
141
207
  ## Change Log
142
208
 
@@ -144,7 +210,7 @@ See [Changelog](CHANGELOG.md) for more information.
144
210
 
145
211
  ## Contributing
146
212
 
147
- Contributions welcome! See [Contributing](CONTRIBUTING.md).
213
+ Contributions welcome! See [Contributing](https://github.com/Evanion/libraries/blob/main/CONTRIBUTING.md).
148
214
 
149
215
  ## Author
150
216
 
@@ -1,2 +1,17 @@
1
1
  export declare const CORRELATION_ID_HEADER = "X-Correlation-Id";
2
- export declare const CORRELATION_CONFIG_TOKEN = "CORRELATION_CONFIG";
2
+ /**
3
+ * Namespaced because the provider is registered in a `global: true` module: a
4
+ * bare 'CORRELATION_CONFIG' would collide silently with any other package that
5
+ * happened to pick the same string. Deliberately a string and not a Symbol --
6
+ * a bare `Symbol()` is not stable across duplicate copies of a module, which
7
+ * would reintroduce exactly the hazard the single ESM build removes.
8
+ */
9
+ export declare const CORRELATION_CONFIG_TOKEN = "@evanion/nestjs-correlation-id:CORRELATION_CONFIG";
10
+ /**
11
+ * Token of the provider that attaches the outgoing-request interceptor to the
12
+ * axios instance `withCorrelation` configures. Nothing injects it; it exists so
13
+ * Nest instantiates the factory that registers the interceptor.
14
+ */
15
+ export declare const CORRELATION_AXIOS_INTERCEPTOR = "@evanion/nestjs-correlation-id:AXIOS_INTERCEPTOR";
16
+ export declare const DEFAULT_CORRELATION_ID_VALIDATOR: (value: string) => boolean;
17
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,sDACgB,CAAC;AACtD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,qDACU,CAAC;AACrD,eAAO,MAAM,gCAAgC,GAAI,OAAO,MAAM,KAAG,OACjC,CAAC"}
package/dist/constants.js CHANGED
@@ -1,5 +1,16 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CORRELATION_CONFIG_TOKEN = exports.CORRELATION_ID_HEADER = void 0;
4
- exports.CORRELATION_ID_HEADER = 'X-Correlation-Id';
5
- exports.CORRELATION_CONFIG_TOKEN = 'CORRELATION_CONFIG';
1
+ export const CORRELATION_ID_HEADER = 'X-Correlation-Id';
2
+ /**
3
+ * Namespaced because the provider is registered in a `global: true` module: a
4
+ * bare 'CORRELATION_CONFIG' would collide silently with any other package that
5
+ * happened to pick the same string. Deliberately a string and not a Symbol --
6
+ * a bare `Symbol()` is not stable across duplicate copies of a module, which
7
+ * would reintroduce exactly the hazard the single ESM build removes.
8
+ */
9
+ export const CORRELATION_CONFIG_TOKEN = '@evanion/nestjs-correlation-id:CORRELATION_CONFIG';
10
+ /**
11
+ * Token of the provider that attaches the outgoing-request interceptor to the
12
+ * axios instance `withCorrelation` configures. Nothing injects it; it exists so
13
+ * Nest instantiates the factory that registers the interceptor.
14
+ */
15
+ export const CORRELATION_AXIOS_INTERCEPTOR = '@evanion/nestjs-correlation-id:AXIOS_INTERCEPTOR';
16
+ export const DEFAULT_CORRELATION_ID_VALIDATOR = (value) => /^[\w.:-]{1,128}$/.test(value);
@@ -1,10 +1,11 @@
1
1
  import { NestMiddleware } from '@nestjs/common';
2
- import { Request, Response } from 'express';
3
- import { CorrelationService } from './correlation.service';
4
- import { CorrelationConfig } from './interfaces/correlation-config.interface';
2
+ import type { IncomingMessage, ServerResponse } from 'node:http';
3
+ import { CorrelationService } from './correlation.service.js';
4
+ import type { CorrelationConfig } from './interfaces/correlation-config.interface.js';
5
5
  export declare class CorrelationIdMiddleware implements NestMiddleware {
6
6
  private correlationService;
7
7
  private correlationConfig;
8
8
  constructor(correlationService: CorrelationService, correlationConfig: CorrelationConfig);
9
- use(req: Request, res: Response, next: () => void): void;
9
+ use(req: IncomingMessage, res: ServerResponse, next: (error?: unknown) => void): void;
10
10
  }
11
+ //# sourceMappingURL=correlation-id.middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation-id.middleware.d.ts","sourceRoot":"","sources":["../src/correlation-id.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAG9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAatF,qBACa,uBAAwB,YAAW,cAAc;IAE1D,OAAO,CAAC,kBAAkB;IAE1B,OAAO,CAAC,iBAAiB;gBAFjB,kBAAkB,EAAE,kBAAkB,EAEtC,iBAAiB,EAAE,iBAAiB;IAG9C,GAAG,CACD,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI;CAwBlC"}
@@ -1,39 +1,45 @@
1
- "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
7
- };
8
- var __metadata = (this && this.__metadata) || function (k, v) {
9
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
- };
11
- var __param = (this && this.__param) || function (paramIndex, decorator) {
12
- return function (target, key) { decorator(target, key, paramIndex); }
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.CorrelationIdMiddleware = void 0;
16
- const common_1 = require("@nestjs/common");
17
- const constants_1 = require("./constants");
18
- const correlation_service_1 = require("./correlation.service");
19
- let CorrelationIdMiddleware = exports.CorrelationIdMiddleware = class CorrelationIdMiddleware {
1
+ import { __decorate, __metadata, __param } from "tslib";
2
+ import { Inject, Injectable } from '@nestjs/common';
3
+ import { CORRELATION_CONFIG_TOKEN, DEFAULT_CORRELATION_ID_VALIDATOR } from './constants.js';
4
+ import { CorrelationService } from './correlation.service.js';
5
+ /**
6
+ * Node normalises repeated request headers into a single comma-joined string
7
+ * for everything except set-cookie, which stays an array. Joining an array the
8
+ * same way keeps both shapes on one code path -- and the default validator
9
+ * rejects the result, which is the wanted behaviour for a duplicated
10
+ * correlation id.
11
+ */
12
+ const singleValue = (value) => (Array.isArray(value) ? value.join(', ') : value);
13
+ let CorrelationIdMiddleware = class CorrelationIdMiddleware {
14
+ correlationService;
15
+ correlationConfig;
20
16
  constructor(correlationService, correlationConfig) {
21
17
  this.correlationService = correlationService;
22
18
  this.correlationConfig = correlationConfig;
23
19
  }
24
20
  use(req, res, next) {
25
- const { header } = this.correlationConfig;
26
- const correlationId = req.get(header) || this.correlationService.getCorrelationId();
27
- if (!req.headers[header])
28
- req.headers[header] = correlationId;
29
- if (!res.get(header))
30
- res.set(header, correlationId);
31
- this.correlationService.setCorrelationId(correlationId);
32
- next();
21
+ const { header, validate = DEFAULT_CORRELATION_ID_VALIDATOR, } = this.correlationConfig;
22
+ const key = header.toLowerCase();
23
+ const incoming = singleValue(req.headers[key]);
24
+ // The generator runs only when nothing usable arrived, so a counter- or
25
+ // sequence-backed generator is not advanced for an id that gets discarded.
26
+ const correlationId = incoming && validate(incoming)
27
+ ? incoming
28
+ : this.correlationService.generate();
29
+ if (!req.headers[key])
30
+ req.headers[key] = correlationId;
31
+ // setHeader preserves the casing it is given, so the configured casing is
32
+ // what goes out on the wire.
33
+ if (res.getHeader(header) === undefined)
34
+ res.setHeader(header, correlationId);
35
+ // Everything downstream of next() -- guards, interceptors, the controller,
36
+ // and anything they await -- runs inside this context.
37
+ this.correlationService.run(correlationId, next);
33
38
  }
34
39
  };
35
- exports.CorrelationIdMiddleware = CorrelationIdMiddleware = __decorate([
36
- (0, common_1.Injectable)(),
37
- __param(1, (0, common_1.Inject)(constants_1.CORRELATION_CONFIG_TOKEN)),
38
- __metadata("design:paramtypes", [correlation_service_1.CorrelationService, Object])
40
+ CorrelationIdMiddleware = __decorate([
41
+ Injectable(),
42
+ __param(1, Inject(CORRELATION_CONFIG_TOKEN)),
43
+ __metadata("design:paramtypes", [CorrelationService, Object])
39
44
  ], CorrelationIdMiddleware);
45
+ export { CorrelationIdMiddleware };
@@ -1,5 +1,6 @@
1
1
  import { DynamicModule } from '@nestjs/common';
2
- import { CorrelationConfig } from './interfaces/correlation-config.interface';
2
+ import type { CorrelationConfig } from './interfaces/correlation-config.interface.js';
3
3
  export declare class CorrelationModule {
4
4
  static forRoot(config?: Partial<CorrelationConfig>): DynamicModule;
5
5
  }
6
+ //# sourceMappingURL=correlation.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.module.d.ts","sourceRoot":"","sources":["../src/correlation.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAoB,MAAM,gBAAgB,CAAC;AAUjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAEtF,qBACa,iBAAiB;IAC5B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,aAAa;CAiBnE"}
@@ -1,31 +1,29 @@
1
- "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
7
- };
8
1
  var CorrelationModule_1;
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.CorrelationModule = void 0;
11
- const common_1 = require("@nestjs/common");
12
- const uuid_1 = require("uuid");
13
- const constants_1 = require("./constants");
14
- const correlation_service_1 = require("./correlation.service");
15
- let CorrelationModule = exports.CorrelationModule = CorrelationModule_1 = class CorrelationModule {
2
+ import { __decorate } from "tslib";
3
+ import { Module } from '@nestjs/common';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { CORRELATION_CONFIG_TOKEN, CORRELATION_ID_HEADER, DEFAULT_CORRELATION_ID_VALIDATOR, } from './constants.js';
6
+ import { CorrelationService } from './correlation.service.js';
7
+ let CorrelationModule = CorrelationModule_1 = class CorrelationModule {
16
8
  static forRoot(config) {
17
9
  const correlationConfigProvider = {
18
- provide: constants_1.CORRELATION_CONFIG_TOKEN,
19
- useValue: Object.assign(Object.assign({}, config), { header: (config === null || config === void 0 ? void 0 : config.header) || constants_1.CORRELATION_ID_HEADER, generator: (config === null || config === void 0 ? void 0 : config.generator) || uuid_1.v4 }),
10
+ provide: CORRELATION_CONFIG_TOKEN,
11
+ useValue: {
12
+ ...config,
13
+ header: config?.header || CORRELATION_ID_HEADER,
14
+ generator: config?.generator || randomUUID,
15
+ validate: config?.validate || DEFAULT_CORRELATION_ID_VALIDATOR,
16
+ },
20
17
  };
21
18
  return {
22
19
  global: true,
23
20
  module: CorrelationModule_1,
24
- providers: [correlationConfigProvider, correlation_service_1.CorrelationService],
25
- exports: [correlationConfigProvider, correlation_service_1.CorrelationService],
21
+ providers: [correlationConfigProvider, CorrelationService],
22
+ exports: [correlationConfigProvider, CorrelationService],
26
23
  };
27
24
  }
28
25
  };
29
- exports.CorrelationModule = CorrelationModule = CorrelationModule_1 = __decorate([
30
- (0, common_1.Module)({})
26
+ CorrelationModule = CorrelationModule_1 = __decorate([
27
+ Module({})
31
28
  ], CorrelationModule);
29
+ export { CorrelationModule };
@@ -1,7 +1,38 @@
1
- import { CorrelationConfig } from './interfaces/correlation-config.interface';
1
+ import type { CorrelationConfig } from './interfaces/correlation-config.interface.js';
2
+ /**
3
+ * A plain singleton over AsyncLocalStorage.
4
+ *
5
+ * It used to be `@Injectable({ scope: Scope.REQUEST })`. Nest propagates scope
6
+ * upward through the injection graph, so every provider that reached this one
7
+ * -- including `HttpService`, via `withCorrelation` -- silently became
8
+ * request-scoped: re-instantiated per request, and never given `onModuleInit`.
9
+ * AsyncLocalStorage gives the same per-request isolation with none of that.
10
+ */
2
11
  export declare class CorrelationService {
3
- private correlationId;
12
+ private readonly correlationConfig;
13
+ private readonly storage;
4
14
  constructor(correlationConfig: CorrelationConfig);
5
- getCorrelationId(): string;
15
+ /**
16
+ * Runs `callback` in a correlation context. Everything it awaits, schedules
17
+ * or calls sees `correlationId`, and overlapping contexts stay isolated.
18
+ *
19
+ * The middleware does this per request. Call it directly for work that has no
20
+ * request behind it -- queue consumers, cron jobs, scripts.
21
+ */
22
+ run<T>(correlationId: string, callback: () => T): T;
23
+ /**
24
+ * The id of the surrounding correlation context, or `undefined` when there is
25
+ * none. Outside a context there is genuinely no correlation id, so this does
26
+ * not invent one.
27
+ */
28
+ getCorrelationId(): string | undefined;
29
+ /** Replaces the id of the surrounding correlation context. */
6
30
  setCorrelationId(correlationId: string): void;
31
+ /**
32
+ * A fresh id from the configured generator. Called only when no usable id
33
+ * arrived with the request, so a counter- or sequence-backed generator is not
34
+ * advanced for ids that get discarded.
35
+ */
36
+ generate(): string;
7
37
  }
38
+ //# sourceMappingURL=correlation.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.service.d.ts","sourceRoot":"","sources":["../src/correlation.service.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAMtF;;;;;;;;GAQG;AACH,qBACa,kBAAkB;IAK3B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAJpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6C;gBAIlD,iBAAiB,EAAE,iBAAiB;IAGvD;;;;;;OAMG;IACH,GAAG,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC;IAInD;;;;OAIG;IACH,gBAAgB,IAAI,MAAM,GAAG,SAAS;IAItC,8DAA8D;IAC9D,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IAU7C;;;;OAIG;IACH,QAAQ,IAAI,MAAM;CAGnB"}
@@ -1,36 +1,60 @@
1
- "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
7
- };
8
- var __metadata = (this && this.__metadata) || function (k, v) {
9
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
- };
11
- var __param = (this && this.__param) || function (paramIndex, decorator) {
12
- return function (target, key) { decorator(target, key, paramIndex); }
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.CorrelationService = void 0;
16
- const common_1 = require("@nestjs/common");
17
- const uuid_1 = require("uuid");
18
- const constants_1 = require("./constants");
19
- let CorrelationService = exports.CorrelationService = class CorrelationService {
1
+ import { __decorate, __metadata, __param } from "tslib";
2
+ import { Inject, Injectable } from '@nestjs/common';
3
+ import { AsyncLocalStorage } from 'node:async_hooks';
4
+ import { CORRELATION_CONFIG_TOKEN } from './constants.js';
5
+ /**
6
+ * A plain singleton over AsyncLocalStorage.
7
+ *
8
+ * It used to be `@Injectable({ scope: Scope.REQUEST })`. Nest propagates scope
9
+ * upward through the injection graph, so every provider that reached this one
10
+ * -- including `HttpService`, via `withCorrelation` -- silently became
11
+ * request-scoped: re-instantiated per request, and never given `onModuleInit`.
12
+ * AsyncLocalStorage gives the same per-request isolation with none of that.
13
+ */
14
+ let CorrelationService = class CorrelationService {
15
+ correlationConfig;
16
+ storage = new AsyncLocalStorage();
20
17
  constructor(correlationConfig) {
21
- this.correlationId = correlationConfig.generator
22
- ? correlationConfig.generator()
23
- : (0, uuid_1.v4)();
18
+ this.correlationConfig = correlationConfig;
24
19
  }
20
+ /**
21
+ * Runs `callback` in a correlation context. Everything it awaits, schedules
22
+ * or calls sees `correlationId`, and overlapping contexts stay isolated.
23
+ *
24
+ * The middleware does this per request. Call it directly for work that has no
25
+ * request behind it -- queue consumers, cron jobs, scripts.
26
+ */
27
+ run(correlationId, callback) {
28
+ return this.storage.run({ correlationId }, callback);
29
+ }
30
+ /**
31
+ * The id of the surrounding correlation context, or `undefined` when there is
32
+ * none. Outside a context there is genuinely no correlation id, so this does
33
+ * not invent one.
34
+ */
25
35
  getCorrelationId() {
26
- return this.correlationId;
36
+ return this.storage.getStore()?.correlationId;
27
37
  }
38
+ /** Replaces the id of the surrounding correlation context. */
28
39
  setCorrelationId(correlationId) {
29
- this.correlationId = correlationId;
40
+ const store = this.storage.getStore();
41
+ if (!store) {
42
+ throw new Error('setCorrelationId() was called outside a correlation context. Apply CorrelationIdMiddleware, or wrap the work in CorrelationService.run().');
43
+ }
44
+ store.correlationId = correlationId;
45
+ }
46
+ /**
47
+ * A fresh id from the configured generator. Called only when no usable id
48
+ * arrived with the request, so a counter- or sequence-backed generator is not
49
+ * advanced for ids that get discarded.
50
+ */
51
+ generate() {
52
+ return this.correlationConfig.generator();
30
53
  }
31
54
  };
32
- exports.CorrelationService = CorrelationService = __decorate([
33
- (0, common_1.Injectable)({ scope: common_1.Scope.REQUEST }),
34
- __param(0, (0, common_1.Inject)(constants_1.CORRELATION_CONFIG_TOKEN)),
55
+ CorrelationService = __decorate([
56
+ Injectable(),
57
+ __param(0, Inject(CORRELATION_CONFIG_TOKEN)),
35
58
  __metadata("design:paramtypes", [Object])
36
59
  ], CorrelationService);
60
+ export { CorrelationService };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- export * from './correlation-id.middleware';
2
- export * from './correlation.service';
3
- export * from './correlation.module';
4
- export * from './withCorrelation.function';
5
- export * from './constants';
1
+ export * from './correlation-id.middleware.js';
2
+ export * from './correlation.service.js';
3
+ export * from './correlation.module.js';
4
+ export * from './withCorrelation.function.js';
5
+ export * from './constants.js';
6
+ export * from './interfaces/correlation-config.interface.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,8CAA8C,CAAC"}