@cqrs-ddd/pipeline-correlation 0.5.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/COMMERCIAL_LICENSE.txt +34 -0
  2. package/LICENSE +661 -0
  3. package/README.md +32 -0
  4. package/dist/constants/correlation.constants.d.ts +5 -0
  5. package/dist/constants/correlation.constants.d.ts.map +1 -0
  6. package/dist/constants/correlation.constants.js +6 -0
  7. package/dist/constants/correlation.constants.js.map +1 -0
  8. package/dist/correlation.store.d.ts +174 -0
  9. package/dist/correlation.store.d.ts.map +1 -0
  10. package/dist/correlation.store.js +176 -0
  11. package/dist/correlation.store.js.map +1 -0
  12. package/dist/decorators/with-correlation.decorator.d.ts +184 -0
  13. package/dist/decorators/with-correlation.decorator.d.ts.map +1 -0
  14. package/dist/decorators/with-correlation.decorator.js +85 -0
  15. package/dist/decorators/with-correlation.decorator.js.map +1 -0
  16. package/dist/helpers/method-decorator.d.ts +13 -0
  17. package/dist/helpers/method-decorator.d.ts.map +1 -0
  18. package/dist/helpers/method-decorator.js +24 -0
  19. package/dist/helpers/method-decorator.js.map +1 -0
  20. package/dist/index.d.ts +9 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +7 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/middlewares/http-correlation.middleware.d.ts +25 -0
  25. package/dist/middlewares/http-correlation.middleware.d.ts.map +1 -0
  26. package/dist/middlewares/http-correlation.middleware.js +63 -0
  27. package/dist/middlewares/http-correlation.middleware.js.map +1 -0
  28. package/dist/options/correlation.options.d.ts +91 -0
  29. package/dist/options/correlation.options.d.ts.map +1 -0
  30. package/dist/options/correlation.options.js +10 -0
  31. package/dist/options/correlation.options.js.map +1 -0
  32. package/dist/types/safe-typing.d.ts +15 -0
  33. package/dist/types/safe-typing.d.ts.map +1 -0
  34. package/dist/types/safe-typing.js +6 -0
  35. package/dist/types/safe-typing.js.map +1 -0
  36. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @cqrs-ddd/pipeline-correlation
