@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,138 @@
1
+ import { emptyMetadata, type Metadata } from "@kronos-ts/common"
2
+ import {
3
+ processingStateStorage,
4
+ createInitialProcessingState,
5
+ Phase,
6
+ type PhaseValue,
7
+ } from "./processing-state.js"
8
+
9
+ /**
10
+ * Runner type: the canonical "enter a UnitOfWork" shape.
11
+ *
12
+ * Plan 03-04 (CTX-04 / D-34): replaces the old `UnitOfWorkFactory` shape.
13
+ * Extensions (kronosdb, axon-server) and transactional wrappers compose
14
+ * runners — `transactionalUnitOfWorkFactory` accepts a delegate runner
15
+ * and returns a new runner that begins/commits/rolls-back a transaction
16
+ * around the inner action.
17
+ */
18
+ export type UoWRunner = <R>(
19
+ metadata: Metadata | undefined,
20
+ action: () => Promise<R>,
21
+ ) => Promise<R>
22
+
23
+ /**
24
+ * Run `action` inside a NEW UnitOfWork. Always creates a fresh UoW even
25
+ * if one is already active on the ALS stack.
26
+ *
27
+ * Used by gateways (D-32): `commandGateway.send`, `queryGateway.query`,
28
+ * and any external entry point. This keeps gateway calls isolated from
29
+ * an injected dispatcher that reuses the active UoW.
30
+ */
31
+ export function runInNewUoW<R>(
32
+ metadata: Metadata | undefined,
33
+ action: () => Promise<R>,
34
+ ): Promise<R> {
35
+ const resolvedMetadata = metadata ?? emptyMetadata()
36
+ const state = createInitialProcessingState(resolvedMetadata)
37
+ return processingStateStorage.run(state, () => drivePhases(state, action))
38
+ }
39
+
40
+ /**
41
+ * Run `action` inside a UnitOfWork. ALS-aware:
42
+ *
43
+ * - If `processingStateStorage` already has an active state (we are
44
+ * nested inside a parent UoW), this REUSES the parent's state and
45
+ * calls `action` directly — no new `processingStateStorage.run`, no
46
+ * new phase lifecycle. This is CTX-02: nested dispatch threads through
47
+ * the same UoW so transactionality spans handler-internal bus calls.
48
+ *
49
+ * - If no state is active, behaves identically to `runInNewUoW` —
50
+ * creates a fresh UoW and drives the full phase lifecycle.
51
+ *
52
+ * Used by `queryBus.query` so handler-internal queries auto-nest, while
53
+ * a primary query (no active UoW) starts a new one.
54
+ *
55
+ * NOTE: `commandBus.dispatch` deliberately does NOT use this — for AF5
56
+ * parity every command gets its own fresh UnitOfWork via `runInNewUoW`,
57
+ * nested or not. See `createSimpleCommandBus`.
58
+ */
59
+ export function runInUoW<R>(
60
+ metadata: Metadata | undefined,
61
+ action: () => Promise<R>,
62
+ ): Promise<R> {
63
+ if (processingStateStorage.getStore() !== undefined) return action()
64
+ return runInNewUoW(metadata, action)
65
+ }
66
+
67
+ // ── private: the lifecycle-driving loop ───────────────────────────────
68
+ //
69
+ // Plan 03-04 (D-34): the phase-driving loop previously lived inside
70
+ // `createUnitOfWork` + `ManagedProcessingContext.executePhases`. Inlined
71
+ // here because the ProcessingContext abstraction is gone — the runner is
72
+ // now the only entry point.
73
+ //
74
+ // Re-sort semantics from the old `executePhases` (default-processing-context.ts
75
+ // lines 168-199): handlers registered during a phase's own execution at
76
+ // EARLIER phase values are silently dropped (the phase is already past).
77
+ // `runPhase` drains its own bucket repeatedly so handlers registered for
78
+ // the SAME phase during execution are picked up before moving on. After
79
+ // a phase finishes, the outer loop re-reads `phaseActions.keys()` so any
80
+ // new LATER phase entries are picked up in order.
81
+
82
+ type State = ReturnType<typeof createInitialProcessingState>
83
+
84
+ async function drivePhases<R>(state: State, action: () => Promise<R>): Promise<R> {
85
+ state.status = "started"
86
+ try {
87
+ await runPhase(state, Phase.PRE_INVOCATION)
88
+
89
+ state.currentPhase = Phase.INVOCATION
90
+ let actions = state.phaseActions.get(Phase.INVOCATION)
91
+ while (actions && actions.length > 0) {
92
+ const bucket = actions
93
+ state.phaseActions.set(Phase.INVOCATION, [])
94
+ for (const a of bucket) await a()
95
+ actions = state.phaseActions.get(Phase.INVOCATION)
96
+ }
97
+ const result = await action()
98
+
99
+ await runPhase(state, Phase.POST_INVOCATION)
100
+ await runPhase(state, Phase.PREPARE_COMMIT)
101
+ await runPhase(state, Phase.COMMIT)
102
+ await runPhase(state, Phase.AFTER_COMMIT)
103
+
104
+ state.status = "completed"
105
+ for (const h of state.completeHandlers) {
106
+ try {
107
+ h()
108
+ } catch (e) {
109
+ console.warn("UnitOfWork: completion handler threw an exception:", e)
110
+ }
111
+ }
112
+ return result
113
+ } catch (error) {
114
+ state.status = "error"
115
+ const failedPhase = state.currentPhase ?? undefined
116
+ for (const h of state.errorHandlers) {
117
+ try {
118
+ await h(error, failedPhase)
119
+ } catch (e) {
120
+ console.warn("UnitOfWork: error handler threw an exception:", e)
121
+ }
122
+ }
123
+ throw error
124
+ }
125
+ }
126
+
127
+ async function runPhase(state: State, phase: PhaseValue): Promise<void> {
128
+ state.currentPhase = phase
129
+ // Late registration: actions registered during a phase's own execution
130
+ // fire next loop iteration (still within the same phase).
131
+ let actions = state.phaseActions.get(phase)
132
+ while (actions && actions.length > 0) {
133
+ const bucket = actions
134
+ state.phaseActions.set(phase, [])
135
+ for (const a of bucket) await a()
136
+ actions = state.phaseActions.get(phase)
137
+ }
138
+ }
@@ -0,0 +1,174 @@
1
+ import type { Serializer, SerializedObject } from "@kronos-ts/common"
2
+
3
+ /**
4
+ * An intermediate representation of an event during the upcasting pipeline.
5
+ *
6
+ * After deserialization from bytes (JSON.parse) but before Zod validation,
7
+ * the event passes through the upcaster chain as an IntermediateEventRepresentation.
8
+ * Each upcaster can transform the payload, type name, revision, and metadata.
9
+ */
10
+ export interface IntermediateEventRepresentation {
11
+ /** The deserialized payload (raw JSON, not yet Zod-validated). */
12
+ readonly payload: unknown
13
+ /** The qualified type name (e.g., "university.CourseCreated"). */
14
+ readonly typeName: string
15
+ /** The schema revision (e.g., "1.0"). */
16
+ readonly revision: string
17
+ /** Event metadata. */
18
+ readonly metadata: Record<string, unknown>
19
+ }
20
+
21
+ /**
22
+ * An event upcaster transforms events from one schema version to another.
23
+ *
24
+ * Upcasters run after JSON deserialization but before Zod validation,
25
+ * so the payload is raw parsed JSON. The upcaster can add fields, rename
26
+ * fields, change the type name, bump the revision, etc.
27
+ *
28
+ * An upcaster can return:
29
+ * - A single representation (one-to-one transform)
30
+ * - An array of representations (one-to-many, e.g., splitting events)
31
+ * - An empty array (filtering out an event)
32
+ */
33
+ export interface EventUpcaster {
34
+ /** Whether this upcaster can handle the given type + revision. */
35
+ canUpcast(typeName: string, revision: string): boolean
36
+
37
+ /**
38
+ * Transform the intermediate representation.
39
+ * Return the upcasted representation(s).
40
+ */
41
+ upcast(event: IntermediateEventRepresentation): IntermediateEventRepresentation | IntermediateEventRepresentation[]
42
+ }
43
+
44
+ /**
45
+ * Convenience function for the common case: a single event type
46
+ * being transformed from one revision to the next.
47
+ *
48
+ * ```typescript
49
+ * const upcaster = singleEventUpcaster({
50
+ * typeName: "university.CourseCreated",
51
+ * fromRevision: "1.0",
52
+ * toRevision: "2.0",
53
+ * upcast: (payload: any) => ({
54
+ * ...payload,
55
+ * capacity: payload.capacity ?? 30, // added in v2
56
+ * }),
57
+ * })
58
+ * ```
59
+ */
60
+ export function singleEventUpcaster(options: {
61
+ typeName: string
62
+ fromRevision: string
63
+ toRevision: string
64
+ upcast: (payload: unknown) => unknown
65
+ upcastMetadata?: (metadata: Record<string, unknown>) => Record<string, unknown>
66
+ }): EventUpcaster {
67
+ return {
68
+ canUpcast(typeName, revision) {
69
+ return typeName === options.typeName && revision === options.fromRevision
70
+ },
71
+
72
+ upcast(event) {
73
+ return {
74
+ payload: options.upcast(event.payload),
75
+ typeName: options.typeName,
76
+ revision: options.toRevision,
77
+ metadata: options.upcastMetadata
78
+ ? options.upcastMetadata(event.metadata)
79
+ : event.metadata,
80
+ }
81
+ },
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Chains multiple upcasters into a single upcaster.
87
+ * Upcasters are applied in order. Each upcaster's output becomes
88
+ * the next upcaster's input. Handles one-to-many expansion.
89
+ *
90
+ * ```typescript
91
+ * const chain = upcasterChain(
92
+ * courseCreatedV1ToV2,
93
+ * courseCreatedV2ToV3,
94
+ * )
95
+ * ```
96
+ */
97
+ export function upcasterChain(...upcasters: EventUpcaster[]): EventUpcaster {
98
+ return {
99
+ canUpcast(typeName, revision) {
100
+ return upcasters.some((u) => u.canUpcast(typeName, revision))
101
+ },
102
+
103
+ upcast(event) {
104
+ let representations: IntermediateEventRepresentation[] = [event]
105
+
106
+ for (const upcaster of upcasters) {
107
+ const next: IntermediateEventRepresentation[] = []
108
+ for (const rep of representations) {
109
+ if (upcaster.canUpcast(rep.typeName, rep.revision)) {
110
+ const result = upcaster.upcast(rep)
111
+ if (Array.isArray(result)) {
112
+ next.push(...result)
113
+ } else {
114
+ next.push(result)
115
+ }
116
+ } else {
117
+ next.push(rep)
118
+ }
119
+ }
120
+ representations = next
121
+ }
122
+
123
+ return representations.length === 1 ? representations[0]! : representations
124
+ },
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Creates an upcasting serializer decorator.
130
+ *
131
+ * On deserialization, the raw payload is first deserialized by the delegate,
132
+ * then passed through the upcaster chain before being returned. The type name
133
+ * and revision from the SerializedObject drive upcaster selection.
134
+ *
135
+ * ```typescript
136
+ * const serializer = upcastingSerializer(
137
+ * jsonSerializer(),
138
+ * upcasterChain(v1ToV2, v2ToV3),
139
+ * )
140
+ * ```
141
+ */
142
+ export function upcastingSerializer(
143
+ delegate: Serializer,
144
+ upcaster: EventUpcaster,
145
+ ): Serializer {
146
+ return {
147
+ serialize(value, type, revision) {
148
+ return delegate.serialize(value, type, revision)
149
+ },
150
+
151
+ deserialize<T>(data: SerializedObject): T {
152
+ const raw = delegate.deserialize<unknown>(data)
153
+
154
+ if (!upcaster.canUpcast(data.type, data.revision)) {
155
+ return raw as T
156
+ }
157
+
158
+ const representation: IntermediateEventRepresentation = {
159
+ payload: raw,
160
+ typeName: data.type,
161
+ revision: data.revision,
162
+ metadata: {},
163
+ }
164
+
165
+ const result = upcaster.upcast(representation)
166
+ const upcasted = Array.isArray(result) ? result[0]! : result
167
+ return upcasted.payload as T
168
+ },
169
+
170
+ canConvert(type) {
171
+ return delegate.canConvert(type)
172
+ },
173
+ }
174
+ }
@@ -0,0 +1,75 @@
1
+ import { qn, type Tag } from "@kronos-ts/common"
2
+ import {
3
+ command as createCommand,
4
+ event as createEvent,
5
+ query as createQuery,
6
+ } from "./descriptor.js"
7
+ import type { z } from "zod"
8
+
9
+ /**
10
+ * Creates a namespace-scoped factory for message descriptors.
11
+ * Reduces repetition when defining many messages in the same bounded context.
12
+ *
13
+ * ```typescript
14
+ * const ns = withNamespace("university.courses")
15
+ *
16
+ * const CreateCourse = ns.command("CreateCourse", {
17
+ * payload: z.object({ courseId: z.string(), name: z.string() }),
18
+ * })
19
+ *
20
+ * const CourseCreated = ns.event("CourseCreated", {
21
+ * payload: z.object({ courseId: z.string(), name: z.string() }),
22
+ * tags: (p) => [tag("courseId", p.courseId)],
23
+ * })
24
+ *
25
+ * const GetCourse = ns.query("GetCourse", {
26
+ * payload: z.object({ courseId: z.string() }),
27
+ * })
28
+ * ```
29
+ */
30
+ export function withNamespace(namespace: string) {
31
+ function command<P extends z.ZodType>(name: string, def: {
32
+ payload: P
33
+ version?: string
34
+ routingKey?: string
35
+ }): ReturnType<typeof createCommand<P>>
36
+ function command<P extends z.ZodType, R extends z.ZodType>(name: string, def: {
37
+ payload: P
38
+ result: R
39
+ version?: string
40
+ routingKey?: string
41
+ }): ReturnType<typeof createCommand<P, R>>
42
+ function command(name: string, def: { payload: z.ZodType }) {
43
+ return createCommand({ name: qn(namespace, name), ...def } as never)
44
+ }
45
+
46
+ function query<P extends z.ZodType>(name: string, def: {
47
+ payload: P
48
+ version?: string
49
+ }): ReturnType<typeof createQuery<P>>
50
+ function query<P extends z.ZodType, R extends z.ZodType>(name: string, def: {
51
+ payload: P
52
+ result: R
53
+ version?: string
54
+ }): ReturnType<typeof createQuery<P, R>>
55
+ function query(name: string, def: { payload: z.ZodType }) {
56
+ return createQuery({ name: qn(namespace, name), ...def } as never)
57
+ }
58
+
59
+ return {
60
+ /** Create a command descriptor in this namespace. */
61
+ command,
62
+
63
+ /** Create an event descriptor in this namespace. */
64
+ event<P extends z.ZodType>(name: string, def: {
65
+ payload: P
66
+ version?: string
67
+ tags?: (payload: z.infer<P>) => Tag[]
68
+ }) {
69
+ return createEvent({ name: qn(namespace, name), ...def })
70
+ },
71
+
72
+ /** Create a query descriptor in this namespace. */
73
+ query,
74
+ }
75
+ }