@kronos-ts/axon-server 0.3.2 → 0.5.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 (102) hide show
  1. package/dist/axon-server-event-store.d.ts +3 -15
  2. package/dist/axon-server-event-store.d.ts.map +1 -1
  3. package/dist/axon-server-event-store.js +85 -44
  4. package/dist/axon-server-event-store.js.map +1 -1
  5. package/dist/axon-server-snapshotting-event-store.d.ts +30 -0
  6. package/dist/axon-server-snapshotting-event-store.d.ts.map +1 -0
  7. package/dist/axon-server-snapshotting-event-store.js +153 -0
  8. package/dist/axon-server-snapshotting-event-store.js.map +1 -0
  9. package/dist/axon-server.d.ts +92 -208
  10. package/dist/axon-server.d.ts.map +1 -1
  11. package/dist/axon-server.js +221 -323
  12. package/dist/axon-server.js.map +1 -1
  13. package/dist/connection-manager.d.ts +2 -2
  14. package/dist/connection-manager.d.ts.map +1 -1
  15. package/dist/connection.d.ts +134 -4
  16. package/dist/connection.d.ts.map +1 -1
  17. package/dist/connection.js +135 -10
  18. package/dist/connection.js.map +1 -1
  19. package/dist/context-view.d.ts +30 -0
  20. package/dist/context-view.d.ts.map +1 -0
  21. package/dist/context-view.js +19 -0
  22. package/dist/context-view.js.map +1 -0
  23. package/dist/control-plane.d.ts +18 -17
  24. package/dist/control-plane.d.ts.map +1 -1
  25. package/dist/control-plane.js +8 -6
  26. package/dist/control-plane.js.map +1 -1
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js.map +1 -1
  29. package/dist/event-processor-info.d.ts +4 -4
  30. package/dist/event-processor-info.d.ts.map +1 -1
  31. package/dist/flow-controlled-sender.d.ts +2 -2
  32. package/dist/flow-controlled-sender.d.ts.map +1 -1
  33. package/dist/flow-controlled-sender.js.map +1 -1
  34. package/dist/generated/command.d.ts +20 -20
  35. package/dist/generated/command.d.ts.map +1 -1
  36. package/dist/generated/command.js.map +1 -1
  37. package/dist/generated/common.d.ts +20 -20
  38. package/dist/generated/common.d.ts.map +1 -1
  39. package/dist/generated/common.js.map +1 -1
  40. package/dist/generated/control.d.ts +36 -40
  41. package/dist/generated/control.d.ts.map +1 -1
  42. package/dist/generated/control.js.map +1 -1
  43. package/dist/generated/dcb.d.ts +88 -94
  44. package/dist/generated/dcb.d.ts.map +1 -1
  45. package/dist/generated/dcb.js.map +1 -1
  46. package/dist/generated/event.d.ts +81 -84
  47. package/dist/generated/event.d.ts.map +1 -1
  48. package/dist/generated/event.js.map +1 -1
  49. package/dist/generated/google/protobuf/empty.d.ts +3 -4
  50. package/dist/generated/google/protobuf/empty.d.ts.map +1 -1
  51. package/dist/generated/google/protobuf/empty.js.map +1 -1
  52. package/dist/generated/query.d.ts +40 -40
  53. package/dist/generated/query.d.ts.map +1 -1
  54. package/dist/generated/query.js.map +1 -1
  55. package/dist/index.d.ts +4 -6
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +4 -5
  58. package/dist/index.js.map +1 -1
  59. package/dist/message-size.d.ts +2 -2
  60. package/dist/message-size.d.ts.map +1 -1
  61. package/dist/metadata-conversion.d.ts +1 -1
  62. package/dist/metadata-conversion.d.ts.map +1 -1
  63. package/dist/outbound-stream.d.ts +2 -2
  64. package/dist/outbound-stream.d.ts.map +1 -1
  65. package/dist/platform-service.d.ts +4 -4
  66. package/dist/platform-service.d.ts.map +1 -1
  67. package/dist/resilience.d.ts +62 -0
  68. package/dist/resilience.d.ts.map +1 -0
  69. package/dist/resilience.js +103 -0
  70. package/dist/resilience.js.map +1 -0
  71. package/dist/shutdown-latch.d.ts +4 -4
  72. package/dist/shutdown-latch.d.ts.map +1 -1
  73. package/dist/shutdown-latch.js.map +1 -1
  74. package/package.json +5 -9
  75. package/src/axon-server-event-store.ts +97 -57
  76. package/src/axon-server-snapshotting-event-store.ts +191 -0
  77. package/src/axon-server.ts +365 -524
  78. package/src/connection-manager.ts +1 -1
  79. package/src/connection.ts +274 -20
  80. package/src/context-view.ts +46 -0
  81. package/src/control-plane.ts +19 -19
  82. package/src/event-processor-info.ts +2 -2
  83. package/src/flow-controlled-sender.ts +1 -1
  84. package/src/generated/command.ts +10 -10
  85. package/src/generated/common.ts +10 -10
  86. package/src/generated/control.ts +20 -20
  87. package/src/generated/dcb.ts +47 -47
  88. package/src/generated/event.ts +42 -42
  89. package/src/generated/google/protobuf/empty.ts +2 -2
  90. package/src/generated/query.ts +20 -20
  91. package/src/index.ts +12 -18
  92. package/src/message-size.ts +1 -1
  93. package/src/metadata-conversion.ts +1 -1
  94. package/src/outbound-stream.ts +1 -1
  95. package/src/platform-service.ts +2 -2
  96. package/src/resilience.ts +149 -0
  97. package/src/shutdown-latch.ts +2 -2
  98. package/dist/axon-server-snapshot-store.d.ts +0 -12
  99. package/dist/axon-server-snapshot-store.d.ts.map +0 -1
  100. package/dist/axon-server-snapshot-store.js +0 -88
  101. package/dist/axon-server-snapshot-store.js.map +0 -1
  102. package/src/axon-server-snapshot-store.ts +0 -118
