@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
@@ -0,0 +1,85 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ import { untyped } from '@cqrs-ddd/untyped';
3
+ import { DEFAULT_CORRELATION_HEADER } from '../constants/correlation.constants.js';
4
+ import { getCorrelationId, runWithCorrelationId, } from '../correlation.store.js';
5
+ import { methodDecorator, } from '../helpers/method-decorator.js';
6
+ import { dyn } from '../types/safe-typing.js';
7
+ /**
8
+ * Resolve a dot-notation path on an object.
9
+ * Returns `undefined` for any missing segment or non-string leaf.
10
+ *
11
+ * @internal
12
+ */
13
+ function getByPath(obj, path) {
14
+ let current = untyped(obj);
15
+ for (const segment of path.split('.')) {
16
+ if (current == null)
17
+ return undefined;
18
+ current = untyped(current[segment]);
19
+ }
20
+ return typeof current === 'string' ? current : undefined;
21
+ }
22
+ export function WithCorrelation(pathOrOptions) {
23
+ const options = typeof pathOrOptions === 'string'
24
+ ? { path: pathOrOptions }
25
+ : (pathOrOptions ?? {});
26
+ const { path = 'data.correlationId', extract } = options;
27
+ return methodDecorator((originalMethod, _propertyKey) => {
28
+ return function (...args) {
29
+ const logger = options.logger ??
30
+ untyped(this)?.logger ??
31
+ console;
32
+ if (!extract && Array.isArray(args[0])) {
33
+ logger.warn(`${untyped(this).constructor?.name}.${String(_propertyKey)}: first argument is an array — ` +
34
+ `dot-path "${path}" cannot extract a correlation ID from it. ` +
35
+ `Use the 'extract' option for array payloads.`);
36
+ }
37
+ const correlationId = extract
38
+ ? extract(...args)
39
+ : getByPath(args[0], path);
40
+ return runWithCorrelationId(correlationId, () => {
41
+ if (options.logLevel !== 'none') {
42
+ const level = options.logLevel ?? 'debug';
43
+ const method = logger[level];
44
+ if (typeof method === 'function') {
45
+ method.call(logger, `🔗 Starting ${untyped(this).constructor?.name}.${String(_propertyKey)} with correlationId: ${getCorrelationId()}`);
46
+ }
47
+ }
48
+ return originalMethod.apply(this, args);
49
+ });
50
+ };
51
+ });
52
+ }
53
+ /**
54
+ * Pre-built extraction presets for {@link WithCorrelation}, for transports whose
55
+ * handlers receive the correlation id in metadata rather than in the payload.
56
+ *
57
+ * For transports that carry it in the payload (BullMQ, PostgreSQL NOTIFY), use
58
+ * the bare `@WithCorrelation()` or a `path`; for any other shape, an `extract`
59
+ * function.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * class Users {
64
+ * // gRPC (@grpc/grpc-js): (request, metadata)
65
+ * @WithCorrelation(CorrelationFrom.grpc())
66
+ * async findOne(request: FindOneRequest, metadata: Metadata) {}
67
+ * }
68
+ * ```
69
+ */
70
+ export const CorrelationFrom = {
71
+ /**
72
+ * gRPC — extracts from gRPC `Metadata` (second argument).
73
+ *
74
+ * In gRPC handlers the signature is `(data, metadata, call)`.
75
+ *
76
+ * @param key - Metadata key. Defaults to `'x-correlation-id'`.
77
+ */
78
+ grpc: (key = DEFAULT_CORRELATION_HEADER) => ({
79
+ extract: (_data, metadata) => {
80
+ const values = dyn(metadata)?.get?.(key);
81
+ return values?.[0]?.toString();
82
+ },
83
+ }),
84
+ };
85
+ //# sourceMappingURL=with-correlation.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-correlation.decorator.js","sourceRoot":"","sources":["../../src/decorators/with-correlation.decorator.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,EACL,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEL,eAAe,GAChB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAqG9C;;;;;GAKG;AACH,SAAS,SAAS,CAAC,GAAY,EAAE,IAAY;IAC3C,IAAI,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,IAAI,OAAO,IAAI,IAAI;YAAE,OAAO,SAAS,CAAC;QACtC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAE,OAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;AACvE,CAAC;AAmED,MAAM,UAAU,eAAe,CAC7B,aAAoD;IAEpD,MAAM,OAAO,GACX,OAAO,aAAa,KAAK,QAAQ;QAC/B,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE;QACzB,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IAE5B,MAAM,EAAE,IAAI,GAAG,oBAAoB,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAEzD,OAAO,eAAe,CAAC,CAAC,cAAc,EAAE,YAAY,EAAE,EAAE;QACtD,OAAO,UAAyB,GAAG,IAAe;YAChD,MAAM,MAAM,GACV,OAAO,CAAC,MAAM;gBACb,OAAO,CAAC,IAAI,CAAC,EAAE,MAAwC;gBACxD,OAAO,CAAC;YAEV,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvC,MAAM,CAAC,IAAI,CACT,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,IAAI,IAAI,MAAM,CAAC,YAAY,CAAC,iCAAiC;oBACzF,aAAa,IAAI,6CAA6C;oBAC9D,8CAA8C,CACjD,CAAC;YACJ,CAAC;YAED,MAAM,aAAa,GAAG,OAAO;gBAC3B,CAAC,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;gBAClB,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAE7B,OAAO,oBAAoB,CAAC,aAAa,EAAE,GAAG,EAAE;gBAC9C,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;oBAChC,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC;oBAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAgC,CAAC,CAAC;oBACxD,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;wBAChC,MAAgC,CAAC,IAAI,CACpC,MAAM,EACN,eAAe,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,IAAI,IAAI,MAAM,CAAC,YAAY,CAAC,wBAAwB,gBAAgB,EAAE,EAAE,CACnH,CAAC;oBACJ,CAAC;gBACH,CAAC;gBACD,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC1C,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B;;;;;;OAMG;IACH,IAAI,EAAE,CAAC,GAAG,GAAG,0BAA0B,EAA+B,EAAE,CAAC,CAAC;QACxE,OAAO,EAAE,CAAC,KAAc,EAAE,QAAiB,EAAE,EAAE;YAC7C,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;YACzC,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;QACjC,CAAC;KACF,CAAC;CACM,CAAC"}
@@ -0,0 +1,13 @@
1
+ type AnyMethod = (this: unknown, ...args: any[]) => unknown;
2
+ /** A method decorator for the standard and the `experimentalDecorators` mode. */
3
+ export interface DualMethodDecorator {
4
+ <F extends AnyMethod>(method: F, context: ClassMethodDecoratorContext): F;
5
+ (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor;
6
+ }
7
+ /**
8
+ * Builds a decorator that replaces a method with `replace(method, name)`, keeping the
9
+ * method's own name for stack traces.
10
+ */
11
+ export declare function methodDecorator(replace: (method: AnyMethod, name: string) => AnyMethod): DualMethodDecorator;
12
+ export {};
13
+ //# sourceMappingURL=method-decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"method-decorator.d.ts","sourceRoot":"","sources":["../../src/helpers/method-decorator.ts"],"names":[],"mappings":"AAGA,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC;AAE5D,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,2BAA2B,GAAG,CAAC,CAAC;IAC1E,CACE,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,UAAU,EAAE,kBAAkB,GAC7B,kBAAkB,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,KAAK,SAAS,GACtD,mBAAmB,CAiBrB"}
@@ -0,0 +1,24 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ /**
3
+ * Builds a decorator that replaces a method with `replace(method, name)`, keeping the
4
+ * method's own name for stack traces.
5
+ */
6
+ export function methodDecorator(replace) {
7
+ const replaced = (method, name) => {
8
+ const replacement = replace(method, name);
9
+ Object.defineProperty(replacement, 'name', {
10
+ value: method.name,
11
+ configurable: true,
12
+ });
13
+ return replacement;
14
+ };
15
+ return ((target, key, descriptor) => {
16
+ if (typeof key === 'object' && key !== null && 'name' in key) {
17
+ return replaced(target, String(key.name));
18
+ }
19
+ const property = descriptor;
20
+ property.value = replaced(property.value, String(key));
21
+ return property;
22
+ });
23
+ }
24
+ //# sourceMappingURL=method-decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"method-decorator.js","sourceRoot":"","sources":["../../src/helpers/method-decorator.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAetE;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAuD;IAEvD,MAAM,QAAQ,GAAG,CAAC,MAAiB,EAAE,IAAY,EAAE,EAAE;QACnD,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE;YACzC,KAAK,EAAE,MAAM,CAAC,IAAI;YAClB,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;QACH,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC;IACF,OAAO,CAAC,CAAC,MAAe,EAAE,GAAY,EAAE,UAA+B,EAAE,EAAE;QACzE,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YAC7D,OAAO,QAAQ,CAAC,MAAmB,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,QAAQ,GAAG,UAAgC,CAAC;QAClD,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACvD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAwB,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { DEFAULT_CORRELATION_HEADER } from './constants/correlation.constants.js';
2
+ export type { WithCorrelationId } from './correlation.store.js';
3
+ export { addCorrelationId, correlationHeaders, correlationSource, getCorrelationId, runWithCorrelationId, } from './correlation.store.js';
4
+ export type { CorrelationDecoratorOptions, CorrelationExtractor, CorrelationLogger, LogLevel, } from './decorators/with-correlation.decorator.js';
5
+ export { CorrelationFrom, WithCorrelation, } from './decorators/with-correlation.decorator.js';
6
+ export { type HttpMiddleware, httpCorrelation, } from './middlewares/http-correlation.middleware.js';
7
+ export type { CorrelationOptions } from './options/correlation.options.js';
8
+ export { DEFAULT_CORRELATION_ID_MAX_LENGTH, DEFAULT_CORRELATION_ID_PATTERN, } from './options/correlation.options.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAClF,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,2BAA2B,EAC3B,oBAAoB,EACpB,iBAAiB,EACjB,QAAQ,GACT,MAAM,4CAA4C,CAAC;AACpD,OAAO,EACL,eAAe,EACf,eAAe,GAChB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EACL,KAAK,cAAc,EACnB,eAAe,GAChB,MAAM,8CAA8C,CAAC;AACtD,YAAY,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAC3E,OAAO,EACL,iCAAiC,EACjC,8BAA8B,GAC/B,MAAM,kCAAkC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ export { DEFAULT_CORRELATION_HEADER } from './constants/correlation.constants.js';
3
+ export { addCorrelationId, correlationHeaders, correlationSource, getCorrelationId, runWithCorrelationId, } from './correlation.store.js';
4
+ export { CorrelationFrom, WithCorrelation, } from './decorators/with-correlation.decorator.js';
5
+ export { httpCorrelation, } from './middlewares/http-correlation.middleware.js';
6
+ export { DEFAULT_CORRELATION_ID_MAX_LENGTH, DEFAULT_CORRELATION_ID_PATTERN, } from './options/correlation.options.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAElF,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAOhC,OAAO,EACL,eAAe,EACf,eAAe,GAChB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAEL,eAAe,GAChB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,iCAAiC,EACjC,8BAA8B,GAC/B,MAAM,kCAAkC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { type CorrelationOptions } from '../options/correlation.options.js';
3
+ /** A Node `http` middleware: Express and Connect take it as it is. */
4
+ export type HttpMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
5
+ /**
6
+ * An HTTP middleware that takes the correlation id from the incoming request header and
7
+ * runs the rest of the request with it ({@link runWithCorrelationId}), so pipelines
8
+ * that the request starts take it. It echoes the id on the response header.
9
+ *
10
+ * The header name defaults to `x-correlation-id`. If `header` is omitted or is any
11
+ * non-string value (including `false`), the default header is used.
12
+ *
13
+ * An incoming id longer than 128 characters or outside the default character set is
14
+ * replaced by a local id. `acceptIncoming`, `trimIncoming`, `maxLength` and
15
+ * `validateIncoming` adjust that policy.
16
+ *
17
+ * @throws TypeError when `header` is not a valid HTTP field name, or `maxLength` is not
18
+ * a positive safe integer.
19
+ * @example Express
20
+ * ```ts
21
+ * app.use(httpCorrelation({ trimIncoming: true, validateIncoming: (id) => id.startsWith('edge:') }));
22
+ * ```
23
+ */
24
+ export declare function httpCorrelation(options?: CorrelationOptions): HttpMiddleware;
25
+ //# sourceMappingURL=http-correlation.middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-correlation.middleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/http-correlation.middleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAMjE,OAAO,EACL,KAAK,kBAAkB,EAGxB,MAAM,mCAAmC,CAAC;AAI3C,sEAAsE;AACtE,MAAM,MAAM,cAAc,GAAG,CAC3B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,MAAM,IAAI,KACb,IAAI,CAAC;AAEV;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,cAAc,CA2C5E"}
@@ -0,0 +1,63 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ import { DEFAULT_CORRELATION_HEADER } from '../constants/correlation.constants.js';
3
+ import { getCorrelationId, runWithCorrelationId, } from '../correlation.store.js';
4
+ import { DEFAULT_CORRELATION_ID_MAX_LENGTH, DEFAULT_CORRELATION_ID_PATTERN, } from '../options/correlation.options.js';
5
+ const HTTP_FIELD_NAME = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
6
+ /**
7
+ * An HTTP middleware that takes the correlation id from the incoming request header and
8
+ * runs the rest of the request with it ({@link runWithCorrelationId}), so pipelines
9
+ * that the request starts take it. It echoes the id on the response header.
10
+ *
11
+ * The header name defaults to `x-correlation-id`. If `header` is omitted or is any
12
+ * non-string value (including `false`), the default header is used.
13
+ *
14
+ * An incoming id longer than 128 characters or outside the default character set is
15
+ * replaced by a local id. `acceptIncoming`, `trimIncoming`, `maxLength` and
16
+ * `validateIncoming` adjust that policy.
17
+ *
18
+ * @throws TypeError when `header` is not a valid HTTP field name, or `maxLength` is not
19
+ * a positive safe integer.
20
+ * @example Express
21
+ * ```ts
22
+ * app.use(httpCorrelation({ trimIncoming: true, validateIncoming: (id) => id.startsWith('edge:') }));
23
+ * ```
24
+ */
25
+ export function httpCorrelation(options) {
26
+ const h = options?.header;
27
+ if (typeof h === 'string' && !HTTP_FIELD_NAME.test(h)) {
28
+ throw new TypeError(`Invalid correlation HTTP header name: "${h}".`);
29
+ }
30
+ if (options?.maxLength !== undefined &&
31
+ (!Number.isSafeInteger(options.maxLength) || options.maxLength <= 0)) {
32
+ throw new TypeError('Correlation maxLength must be a positive safe integer when provided.');
33
+ }
34
+ const header = typeof h === 'string' ? h.toLowerCase() : DEFAULT_CORRELATION_HEADER;
35
+ const acceptIncoming = options?.acceptIncoming ?? true;
36
+ const trimIncoming = options?.trimIncoming ?? false;
37
+ const maxLength = options?.maxLength ?? DEFAULT_CORRELATION_ID_MAX_LENGTH;
38
+ const validateIncoming = options?.validateIncoming ??
39
+ ((id) => DEFAULT_CORRELATION_ID_PATTERN.test(id));
40
+ const resolveIncoming = (raw) => {
41
+ if (!acceptIncoming || typeof raw !== 'string')
42
+ return undefined;
43
+ const value = trimIncoming ? raw.trim() : raw;
44
+ if (value.length === 0 || value.length > maxLength)
45
+ return undefined;
46
+ try {
47
+ return validateIncoming(value) ? value : undefined;
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ };
53
+ return (req, res, next) => {
54
+ const raw = req.headers?.[header];
55
+ const candidate = Array.isArray(raw) ? raw[0] : raw;
56
+ const correlationId = resolveIncoming(candidate) ?? getCorrelationId();
57
+ if (typeof res?.setHeader === 'function') {
58
+ res.setHeader(header, correlationId);
59
+ }
60
+ runWithCorrelationId(correlationId, next);
61
+ };
62
+ }
63
+ //# sourceMappingURL=http-correlation.middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-correlation.middleware.js","sourceRoot":"","sources":["../../src/middlewares/http-correlation.middleware.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAGtE,OAAO,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,EACL,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEL,iCAAiC,EACjC,8BAA8B,GAC/B,MAAM,mCAAmC,CAAC;AAE3C,MAAM,eAAe,GAAG,gCAAgC,CAAC;AASzD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,eAAe,CAAC,OAA4B;IAC1D,MAAM,CAAC,GAAG,OAAO,EAAE,MAAM,CAAC;IAC1B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,SAAS,CAAC,0CAA0C,CAAC,IAAI,CAAC,CAAC;IACvE,CAAC;IACD,IACE,OAAO,EAAE,SAAS,KAAK,SAAS;QAChC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC,EACpE,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,sEAAsE,CACvE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GACV,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,0BAA0B,CAAC;IACvE,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC;IACvD,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,KAAK,CAAC;IACpD,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,iCAAiC,CAAC;IAC1E,MAAM,gBAAgB,GACpB,OAAO,EAAE,gBAAgB;QACzB,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,8BAA8B,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAE5D,MAAM,eAAe,GAAG,CAAC,GAAuB,EAAsB,EAAE;QACtE,IAAI,CAAC,cAAc,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACjE,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAC9C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS;YAAE,OAAO,SAAS,CAAC;QACrE,IAAI,CAAC;YACH,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACxB,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACpD,MAAM,aAAa,GAAG,eAAe,CAAC,SAAS,CAAC,IAAI,gBAAgB,EAAE,CAAC;QACvE,IAAI,OAAO,GAAG,EAAE,SAAS,KAAK,UAAU,EAAE,CAAC;YACzC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACvC,CAAC;QACD,oBAAoB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,91 @@
1
+ /** Default upper bound for an accepted incoming correlation ID. */
2
+ export declare const DEFAULT_CORRELATION_ID_MAX_LENGTH = 128;
3
+ /**
4
+ * Characters accepted in an incoming correlation ID unless `validateIncoming`
5
+ * is configured: letters, digits and `. _ ~ : / + = @ -`, which covers UUIDs,
6
+ * W3C trace identifiers and common request-ID formats.
7
+ */
8
+ export declare const DEFAULT_CORRELATION_ID_PATTERN: RegExp;
9
+ /**
10
+ * Correlation ID configuration consumed by {@link httpCorrelation}.
11
+ *
12
+ * The middleware is registered explicitly by the application. For non-HTTP
13
+ * transports (Bull, RabbitMQ, etc.), use `runWithCorrelationId()` directly in
14
+ * your processor/handler.
15
+ *
16
+ * An incoming header value is accepted only when it is at most
17
+ * {@link DEFAULT_CORRELATION_ID_MAX_LENGTH} characters and matches
18
+ * {@link DEFAULT_CORRELATION_ID_PATTERN}; otherwise a local ID is used.
19
+ * `maxLength` and `validateIncoming` replace these defaults.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * // Custom header name
24
+ * { header: 'x-request-id' }
25
+ *
26
+ * // `header: false` selects the default header; it does not disable the middleware.
27
+ * { header: false } // uses 'x-correlation-id'
28
+ * ```
29
+ *
30
+ * @example Accept only UUIDs from clients
31
+ * ```ts
32
+ * {
33
+ * maxLength: 36,
34
+ * trimIncoming: true,
35
+ * validateIncoming: (id) => /^[0-9a-f-]{36}$/i.test(id),
36
+ * }
37
+ * ```
38
+ *
39
+ * @example Ignore client IDs completely and always generate/use the local ID
40
+ * ```ts
41
+ * { acceptIncoming: false }
42
+ * ```
43
+ */
44
+ export interface CorrelationOptions {
45
+ /**
46
+ * HTTP header name to extract the correlation ID from.
47
+ * A valid, non-empty HTTP field-name string selects that header. Invalid
48
+ * strings throw during middleware construction. Any non-string value, including `false` and
49
+ * `undefined`, makes the middleware use the default `x-correlation-id` header.
50
+ *
51
+ * @default 'x-correlation-id'
52
+ */
53
+ header?: string | false;
54
+ /**
55
+ * Whether a non-empty incoming correlation ID may be used.
56
+ * Set to `false` when correlation IDs are internal-only and should never be
57
+ * controlled by the HTTP client.
58
+ *
59
+ * @default true
60
+ */
61
+ acceptIncoming?: boolean;
62
+ /**
63
+ * Trim surrounding whitespace before optional length/custom validation and
64
+ * before storing/echoing the ID.
65
+ *
66
+ * @default false
67
+ */
68
+ trimIncoming?: boolean;
69
+ /**
70
+ * Maximum accepted incoming correlation-ID length. Longer values are
71
+ * discarded and replaced by the locally resolved/generated ID.
72
+ *
73
+ * @default 128
74
+ */
75
+ maxLength?: number;
76
+ /**
77
+ * Validation predicate for incoming IDs; replaces the default
78
+ * {@link DEFAULT_CORRELATION_ID_PATTERN} check. Returning `false` (or
79
+ * throwing) rejects the incoming value and falls back to the local
80
+ * correlation ID. `maxLength` still applies.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * {
85
+ * validateIncoming: (id) => /^[A-Za-z0-9._-]+$/.test(id),
86
+ * }
87
+ * ```
88
+ */
89
+ validateIncoming?: (correlationId: string) => boolean;
90
+ }
91
+ //# sourceMappingURL=correlation.options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.options.d.ts","sourceRoot":"","sources":["../../src/options/correlation.options.ts"],"names":[],"mappings":"AAEA,mEAAmE;AACnE,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAA4B,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAExB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC;CACvD"}
@@ -0,0 +1,10 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ /** Default upper bound for an accepted incoming correlation ID. */
3
+ export const DEFAULT_CORRELATION_ID_MAX_LENGTH = 128;
4
+ /**
5
+ * Characters accepted in an incoming correlation ID unless `validateIncoming`
6
+ * is configured: letters, digits and `. _ ~ : / + = @ -`, which covers UUIDs,
7
+ * W3C trace identifiers and common request-ID formats.
8
+ */
9
+ export const DEFAULT_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._~:/+=@-]+$/;
10
+ //# sourceMappingURL=correlation.options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.options.js","sourceRoot":"","sources":["../../src/options/correlation.options.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,mEAAmE;AACnE,MAAM,CAAC,MAAM,iCAAiC,GAAG,GAAG,CAAC;AAErD;;;;GAIG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,yBAAyB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Recursive dynamic type for deep chained property/method access on unknown
3
+ *
4
+ * Every property access and method call returns another `DynResult`, so
5
+ * optional chains like `dyn(ctx)?.getMessage?.()?.properties?.correlationId`
6
+ * compile without `any`.
7
+ */
8
+ interface DynResult {
9
+ [key: string | symbol]: DynResult;
10
+ (...args: unknown[]): DynResult;
11
+ }
12
+ /** Cast unknown value to a recursively-callable dynamic type. */
13
+ export declare function dyn(value: unknown): DynResult | undefined;
14
+ export {};
15
+ //# sourceMappingURL=safe-typing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-typing.d.ts","sourceRoot":"","sources":["../../src/types/safe-typing.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,UAAU,SAAS;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAClC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,SAAS,CAAC;CACjC;AAED,iEAAiE;AACjE,wBAAgB,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAEzD"}
@@ -0,0 +1,6 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ /** Cast unknown value to a recursively-callable dynamic type. */
3
+ export function dyn(value) {
4
+ return value;
5
+ }
6
+ //# sourceMappingURL=safe-typing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-typing.js","sourceRoot":"","sources":["../../src/types/safe-typing.ts"],"names":[],"mappings":"AAAA,sEAAsE;AActE,iEAAiE;AACjE,MAAM,UAAU,GAAG,CAAC,KAAc;IAChC,OAAO,KAA8B,CAAC;AACxC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@cqrs-ddd/pipeline-correlation",
3
+ "version": "0.5.0",
4
+ "description": "Correlation id propagation for any Node.js application: HTTP middleware, queue and message consumers, cron jobs",
5
+ "author": "Aristotelis",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=22.12.0"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/aristoteliss/ddd-cqrs.git",
14
+ "directory": "packages/pipeline-correlation"
15
+ },
16
+ "homepage": "https://aristoteliss.github.io/ddd-cqrs/packages/pipeline-correlation/",
17
+ "bugs": {
18
+ "url": "https://github.com/aristoteliss/ddd-cqrs/issues"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "main": "dist/index.js",
28
+ "types": "dist/index.d.ts",
29
+ "files": [
30
+ "dist",
31
+ "LICENSE",
32
+ "COMMERCIAL_LICENSE.txt"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "devDependencies": {
38
+ "@vitest/coverage-v8": "^5.0.3",
39
+ "vitest": "^5.0.3"
40
+ },
41
+ "dependencies": {
42
+ "@cqrs-ddd/uuidv7": "^0.5.0",
43
+ "@cqrs-ddd/untyped": "^0.5.0"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc -p tsconfig.build.json",
47
+ "build:watch": "tsc -p tsconfig.build.json --watch",
48
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
49
+ "rebuild": "pnpm run clean && pnpm run build",
50
+ "lint": "tsc --noEmit",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest"
53
+ }
54
+ }