@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.
- package/CHANGELOG.md +59 -1
- package/README.md +100 -34
- package/dist/constants.d.ts +16 -1
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +16 -5
- package/dist/correlation-id.middleware.d.ts +5 -4
- package/dist/correlation-id.middleware.d.ts.map +1 -0
- package/dist/correlation-id.middleware.js +37 -31
- package/dist/correlation.module.d.ts +2 -1
- package/dist/correlation.module.d.ts.map +1 -0
- package/dist/correlation.module.js +18 -20
- package/dist/correlation.service.d.ts +34 -3
- package/dist/correlation.service.d.ts.map +1 -0
- package/dist/correlation.service.js +51 -27
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -21
- package/dist/interfaces/correlation-config.interface.d.ts +2 -0
- package/dist/interfaces/correlation-config.interface.d.ts.map +1 -0
- package/dist/interfaces/correlation-config.interface.js +1 -2
- package/dist/withCorrelation.function.d.ts +16 -74
- package/dist/withCorrelation.function.d.ts.map +1 -0
- package/dist/withCorrelation.function.js +41 -22
- package/package.json +76 -62
- package/src/constants.ts +19 -0
- package/src/correlation-id.middleware.ts +55 -0
- package/src/correlation.module.ts +32 -0
- package/src/correlation.service.ts +69 -0
- package/src/index.ts +6 -0
- package/src/interfaces/correlation-config.interface.ts +5 -0
- package/src/withCorrelation.function.ts +60 -0
- package/CONTRIBUTING.md +0 -31
- package/dist/correlation-id.middleware.spec.d.ts +0 -1
- package/dist/correlation-id.middleware.spec.js +0 -62
- package/dist/correlation.service.spec.d.ts +0 -1
- package/dist/correlation.service.spec.js +0 -35
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 {
|
|
90
|
-
import
|
|
163
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
164
|
+
import * as Sentry from '@sentry/node';
|
|
91
165
|
|
|
92
166
|
@Injectable()
|
|
93
|
-
export class
|
|
94
|
-
constructor(
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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(
|
|
189
|
+
consumer.apply(SentryTagMiddleware).forRoutes('*');
|
|
129
190
|
}
|
|
130
191
|
}
|
|
131
192
|
```
|
|
132
193
|
|
|
133
|
-
|
|
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
|
-
|
|
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
|
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
1
|
export declare const CORRELATION_ID_HEADER = "X-Correlation-Id";
|
|
2
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
__param(1,
|
|
38
|
-
__metadata("design:paramtypes", [
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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:
|
|
19
|
-
useValue:
|
|
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,
|
|
25
|
-
exports: [correlationConfigProvider,
|
|
21
|
+
providers: [correlationConfigProvider, CorrelationService],
|
|
22
|
+
exports: [correlationConfigProvider, CorrelationService],
|
|
26
23
|
};
|
|
27
24
|
}
|
|
28
25
|
};
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
12
|
+
private readonly correlationConfig;
|
|
13
|
+
private readonly storage;
|
|
4
14
|
constructor(correlationConfig: CorrelationConfig);
|
|
5
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
__param(0,
|
|
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"}
|