@kronos-ts/messaging 0.1.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-bus.d.ts +30 -0
- package/dist/command-bus.d.ts.map +1 -0
- package/dist/command-bus.js +2 -0
- package/dist/command-bus.js.map +1 -0
- package/dist/command-handler.d.ts +58 -0
- package/dist/command-handler.d.ts.map +1 -0
- package/dist/command-handler.js +12 -0
- package/dist/command-handler.js.map +1 -0
- package/dist/command-handling-module.d.ts +53 -0
- package/dist/command-handling-module.d.ts.map +1 -0
- package/dist/command-handling-module.js +130 -0
- package/dist/command-handling-module.js.map +1 -0
- package/dist/correlation-data.d.ts +79 -0
- package/dist/correlation-data.d.ts.map +1 -0
- package/dist/correlation-data.js +133 -0
- package/dist/correlation-data.js.map +1 -0
- package/dist/dead-letter-queue.d.ts +134 -0
- package/dist/dead-letter-queue.d.ts.map +1 -0
- package/dist/dead-letter-queue.js +176 -0
- package/dist/dead-letter-queue.js.map +1 -0
- package/dist/dead-lettering-handler.d.ts +42 -0
- package/dist/dead-lettering-handler.d.ts.map +1 -0
- package/dist/dead-lettering-handler.js +67 -0
- package/dist/dead-lettering-handler.js.map +1 -0
- package/dist/descriptor.d.ts +135 -0
- package/dist/descriptor.d.ts.map +1 -0
- package/dist/descriptor.js +36 -0
- package/dist/descriptor.js.map +1 -0
- package/dist/emit-update.d.ts +22 -0
- package/dist/emit-update.d.ts.map +1 -0
- package/dist/emit-update.js +23 -0
- package/dist/emit-update.js.map +1 -0
- package/dist/event-bus.d.ts +29 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +22 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/event-criteria.d.ts +87 -0
- package/dist/event-criteria.d.ts.map +1 -0
- package/dist/event-criteria.js +90 -0
- package/dist/event-criteria.js.map +1 -0
- package/dist/event-gateway.d.ts +19 -0
- package/dist/event-gateway.d.ts.map +1 -0
- package/dist/event-gateway.js +22 -0
- package/dist/event-gateway.js.map +1 -0
- package/dist/event-handler.d.ts +30 -0
- package/dist/event-handler.d.ts.map +1 -0
- package/dist/event-handler.js +18 -0
- package/dist/event-handler.js.map +1 -0
- package/dist/event-processor-builder.d.ts +148 -0
- package/dist/event-processor-builder.d.ts.map +1 -0
- package/dist/event-processor-builder.js +175 -0
- package/dist/event-processor-builder.js.map +1 -0
- package/dist/event-processor.d.ts +10 -0
- package/dist/event-processor.d.ts.map +1 -0
- package/dist/event-processor.js +2 -0
- package/dist/event-processor.js.map +1 -0
- package/dist/event-sink.d.ts +23 -0
- package/dist/event-sink.d.ts.map +1 -0
- package/dist/event-sink.js +2 -0
- package/dist/event-sink.js.map +1 -0
- package/dist/event-source.d.ts +98 -0
- package/dist/event-source.d.ts.map +1 -0
- package/dist/event-source.js +191 -0
- package/dist/event-source.js.map +1 -0
- package/dist/gateway.d.ts +68 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +62 -0
- package/dist/gateway.js.map +1 -0
- package/dist/handler-enhancer.d.ts +53 -0
- package/dist/handler-enhancer.d.ts.map +1 -0
- package/dist/handler-enhancer.js +17 -0
- package/dist/handler-enhancer.js.map +1 -0
- package/dist/handler.d.ts +51 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +26 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +103 -0
- package/dist/index.js.map +1 -0
- package/dist/intercepting-command-bus.d.ts +17 -0
- package/dist/intercepting-command-bus.d.ts.map +1 -0
- package/dist/intercepting-command-bus.js +54 -0
- package/dist/intercepting-command-bus.js.map +1 -0
- package/dist/intercepting-event-bus.d.ts +8 -0
- package/dist/intercepting-event-bus.d.ts.map +1 -0
- package/dist/intercepting-event-bus.js +22 -0
- package/dist/intercepting-event-bus.js.map +1 -0
- package/dist/intercepting-query-bus.d.ts +17 -0
- package/dist/intercepting-query-bus.d.ts.map +1 -0
- package/dist/intercepting-query-bus.js +68 -0
- package/dist/intercepting-query-bus.js.map +1 -0
- package/dist/interceptor.d.ts +46 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +2 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/message-monitor-registry.d.ts +28 -0
- package/dist/message-monitor-registry.d.ts.map +1 -0
- package/dist/message-monitor-registry.js +37 -0
- package/dist/message-monitor-registry.js.map +1 -0
- package/dist/message-monitor.d.ts +36 -0
- package/dist/message-monitor.d.ts.map +1 -0
- package/dist/message-monitor.js +39 -0
- package/dist/message-monitor.js.map +1 -0
- package/dist/message.d.ts +42 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/message.js +2 -0
- package/dist/message.js.map +1 -0
- package/dist/processing-state.d.ts +115 -0
- package/dist/processing-state.d.ts.map +1 -0
- package/dist/processing-state.js +205 -0
- package/dist/processing-state.js.map +1 -0
- package/dist/processor-configuration.d.ts +51 -0
- package/dist/processor-configuration.d.ts.map +1 -0
- package/dist/processor-configuration.js +2 -0
- package/dist/processor-configuration.js.map +1 -0
- package/dist/query-bus.d.ts +51 -0
- package/dist/query-bus.d.ts.map +1 -0
- package/dist/query-bus.js +2 -0
- package/dist/query-bus.js.map +1 -0
- package/dist/query-handler.d.ts +35 -0
- package/dist/query-handler.d.ts.map +1 -0
- package/dist/query-handler.js +19 -0
- package/dist/query-handler.js.map +1 -0
- package/dist/query-handling-module.d.ts +24 -0
- package/dist/query-handling-module.d.ts.map +1 -0
- package/dist/query-handling-module.js +32 -0
- package/dist/query-handling-module.js.map +1 -0
- package/dist/replay-token.d.ts +31 -0
- package/dist/replay-token.d.ts.map +1 -0
- package/dist/replay-token.js +37 -0
- package/dist/replay-token.js.map +1 -0
- package/dist/retrying-command-bus.d.ts +32 -0
- package/dist/retrying-command-bus.d.ts.map +1 -0
- package/dist/retrying-command-bus.js +58 -0
- package/dist/retrying-command-bus.js.map +1 -0
- package/dist/routing-strategy.d.ts +30 -0
- package/dist/routing-strategy.d.ts.map +1 -0
- package/dist/routing-strategy.js +37 -0
- package/dist/routing-strategy.js.map +1 -0
- package/dist/segment.d.ts +72 -0
- package/dist/segment.d.ts.map +1 -0
- package/dist/segment.js +103 -0
- package/dist/segment.js.map +1 -0
- package/dist/send.d.ts +28 -0
- package/dist/send.d.ts.map +1 -0
- package/dist/send.js +36 -0
- package/dist/send.js.map +1 -0
- package/dist/serializer.d.ts +40 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/serializer.js +90 -0
- package/dist/serializer.js.map +1 -0
- package/dist/simple-command-bus.d.ts +23 -0
- package/dist/simple-command-bus.d.ts.map +1 -0
- package/dist/simple-command-bus.js +49 -0
- package/dist/simple-command-bus.js.map +1 -0
- package/dist/simple-query-bus.d.ts +16 -0
- package/dist/simple-query-bus.d.ts.map +1 -0
- package/dist/simple-query-bus.js +122 -0
- package/dist/simple-query-bus.js.map +1 -0
- package/dist/span-factory.d.ts +58 -0
- package/dist/span-factory.d.ts.map +1 -0
- package/dist/span-factory.js +19 -0
- package/dist/span-factory.js.map +1 -0
- package/dist/streaming-event-processor.d.ts +65 -0
- package/dist/streaming-event-processor.d.ts.map +1 -0
- package/dist/streaming-event-processor.js +239 -0
- package/dist/streaming-event-processor.js.map +1 -0
- package/dist/subscribing-event-processor.d.ts +57 -0
- package/dist/subscribing-event-processor.d.ts.map +1 -0
- package/dist/subscribing-event-processor.js +100 -0
- package/dist/subscribing-event-processor.js.map +1 -0
- package/dist/subscription-query.d.ts +63 -0
- package/dist/subscription-query.d.ts.map +1 -0
- package/dist/subscription-query.js +119 -0
- package/dist/subscription-query.js.map +1 -0
- package/dist/token-store.d.ts +83 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +112 -0
- package/dist/token-store.js.map +1 -0
- package/dist/tracing-command-bus.d.ts +16 -0
- package/dist/tracing-command-bus.d.ts.map +1 -0
- package/dist/tracing-command-bus.js +44 -0
- package/dist/tracing-command-bus.js.map +1 -0
- package/dist/tracing-handler-enhancer.d.ts +11 -0
- package/dist/tracing-handler-enhancer.d.ts.map +1 -0
- package/dist/tracing-handler-enhancer.js +27 -0
- package/dist/tracing-handler-enhancer.js.map +1 -0
- package/dist/tracking-event-processor.d.ts +72 -0
- package/dist/tracking-event-processor.d.ts.map +1 -0
- package/dist/tracking-event-processor.js +223 -0
- package/dist/tracking-event-processor.js.map +1 -0
- package/dist/tracking-token.d.ts +120 -0
- package/dist/tracking-token.d.ts.map +1 -0
- package/dist/tracking-token.js +132 -0
- package/dist/tracking-token.js.map +1 -0
- package/dist/transaction.d.ts +60 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +74 -0
- package/dist/transaction.js.map +1 -0
- package/dist/unit-of-work.d.ts +41 -0
- package/dist/unit-of-work.d.ts.map +1 -0
- package/dist/unit-of-work.js +96 -0
- package/dist/unit-of-work.js.map +1 -0
- package/dist/upcaster.d.ts +91 -0
- package/dist/upcaster.d.ts.map +1 -0
- package/dist/upcaster.js +114 -0
- package/dist/upcaster.js.map +1 -0
- package/dist/with-namespace.d.ts +59 -0
- package/dist/with-namespace.d.ts.map +1 -0
- package/dist/with-namespace.js +42 -0
- package/dist/with-namespace.js.map +1 -0
- package/package.json +65 -0
- package/src/command-bus.ts +34 -0
- package/src/command-handler.ts +116 -0
- package/src/command-handling-module.ts +183 -0
- package/src/correlation-data.ts +169 -0
- package/src/dead-letter-queue.ts +330 -0
- package/src/dead-lettering-handler.ts +109 -0
- package/src/descriptor.ts +176 -0
- package/src/emit-update.ts +35 -0
- package/src/event-bus.ts +45 -0
- package/src/event-criteria.ts +141 -0
- package/src/event-gateway.ts +42 -0
- package/src/event-handler.ts +44 -0
- package/src/event-processor-builder.ts +246 -0
- package/src/event-processor.ts +9 -0
- package/src/event-sink.ts +23 -0
- package/src/event-source.ts +301 -0
- package/src/gateway.ts +144 -0
- package/src/handler-enhancer.ts +70 -0
- package/src/handler.ts +133 -0
- package/src/index.ts +356 -0
- package/src/intercepting-command-bus.ts +73 -0
- package/src/intercepting-event-bus.ts +29 -0
- package/src/intercepting-query-bus.ts +104 -0
- package/src/interceptor.ts +48 -0
- package/src/message-monitor-registry.ts +64 -0
- package/src/message-monitor.ts +68 -0
- package/src/message.ts +41 -0
- package/src/processing-state.ts +258 -0
- package/src/processor-configuration.ts +59 -0
- package/src/query-bus.ts +69 -0
- package/src/query-handler.ts +49 -0
- package/src/query-handling-module.ts +44 -0
- package/src/replay-token.ts +53 -0
- package/src/retrying-command-bus.ts +80 -0
- package/src/routing-strategy.ts +59 -0
- package/src/segment.ts +136 -0
- package/src/send.ts +44 -0
- package/src/serializer.ts +122 -0
- package/src/simple-command-bus.ts +59 -0
- package/src/simple-query-bus.ts +158 -0
- package/src/span-factory.ts +81 -0
- package/src/streaming-event-processor.ts +351 -0
- package/src/subscribing-event-processor.ts +169 -0
- package/src/subscription-query.ts +173 -0
- package/src/token-store.ts +211 -0
- package/src/tracing-command-bus.ts +52 -0
- package/src/tracing-handler-enhancer.ts +34 -0
- package/src/tracking-event-processor.ts +336 -0
- package/src/tracking-token.ts +231 -0
- package/src/transaction.ts +98 -0
- package/src/unit-of-work.ts +138 -0
- package/src/upcaster.ts +174 -0
- package/src/with-namespace.ts +75 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Message } from "./message.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A span representing a unit of tracing work.
|
|
5
|
+
* Start the span, do work, then end it.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
export interface Span {
|
|
9
|
+
/** Start the span. Returns the span for chaining. */
|
|
10
|
+
start(): Span
|
|
11
|
+
/** End the span normally. */
|
|
12
|
+
end(): void
|
|
13
|
+
/** Record an error on the span and end it. */
|
|
14
|
+
recordException(error: Error): void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Provides custom attributes to add to spans.
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
export interface SpanAttributesProvider {
|
|
22
|
+
provideAttributes(message: Message): Record<string, string>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Factory for creating tracing spans around message processing.
|
|
27
|
+
*
|
|
28
|
+
* The SpanFactory is the core tracing abstraction. Implementations
|
|
29
|
+
* (e.g., OpenTelemetry) provide the actual span creation and context
|
|
30
|
+
* propagation. The framework calls these methods during dispatch and
|
|
31
|
+
* handling.
|
|
32
|
+
*
|
|
33
|
+
*/
|
|
34
|
+
export interface SpanFactory {
|
|
35
|
+
/** Create a root trace span (no parent). */
|
|
36
|
+
createRootTrace(operationName: string): Span
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a span for handling a message (consumer side).
|
|
40
|
+
* Extracts trace context from the parent message's metadata.
|
|
41
|
+
*/
|
|
42
|
+
createHandlerSpan(operationName: string, parentMessage: Message): Span
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a span for dispatching a message (producer side).
|
|
46
|
+
* Links to the parent message's trace context.
|
|
47
|
+
*/
|
|
48
|
+
createDispatchSpan(operationName: string, parentMessage: Message): Span
|
|
49
|
+
|
|
50
|
+
/** Create a span for internal framework operations. */
|
|
51
|
+
createInternalSpan(operationName: string): Span
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Inject trace context into a message's metadata so it propagates
|
|
55
|
+
* across message boundaries.
|
|
56
|
+
*/
|
|
57
|
+
propagateContext<M extends Message>(message: M): M
|
|
58
|
+
|
|
59
|
+
/** Register a custom span attribute provider. */
|
|
60
|
+
registerSpanAttributeProvider(provider: SpanAttributesProvider): void
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A no-op span factory. Default when no tracing is configured.
|
|
65
|
+
*/
|
|
66
|
+
export function noOpSpanFactory(): SpanFactory {
|
|
67
|
+
const noOpSpan: Span = {
|
|
68
|
+
start() { return this },
|
|
69
|
+
end() {},
|
|
70
|
+
recordException() {},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
createRootTrace() { return noOpSpan },
|
|
75
|
+
createHandlerSpan() { return noOpSpan },
|
|
76
|
+
createDispatchSpan() { return noOpSpan },
|
|
77
|
+
createInternalSpan() { return noOpSpan },
|
|
78
|
+
propagateContext<M extends Message>(message: M) { return message },
|
|
79
|
+
registerSpanAttributeProvider() {},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { emptyMetadata, qualifiedNameToString } from "@kronos-ts/common"
|
|
2
|
+
import type { EventHandlerRegistration } from "./handler.js"
|
|
3
|
+
import type { EventHandlerDefinition } from "./event-handler.js"
|
|
4
|
+
import type { StreamableEventSource, MessageStream, SequencedEvent } from "./event-source.js"
|
|
5
|
+
import type { UoWRunner } from "./unit-of-work.js"
|
|
6
|
+
import { runInNewUoW } from "./unit-of-work.js"
|
|
7
|
+
import type { TokenStore } from "./token-store.js"
|
|
8
|
+
import type { EventProcessingErrorHandler } from "./tracking-event-processor.js"
|
|
9
|
+
import { loggingErrorHandler } from "./tracking-event-processor.js"
|
|
10
|
+
import type { HandlerEnhancerDefinition } from "./handler-enhancer.js"
|
|
11
|
+
import type { TrackingToken } from "./tracking-token.js"
|
|
12
|
+
import {
|
|
13
|
+
globalSequenceToken,
|
|
14
|
+
replayToken,
|
|
15
|
+
isReplayToken,
|
|
16
|
+
isReplaying,
|
|
17
|
+
advanceToken,
|
|
18
|
+
} from "./tracking-token.js"
|
|
19
|
+
import { REPLAY_STATE_KEY } from "./replay-token.js"
|
|
20
|
+
import { setResource, onPrepareCommit } from "./processing-state.js"
|
|
21
|
+
import type { CommandBus } from "./command-bus.js"
|
|
22
|
+
import type { QueryBus } from "./query-bus.js"
|
|
23
|
+
import { STATE_MANAGER_KEY } from "@kronos-ts/eventsourcing"
|
|
24
|
+
import { COMMAND_BUS_KEY } from "./send.js"
|
|
25
|
+
import { QUERY_BUS_KEY } from "./emit-update.js"
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A streaming event processor that uses push-based event delivery
|
|
29
|
+
* via {@link MessageStream}.
|
|
30
|
+
*
|
|
31
|
+
* Architecture:
|
|
32
|
+
* - Opens a MessageStream from the event source via {@link StreamableEventSource.open}
|
|
33
|
+
* - When events become available (via setCallback), pulls them
|
|
34
|
+
* - Processes batches within a UnitOfWork
|
|
35
|
+
* - Stores token at PREPARE_COMMIT (same transaction as handler work)
|
|
36
|
+
*
|
|
37
|
+
*/
|
|
38
|
+
export interface EventProcessorStatus {
|
|
39
|
+
readonly segmentId: number
|
|
40
|
+
readonly position: bigint
|
|
41
|
+
readonly replaying: boolean
|
|
42
|
+
readonly caughtUp: boolean
|
|
43
|
+
readonly error?: Error
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface StreamingEventProcessor {
|
|
47
|
+
readonly name: string
|
|
48
|
+
readonly running: boolean
|
|
49
|
+
readonly position: bigint
|
|
50
|
+
readonly replaying: boolean
|
|
51
|
+
/** Status per segment. */
|
|
52
|
+
processingStatus(): Map<number, EventProcessorStatus>
|
|
53
|
+
/** Whether this processor supports reset (always true if not running). */
|
|
54
|
+
supportsReset(): boolean
|
|
55
|
+
start(): Promise<void>
|
|
56
|
+
stop(): void
|
|
57
|
+
resetTokens(startPosition?: bigint, resetContext?: unknown): Promise<void>
|
|
58
|
+
splitSegment(segmentId: number): Promise<boolean>
|
|
59
|
+
mergeSegment(segmentId: number): Promise<boolean>
|
|
60
|
+
releaseSegment(segmentId: number): Promise<void>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface StreamingEventProcessorOptions {
|
|
64
|
+
name: string
|
|
65
|
+
eventSource: StreamableEventSource
|
|
66
|
+
eventHandlers: ReadonlyArray<EventHandlerDefinition>
|
|
67
|
+
/** State manager injected into ALS at handler-invocation entry (D-44). */
|
|
68
|
+
stateManager?: unknown
|
|
69
|
+
/** Command bus injected into ALS at handler-invocation entry (D-44). */
|
|
70
|
+
commandBus?: CommandBus
|
|
71
|
+
/** Query bus injected into ALS at handler-invocation entry (D-44). */
|
|
72
|
+
queryBus?: QueryBus
|
|
73
|
+
/** Optional per-event callback fired inside the UoW before handler invocation (e.g. monitoring). */
|
|
74
|
+
onEventDelivery?: () => void
|
|
75
|
+
unitOfWorkRunner?: UoWRunner
|
|
76
|
+
tokenStore?: TokenStore
|
|
77
|
+
batchSize?: number
|
|
78
|
+
errorHandler?: EventProcessingErrorHandler
|
|
79
|
+
/** Optional handler enhancer applied to all event handlers at setup time. */
|
|
80
|
+
handlerEnhancer?: HandlerEnhancerDefinition
|
|
81
|
+
/** Reset callback invoked from resetTokens(). */
|
|
82
|
+
onReset?: () => Promise<void> | void
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createStreamingEventProcessor(
|
|
86
|
+
options: StreamingEventProcessorOptions,
|
|
87
|
+
): StreamingEventProcessor {
|
|
88
|
+
const {
|
|
89
|
+
name,
|
|
90
|
+
eventSource,
|
|
91
|
+
eventHandlers,
|
|
92
|
+
stateManager,
|
|
93
|
+
commandBus,
|
|
94
|
+
queryBus,
|
|
95
|
+
onEventDelivery,
|
|
96
|
+
unitOfWorkRunner = runInNewUoW,
|
|
97
|
+
tokenStore,
|
|
98
|
+
batchSize = 100,
|
|
99
|
+
errorHandler = loggingErrorHandler(name),
|
|
100
|
+
handlerEnhancer,
|
|
101
|
+
onReset,
|
|
102
|
+
} = options
|
|
103
|
+
|
|
104
|
+
const segment = 0
|
|
105
|
+
|
|
106
|
+
const handlerMap = new Map<string, Array<EventHandlerRegistration<any>>>()
|
|
107
|
+
for (const reg of eventHandlers) {
|
|
108
|
+
const eventName = qualifiedNameToString(reg.descriptor.name)
|
|
109
|
+
if (!handlerMap.has(eventName)) {
|
|
110
|
+
handlerMap.set(eventName, [])
|
|
111
|
+
}
|
|
112
|
+
const enhanced = handlerEnhancer
|
|
113
|
+
? {
|
|
114
|
+
...reg,
|
|
115
|
+
handler: handlerEnhancer.wrapHandler(reg.handler, {
|
|
116
|
+
messageType: "event" as const,
|
|
117
|
+
messageName: eventName,
|
|
118
|
+
handlerGroup: name,
|
|
119
|
+
}),
|
|
120
|
+
}
|
|
121
|
+
: reg
|
|
122
|
+
handlerMap.get(eventName)!.push(enhanced as EventHandlerRegistration<any>)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let token: TrackingToken = globalSequenceToken(0n)
|
|
126
|
+
let isRunning = false
|
|
127
|
+
let stream: MessageStream<SequencedEvent> | null = null
|
|
128
|
+
let processTimer: ReturnType<typeof setTimeout> | null = null
|
|
129
|
+
let processing = false
|
|
130
|
+
let caughtUp = false
|
|
131
|
+
let lastError: Error | undefined
|
|
132
|
+
|
|
133
|
+
async function initialize() {
|
|
134
|
+
if (tokenStore) {
|
|
135
|
+
await tokenStore.initializeSegments(name, 1)
|
|
136
|
+
const stored = await tokenStore.get(name, segment)
|
|
137
|
+
if (stored !== undefined) {
|
|
138
|
+
token = stored
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function openStream() {
|
|
144
|
+
stream = eventSource.open({ position: token.position() })
|
|
145
|
+
stream.setCallback(() => {
|
|
146
|
+
if (isRunning && !processing) {
|
|
147
|
+
scheduleImmediate()
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function processAvailable() {
|
|
153
|
+
if (!isRunning || processing) return
|
|
154
|
+
processing = true
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
await processFromStream()
|
|
158
|
+
} catch (err) {
|
|
159
|
+
lastError = err instanceof Error ? err : new Error(String(err))
|
|
160
|
+
console.error(`Event processor "${name}" error:`, err)
|
|
161
|
+
} finally {
|
|
162
|
+
processing = false
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (isRunning && stream) {
|
|
166
|
+
if (stream.hasNextAvailable()) {
|
|
167
|
+
scheduleImmediate()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function processFromStream() {
|
|
173
|
+
if (!stream) return
|
|
174
|
+
|
|
175
|
+
// Check for stream errors — reopen if needed
|
|
176
|
+
const streamError = stream.error()
|
|
177
|
+
if (streamError) {
|
|
178
|
+
console.error(`Event processor "${name}": stream error, reopening:`, streamError)
|
|
179
|
+
stream.close()
|
|
180
|
+
stream = null
|
|
181
|
+
openStream()
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const batch: SequencedEvent[] = []
|
|
186
|
+
let event = stream.next()
|
|
187
|
+
while (event && batch.length < batchSize) {
|
|
188
|
+
batch.push(event)
|
|
189
|
+
if (batch.length < batchSize && stream.hasNextAvailable()) {
|
|
190
|
+
event = stream.next()
|
|
191
|
+
} else {
|
|
192
|
+
break
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (batch.length > 0) {
|
|
197
|
+
caughtUp = false
|
|
198
|
+
await processBatch(batch)
|
|
199
|
+
if (stream.hasNextAvailable()) {
|
|
200
|
+
scheduleImmediate()
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
caughtUp = true
|
|
204
|
+
if (isReplayToken(token)) {
|
|
205
|
+
token = globalSequenceToken(token.position())
|
|
206
|
+
if (tokenStore) {
|
|
207
|
+
await tokenStore.store(name, segment, token)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function processBatch(batch: SequencedEvent[]) {
|
|
214
|
+
let batchEndToken: TrackingToken = token
|
|
215
|
+
|
|
216
|
+
await unitOfWorkRunner(emptyMetadata(), async () => {
|
|
217
|
+
for (const sequencedEvent of batch) {
|
|
218
|
+
setResource(REPLAY_STATE_KEY, { replaying: isReplaying(batchEndToken) })
|
|
219
|
+
|
|
220
|
+
await deliverEvent(sequencedEvent)
|
|
221
|
+
|
|
222
|
+
batchEndToken = advanceToken(batchEndToken, sequencedEvent.sequence + 1n)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (tokenStore) {
|
|
226
|
+
onPrepareCommit(async () => {
|
|
227
|
+
await tokenStore.store(name, segment, batchEndToken)
|
|
228
|
+
// Extend claim to prevent expiry during long batches
|
|
229
|
+
await tokenStore.extendClaim(name, segment, name)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
token = batchEndToken
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function deliverEvent(sequencedEvent: SequencedEvent) {
|
|
238
|
+
const event = sequencedEvent.event
|
|
239
|
+
const eventName = qualifiedNameToString(event.name)
|
|
240
|
+
const handlers = handlerMap.get(eventName)
|
|
241
|
+
if (!handlers || handlers.length === 0) return
|
|
242
|
+
|
|
243
|
+
// D-44 wiring: write framework components into ALS at per-event invocation entry.
|
|
244
|
+
if (stateManager !== undefined) setResource(STATE_MANAGER_KEY, stateManager as any)
|
|
245
|
+
if (commandBus !== undefined) setResource(COMMAND_BUS_KEY, commandBus)
|
|
246
|
+
if (queryBus !== undefined) setResource(QUERY_BUS_KEY, queryBus)
|
|
247
|
+
// Optional per-event callback (e.g. monitoring hooks registered inside the UoW).
|
|
248
|
+
if (onEventDelivery) onEventDelivery()
|
|
249
|
+
|
|
250
|
+
for (const reg of handlers) {
|
|
251
|
+
try {
|
|
252
|
+
await reg.handler(event.payload, event.metadata)
|
|
253
|
+
} catch (err) {
|
|
254
|
+
await errorHandler.handleError(err, eventName, sequencedEvent.sequence)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function scheduleImmediate() {
|
|
260
|
+
if (processTimer !== null) {
|
|
261
|
+
clearTimeout(processTimer)
|
|
262
|
+
}
|
|
263
|
+
processTimer = setTimeout(processAvailable, 0)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
get name() { return name },
|
|
268
|
+
get running() { return isRunning },
|
|
269
|
+
get position() { return token.position() },
|
|
270
|
+
get replaying() { return isReplaying(token) },
|
|
271
|
+
|
|
272
|
+
processingStatus() {
|
|
273
|
+
const status = new Map<number, EventProcessorStatus>()
|
|
274
|
+
status.set(segment, {
|
|
275
|
+
segmentId: segment,
|
|
276
|
+
position: token.position(),
|
|
277
|
+
replaying: isReplaying(token),
|
|
278
|
+
caughtUp,
|
|
279
|
+
error: lastError,
|
|
280
|
+
})
|
|
281
|
+
return status
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
async start() {
|
|
285
|
+
if (isRunning) return
|
|
286
|
+
await initialize()
|
|
287
|
+
isRunning = true
|
|
288
|
+
openStream()
|
|
289
|
+
scheduleImmediate()
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
stop() {
|
|
293
|
+
isRunning = false
|
|
294
|
+
if (processTimer !== null) {
|
|
295
|
+
clearTimeout(processTimer)
|
|
296
|
+
processTimer = null
|
|
297
|
+
}
|
|
298
|
+
if (stream) {
|
|
299
|
+
stream.close()
|
|
300
|
+
stream = null
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
async resetTokens(startPosition: bigint = 0n, resetContext?: unknown) {
|
|
305
|
+
if (isRunning) {
|
|
306
|
+
throw new Error(`Processor "${name}" must be stopped before resetting tokens`)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const headPosition = await eventSource.getHeadPosition()
|
|
310
|
+
|
|
311
|
+
if (headPosition <= startPosition) {
|
|
312
|
+
token = globalSequenceToken(startPosition)
|
|
313
|
+
} else {
|
|
314
|
+
token = replayToken(
|
|
315
|
+
globalSequenceToken(headPosition),
|
|
316
|
+
globalSequenceToken(startPosition),
|
|
317
|
+
resetContext,
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (tokenStore) {
|
|
322
|
+
await tokenStore.store(name, segment, token)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (onReset) {
|
|
326
|
+
await onReset()
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
async splitSegment(_segmentId: number): Promise<boolean> {
|
|
331
|
+
if (!tokenStore) return false
|
|
332
|
+
console.warn(`Processor "${name}": segment splitting requires multi-segment support (not yet implemented)`)
|
|
333
|
+
return false
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
async mergeSegment(_segmentId: number): Promise<boolean> {
|
|
337
|
+
if (!tokenStore) return false
|
|
338
|
+
console.warn(`Processor "${name}": segment merging requires multi-segment support (not yet implemented)`)
|
|
339
|
+
return false
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
async releaseSegment(_segmentId: number): Promise<void> {
|
|
343
|
+
if (!tokenStore) return
|
|
344
|
+
await tokenStore.releaseClaim(name, segment, name)
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
supportsReset() {
|
|
348
|
+
return !isRunning
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { emptyMetadata, qualifiedNameToString } from "@kronos-ts/common"
|
|
2
|
+
import type { EventMessage } from "./message.js"
|
|
3
|
+
import type { EventHandlerRegistration } from "./handler.js"
|
|
4
|
+
import type { EventHandlerDefinition } from "./event-handler.js"
|
|
5
|
+
import type { UoWRunner } from "./unit-of-work.js"
|
|
6
|
+
import { runInNewUoW } from "./unit-of-work.js"
|
|
7
|
+
import type { EventProcessingErrorHandler } from "./tracking-event-processor.js"
|
|
8
|
+
import { loggingErrorHandler } from "./tracking-event-processor.js"
|
|
9
|
+
import type { SubscribableEventSource } from "./event-bus.js"
|
|
10
|
+
import type { CommandBus } from "./command-bus.js"
|
|
11
|
+
import type { QueryBus } from "./query-bus.js"
|
|
12
|
+
import type { HandlerEnhancerDefinition } from "./handler-enhancer.js"
|
|
13
|
+
import { setResource } from "./processing-state.js"
|
|
14
|
+
import { STATE_MANAGER_KEY } from "@kronos-ts/eventsourcing"
|
|
15
|
+
import { COMMAND_BUS_KEY } from "./send.js"
|
|
16
|
+
import { QUERY_BUS_KEY } from "./emit-update.js"
|
|
17
|
+
|
|
18
|
+
// Re-export for backward compatibility
|
|
19
|
+
export type { SubscribableEventSource } from "./event-bus.js"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A push-based event processor that subscribes directly to an event source.
|
|
23
|
+
*
|
|
24
|
+
* Unlike tracking/streaming processors, the subscribing processor:
|
|
25
|
+
* - Does **not** use a token store (no position tracking)
|
|
26
|
+
* - Does **not** support replay or reset
|
|
27
|
+
* - Processes events synchronously with the publisher
|
|
28
|
+
* - Is suitable for in-memory projections that don't need persistence
|
|
29
|
+
*
|
|
30
|
+
*/
|
|
31
|
+
export interface SubscribingEventProcessor {
|
|
32
|
+
readonly name: string
|
|
33
|
+
readonly running: boolean
|
|
34
|
+
start(): void
|
|
35
|
+
stop(): void
|
|
36
|
+
/** Always returns false — subscribing processors don't support reset. */
|
|
37
|
+
supportsReset(): boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SubscribingEventProcessorOptions {
|
|
41
|
+
name: string
|
|
42
|
+
eventSource: SubscribableEventSource
|
|
43
|
+
eventHandlers: ReadonlyArray<EventHandlerDefinition>
|
|
44
|
+
/** State manager injected into ALS at handler-invocation entry (D-44). */
|
|
45
|
+
stateManager?: unknown
|
|
46
|
+
/** Command bus injected into ALS at handler-invocation entry (D-44). */
|
|
47
|
+
commandBus?: CommandBus
|
|
48
|
+
/** Query bus injected into ALS at handler-invocation entry (D-44). */
|
|
49
|
+
queryBus?: QueryBus
|
|
50
|
+
/** Optional per-event callback fired inside the UoW before handler invocation (e.g. monitoring). */
|
|
51
|
+
onEventDelivery?: () => void
|
|
52
|
+
unitOfWorkRunner?: UoWRunner
|
|
53
|
+
errorHandler?: EventProcessingErrorHandler
|
|
54
|
+
/**
|
|
55
|
+
* Plan 09-01: optional handler enhancer applied to each event handler at
|
|
56
|
+
* registration time. Symmetric to TrackingEventProcessor.handlerEnhancer.
|
|
57
|
+
* When set, each registered handler is wrapped via wrapHandler with
|
|
58
|
+
* messageType "event" and the group name as handlerGroup.
|
|
59
|
+
*/
|
|
60
|
+
handlerEnhancer?: HandlerEnhancerDefinition
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates a subscribing event processor.
|
|
65
|
+
*
|
|
66
|
+
* The processor subscribes to the event source and processes events
|
|
67
|
+
* within a UnitOfWork as they arrive. Events are delivered on the
|
|
68
|
+
* publisher's call stack (synchronous with append).
|
|
69
|
+
*/
|
|
70
|
+
export function createSubscribingEventProcessor(
|
|
71
|
+
options: SubscribingEventProcessorOptions,
|
|
72
|
+
): SubscribingEventProcessor {
|
|
73
|
+
const {
|
|
74
|
+
name,
|
|
75
|
+
eventSource,
|
|
76
|
+
eventHandlers,
|
|
77
|
+
stateManager,
|
|
78
|
+
commandBus,
|
|
79
|
+
queryBus,
|
|
80
|
+
onEventDelivery,
|
|
81
|
+
unitOfWorkRunner = runInNewUoW,
|
|
82
|
+
errorHandler = loggingErrorHandler(name),
|
|
83
|
+
handlerEnhancer,
|
|
84
|
+
} = options
|
|
85
|
+
|
|
86
|
+
// Build handler lookup: eventName → handler[]
|
|
87
|
+
// Plan 09-01: when a handlerEnhancer is supplied, wrap each handler at
|
|
88
|
+
// registration time symmetric to TrackingEventProcessor. Plan 11-02:
|
|
89
|
+
// handlerGroup is now the processor name (no separate group identity).
|
|
90
|
+
const handlerMap = new Map<string, EventHandlerRegistration<any>[]>()
|
|
91
|
+
for (const reg of eventHandlers) {
|
|
92
|
+
const eventName = qualifiedNameToString(reg.descriptor.name)
|
|
93
|
+
const enhanced = handlerEnhancer
|
|
94
|
+
? {
|
|
95
|
+
...reg,
|
|
96
|
+
handler: handlerEnhancer.wrapHandler(reg.handler, {
|
|
97
|
+
messageType: "event" as const,
|
|
98
|
+
messageName: eventName,
|
|
99
|
+
handlerGroup: name,
|
|
100
|
+
}),
|
|
101
|
+
}
|
|
102
|
+
: reg
|
|
103
|
+
const existing = handlerMap.get(eventName)
|
|
104
|
+
if (existing) {
|
|
105
|
+
existing.push(enhanced as EventHandlerRegistration<any>)
|
|
106
|
+
} else {
|
|
107
|
+
handlerMap.set(eventName, [enhanced as EventHandlerRegistration<any>])
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let isRunning = false
|
|
112
|
+
let unsubscribe: (() => void) | null = null
|
|
113
|
+
|
|
114
|
+
async function handleEvents(events: ReadonlyArray<EventMessage>) {
|
|
115
|
+
if (!isRunning || events.length === 0) return
|
|
116
|
+
|
|
117
|
+
await unitOfWorkRunner(emptyMetadata(), async () => {
|
|
118
|
+
for (const event of events) {
|
|
119
|
+
await deliverEvent(event)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function deliverEvent(event: EventMessage) {
|
|
125
|
+
const eventName = qualifiedNameToString(event.name)
|
|
126
|
+
const handlers = handlerMap.get(eventName)
|
|
127
|
+
if (!handlers || handlers.length === 0) return
|
|
128
|
+
|
|
129
|
+
// D-44 wiring: write framework components into ALS at per-event invocation entry.
|
|
130
|
+
if (stateManager !== undefined) setResource(STATE_MANAGER_KEY, stateManager as any)
|
|
131
|
+
if (commandBus !== undefined) setResource(COMMAND_BUS_KEY, commandBus)
|
|
132
|
+
if (queryBus !== undefined) setResource(QUERY_BUS_KEY, queryBus)
|
|
133
|
+
// Optional per-event callback (e.g. monitoring hooks registered inside the UoW).
|
|
134
|
+
if (onEventDelivery) onEventDelivery()
|
|
135
|
+
|
|
136
|
+
for (const reg of handlers) {
|
|
137
|
+
try {
|
|
138
|
+
await reg.handler(event.payload, event.metadata)
|
|
139
|
+
} catch (err) {
|
|
140
|
+
// SubscribingEventProcessor doesn't have position tracking,
|
|
141
|
+
// so pass -1n as position indicator
|
|
142
|
+
await errorHandler.handleError(err, eventName, -1n)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
get name() { return name },
|
|
149
|
+
get running() { return isRunning },
|
|
150
|
+
|
|
151
|
+
start() {
|
|
152
|
+
if (isRunning) return
|
|
153
|
+
isRunning = true
|
|
154
|
+
unsubscribe = eventSource.subscribe(handleEvents)
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
stop() {
|
|
158
|
+
isRunning = false
|
|
159
|
+
if (unsubscribe) {
|
|
160
|
+
unsubscribe()
|
|
161
|
+
unsubscribe = null
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
supportsReset() {
|
|
166
|
+
return false
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
}
|