@@ -1,74 +1,48 @@
1
1
  /**
2
- * Axon Server backend for kronos.
2
+ * The Axon Server command and query buses.
3
3
  *
4
- * `axonServer(config)` is an async factory: it connects eagerly, hands back
5
- * the four components it provides (eventStore, snapshotStore, commandBus,
6
- * queryBus), and gives you a `start`/`close` pair. There is no lifecycle
7
- * framework the ordering that used to be encoded as `onStart("connect")` /
8
- * `onStart("processors")` / `onStop("connect")` is now three lines you write
9
- * in your composition root:
4
+ * Axon Server is a SMART HUB: outbound dispatch always goes to the server, and
5
+ * the server decides which node handles it there is no client-side
6
+ * prefer-next fork here, which is the whole difference from the dumb-pipe
7
+ * broker in `@kronos-ts/rabbitmq`.
10
8
  *
11
- * ```ts
12
- * const axon = await axonServer({
13
- * componentName: "university-service",
14
- * serializer,
15
- * unitOfWorkFactory,
16
- * })
17
- * const app = kronos({
18
- * components: { ...inMemoryComponents({ serializer, unitOfWorkFactory }), ...axon.components },
19
- * modules,
20
- * })
21
- * await axon.start() // readiness barrier: the server can route to our handlers
22
- * // …
23
- * await app.stop(); await axon.close()
24
- * ```
25
- *
26
- * Connecting before the app is built is what removes the lazy proxies and
27
- * subscribe-buffering wrappers the container version needed: by the time
28
- * `kronos` subscribes a handler, the gRPC streams are already live.
29
- *
30
- * REMOTE ADMINISTRATION IS NOT IN HERE. Processor instructions (pause / start /
31
- * release / split / merge) and processor status reporting are the platform
32
- * CONTROL PLANE — they are neither persistence nor transport, and lived here
33
- * only because they share this gRPC connection. They are now an opt-in second
34
- * object built on the platform stream this backend exposes:
9
+ * Both buses are plain functions over the shared connection and YOUR next bus:
35
10
  *
36
11
  * ```ts
37
- * const control = await axonServerControlPlane(axon.platform, app.processors.values())
12
+ * const commandBus = interceptingCommandBus(
13
+ * axonServerCommandBus(localCommandBus(unitOfWork), axon), correlation)
14
+ * const queryBus = interceptingQueryBus(
15
+ * axonServerQueryBus(localQueryBus(unitOfWork), axon), correlation)
38
16
  * ```
39
17
  *
40
- * `start()` therefore takes NO arguments and does exactly one thing: the
41
- * data-path readiness barrier. See `control-plane.ts`.
42
- *
43
18
  * Axon-specific protocol invariants are preserved byte-for-byte:
44
19
  *
45
20
  * - CLIENT_SUPPORTS_STREAMING capability advertised on every dispatched
46
21
  * query via `defaultQueryInstructions(...)`;
47
22
  * - AxonIQ-Context + AxonIQ-Access-Token gRPC metadata headers built by
48
- * `createAxonMetadata(...)` and attached to every outbound stream/RPC;
23
+ * `contextView(...)` and attached to every outbound stream/RPC;
49
24
  * - permits-AFTER-subscriptions stream ordering preserved on the initial
50
25
  * handshake AND on reconnect (see `ensureStreamStarted` /
51
- * `reestablishStreamBody`);
52
- * - shutdown ordering: busLatches → platform.stop → connection.close.
26
+ * `reestablishStreamBody`).
53
27
  */
54
- import { qualifiedNameToString, qualifiedNameFromString, generateIdentifier, withRetry, healthCheck, } from "@kronos-ts/common";
55
- import { applySubscriptionFilter, correlationDataDispatchInterceptor, interceptingCommandBus, interceptingQueryBus, updateHandler, runAfterCommitOrImmediately, } from "@kronos-ts/messaging";
56
- import { Metadata } from "nice-grpc";
57
- import { connectToAxonServer } from "./connection.js";
58
- import { axonServerEventStore } from "./axon-server-event-store.js";
59
- import { axonServerSnapshotStore } from "./axon-server-snapshot-store.js";
28
+ import { qualifiedNameToString, qualifiedNameFromString, generateIdentifier, } from "@kronos-ts/core";
29
+ import { withRetry } from "./resilience.js";
30
+ import { applySubscriptionFilter, updateHandler, runAfterCommitOrImmediately, } from "@kronos-ts/core";
31
+ import { contextView } from "./context-view.js";
60
32
  import { metadataToProto, metadataFromProto } from "./metadata-conversion.js";
61
33
  import { outboundStream } from "./outbound-stream.js";
62
34
  import { mapErrorCode, AxonServerErrorCode } from "./errors.js";
63
- import { shutdownLatch } from "./shutdown-latch.js";
64
- import { platformConnection, } from "./platform-service.js";
65
35
  /** Default flow control settings — aligned with Java's 5000 permits. */
66
36
  const DEFAULT_PERMITS = 5000n;
67
37
  const DEFAULT_THRESHOLD = 2500n;
38
+ /** Default query dispatch timeout — aligned with Java's one hour. */
39
+ const DEFAULT_QUERY_TIMEOUT_MS = 3_600_000;
40
+ /** Default command handler load factor — aligned with Java's 100. */
41
+ const DEFAULT_LOAD_FACTOR = 100;
68
42
  // Processing instruction keys — aligned with proto ProcessingKey enum.
69
43
  // CLIENT_SUPPORTS_STREAMING (key=8) is an Axon-Server-specific capability
70
- // advertisement that MUST survive the migration verbatim — see file-level
71
- // JSDoc above and `defaultQueryInstructions` below.
44
+ // advertisement that MUST survive verbatim — see file-level JSDoc above and
45
+ // `defaultQueryInstructions` below.
72
46
  const INSTRUCTION_KEY = {
73
47
  ROUTING_KEY: 0,
74
48
  PRIORITY: 1,
@@ -84,10 +58,16 @@ function toProtoProcessingInstructions(instructions) {
84
58
  result.push({ key: INSTRUCTION_KEY.ROUTING_KEY, value: { textValue: instructions.routingKey } });
85
59
  }
86
60
  if (instructions.priority !== undefined) {
87
- result.push({ key: INSTRUCTION_KEY.PRIORITY, value: { numberValue: BigInt(instructions.priority) } });
61
+ result.push({
62
+ key: INSTRUCTION_KEY.PRIORITY,
63
+ value: { numberValue: BigInt(instructions.priority) },
64
+ });
88
65
  }
89
66
  if (instructions.timeoutMs !== undefined) {
90
- result.push({ key: INSTRUCTION_KEY.TIMEOUT, value: { numberValue: BigInt(instructions.timeoutMs) } });
67
+ result.push({
68
+ key: INSTRUCTION_KEY.TIMEOUT,
69
+ value: { numberValue: BigInt(instructions.timeoutMs) },
70
+ });
91
71
  }
92
72
  return result;
93
73
  }
