@kronos-ts/messaging 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/command-bus.d.ts +30 -0
- package/dist/command-bus.d.ts.map +1 -0
- package/dist/command-bus.js +2 -0
- package/dist/command-bus.js.map +1 -0
- package/dist/command-handler.d.ts +58 -0
- package/dist/command-handler.d.ts.map +1 -0
- package/dist/command-handler.js +12 -0
- package/dist/command-handler.js.map +1 -0
- package/dist/command-handling-module.d.ts +53 -0
- package/dist/command-handling-module.d.ts.map +1 -0
- package/dist/command-handling-module.js +130 -0
- package/dist/command-handling-module.js.map +1 -0
- package/dist/correlation-data.d.ts +79 -0
- package/dist/correlation-data.d.ts.map +1 -0
- package/dist/correlation-data.js +133 -0
- package/dist/correlation-data.js.map +1 -0
- package/dist/dead-letter-queue.d.ts +134 -0
- package/dist/dead-letter-queue.d.ts.map +1 -0
- package/dist/dead-letter-queue.js +176 -0
- package/dist/dead-letter-queue.js.map +1 -0
- package/dist/dead-lettering-handler.d.ts +42 -0
- package/dist/dead-lettering-handler.d.ts.map +1 -0
- package/dist/dead-lettering-handler.js +67 -0
- package/dist/dead-lettering-handler.js.map +1 -0
- package/dist/descriptor.d.ts +135 -0
- package/dist/descriptor.d.ts.map +1 -0
- package/dist/descriptor.js +36 -0
- package/dist/descriptor.js.map +1 -0
- package/dist/emit-update.d.ts +22 -0
- package/dist/emit-update.d.ts.map +1 -0
- package/dist/emit-update.js +23 -0
- package/dist/emit-update.js.map +1 -0
- package/dist/event-bus.d.ts +29 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +22 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/event-criteria.d.ts +87 -0
- package/dist/event-criteria.d.ts.map +1 -0
- package/dist/event-criteria.js +90 -0
- package/dist/event-criteria.js.map +1 -0
- package/dist/event-gateway.d.ts +19 -0
- package/dist/event-gateway.d.ts.map +1 -0
- package/dist/event-gateway.js +22 -0
- package/dist/event-gateway.js.map +1 -0
- package/dist/event-handler.d.ts +30 -0
- package/dist/event-handler.d.ts.map +1 -0
- package/dist/event-handler.js +18 -0
- package/dist/event-handler.js.map +1 -0
- package/dist/event-processor-builder.d.ts +148 -0
- package/dist/event-processor-builder.d.ts.map +1 -0
- package/dist/event-processor-builder.js +175 -0
- package/dist/event-processor-builder.js.map +1 -0
- package/dist/event-processor.d.ts +10 -0
- package/dist/event-processor.d.ts.map +1 -0
- package/dist/event-processor.js +2 -0
- package/dist/event-processor.js.map +1 -0
- package/dist/event-sink.d.ts +23 -0
- package/dist/event-sink.d.ts.map +1 -0
- package/dist/event-sink.js +2 -0
- package/dist/event-sink.js.map +1 -0
- package/dist/event-source.d.ts +98 -0
- package/dist/event-source.d.ts.map +1 -0
- package/dist/event-source.js +191 -0
- package/dist/event-source.js.map +1 -0
- package/dist/gateway.d.ts +68 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +62 -0
- package/dist/gateway.js.map +1 -0
- package/dist/handler-enhancer.d.ts +53 -0
- package/dist/handler-enhancer.d.ts.map +1 -0
- package/dist/handler-enhancer.js +17 -0
- package/dist/handler-enhancer.js.map +1 -0
- package/dist/handler.d.ts +51 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +26 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +103 -0
- package/dist/index.js.map +1 -0
- package/dist/intercepting-command-bus.d.ts +17 -0
- package/dist/intercepting-command-bus.d.ts.map +1 -0
- package/dist/intercepting-command-bus.js +54 -0
- package/dist/intercepting-command-bus.js.map +1 -0
- package/dist/intercepting-event-bus.d.ts +8 -0
- package/dist/intercepting-event-bus.d.ts.map +1 -0
- package/dist/intercepting-event-bus.js +22 -0
- package/dist/intercepting-event-bus.js.map +1 -0
- package/dist/intercepting-query-bus.d.ts +17 -0
- package/dist/intercepting-query-bus.d.ts.map +1 -0
- package/dist/intercepting-query-bus.js +68 -0
- package/dist/intercepting-query-bus.js.map +1 -0
- package/dist/interceptor.d.ts +46 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +2 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/message-monitor-registry.d.ts +28 -0
- package/dist/message-monitor-registry.d.ts.map +1 -0
- package/dist/message-monitor-registry.js +37 -0
- package/dist/message-monitor-registry.js.map +1 -0
- package/dist/message-monitor.d.ts +36 -0
- package/dist/message-monitor.d.ts.map +1 -0
- package/dist/message-monitor.js +39 -0
- package/dist/message-monitor.js.map +1 -0
- package/dist/message.d.ts +42 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/message.js +2 -0
- package/dist/message.js.map +1 -0
- package/dist/processing-state.d.ts +115 -0
- package/dist/processing-state.d.ts.map +1 -0
- package/dist/processing-state.js +205 -0
- package/dist/processing-state.js.map +1 -0
- package/dist/processor-configuration.d.ts +51 -0
- package/dist/processor-configuration.d.ts.map +1 -0
- package/dist/processor-configuration.js +2 -0
- package/dist/processor-configuration.js.map +1 -0
- package/dist/query-bus.d.ts +51 -0
- package/dist/query-bus.d.ts.map +1 -0
- package/dist/query-bus.js +2 -0
- package/dist/query-bus.js.map +1 -0
- package/dist/query-handler.d.ts +35 -0
- package/dist/query-handler.d.ts.map +1 -0
- package/dist/query-handler.js +19 -0
- package/dist/query-handler.js.map +1 -0
- package/dist/query-handling-module.d.ts +24 -0
- package/dist/query-handling-module.d.ts.map +1 -0
- package/dist/query-handling-module.js +32 -0
- package/dist/query-handling-module.js.map +1 -0
- package/dist/replay-token.d.ts +31 -0
- package/dist/replay-token.d.ts.map +1 -0
- package/dist/replay-token.js +37 -0
- package/dist/replay-token.js.map +1 -0
- package/dist/retrying-command-bus.d.ts +32 -0
- package/dist/retrying-command-bus.d.ts.map +1 -0
- package/dist/retrying-command-bus.js +58 -0
- package/dist/retrying-command-bus.js.map +1 -0
- package/dist/routing-strategy.d.ts +30 -0
- package/dist/routing-strategy.d.ts.map +1 -0
- package/dist/routing-strategy.js +37 -0
- package/dist/routing-strategy.js.map +1 -0
- package/dist/segment.d.ts +72 -0
- package/dist/segment.d.ts.map +1 -0
- package/dist/segment.js +103 -0
- package/dist/segment.js.map +1 -0
- package/dist/send.d.ts +28 -0
- package/dist/send.d.ts.map +1 -0
- package/dist/send.js +36 -0
- package/dist/send.js.map +1 -0
- package/dist/serializer.d.ts +40 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/serializer.js +90 -0
- package/dist/serializer.js.map +1 -0
- package/dist/simple-command-bus.d.ts +23 -0
- package/dist/simple-command-bus.d.ts.map +1 -0
- package/dist/simple-command-bus.js +49 -0
- package/dist/simple-command-bus.js.map +1 -0
- package/dist/simple-query-bus.d.ts +16 -0
- package/dist/simple-query-bus.d.ts.map +1 -0
- package/dist/simple-query-bus.js +122 -0
- package/dist/simple-query-bus.js.map +1 -0
- package/dist/span-factory.d.ts +58 -0
- package/dist/span-factory.d.ts.map +1 -0
- package/dist/span-factory.js +19 -0
- package/dist/span-factory.js.map +1 -0
- package/dist/streaming-event-processor.d.ts +65 -0
- package/dist/streaming-event-processor.d.ts.map +1 -0
- package/dist/streaming-event-processor.js +239 -0
- package/dist/streaming-event-processor.js.map +1 -0
- package/dist/subscribing-event-processor.d.ts +57 -0
- package/dist/subscribing-event-processor.d.ts.map +1 -0
- package/dist/subscribing-event-processor.js +100 -0
- package/dist/subscribing-event-processor.js.map +1 -0
- package/dist/subscription-query.d.ts +63 -0
- package/dist/subscription-query.d.ts.map +1 -0
- package/dist/subscription-query.js +119 -0
- package/dist/subscription-query.js.map +1 -0
- package/dist/token-store.d.ts +83 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +112 -0
- package/dist/token-store.js.map +1 -0
- package/dist/tracing-command-bus.d.ts +16 -0
- package/dist/tracing-command-bus.d.ts.map +1 -0
- package/dist/tracing-command-bus.js +44 -0
- package/dist/tracing-command-bus.js.map +1 -0
- package/dist/tracing-handler-enhancer.d.ts +11 -0
- package/dist/tracing-handler-enhancer.d.ts.map +1 -0
- package/dist/tracing-handler-enhancer.js +27 -0
- package/dist/tracing-handler-enhancer.js.map +1 -0
- package/dist/tracking-event-processor.d.ts +72 -0
- package/dist/tracking-event-processor.d.ts.map +1 -0
- package/dist/tracking-event-processor.js +223 -0
- package/dist/tracking-event-processor.js.map +1 -0
- package/dist/tracking-token.d.ts +120 -0
- package/dist/tracking-token.d.ts.map +1 -0
- package/dist/tracking-token.js +132 -0
- package/dist/tracking-token.js.map +1 -0
- package/dist/transaction.d.ts +60 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +74 -0
- package/dist/transaction.js.map +1 -0
- package/dist/unit-of-work.d.ts +41 -0
- package/dist/unit-of-work.d.ts.map +1 -0
- package/dist/unit-of-work.js +96 -0
- package/dist/unit-of-work.js.map +1 -0
- package/dist/upcaster.d.ts +91 -0
- package/dist/upcaster.d.ts.map +1 -0
- package/dist/upcaster.js +114 -0
- package/dist/upcaster.js.map +1 -0
- package/dist/with-namespace.d.ts +59 -0
- package/dist/with-namespace.d.ts.map +1 -0
- package/dist/with-namespace.js +42 -0
- package/dist/with-namespace.js.map +1 -0
- package/package.json +65 -0
- package/src/command-bus.ts +34 -0
- package/src/command-handler.ts +116 -0
- package/src/command-handling-module.ts +183 -0
- package/src/correlation-data.ts +169 -0
- package/src/dead-letter-queue.ts +330 -0
- package/src/dead-lettering-handler.ts +109 -0
- package/src/descriptor.ts +176 -0
- package/src/emit-update.ts +35 -0
- package/src/event-bus.ts +45 -0
- package/src/event-criteria.ts +141 -0
- package/src/event-gateway.ts +42 -0
- package/src/event-handler.ts +44 -0
- package/src/event-processor-builder.ts +246 -0
- package/src/event-processor.ts +9 -0
- package/src/event-sink.ts +23 -0
- package/src/event-source.ts +301 -0
- package/src/gateway.ts +144 -0
- package/src/handler-enhancer.ts +70 -0
- package/src/handler.ts +133 -0
- package/src/index.ts +356 -0
- package/src/intercepting-command-bus.ts +73 -0
- package/src/intercepting-event-bus.ts +29 -0
- package/src/intercepting-query-bus.ts +104 -0
- package/src/interceptor.ts +48 -0
- package/src/message-monitor-registry.ts +64 -0
- package/src/message-monitor.ts +68 -0
- package/src/message.ts +41 -0
- package/src/processing-state.ts +258 -0
- package/src/processor-configuration.ts +59 -0
- package/src/query-bus.ts +69 -0
- package/src/query-handler.ts +49 -0
- package/src/query-handling-module.ts +44 -0
- package/src/replay-token.ts +53 -0
- package/src/retrying-command-bus.ts +80 -0
- package/src/routing-strategy.ts +59 -0
- package/src/segment.ts +136 -0
- package/src/send.ts +44 -0
- package/src/serializer.ts +122 -0
- package/src/simple-command-bus.ts +59 -0
- package/src/simple-query-bus.ts +158 -0
- package/src/span-factory.ts +81 -0
- package/src/streaming-event-processor.ts +351 -0
- package/src/subscribing-event-processor.ts +169 -0
- package/src/subscription-query.ts +173 -0
- package/src/token-store.ts +211 -0
- package/src/tracing-command-bus.ts +52 -0
- package/src/tracing-handler-enhancer.ts +34 -0
- package/src/tracking-event-processor.ts +336 -0
- package/src/tracking-token.ts +231 -0
- package/src/transaction.ts +98 -0
- package/src/unit-of-work.ts +138 -0
- package/src/upcaster.ts +174 -0
- package/src/with-namespace.ts +75 -0
|
@@ -0,0 +1,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
|
+
}
|
package/src/upcaster.ts
ADDED
|
@@ -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
|
+
}
|