@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,173 @@
|
|
|
1
|
+
import { resourceKey, type ResourceKey } from "@kronos-ts/common"
|
|
2
|
+
import type { QueryMessage } from "./message.js"
|
|
3
|
+
import {
|
|
4
|
+
processingStateStorage,
|
|
5
|
+
computeIfAbsent,
|
|
6
|
+
onAfterCommit,
|
|
7
|
+
} from "./processing-state.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The result of a subscription query — initial result plus a stream
|
|
11
|
+
* of incremental updates.
|
|
12
|
+
*
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const result = queryGateway.subscriptionQuery(GetCourseView, { courseId: "cs-101" })
|
|
15
|
+
* const initial = await result.initialResult
|
|
16
|
+
* console.log("Initial:", initial)
|
|
17
|
+
*
|
|
18
|
+
* for await (const update of result.updates) {
|
|
19
|
+
* console.log("Update:", update)
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export interface SubscriptionQueryResult {
|
|
24
|
+
/** The initial query result (same as a regular query dispatch). */
|
|
25
|
+
readonly initialResult: Promise<unknown>
|
|
26
|
+
/** Stream of incremental updates. Completes when the subscription is closed or completed by the emitter. */
|
|
27
|
+
readonly updates: AsyncIterable<unknown>
|
|
28
|
+
/** Close the subscription and release resources. */
|
|
29
|
+
close(): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Internal update handler — receives updates from emitUpdate() calls
|
|
34
|
+
* and buffers them for the AsyncIterable consumer.
|
|
35
|
+
*/
|
|
36
|
+
export interface UpdateHandler {
|
|
37
|
+
/** The original subscription query message (for filter matching). */
|
|
38
|
+
readonly query: QueryMessage
|
|
39
|
+
/** Offer an update to the buffer. Returns false if buffer is full. */
|
|
40
|
+
offer(update: unknown): boolean
|
|
41
|
+
/** Mark the subscription as complete (no more updates). */
|
|
42
|
+
complete(): void
|
|
43
|
+
/** Mark the subscription as failed. */
|
|
44
|
+
completeExceptionally(error: Error): void
|
|
45
|
+
/** Whether this handler is still active. */
|
|
46
|
+
readonly active: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Resource key for deferred update tasks in ProcessingContext. */
|
|
50
|
+
const UPDATE_TASKS_KEY: ResourceKey<Array<() => void>> = resourceKey("subscriptionQueryUpdateTasks")
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Defers a task to AFTER_COMMIT if a UnitOfWork is active, otherwise runs
|
|
54
|
+
* it immediately.
|
|
55
|
+
*
|
|
56
|
+
* Ensures subscription query updates are only emitted after the
|
|
57
|
+
* transaction commits successfully.
|
|
58
|
+
*
|
|
59
|
+
* UoW presence is decided by ALS state
|
|
60
|
+
* (`processingStateStorage.getStore() !== undefined`) — same precedent as
|
|
61
|
+
* `correlationDataDispatchInterceptor` (Plan 02-03) and `getActiveTransaction`
|
|
62
|
+
* (Plan 02-04). CTX-01 / Plan 03-03: vestigial `_context` parameter removed.
|
|
63
|
+
*/
|
|
64
|
+
export function runAfterCommitOrImmediately(task: () => void): void {
|
|
65
|
+
if (processingStateStorage.getStore() !== undefined) {
|
|
66
|
+
const tasks = computeIfAbsent(UPDATE_TASKS_KEY, () => {
|
|
67
|
+
const list: Array<() => void> = []
|
|
68
|
+
onAfterCommit(() => {
|
|
69
|
+
for (const t of list) t()
|
|
70
|
+
})
|
|
71
|
+
return list
|
|
72
|
+
})
|
|
73
|
+
tasks.push(task)
|
|
74
|
+
} else {
|
|
75
|
+
task()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates a bounded async buffer that implements both UpdateHandler
|
|
81
|
+
* (for the producer side) and provides an AsyncIterable (for the consumer).
|
|
82
|
+
*
|
|
83
|
+
* @param query The subscription query message
|
|
84
|
+
* @param bufferSize Maximum number of buffered updates (default: 256)
|
|
85
|
+
*/
|
|
86
|
+
export function createUpdateHandler(
|
|
87
|
+
query: QueryMessage,
|
|
88
|
+
bufferSize: number = 256,
|
|
89
|
+
): UpdateHandler & { iterable: AsyncIterable<unknown> } {
|
|
90
|
+
const buffer: unknown[] = []
|
|
91
|
+
let completed = false
|
|
92
|
+
let error: Error | undefined
|
|
93
|
+
let waiting: { resolve: (value: IteratorResult<unknown>) => void } | null = null
|
|
94
|
+
|
|
95
|
+
function wake() {
|
|
96
|
+
if (waiting && (buffer.length > 0 || completed)) {
|
|
97
|
+
const w = waiting
|
|
98
|
+
waiting = null
|
|
99
|
+
if (buffer.length > 0) {
|
|
100
|
+
w.resolve({ value: buffer.shift()!, done: false })
|
|
101
|
+
} else {
|
|
102
|
+
w.resolve({ value: undefined, done: true })
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const handler: UpdateHandler & { iterable: AsyncIterable<unknown> } = {
|
|
108
|
+
query,
|
|
109
|
+
|
|
110
|
+
offer(update: unknown): boolean {
|
|
111
|
+
if (completed) return false
|
|
112
|
+
if (buffer.length >= bufferSize) return false
|
|
113
|
+
buffer.push(update)
|
|
114
|
+
wake()
|
|
115
|
+
return true
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
complete() {
|
|
119
|
+
completed = true
|
|
120
|
+
wake()
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
completeExceptionally(err: Error) {
|
|
124
|
+
error = err
|
|
125
|
+
completed = true
|
|
126
|
+
wake()
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
get active() {
|
|
130
|
+
return !completed
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
iterable: {
|
|
134
|
+
[Symbol.asyncIterator]() {
|
|
135
|
+
return {
|
|
136
|
+
next(): Promise<IteratorResult<unknown>> {
|
|
137
|
+
// If there's buffered data, return immediately
|
|
138
|
+
if (buffer.length > 0) {
|
|
139
|
+
return Promise.resolve({ value: buffer.shift()!, done: false })
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// If completed, signal done
|
|
143
|
+
if (completed) {
|
|
144
|
+
if (error) return Promise.reject(error)
|
|
145
|
+
return Promise.resolve({ value: undefined, done: true })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Wait for data or completion
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
waiting = {
|
|
151
|
+
resolve(result) {
|
|
152
|
+
if (error && result.done) {
|
|
153
|
+
reject(error)
|
|
154
|
+
} else {
|
|
155
|
+
resolve(result)
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
return(): Promise<IteratorResult<unknown>> {
|
|
163
|
+
completed = true
|
|
164
|
+
buffer.length = 0
|
|
165
|
+
return Promise.resolve({ value: undefined, done: true })
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return handler
|
|
173
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { TrackingToken } from "./tracking-token.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stores tracking tokens for event processors and manages segment claims.
|
|
5
|
+
*
|
|
6
|
+
* Each processor has a name and one or more segments. The token
|
|
7
|
+
* represents the processor's position in the event stream.
|
|
8
|
+
*
|
|
9
|
+
* Claim management enables distributed processing: multiple instances
|
|
10
|
+
* of the same processor each claim different segments, preventing
|
|
11
|
+
* double-processing.
|
|
12
|
+
*
|
|
13
|
+
* Implementations should participate in the active transaction when
|
|
14
|
+
* available (via `getActiveTransaction()`) so that token updates
|
|
15
|
+
* and projection updates are atomic.
|
|
16
|
+
*/
|
|
17
|
+
export interface TokenStore {
|
|
18
|
+
/**
|
|
19
|
+
* Store the token for a processor segment.
|
|
20
|
+
* Called during PREPARE_COMMIT phase (same transaction as handler work).
|
|
21
|
+
* The caller must own the claim for this segment.
|
|
22
|
+
*/
|
|
23
|
+
store(processorName: string, segment: number, token: TrackingToken): Promise<void>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the current token for a processor segment.
|
|
27
|
+
* Returns undefined if no token has been stored (start from beginning).
|
|
28
|
+
*/
|
|
29
|
+
get(processorName: string, segment: number): Promise<TrackingToken | undefined>
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initialize token segments for a processor if they don't exist yet.
|
|
33
|
+
* Called once during processor startup.
|
|
34
|
+
*
|
|
35
|
+
* @param processorName The processor name
|
|
36
|
+
* @param segmentCount Number of segments to initialize
|
|
37
|
+
*/
|
|
38
|
+
initializeSegments(processorName: string, segmentCount: number): Promise<void>
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Claim a segment for processing. Returns the current token for
|
|
42
|
+
* that segment. Throws `UnableToClaimTokenError` if the segment
|
|
43
|
+
* is already claimed by another instance.
|
|
44
|
+
*
|
|
45
|
+
* @param processorName The processor name
|
|
46
|
+
* @param segment The segment ID to claim
|
|
47
|
+
* @param ownerId Unique identifier of this instance
|
|
48
|
+
*/
|
|
49
|
+
claimToken(processorName: string, segment: number, ownerId: string): Promise<TrackingToken | undefined>
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extend the claim lease for a segment without processing.
|
|
53
|
+
* Called periodically to prevent claim expiry during long batches.
|
|
54
|
+
*/
|
|
55
|
+
extendClaim(processorName: string, segment: number, ownerId: string): Promise<void>
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Release a segment claim, allowing other instances to pick it up.
|
|
59
|
+
*/
|
|
60
|
+
releaseClaim(processorName: string, segment: number, ownerId: string): Promise<void>
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fetch all segment IDs that have tokens for a processor.
|
|
64
|
+
* Used during startup to discover available segments.
|
|
65
|
+
*/
|
|
66
|
+
fetchSegments(processorName: string): Promise<number[]>
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Fetch segment IDs that are not currently claimed by any instance.
|
|
70
|
+
* Used to discover segments available for claiming.
|
|
71
|
+
*/
|
|
72
|
+
fetchAvailableSegments(processorName: string): Promise<number[]>
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Delete the token for a segment (e.g., after merging).
|
|
76
|
+
*/
|
|
77
|
+
deleteToken(processorName: string, segment: number): Promise<void>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Thrown when a segment claim cannot be acquired (already owned by another instance).
|
|
82
|
+
*/
|
|
83
|
+
export class UnableToClaimTokenError extends Error {
|
|
84
|
+
constructor(processorName: string, segment: number) {
|
|
85
|
+
super(`Unable to claim token for processor "${processorName}" segment ${segment}: already claimed`)
|
|
86
|
+
this.name = "UnableToClaimTokenError"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* In-memory token store with claim management.
|
|
92
|
+
* Tokens and claims are lost on restart.
|
|
93
|
+
*
|
|
94
|
+
* @param claimTimeoutMs How long a claim lasts before it can be stolen (default: 10000ms)
|
|
95
|
+
*/
|
|
96
|
+
export function createInMemoryTokenStore(claimTimeoutMs: number = 10000): TokenStore {
|
|
97
|
+
interface TokenEntry {
|
|
98
|
+
token: TrackingToken | undefined
|
|
99
|
+
ownerId: string | null
|
|
100
|
+
claimedAt: number
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const entries = new Map<string, TokenEntry>()
|
|
104
|
+
|
|
105
|
+
function key(processorName: string, segment: number): string {
|
|
106
|
+
return `${processorName}:${segment}`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getEntry(processorName: string, segment: number): TokenEntry | undefined {
|
|
110
|
+
return entries.get(key(processorName, segment))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isClaimExpired(entry: TokenEntry): boolean {
|
|
114
|
+
if (!entry.ownerId) return true
|
|
115
|
+
return Date.now() - entry.claimedAt > claimTimeoutMs
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
async store(processorName, segment, token) {
|
|
120
|
+
const k = key(processorName, segment)
|
|
121
|
+
const entry = entries.get(k)
|
|
122
|
+
if (entry) {
|
|
123
|
+
entry.token = token
|
|
124
|
+
entry.claimedAt = Date.now() // Refresh claim on store
|
|
125
|
+
} else {
|
|
126
|
+
entries.set(k, { token, ownerId: null, claimedAt: Date.now() })
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async get(processorName, segment) {
|
|
131
|
+
return getEntry(processorName, segment)?.token
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
async initializeSegments(processorName, segmentCount) {
|
|
135
|
+
for (let i = 0; i < segmentCount; i++) {
|
|
136
|
+
const k = key(processorName, i)
|
|
137
|
+
if (!entries.has(k)) {
|
|
138
|
+
entries.set(k, { token: undefined, ownerId: null, claimedAt: 0 })
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
async claimToken(processorName, segment, ownerId) {
|
|
144
|
+
const k = key(processorName, segment)
|
|
145
|
+
const entry = entries.get(k)
|
|
146
|
+
|
|
147
|
+
if (!entry) {
|
|
148
|
+
// Segment doesn't exist yet — create and claim
|
|
149
|
+
const newEntry: TokenEntry = { token: undefined, ownerId, claimedAt: Date.now() }
|
|
150
|
+
entries.set(k, newEntry)
|
|
151
|
+
return undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (entry.ownerId === ownerId) {
|
|
155
|
+
// Already owned by this instance — refresh claim
|
|
156
|
+
entry.claimedAt = Date.now()
|
|
157
|
+
return entry.token
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (isClaimExpired(entry)) {
|
|
161
|
+
// Claim expired — steal it
|
|
162
|
+
entry.ownerId = ownerId
|
|
163
|
+
entry.claimedAt = Date.now()
|
|
164
|
+
return entry.token
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw new UnableToClaimTokenError(processorName, segment)
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async extendClaim(processorName, segment, ownerId) {
|
|
171
|
+
const entry = getEntry(processorName, segment)
|
|
172
|
+
if (entry && entry.ownerId === ownerId) {
|
|
173
|
+
entry.claimedAt = Date.now()
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
async releaseClaim(processorName, segment, ownerId) {
|
|
178
|
+
const entry = getEntry(processorName, segment)
|
|
179
|
+
if (entry && entry.ownerId === ownerId) {
|
|
180
|
+
entry.ownerId = null
|
|
181
|
+
entry.claimedAt = 0
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
async fetchSegments(processorName) {
|
|
186
|
+
const segments: number[] = []
|
|
187
|
+
const prefix = `${processorName}:`
|
|
188
|
+
for (const k of entries.keys()) {
|
|
189
|
+
if (k.startsWith(prefix)) {
|
|
190
|
+
segments.push(parseInt(k.slice(prefix.length), 10))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return segments.sort((a, b) => a - b)
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
async fetchAvailableSegments(processorName) {
|
|
197
|
+
const segments: number[] = []
|
|
198
|
+
const prefix = `${processorName}:`
|
|
199
|
+
for (const [k, entry] of entries) {
|
|
200
|
+
if (k.startsWith(prefix) && (!entry.ownerId || isClaimExpired(entry))) {
|
|
201
|
+
segments.push(parseInt(k.slice(prefix.length), 10))
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return segments.sort((a, b) => a - b)
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
async deleteToken(processorName, segment) {
|
|
208
|
+
entries.delete(key(processorName, segment))
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CommandBus } from "./command-bus.js"
|
|
2
|
+
import type { CommandMessage } from "./message.js"
|
|
3
|
+
import type { SpanFactory } from "./span-factory.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A {@link CommandBus} decorator that wraps dispatch and handler invocations
|
|
7
|
+
* with tracing spans.
|
|
8
|
+
*
|
|
9
|
+
* Dispatch creates a "dispatch" span and propagates trace context into the
|
|
10
|
+
* message metadata. Subscribe wraps each handler with a "handle" span.
|
|
11
|
+
*
|
|
12
|
+
*
|
|
13
|
+
* @param delegate The underlying command bus to decorate.
|
|
14
|
+
* @param spanFactory The span factory for creating tracing spans.
|
|
15
|
+
* @returns A decorated command bus with tracing instrumentation.
|
|
16
|
+
*/
|
|
17
|
+
export function createTracingCommandBus(
|
|
18
|
+
delegate: CommandBus,
|
|
19
|
+
spanFactory: SpanFactory,
|
|
20
|
+
): CommandBus {
|
|
21
|
+
return {
|
|
22
|
+
async dispatch(message: CommandMessage): Promise<unknown> {
|
|
23
|
+
const span = spanFactory.createDispatchSpan(`dispatch(${String(message.name)})`, message).start()
|
|
24
|
+
try {
|
|
25
|
+
const propagated = spanFactory.propagateContext(message)
|
|
26
|
+
const result = await delegate.dispatch(propagated)
|
|
27
|
+
span.end()
|
|
28
|
+
return result
|
|
29
|
+
} catch (err) {
|
|
30
|
+
span.recordException(err instanceof Error ? err : new Error(String(err)))
|
|
31
|
+
throw err
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
subscribe(
|
|
36
|
+
commandName: string,
|
|
37
|
+
handler: (message: CommandMessage) => Promise<unknown>,
|
|
38
|
+
): void {
|
|
39
|
+
delegate.subscribe(commandName, async (msg: CommandMessage) => {
|
|
40
|
+
const span = spanFactory.createHandlerSpan(`handle(${commandName})`, msg).start()
|
|
41
|
+
try {
|
|
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
|
+
})
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { HandlerEnhancerDefinition, HandlerMetadata } from "./handler-enhancer.js"
|
|
2
|
+
import type { SpanFactory } from "./span-factory.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handler enhancer that wraps message handler invocations with tracing spans.
|
|
6
|
+
*
|
|
7
|
+
* Creates an internal span per handler invocation, recording the handler
|
|
8
|
+
* name and message type as context. Errors are recorded on the span.
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
export function tracingHandlerEnhancerDefinition(
|
|
12
|
+
spanFactory: SpanFactory,
|
|
13
|
+
): HandlerEnhancerDefinition {
|
|
14
|
+
return {
|
|
15
|
+
wrapHandler<T extends (...args: any[]) => any>(
|
|
16
|
+
handler: T,
|
|
17
|
+
metadata: HandlerMetadata,
|
|
18
|
+
): T {
|
|
19
|
+
const spanName = `${metadata.handlerGroup}.${metadata.messageName}`
|
|
20
|
+
|
|
21
|
+
return (async (...args: any[]) => {
|
|
22
|
+
const span = spanFactory.createInternalSpan(spanName).start()
|
|
23
|
+
try {
|
|
24
|
+
const result = await handler(...args)
|
|
25
|
+
span.end()
|
|
26
|
+
return result
|
|
27
|
+
} catch (error) {
|
|
28
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)))
|
|
29
|
+
throw error
|
|
30
|
+
}
|
|
31
|
+
}) as unknown as T
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
}
|