@@ -104,102 +84,8 @@ function defaultQueryInstructions(timeoutMs) {
104
84
  { key: INSTRUCTION_KEY.CLIENT_SUPPORTS_STREAMING, value: { booleanValue: true } },
105
85
  ];
106
86
  }
107
- /**
108
- * Build the gRPC metadata headers required by Axon Server. AxonIQ-Context
109
- * is mandatory (identifies the tenant/context); AxonIQ-Access-Token is
110
- * optional auth. Both must be attached to every outbound stream/RPC —
111
- * preserved verbatim from the legacy enhancer.
112
- */
113
- function createAxonMetadata(config) {
114
- const metadata = new Metadata();
115
- metadata.set("AxonIQ-Context", config.context);
116
- if (config.token) {
117
- metadata.set("AxonIQ-Access-Token", config.token);
118
- }
119
- return metadata;
120
- }
121
- /**
122
- * Connect to Axon Server and build the components it backs.
123
- *
124
- * `serializer` and `unitOfWorkFactory` are arguments rather than slot lookups:
125
- * the buses serialize payloads with the former and run every inbound command /
126
- * query in the latter, so they must be the SAME instances the rest of the app
127
- * uses. Pass the ones you hand to `kronos`.
128
- */
129
- export async function axonServer(options) {
130
- const config = options;
131
- const { serializer, unitOfWorkFactory, resilience } = config;
132
- const connection = await withRetry(async () => connectToAxonServer(config), {
133
- event: "initial-connect",
134
- ...resilience,
135
- });
136
- // Health-check ping with warn-then-continue (D-100). AxonServerConnection has
137
- // no dedicated probe surface today; the gRPC channel itself is created
138
- // eagerly in connectToAxonServer so the meaningful probe is a round-trip — we
139
- // approximate via a soft no-op promise that satisfies the threshold contract.
140
- // Real network failure is surfaced by the first bus call against the channel.
141
- await healthCheck(async () => undefined, {
142
- thresholdMs: resilience?.healthCheckThresholdMs,
143
- log: resilience?.log,
144
- });
145
- // One latch per bus, drained in close() before the transport goes away.
146
- const commandLatch = shutdownLatch();
147
- const queryLatch = shutdownLatch();
148
- const busLatches = [commandLatch, queryLatch];
149
- // The connection is live before anything below is built, so the buses open
150
- // their gRPC streams for real and `subscribe()` reaches the wire immediately —
151
- // no lazy proxy, no subscription buffering, no readiness promise.
152
- const components = {
153
- eventStore: axonServerEventStore(connection, serializer),
154
- snapshotStore: axonServerSnapshotStore(connection, serializer),
155
- commandBus: distributedCommandBus(connection, unitOfWorkFactory, commandLatch, serializer, config.commandFlowControl, config.commandLoadFactor, resilience),
156
- queryBus: distributedQueryBus(connection, unitOfWorkFactory, queryLatch, serializer, config.queryFlowControl, config.shortcutQueriesToLocalHandlers, config.queryTimeoutMs, resilience),
157
- };
158
- // Built here, started by the control plane (or by the caller). Constructing it
159
- // eagerly is what lets the control plane be a separate object at all — and it
160
- // keeps `platformService` tuning and `stop()` ownership in one place, so the
161
- // documented shutdown order below holds whether or not anyone opted in.
162
- const platform = platformConnection(connection, config.platformService);
163
- return {
164
- components,
165
- platform,
166
- async start() {
167
- // RECONNECT DETECTION IS DATA PATH. The heartbeat on the platform stream
168
- // is what notices a dead channel and calls `connection.reconnect()`; both
169
- // buses above hook `connection.onReconnect(...)` to rebuild their own
170
- // streams. Arming it used to be a side effect of `platform.start()`, which
171
- // only `axonServerControlPlane(...)` calls — so a service that never opted
172
- // into remote administration had NO reconnect detection at all and would
173
- // sit on a dead channel forever. It is armed here, unconditionally,
174
- // independent of whether anyone administers this service.
175
- //
176
- // `armConnectionMonitoring()` opens the stream and starts heartbeats but
177
- // arms NO processor status reporting; that stays the control plane's, and
178
- // a later `platform.start()` adds it to this same live stream. Both calls
179
- // are idempotent, so either order works.
180
- await platform.armConnectionMonitoring();
181
- // The only thing the data path has to wait for: Axon Server's
182
- // command/query routing tables registering the subscribe frames sent on
183
- // the BUS streams. It cannot be derived from the platform stream, because
184
- // subscribes travel on a different stream entirely — and the platform
185
- // stream's own `subscriptionsAcked()` latch says nothing about them (it
186
- // latches unconditionally once `register` has been flushed; see
187
- // platform-service.ts). So this barrier is the settle wait, and it is
188
- // deliberately independent of whether the platform stream is up at all.
189
- // The legacy enhancer carried the same 1s wait.
190
- await new Promise((r) => setTimeout(r, config.busSubscriptionAckDelayMs ?? 1000));
191
- },
192
- async close() {
193
- await Promise.all(busLatches.map((l) => l.initiateShutdown()));
194
- // Idempotent, and independent of `control.close()` — a backend that was
195
- // never administered still stops a platform stream someone else started.
196
- platform.stop();
197
- connection.close();
198
- },
199
- };
200
- }
201
87
  // ---------------------------------------------------------------------------
202
- // Shared payload helpers (moved verbatim from legacy enhancer)
88
+ // Shared payload helpers
203
89
  // ---------------------------------------------------------------------------