2
+
3
+ Correlation id propagation for any Node.js application: an HTTP middleware
4
+ (`httpCorrelation()`, for Node's `http` server, Express or Connect), the
5
+ `@WithCorrelation()` method decorator for queue and message consumers and cron jobs
6
+ (both TypeScript decorator modes), a gRPC extraction preset (`CorrelationFrom.grpc()`), and
7
+ `correlationSource` for `createPipeline({ sources })` of `@cqrs-ddd/pipeline`.
8
+
9
+ **Documentation:** [guide](https://aristoteliss.github.io/ddd-cqrs/packages/pipeline-correlation/) · [API reference](https://aristoteliss.github.io/ddd-cqrs/api/cqrs-ddd/pipeline-correlation/) · [all packages](https://aristoteliss.github.io/ddd-cqrs/)
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @cqrs-ddd/pipeline-correlation
15
+ ```
16
+
17
+ Requires Node.js 22.12 or later. Published as an ES module; a CommonJS application loads
18
+ it with `require()`.
19
+
20
+ ## Example
21
+
22
+ ```ts
23
+ import { getCorrelationId, httpCorrelation } from '@cqrs-ddd/pipeline-correlation';
24
+
25
+ app.use(httpCorrelation());
26
+ app.get('/ping', (_req, res) => res.send(getCorrelationId()));
27
+ ```
28
+
29
+ ## License
30
+
31
+ Dual-licensed under **AGPLv3** and a **Commercial License**. See `LICENSE` and
32
+ `COMMERCIAL_LICENSE.txt`.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Default HTTP header name used for distributed correlation tracing.
3
+ */
4
+ export declare const DEFAULT_CORRELATION_HEADER = "x-correlation-id";
5
+ //# sourceMappingURL=correlation.constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.constants.d.ts","sourceRoot":"","sources":["../../src/constants/correlation.constants.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,0BAA0B,qBAAqB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ /**
3
+ * Default HTTP header name used for distributed correlation tracing.
4
+ */
5
+ export const DEFAULT_CORRELATION_HEADER = 'x-correlation-id';
6
+ //# sourceMappingURL=correlation.constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.constants.js","sourceRoot":"","sources":["../../src/constants/correlation.constants.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE;;GAEG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,kBAAkB,CAAC"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * The correlation id as a context source, for `createPipeline({ sources })`
3
+ * of `@cqrs-ddd/pipeline` and the job context registration of
4
+ * `@cqrs-ddd/pipeline-job-context`. Unlike {@link getCorrelationId}, `current()`
5
+ * generates nothing: it returns `undefined` when no id is set, so a pipeline
6
+ * generates one and runs with it, and {@link getCorrelationId} inside the
7
+ * handler returns that id. `create()` makes a new id, the one way this
8
+ * package, pipelines and jobs generate one. `accepts(id)` tells whether an id received from
9
+ * outside, such as in a job payload, is well formed: at most
10
+ * {@link DEFAULT_CORRELATION_ID_MAX_LENGTH} characters matching
11
+ * {@link DEFAULT_CORRELATION_ID_PATTERN}.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * createPipeline({ sources: { correlationId: correlationSource } });
16
+ * ```
17
+ */
18
+ export declare const correlationSource: {
19
+ readonly current: () => string | undefined;
20
+ readonly run: <T>(value: string | undefined, fn: () => T) => T;
21
+ readonly create: () => string;
22
+ readonly accepts: (id: string) => boolean;
23
+ };
24
+ /**
25
+ * Run a callback within a correlation context.
26
+ *
27
+ * Use this in **non-HTTP entry points** (queue workers, message consumers,
28
+ * WebSocket handlers, scheduled jobs) to propagate an external correlation ID
29
+ * into everything the callback runs, wrapped operations included.
30
+ *
31
+ * If `correlationId` is falsy (`undefined` or empty), the id is resolved via
32
+ * {@link getCorrelationId} (the current one, or a new one from
33
+ * `correlationSource.create()`), so
34
+ * `fn` always runs with a correlation id. A pipeline dispatched inside `fn`
35
+ * takes it as its `context.correlationId`.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * // BullMQ worker
40
+ * new Worker('emails', (job) =>
41
+ * runWithCorrelationId(job.data.correlationId, () => sendEmail(job.data)),
42
+ * );
43
+ *
44
+ * // RabbitMQ consumer (amqplib)
45
+ * channel.consume('users', (message) =>
46
+ * runWithCorrelationId(message.properties.correlationId, () =>
47
+ * onUserCreated(JSON.parse(message.content.toString())),
48
+ * ),
49
+ * );
50
+ *
51
+ * // Scheduled job: no incoming id, so a new one is made
52
+ * setInterval(() => runWithCorrelationId(undefined, () => hourlySync()), 3_600_000);
53
+ * ```
54
+ *
55
+ * @param correlationId - The correlation ID to propagate. If falsy, falls back to {@link getCorrelationId}.
56
+ * @param fn - The callback to execute within the correlation context.
57
+ */
58
+ export declare function runWithCorrelationId<T>(correlationId: string | undefined, fn: () => T): T;
59
+ /**
60
+ * Read the current correlation ID.
61
+ *
62
+ * It is the correlation id set by {@link runWithCorrelationId}, the HTTP
63
+ * middleware, or a running pipeline. Outside any, a new UUIDv7 is generated on
64
+ * each call, so it always returns a string.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * import { getCorrelationId } from '@cqrs-ddd/pipeline-correlation';
69
+ *
70
+ * class EmailWorker {
71
+ * @WithCorrelation()
72
+ * async process(job: Job) {
73
+ * console.log(`Processing with correlation: ${getCorrelationId()}`);
74
+ * await sendEmail(job.data);
75
+ * }
76
+ * }
77
+ * ```
78
+ */
79
+ export declare function getCorrelationId(): string;
80
+ /**
81
+ * Utility type that adds a `correlationId` field to any data shape.
82
+ *
83
+ * Use it to type BullMQ job data, RabbitMQ payloads, Kafka messages, etc.
84
+ * so that the correlation ID is part of the contract.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * interface WelcomeEmailJobData {
89
+ * userId: string;
90
+ * email: string;
91
+ * }
92
+ *
93
+ * // Queue typed as Queue<WithCorrelationId<WelcomeEmailJobData>>
94
+ * await queue.add('send', addCorrelationId({ userId, email }));
95
+ * ```
96
+ */
97
+ export type WithCorrelationId<T = Record<string, unknown>> = T & {
98
+ correlationId: string;
99
+ };
100
+ /**
101
+ * Stamp the current correlation ID onto a data object.
102
+ *
103
+ * This is the **producer-side** counterpart to {@link WithCorrelation}:
104
+ * - `addCorrelationId(data)` → stamps the ID when **enqueuing** a job / publishing a message
105
+ * - `@WithCorrelation()` → extracts the ID when **processing** the job / handling the message
106
+ *
107
+ * Both default to the `'correlationId'` key, so they work together out of the box.
108
+ *
109
+ * @throws {TypeError} If `data` is not a plain object.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * // Bull / BullMQ
114
+ * await queue.add('send-email', addCorrelationId({ userId, email }));
115
+ *
116
+ * // RabbitMQ (ClientProxy)
117
+ * this.client.emit('user.created', addCorrelationId(payload));
118
+ *
119
+ * // Kafka
120
+ * await this.producer.send({
121
+ * topic: 'orders',
122
+ * messages: [{ value: JSON.stringify(addCorrelationId(order)) }],
123
+ * });
124
+ *
125
+ * // PostgreSQL NOTIFY
126
+ * await sql`SELECT pg_notify('events', ${JSON.stringify(addCorrelationId(data))})`;
127
+ *
128
+ * // ⚠️ Arrays must be wrapped — passing one directly throws:
129
+ * // addCorrelationId([item1, item2]); // ❌ TypeError
130
+ * addCorrelationId({ items: [item1, item2] }); // ✅
131
+ * ```
132
+ *
133
+ * @param data - The payload to enrich. Must be a plain object, **not** an array.
134
+ * A shallow copy is returned; the original is not mutated.
135
+ * @returns A new object with `correlationId` added.
136
+ */
137
+ export declare function addCorrelationId<T extends Record<string, unknown>>(data: T): WithCorrelationId<T>;
138
+ /**
139
+ * Return a headers object stamped with the current correlation ID.
140
+ *
141
+ * Use this for **header-based** transports (Kafka, NATS, gRPC metadata, HTTP)
142
+ * where the correlation ID belongs in message headers / metadata — **not** in
143
+ * the data payload.
144
+ *
145
+ * For transports without native headers (Bull/BullMQ, PostgreSQL NOTIFY), use
146
+ * {@link addCorrelationId} instead.
147
+ *
148
+ * @param key - Header name. Defaults to `'x-correlation-id'`.
149
+ * @returns `{ [key]: correlationId }` — spread into your transport's headers.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * // Kafka
154
+ * await producer.send({
155
+ * topic: 'orders',
156
+ * messages: [{ value: JSON.stringify(order), headers: correlationHeaders() }],
157
+ * });
158
+ *
159
+ * // RabbitMQ (amqplib) — AMQP has a first-class correlationId property:
160
+ * channel.publish(exchange, key, buffer, { correlationId: getCorrelationId() });
161
+ * // …or in headers:
162
+ * channel.publish(exchange, key, buffer, { headers: correlationHeaders() });
163
+ *
164
+ * // NATS
165
+ * const headers = nats.headers();
166
+ * Object.entries(correlationHeaders()).forEach(([k, v]) => headers.set(k, v));
167
+ * nc.publish(subject, payload, { headers });
168
+ *
169
+ * // HTTP (outgoing)
170
+ * await fetch(url, { headers: { ...correlationHeaders(), 'content-type': 'application/json' } });
171
+ * ```
172
+ */
173
+ export declare function correlationHeaders(key?: string): Record<string, string>;
174
+ //# sourceMappingURL=correlation.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.store.d.ts","sourceRoot":"","sources":["../src/correlation.store.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB;aAC5B,OAAO,QAAM,MAAM,GAAG,SAAS;aAC/B,GAAG,GAAG,CAAC,SAAS,MAAM,GAAG,SAAS,MAAM,MAAM,CAAC,KAAG,CAAC;aAEnD,MAAM,QAAM,MAAM;aAClB,OAAO,OAAO,MAAM,KAAG,OAAO;CAGtB,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAEH;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG;IAC/D,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChE,IAAI,EAAE,CAAC,GACN,iBAAiB,CAAC,CAAC,CAAC,CAYtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,SAA6B,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAExB"}
@@ -0,0 +1,176 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import { uuidv7 } from '@cqrs-ddd/uuidv7';
4
+ import { DEFAULT_CORRELATION_HEADER } from './constants/correlation.constants.js';
5
+ import { DEFAULT_CORRELATION_ID_MAX_LENGTH, DEFAULT_CORRELATION_ID_PATTERN, } from './options/correlation.options.js';
6
+ const correlation = new AsyncLocalStorage();
7
+ /**
8
+ * The correlation id as a context source, for `createPipeline({ sources })`
9
+ * of `@cqrs-ddd/pipeline` and the job context registration of
10
+ * `@cqrs-ddd/pipeline-job-context`. Unlike {@link getCorrelationId}, `current()`
11
+ * generates nothing: it returns `undefined` when no id is set, so a pipeline
12
+ * generates one and runs with it, and {@link getCorrelationId} inside the
13
+ * handler returns that id. `create()` makes a new id, the one way this
14
+ * package, pipelines and jobs generate one. `accepts(id)` tells whether an id received from
15
+ * outside, such as in a job payload, is well formed: at most
16
+ * {@link DEFAULT_CORRELATION_ID_MAX_LENGTH} characters matching
17
+ * {@link DEFAULT_CORRELATION_ID_PATTERN}.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * createPipeline({ sources: { correlationId: correlationSource } });
22
+ * ```
23
+ */
24
+ export const correlationSource = {
25
+ current: () => correlation.getStore(),
26
+ run: (value, fn) => correlation.run(value, fn),
27
+ create: () => uuidv7(),
28
+ accepts: (id) => id.length <= DEFAULT_CORRELATION_ID_MAX_LENGTH &&
29
+ DEFAULT_CORRELATION_ID_PATTERN.test(id),
30
+ };
31
+ /**
32
+ * Run a callback within a correlation context.
33
+ *
34
+ * Use this in **non-HTTP entry points** (queue workers, message consumers,
35
+ * WebSocket handlers, scheduled jobs) to propagate an external correlation ID
36
+ * into everything the callback runs, wrapped operations included.
37
+ *
38
+ * If `correlationId` is falsy (`undefined` or empty), the id is resolved via
39
+ * {@link getCorrelationId} (the current one, or a new one from
40
+ * `correlationSource.create()`), so
41
+ * `fn` always runs with a correlation id. A pipeline dispatched inside `fn`
42
+ * takes it as its `context.correlationId`.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // BullMQ worker
47
+ * new Worker('emails', (job) =>
48
+ * runWithCorrelationId(job.data.correlationId, () => sendEmail(job.data)),
49
+ * );
50
+ *
51
+ * // RabbitMQ consumer (amqplib)
52
+ * channel.consume('users', (message) =>
53
+ * runWithCorrelationId(message.properties.correlationId, () =>
54
+ * onUserCreated(JSON.parse(message.content.toString())),
55
+ * ),
56
+ * );
57
+ *
58
+ * // Scheduled job: no incoming id, so a new one is made
59
+ * setInterval(() => runWithCorrelationId(undefined, () => hourlySync()), 3_600_000);
60
+ * ```
61
+ *
62
+ * @param correlationId - The correlation ID to propagate. If falsy, falls back to {@link getCorrelationId}.
63
+ * @param fn - The callback to execute within the correlation context.
64
+ */
65
+ export function runWithCorrelationId(correlationId, fn) {
66
+ return correlation.run(correlationId || getCorrelationId(), fn);
67
+ }
68
+ /**
69
+ * Read the current correlation ID.
70
+ *
71
+ * It is the correlation id set by {@link runWithCorrelationId}, the HTTP
72
+ * middleware, or a running pipeline. Outside any, a new UUIDv7 is generated on
73
+ * each call, so it always returns a string.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * import { getCorrelationId } from '@cqrs-ddd/pipeline-correlation';
78
+ *
79
+ * class EmailWorker {
80
+ * @WithCorrelation()
81
+ * async process(job: Job) {
82
+ * console.log(`Processing with correlation: ${getCorrelationId()}`);
83
+ * await sendEmail(job.data);
84
+ * }
85
+ * }
86
+ * ```
87
+ */
88
+ export function getCorrelationId() {
89
+ return correlation.getStore() || correlationSource.create();
90
+ }
91
+ /**
92
+ * Stamp the current correlation ID onto a data object.
93
+ *
94
+ * This is the **producer-side** counterpart to {@link WithCorrelation}:
95
+ * - `addCorrelationId(data)` → stamps the ID when **enqueuing** a job / publishing a message
96
+ * - `@WithCorrelation()` → extracts the ID when **processing** the job / handling the message
97
+ *
98
+ * Both default to the `'correlationId'` key, so they work together out of the box.
99
+ *
100
+ * @throws {TypeError} If `data` is not a plain object.
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * // Bull / BullMQ
105
+ * await queue.add('send-email', addCorrelationId({ userId, email }));
106
+ *
107
+ * // RabbitMQ (ClientProxy)
108
+ * this.client.emit('user.created', addCorrelationId(payload));
109
+ *
110
+ * // Kafka
111
+ * await this.producer.send({
112
+ * topic: 'orders',
113
+ * messages: [{ value: JSON.stringify(addCorrelationId(order)) }],
114
+ * });
115
+ *
116
+ * // PostgreSQL NOTIFY
117
+ * await sql`SELECT pg_notify('events', ${JSON.stringify(addCorrelationId(data))})`;
118
+ *
119
+ * // ⚠️ Arrays must be wrapped — passing one directly throws:
120
+ * // addCorrelationId([item1, item2]); // ❌ TypeError
121
+ * addCorrelationId({ items: [item1, item2] }); // ✅
122
+ * ```
123
+ *
124
+ * @param data - The payload to enrich. Must be a plain object, **not** an array.
125
+ * A shallow copy is returned; the original is not mutated.
126
+ * @returns A new object with `correlationId` added.
127
+ */
128
+ export function addCorrelationId(data) {
129
+ const prototype = data !== null && typeof data === 'object'
130
+ ? Object.getPrototypeOf(data)
131
+ : undefined;
132
+ if (prototype !== Object.prototype && prototype !== null) {
133
+ throw new TypeError('addCorrelationId(data) requires a plain object payload. ' +
134
+ 'Wrap arrays and class instances in a plain object first.');
135
+ }
136
+ return { ...data, correlationId: getCorrelationId() };
137
+ }
138
+ /**
139
+ * Return a headers object stamped with the current correlation ID.
140
+ *
141
+ * Use this for **header-based** transports (Kafka, NATS, gRPC metadata, HTTP)
142
+ * where the correlation ID belongs in message headers / metadata — **not** in
143
+ * the data payload.
144
+ *
145
+ * For transports without native headers (Bull/BullMQ, PostgreSQL NOTIFY), use
146
+ * {@link addCorrelationId} instead.
147
+ *
148
+ * @param key - Header name. Defaults to `'x-correlation-id'`.
149
+ * @returns `{ [key]: correlationId }` — spread into your transport's headers.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * // Kafka
154
+ * await producer.send({
155
+ * topic: 'orders',
156
+ * messages: [{ value: JSON.stringify(order), headers: correlationHeaders() }],
157
+ * });
158
+ *
159
+ * // RabbitMQ (amqplib) — AMQP has a first-class correlationId property:
160
+ * channel.publish(exchange, key, buffer, { correlationId: getCorrelationId() });
161
+ * // …or in headers:
162
+ * channel.publish(exchange, key, buffer, { headers: correlationHeaders() });
163
+ *
164
+ * // NATS
165
+ * const headers = nats.headers();
166
+ * Object.entries(correlationHeaders()).forEach(([k, v]) => headers.set(k, v));
167
+ * nc.publish(subject, payload, { headers });
168
+ *
169
+ * // HTTP (outgoing)
170
+ * await fetch(url, { headers: { ...correlationHeaders(), 'content-type': 'application/json' } });
171
+ * ```
172
+ */
173
+ export function correlationHeaders(key = DEFAULT_CORRELATION_HEADER) {
174
+ return { [key]: getCorrelationId() };
175
+ }
176
+ //# sourceMappingURL=correlation.store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.store.js","sourceRoot":"","sources":["../src/correlation.store.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAClF,OAAO,EACL,iCAAiC,EACjC,8BAA8B,GAC/B,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,GAAG,IAAI,iBAAiB,EAAsB,CAAC;AAEhE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,OAAO,EAAE,GAAuB,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE;IACzD,GAAG,EAAE,CAAI,KAAyB,EAAE,EAAW,EAAK,EAAE,CACpD,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC;IAC5B,MAAM,EAAE,GAAW,EAAE,CAAC,MAAM,EAAE;IAC9B,OAAO,EAAE,CAAC,EAAU,EAAW,EAAE,CAC/B,EAAE,CAAC,MAAM,IAAI,iCAAiC;QAC9C,8BAA8B,CAAC,IAAI,CAAC,EAAE,CAAC;CACjC,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,UAAU,oBAAoB,CAClC,aAAiC,EACjC,EAAW;IAEX,OAAO,WAAW,CAAC,GAAG,CAAC,aAAa,IAAI,gBAAgB,EAAE,EAAE,EAAE,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,WAAW,CAAC,QAAQ,EAAE,IAAI,iBAAiB,CAAC,MAAM,EAAE,CAAC;AAC9D,CAAC;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAO;IAEP,MAAM,SAAS,GACb,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QACvC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC;QAC7B,CAAC,CAAC,SAAS,CAAC;IAChB,IAAI,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACzD,MAAM,IAAI,SAAS,CACjB,0DAA0D;YACxD,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,GAAG,IAAI,EAAE,aAAa,EAAE,gBAAgB,EAAE,EAAE,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,kBAAkB,CAChC,GAAG,GAAG,0BAA0B;IAEhC,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,gBAAgB,EAAE,EAAE,CAAC;AACvC,CAAC"}
@@ -0,0 +1,184 @@
1
+ import { type DualMethodDecorator } from '../helpers/method-decorator.js';
2
+ /**
3
+ * A function that extracts the correlation ID from the method arguments.
4
+ *
5
+ * Receives the same arguments the decorated method receives.
6
+ * Return `undefined` to keep the current id, or get a new one from
7
+ * `correlationSource.create()` when there is none.
8
+ */
9
+ export type CorrelationExtractor = (...args: unknown[]) => string | undefined;
10
+ /**
11
+ * Options for the {@link WithCorrelation} decorator.
12
+ */
13
+ export interface CorrelationDecoratorOptions {
14
+ /**
15
+ * Property path (dot-notation) to the correlation ID in the **first argument**.
16
+ *
17
+ * - For Bull: `'data.correlationId'` (default)
18
+ * - For a flat object: `'correlationId'`
19
+ * - For deeply nested: `'metadata.tracing.correlationId'`
20
+ *
21
+ * Ignored when `extract` is provided.
22
+ *
23
+ * @default 'data.correlationId'
24
+ */
25
+ path?: string;
26
+ /**
27
+ * Custom extractor function. Receives all method arguments.
28
+ * Takes precedence over `path`.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // RabbitMQ
33
+ * extract: (data, ctx) => ctx.getMessage().properties.correlationId
34
+ *
35
+ * // Kafka
36
+ * extract: (data, ctx) =>
37
+ * ctx.getMessage().headers?.['x-correlation-id']?.toString()
38
+ * ```
39
+ */
40
+ extract?: CorrelationExtractor;
41
+ /**
42
+ * Log level for the start of the method execution.
43
+ *
44
+ * - Any {@link LogLevel} value routes to the logger method of that name (`'log'`, `'debug'`, `'verbose'`, `'warn'`, `'error'`).
45
+ * - `'none'` suppresses the log message entirely.
46
+ *
47
+ * A pino-backed logger maps the levels as `'verbose'` → `trace`, `'debug'` → `debug`,
48
+ * `'log'` → `info`, `'warn'` → `warn`, `'error'` → `error`, `'fatal'` → `fatal`.
49
+ *
50
+ * @default 'debug'
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * // Uses default 'debug' level
55
+ * @WithCorrelation()
56
+ *
57
+ * // Log at 'verbose' level
58
+ * @WithCorrelation({ logLevel: 'verbose' })
59
+ *
60
+ * // Suppress the log message
61
+ * @WithCorrelation({ logLevel: 'none' })
62
+ * ```
63
+ */
64
+ logLevel?: LogLevel | 'none';
65
+ /**
66
+ * Logger to write through instead of the decorated instance's `logger` property or
67
+ * `console`.
68
+ *
69
+ * Accepts any object with the methods of {@link CorrelationLogger}
70
+ * (e.g. a Pino or Winston adapter).
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * @WithCorrelation({ logger: myPinoLogger })
75
+ * async handleJob(job: Job) { ... }
76
+ * ```
77
+ */
78
+ logger?: CorrelationLogger;
79
+ }
80
+ /** The levels `@WithCorrelation` logs at, named as NestJS names them. */
81
+ export type LogLevel = 'log' | 'error' | 'warn' | 'debug' | 'verbose' | 'fatal';
82
+ /**
83
+ * The logger `@WithCorrelation` writes through; `console`, a NestJS `LoggerService` and
84
+ * pino or winston adapters satisfy it.
85
+ */
86
+ export interface CorrelationLogger {
87
+ log(message: unknown, ...optionalParams: unknown[]): unknown;
88
+ warn(message: unknown, ...optionalParams: unknown[]): unknown;
89
+ debug?(message: unknown, ...optionalParams: unknown[]): unknown;
90
+ verbose?(message: unknown, ...optionalParams: unknown[]): unknown;
91
+ error?(message: unknown, ...optionalParams: unknown[]): unknown;
92
+ fatal?(message: unknown, ...optionalParams: unknown[]): unknown;
93
+ }
94
+ /**
95
+ * Queue-agnostic `MethodDecorator` that propagates a correlation ID into the
96
+ * pipeline's async-local correlation context.
97
+ *
98
+ * Place it **under** any transport or scheduling decorator a framework adds. It does
99
+ * not replace or interfere with them.
100
+ *
101
+ * It extracts the correlation ID from the method arguments (via `path` or
102
+ * `extract`) and runs the method inside {@link runWithCorrelationId}, which
103
+ * keeps the current id, or makes one with `correlationSource.create()`, when
104
+ * no ID is found.
105
+ *
106
+ * **⚠️ Array payloads:**
107
+ * When using the default dot-path extraction, the first argument must be an
108
+ * object (e.g. a Bull `Job`). If it is an array, the dot-path cannot resolve
109
+ * and the decorator logs a warning at runtime. For transports that
110
+ * deliver an array as the first argument, use the `extract` option:
111
+ * ```ts
112
+ * @WithCorrelation({ extract: (items) => items?.[0]?.correlationId })
113
+ * ```
114
+ *
115
+ * Inside the method body, read the active ID with:
116
+ * ```ts
117
+ * import { getCorrelationId } from '@cqrs-ddd/pipeline-correlation';
118
+ * const id = getCorrelationId();
119
+ * ```
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * class Consumers {
124
+ * // BullMQ job (default path: data.correlationId)
125
+ * @WithCorrelation()
126
+ * async sendEmail(job: Job) {
127
+ * await mailer.send(job.data);
128
+ * }
129
+ *
130
+ * // Another key in the job data
131
+ * @WithCorrelation('data.x-request-id')
132
+ * async sendSms(job: Job) {}
133
+ *
134
+ * // RabbitMQ message (amqplib)
135
+ * @WithCorrelation({ extract: (message) => message.properties.correlationId })
136
+ * async onUserCreated(message: ConsumeMessage) {}
137
+ *
138
+ * // Kafka message (kafkajs)
139
+ * @WithCorrelation({
140
+ * extract: ({ message }) => message.headers?.['x-correlation-id']?.toString(),
141
+ * })
142
+ * async onOrderPlaced(payload: EachMessagePayload) {}
143
+ *
144
+ * // PostgreSQL LISTEN/NOTIFY
145
+ * @WithCorrelation({ path: 'correlationId' })
146
+ * async onNotification(notification: { correlationId?: string }) {}
147
+ *
148
+ * // Scheduled job: no id in the arguments, so a new one is made
149
+ * @WithCorrelation()
150
+ * async hourlySync() {}
151
+ * }
152
+ * ```
153
+ */
154
+ export declare function WithCorrelation(): DualMethodDecorator;
155
+ export declare function WithCorrelation(path: string): DualMethodDecorator;
156
+ export declare function WithCorrelation(options: CorrelationDecoratorOptions): DualMethodDecorator;
157
+ /**
158
+ * Pre-built extraction presets for {@link WithCorrelation}, for transports whose
159
+ * handlers receive the correlation id in metadata rather than in the payload.
160
+ *
161
+ * For transports that carry it in the payload (BullMQ, PostgreSQL NOTIFY), use
162
+ * the bare `@WithCorrelation()` or a `path`; for any other shape, an `extract`
163
+ * function.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * class Users {
168
+ * // gRPC (@grpc/grpc-js): (request, metadata)
169
+ * @WithCorrelation(CorrelationFrom.grpc())
170
+ * async findOne(request: FindOneRequest, metadata: Metadata) {}
171
+ * }
172
+ * ```
173
+ */
174
+ export declare const CorrelationFrom: {
175
+ /**
176
+ * gRPC — extracts from gRPC `Metadata` (second argument).
177
+ *
178
+ * In gRPC handlers the signature is `(data, metadata, call)`.
179
+ *
180
+ * @param key - Metadata key. Defaults to `'x-correlation-id'`.
181
+ */
182
+ readonly grpc: (key?: string) => CorrelationDecoratorOptions;
183
+ };
184
+ //# sourceMappingURL=with-correlation.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-correlation.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/with-correlation.decorator.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,gCAAgC,CAAC;AAGxC;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,GAAG,SAAS,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAE/B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAE7B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,yEAAyE;AACzE,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAEhF;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC7D,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC9D,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAChE,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAClE,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAChE,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;CACjE;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,eAAe,IAAI,mBAAmB,CAAC;AACvD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAAC;AACnE,wBAAgB,eAAe,CAC7B,OAAO,EAAE,2BAA2B,GACnC,mBAAmB,CAAC;AA+CvB;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,eAAe;IAC1B;;;;;;OAMG;qCACuC,2BAA2B;CAM7D,CAAC"}