@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.
Files changed (266) hide show
  1. package/dist/command-bus.d.ts +30 -0
  2. package/dist/command-bus.d.ts.map +1 -0
  3. package/dist/command-bus.js +2 -0
  4. package/dist/command-bus.js.map +1 -0
  5. package/dist/command-handler.d.ts +58 -0
  6. package/dist/command-handler.d.ts.map +1 -0
  7. package/dist/command-handler.js +12 -0
  8. package/dist/command-handler.js.map +1 -0
  9. package/dist/command-handling-module.d.ts +53 -0
  10. package/dist/command-handling-module.d.ts.map +1 -0
  11. package/dist/command-handling-module.js +130 -0
  12. package/dist/command-handling-module.js.map +1 -0
  13. package/dist/correlation-data.d.ts +79 -0
  14. package/dist/correlation-data.d.ts.map +1 -0
  15. package/dist/correlation-data.js +133 -0
  16. package/dist/correlation-data.js.map +1 -0
  17. package/dist/dead-letter-queue.d.ts +134 -0
  18. package/dist/dead-letter-queue.d.ts.map +1 -0
  19. package/dist/dead-letter-queue.js +176 -0
  20. package/dist/dead-letter-queue.js.map +1 -0
  21. package/dist/dead-lettering-handler.d.ts +42 -0
  22. package/dist/dead-lettering-handler.d.ts.map +1 -0
  23. package/dist/dead-lettering-handler.js +67 -0
  24. package/dist/dead-lettering-handler.js.map +1 -0
  25. package/dist/descriptor.d.ts +135 -0
  26. package/dist/descriptor.d.ts.map +1 -0
  27. package/dist/descriptor.js +36 -0
  28. package/dist/descriptor.js.map +1 -0
  29. package/dist/emit-update.d.ts +22 -0
  30. package/dist/emit-update.d.ts.map +1 -0
  31. package/dist/emit-update.js +23 -0
  32. package/dist/emit-update.js.map +1 -0
  33. package/dist/event-bus.d.ts +29 -0
  34. package/dist/event-bus.d.ts.map +1 -0
  35. package/dist/event-bus.js +22 -0
  36. package/dist/event-bus.js.map +1 -0
  37. package/dist/event-criteria.d.ts +87 -0
  38. package/dist/event-criteria.d.ts.map +1 -0
  39. package/dist/event-criteria.js +90 -0
  40. package/dist/event-criteria.js.map +1 -0
  41. package/dist/event-gateway.d.ts +19 -0
  42. package/dist/event-gateway.d.ts.map +1 -0
  43. package/dist/event-gateway.js +22 -0
  44. package/dist/event-gateway.js.map +1 -0
  45. package/dist/event-handler.d.ts +30 -0
  46. package/dist/event-handler.d.ts.map +1 -0
  47. package/dist/event-handler.js +18 -0
  48. package/dist/event-handler.js.map +1 -0
  49. package/dist/event-processor-builder.d.ts +148 -0
  50. package/dist/event-processor-builder.d.ts.map +1 -0
  51. package/dist/event-processor-builder.js +175 -0
  52. package/dist/event-processor-builder.js.map +1 -0
  53. package/dist/event-processor.d.ts +10 -0
  54. package/dist/event-processor.d.ts.map +1 -0
  55. package/dist/event-processor.js +2 -0
  56. package/dist/event-processor.js.map +1 -0
  57. package/dist/event-sink.d.ts +23 -0
  58. package/dist/event-sink.d.ts.map +1 -0
  59. package/dist/event-sink.js +2 -0
  60. package/dist/event-sink.js.map +1 -0
  61. package/dist/event-source.d.ts +98 -0
  62. package/dist/event-source.d.ts.map +1 -0
  63. package/dist/event-source.js +191 -0
  64. package/dist/event-source.js.map +1 -0
  65. package/dist/gateway.d.ts +68 -0
  66. package/dist/gateway.d.ts.map +1 -0
  67. package/dist/gateway.js +62 -0
  68. package/dist/gateway.js.map +1 -0
  69. package/dist/handler-enhancer.d.ts +53 -0
  70. package/dist/handler-enhancer.d.ts.map +1 -0
  71. package/dist/handler-enhancer.js +17 -0
  72. package/dist/handler-enhancer.js.map +1 -0
  73. package/dist/handler.d.ts +51 -0
  74. package/dist/handler.d.ts.map +1 -0
  75. package/dist/handler.js +26 -0
  76. package/dist/handler.js.map +1 -0
  77. package/dist/index.d.ts +53 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +103 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/intercepting-command-bus.d.ts +17 -0
  82. package/dist/intercepting-command-bus.d.ts.map +1 -0
  83. package/dist/intercepting-command-bus.js +54 -0
  84. package/dist/intercepting-command-bus.js.map +1 -0
  85. package/dist/intercepting-event-bus.d.ts +8 -0
  86. package/dist/intercepting-event-bus.d.ts.map +1 -0
  87. package/dist/intercepting-event-bus.js +22 -0
  88. package/dist/intercepting-event-bus.js.map +1 -0
  89. package/dist/intercepting-query-bus.d.ts +17 -0
  90. package/dist/intercepting-query-bus.d.ts.map +1 -0
  91. package/dist/intercepting-query-bus.js +68 -0
  92. package/dist/intercepting-query-bus.js.map +1 -0
  93. package/dist/interceptor.d.ts +46 -0
  94. package/dist/interceptor.d.ts.map +1 -0
  95. package/dist/interceptor.js +2 -0
  96. package/dist/interceptor.js.map +1 -0
  97. package/dist/message-monitor-registry.d.ts +28 -0
  98. package/dist/message-monitor-registry.d.ts.map +1 -0
  99. package/dist/message-monitor-registry.js +37 -0
  100. package/dist/message-monitor-registry.js.map +1 -0
  101. package/dist/message-monitor.d.ts +36 -0
  102. package/dist/message-monitor.d.ts.map +1 -0
  103. package/dist/message-monitor.js +39 -0
  104. package/dist/message-monitor.js.map +1 -0
  105. package/dist/message.d.ts +42 -0
  106. package/dist/message.d.ts.map +1 -0
  107. package/dist/message.js +2 -0
  108. package/dist/message.js.map +1 -0
  109. package/dist/processing-state.d.ts +115 -0
  110. package/dist/processing-state.d.ts.map +1 -0
  111. package/dist/processing-state.js +205 -0
  112. package/dist/processing-state.js.map +1 -0
  113. package/dist/processor-configuration.d.ts +51 -0
  114. package/dist/processor-configuration.d.ts.map +1 -0
  115. package/dist/processor-configuration.js +2 -0
  116. package/dist/processor-configuration.js.map +1 -0
  117. package/dist/query-bus.d.ts +51 -0
  118. package/dist/query-bus.d.ts.map +1 -0
  119. package/dist/query-bus.js +2 -0
  120. package/dist/query-bus.js.map +1 -0
  121. package/dist/query-handler.d.ts +35 -0
  122. package/dist/query-handler.d.ts.map +1 -0
  123. package/dist/query-handler.js +19 -0
  124. package/dist/query-handler.js.map +1 -0
  125. package/dist/query-handling-module.d.ts +24 -0
  126. package/dist/query-handling-module.d.ts.map +1 -0
  127. package/dist/query-handling-module.js +32 -0
  128. package/dist/query-handling-module.js.map +1 -0
  129. package/dist/replay-token.d.ts +31 -0
  130. package/dist/replay-token.d.ts.map +1 -0
  131. package/dist/replay-token.js +37 -0
  132. package/dist/replay-token.js.map +1 -0
  133. package/dist/retrying-command-bus.d.ts +32 -0
  134. package/dist/retrying-command-bus.d.ts.map +1 -0
  135. package/dist/retrying-command-bus.js +58 -0
  136. package/dist/retrying-command-bus.js.map +1 -0
  137. package/dist/routing-strategy.d.ts +30 -0
  138. package/dist/routing-strategy.d.ts.map +1 -0
  139. package/dist/routing-strategy.js +37 -0
  140. package/dist/routing-strategy.js.map +1 -0
  141. package/dist/segment.d.ts +72 -0
  142. package/dist/segment.d.ts.map +1 -0
  143. package/dist/segment.js +103 -0
  144. package/dist/segment.js.map +1 -0
  145. package/dist/send.d.ts +28 -0
  146. package/dist/send.d.ts.map +1 -0
  147. package/dist/send.js +36 -0
  148. package/dist/send.js.map +1 -0
  149. package/dist/serializer.d.ts +40 -0
  150. package/dist/serializer.d.ts.map +1 -0
  151. package/dist/serializer.js +90 -0
  152. package/dist/serializer.js.map +1 -0
  153. package/dist/simple-command-bus.d.ts +23 -0
  154. package/dist/simple-command-bus.d.ts.map +1 -0
  155. package/dist/simple-command-bus.js +49 -0
  156. package/dist/simple-command-bus.js.map +1 -0
  157. package/dist/simple-query-bus.d.ts +16 -0
  158. package/dist/simple-query-bus.d.ts.map +1 -0
  159. package/dist/simple-query-bus.js +122 -0
  160. package/dist/simple-query-bus.js.map +1 -0
  161. package/dist/span-factory.d.ts +58 -0
  162. package/dist/span-factory.d.ts.map +1 -0
  163. package/dist/span-factory.js +19 -0
  164. package/dist/span-factory.js.map +1 -0
  165. package/dist/streaming-event-processor.d.ts +65 -0
  166. package/dist/streaming-event-processor.d.ts.map +1 -0
  167. package/dist/streaming-event-processor.js +239 -0
  168. package/dist/streaming-event-processor.js.map +1 -0
  169. package/dist/subscribing-event-processor.d.ts +57 -0
  170. package/dist/subscribing-event-processor.d.ts.map +1 -0
  171. package/dist/subscribing-event-processor.js +100 -0
  172. package/dist/subscribing-event-processor.js.map +1 -0
  173. package/dist/subscription-query.d.ts +63 -0
  174. package/dist/subscription-query.d.ts.map +1 -0
  175. package/dist/subscription-query.js +119 -0
  176. package/dist/subscription-query.js.map +1 -0
  177. package/dist/token-store.d.ts +83 -0
  178. package/dist/token-store.d.ts.map +1 -0
  179. package/dist/token-store.js +112 -0
  180. package/dist/token-store.js.map +1 -0
  181. package/dist/tracing-command-bus.d.ts +16 -0
  182. package/dist/tracing-command-bus.d.ts.map +1 -0
  183. package/dist/tracing-command-bus.js +44 -0
  184. package/dist/tracing-command-bus.js.map +1 -0
  185. package/dist/tracing-handler-enhancer.d.ts +11 -0
  186. package/dist/tracing-handler-enhancer.d.ts.map +1 -0
  187. package/dist/tracing-handler-enhancer.js +27 -0
  188. package/dist/tracing-handler-enhancer.js.map +1 -0
  189. package/dist/tracking-event-processor.d.ts +72 -0
  190. package/dist/tracking-event-processor.d.ts.map +1 -0
  191. package/dist/tracking-event-processor.js +223 -0
  192. package/dist/tracking-event-processor.js.map +1 -0
  193. package/dist/tracking-token.d.ts +120 -0
  194. package/dist/tracking-token.d.ts.map +1 -0
  195. package/dist/tracking-token.js +132 -0
  196. package/dist/tracking-token.js.map +1 -0
  197. package/dist/transaction.d.ts +60 -0
  198. package/dist/transaction.d.ts.map +1 -0
  199. package/dist/transaction.js +74 -0
  200. package/dist/transaction.js.map +1 -0
  201. package/dist/unit-of-work.d.ts +41 -0
  202. package/dist/unit-of-work.d.ts.map +1 -0
  203. package/dist/unit-of-work.js +96 -0
  204. package/dist/unit-of-work.js.map +1 -0
  205. package/dist/upcaster.d.ts +91 -0
  206. package/dist/upcaster.d.ts.map +1 -0
  207. package/dist/upcaster.js +114 -0
  208. package/dist/upcaster.js.map +1 -0
  209. package/dist/with-namespace.d.ts +59 -0
  210. package/dist/with-namespace.d.ts.map +1 -0
  211. package/dist/with-namespace.js +42 -0
  212. package/dist/with-namespace.js.map +1 -0
  213. package/package.json +65 -0
  214. package/src/command-bus.ts +34 -0
  215. package/src/command-handler.ts +116 -0
  216. package/src/command-handling-module.ts +183 -0
  217. package/src/correlation-data.ts +169 -0
  218. package/src/dead-letter-queue.ts +330 -0
  219. package/src/dead-lettering-handler.ts +109 -0
  220. package/src/descriptor.ts +176 -0
  221. package/src/emit-update.ts +35 -0
  222. package/src/event-bus.ts +45 -0
  223. package/src/event-criteria.ts +141 -0
  224. package/src/event-gateway.ts +42 -0
  225. package/src/event-handler.ts +44 -0
  226. package/src/event-processor-builder.ts +246 -0
  227. package/src/event-processor.ts +9 -0
  228. package/src/event-sink.ts +23 -0
  229. package/src/event-source.ts +301 -0
  230. package/src/gateway.ts +144 -0
  231. package/src/handler-enhancer.ts +70 -0
  232. package/src/handler.ts +133 -0
  233. package/src/index.ts +356 -0
  234. package/src/intercepting-command-bus.ts +73 -0
  235. package/src/intercepting-event-bus.ts +29 -0
  236. package/src/intercepting-query-bus.ts +104 -0
  237. package/src/interceptor.ts +48 -0
  238. package/src/message-monitor-registry.ts +64 -0
  239. package/src/message-monitor.ts +68 -0
  240. package/src/message.ts +41 -0
  241. package/src/processing-state.ts +258 -0
  242. package/src/processor-configuration.ts +59 -0
  243. package/src/query-bus.ts +69 -0
  244. package/src/query-handler.ts +49 -0
  245. package/src/query-handling-module.ts +44 -0
  246. package/src/replay-token.ts +53 -0
  247. package/src/retrying-command-bus.ts +80 -0
  248. package/src/routing-strategy.ts +59 -0
  249. package/src/segment.ts +136 -0
  250. package/src/send.ts +44 -0
  251. package/src/serializer.ts +122 -0
  252. package/src/simple-command-bus.ts +59 -0
  253. package/src/simple-query-bus.ts +158 -0
  254. package/src/span-factory.ts +81 -0
  255. package/src/streaming-event-processor.ts +351 -0
  256. package/src/subscribing-event-processor.ts +169 -0
  257. package/src/subscription-query.ts +173 -0
  258. package/src/token-store.ts +211 -0
  259. package/src/tracing-command-bus.ts +52 -0
  260. package/src/tracing-handler-enhancer.ts +34 -0
  261. package/src/tracking-event-processor.ts +336 -0
  262. package/src/tracking-token.ts +231 -0
  263. package/src/transaction.ts +98 -0
  264. package/src/unit-of-work.ts +138 -0
  265. package/src/upcaster.ts +174 -0
  266. 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
+ }