204
90
  function createPayloadHelpers(serializer) {
205
91
  return {
@@ -214,68 +100,66 @@ function createPayloadHelpers(serializer) {
214
100
  };
215
101
  }
216
102
  // ---------------------------------------------------------------------------
217
- // Distributed Command Bus
218
- //
219
- // Bus implementation moved verbatim from the legacy enhancer with TWO
220
- // behavioural additions per D-97:
221
- // 1) reestablishStream() body wrapped in withRetry({ event: "reconnect" })
222
- // 2) inbound-stream backoff replaced by the same withRetry path
223
- //
224
- // Axon-specific protocol invariants preserved BYTE-FOR-BYTE:
225
- // - AxonIQ-Context + AxonIQ-Access-Token gRPC metadata headers via
226
- // createAxonMetadata(connection.config)
227
- // - permits-AFTER-subscriptions ordering on reestablishStreamBody (subs
228
- // are sent BEFORE grantPermits() in the reconnect path; the initial
229
- // handshake matches this — see ensureStreamStarted's grantPermits call
230
- // in subscribe()).
103
+ // Axon Server Command Bus
231
104
  // ---------------------------------------------------------------------------
232
105
  /**
233
- * A command bus backed by Axon Server.
106
+ * A command bus backed by Axon Server, over YOUR next bus.
234
107
  *
235
- * - **Outbound dispatch**: Always goes through Axon Server via the unary Dispatch RPC.
236
- * Axon Server routes the command to the appropriate node (which may be this one).
237
- * - **Local segment**: Handlers subscribed via `subscribe()` are registered with
238
- * Axon Server (so other nodes can route to us) and stored locally. When Axon Server
239
- * routes an inbound command to this node, it's executed on the local segment
240
- * within a UnitOfWork.
241
- */
242
- /**
243
- * A command bus backed by Axon Server.
108
+ * - **Outbound dispatch**: ALWAYS through Axon Server, via the unary Dispatch
109
+ * RPC. Axon Server routes the command to the appropriate node (which may be
110
+ * this one). There is deliberately no client-side prefer-next fork: the hub
111
+ * is the router, and short-circuiting it would silently defeat load factors,
112
+ * priorities and routing keys.
113
+ * - **Inbound**: a command the server routes here is dispatched into `next` —
114
+ * not into a privately-held handler map. That is what makes the unit-of-work
115
+ * policy you chose for `next` (say `postgresUnitOfWork(unitOfWork, pg)`)
116
+ * apply to server-routed work exactly as it applies to work this process
117
+ * originated. It is also why this function takes no `unitOfWork` argument:
118
+ * `next` carries that policy now.
119
+ * - **subscribe**: registers the handler on `next` AND announces the name to
120
+ * Axon Server, so other nodes can route to us.
244
121
  *
245
- * ## Correlation lineage and the interceptor layer
122
+ * ## correlation and the interceptor layer
246
123
  *
247
- * The returned bus is wrapped in {@link interceptingCommandBus} carrying
248
- * {@link correlationDataDispatchInterceptor}, so lineage is stamped onto the
249
- * outgoing message BEFORE it is serialized onto the wire.
124
+ * The returned bus stamps no correlation of its own. A host that wants it wraps the
125
+ * OUTERMOST bus:
126
+ *
127
+ * ```ts
128
+ * interceptingCommandBus(axonServerCommandBus(next, conn), correlation)
129
+ * ```
130
+ *
131
+ * so whatever a host adds runs BEFORE the message is serialized onto the wire.
132
+ * Correlation itself is usually already on `message.metadata` by then — `ctx.send`
133
+ * stamps the unit of work's correlation data before any bus sees the message.
250
134
  *
251
135
  * This is precisely how the Java client does it. AF4's `AxonServerCommandBus`
252
136
  * holds its own `DispatchInterceptors` and dispatches as
253
137
  * `doDispatch(dispatchInterceptors.intercept(commandMessage), cb)` — one call
254
- * site, at the top, ahead of any routing; and its `doDispatch` (like this one)
255
- * always goes to the server, letting Axon Server decide where the command lands.
256
- * AF5 keeps the property via decorator order:
257
- * `DISTRIBUTED_COMMAND_BUS_ORDER = InterceptingCommandBus.DECORATION_ORDER - 50`
258
- * stacks `InterceptingCommandBus → DistributedCommandBus → SimpleCommandBus`.
259
- *
260
- * Before this wrap, an Axon-backed service lost lineage on EVERY command: the
261
- * only registration of `correlationDataDispatchInterceptor` lives in
262
- * `@kronos-ts/app`'s in-memory default bus, and `components.commandBus` from
263
- * this backend replaces it wholesale.
138
+ * site, at the top, ahead of any routing. AF5 keeps the property via decorator
139
+ * order: `DISTRIBUTED_COMMAND_BUS_ORDER = InterceptingCommandBus.DECORATION_ORDER - 50`
140
+ * stacks `InterceptingCommandBus DistributedCommandBus LocalCommandBus`.
264
141
  *
265
- * No double-application risk: the local segment here is a plain handler map, not
266
- * a `CommandBus`, so this is the only interceptor in the chain. Inbound commands
267
- * from the server are invoked through that map directly, which matches AF —
268
- * `CommandProcessingTask` runs the local segment WITHOUT re-running dispatch
269
- * interceptors.
142
+ * If `next` is itself an intercepting bus, a server-routed command sees
143
+ * `correlation` twice. That is harmless: both of its fields are `??` seeds, so the
144
+ * second application finds them set and changes nothing.
270
145
  */
271
- export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatch, serializer, flowControl, commandLoadFactor, resilience) {
272
- const metadata = createAxonMetadata(connection.config);
146
+ export function axonServerCommandBus(next, conn, options = {}) {
147
+ const { connection, serializer, metadata: axonMetadata, } = contextView(conn, options.context ?? conn.connection.config.context);
148
+ const shutdownLatch = conn.shutdown;
149
+ const resilience = options.resilience ?? conn.resilience;
150
+ const metadata = axonMetadata();
273
151
  const { serializePayload, deserializePayload } = createPayloadHelpers(serializer);
274
- const PERMITS = BigInt(flowControl?.permits ?? Number(DEFAULT_PERMITS));
275
- const THRESHOLD = BigInt(flowControl?.refillThreshold ?? Number(DEFAULT_THRESHOLD));
276
- // Local segment handlers that execute on this node
277
- const localSegment = new Map();
278
- // Bidirectional stream for handler registration + inbound command handling
152
+ const PERMITS = BigInt(options.flowControl?.permits ?? Number(DEFAULT_PERMITS));
153
+ const THRESHOLD = BigInt(options.flowControl?.refillThreshold ?? Number(DEFAULT_THRESHOLD));
154
+ const loadFactor = options.loadFactor ?? DEFAULT_LOAD_FACTOR;
155
+ /**
156
+ * The names this node announced to Axon Server. The handlers themselves live
157
+ * on `next`; this set exists so an inbound command for a name we never
158
+ * subscribed still answers NO_HANDLER_FOR_COMMAND rather than whatever
159
+ * `next.dispatch` happens to throw — and so a reconnect can re-announce.
160
+ */
161
+ const subscribedNames = new Set();
162
+ // Bidirectional stream for handler subscription + inbound command handling
279
163
  let outbound = outboundStream();
280
164
  let streamStarted = false;
281
165
  let permits = 0n;
@@ -294,8 +178,20 @@ export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatc
294
178
  });
