@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,68 @@
1
+ import type { Message } from "./message.js"
2
+
3
+ /**
4
+ * Callback returned by a MessageMonitor when a message is ingested.
5
+ * Call `reportSuccess()` or `reportFailure()` when processing completes.
6
+ */
7
+ export interface MonitorCallback {
8
+ /** Report that the message was processed successfully. */
9
+ reportSuccess(): void
10
+ /** Report that processing failed. */
11
+ reportFailure(error?: Error): void
12
+ }
13
+
14
+ /**
15
+ * Monitors message processing — observes ingestion, success, and failure
16
+ * of messages flowing through the framework.
17
+ *
18
+ * Used by tracing, metrics, and custom observability extensions.
19
+ * Monitors are registered via the kronos() App messageMonitorRegistry slot
20
+ * (Phase 8 reshape — was previously the MessagingConfigurer surface).
21
+ *
22
+ */
23
+ export interface MessageMonitor<M extends Message = Message> {
24
+ /**
25
+ * Called when a message enters processing.
26
+ * Returns a callback to report the outcome.
27
+ */
28
+ onMessageIngested(message: M): MonitorCallback
29
+ }
30
+
31
+ /**
32
+ * A no-op monitor that does nothing. Default when no monitors are registered.
33
+ */
34
+ export function noOpMessageMonitor<M extends Message = Message>(): MessageMonitor<M> {
35
+ const noOpCallback: MonitorCallback = {
36
+ reportSuccess() {},
37
+ reportFailure() {},
38
+ }
39
+ return {
40
+ onMessageIngested(): MonitorCallback {
41
+ return noOpCallback
42
+ },
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Combines multiple monitors into one. Each monitor is called for every message.
48
+ */
49
+ export function multiMessageMonitor<M extends Message = Message>(
50
+ monitors: ReadonlyArray<MessageMonitor<M>>,
51
+ ): MessageMonitor<M> {
52
+ if (monitors.length === 0) return noOpMessageMonitor()
53
+ if (monitors.length === 1) return monitors[0]!
54
+
55
+ return {
56
+ onMessageIngested(message: M): MonitorCallback {
57
+ const callbacks = monitors.map(m => m.onMessageIngested(message))
58
+ return {
59
+ reportSuccess() {
60
+ for (const cb of callbacks) cb.reportSuccess()
61
+ },
62
+ reportFailure(error?: Error) {
63
+ for (const cb of callbacks) cb.reportFailure(error)
64
+ },
65
+ }
66
+ },
67
+ }
68
+ }
package/src/message.ts ADDED
@@ -0,0 +1,41 @@
1
+ import type { QualifiedName, Metadata } from "@kronos-ts/common"
2
+
3
+ /**
4
+ * A message carrying a payload and metadata, identified by a unique ID
5
+ * and routed by its qualified name.
6
+ */
7
+ export interface Message<P = unknown> {
8
+ readonly identifier: string
9
+ readonly name: QualifiedName
10
+ readonly payload: P
11
+ readonly metadata: Metadata
12
+ readonly timestamp: number
13
+ }
14
+
15
+ /**
16
+ * A command message — dispatched to exactly one handler, may return a result.
17
+ */
18
+ export interface CommandMessage<P = unknown> extends Message<P> {}
19
+
20
+ /**
21
+ * A command result message — the response from handling a command.
22
+ */
23
+ export interface CommandResultMessage<R = unknown> {
24
+ readonly identifier: string
25
+ readonly payload: R | undefined
26
+ readonly metadata: Metadata
27
+ readonly error?: Error
28
+ }
29
+
30
+ /**
31
+ * An event message — published to all interested handlers.
32
+ */
33
+ export interface EventMessage<P = unknown> extends Message<P> {
34
+ readonly version: string
35
+ readonly tags: ReadonlyArray<{ readonly key: string; readonly value: string }>
36
+ }
37
+
38
+ /**
39
+ * A query message — dispatched to handler(s) that can answer it.
40
+ */
41
+ export interface QueryMessage<P = unknown> extends Message<P> {}
@@ -0,0 +1,258 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks"
2
+ import type { Metadata, ResourceKey } from "@kronos-ts/common"
3
+
4
+ /**
5
+ * Lifecycle phases for message processing, ordered by execution priority.
6
+ *
7
+ * Phases execute in ascending order. Actions within the same phase
8
+ * execute in registration order. Actions registered during execution
9
+ * (e.g., onPrepareCommit from a handler) are picked up when their
10
+ * phase comes.
11
+ *
12
+ * Plan 03-04 (CTX-04 / D-34): Phase enum relocated here from the
13
+ * deleted processing-context.ts. The numeric values are stable —
14
+ * external code (handler enhancers, processors) compares phase numbers.
15
+ */
16
+ export const Phase = {
17
+ /** Setup before handler invocation (e.g., transaction start). */
18
+ PRE_INVOCATION: -10000,
19
+ /** Actual handler execution. */
20
+ INVOCATION: 0,
21
+ /** Cleanup after handler, before commit. */
22
+ POST_INVOCATION: 10000,
23
+ /** Prepare for commit (e.g., event store flush, token store). */
24
+ PREPARE_COMMIT: 20000,
25
+ /** Actual commit (e.g., database transaction commit). */
26
+ COMMIT: 30000,
27
+ /** Post-commit notifications (e.g., subscription query updates). */
28
+ AFTER_COMMIT: 40000,
29
+ } as const
30
+
31
+ export type PhaseValue = (typeof Phase)[keyof typeof Phase]
32
+
33
+ // D-13: NOT exported.
34
+ type PhaseAction = () => Promise<void> | void
35
+ type ErrorHandler = (error: unknown, phase?: PhaseValue) => Promise<void> | void
36
+ type CompleteHandler = () => void
37
+
38
+ type Status = "not_started" | "started" | "completed" | "error"
39
+
40
+ type InternalProcessingState = {
41
+ resources: Map<symbol, unknown>
42
+ phaseActions: Map<PhaseValue, PhaseAction[]>
43
+ errorHandlers: ErrorHandler[]
44
+ completeHandlers: CompleteHandler[]
45
+ currentPhase: PhaseValue | null
46
+ status: Status
47
+ metadata: Metadata
48
+ }
49
+
50
+ // D-04: Framework-wide ALS instance. Exported (deep-path only per D-05).
51
+ export const processingStateStorage = new AsyncLocalStorage<InternalProcessingState>()
52
+
53
+ // D-10: stable error name.
54
+ export class NoActiveUnitOfWork extends Error {
55
+ constructor(message = "No active UnitOfWork: accessor called outside processingStateStorage.run()") {
56
+ super(message)
57
+ this.name = "NoActiveUnitOfWork"
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Plan 04-01 (HDL-02 / D-43): thrown by mutating helpers (append, send, emitUpdate)
63
+ * when called outside the INVOCATION phase. Distinct class (not a NoActiveUnitOfWork
64
+ * subclass) — a UoW IS active, but it's in the wrong phase. Catches the real bug
65
+ * pattern of mutations from lifecycle hooks (onCommit, onPrepareCommit, etc.).
66
+ */
67
+ export class WrongUoWPhase extends Error {
68
+ readonly currentPhase: PhaseValue | null
69
+ constructor(currentPhase: PhaseValue | null) {
70
+ super(
71
+ `Mutating helper called during phase ${currentPhase} — must be called during INVOCATION (${Phase.INVOCATION}) only. ` +
72
+ `Do not call append/send/emitUpdate from lifecycle hooks (onPrepareCommit, onCommit, onAfterCommit, onError, whenComplete).`
73
+ )
74
+ this.name = "WrongUoWPhase"
75
+ this.currentPhase = currentPhase
76
+ }
77
+ }
78
+
79
+ function requireState(): InternalProcessingState {
80
+ const state = processingStateStorage.getStore()
81
+ if (state === undefined) throw new NoActiveUnitOfWork()
82
+ return state
83
+ }
84
+
85
+ /**
86
+ * Plan 04-01 (HDL-02 / D-43): mutator guard. Throws NoActiveUnitOfWork outside a UoW;
87
+ * throws WrongUoWPhase when active phase != INVOCATION; otherwise returns the state.
88
+ * Used by append (eventsourcing), send + emitUpdate (messaging).
89
+ * Internal — NOT exported from index.ts barrel. Consumed via deep-path import.
90
+ */
91
+ export function requireInvocationPhase(): InternalProcessingState {
92
+ const state = processingStateStorage.getStore()
93
+ if (state === undefined) throw new NoActiveUnitOfWork()
94
+ if (state.currentPhase !== Phase.INVOCATION) throw new WrongUoWPhase(state.currentPhase)
95
+ return state
96
+ }
97
+
98
+ // ── Resource accessors ──────────────────────────────────────────────────
99
+
100
+ export function getResource<T>(key: ResourceKey<T>): T | undefined {
101
+ const state = requireState()
102
+ return state.resources.get(key.symbol) as T | undefined
103
+ }
104
+
105
+ export function setResource<T>(key: ResourceKey<T>, value: T): T | undefined {
106
+ const state = requireState()
107
+ const previous = state.resources.get(key.symbol) as T | undefined
108
+ state.resources.set(key.symbol, value)
109
+ return previous
110
+ }
111
+
112
+ export function computeIfAbsent<T>(key: ResourceKey<T>, supplier: () => T): T {
113
+ const state = requireState()
114
+ const existing = state.resources.get(key.symbol)
115
+ if (existing !== undefined) return existing as T
116
+ const value = supplier()
117
+ state.resources.set(key.symbol, value)
118
+ return value
119
+ }
120
+
121
+ export function removeResource<T>(key: ResourceKey<T>): T | undefined {
122
+ const state = requireState()
123
+ const previous = state.resources.get(key.symbol) as T | undefined
124
+ state.resources.delete(key.symbol)
125
+ return previous
126
+ }
127
+
128
+ export function hasResource<T>(key: ResourceKey<T>): boolean {
129
+ const state = requireState()
130
+ return state.resources.has(key.symbol)
131
+ }
132
+
133
+ export function updateResource<T>(
134
+ key: ResourceKey<T>,
135
+ updater: (current: T | undefined) => T,
136
+ ): T {
137
+ const state = requireState()
138
+ const current = state.resources.get(key.symbol) as T | undefined
139
+ const updated = updater(current)
140
+ state.resources.set(key.symbol, updated)
141
+ return updated
142
+ }
143
+
144
+ // ── Lifecycle registration ──────────────────────────────────────────────
145
+
146
+ export function registerPhaseAction(phase: PhaseValue, action: PhaseAction): void {
147
+ const state = requireState()
148
+ let bucket = state.phaseActions.get(phase)
149
+ if (!bucket) {
150
+ bucket = []
151
+ state.phaseActions.set(phase, bucket)
152
+ }
153
+ bucket.push(action)
154
+ }
155
+
156
+ export function registerErrorHandler(handler: ErrorHandler): void {
157
+ const state = requireState()
158
+ state.errorHandlers.push(handler)
159
+ }
160
+
161
+ export function registerCompleteHandler(handler: CompleteHandler): void {
162
+ const state = requireState()
163
+ state.completeHandlers.push(handler)
164
+ }
165
+
166
+ // ── Lifecycle accessors (CTX-03, D-30) ──────────────────────────────────
167
+ //
168
+ // Module-level equivalents of ctx.on / ctx.onError / ctx.whenComplete /
169
+ // ctx.onPrepareCommit / ctx.onCommit / ctx.onAfterCommit. Thin wrappers
170
+ // over the Phase 1 accessors (registerPhaseAction / registerErrorHandler /
171
+ // registerCompleteHandler). Same fail-fast contract (D-31): throw
172
+ // NoActiveUnitOfWork outside an active processingStateStorage.run.
173
+
174
+ export function on(phase: PhaseValue, action: PhaseAction): void {
175
+ registerPhaseAction(phase, action)
176
+ }
177
+
178
+ export function onPrepareCommit(action: PhaseAction): void {
179
+ registerPhaseAction(Phase.PREPARE_COMMIT, action)
180
+ }
181
+
182
+ export function onCommit(action: PhaseAction): void {
183
+ registerPhaseAction(Phase.COMMIT, action)
184
+ }
185
+
186
+ export function onAfterCommit(action: PhaseAction): void {
187
+ registerPhaseAction(Phase.AFTER_COMMIT, action)
188
+ }
189
+
190
+ export function onError(handler: ErrorHandler): void {
191
+ registerErrorHandler(handler)
192
+ }
193
+
194
+ export function whenComplete(handler: CompleteHandler): void {
195
+ registerCompleteHandler(handler)
196
+ }
197
+
198
+ // ── withOverride (D-07..D-09) ──────────────────────────────────────────
199
+
200
+ /**
201
+ * Run `fn` in a nested processingStateStorage context where `key` resolves to `value`.
202
+ *
203
+ * Only the `resources` Map is forked (D-08). All other fields — phaseActions,
204
+ * errorHandlers, completeHandlers, status, currentPhase, metadata — are SHARED
205
+ * references to the parent state. Registering a phase action or error handler
206
+ * inside `fn` targets the parent's lifecycle.
207
+ *
208
+ * After `fn` resolves, the parent's resources are unchanged (D-09) — nested
209
+ * `storage.run()` naturally pops the forked state.
210
+ *
211
+ * Throws NoActiveUnitOfWork if called outside an existing processingStateStorage.run().
212
+ */
213
+ export function withOverride<T, R>(
214
+ key: ResourceKey<T>,
215
+ value: T,
216
+ fn: () => Promise<R>,
217
+ ): Promise<R> {
218
+ const parent = requireState()
219
+ const forkedResources = new Map(parent.resources)
220
+ forkedResources.set(key.symbol, value)
221
+
222
+ const forkedState: InternalProcessingState = {
223
+ resources: forkedResources,
224
+ // SHARED references to parent (D-08):
225
+ phaseActions: parent.phaseActions,
226
+ errorHandlers: parent.errorHandlers,
227
+ completeHandlers: parent.completeHandlers,
228
+ status: parent.status,
229
+ currentPhase: parent.currentPhase,
230
+ metadata: parent.metadata,
231
+ }
232
+
233
+ return processingStateStorage.run(forkedState, fn)
234
+ }
235
+
236
+ /**
237
+ * INTERNAL — used by UnitOfWork to construct the initial state passed to
238
+ * processingStateStorage.run(). The InternalProcessingState type is intentionally
239
+ * non-exported (D-13); this factory is the sanctioned construction point.
240
+ * Deep-path import only; NOT re-exported from the package barrel (D-05).
241
+ *
242
+ * Return type is intentionally INFERRED (no `: InternalProcessingState` annotation).
243
+ * Annotating the return type with a non-exported name produces TS4023 under
244
+ * `declaration: true` / `isolatedDeclarations`, breaking the package build.
245
+ * Inference preserves D-13 (the type remains non-exported) while allowing `.d.ts`
246
+ * emission.
247
+ */
248
+ export function createInitialProcessingState(metadata: Metadata) {
249
+ return {
250
+ resources: new Map<symbol, unknown>(),
251
+ phaseActions: new Map<PhaseValue, PhaseAction[]>(),
252
+ errorHandlers: [] as ErrorHandler[],
253
+ completeHandlers: [] as CompleteHandler[],
254
+ currentPhase: null as PhaseValue | null,
255
+ status: "not_started" as Status,
256
+ metadata,
257
+ }
258
+ }
@@ -0,0 +1,59 @@
1
+ import type { TokenStore } from "./token-store.js"
2
+ import type { UoWRunner } from "./unit-of-work.js"
3
+ import type { EventProcessingErrorHandler } from "./tracking-event-processor.js"
4
+ import type { SequencedDeadLetterQueue } from "./dead-letter-queue.js"
5
+
6
+ /**
7
+ * Configuration for an individual event processor.
8
+ *
9
+ * All properties are optional — when not specified, the processor
10
+ * uses the global defaults from the messaging configurer.
11
+ *
12
+ * This enables per-processor tuning: different processors can have
13
+ * different batch sizes, token stores, error handlers, etc.
14
+ *
15
+ * ```typescript
16
+ * messagingConfigurer({
17
+ * eventHandlers: [courseProjection],
18
+ * processorConfiguration: {
19
+ * "course-projection": {
20
+ * batchSize: 50,
21
+ * initialSegmentCount: 4,
22
+ * errorHandler: propagatingErrorHandler(),
23
+ * },
24
+ * },
25
+ * })
26
+ * ```
27
+ */
28
+ /**
29
+ * @deprecated Use `trackingProcessor()` / `subscribingProcessor()` builders
30
+ * with `registerEventProcessor()` instead.
31
+ */
32
+ export interface ProcessorConfiguration {
33
+ /** Events per transaction. Default: 100 */
34
+ batchSize?: number
35
+
36
+ /** Number of segments to create on first startup. Default: 1 */
37
+ initialSegmentCount?: number
38
+
39
+ /** Polling interval in ms (for TrackingEventProcessor). Default: 500 */
40
+ pollingIntervalMs?: number
41
+
42
+ /** Override the token store for this processor. */
43
+ tokenStore?: TokenStore
44
+
45
+ /** Override the UnitOfWork runner for this processor. */
46
+ unitOfWorkRunner?: UoWRunner
47
+
48
+ /** Override the error handler for this processor. */
49
+ errorHandler?: EventProcessingErrorHandler
50
+
51
+ /** Dead letter queue for this processor. */
52
+ deadLetterQueue?: SequencedDeadLetterQueue
53
+
54
+ /** How often to extend claims in ms. Default: 5000 */
55
+ claimExtensionThresholdMs?: number
56
+
57
+ /** How often to attempt claiming new segments in ms. Default: 5000 */
58
+ tokenClaimIntervalMs?: number
59
+ }
@@ -0,0 +1,69 @@
1
+ import type { QueryMessage } from "./message.js"
2
+ import type { SubscriptionQueryResult } from "./subscription-query.js"
3
+
4
+ /**
5
+ * The query bus — low-level infrastructure for dispatching query messages.
6
+ *
7
+ */
8
+ export interface QueryBus {
9
+ /**
10
+ * Dispatch a query message to its handler(s).
11
+ *
12
+ * The bus auto-nests via ALS (CTX-02): when called outside a UnitOfWork,
13
+ * `runInUoW` creates one; when called inside an active UoW (handler-internal
14
+ * re-dispatch), the active UoW is reused.
15
+ */
16
+ query(message: QueryMessage): Promise<unknown>
17
+
18
+ /**
19
+ * Subscribe a handler for the given query name.
20
+ *
21
+ * Plan 03-04 (CTX-04 / D-34): handler signature dropped its `ctx`
22
+ * parameter. The ProcessingContext type is gone.
23
+ */
24
+ subscribe(
25
+ queryName: string,
26
+ handler: (message: QueryMessage) => Promise<unknown>,
27
+ ): void
28
+
29
+ /**
30
+ * Start a subscription query — returns the initial result plus a stream
31
+ * of incremental updates.
32
+ */
33
+ subscriptionQuery(message: QueryMessage, bufferSize?: number): SubscriptionQueryResult
34
+
35
+ /**
36
+ * Subscribe to updates only (no initial result).
37
+ * Returns an async iterable of update payloads.
38
+ *
39
+ */
40
+ subscribeToUpdates(message: QueryMessage, bufferSize?: number): AsyncIterable<unknown> & { close(): void }
41
+
42
+ /**
43
+ * Emit an update to all active subscription queries matching the filter.
44
+ * When called within an active UnitOfWork (detected via ALS), the update is
45
+ * deferred to AFTER_COMMIT.
46
+ */
47
+ emitUpdate(
48
+ queryName: string,
49
+ filter: (queryPayload: unknown) => boolean,
50
+ update: unknown,
51
+ ): Promise<void>
52
+
53
+ /**
54
+ * Complete all subscription queries matching the filter.
55
+ */
56
+ completeSubscription(
57
+ queryName: string,
58
+ filter?: (queryPayload: unknown) => boolean,
59
+ ): Promise<void>
60
+
61
+ /**
62
+ * Complete all subscription queries matching the filter with an error.
63
+ */
64
+ completeSubscriptionExceptionally(
65
+ queryName: string,
66
+ error: Error,
67
+ filter?: (queryPayload: unknown) => boolean,
68
+ ): Promise<void>
69
+ }
@@ -0,0 +1,49 @@
1
+ import type { z } from "zod"
2
+ import type { Metadata } from "@kronos-ts/common"
3
+ import type { QueryDescriptor } from "./descriptor.js"
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Singular factory — mirrors commandHandler / eventHandler.
7
+ // The app consumes these via `app.queries(...handlers)` varargs.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ /**
11
+ * A registered singular query handler — pairs a query descriptor with its handler
12
+ * function. Mirrors {@link import("./command-handler.js").CommandHandlerDefinition}
13
+ * structurally so all three handler shapes (command / event / query) share the same
14
+ * pattern.
15
+ *
16
+ * Note: queries do NOT carry a result schema on the descriptor (see `QueryDescriptor`
17
+ * in `./descriptor.ts` — no `result` field used here). The result type `R` is
18
+ * inferred from the handler's return type.
19
+ */
20
+ export interface QueryHandlerDefinition<
21
+ Q extends z.ZodType = z.ZodType,
22
+ R = unknown,
23
+ > {
24
+ readonly kind: "query-handler"
25
+ readonly descriptor: QueryDescriptor<Q, z.ZodType | undefined>
26
+ readonly handler: (query: z.infer<Q>, metadata: Metadata) => Promise<R> | R
27
+ }
28
+
29
+ /**
30
+ * Defines a singular query handler.
31
+ *
32
+ * ```
33
+ * const getCourseView = queryHandler(GetCourseView, async (q, metadata) => {
34
+ * const view = courseViews.get(q.courseId)
35
+ * if (!view) throw new Error("not found")
36
+ * return view
37
+ * })
38
+ * ```
39
+ *
40
+ * Use with `app.queries(getCourseView, getAllCourses)`. Symmetric to
41
+ * {@link import("./command-handler.js").commandHandler} and
42
+ * {@link import("./event-handler.js").eventHandler}.
43
+ */
44
+ export function queryHandler<Q extends z.ZodType, R>(
45
+ descriptor: QueryDescriptor<Q, z.ZodType | undefined>,
46
+ handler: (query: z.infer<Q>, metadata: Metadata) => Promise<R> | R,
47
+ ): QueryHandlerDefinition<Q, R> {
48
+ return { kind: "query-handler", descriptor, handler }
49
+ }
@@ -0,0 +1,44 @@
1
+ import { qualifiedNameToString } from "@kronos-ts/common"
2
+ import type { QueryHandlerDefinition } from "./query-handler.js"
3
+ import type { QueryBus } from "./query-bus.js"
4
+ import type { QueryMessage } from "./message.js"
5
+ import type { HandlerEnhancerDefinition } from "./handler-enhancer.js"
6
+
7
+ /**
8
+ * Function-style helper called by AppImpl.start() to subscribe query handlers
9
+ * natively, without the configurer's Module shape.
10
+ *
11
+ * Accepts a flat ReadonlyArray<QueryHandlerDefinition> (singular handler shape).
12
+ *
13
+ * Symmetric to registerCommandHandlersNatively — accepts an optional
14
+ * handlerEnhancer that wraps each query invocation so query handlers receive
15
+ * the same tracing / timing / cross-cutting treatment as command and event
16
+ * handlers. moduleName defaults to "queries" for HandlerMetadata.handlerGroup.
17
+ *
18
+ * Query handlers do NOT need a Configuration shim — queries don't append
19
+ * events, so no per-invocation ALS resource setup is required at query
20
+ * subscription level.
21
+ */
22
+ export function registerQueryHandlersNatively(
23
+ handlers: ReadonlyArray<QueryHandlerDefinition>,
24
+ deps: {
25
+ queryBus: QueryBus
26
+ handlerEnhancer?: HandlerEnhancerDefinition
27
+ moduleName?: string
28
+ },
29
+ ): void {
30
+ const moduleName = deps.moduleName ?? "queries"
31
+ for (const reg of handlers) {
32
+ const queryName = qualifiedNameToString(reg.descriptor.name)
33
+ let invocation = async (message: QueryMessage) =>
34
+ reg.handler(message.payload, message.metadata)
35
+ if (deps.handlerEnhancer) {
36
+ invocation = deps.handlerEnhancer.wrapHandler(invocation, {
37
+ messageType: "query",
38
+ messageName: queryName,
39
+ handlerGroup: moduleName,
40
+ })
41
+ }
42
+ deps.queryBus.subscribe(queryName, invocation)
43
+ }
44
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Re-exports from tracking-token.ts plus the isReplay() helper.
3
+ *
4
+ * Plan 03-04 (CTX-04 / D-29): `isReplay()` is a no-arg permissive ALS read.
5
+ * It returns `false` when called outside an active UnitOfWork (replay-state
6
+ * has only ever been set by the tracking processor; absence == not replaying).
7
+ */
8
+ import { resourceKey, type ResourceKey } from "@kronos-ts/common"
9
+ import { processingStateStorage } from "./processing-state.js"
10
+
11
+ // Re-export token types and operations
12
+ export {
13
+ type TrackingToken,
14
+ type GlobalSequenceToken,
15
+ type ReplayToken,
16
+ globalSequenceToken,
17
+ replayToken,
18
+ isReplayToken,
19
+ isGlobalSequenceToken,
20
+ advanceToken,
21
+ isReplaying,
22
+ unwrapToken,
23
+ wasProcessedBeforeReset,
24
+ } from "./tracking-token.js"
25
+
26
+ /** Resource key for storing replay state in the active UnitOfWork. */
27
+ export const REPLAY_STATE_KEY: ResourceKey<{ replaying: boolean }> =
28
+ resourceKey("replayState")
29
+
30
+ /**
31
+ * Check if the current processing is a replay.
32
+ * Use this in event handlers to skip side effects during replay.
33
+ *
34
+ * Plan 03-04 (D-29): no-arg permissive ALS read. Returns `false` when called
35
+ * outside an active UnitOfWork.
36
+ *
37
+ * ```
38
+ * on(OrderPlaced, async (event) => {
39
+ * await db.orders.insert(event)
40
+ * if (!isReplay()) {
41
+ * await sendConfirmationEmail(event.email)
42
+ * }
43
+ * })
44
+ * ```
45
+ */
46
+ export function isReplay(): boolean {
47
+ const state = processingStateStorage.getStore()
48
+ if (!state) return false
49
+ const replayState = state.resources.get(REPLAY_STATE_KEY.symbol) as
50
+ | { replaying: boolean }
51
+ | undefined
52
+ return replayState?.replaying === true
53
+ }