@kronos-ts/messaging 0.5.1 → 0.6.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/dist/command-handling-module.d.ts.map +1 -1
- package/dist/command-handling-module.js +4 -12
- package/dist/command-handling-module.js.map +1 -1
- package/dist/correlation-data.d.ts +38 -8
- package/dist/correlation-data.d.ts.map +1 -1
- package/dist/correlation-data.js +57 -20
- package/dist/correlation-data.js.map +1 -1
- package/dist/event-gateway.d.ts.map +1 -1
- package/dist/event-gateway.js +1 -0
- package/dist/event-gateway.js.map +1 -1
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +3 -0
- package/dist/gateway.js.map +1 -1
- package/dist/handler-enhancer.d.ts +2 -1
- package/dist/handler-enhancer.d.ts.map +1 -1
- package/dist/handler-enhancer.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/message.d.ts +18 -0
- package/dist/message.d.ts.map +1 -1
- package/dist/send.d.ts.map +1 -1
- package/dist/send.js +1 -0
- package/dist/send.js.map +1 -1
- package/dist/span-factory.d.ts +32 -1
- package/dist/span-factory.d.ts.map +1 -1
- package/dist/span-factory.js +3 -0
- package/dist/span-factory.js.map +1 -1
- package/dist/streaming-event-processor.d.ts +7 -0
- package/dist/streaming-event-processor.d.ts.map +1 -1
- package/dist/streaming-event-processor.js +7 -1
- package/dist/streaming-event-processor.js.map +1 -1
- package/dist/subscribing-event-processor.d.ts +8 -0
- package/dist/subscribing-event-processor.d.ts.map +1 -1
- package/dist/subscribing-event-processor.js +7 -1
- package/dist/subscribing-event-processor.js.map +1 -1
- package/dist/tracing-command-bus.d.ts +8 -5
- package/dist/tracing-command-bus.d.ts.map +1 -1
- package/dist/tracing-command-bus.js +21 -19
- package/dist/tracing-command-bus.js.map +1 -1
- package/dist/tracing-handler-enhancer.d.ts +8 -2
- package/dist/tracing-handler-enhancer.d.ts.map +1 -1
- package/dist/tracing-handler-enhancer.js +45 -4
- package/dist/tracing-handler-enhancer.js.map +1 -1
- package/dist/tracking-event-processor.d.ts +7 -0
- package/dist/tracking-event-processor.d.ts.map +1 -1
- package/dist/tracking-event-processor.js +10 -1
- package/dist/tracking-event-processor.js.map +1 -1
- package/package.json +8 -3
- package/src/command-handling-module.ts +4 -12
- package/src/correlation-data.ts +67 -25
- package/src/event-gateway.ts +1 -0
- package/src/gateway.ts +3 -0
- package/src/handler-enhancer.ts +3 -1
- package/src/index.ts +2 -0
- package/src/message.ts +23 -2
- package/src/send.ts +1 -0
- package/src/span-factory.ts +37 -1
- package/src/streaming-event-processor.ts +13 -0
- package/src/subscribing-event-processor.ts +14 -0
- package/src/tracing-command-bus.ts +23 -19
- package/src/tracing-handler-enhancer.ts +56 -5
- package/src/tracking-event-processor.ts +16 -0
package/src/correlation-data.ts
CHANGED
|
@@ -92,6 +92,68 @@ export function simpleCorrelationDataProvider(...metadataKeys: string[]): Correl
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Extract phase (shared by the handler interceptor and the event processors)
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Compute correlation data from the given providers for `message` and merge it
|
|
101
|
+
* into the active UnitOfWork's correlation-data resource (`CORRELATION_DATA_KEY`).
|
|
102
|
+
*
|
|
103
|
+
* This is the reusable "extract" step. It is run:
|
|
104
|
+
* - by {@link correlationDataHandlerInterceptor} for command/query handlers, and
|
|
105
|
+
* - by the event processors per-event before invoking event handlers, so an
|
|
106
|
+
* automation's outgoing commands/events inherit the triggering event's
|
|
107
|
+
* lineage.
|
|
108
|
+
*
|
|
109
|
+
* Each provider is called with the message. Exceptions are caught and logged
|
|
110
|
+
* (they don't break message processing). Results merge over any existing
|
|
111
|
+
* correlation data, so values contributed earlier (e.g. via
|
|
112
|
+
* {@link contributeCorrelationData}) are preserved unless a provider overrides
|
|
113
|
+
* the same key.
|
|
114
|
+
*
|
|
115
|
+
* Must be called inside an active UnitOfWork.
|
|
116
|
+
*/
|
|
117
|
+
export function applyCorrelationData(
|
|
118
|
+
message: Message,
|
|
119
|
+
providers: ReadonlyArray<CorrelationDataProvider>,
|
|
120
|
+
): void {
|
|
121
|
+
const correlationData: Record<string, string> = {}
|
|
122
|
+
|
|
123
|
+
for (const provider of providers) {
|
|
124
|
+
try {
|
|
125
|
+
Object.assign(correlationData, provider.correlationDataFor(message))
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.warn(
|
|
128
|
+
"Encountered exception creating correlation data from provider:",
|
|
129
|
+
err,
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const existing = getActiveCorrelationData() ?? {}
|
|
135
|
+
setResource(CORRELATION_DATA_KEY, { ...existing, ...correlationData })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Contribute additional correlation data to the active UnitOfWork, merged over
|
|
140
|
+
* whatever is already present under `CORRELATION_DATA_KEY`. The merged set is
|
|
141
|
+
* applied to every message dispatched/appended from this UnitOfWork by the
|
|
142
|
+
* correlation-data dispatch interceptor and the event appender.
|
|
143
|
+
*
|
|
144
|
+
* Use this from a handler enhancer or handler to seed extra lineage keys that
|
|
145
|
+
* the built-in providers don't cover — for example an OpenTelemetry
|
|
146
|
+
* `traceparent` so the trace context rides along on outgoing messages. This is
|
|
147
|
+
* the supported alternative to mutating the object returned by
|
|
148
|
+
* {@link getActiveCorrelationData}.
|
|
149
|
+
*
|
|
150
|
+
* Throws `NoActiveUnitOfWork` when called outside an active UnitOfWork.
|
|
151
|
+
*/
|
|
152
|
+
export function contributeCorrelationData(partial: Record<string, string>): void {
|
|
153
|
+
const existing = getActiveCorrelationData() ?? {}
|
|
154
|
+
setResource(CORRELATION_DATA_KEY, { ...existing, ...partial })
|
|
155
|
+
}
|
|
156
|
+
|
|
95
157
|
// ---------------------------------------------------------------------------
|
|
96
158
|
// Interceptor factory
|
|
97
159
|
// ---------------------------------------------------------------------------
|
|
@@ -100,38 +162,18 @@ export function simpleCorrelationDataProvider(...metadataKeys: string[]): Correl
|
|
|
100
162
|
* Creates a handler interceptor that extracts correlation data from the
|
|
101
163
|
* incoming message and stores it in the ProcessingContext.
|
|
102
164
|
*
|
|
103
|
-
* This is the "extract" phase of the dual-interceptor pattern
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* later providers override earlier ones on key conflicts.
|
|
108
|
-
*
|
|
109
|
-
* The correlation data is stored as a ProcessingContext resource under
|
|
110
|
-
* `CORRELATION_DATA_KEY`, where the dispatch interceptor reads it.
|
|
165
|
+
* This is the "extract" phase of the dual-interceptor pattern, delegating to
|
|
166
|
+
* {@link applyCorrelationData}. The correlation data is stored as a
|
|
167
|
+
* ProcessingContext resource under `CORRELATION_DATA_KEY`, where the dispatch
|
|
168
|
+
* interceptor reads it.
|
|
111
169
|
*/
|
|
112
170
|
export function correlationDataHandlerInterceptor(
|
|
113
171
|
providers: ReadonlyArray<CorrelationDataProvider>,
|
|
114
172
|
): HandlerInterceptor {
|
|
115
173
|
return (message, next) => {
|
|
116
|
-
const correlationData: Record<string, string> = {}
|
|
117
|
-
|
|
118
|
-
for (const provider of providers) {
|
|
119
|
-
try {
|
|
120
|
-
const data = provider.correlationDataFor(message)
|
|
121
|
-
Object.assign(correlationData, data)
|
|
122
|
-
} catch (err) {
|
|
123
|
-
console.warn(
|
|
124
|
-
"Encountered exception creating correlation data from provider:",
|
|
125
|
-
err,
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Store in ALS-backed processing state.
|
|
131
174
|
// CTX-01 / Plan 03-03: HandlerInterceptor no longer threads ProcessingContext;
|
|
132
175
|
// resource writes go directly through the module-level ALS accessor.
|
|
133
|
-
|
|
134
|
-
|
|
176
|
+
applyCorrelationData(message, providers)
|
|
135
177
|
return next()
|
|
136
178
|
}
|
|
137
179
|
}
|
package/src/event-gateway.ts
CHANGED
|
@@ -28,6 +28,7 @@ export function createEventGateway(eventSink: EventSink): EventGateway {
|
|
|
28
28
|
async publish(descriptor, payload, metadata = {}) {
|
|
29
29
|
const tags = descriptor.tags ? descriptor.tags(payload) : []
|
|
30
30
|
const event: EventMessage = {
|
|
31
|
+
kind: "event",
|
|
31
32
|
identifier: generateIdentifier(),
|
|
32
33
|
name: descriptor.name,
|
|
33
34
|
version: descriptor.version,
|
package/src/gateway.ts
CHANGED
|
@@ -86,6 +86,7 @@ export function createCommandGateway(bus: CommandBus): CommandGateway {
|
|
|
86
86
|
async send(descriptor, payload, metadata) {
|
|
87
87
|
const resolvedMetadata = metadata ?? emptyMetadata()
|
|
88
88
|
return bus.dispatch({
|
|
89
|
+
kind: "command",
|
|
89
90
|
identifier: generateIdentifier(),
|
|
90
91
|
name: descriptor.name,
|
|
91
92
|
payload,
|
|
@@ -114,6 +115,7 @@ export function createQueryGateway(
|
|
|
114
115
|
// bus.query, which handles its own UoW.
|
|
115
116
|
return unitOfWorkRunner(resolvedMetadata, () =>
|
|
116
117
|
bus.query({
|
|
118
|
+
kind: "query",
|
|
117
119
|
identifier: generateIdentifier(),
|
|
118
120
|
name: descriptor.name,
|
|
119
121
|
payload,
|
|
@@ -125,6 +127,7 @@ export function createQueryGateway(
|
|
|
125
127
|
|
|
126
128
|
subscriptionQuery(descriptor, payload, metadata) {
|
|
127
129
|
return bus.subscriptionQuery({
|
|
130
|
+
kind: "query",
|
|
128
131
|
identifier: generateIdentifier(),
|
|
129
132
|
name: descriptor.name,
|
|
130
133
|
payload,
|
package/src/handler-enhancer.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import type { MessageKind } from "./message.js"
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Metadata about a handler being enhanced. Allows enhancers to
|
|
3
5
|
* selectively wrap based on handler type, message name, etc.
|
|
4
6
|
*/
|
|
5
7
|
export interface HandlerMetadata {
|
|
6
8
|
/** The type of message this handler processes. */
|
|
7
|
-
readonly messageType:
|
|
9
|
+
readonly messageType: MessageKind
|
|
8
10
|
/** The qualified name of the message (e.g., "university.courses.CreateCourse"). */
|
|
9
11
|
readonly messageName: string
|
|
10
12
|
/** The name of the handler group or module (e.g., "course-commands"). */
|
package/src/index.ts
CHANGED
|
@@ -109,6 +109,8 @@ export {
|
|
|
109
109
|
type CorrelationDataProvider,
|
|
110
110
|
CORRELATION_DATA_KEY,
|
|
111
111
|
getActiveCorrelationData,
|
|
112
|
+
applyCorrelationData,
|
|
113
|
+
contributeCorrelationData,
|
|
112
114
|
messageOriginProvider,
|
|
113
115
|
simpleCorrelationDataProvider,
|
|
114
116
|
correlationDataHandlerInterceptor,
|
package/src/message.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import type { QualifiedName, Metadata } from "@kronos-ts/common"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Discriminates a message by its dispatch category.
|
|
5
|
+
*
|
|
6
|
+
* TypeScript erases interfaces at runtime, so `CommandMessage`, `EventMessage`,
|
|
7
|
+
* and `QueryMessage` — which are otherwise shape-identical — cannot be told
|
|
8
|
+
* apart with `instanceof` the way Axon Framework's nominal interfaces can.
|
|
9
|
+
* This field is the structural-typing equivalent of that `instanceof` check:
|
|
10
|
+
* it lets a reusable handler interceptor branch on message category without
|
|
11
|
+
* being pinned to a single bus.
|
|
12
|
+
*/
|
|
13
|
+
export type MessageKind = "command" | "event" | "query"
|
|
14
|
+
|
|
3
15
|
/**
|
|
4
16
|
* A message carrying a payload and metadata, identified by a unique ID
|
|
5
17
|
* and routed by its qualified name.
|
|
18
|
+
*
|
|
19
|
+
* `kind` is derived at construction/reconstruction time, never persisted —
|
|
20
|
+
* each bus and event-store reconstruction site sets it from context.
|
|
6
21
|
*/
|
|
7
22
|
export interface Message<P = unknown> {
|
|
23
|
+
readonly kind: MessageKind
|
|
8
24
|
readonly identifier: string
|
|
9
25
|
readonly name: QualifiedName
|
|
10
26
|
readonly payload: P
|
|
@@ -15,7 +31,9 @@ export interface Message<P = unknown> {
|
|
|
15
31
|
/**
|
|
16
32
|
* A command message — dispatched to exactly one handler, may return a result.
|
|
17
33
|
*/
|
|
18
|
-
export interface CommandMessage<P = unknown> extends Message<P> {
|
|
34
|
+
export interface CommandMessage<P = unknown> extends Message<P> {
|
|
35
|
+
readonly kind: "command"
|
|
36
|
+
}
|
|
19
37
|
|
|
20
38
|
/**
|
|
21
39
|
* A command result message — the response from handling a command.
|
|
@@ -31,6 +49,7 @@ export interface CommandResultMessage<R = unknown> {
|
|
|
31
49
|
* An event message — published to all interested handlers.
|
|
32
50
|
*/
|
|
33
51
|
export interface EventMessage<P = unknown> extends Message<P> {
|
|
52
|
+
readonly kind: "event"
|
|
34
53
|
readonly version: string
|
|
35
54
|
readonly tags: ReadonlyArray<{ readonly key: string; readonly value: string }>
|
|
36
55
|
}
|
|
@@ -43,4 +62,6 @@ export interface SequencedEventMessage<P = unknown> extends EventMessage<P> {
|
|
|
43
62
|
/**
|
|
44
63
|
* A query message — dispatched to handler(s) that can answer it.
|
|
45
64
|
*/
|
|
46
|
-
export interface QueryMessage<P = unknown> extends Message<P> {
|
|
65
|
+
export interface QueryMessage<P = unknown> extends Message<P> {
|
|
66
|
+
readonly kind: "query"
|
|
67
|
+
}
|
package/src/send.ts
CHANGED
|
@@ -35,6 +35,7 @@ export const send: CommandDispatchFunction = async (descriptor, payload) => {
|
|
|
35
35
|
const bus = state.resources.get(COMMAND_BUS_KEY.symbol) as CommandBus | undefined
|
|
36
36
|
if (!bus) throw new Error("No command bus configured")
|
|
37
37
|
return bus.dispatch({
|
|
38
|
+
kind: "command",
|
|
38
39
|
identifier: generateIdentifier(),
|
|
39
40
|
name: descriptor.name,
|
|
40
41
|
payload,
|
package/src/span-factory.ts
CHANGED
|
@@ -12,6 +12,16 @@ export interface Span {
|
|
|
12
12
|
end(): void
|
|
13
13
|
/** Record an error on the span and end it. */
|
|
14
14
|
recordException(error: Error): void
|
|
15
|
+
/**
|
|
16
|
+
* Run `fn` with this span set as the active trace context, returning `fn`'s
|
|
17
|
+
* result. Spans created — and trace context read (`propagateContext` /
|
|
18
|
+
* `currentTraceContext`) — inside `fn` are parented to this span. When `fn`
|
|
19
|
+
* is async the span stays active across its awaits.
|
|
20
|
+
*
|
|
21
|
+
* Optional: callers must fall back to invoking `fn` directly when a Span
|
|
22
|
+
* implementation doesn't provide it.
|
|
23
|
+
*/
|
|
24
|
+
runActive?<T>(fn: () => T): T
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
/**
|
|
@@ -37,10 +47,22 @@ export interface SpanFactory {
|
|
|
37
47
|
|
|
38
48
|
/**
|
|
39
49
|
* Create a span for handling a message (consumer side).
|
|
40
|
-
* Extracts trace context from the parent message's metadata
|
|
50
|
+
* Extracts trace context from the parent message's metadata and continues
|
|
51
|
+
* that trace (the new span is a child in the same trace).
|
|
41
52
|
*/
|
|
42
53
|
createHandlerSpan(operationName: string, parentMessage: Message): Span
|
|
43
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Like {@link createHandlerSpan}, but starts a NEW trace linked to the parent
|
|
57
|
+
* message's trace context instead of continuing it. Use for asynchronously
|
|
58
|
+
* handled messages (e.g. streaming/tracking event processors) where joining a
|
|
59
|
+
* possibly long-finished originating trace would be misleading — the link
|
|
60
|
+
* preserves correlation without false nesting.
|
|
61
|
+
*
|
|
62
|
+
* Optional: callers fall back to {@link createHandlerSpan} when absent.
|
|
63
|
+
*/
|
|
64
|
+
createLinkedHandlerSpan?(operationName: string, parentMessage: Message): Span
|
|
65
|
+
|
|
44
66
|
/**
|
|
45
67
|
* Create a span for dispatching a message (producer side).
|
|
46
68
|
* Links to the parent message's trace context.
|
|
@@ -56,6 +78,17 @@ export interface SpanFactory {
|
|
|
56
78
|
*/
|
|
57
79
|
propagateContext<M extends Message>(message: M): M
|
|
58
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Returns the currently active trace context as propagation headers (e.g. the
|
|
83
|
+
* W3C `traceparent`), or an empty object when no span is active. Used to store
|
|
84
|
+
* the handler's trace context on the UnitOfWork (via `contributeCorrelationData`)
|
|
85
|
+
* so it rides along on appended and dispatched messages — including those
|
|
86
|
+
* published at commit time, after the handler span has ended.
|
|
87
|
+
*
|
|
88
|
+
* Optional: callers treat absence as "no trace context".
|
|
89
|
+
*/
|
|
90
|
+
currentTraceContext?(): Record<string, string>
|
|
91
|
+
|
|
59
92
|
/** Register a custom span attribute provider. */
|
|
60
93
|
registerSpanAttributeProvider(provider: SpanAttributesProvider): void
|
|
61
94
|
}
|
|
@@ -68,14 +101,17 @@ export function noOpSpanFactory(): SpanFactory {
|
|
|
68
101
|
start() { return this },
|
|
69
102
|
end() {},
|
|
70
103
|
recordException() {},
|
|
104
|
+
runActive<T>(fn: () => T): T { return fn() },
|
|
71
105
|
}
|
|
72
106
|
|
|
73
107
|
return {
|
|
74
108
|
createRootTrace() { return noOpSpan },
|
|
75
109
|
createHandlerSpan() { return noOpSpan },
|
|
110
|
+
createLinkedHandlerSpan() { return noOpSpan },
|
|
76
111
|
createDispatchSpan() { return noOpSpan },
|
|
77
112
|
createInternalSpan() { return noOpSpan },
|
|
78
113
|
propagateContext<M extends Message>(message: M) { return message },
|
|
114
|
+
currentTraceContext() { return {} },
|
|
79
115
|
registerSpanAttributeProvider() {},
|
|
80
116
|
}
|
|
81
117
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
advanceToken,
|
|
26
26
|
} from "./tracking-token.js"
|
|
27
27
|
import { REPLAY_STATE_KEY } from "./replay-token.js"
|
|
28
|
+
import { applyCorrelationData, type CorrelationDataProvider } from "./correlation-data.js"
|
|
28
29
|
import { setResource, onPrepareCommit } from "./processing-state.js"
|
|
29
30
|
import type { CommandBus } from "./command-bus.js"
|
|
30
31
|
import type { QueryBus } from "./query-bus.js"
|
|
@@ -84,6 +85,12 @@ export interface StreamingEventProcessorOptions {
|
|
|
84
85
|
queryBus?: QueryBus
|
|
85
86
|
/** Event scheduler injected into ALS at handler-invocation entry (read by schedule()). */
|
|
86
87
|
eventScheduler?: EventScheduler
|
|
88
|
+
/**
|
|
89
|
+
* Correlation data providers run against each event before its handlers are
|
|
90
|
+
* invoked, so commands/events dispatched from an event handler inherit the
|
|
91
|
+
* triggering event's correlationId/causationId.
|
|
92
|
+
*/
|
|
93
|
+
correlationDataProviders?: ReadonlyArray<CorrelationDataProvider>
|
|
87
94
|
/** Optional per-event callback fired inside the UoW before handler invocation (e.g. monitoring). */
|
|
88
95
|
onEventDelivery?: () => void
|
|
89
96
|
unitOfWorkRunner?: UoWRunner
|
|
@@ -127,6 +134,7 @@ export function createStreamingEventProcessor(
|
|
|
127
134
|
commandBus,
|
|
128
135
|
queryBus,
|
|
129
136
|
eventScheduler,
|
|
137
|
+
correlationDataProviders,
|
|
130
138
|
onEventDelivery,
|
|
131
139
|
unitOfWorkRunner = runInNewUoW,
|
|
132
140
|
tokenStore,
|
|
@@ -330,6 +338,11 @@ export function createStreamingEventProcessor(
|
|
|
330
338
|
if (commandBus !== undefined) setResource(COMMAND_BUS_KEY, commandBus)
|
|
331
339
|
if (queryBus !== undefined) setResource(QUERY_BUS_KEY, queryBus)
|
|
332
340
|
if (eventScheduler !== undefined) setResource(EVENT_SCHEDULER_KEY, eventScheduler)
|
|
341
|
+
// Seed correlation data from the triggering event so an automation's
|
|
342
|
+
// outgoing commands/events inherit its lineage.
|
|
343
|
+
if (correlationDataProviders && correlationDataProviders.length > 0) {
|
|
344
|
+
applyCorrelationData(event, correlationDataProviders)
|
|
345
|
+
}
|
|
333
346
|
// Optional per-event callback (e.g. monitoring hooks registered inside the UoW).
|
|
334
347
|
if (onEventDelivery) onEventDelivery()
|
|
335
348
|
|
|
@@ -10,6 +10,7 @@ import type { SubscribableEventSource } from "./event-bus.js"
|
|
|
10
10
|
import type { CommandBus } from "./command-bus.js"
|
|
11
11
|
import type { QueryBus } from "./query-bus.js"
|
|
12
12
|
import type { HandlerEnhancerDefinition } from "./handler-enhancer.js"
|
|
13
|
+
import { applyCorrelationData, type CorrelationDataProvider } from "./correlation-data.js"
|
|
13
14
|
import { setResource } from "./processing-state.js"
|
|
14
15
|
import { STATE_MANAGER_KEY, EVENT_SCHEDULER_KEY } from "@kronos-ts/eventsourcing"
|
|
15
16
|
import type { EventScheduler } from "./event-scheduler.js"
|
|
@@ -56,6 +57,13 @@ export interface SubscribingEventProcessorOptions {
|
|
|
56
57
|
queryBus?: QueryBus
|
|
57
58
|
/** Event scheduler injected into ALS at handler-invocation entry (read by schedule()). */
|
|
58
59
|
eventScheduler?: EventScheduler
|
|
60
|
+
/**
|
|
61
|
+
* Correlation data providers run against each event before its handlers are
|
|
62
|
+
* invoked. Their output is seeded into the UoW so commands/events dispatched
|
|
63
|
+
* from an event handler inherit the triggering event's
|
|
64
|
+
* correlationId/causationId. Empty/undefined → no seeding.
|
|
65
|
+
*/
|
|
66
|
+
correlationDataProviders?: ReadonlyArray<CorrelationDataProvider>
|
|
59
67
|
/** Optional per-event callback fired inside the UoW before handler invocation (e.g. monitoring). */
|
|
60
68
|
onEventDelivery?: () => void
|
|
61
69
|
unitOfWorkRunner?: UoWRunner
|
|
@@ -87,6 +95,7 @@ export function createSubscribingEventProcessor(
|
|
|
87
95
|
commandBus,
|
|
88
96
|
queryBus,
|
|
89
97
|
eventScheduler,
|
|
98
|
+
correlationDataProviders,
|
|
90
99
|
onEventDelivery,
|
|
91
100
|
unitOfWorkRunner = runInNewUoW,
|
|
92
101
|
errorHandler = loggingErrorHandler(name),
|
|
@@ -141,6 +150,11 @@ export function createSubscribingEventProcessor(
|
|
|
141
150
|
if (commandBus !== undefined) setResource(COMMAND_BUS_KEY, commandBus)
|
|
142
151
|
if (queryBus !== undefined) setResource(QUERY_BUS_KEY, queryBus)
|
|
143
152
|
if (eventScheduler !== undefined) setResource(EVENT_SCHEDULER_KEY, eventScheduler)
|
|
153
|
+
// Seed correlation data from the triggering event so an automation's
|
|
154
|
+
// outgoing commands/events inherit its lineage.
|
|
155
|
+
if (correlationDataProviders && correlationDataProviders.length > 0) {
|
|
156
|
+
applyCorrelationData(event, correlationDataProviders)
|
|
157
|
+
}
|
|
144
158
|
// Optional per-event callback (e.g. monitoring hooks registered inside the UoW).
|
|
145
159
|
if (onEventDelivery) onEventDelivery()
|
|
146
160
|
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import type { CommandBus } from "./command-bus.js"
|
|
2
2
|
import type { CommandMessage } from "./message.js"
|
|
3
|
-
import type { SpanFactory } from "./span-factory.js"
|
|
3
|
+
import type { SpanFactory, Span } from "./span-factory.js"
|
|
4
|
+
|
|
5
|
+
/** Run `fn` with `span` active, falling back to a plain call when the span lacks runActive. */
|
|
6
|
+
function runActive<R>(span: Span, fn: () => R): R {
|
|
7
|
+
return span.runActive ? span.runActive(fn) : fn()
|
|
8
|
+
}
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
|
-
* A {@link CommandBus} decorator that
|
|
7
|
-
*
|
|
11
|
+
* A {@link CommandBus} decorator that traces command dispatch (the producer
|
|
12
|
+
* side).
|
|
8
13
|
*
|
|
9
14
|
* Dispatch creates a "dispatch" span and propagates trace context into the
|
|
10
|
-
* message metadata
|
|
11
|
-
*
|
|
15
|
+
* message metadata, so the handler links back to it across the bus boundary.
|
|
16
|
+
* The handler ("handle") span is created by {@link tracingHandlerEnhancerDefinition},
|
|
17
|
+
* the single authority for handler-side spans across command/query/event
|
|
18
|
+
* handlers — so this decorator does not wrap subscribe, avoiding a duplicate
|
|
19
|
+
* command handle span.
|
|
12
20
|
*
|
|
13
21
|
* @param delegate The underlying command bus to decorate.
|
|
14
22
|
* @param spanFactory The span factory for creating tracing spans.
|
|
15
|
-
* @returns A decorated command bus with tracing
|
|
23
|
+
* @returns A decorated command bus with dispatch tracing.
|
|
16
24
|
*/
|
|
17
25
|
export function createTracingCommandBus(
|
|
18
26
|
delegate: CommandBus,
|
|
@@ -22,8 +30,12 @@ export function createTracingCommandBus(
|
|
|
22
30
|
async dispatch(message: CommandMessage): Promise<unknown> {
|
|
23
31
|
const span = spanFactory.createDispatchSpan(`dispatch(${String(message.name)})`, message).start()
|
|
24
32
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
// Propagate inside the active dispatch span so the outgoing message
|
|
34
|
+
// carries this span's trace context and the handler links to it.
|
|
35
|
+
const result = await runActive(span, () => {
|
|
36
|
+
const propagated = spanFactory.propagateContext(message)
|
|
37
|
+
return delegate.dispatch(propagated)
|
|
38
|
+
})
|
|
27
39
|
span.end()
|
|
28
40
|
return result
|
|
29
41
|
} catch (err) {
|
|
@@ -36,17 +48,9 @@ export function createTracingCommandBus(
|
|
|
36
48
|
commandName: string,
|
|
37
49
|
handler: (message: CommandMessage) => Promise<unknown>,
|
|
38
50
|
): void {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const result = await handler(msg)
|
|
43
|
-
span.end()
|
|
44
|
-
return result
|
|
45
|
-
} catch (err) {
|
|
46
|
-
span.recordException(err instanceof Error ? err : new Error(String(err)))
|
|
47
|
-
throw err
|
|
48
|
-
}
|
|
49
|
-
})
|
|
51
|
+
// Handler-side spans are owned by tracingHandlerEnhancerDefinition; pass
|
|
52
|
+
// the handler through untouched so commands get exactly one handle span.
|
|
53
|
+
delegate.subscribe(commandName, handler)
|
|
50
54
|
},
|
|
51
55
|
}
|
|
52
56
|
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import type { HandlerEnhancerDefinition, HandlerMetadata } from "./handler-enhancer.js"
|
|
2
|
-
import type { SpanFactory } from "./span-factory.js"
|
|
2
|
+
import type { SpanFactory, Span } from "./span-factory.js"
|
|
3
|
+
import type { Message } from "./message.js"
|
|
4
|
+
import { contributeCorrelationData } from "./correlation-data.js"
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Handler enhancer that wraps message handler invocations with tracing spans.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* The span is created from the message being handled (extracting any trace
|
|
10
|
+
* context from its metadata) so the handler re-parents onto the dispatcher's
|
|
11
|
+
* trace across the message boundary. Event handlers start a new trace linked to
|
|
12
|
+
* the triggering event; command/query handlers continue the current trace.
|
|
9
13
|
*
|
|
14
|
+
* The handler runs inside the span's active context, and the active trace
|
|
15
|
+
* context is captured onto the UnitOfWork (via contributeCorrelationData) so
|
|
16
|
+
* appended and dispatched messages carry it — including events published at
|
|
17
|
+
* commit time, after the span has ended.
|
|
10
18
|
*/
|
|
11
19
|
export function tracingHandlerEnhancerDefinition(
|
|
12
20
|
spanFactory: SpanFactory,
|
|
@@ -19,9 +27,27 @@ export function tracingHandlerEnhancerDefinition(
|
|
|
19
27
|
const spanName = `${metadata.handlerGroup}.${metadata.messageName}`
|
|
20
28
|
|
|
21
29
|
return (async (...args: any[]) => {
|
|
22
|
-
const
|
|
30
|
+
const message = args[0]
|
|
31
|
+
const span = createSpan(spanFactory, spanName, message, metadata).start()
|
|
32
|
+
const runActive: <R>(fn: () => R) => R = span.runActive
|
|
33
|
+
? span.runActive.bind(span)
|
|
34
|
+
: (fn) => fn()
|
|
35
|
+
|
|
23
36
|
try {
|
|
24
|
-
const result = await
|
|
37
|
+
const result = await runActive(() => {
|
|
38
|
+
// Store the active trace context on the UnitOfWork so outgoing and
|
|
39
|
+
// appended messages carry it. Best-effort: tracing must never break
|
|
40
|
+
// handling, and there may be no active UnitOfWork.
|
|
41
|
+
try {
|
|
42
|
+
const traceContext = spanFactory.currentTraceContext?.()
|
|
43
|
+
if (traceContext && Object.keys(traceContext).length > 0) {
|
|
44
|
+
contributeCorrelationData(traceContext)
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// no active UnitOfWork or no tracing context — skip
|
|
48
|
+
}
|
|
49
|
+
return handler(...args)
|
|
50
|
+
})
|
|
25
51
|
span.end()
|
|
26
52
|
return result
|
|
27
53
|
} catch (error) {
|
|
@@ -32,3 +58,28 @@ export function tracingHandlerEnhancerDefinition(
|
|
|
32
58
|
},
|
|
33
59
|
}
|
|
34
60
|
}
|
|
61
|
+
|
|
62
|
+
function isMessage(value: unknown): value is Message {
|
|
63
|
+
return (
|
|
64
|
+
typeof value === "object" &&
|
|
65
|
+
value !== null &&
|
|
66
|
+
"metadata" in value &&
|
|
67
|
+
"identifier" in value
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createSpan(
|
|
72
|
+
spanFactory: SpanFactory,
|
|
73
|
+
spanName: string,
|
|
74
|
+
message: unknown,
|
|
75
|
+
metadata: HandlerMetadata,
|
|
76
|
+
): Span {
|
|
77
|
+
if (!isMessage(message)) {
|
|
78
|
+
// No message to re-parent from (defensive — wired handlers always receive one).
|
|
79
|
+
return spanFactory.createInternalSpan(spanName)
|
|
80
|
+
}
|
|
81
|
+
if (metadata.messageType === "event" && spanFactory.createLinkedHandlerSpan) {
|
|
82
|
+
return spanFactory.createLinkedHandlerSpan(spanName, message)
|
|
83
|
+
}
|
|
84
|
+
return spanFactory.createHandlerSpan(spanName, message)
|
|
85
|
+
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
advanceToken,
|
|
23
23
|
} from "./tracking-token.js"
|
|
24
24
|
import { REPLAY_STATE_KEY } from "./replay-token.js"
|
|
25
|
+
import { applyCorrelationData, type CorrelationDataProvider } from "./correlation-data.js"
|
|
25
26
|
import { setResource, onPrepareCommit } from "./processing-state.js"
|
|
26
27
|
import type { HandlerEnhancerDefinition } from "./handler-enhancer.js"
|
|
27
28
|
import type { CommandBus } from "./command-bus.js"
|
|
@@ -76,6 +77,12 @@ export interface TrackingEventProcessorOptions {
|
|
|
76
77
|
queryBus?: QueryBus
|
|
77
78
|
/** Event scheduler injected into ALS at handler-invocation entry (read by schedule()). */
|
|
78
79
|
eventScheduler?: EventScheduler
|
|
80
|
+
/**
|
|
81
|
+
* Correlation data providers run against each event before its handlers are
|
|
82
|
+
* invoked, so commands/events dispatched from an event handler inherit the
|
|
83
|
+
* triggering event's correlationId/causationId.
|
|
84
|
+
*/
|
|
85
|
+
correlationDataProviders?: ReadonlyArray<CorrelationDataProvider>
|
|
79
86
|
/** Optional per-event callback fired inside the UoW before handler invocation (e.g. monitoring). */
|
|
80
87
|
onEventDelivery?: () => void
|
|
81
88
|
unitOfWorkRunner?: UoWRunner
|
|
@@ -151,6 +158,7 @@ export function createTrackingEventProcessor(
|
|
|
151
158
|
commandBus,
|
|
152
159
|
queryBus,
|
|
153
160
|
eventScheduler,
|
|
161
|
+
correlationDataProviders,
|
|
154
162
|
onEventDelivery,
|
|
155
163
|
unitOfWorkRunner = runInNewUoW,
|
|
156
164
|
tokenStore,
|
|
@@ -341,6 +349,11 @@ export function createTrackingEventProcessor(
|
|
|
341
349
|
if (commandBus !== undefined) setResource(COMMAND_BUS_KEY, commandBus)
|
|
342
350
|
if (queryBus !== undefined) setResource(QUERY_BUS_KEY, queryBus)
|
|
343
351
|
if (eventScheduler !== undefined) setResource(EVENT_SCHEDULER_KEY, eventScheduler)
|
|
352
|
+
// Seed correlation data from the triggering event so an automation's
|
|
353
|
+
// outgoing commands/events inherit its lineage.
|
|
354
|
+
if (correlationDataProviders && correlationDataProviders.length > 0) {
|
|
355
|
+
applyCorrelationData(event, correlationDataProviders)
|
|
356
|
+
}
|
|
344
357
|
// Optional per-event callback (e.g. monitoring hooks registered inside the UoW).
|
|
345
358
|
if (onEventDelivery) onEventDelivery()
|
|
346
359
|
|
|
@@ -375,6 +388,9 @@ export function createTrackingEventProcessor(
|
|
|
375
388
|
if (commandBus !== undefined) setResource(COMMAND_BUS_KEY, commandBus)
|
|
376
389
|
if (queryBus !== undefined) setResource(QUERY_BUS_KEY, queryBus)
|
|
377
390
|
if (eventScheduler !== undefined) setResource(EVENT_SCHEDULER_KEY, eventScheduler)
|
|
391
|
+
if (correlationDataProviders && correlationDataProviders.length > 0) {
|
|
392
|
+
applyCorrelationData(event, correlationDataProviders)
|
|
393
|
+
}
|
|
378
394
|
|
|
379
395
|
const position =
|
|
380
396
|
typeof letter.diagnostics.position === "number" ? BigInt(letter.diagnostics.position) : 0n
|