295
179
  permits += PERMITS;
296
180
  }
181
+ function sendSubscribe(commandName) {
182
+ outbound.send({
183
+ subscribe: {
184
+ messageId: generateIdentifier(),
185
+ command: commandName,
186
+ componentName: connection.config.componentName,
187
+ clientId: connection.config.clientId,
188
+ loadFactor,
189
+ },
190
+ instructionId: generateIdentifier(),
191
+ });
192
+ }
297
193
  /**
298
- * Re-establish the bidirectional stream and re-subscribe all handlers.
194
+ * Re-establish the bidirectional stream and re-announce all handlers.
299
195
  * Called on stream error or when the connection reconnects.
300
196
  *
301
197
  * ORDER (preserves Axon-specific invariant): subscriptions are
@@ -309,18 +205,8 @@ export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatc
309
205
  permits = 0n;
310
206
  ensureStreamStarted();
311
207
  // Re-subscribe all handlers FIRST
312
- for (const commandName of localSegment.keys()) {
313
- outbound.send({
314
- subscribe: {
315
- messageId: generateIdentifier(),
316
- command: commandName,
317
- componentName: connection.config.componentName,
318
- clientId: connection.config.clientId,
319
- loadFactor: commandLoadFactor ?? 100,
320
- },
321
- instructionId: generateIdentifier(),
322
- });
323
- }
208
+ for (const commandName of subscribedNames)
209
+ sendSubscribe(commandName);
324
210
  // Permits AFTER subscriptions (Axon-specific ordering invariant)
325
211
  grantPermits();
326
212
  }
@@ -336,7 +222,7 @@ export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatc
336
222
  connection.onReconnect(() => {
337
223
  if (!shutdownLatch.shuttingDown && streamStarted) {
338
224
  reestablishStreamWithRetry().catch((err) => {
339
- console.error("Distributed command bus: reconnect retries exhausted", err);
225
+ console.error("Axon Server command bus: reconnect retries exhausted", err);
340
226
  });
341
227
  }
342
228
  });
@@ -348,11 +234,10 @@ export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatc
348
234
  permits--;
349
235
  const proto = message.command;
350
236
  const commandName = proto.name;
351
- const handler = localSegment.get(commandName);
352
237
  let resultPayload;
353
238
  let errorCode = "";
354
239
  let errorMsg = "";
355
- if (handler) {
240
+ if (subscribedNames.has(commandName)) {
356
241
  try {
357
242
  const commandMessage = {
358
243
  kind: "command",
@@ -362,8 +247,12 @@ export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatc
362
247
  metadata: metadataFromProto(proto.metaData),
363
248
  timestamp: Number(proto.timestamp),
364
249
  };
365
- // Execute inbound command within its own UnitOfWork (AF5 parity)
366
- resultPayload = await unitOfWorkRunner(commandMessage.metadata, () => handler(commandMessage));
250
+ // Through the LOCAL BUS, so the caller's unit-of-work policy runs.
251
+ // AF parity is preserved: `CommandProcessingTask` runs the next
252
+ // segment without re-running dispatch interceptors, and a `next`
253
+ // that happens to carry `correlation` re-applies a pair of `??` seeds
254
+ // that are already set.
255
+ resultPayload = await next.dispatch(commandMessage);
367
256
  }
368
257
  catch (err) {
369
258
  errorCode = AxonServerErrorCode.COMMAND_EXECUTION_ERROR;
@@ -372,7 +261,7 @@ export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatc
372
261
  }
373
262
  else {
374
263
  errorCode = AxonServerErrorCode.NO_HANDLER_FOR_COMMAND;
375
- errorMsg = `No local handler for command "${commandName}"`;
264
+ errorMsg = `No next handler for command "${commandName}"`;
376
265
  }
377
266
  // Send response back to Axon Server
378
267
  outbound.send({
@@ -381,11 +270,14 @@ export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatc
381
270
  requestIdentifier: proto.messageIdentifier,
382
271
  errorCode,
383
272
  errorMessage: errorCode
384
- ? { message: errorMsg, location: connection.config.componentName, details: [], errorCode }
385
- : undefined,
386
- payload: resultPayload !== undefined
387
- ? serializePayload("result", resultPayload)
273
+ ? {
274
+ message: errorMsg,
275
+ location: connection.config.componentName,
276
+ details: [],
277
+ errorCode,
278
+ }
388
279
  : undefined,
280
+ payload: resultPayload !== undefined ? serializePayload("result", resultPayload) : undefined,
389
281
  metaData: {},
390
282
  processingInstructions: [],
391
283
  },
@@ -406,14 +298,20 @@ export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatc
406
298
  return;
407
299
  if (String(err).includes("Connection dropped"))
408
300
  return;
409
- console.error("Distributed command bus: inbound stream error, attempting re-establishment via withRetry", err);
301
+ console.error("Axon Server command bus: inbound stream error, attempting re-establishment via withRetry", err);
410
302
  await reestablishStreamWithRetry().catch((retryErr) => {
411
- console.error("Distributed command bus: reconnect retries exhausted", retryErr);
303
+ console.error("Axon Server command bus: reconnect retries exhausted", retryErr);
412
304
  });
413
305
  }
414
306
  }
415
- const routing = {
416
- async dispatch(message) {
307
+ return {
308
+ async dispatch(unstamped) {
309
+ // A transport is not a task: it has no unit of work, so it has no clock.
310
+ // A message that reaches the wire with no instant yet gets one from system
311
+ // time here — the envelope crosses a process boundary and must be fully
312
+ // formed. A locally-shortcut message is handed to `next` untouched
313
+ // instead, so the task that handles it supplies the instant.
314
+ const message = { ...unstamped, timestamp: unstamped.timestamp ?? Date.now() };
417
315
  const activity = shutdownLatch.registerActivity();
418
316
  try {
419
317
  const commandName = qualifiedNameToString(message.name);
@@ -437,58 +335,56 @@ export function distributedCommandBus(connection, unitOfWorkRunner, shutdownLatc
437
335
  }
438
336
  },
439
337
  subscribe(commandName, handler) {
440
- localSegment.set(commandName, handler);
338
+ subscribedNames.add(commandName);
339
+ next.subscribe(commandName, handler);
441
340
  ensureStreamStarted();
442
341
  // Subscription FIRST
443
- outbound.send({
444
- subscribe: {
445
- messageId: generateIdentifier(),
446
- command: commandName,
447
- componentName: connection.config.componentName,
448
- clientId: connection.config.clientId,
449
- loadFactor: commandLoadFactor ?? 100,
450
- },
451
- instructionId: generateIdentifier(),
452
- });
342
+ sendSubscribe(commandName);
453
343
  // Permits AFTER subscription (Axon-specific ordering invariant)
454
344
  grantPermits();
455
345
  },
456
346
  };
457
- // Interception OUTSIDE routing — see the note on this function.
458
- const bus = interceptingCommandBus(routing);
459
- bus.registerDispatchInterceptor(correlationDataDispatchInterceptor());
460
- return bus;
461
347
  }
462
348
  // ---------------------------------------------------------------------------
463
- // Distributed Query Bus
349
+ // Axon Server Query Bus
464
350
  // ---------------------------------------------------------------------------
465
351
  /**
466
- * A query bus backed by Axon Server.
352
+ * A query bus backed by Axon Server, over YOUR next bus.
467
353
  *
468
- * Same architecture as the distributed command bus:
469
- * - **Outbound dispatch**: Always through Axon Server.
470
- * - **Local segment**: Handlers registered here are stored locally and
471
- * registered with Axon Server for inbound routing. Inbound queries
472
- * are executed within a UnitOfWork.
354
+ * Same architecture as {@link axonServerCommandBus}: outbound dispatch goes
355
+ * through Axon Server, and a query the server routes here runs through `next`,
356
+ * so your unit-of-work policy applies to server-routed reads too. `subscribe`
357
+ * registers on `next` and announces the name to the server.
473
358
  *
474
- * Wrapped in {@link interceptingQueryBus} with
475
- * {@link correlationDataDispatchInterceptor}, matching AF4's
476
- * `AxonServerQueryBus`, which calls `dispatchInterceptors.intercept(...)` at the
477
- * top of `query`, `streamingQuery`, `scatterGather` and `subscriptionQuery`.
478
- * Because the wrap is outside, the `shortcutQueriesToLocalHandlers` branch in
479
- * `query()` gets identical lineage to the remote branch.
359
+ * The one asymmetry with commands is `shortcutQueriesToLocalHandlers` — Java
360
+ * has it for queries and not for commands, and so do we. When it is on and this
361
+ * node subscribed the name, `query()` goes straight to `next` and the caller's
362
+ * unit of work is passed through, so the next branch nests exactly as the
363
+ * in-process bus does.
364
+ *
365
+ * Correlation, if wanted, is `interceptingQueryBus(bus, correlation)` at the host,
366
+ * matching AF4's `AxonServerQueryBus`, which calls
367
+ * `dispatchInterceptors.intercept(...)` at the top of `query`, `streamingQuery`,
368
+ * `scatterGather` and `subscriptionQuery`. Because the wrap is outside, the
369
+ * shortcut branch gets identical correlation to the remote branch.
480
370
  *
481
371
  * KNOWN GAP: `subscriptionQuery` / `subscribeToUpdates` build their proto
482
372
  * straight from `message.metadata`, and `interceptingQueryBus` (in
483
- * `@kronos-ts/messaging`) forwards those two calls to the delegate without
484
- * running the dispatch chain. Closing that needs a messaging-package change.
373
+ * `@kronos-ts/core`) forwards those two calls to the delegate without
374
+ * running the dispatch chain. Closing that needs a core change.
485
375
  */
