@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.
- package/COMMERCIAL_LICENSE.txt +34 -0
- package/LICENSE +661 -0
- package/README.md +32 -0
- package/dist/constants/correlation.constants.d.ts +5 -0
- package/dist/constants/correlation.constants.d.ts.map +1 -0
- package/dist/constants/correlation.constants.js +6 -0
- package/dist/constants/correlation.constants.js.map +1 -0
- package/dist/correlation.store.d.ts +174 -0
- package/dist/correlation.store.d.ts.map +1 -0
- package/dist/correlation.store.js +176 -0
- package/dist/correlation.store.js.map +1 -0
- package/dist/decorators/with-correlation.decorator.d.ts +184 -0
- package/dist/decorators/with-correlation.decorator.d.ts.map +1 -0
- package/dist/decorators/with-correlation.decorator.js +85 -0
- package/dist/decorators/with-correlation.decorator.js.map +1 -0
- package/dist/helpers/method-decorator.d.ts +13 -0
- package/dist/helpers/method-decorator.d.ts.map +1 -0
- package/dist/helpers/method-decorator.js +24 -0
- package/dist/helpers/method-decorator.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/middlewares/http-correlation.middleware.d.ts +25 -0
- package/dist/middlewares/http-correlation.middleware.d.ts.map +1 -0
- package/dist/middlewares/http-correlation.middleware.js +63 -0
- package/dist/middlewares/http-correlation.middleware.js.map +1 -0
- package/dist/options/correlation.options.d.ts +91 -0
- package/dist/options/correlation.options.d.ts.map +1 -0
- package/dist/options/correlation.options.js +10 -0
- package/dist/options/correlation.options.js.map +1 -0
- package/dist/types/safe-typing.d.ts +15 -0
- package/dist/types/safe-typing.d.ts.map +1 -0
- package/dist/types/safe-typing.js +6 -0
- package/dist/types/safe-typing.js.map +1 -0
- 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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|