@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,301 @@
|
|
|
1
|
+
import type { EventMessage } from "./message.js"
|
|
2
|
+
import type { EventCriteria } from "./event-criteria.js"
|
|
3
|
+
import type { TrackingToken } from "./tracking-token.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An event with its global sequence position.
|
|
7
|
+
*/
|
|
8
|
+
export interface SequencedEvent {
|
|
9
|
+
readonly sequence: bigint
|
|
10
|
+
readonly event: EventMessage
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Condition for opening a streaming event source.
|
|
15
|
+
* Defines the starting position and optional event criteria filter.
|
|
16
|
+
*/
|
|
17
|
+
export interface StreamingCondition {
|
|
18
|
+
/** Position to start streaming from. */
|
|
19
|
+
readonly position: bigint
|
|
20
|
+
/** Optional criteria to filter events. When omitted, all events are delivered. */
|
|
21
|
+
readonly criteria?: EventCriteria
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A push-based message stream. Events are buffered internally and
|
|
26
|
+
* pulled via {@link next}. The stream notifies when events become
|
|
27
|
+
* available via {@link setCallback}.
|
|
28
|
+
*/
|
|
29
|
+
export interface MessageStream<M> {
|
|
30
|
+
/** Pull the next available item (non-blocking). */
|
|
31
|
+
next(): M | undefined
|
|
32
|
+
/** Peek at the next item without consuming it. */
|
|
33
|
+
peek(): M | undefined
|
|
34
|
+
/** Check if there are items ready to be pulled. */
|
|
35
|
+
hasNextAvailable(): boolean
|
|
36
|
+
/** Whether the stream has been completed. */
|
|
37
|
+
isCompleted(): boolean
|
|
38
|
+
/** The error that caused the stream to fail, if any. */
|
|
39
|
+
error(): Error | undefined
|
|
40
|
+
/** Register a callback for when items become available. */
|
|
41
|
+
setCallback(callback: () => void): void
|
|
42
|
+
/** Close the stream and release resources. */
|
|
43
|
+
close(): void
|
|
44
|
+
/** Transform each item. */
|
|
45
|
+
map<R>(mapper: (item: M) => R): MessageStream<R>
|
|
46
|
+
/** Filter items by predicate. */
|
|
47
|
+
filter(predicate: (item: M) => boolean): MessageStream<M>
|
|
48
|
+
/** Recover from stream errors by providing a continuation stream. */
|
|
49
|
+
onErrorContinue(recovery: (error: Error) => MessageStream<M>): MessageStream<M>
|
|
50
|
+
/** Reduce all items to a single value. Resolves when the stream completes. */
|
|
51
|
+
reduce<R>(identity: R, accumulator: (acc: R, item: M) => R): Promise<R>
|
|
52
|
+
/** Concatenate another stream after this one completes. */
|
|
53
|
+
concatWith(other: MessageStream<M>): MessageStream<M>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates a MessageStream that wraps a source with transformation support.
|
|
58
|
+
* Used by event store implementations to provide stream instances.
|
|
59
|
+
*/
|
|
60
|
+
export function createMessageStream<M>(source: {
|
|
61
|
+
next(): M | undefined
|
|
62
|
+
peek(): M | undefined
|
|
63
|
+
hasNextAvailable(): boolean
|
|
64
|
+
isCompleted(): boolean
|
|
65
|
+
error(): Error | undefined
|
|
66
|
+
setCallback(callback: () => void): void
|
|
67
|
+
close(): void
|
|
68
|
+
}): MessageStream<M> {
|
|
69
|
+
const stream: MessageStream<M> = {
|
|
70
|
+
...source,
|
|
71
|
+
map<R>(mapper: (item: M) => R): MessageStream<R> {
|
|
72
|
+
return createMappedStream(stream, mapper)
|
|
73
|
+
},
|
|
74
|
+
filter(predicate: (item: M) => boolean): MessageStream<M> {
|
|
75
|
+
return createFilteredStream(stream, predicate)
|
|
76
|
+
},
|
|
77
|
+
onErrorContinue(recovery: (error: Error) => MessageStream<M>): MessageStream<M> {
|
|
78
|
+
return createErrorRecoveryStream(stream, recovery)
|
|
79
|
+
},
|
|
80
|
+
reduce<R>(identity: R, accumulator: (acc: R, item: M) => R): Promise<R> {
|
|
81
|
+
return reduceStream(stream, identity, accumulator)
|
|
82
|
+
},
|
|
83
|
+
concatWith(other: MessageStream<M>): MessageStream<M> {
|
|
84
|
+
return createConcatStream(stream, other)
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
return stream
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Creates a completed empty MessageStream.
|
|
92
|
+
*/
|
|
93
|
+
export function emptyMessageStream<M>(): MessageStream<M> {
|
|
94
|
+
return createMessageStream<M>({
|
|
95
|
+
next: () => undefined,
|
|
96
|
+
peek: () => undefined,
|
|
97
|
+
hasNextAvailable: () => false,
|
|
98
|
+
isCompleted: () => true,
|
|
99
|
+
error: () => undefined,
|
|
100
|
+
setCallback: () => {},
|
|
101
|
+
close: () => {},
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a MessageStream that immediately fails with the given error.
|
|
107
|
+
*/
|
|
108
|
+
export function failedMessageStream<M>(error: Error): MessageStream<M> {
|
|
109
|
+
return createMessageStream<M>({
|
|
110
|
+
next: () => undefined,
|
|
111
|
+
peek: () => undefined,
|
|
112
|
+
hasNextAvailable: () => false,
|
|
113
|
+
isCompleted: () => true,
|
|
114
|
+
error: () => error,
|
|
115
|
+
setCallback: () => {},
|
|
116
|
+
close: () => {},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Stream transformations
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
function createMappedStream<M, R>(source: MessageStream<M>, mapper: (item: M) => R): MessageStream<R> {
|
|
125
|
+
return createMessageStream<R>({
|
|
126
|
+
next() {
|
|
127
|
+
const item = source.next()
|
|
128
|
+
return item !== undefined ? mapper(item) : undefined
|
|
129
|
+
},
|
|
130
|
+
peek() {
|
|
131
|
+
const item = source.peek()
|
|
132
|
+
return item !== undefined ? mapper(item) : undefined
|
|
133
|
+
},
|
|
134
|
+
hasNextAvailable: () => source.hasNextAvailable(),
|
|
135
|
+
isCompleted: () => source.isCompleted(),
|
|
136
|
+
error: () => source.error(),
|
|
137
|
+
setCallback: (cb) => source.setCallback(cb),
|
|
138
|
+
close: () => source.close(),
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function createFilteredStream<M>(source: MessageStream<M>, predicate: (item: M) => boolean): MessageStream<M> {
|
|
143
|
+
let buffered: M | undefined
|
|
144
|
+
|
|
145
|
+
function advance(): M | undefined {
|
|
146
|
+
while (true) {
|
|
147
|
+
const item = source.next()
|
|
148
|
+
if (item === undefined) return undefined
|
|
149
|
+
if (predicate(item)) return item
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return createMessageStream<M>({
|
|
154
|
+
next() {
|
|
155
|
+
if (buffered !== undefined) {
|
|
156
|
+
const item = buffered
|
|
157
|
+
buffered = undefined
|
|
158
|
+
return item
|
|
159
|
+
}
|
|
160
|
+
return advance()
|
|
161
|
+
},
|
|
162
|
+
peek() {
|
|
163
|
+
if (buffered !== undefined) return buffered
|
|
164
|
+
buffered = advance()
|
|
165
|
+
return buffered
|
|
166
|
+
},
|
|
167
|
+
hasNextAvailable() {
|
|
168
|
+
if (buffered !== undefined) return true
|
|
169
|
+
buffered = advance()
|
|
170
|
+
return buffered !== undefined
|
|
171
|
+
},
|
|
172
|
+
isCompleted: () => source.isCompleted() && buffered === undefined,
|
|
173
|
+
error: () => source.error(),
|
|
174
|
+
setCallback: (cb) => source.setCallback(cb),
|
|
175
|
+
close: () => source.close(),
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function createErrorRecoveryStream<M>(
|
|
180
|
+
source: MessageStream<M>,
|
|
181
|
+
recovery: (error: Error) => MessageStream<M>,
|
|
182
|
+
): MessageStream<M> {
|
|
183
|
+
let current: MessageStream<M> = source
|
|
184
|
+
let recovered = false
|
|
185
|
+
|
|
186
|
+
function checkRecovery(): void {
|
|
187
|
+
if (!recovered && current.error()) {
|
|
188
|
+
current = recovery(current.error()!)
|
|
189
|
+
recovered = true
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return createMessageStream<M>({
|
|
194
|
+
next() { checkRecovery(); return current.next() },
|
|
195
|
+
peek() { checkRecovery(); return current.peek() },
|
|
196
|
+
hasNextAvailable() { checkRecovery(); return current.hasNextAvailable() },
|
|
197
|
+
isCompleted() { checkRecovery(); return current.isCompleted() },
|
|
198
|
+
error() { return recovered ? current.error() : undefined },
|
|
199
|
+
setCallback(cb) { current.setCallback(cb) },
|
|
200
|
+
close() { current.close() },
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function createConcatStream<M>(first: MessageStream<M>, second: MessageStream<M>): MessageStream<M> {
|
|
205
|
+
let usingFirst = true
|
|
206
|
+
|
|
207
|
+
function current(): MessageStream<M> {
|
|
208
|
+
if (usingFirst && first.isCompleted() && !first.hasNextAvailable()) {
|
|
209
|
+
usingFirst = false
|
|
210
|
+
}
|
|
211
|
+
return usingFirst ? first : second
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return createMessageStream<M>({
|
|
215
|
+
next() { return current().next() },
|
|
216
|
+
peek() { return current().peek() },
|
|
217
|
+
hasNextAvailable() { return current().hasNextAvailable() },
|
|
218
|
+
isCompleted() { return current().isCompleted() },
|
|
219
|
+
error() { return current().error() },
|
|
220
|
+
setCallback(cb) {
|
|
221
|
+
if (usingFirst) {
|
|
222
|
+
first.setCallback(() => {
|
|
223
|
+
if (first.isCompleted() && !first.hasNextAvailable()) {
|
|
224
|
+
usingFirst = false
|
|
225
|
+
second.setCallback(cb)
|
|
226
|
+
cb()
|
|
227
|
+
} else {
|
|
228
|
+
cb()
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
} else {
|
|
232
|
+
second.setCallback(cb)
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
close() { first.close(); second.close() },
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function reduceStream<M, R>(
|
|
240
|
+
stream: MessageStream<M>,
|
|
241
|
+
identity: R,
|
|
242
|
+
accumulator: (acc: R, item: M) => R,
|
|
243
|
+
): Promise<R> {
|
|
244
|
+
let result = identity
|
|
245
|
+
return new Promise<R>((resolve, reject) => {
|
|
246
|
+
function drain() {
|
|
247
|
+
while (true) {
|
|
248
|
+
const item = stream.next()
|
|
249
|
+
if (item !== undefined) {
|
|
250
|
+
result = accumulator(result, item)
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
253
|
+
if (stream.error()) {
|
|
254
|
+
reject(stream.error())
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
if (stream.isCompleted()) {
|
|
258
|
+
resolve(result)
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
// Wait for more items
|
|
262
|
+
stream.setCallback(drain)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
drain()
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// StreamableEventSource
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* A source of events that can be opened as an infinite stream.
|
|
276
|
+
*/
|
|
277
|
+
export interface StreamableEventSource {
|
|
278
|
+
/**
|
|
279
|
+
* Open an infinite event stream starting from the given condition.
|
|
280
|
+
*/
|
|
281
|
+
open(condition: StreamingCondition): MessageStream<SequencedEvent>
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get the token representing the beginning of the event stream.
|
|
285
|
+
* A processor starting from this token will read all events.
|
|
286
|
+
*/
|
|
287
|
+
firstToken(): Promise<TrackingToken>
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get the token representing the current tail of the event stream.
|
|
291
|
+
* A processor starting from this token will only see new events.
|
|
292
|
+
*/
|
|
293
|
+
latestToken(): Promise<TrackingToken>
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get the current head position — the sequence of the next event
|
|
297
|
+
* to be appended. Convenience method equivalent to
|
|
298
|
+
* {@code (await latestToken()).position()}.
|
|
299
|
+
*/
|
|
300
|
+
getHeadPosition(): Promise<bigint>
|
|
301
|
+
}
|
package/src/gateway.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateIdentifier,
|
|
3
|
+
emptyMetadata,
|
|
4
|
+
type Metadata,
|
|
5
|
+
} from "@kronos-ts/common"
|
|
6
|
+
import type { CommandBus } from "./command-bus.js"
|
|
7
|
+
import type { QueryBus } from "./query-bus.js"
|
|
8
|
+
import type { CommandDescriptor, QueryDescriptor } from "./descriptor.js"
|
|
9
|
+
import type { SubscriptionQueryResult } from "./subscription-query.js"
|
|
10
|
+
import { runInNewUoW, type UoWRunner } from "./unit-of-work.js"
|
|
11
|
+
import type { z } from "zod"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Infers the result type from a descriptor's `result` schema.
|
|
15
|
+
* If no result schema, returns `unknown`.
|
|
16
|
+
*/
|
|
17
|
+
type InferResult<R extends z.ZodType | undefined> =
|
|
18
|
+
R extends z.ZodType ? z.infer<R> : unknown
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* User-facing command gateway. Wraps payloads into proper command messages
|
|
22
|
+
* and delegates to the command bus.
|
|
23
|
+
*
|
|
24
|
+
* When the command descriptor has a `result` schema, the return type is inferred:
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const CreateCourse = command({
|
|
27
|
+
* name: qn("university", "CreateCourse"),
|
|
28
|
+
* payload: z.object({ courseId: z.string() }),
|
|
29
|
+
* result: z.object({ id: z.string() }),
|
|
30
|
+
* })
|
|
31
|
+
*
|
|
32
|
+
* const result = await gateway.send(CreateCourse, { courseId: "cs-101" })
|
|
33
|
+
* // ^ { id: string } — inferred from descriptor
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export interface CommandGateway {
|
|
37
|
+
send<P extends z.ZodType, R extends z.ZodType | undefined = undefined>(
|
|
38
|
+
descriptor: CommandDescriptor<P, R>,
|
|
39
|
+
payload: z.infer<P>,
|
|
40
|
+
metadata?: Metadata,
|
|
41
|
+
): Promise<InferResult<R>>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* User-facing query gateway. Wraps payloads into proper query messages
|
|
46
|
+
* and delegates to the query bus.
|
|
47
|
+
*
|
|
48
|
+
* When the query descriptor has a `result` schema, the return type is inferred:
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const GetCourse = query({
|
|
51
|
+
* name: qn("university", "GetCourseView"),
|
|
52
|
+
* payload: z.object({ courseId: z.string() }),
|
|
53
|
+
* result: z.object({ courseId: z.string(), name: z.string() }),
|
|
54
|
+
* })
|
|
55
|
+
*
|
|
56
|
+
* const course = await gateway.query(GetCourse, { courseId: "cs-101" })
|
|
57
|
+
* // ^ { courseId: string, name: string } — inferred from descriptor
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export interface QueryGateway {
|
|
61
|
+
query<P extends z.ZodType, R extends z.ZodType | undefined = undefined>(
|
|
62
|
+
descriptor: QueryDescriptor<P, R>,
|
|
63
|
+
payload: z.infer<P>,
|
|
64
|
+
metadata?: Metadata,
|
|
65
|
+
): Promise<InferResult<R>>
|
|
66
|
+
|
|
67
|
+
subscriptionQuery<P extends z.ZodType, R extends z.ZodType | undefined = undefined>(
|
|
68
|
+
descriptor: QueryDescriptor<P, R>,
|
|
69
|
+
payload: z.infer<P>,
|
|
70
|
+
metadata?: Metadata,
|
|
71
|
+
): SubscriptionQueryResult
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a command gateway backed by a command bus.
|
|
76
|
+
*
|
|
77
|
+
* Plan 03-04 (CTX-04 / D-34): the optional `unitOfWorkRunner` lets the
|
|
78
|
+
* configurer inject a transactional wrapper (`transactionalUnitOfWorkFactory`)
|
|
79
|
+
* around the dispatch boundary. Defaults to `runInNewUoW` — preserves the
|
|
80
|
+
* Plan 03-01 contract that every gateway call starts a fresh UoW.
|
|
81
|
+
*/
|
|
82
|
+
export function createCommandGateway(
|
|
83
|
+
bus: CommandBus,
|
|
84
|
+
unitOfWorkRunner: UoWRunner = runInNewUoW,
|
|
85
|
+
): CommandGateway {
|
|
86
|
+
return {
|
|
87
|
+
async send(descriptor, payload, metadata) {
|
|
88
|
+
const resolvedMetadata = metadata ?? emptyMetadata()
|
|
89
|
+
// Plan 03-01 (D-32) / Plan 03-03 (CTX-01): gateways always start a new
|
|
90
|
+
// UoW. The bus.dispatch call below will detect the ALS state we just
|
|
91
|
+
// established (via runInUoW in simple-command-bus) and reuse it — so
|
|
92
|
+
// this is the single UoW boundary for the dispatch chain. No
|
|
93
|
+
// ProcessingContext parameter is threaded.
|
|
94
|
+
return unitOfWorkRunner(resolvedMetadata, () =>
|
|
95
|
+
bus.dispatch({
|
|
96
|
+
identifier: generateIdentifier(),
|
|
97
|
+
name: descriptor.name,
|
|
98
|
+
payload,
|
|
99
|
+
metadata: resolvedMetadata,
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
}) as Promise<any>,
|
|
102
|
+
) as any
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates a query gateway backed by a query bus.
|
|
109
|
+
*
|
|
110
|
+
* See `createCommandGateway` for the `unitOfWorkRunner` injection contract.
|
|
111
|
+
*/
|
|
112
|
+
export function createQueryGateway(
|
|
113
|
+
bus: QueryBus,
|
|
114
|
+
unitOfWorkRunner: UoWRunner = runInNewUoW,
|
|
115
|
+
): QueryGateway {
|
|
116
|
+
return {
|
|
117
|
+
async query(descriptor, payload, metadata) {
|
|
118
|
+
const resolvedMetadata = metadata ?? emptyMetadata()
|
|
119
|
+
// Plan 03-01 (D-32) / Plan 03-03 (CTX-01): gateway always starts a new
|
|
120
|
+
// UoW; bus.query auto-nests via runInUoW in simple-query-bus.
|
|
121
|
+
// subscriptionQuery below stays as-is — its initialResult goes through
|
|
122
|
+
// bus.query, which handles its own UoW.
|
|
123
|
+
return unitOfWorkRunner(resolvedMetadata, () =>
|
|
124
|
+
bus.query({
|
|
125
|
+
identifier: generateIdentifier(),
|
|
126
|
+
name: descriptor.name,
|
|
127
|
+
payload,
|
|
128
|
+
metadata: resolvedMetadata,
|
|
129
|
+
timestamp: Date.now(),
|
|
130
|
+
}) as Promise<any>,
|
|
131
|
+
) as any
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
subscriptionQuery(descriptor, payload, metadata) {
|
|
135
|
+
return bus.subscriptionQuery({
|
|
136
|
+
identifier: generateIdentifier(),
|
|
137
|
+
name: descriptor.name,
|
|
138
|
+
payload,
|
|
139
|
+
metadata: metadata ?? emptyMetadata(),
|
|
140
|
+
timestamp: Date.now(),
|
|
141
|
+
})
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata about a handler being enhanced. Allows enhancers to
|
|
3
|
+
* selectively wrap based on handler type, message name, etc.
|
|
4
|
+
*/
|
|
5
|
+
export interface HandlerMetadata {
|
|
6
|
+
/** The type of message this handler processes. */
|
|
7
|
+
readonly messageType: "command" | "event" | "query"
|
|
8
|
+
/** The qualified name of the message (e.g., "university.courses.CreateCourse"). */
|
|
9
|
+
readonly messageName: string
|
|
10
|
+
/** The name of the handler group or module (e.g., "course-commands"). */
|
|
11
|
+
readonly handlerGroup: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Wraps message handler functions at registration time to add cross-cutting
|
|
16
|
+
* concerns. Different from interceptors — interceptors wrap dispatch,
|
|
17
|
+
* enhancers wrap the handler itself.
|
|
18
|
+
*
|
|
19
|
+
* Applied once at handler discovery/registration time, not per-invocation.
|
|
20
|
+
* This makes enhancers ideal for:
|
|
21
|
+
* - Tracing spans per-handler
|
|
22
|
+
* - Security checks
|
|
23
|
+
* - Timeout enforcement
|
|
24
|
+
* - Caching
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const timingEnhancer: HandlerEnhancerDefinition = {
|
|
29
|
+
* wrapHandler(handler, metadata) {
|
|
30
|
+
* return async (...args) => {
|
|
31
|
+
* const start = performance.now()
|
|
32
|
+
* try {
|
|
33
|
+
* return await handler(...args)
|
|
34
|
+
* } finally {
|
|
35
|
+
* console.log(`${metadata.messageName} took ${performance.now() - start}ms`)
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* },
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export interface HandlerEnhancerDefinition {
|
|
43
|
+
/**
|
|
44
|
+
* Wrap a handler function. Return the original handler to skip enhancement.
|
|
45
|
+
* The returned function must have the same signature as the input.
|
|
46
|
+
*/
|
|
47
|
+
wrapHandler<T extends (...args: any[]) => any>(
|
|
48
|
+
handler: T,
|
|
49
|
+
metadata: HandlerMetadata,
|
|
50
|
+
): T
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Combines multiple handler enhancer definitions into a single one.
|
|
55
|
+
* Enhancers are applied in order — the first enhancer wraps outermost.
|
|
56
|
+
*/
|
|
57
|
+
export function multiHandlerEnhancerDefinition(
|
|
58
|
+
enhancers: ReadonlyArray<HandlerEnhancerDefinition>,
|
|
59
|
+
): HandlerEnhancerDefinition {
|
|
60
|
+
return {
|
|
61
|
+
wrapHandler<T extends (...args: any[]) => any>(handler: T, metadata: HandlerMetadata): T {
|
|
62
|
+
let wrapped = handler
|
|
63
|
+
// Apply in reverse order so first enhancer is outermost
|
|
64
|
+
for (let i = enhancers.length - 1; i >= 0; i--) {
|
|
65
|
+
wrapped = enhancers[i]!.wrapHandler(wrapped, metadata)
|
|
66
|
+
}
|
|
67
|
+
return wrapped
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/handler.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { z } from "zod"
|
|
2
|
+
import type { Metadata } from "@kronos-ts/common"
|
|
3
|
+
import type {
|
|
4
|
+
CommandDescriptor,
|
|
5
|
+
EventDescriptor,
|
|
6
|
+
QueryDescriptor,
|
|
7
|
+
} from "./descriptor.js"
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Handler context shapes — DELETED (Plan 04-02, D-41)
|
|
11
|
+
// CommandHandlerContext / EventHandlerContext / QueryHandlerContext removed.
|
|
12
|
+
// LoadFunction / AppendFunction / SendFunction / EmitUpdateFunction removed.
|
|
13
|
+
// Consumers import load/append from @kronos-ts/eventsourcing and
|
|
14
|
+
// send/dispatch/emitUpdate from @kronos-ts/messaging directly.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Registration types
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/** A paired event descriptor + handler function, used in handler arrays. */
|
|
22
|
+
export interface EventHandlerRegistration<P extends z.ZodType = z.ZodType> {
|
|
23
|
+
readonly kind: "event-handler"
|
|
24
|
+
readonly descriptor: EventDescriptor<P>
|
|
25
|
+
readonly handler: (
|
|
26
|
+
event: z.infer<P>,
|
|
27
|
+
metadata: Metadata,
|
|
28
|
+
) => Promise<void> | void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A paired event descriptor + evolver function, used in entity evolve arrays.
|
|
33
|
+
*
|
|
34
|
+
* Evolvers can be sync or async. Async evolvers don't block the event loop
|
|
35
|
+
* during state reconstruction.
|
|
36
|
+
*/
|
|
37
|
+
export interface EvolverRegistration<
|
|
38
|
+
S = unknown,
|
|
39
|
+
P extends z.ZodType = z.ZodType,
|
|
40
|
+
> {
|
|
41
|
+
readonly kind: "evolver"
|
|
42
|
+
readonly descriptor: EventDescriptor<P>
|
|
43
|
+
readonly evolve: (state: S, event: z.infer<P>, id: unknown) => S | Promise<S>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A paired query descriptor + handler function, with result type on the handler. */
|
|
47
|
+
export interface QueryHandlerRegistration<
|
|
48
|
+
Q extends z.ZodType = z.ZodType,
|
|
49
|
+
R = unknown,
|
|
50
|
+
> {
|
|
51
|
+
readonly kind: "query-handler"
|
|
52
|
+
readonly descriptor: QueryDescriptor<Q>
|
|
53
|
+
readonly handler: (
|
|
54
|
+
query: z.infer<Q>,
|
|
55
|
+
metadata: Metadata,
|
|
56
|
+
) => Promise<R> | R
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// on() — universal registration
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Universal registration function.
|
|
65
|
+
* Pairs a descriptor with its handler for use in handler/evolve arrays.
|
|
66
|
+
*
|
|
67
|
+
* Usage:
|
|
68
|
+
* - Event handlers: `on(CourseCreated, async (event, ctx) => { ... })`
|
|
69
|
+
* - Query handlers: `on(GetCourse, async (query, ctx) => { return { ... } })`
|
|
70
|
+
* - Evolvers: `on(CourseCreated, (state, event) => ({ ...state, name: event.name }))`
|
|
71
|
+
*
|
|
72
|
+
* The overload is resolved by the descriptor kind and how many arguments
|
|
73
|
+
* the callback declares. Evolvers receive `(state, event)` or `(state, event, id)`,
|
|
74
|
+
* while event handlers receive `(event, context)`.
|
|
75
|
+
*
|
|
76
|
+
* In practice the distinction is enforced by the array type:
|
|
77
|
+
* - `evolve: [on(...)]` expects `EvolverRegistration`
|
|
78
|
+
* - `handlers: [on(...)]` expects `EventHandlerRegistration`
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// Overload: evolver (event descriptor + state evolve function)
|
|
82
|
+
export function on<S, P extends z.ZodType>(
|
|
83
|
+
descriptor: EventDescriptor<P>,
|
|
84
|
+
evolve: (state: S, event: z.infer<P>, id: unknown) => S | Promise<S>,
|
|
85
|
+
): EvolverRegistration<S, P>
|
|
86
|
+
|
|
87
|
+
// Overload: event handler (event descriptor + handler function)
|
|
88
|
+
export function on<P extends z.ZodType>(
|
|
89
|
+
descriptor: EventDescriptor<P>,
|
|
90
|
+
handler: (event: z.infer<P>, metadata: Metadata) => Promise<void> | void,
|
|
91
|
+
): EventHandlerRegistration<P>
|
|
92
|
+
|
|
93
|
+
// Overload: query handler
|
|
94
|
+
export function on<Q extends z.ZodType, R>(
|
|
95
|
+
descriptor: QueryDescriptor<Q>,
|
|
96
|
+
handler: (
|
|
97
|
+
query: z.infer<Q>,
|
|
98
|
+
metadata: Metadata,
|
|
99
|
+
) => Promise<R> | R,
|
|
100
|
+
): QueryHandlerRegistration<Q, R>
|
|
101
|
+
|
|
102
|
+
export function on(
|
|
103
|
+
descriptor: EventDescriptor | QueryDescriptor,
|
|
104
|
+
handler: (...args: any[]) => any,
|
|
105
|
+
): EventHandlerRegistration | EvolverRegistration | QueryHandlerRegistration {
|
|
106
|
+
if (descriptor.kind === "event") {
|
|
107
|
+
// For event descriptors, the same `on()` call is used for both event handlers
|
|
108
|
+
// and evolvers. We return an object that satisfies both interfaces — the
|
|
109
|
+
// consumer's array type (evolve: EvolverRegistration[] vs handlers: EventHandlerRegistration[])
|
|
110
|
+
// enforces correct usage at compile time.
|
|
111
|
+
return {
|
|
112
|
+
kind: "event-handler" as any,
|
|
113
|
+
descriptor: descriptor as EventDescriptor,
|
|
114
|
+
handler,
|
|
115
|
+
evolve: handler,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
kind: "query-handler",
|
|
120
|
+
descriptor: descriptor as QueryDescriptor,
|
|
121
|
+
handler,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @deprecated Use `on()` instead. `onEvent` is kept for backward compatibility.
|
|
127
|
+
*/
|
|
128
|
+
export function onEvent<S, P extends z.ZodType>(
|
|
129
|
+
descriptor: EventDescriptor<P>,
|
|
130
|
+
evolve: (state: S, event: z.infer<P>, id: unknown) => S | Promise<S>,
|
|
131
|
+
): EvolverRegistration<S, P> {
|
|
132
|
+
return { kind: "evolver", descriptor, evolve }
|
|
133
|
+
}
|