486
- export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch, serializer, flowControl, shortcutQueriesToLocalHandlers, queryTimeoutMs, resilience) {
487
- const metadata = createAxonMetadata(connection.config);
488
- const PERMITS = BigInt(flowControl?.permits ?? Number(DEFAULT_PERMITS));
489
- const THRESHOLD = BigInt(flowControl?.refillThreshold ?? Number(DEFAULT_THRESHOLD));
376
+ export function axonServerQueryBus(next, conn, options = {}) {
377
+ const { connection, serializer, metadata: axonMetadata, } = contextView(conn, options.context ?? conn.connection.config.context);
378
+ const shutdownLatch = conn.shutdown;
379
+ const resilience = options.resilience ?? conn.resilience;
380
+ const metadata = axonMetadata();
381
+ const PERMITS = BigInt(options.flowControl?.permits ?? Number(DEFAULT_PERMITS));
382
+ const THRESHOLD = BigInt(options.flowControl?.refillThreshold ?? Number(DEFAULT_THRESHOLD));
383
+ const shortcutQueriesToLocalHandlers = options.shortcutQueriesToLocalHandlers ?? false;
384
+ const queryTimeoutMs = options.timeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS;
490
385
  const { serializePayload, deserializePayload } = createPayloadHelpers(serializer);
491
- const localSegment = new Map();
386
+ /** Query names announced to Axon Server; the handlers live on `next`. */
387
+ const subscribedNames = new Set();
492
388
  // Local subscription store — subscription queries opened by THIS instance.
493
389
  // Inbound updates from the server are offered into these via the Subscription RPC loop.
494
390
  const subscriptions = new Map();
@@ -515,8 +411,20 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
515
411
  });
516
412
  permits += PERMITS;
517
413
  }
