@cqrs-ddd/pipeline 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 (112) hide show
  1. package/COMMERCIAL_LICENSE.txt +34 -0
  2. package/LICENSE +661 -0
  3. package/README.md +42 -0
  4. package/dist/behaviors/logging.behavior.d.ts +195 -0
  5. package/dist/behaviors/logging.behavior.d.ts.map +1 -0
  6. package/dist/behaviors/logging.behavior.js +206 -0
  7. package/dist/behaviors/logging.behavior.js.map +1 -0
  8. package/dist/constants/pipeline-context.constants.d.ts +34 -0
  9. package/dist/constants/pipeline-context.constants.d.ts.map +1 -0
  10. package/dist/constants/pipeline-context.constants.js +34 -0
  11. package/dist/constants/pipeline-context.constants.js.map +1 -0
  12. package/dist/create-pipeline.d.ts +122 -0
  13. package/dist/create-pipeline.d.ts.map +1 -0
  14. package/dist/create-pipeline.js +193 -0
  15. package/dist/create-pipeline.js.map +1 -0
  16. package/dist/entries.d.ts +13 -0
  17. package/dist/entries.d.ts.map +1 -0
  18. package/dist/entries.js +3 -0
  19. package/dist/entries.js.map +1 -0
  20. package/dist/errors/missing-partition.error.d.ts +27 -0
  21. package/dist/errors/missing-partition.error.d.ts.map +1 -0
  22. package/dist/errors/missing-partition.error.js +33 -0
  23. package/dist/errors/missing-partition.error.js.map +1 -0
  24. package/dist/errors/missing-pipeline-item.error.d.ts +8 -0
  25. package/dist/errors/missing-pipeline-item.error.d.ts.map +1 -0
  26. package/dist/errors/missing-pipeline-item.error.js +16 -0
  27. package/dist/errors/missing-pipeline-item.error.js.map +1 -0
  28. package/dist/handler-pipeline.d.ts +62 -0
  29. package/dist/handler-pipeline.d.ts.map +1 -0
  30. package/dist/handler-pipeline.js +74 -0
  31. package/dist/handler-pipeline.js.map +1 -0
  32. package/dist/helpers/behavior-entries.d.ts +34 -0
  33. package/dist/helpers/behavior-entries.d.ts.map +1 -0
  34. package/dist/helpers/behavior-entries.js +50 -0
  35. package/dist/helpers/behavior-entries.js.map +1 -0
  36. package/dist/helpers/behavior-id.d.ts +43 -0
  37. package/dist/helpers/behavior-id.d.ts.map +1 -0
  38. package/dist/helpers/behavior-id.js +38 -0
  39. package/dist/helpers/behavior-id.js.map +1 -0
  40. package/dist/helpers/logging.intent.d.ts +17 -0
  41. package/dist/helpers/logging.intent.d.ts.map +1 -0
  42. package/dist/helpers/logging.intent.js +18 -0
  43. package/dist/helpers/logging.intent.js.map +1 -0
  44. package/dist/helpers/postgres-json.d.ts +25 -0
  45. package/dist/helpers/postgres-json.d.ts.map +1 -0
  46. package/dist/helpers/postgres-json.js +35 -0
  47. package/dist/helpers/postgres-json.js.map +1 -0
  48. package/dist/helpers/replace-request.d.ts +19 -0
  49. package/dist/helpers/replace-request.d.ts.map +1 -0
  50. package/dist/helpers/replace-request.js +29 -0
  51. package/dist/helpers/replace-request.js.map +1 -0
  52. package/dist/helpers/tenant-partition.d.ts +38 -0
  53. package/dist/helpers/tenant-partition.d.ts.map +1 -0
  54. package/dist/helpers/tenant-partition.js +27 -0
  55. package/dist/helpers/tenant-partition.js.map +1 -0
  56. package/dist/index.d.ts +27 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +24 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/interfaces/context-source.interface.d.ts +42 -0
  61. package/dist/interfaces/context-source.interface.d.ts.map +1 -0
  62. package/dist/interfaces/context-source.interface.js +3 -0
  63. package/dist/interfaces/context-source.interface.js.map +1 -0
  64. package/dist/interfaces/pipeline-behavior-contract.interface.d.ts +99 -0
  65. package/dist/interfaces/pipeline-behavior-contract.interface.d.ts.map +1 -0
  66. package/dist/interfaces/pipeline-behavior-contract.interface.js +22 -0
  67. package/dist/interfaces/pipeline-behavior-contract.interface.js.map +1 -0
  68. package/dist/interfaces/pipeline-handler-meta.interface.d.ts +15 -0
  69. package/dist/interfaces/pipeline-handler-meta.interface.d.ts.map +1 -0
  70. package/dist/interfaces/pipeline-handler-meta.interface.js +3 -0
  71. package/dist/interfaces/pipeline-handler-meta.interface.js.map +1 -0
  72. package/dist/interfaces/pipeline.behavior.interface.d.ts +38 -0
  73. package/dist/interfaces/pipeline.behavior.interface.d.ts.map +1 -0
  74. package/dist/interfaces/pipeline.behavior.interface.js +3 -0
  75. package/dist/interfaces/pipeline.behavior.interface.js.map +1 -0
  76. package/dist/interfaces/pipeline.context.interface.d.ts +69 -0
  77. package/dist/interfaces/pipeline.context.interface.d.ts.map +1 -0
  78. package/dist/interfaces/pipeline.context.interface.js +3 -0
  79. package/dist/interfaces/pipeline.context.interface.js.map +1 -0
  80. package/dist/logger.d.ts +45 -0
  81. package/dist/logger.d.ts.map +1 -0
  82. package/dist/logger.js +64 -0
  83. package/dist/logger.js.map +1 -0
  84. package/dist/options/global-behaviors.options.d.ts +57 -0
  85. package/dist/options/global-behaviors.options.d.ts.map +1 -0
  86. package/dist/options/global-behaviors.options.js +3 -0
  87. package/dist/options/global-behaviors.options.js.map +1 -0
  88. package/dist/pipeline-items.d.ts +22 -0
  89. package/dist/pipeline-items.d.ts.map +1 -0
  90. package/dist/pipeline-items.js +34 -0
  91. package/dist/pipeline-items.js.map +1 -0
  92. package/dist/pipeline.context.d.ts +86 -0
  93. package/dist/pipeline.context.d.ts.map +1 -0
  94. package/dist/pipeline.context.js +109 -0
  95. package/dist/pipeline.context.js.map +1 -0
  96. package/dist/services/pipeline-contracts.d.ts +29 -0
  97. package/dist/services/pipeline-contracts.d.ts.map +1 -0
  98. package/dist/services/pipeline-contracts.js +102 -0
  99. package/dist/services/pipeline-contracts.js.map +1 -0
  100. package/dist/services/pipeline-plan.d.ts +49 -0
  101. package/dist/services/pipeline-plan.d.ts.map +1 -0
  102. package/dist/services/pipeline-plan.js +111 -0
  103. package/dist/services/pipeline-plan.js.map +1 -0
  104. package/dist/services/pipeline-runner.d.ts +26 -0
  105. package/dist/services/pipeline-runner.d.ts.map +1 -0
  106. package/dist/services/pipeline-runner.js +68 -0
  107. package/dist/services/pipeline-runner.js.map +1 -0
  108. package/dist/types.d.ts +3 -0
  109. package/dist/types.d.ts.map +1 -0
  110. package/dist/types.js +3 -0
  111. package/dist/types.js.map +1 -0
  112. package/package.json +63 -0
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @cqrs-ddd/pipeline
2
+
3
+ A framework-neutral pipeline engine: wrap plain functions and class methods with
4
+ behaviors such as logging, validation, authorization, caching or idempotency, and declare
5
+ the behaviors of handler classes with `@UsePipeline` and `@SkipPipeline`. No
6
+ framework, no dependency-injection container and no CQRS infrastructure are required;
7
+ the `@cqrs-ddd/pipeline-<name>` packages provide the behaviors.
8
+
9
+ **Documentation:** [guide](https://aristoteliss.github.io/ddd-cqrs/packages/pipeline/) · [API reference](https://aristoteliss.github.io/ddd-cqrs/api/cqrs-ddd/pipeline/) · [all packages](https://aristoteliss.github.io/ddd-cqrs/)
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @cqrs-ddd/pipeline
15
+ ```
16
+
17
+ Requires Node.js 22.12 or later, or another runtime with `AsyncLocalStorage` (Bun, Deno).
18
+ Published as an ES module; a CommonJS application loads it with `require()`.
19
+
20
+ ## Example
21
+
22
+ ```ts
23
+ import { createPipeline, logging } from '@cqrs-ddd/pipeline';
24
+
25
+ const pipeline = createPipeline({
26
+ globalBehaviors: { before: [logging({ requestResponseLogLevel: 'log' })] },
27
+ });
28
+
29
+ export const getPrice = pipeline.wrap({ name: 'getPrice', kind: 'query' })(
30
+ async (sku: string) => prices.find(sku),
31
+ );
32
+ ```
33
+
34
+ A method can be decorated instead, in either TypeScript decorator mode:
35
+ `@pipeline.wrap({ kind: 'query' })`. A plain function needs a `name`, which keys and logs
36
+ use to tell operations apart, and a `kind`: a `query` reads, a `command` changes state,
37
+ an `event` reacts.
38
+
39
+ ## License
40
+
41
+ Dual-licensed under **AGPLv3** and a **Commercial License**. See `LICENSE` and
42
+ `COMMERCIAL_LICENSE.txt`.
@@ -0,0 +1,195 @@
1
+ import { IPipelineBehavior, NextDelegate } from '../interfaces/pipeline.behavior.interface.js';
2
+ import { IPipelineContext } from '../interfaces/pipeline.context.interface.js';
3
+ import type { LogLevel, PipelineLogger } from '../logger.js';
4
+ /**
5
+ * An error class used as a `mapLogLevel` key. It is matched with `instanceof`, so
6
+ * it also covers its subclasses.
7
+ */
8
+ export type ErrorClass = abstract new (...args: never[]) => Error;
9
+ /**
10
+ * Configuration options for the logging behavior.
11
+ *
12
+ * Levels use {@link LogLevel} names (`'verbose'`, `'debug'`, `'log'`,
13
+ * `'warn'`, `'error'`, `'fatal'`).
14
+ * When using `nestjs-pino`, these map to pino levels:
15
+ * `'verbose'` → `trace`, `'debug'` → `debug`, `'log'` → `info`, `'warn'` → `warn`,
16
+ * `'error'` → `error`, `'fatal'` → `fatal`.
17
+ * Use `'none'` to suppress a message entirely.
18
+ *
19
+ * @example Structured application logging with safe payload visibility
20
+ * ```ts
21
+ * export const createUser = pipeline.wrap(
22
+ * { name: 'createUser', kind: 'command' },
23
+ * [LoggingBehavior, {
24
+ * logFormat: 'structured',
25
+ * requestResponseLogLevel: 'debug',
26
+ * excludeRequestObj: false,
27
+ * excludeResponseObj: true,
28
+ * redactKeys: ['code'],
29
+ * mapLogLevel: new Map([[UniqueEmailException, 'warn']]),
30
+ * }],
31
+ * )(async (command: CreateUserCommand) => users.create(command));
32
+ * ```
33
+ */
34
+ export interface LoggingBehaviorOptions {
35
+ /**
36
+ * Log level for the post-success metric line (correlation ID, request
37
+ * kind/name, handler name, and elapsed duration in ms). Emitted once
38
+ * `next()` resolves without throwing.
39
+ * Uses LogLevel names or 'none' to suppress.
40
+ * Default: 'log'.
41
+ */
42
+ metricLogLevel?: LogLevel | 'none';
43
+ /**
44
+ * Log level used when the wrapped handler throws.
45
+ * Uses LogLevel names or 'none' to suppress.
46
+ * Overridden per error type when `mapLogLevel` has a matching entry.
47
+ * Default: 'error'.
48
+ */
49
+ errorLogLevel?: LogLevel | 'none';
50
+ /**
51
+ * Per-exception-type log level override, keyed by error class.
52
+ *
53
+ * When the thrown error is an `instanceof` more than one registered key,
54
+ * the most specific (most derived) matching class wins — e.g. with both
55
+ * `Error` and `ForbiddenException` registered, a thrown `ForbiddenException`
56
+ * logs at the `ForbiddenException` level, regardless of the order the
57
+ * entries were inserted into the Map. Falls back to `errorLogLevel` when
58
+ * no key matches.
59
+ * Example: `new Map([[Error, 'error'], [ForbiddenException, 'warn']])`
60
+ */
61
+ mapLogLevel?: Map<ErrorClass, LogLevel | 'none'>;
62
+ /**
63
+ * Log level for request/response payloads.
64
+ * Uses LogLevel names or 'none' to suppress.
65
+ * Default: 'debug'.
66
+ */
67
+ requestResponseLogLevel?: LogLevel | 'none';
68
+ /**
69
+ * Keys to exclude from request/response logs.
70
+ * Passed to `safeStringify`/`safeSanitize` as a `Set<string>` for filtering object properties.
71
+ * Supports dot notation for nested properties (e.g. `'ctx.sessionUser'`).
72
+ * Example: `['password', 'token', 'ctx.sessionUser']` omits those fields from the log output.
73
+ * Default: [] (no keys excluded).
74
+ */
75
+ excludeKeys?: string[];
76
+ /**
77
+ * Additional keys or dot-paths to mask with `[REDACTED]` when request/response
78
+ * payload logging is enabled. Unlike {@link excludeKeys}, redacted properties
79
+ * stay in the payload so its shape remains visible.
80
+ *
81
+ * This option is additive and has no effect unless configured.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * logging({
86
+ * excludeRequestObj: false,
87
+ * redactKeys: ['profile.email', 'payment.cardToken'],
88
+ * });
89
+ * ```
90
+ */
91
+ redactKeys?: string[];
92
+ /**
93
+ * Masks the core {@link DEFAULT_REDACT_KEYS} (password, token, authorization,
94
+ * cookie, API keys, card data, etc.) in request/response payload logs. Custom
95
+ * {@link redactKeys} are merged on top.
96
+ *
97
+ * Defaults to `true`. Set it to `false` only when raw payload logging is an
98
+ * explicit application requirement and the caller owns the resulting exposure
99
+ * risk.
100
+ *
101
+ * @default true
102
+ *
103
+ * @example Opting out, deliberately
104
+ * ```ts
105
+ * logging({ excludeRequestObj: false, redactSensitiveKeys: false });
106
+ * ```
107
+ */
108
+ redactSensitiveKeys?: boolean;
109
+ /**
110
+ * If true, omits the request payload from logs entirely, logging the
111
+ * placeholder `'[exclude request obj]'` instead.
112
+ * Default: true.
113
+ */
114
+ excludeRequestObj?: boolean;
115
+ /**
116
+ * If true, omits the response payload from logs entirely, logging the
117
+ * placeholder `'[exclude response obj]'` instead.
118
+ * Default: true.
119
+ */
120
+ excludeResponseObj?: boolean;
121
+ /**
122
+ * Output shape for request/response/metric/error logs.
123
+ * - 'text': single interpolated string (default).
124
+ * - 'structured': plain object payload (e.g. `{ msg, request }` for
125
+ * request/response, or `{ message, stack, ... }` for errors), suitable
126
+ * for structured loggers like nestjs-pino/pino that serialize objects
127
+ * into JSON fields.
128
+ * Default: 'text'.
129
+ */
130
+ logFormat?: 'text' | 'structured';
131
+ }
132
+ /**
133
+ * Pipeline behavior that logs each request/response and execution timing.
134
+ *
135
+ * Emits the incoming request, a completion metric (duration), and the response,
136
+ * each at a configurable log level (see `LoggingBehaviorOptions`). Request and
137
+ * response payloads are excluded by default and can be opted in, with key-based
138
+ * exclusion/redaction. Logging and serialization failures do not change the
139
+ * handler result or error. If the wrapped handler throws,
140
+ * the error is logged — including its stack trace and, when present, the error's
141
+ * `optionalParams` (see `ErrorWithOptionalParams`) — and then re-thrown
142
+ * unchanged. Writes through the logger passed to the constructor, or `console`.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const pipeline = createPipeline({ behaviors: [new LoggingBehavior(pinoAdapter)] });
147
+ * ```
148
+ */
149
+ export declare class LoggingBehavior implements IPipelineBehavior {
150
+ private readonly logger;
151
+ constructor(logger?: PipelineLogger);
152
+ /**
153
+ * Logs the request, a duration metric, and the response or error around
154
+ * `next()`, using the options from `context.getBehaviorOptions(LoggingBehavior)`
155
+ * and the defaults documented on {@link LoggingBehaviorOptions}.
156
+ *
157
+ * @param context - The pipeline context for the current request.
158
+ * @param next - Delegate that invokes the next behavior/handler in the chain.
159
+ * @returns The value resolved by `next()`.
160
+ * @throws Whatever `next()` throws, unchanged, after logging it. A logging
161
+ * or serialization failure never changes the returned value or the error.
162
+ */
163
+ handle(context: IPipelineContext, next: NextDelegate): Promise<unknown>;
164
+ private observe;
165
+ /**
166
+ * Builds the sanitizer configuration for payload logging.
167
+ *
168
+ * The default sensitive keys are masked unless `redactSensitiveKeys` is
169
+ * false; keys listed in `redactKeys` are always masked. With no key to mask,
170
+ * only the exclusion set applies.
171
+ */
172
+ private buildSanitizeOptions;
173
+ /**
174
+ * Dispatches `message` (and any `optionalParams`) to the logger method
175
+ * matching `level` — e.g. `level: 'warn'` calls `this.logger.warn(...)`.
176
+ * No-op when `level` is `'none'` or the underlying logger doesn't
177
+ * implement that method.
178
+ */
179
+ private log;
180
+ /**
181
+ * Extracts extra, loggable context from an error matching
182
+ * `ErrorWithOptionalParams`: a non-null object exposing a defined
183
+ * `optionalParams` property. `handle`'s `catch` branch uses this to decide
184
+ * whether the error log should be enriched with that extra context.
185
+ *
186
+ * The value is normalized into an array so it can be spread into the error
187
+ * log payload: passed through as-is if it's already an array, or wrapped in a
188
+ * single-element array otherwise.
189
+ *
190
+ * @returns The normalized `optionalParams`, or `undefined` when the error is
191
+ * not an object or does not define `optionalParams`.
192
+ */
193
+ private optionalParamsOf;
194
+ }
195
+ //# sourceMappingURL=logging.behavior.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.behavior.d.ts","sourceRoot":"","sources":["../../src/behaviors/logging.behavior.ts"],"names":[],"mappings":"AASA,OAAO,EACL,iBAAiB,EACjB,YAAY,EACb,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAC;AAC/E,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE7D;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,KAAK,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAEnC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAElC;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,GAAG,CAAC,UAAU,EAAE,QAAQ,GAAG,MAAM,CAAC,CAAC;IAEjD;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAE5C;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;CACnC;AAOD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,eAAgB,YAAW,iBAAiB;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IAExC,YAAY,MAAM,CAAC,EAAE,cAAc,EAElC;IAED;;;;;;;;;;OAUG;IACG,MAAM,CACV,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,OAAO,CAAC,CA6HlB;IAED,OAAO,CAAC,OAAO;IAsBf;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAiB5B;;;;;OAKG;IACH,OAAO,CAAC,GAAG;IAeX;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,gBAAgB;CAMzB"}
@@ -0,0 +1,206 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ import { performance } from 'node:perf_hooks';
3
+ import { DEFAULT_REDACT_KEYS, safeSanitize, safeStringify, } from '@cqrs-ddd/safe-stringify';
4
+ /**
5
+ * Pipeline behavior that logs each request/response and execution timing.
6
+ *
7
+ * Emits the incoming request, a completion metric (duration), and the response,
8
+ * each at a configurable log level (see `LoggingBehaviorOptions`). Request and
9
+ * response payloads are excluded by default and can be opted in, with key-based
10
+ * exclusion/redaction. Logging and serialization failures do not change the
11
+ * handler result or error. If the wrapped handler throws,
12
+ * the error is logged — including its stack trace and, when present, the error's
13
+ * `optionalParams` (see `ErrorWithOptionalParams`) — and then re-thrown
14
+ * unchanged. Writes through the logger passed to the constructor, or `console`.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const pipeline = createPipeline({ behaviors: [new LoggingBehavior(pinoAdapter)] });
19
+ * ```
20
+ */
21
+ export class LoggingBehavior {
22
+ logger;
23
+ constructor(logger) {
24
+ this.logger = logger ?? console;
25
+ }
26
+ /**
27
+ * Logs the request, a duration metric, and the response or error around
28
+ * `next()`, using the options from `context.getBehaviorOptions(LoggingBehavior)`
29
+ * and the defaults documented on {@link LoggingBehaviorOptions}.
30
+ *
31
+ * @param context - The pipeline context for the current request.
32
+ * @param next - Delegate that invokes the next behavior/handler in the chain.
33
+ * @returns The value resolved by `next()`.
34
+ * @throws Whatever `next()` throws, unchanged, after logging it. A logging
35
+ * or serialization failure never changes the returned value or the error.
36
+ */
37
+ async handle(context, next) {
38
+ const options = context.getBehaviorOptions(LoggingBehavior);
39
+ const metricLogLevel = options?.metricLogLevel ?? 'log';
40
+ const requestResponseLogLevel = options?.requestResponseLogLevel ?? 'debug';
41
+ const errorLogLevel = options?.errorLogLevel ?? 'error';
42
+ const excludeKeys = new Set(options?.excludeKeys);
43
+ const sanitizeOptions = this.buildSanitizeOptions(options, excludeKeys);
44
+ const excludeRequestObj = options?.excludeRequestObj ?? true;
45
+ const excludeResponseObj = options?.excludeResponseObj ?? true;
46
+ const logFormat = options?.logFormat ?? 'text';
47
+ const structured = logFormat === 'structured';
48
+ this.observe(() => {
49
+ if (requestResponseLogLevel === 'none')
50
+ return;
51
+ const requestPayload = excludeRequestObj
52
+ ? '[exclude request obj]'
53
+ : structured
54
+ ? safeSanitize(context.request, sanitizeOptions)
55
+ : safeStringify(context.request, sanitizeOptions);
56
+ this.log(requestResponseLogLevel, structured
57
+ ? { msg: `Request → ${context.handlerName}`, request: requestPayload }
58
+ : `Request: ${requestPayload}`, context.handlerName);
59
+ });
60
+ const startTime = performance.now();
61
+ try {
62
+ const result = await next();
63
+ this.observe(() => {
64
+ const duration = (performance.now() - startTime).toFixed(2);
65
+ const metricMsg = `[${context.correlationId}] ${context.requestKind.toUpperCase()} ` +
66
+ `${context.requestName} → ${context.handlerName} completed in ${duration}ms`;
67
+ this.log(metricLogLevel, structured
68
+ ? {
69
+ msg: metricMsg,
70
+ correlationId: context.correlationId,
71
+ requestKind: context.requestKind,
72
+ requestName: context.requestName,
73
+ handlerName: context.handlerName,
74
+ durationMs: Number(duration),
75
+ }
76
+ : metricMsg, context.handlerName);
77
+ if (requestResponseLogLevel === 'none')
78
+ return;
79
+ const responsePayload = excludeResponseObj
80
+ ? '[exclude response obj]'
81
+ : result != null
82
+ ? structured
83
+ ? safeSanitize(result, sanitizeOptions)
84
+ : safeStringify(result, sanitizeOptions)
85
+ : '(void)';
86
+ this.log(requestResponseLogLevel, structured
87
+ ? {
88
+ msg: `Response ← ${context.handlerName}`,
89
+ response: responsePayload,
90
+ }
91
+ : `Response: ${responsePayload}`, context.handlerName);
92
+ });
93
+ return result;
94
+ }
95
+ catch (error) {
96
+ this.observe(() => {
97
+ const duration = (performance.now() - startTime).toFixed(2);
98
+ const err = error instanceof Error ? error : undefined;
99
+ let logLevel = errorLogLevel;
100
+ if (options?.mapLogLevel) {
101
+ let bestMatch;
102
+ for (const [errorType, level] of options.mapLogLevel.entries()) {
103
+ if (!(error instanceof errorType))
104
+ continue;
105
+ if (!bestMatch || errorType.prototype instanceof bestMatch) {
106
+ bestMatch = errorType;
107
+ logLevel = level;
108
+ }
109
+ }
110
+ }
111
+ const message = `[${context.correlationId}] ${context.requestKind.toUpperCase()} ` +
112
+ `${context.requestName} → ${context.handlerName} failed after ${duration}ms: ` +
113
+ `${err ? `${err.name}: ${err.message}` : String(error)}`;
114
+ const params = this.optionalParamsOf(error);
115
+ const optionalParams = params
116
+ ? safeSanitize(params, sanitizeOptions)
117
+ : undefined;
118
+ if (structured) {
119
+ const payload = {
120
+ message,
121
+ ...(err?.stack ? { stack: err.stack } : {}),
122
+ ...(optionalParams ? { optionalParams } : {}),
123
+ };
124
+ this.log(logLevel, payload, context.handlerName);
125
+ }
126
+ else {
127
+ this.log(logLevel, message, ...(err?.stack ? [err.stack] : []), ...(optionalParams ?? []), context.handlerName);
128
+ }
129
+ });
130
+ throw error;
131
+ }
132
+ }
133
+ observe(write) {
134
+ try {
135
+ write();
136
+ }
137
+ catch (error) {
138
+ // Observability failures must not alter business execution or error
139
+ // identity, but they must not be invisible either: a payload that always
140
+ // throws would otherwise silence a handler's logging with no trace.
141
+ try {
142
+ this.log('warn', `LoggingBehavior suppressed a log write: ${error instanceof Error
143
+ ? `${error.name}: ${error.message}`
144
+ : 'unknown error'}`);
145
+ }
146
+ catch {
147
+ // The logger itself is unusable; nothing further is safe to attempt.
148
+ }
149
+ }
150
+ }
151
+ /**
152
+ * Builds the sanitizer configuration for payload logging.
153
+ *
154
+ * The default sensitive keys are masked unless `redactSensitiveKeys` is
155
+ * false; keys listed in `redactKeys` are always masked. With no key to mask,
156
+ * only the exclusion set applies.
157
+ */
158
+ buildSanitizeOptions(options, excludeKeys) {
159
+ const redactKeys = [
160
+ ...(options?.redactSensitiveKeys === false ? [] : DEFAULT_REDACT_KEYS),
161
+ ...(options?.redactKeys ?? []),
162
+ ];
163
+ if (redactKeys.length === 0)
164
+ return excludeKeys;
165
+ return {
166
+ excludeKeys,
167
+ redactKeys,
168
+ };
169
+ }
170
+ /**
171
+ * Dispatches `message` (and any `optionalParams`) to the logger method
172
+ * matching `level` — e.g. `level: 'warn'` calls `this.logger.warn(...)`.
173
+ * No-op when `level` is `'none'` or the underlying logger doesn't
174
+ * implement that method.
175
+ */
176
+ log(level, message, ...optionalParams) {
177
+ if (level === 'none')
178
+ return;
179
+ const method = this.logger[level];
180
+ if (typeof method === 'function') {
181
+ method.call(this.logger, message, ...optionalParams);
182
+ }
183
+ }
184
+ /**
185
+ * Extracts extra, loggable context from an error matching
186
+ * `ErrorWithOptionalParams`: a non-null object exposing a defined
187
+ * `optionalParams` property. `handle`'s `catch` branch uses this to decide
188
+ * whether the error log should be enriched with that extra context.
189
+ *
190
+ * The value is normalized into an array so it can be spread into the error
191
+ * log payload: passed through as-is if it's already an array, or wrapped in a
192
+ * single-element array otherwise.
193
+ *
194
+ * @returns The normalized `optionalParams`, or `undefined` when the error is
195
+ * not an object or does not define `optionalParams`.
196
+ */
197
+ optionalParamsOf(error) {
198
+ if (typeof error !== 'object' || error === null)
199
+ return undefined;
200
+ const { optionalParams } = error;
201
+ if (optionalParams === undefined)
202
+ return undefined;
203
+ return Array.isArray(optionalParams) ? optionalParams : [optionalParams];
204
+ }
205
+ }
206
+ //# sourceMappingURL=logging.behavior.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.behavior.js","sourceRoot":"","sources":["../../src/behaviors/logging.behavior.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { IPipelineContext } from '../interfaces/pipeline.context.interface.js';
3
+ /**
4
+ * Async-local store that propagates pipeline context across the async call chain.
5
+ * When a saga or nested CommandBus.execute() triggers a child handler,
6
+ * the child's PipelineContext inherits the parent's correlationId automatically.
7
+ *
8
+ * Stores a reference to the live context object so child pipelines inherit the
9
+ * same immutable correlation ID.
10
+ *
11
+ * Uses Node.js built-in `AsyncLocalStorage` — zero external dependencies.
12
+ */
13
+ export declare const pipelineStore: AsyncLocalStorage<IPipelineContext<unknown, unknown>>;
14
+ /**
15
+ * Symbol-keyed setter for `response`. Only code that imports this symbol
16
+ * can write to `context.response` — keeping it readonly for behaviors.
17
+ *
18
+ * @internal Used by the pipeline runner.
19
+ */
20
+ export declare const SET_RESPONSE: unique symbol;
21
+ /** @internal Sets the correlation ID before execution begins. */
22
+ export declare const SET_CORRELATION_ID: unique symbol;
23
+ /**
24
+ * Symbol-keyed setter for `tenantId`. The runner assigns the tenant before the
25
+ * behavior chain starts; a custom runner may assign it on a context it
26
+ * constructs. The tenant is write-once: assigning a different value throws.
27
+ */
28
+ export declare const SET_TENANT_ID: unique symbol;
29
+ /**
30
+ * Symbol-keyed setter for `request`, through which a behavior hands the handler a
31
+ * replacement input, such as a parsed copy; see `replaceRequest`.
32
+ */
33
+ export declare const SET_REQUEST: unique symbol;
34
+ //# sourceMappingURL=pipeline-context.constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline-context.constants.d.ts","sourceRoot":"","sources":["../../src/constants/pipeline-context.constants.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAC;AAE/E;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,uDAA4C,CAAC;AAEvE;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,OAAO,MAEjC,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,EAAE,OAAO,MAEvC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,OAAO,MAElC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,OAAO,MAA6C,CAAC"}
@@ -0,0 +1,34 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ /**
4
+ * Async-local store that propagates pipeline context across the async call chain.
5
+ * When a saga or nested CommandBus.execute() triggers a child handler,
6
+ * the child's PipelineContext inherits the parent's correlationId automatically.
7
+ *
8
+ * Stores a reference to the live context object so child pipelines inherit the
9
+ * same immutable correlation ID.
10
+ *
11
+ * Uses Node.js built-in `AsyncLocalStorage` — zero external dependencies.
12
+ */
13
+ export const pipelineStore = new AsyncLocalStorage();
14
+ /**
15
+ * Symbol-keyed setter for `response`. Only code that imports this symbol
16
+ * can write to `context.response` — keeping it readonly for behaviors.
17
+ *
18
+ * @internal Used by the pipeline runner.
19
+ */
20
+ export const SET_RESPONSE = Symbol('PipelineContext.setResponse');
21
+ /** @internal Sets the correlation ID before execution begins. */
22
+ export const SET_CORRELATION_ID = Symbol('PipelineContext.setCorrelationId');
23
+ /**
24
+ * Symbol-keyed setter for `tenantId`. The runner assigns the tenant before the
25
+ * behavior chain starts; a custom runner may assign it on a context it
26
+ * constructs. The tenant is write-once: assigning a different value throws.
27
+ */
28
+ export const SET_TENANT_ID = Symbol('PipelineContext.setTenantId');
29
+ /**
30
+ * Symbol-keyed setter for `request`, through which a behavior hands the handler a
31
+ * replacement input, such as a parsed copy; see `replaceRequest`.
32
+ */
33
+ export const SET_REQUEST = Symbol('PipelineContext.setRequest');
34
+ //# sourceMappingURL=pipeline-context.constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline-context.constants.js","sourceRoot":"","sources":["../../src/constants/pipeline-context.constants.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,iBAAiB,EAAoB,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAkB,MAAM,CAC/C,6BAA6B,CAC9B,CAAC;AAEF,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAkB,MAAM,CACrD,kCAAkC,CACnC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAkB,MAAM,CAChD,6BAA6B,CAC9B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAkB,MAAM,CAAC,4BAA4B,CAAC,CAAC"}
@@ -0,0 +1,122 @@
1
+ import type { PipelineBehaviorEntry } from './entries.js';
2
+ import type { ContextSources } from './interfaces/context-source.interface.js';
3
+ import type { IPipelineBehavior } from './interfaces/pipeline.behavior.interface.js';
4
+ import type { PipelineLogger } from './logger.js';
5
+ import type { GlobalBehaviorsOptions } from './options/global-behaviors.options.js';
6
+ import { type RequestKind } from './services/pipeline-plan.js';
7
+ import type { Constructor } from './types.js';
8
+ /** The kinds a wrapped function or a request declares. */
9
+ export type DeclaredKind = Exclude<RequestKind, 'unknown'>;
10
+ /**
11
+ * Brand by which a request tells a pipeline its kind, so a wrapped method needs no
12
+ * `kind` option; the request's class name then names the operation. `@cqrs-ddd/core`
13
+ * puts it on `BaseCommand`, `BaseQuery` and `DomainEvent`; any class can declare it.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * class GetPriceQuery {
18
+ * get [REQUEST_KIND]() {
19
+ * return 'query' as const;
20
+ * }
21
+ * constructor(readonly sku: string) {}
22
+ * }
23
+ * ```
24
+ */
25
+ export declare const REQUEST_KIND: unique symbol;
26
+ /** Configuration of {@link createPipeline}. */
27
+ export interface PipelineOptions {
28
+ /**
29
+ * Instances of the behaviors that need dependencies, one per behavior class. A placed
30
+ * behavior without an instance is constructed with no arguments.
31
+ */
32
+ behaviors?: readonly IPipelineBehavior[];
33
+ /**
34
+ * Behaviors applied to every wrapped function, by scope: `before` the call-site
35
+ * entries, `after` them. A behavior placed here and at a call site runs once, at its
36
+ * global position, with the call-site options shallowly merged over these.
37
+ */
38
+ globalBehaviors?: GlobalBehaviorsOptions | GlobalBehaviorsOptions[];
39
+ /** Where executions take their tenant and correlation id from. */
40
+ sources?: ContextSources;
41
+ /**
42
+ * What a behavior contract violation does when a function or method is wrapped (module
43
+ * load or class definition): throw (`'strict'`), log a warning (`'warn'`), or nothing
44
+ * (`'off'`).
45
+ * @default 'strict'
46
+ */
47
+ diagnostics?: 'strict' | 'warn' | 'off';
48
+ /**
49
+ * Receives the `'warn'` diagnostics, and is the logger of a `LoggingBehavior` the pipeline
50
+ * constructs itself (one placed without an instance in `behaviors`).
51
+ * @default console
52
+ */
53
+ logger?: PipelineLogger;
54
+ }
55
+ /** Options of one {@link Pipeline.wrap} call. */
56
+ export interface WrapOptions {
57
+ /**
58
+ * The operation name, used in keys and logs. Required for a plain function; a method
59
+ * defaults to `Class.method`, or to the class name of a branded request.
60
+ */
61
+ name?: string;
62
+ /**
63
+ * What the operation does: a `query` reads, a `command` changes state, an `event`
64
+ * reacts. Required unless every request carries {@link REQUEST_KIND}.
65
+ */
66
+ kind?: DeclaredKind;
67
+ /** Global behaviors this operation opts out of. */
68
+ skip?: readonly Constructor<IPipelineBehavior>[];
69
+ }
70
+ type AnyFunction = (...args: any[]) => unknown;
71
+ /**
72
+ * What {@link Pipeline.wrap} returns: wraps a plain function, or decorates a method in
73
+ * the standard and the `experimentalDecorators` mode. The wrapped function returns a
74
+ * promise.
75
+ */
76
+ export interface Wrap {
77
+ <F extends AnyFunction>(fn: F): (...args: Parameters<F>) => Promise<Awaited<ReturnType<F>>>;
78
+ <F extends AnyFunction>(method: F, context: ClassMethodDecoratorContext): F;
79
+ (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor): PropertyDescriptor;
80
+ }
81
+ /** A configured pipeline; see {@link createPipeline}. */
82
+ export interface Pipeline {
83
+ /**
84
+ * Prepares the behaviors of one operation: entries are behavior classes or
85
+ * `[Behavior, options]` tuples, outermost first, such as `cache({ key })`.
86
+ *
87
+ * @throws TypeError on a malformed entry or an unknown `kind`.
88
+ * @throws PipelineConfigurationError in `'strict'` mode when the result wraps a function
89
+ * or method of a declared `kind` whose behaviors violate a contract.
90
+ */
91
+ wrap(...entries: PipelineBehaviorEntry[]): Wrap;
92
+ wrap(options: WrapOptions, ...entries: PipelineBehaviorEntry[]): Wrap;
93
+ }
94
+ /**
95
+ * Creates a pipeline: behaviors that wrap plain functions and class methods with
96
+ * cross-cutting concerns, with no framework and no dependency-injection container.
97
+ *
98
+ * Global behaviors are constructed when the pipeline is created, so a behavior whose
99
+ * required dependency is missing fails here rather than at its first call.
100
+ *
101
+ * @throws TypeError on a malformed global entry or two instances of one behavior class.
102
+ * @example
103
+ * ```ts
104
+ * const pipeline = createPipeline({
105
+ * behaviors: [new CacheBehavior(buildCache({}))],
106
+ * globalBehaviors: { before: [logging({ requestResponseLogLevel: 'log' })] },
107
+ * });
108
+ *
109
+ * export const getPrice = pipeline.wrap(
110
+ * { name: 'getPrice', kind: 'query' },
111
+ * cache({ key }),
112
+ * )(async (sku: string) => prices.find(sku));
113
+ *
114
+ * class Prices {
115
+ * @pipeline.wrap({ kind: 'query' }, cache({ key }))
116
+ * async find(sku: string) {}
117
+ * }
118
+ * ```
119
+ */
120
+ export declare function createPipeline(options?: PipelineOptions): Pipeline;
121
+ export {};
122
+ //# sourceMappingURL=create-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-pipeline.d.ts","sourceRoot":"","sources":["../src/create-pipeline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAM1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6CAA6C,CAAC;AAKrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAEpF,OAAO,EAEL,KAAK,WAAW,EAEjB,MAAM,6BAA6B,CAAC;AAKrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,0DAA0D;AAC1D,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;AAI3D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,eAAuC,CAAC;AAEjE,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACzC;;;;OAIG;IACH,eAAe,CAAC,EAAE,sBAAsB,GAAG,sBAAsB,EAAE,CAAC;IACpE,kEAAkE;IAClE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;IACxC;;;;OAIG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,mDAAmD;IACnD,IAAI,CAAC,EAAE,SAAS,WAAW,CAAC,iBAAiB,CAAC,EAAE,CAAC;CAClD;AAGD,KAAK,WAAW,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC;AAE/C;;;;GAIG;AACH,MAAM,WAAW,IAAI;IACnB,CAAC,CAAC,SAAS,WAAW,EACpB,EAAE,EAAE,CAAC,GACJ,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,2BAA2B,GAAG,CAAC,CAAC;IAC5E,CACE,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,UAAU,EAAE,kBAAkB,GAC7B,kBAAkB,CAAC;CACvB;AAED,yDAAyD;AACzD,MAAM,WAAW,QAAQ;IACvB;;;;;;;OAOG;IACH,IAAI,CAAC,GAAG,OAAO,EAAE,qBAAqB,EAAE,GAAG,IAAI,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,EAAE,qBAAqB,EAAE,GAAG,IAAI,CAAC;CACvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CA0KtE"}