414
+ function sendSubscribe(queryName) {
415
+ outbound.send({
416
+ subscribe: {
417
+ messageId: generateIdentifier(),
418
+ query: queryName,
419
+ resultName: "",
420
+ componentName: connection.config.componentName,
421
+ clientId: connection.config.clientId,
422
+ },
423
+ instructionId: generateIdentifier(),
424
+ });
425
+ }
518
426
  /**
519
- * Re-establish the bidirectional stream and re-subscribe all handlers.
427
+ * Re-establish the bidirectional stream and re-announce all handlers.
520
428
  * Called on stream error or when the connection reconnects.
521
429
  *
522
430
  * ORDER (preserves Axon-specific invariant): subscriptions are
@@ -528,18 +436,8 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
528
436
  streamStarted = false;
529
437
  permits = 0n;
530
438
  ensureStreamStarted();
531
- for (const queryName of localSegment.keys()) {
532
- outbound.send({
533
- subscribe: {
534
- messageId: generateIdentifier(),
535
- query: queryName,
536
- resultName: "",
537
- componentName: connection.config.componentName,
538
- clientId: connection.config.clientId,
539
- },
540
- instructionId: generateIdentifier(),
541
- });
542
- }
439
+ for (const queryName of subscribedNames)
440
+ sendSubscribe(queryName);
543
441
  grantQueryPermits();
544
442
  }
545
443
  async function reestablishStreamWithRetry() {
@@ -554,7 +452,7 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
554
452
  connection.onReconnect(() => {
555
453
  if (!shutdownLatch.shuttingDown && streamStarted) {
556
454
  reestablishStreamWithRetry().catch((err) => {
557
- console.error("Distributed query bus: reconnect retries exhausted", err);
455
+ console.error("Axon Server query bus: reconnect retries exhausted", err);
558
456
  });
559
457
  }
560
458
  });
@@ -568,11 +466,10 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
568
466
  const queryName = proto.query;
569
467
  const payload = deserializePayload(proto.payload?.data, proto.payload?.type, proto.payload?.revision);
570
468
  handlerSubscriptions.set(subId, { queryName, payload });
571
- const handler = localSegment.get(queryName);
572
469
  let resultPayload;
573
470
  let errorCode = "";
574
471
  let errorMsg = "";
575
- if (handler) {
472
+ if (subscribedNames.has(queryName)) {
576
473
  try {
577
474
  const queryMessage = {
578
475
  kind: "query",
@@ -582,9 +479,7 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
582
479
  metadata: metadataFromProto(proto.metaData ?? {}),
583
480
  timestamp: Number(proto.timestamp),
584
481
  };
585
- resultPayload = await unitOfWorkRunner(queryMessage.metadata, async () => {
586
- return handler(queryMessage);
587
- });
482
+ resultPayload = await next.query(queryMessage);
588
483
  }
589
484
  catch (err) {
590
485
  errorCode = AxonServerErrorCode.QUERY_EXECUTION_ERROR;
@@ -593,11 +488,9 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
593
488
  }
594
489
  else {
595
490
  errorCode = AxonServerErrorCode.NO_HANDLER_FOR_QUERY;
596
- errorMsg = `No local handler for query "${queryName}"`;
491
+ errorMsg = `No next handler for query "${queryName}"`;
597
492
  }
598
- const responseSerialized = resultPayload !== undefined
599
- ? serializePayload("result", resultPayload)
600
- : undefined;
493
+ const responseSerialized = resultPayload !== undefined ? serializePayload("result", resultPayload) : undefined;
601
494
  outbound.send({
602
495
  subscriptionQueryResponse: {
603
496
  messageIdentifier: generateIdentifier(),
@@ -607,7 +500,12 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
607
500
  requestIdentifier: proto.messageIdentifier,
608
501
  errorCode,
609
502
  errorMessage: errorCode
610
- ? { message: errorMsg, location: connection.config.componentName, details: [], errorCode }
503
+ ? {
504
+ message: errorMsg,
505
+ location: connection.config.componentName,
506
+ details: [],
507
+ errorCode,
508
+ }
611
509
  : undefined,
612
510
  payload: responseSerialized,
613
511
  metaData: {},
@@ -635,11 +533,10 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
635
533
  permits--;
636
534
  const proto = message.query;
637
535
  const queryName = proto.query;
638
- const handler = localSegment.get(queryName);
639
536
  let resultPayload;
640
537
  let errorCode = "";
641
538
  let errorMsg = "";
642
- if (handler) {
539
+ if (subscribedNames.has(queryName)) {
643
540
  try {
644
541
  const queryMessage = {
645
542
  kind: "query",
@@ -649,9 +546,9 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
649
546
  metadata: metadataFromProto(proto.metaData),
650
547
  timestamp: Number(proto.timestamp),
651
548
  };
652
- resultPayload = await unitOfWorkRunner(queryMessage.metadata, async () => {
653
- return handler(queryMessage);
654
- });
549
+ // Through the LOCAL BUS: no unit of work is handed in, so `next`
550
+ // opens one under whatever policy the caller gave it.
551
+ resultPayload = await next.query(queryMessage);
655
552
  }
656
553
  catch (err) {
657
554
  errorCode = AxonServerErrorCode.QUERY_EXECUTION_ERROR;
@@ -660,7 +557,7 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
660
557
  }
661
558
  else {
662
559
  errorCode = AxonServerErrorCode.NO_HANDLER_FOR_QUERY;
663
- errorMsg = `No local handler for query "${queryName}"`;
560
+ errorMsg = `No next handler for query "${queryName}"`;
664
561
  }
665
562
  outbound.send({
666
563
  queryResponse: {
@@ -668,11 +565,14 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
668
565
  requestIdentifier: proto.messageIdentifier,
669
566
  errorCode,
670
567
  errorMessage: errorCode
671
- ? { message: errorMsg, location: connection.config.componentName, details: [], errorCode }
672
- : undefined,
673
- payload: resultPayload !== undefined
674
- ? serializePayload("result", resultPayload)
568
+ ? {
569
+ message: errorMsg,
570
+ location: connection.config.componentName,
571
+ details: [],
572
+ errorCode,
573
+ }
675
574
  : undefined,
575
+ payload: resultPayload !== undefined ? serializePayload("result", resultPayload) : undefined,
676
576
  metaData: {},
677
577
  processingInstructions: [],
678
578
  },
@@ -699,33 +599,38 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
699
599
  return;
700
600
  if (String(err).includes("Connection dropped"))
701
601
  return;
702
- console.error("Distributed query bus: inbound stream error, attempting re-establishment via withRetry", err);
602
+ console.error("Axon Server query bus: inbound stream error, attempting re-establishment via withRetry", err);
703
603
  await reestablishStreamWithRetry().catch((retryErr) => {
704
- console.error("Distributed query bus: reconnect retries exhausted", retryErr);
604
+ console.error("Axon Server query bus: reconnect retries exhausted", retryErr);
705
605
  });
706
606
  }
707
607
  }
708
608
  const routing = {
709
- async query(message) {
609
+ async query(unstamped, uow) {
710
610
  const activity = shutdownLatch.registerActivity();
711
611
  try {
712
- const queryName = qualifiedNameToString(message.name);
713
- // Local shortcut — handle locally if handler is co-located
714
- if (shortcutQueriesToLocalHandlers) {
715
- const localHandler = localSegment.get(queryName);
716
- if (localHandler) {
717
- return unitOfWorkRunner(message.metadata, async () => {
718
- return localHandler(message);
719
- });
720
- }
612
+ const queryName = qualifiedNameToString(unstamped.name);
613
+ // Local shortcut — handle locally if a handler is co-located. The
614
+ // caller's unit of work is passed straight through, so `next` makes the
615
+ // nest-or-open decision on the HANDLE exactly as it does for an
616
+ // in-process read: a live unit of work handed in by `ctx.query` is
617
+ // reused so the consulting read shares the caller's transaction.
618
+ if (shortcutQueriesToLocalHandlers && subscribedNames.has(queryName)) {
619
+ return next.query(unstamped, uow);
721
620
  }
621
+ // A transport is not a task: it has no unit of work, so it has no clock.
622
+ // A message that reaches the wire with no instant yet gets one from system
623
+ // time here — the envelope crosses a process boundary and must be fully
624
+ // formed. A locally-shortcut message is handed to `next` untouched
625
+ // instead, so the task that handles it supplies the instant.
626
+ const message = { ...unstamped, timestamp: unstamped.timestamp ?? Date.now() };
722
627
  const responseStream = connection.queries.query({
723
628
  messageIdentifier: message.identifier,
724
629
  query: queryName,
725
630
  timestamp: BigInt(message.timestamp),
726
631
  payload: serializePayload(queryName, message.payload),
727
632
  metaData: metadataToProto(message.metadata),
728
- processingInstructions: defaultQueryInstructions(queryTimeoutMs ?? 3600000),
633
+ processingInstructions: defaultQueryInstructions(queryTimeoutMs),
729
634
  clientId: connection.config.clientId,
730
635
  componentName: connection.config.componentName,
731
636
  }, { metadata });
@@ -742,22 +647,15 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
742
647
  }
743
648
  },
744
649
  subscribe(queryName, handler) {
745
- localSegment.set(queryName, handler);
650
+ subscribedNames.add(queryName);
651
+ next.subscribe(queryName, handler);
746
652
  ensureStreamStarted();
747
- outbound.send({
748
- subscribe: {
749
- messageId: generateIdentifier(),
750
- query: queryName,
751
- resultName: "",
752
- componentName: connection.config.componentName,
753
- clientId: connection.config.clientId,
754
- },
755
- instructionId: generateIdentifier(),
756
- });
653
+ sendSubscribe(queryName);
757
654
  // Permits AFTER subscription (Axon-specific ordering invariant)
758
655
  grantQueryPermits();
759
656
  },
760
- subscriptionQuery(message, bufferSize) {
657
+ subscriptionQuery(unstamped, bufferSize) {
658
+ const message = { ...unstamped, timestamp: unstamped.timestamp ?? Date.now() };
761
659
  const queryId = message.identifier;
762
660
  if (subscriptions.has(queryId)) {
763
661
  throw new Error(`Subscription query already registered for identifier "${queryId}"`);
@@ -777,7 +675,7 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
777
675
  timestamp: BigInt(message.timestamp),
778
676
  payload: serializePayload(queryName, message.payload),
779
677
  metaData: metadataToProto(message.metadata),
780
- processingInstructions: defaultQueryInstructions(queryTimeoutMs ?? 3600000),
678
+ processingInstructions: defaultQueryInstructions(queryTimeoutMs),
781
679
  clientId: connection.config.clientId,
782
680
  componentName: connection.config.componentName,
783
681
  },
@@ -793,7 +691,7 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
793
691
  timestamp: BigInt(message.timestamp),
794
692
  payload: serializePayload(queryName, message.payload),
795
693
  metaData: metadataToProto(message.metadata),
796
- processingInstructions: defaultQueryInstructions(queryTimeoutMs ?? 3600000),
694
+ processingInstructions: defaultQueryInstructions(queryTimeoutMs),
797
695
  clientId: connection.config.clientId,
798
696
  componentName: connection.config.componentName,
799
697
  },
@@ -831,7 +729,8 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
831
729
  break;
832
730
  }
833
731
  else if (response.completeExceptionally) {
834
- handler.completeExceptionally(new Error(response.completeExceptionally.errorMessage?.message ?? "Subscription query failed"));
732
+ handler.completeExceptionally(new Error(response.completeExceptionally.errorMessage?.message ??
733
+ "Subscription query failed"));
835
734
  break;
836
735
  }
837
736
  }
@@ -863,7 +762,8 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
863
762
  },
864
763
  };
865
764
  },
866
- subscribeToUpdates(message, bufferSize) {
765
+ subscribeToUpdates(unstamped, bufferSize) {
766
+ const message = { ...unstamped, timestamp: unstamped.timestamp ?? Date.now() };
867
767
  const queryId = message.identifier;
868
768
  if (subscriptions.has(queryId)) {
869
769
  throw new Error(`Subscription query already registered for identifier "${queryId}"`);
@@ -957,8 +857,6 @@ export function distributedQueryBus(connection, unitOfWorkRunner, shutdownLatch,
957
857
  });
958
858
  },
959
859
  };
960
- const bus = interceptingQueryBus(routing);
961
- bus.registerDispatchInterceptor(correlationDataDispatchInterceptor());
962
- return bus;
860
+ return routing;
963
861
  }
964
862
  //# sourceMappingURL=axon-server.js.map