@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
@@ -22,7 +22,7 @@ import { connectToAxonServer } from "./connection.js"
22
22
  * await manager.disconnectAll()
23
23
  * ```
24
24
  */
25
- export interface AxonServerConnectionManager {
25
+ export type AxonServerConnectionManager = {
26
26
  /**
27
27
  * Get or create a connection for the given context.
28
28
  * Connections are created lazily and cached.
package/src/connection.ts CHANGED
@@ -1,16 +1,29 @@
1
- import { createChannel, createClient, type Channel, type Client, type ChannelCredentials } from "nice-grpc"
1
+ import {
2
+ createChannel,
3
+ createClient,
4
+ type Channel,
5
+ type Client,
6
+ type ChannelCredentials,
7
+ } from "nice-grpc"
2
8
  import { ChannelCredentials as GrpcChannelCredentials } from "@grpc/grpc-js"
3
- import { Metadata } from "nice-grpc"
4
9
  import { readFileSync } from "node:fs"
10
+ import type { Serializer } from "@kronos-ts/core"
11
+ import { withRetry, healthCheck, type ResilienceConfig } from "./resilience.js"
5
12
  import { PlatformServiceDefinition } from "./generated/control.js"
6
13
  import { CommandServiceDefinition } from "./generated/command.js"
7
14
  import { QueryServiceDefinition } from "./generated/query.js"
8
15
  import { DcbEventStoreDefinition, DcbSnapshotStoreDefinition } from "./generated/dcb.js"
16
+ import { shutdownLatch, type ShutdownLatch } from "./shutdown-latch.js"
17
+ import {
18
+ platformConnection,
19
+ type PlatformConnection,
20
+ type PlatformServiceOptions,
21
+ } from "./platform-service.js"
9
22
 
10
23
  /**
11
24
  * Configuration for connecting to Axon Server.
12
25
  */
13
- export interface AxonServerConnectionConfig {
26
+ export type AxonServerConnectionConfig = {
14
27
  /**
15
28
  * Host of the Axon Server. Defaults to "localhost".
16
29
  * For single-server setups.
@@ -84,13 +97,18 @@ export interface AxonServerConnectionConfig {
84
97
  }
85
98
  }
86
99
 
87
- export type ConnectionState = "disconnected" | "connecting" | "connected" | "reconnecting" | "closed"
100
+ export type ConnectionState =
101
+ | "disconnected"
102
+ | "connecting"
103
+ | "connected"
104
+ | "reconnecting"
105
+ | "closed"
88
106
 
89
107
  /**
90
108
  * An active connection to Axon Server, providing typed gRPC clients
91
109
  * for all services. Supports reconnection on failure.
92
110
  */
93
- export interface AxonServerConnection {
111
+ export type AxonServerConnection = {
94
112
  /** The underlying gRPC channel. */
95
113
  readonly channel: Channel
96
114
  /** Platform service — connection management, topology. */
@@ -159,7 +177,11 @@ export function connectToAxonServer(config: AxonServerConnectionConfig): AxonSer
159
177
  const rootCerts = sslConfig.certFile ? readFileSync(sslConfig.certFile) : null
160
178
  const clientKey = sslConfig.clientKeyFile ? readFileSync(sslConfig.clientKeyFile) : null
161
179
  const clientCert = sslConfig.clientCertFile ? readFileSync(sslConfig.clientCertFile) : null
162
- credentials = GrpcChannelCredentials.createSsl(rootCerts, clientKey, clientCert) as ChannelCredentials
180
+ credentials = GrpcChannelCredentials.createSsl(
181
+ rootCerts,
182
+ clientKey,
183
+ clientCert,
184
+ ) as ChannelCredentials
163
185
  }
164
186
 
165
187
  // gRPC channel options — keepalive to maintain persistent connections
@@ -170,9 +192,10 @@ export function connectToAxonServer(config: AxonServerConnectionConfig): AxonSer
170
192
  }
171
193
 
172
194
  // Build server address list for failover
173
- const serverAddresses = config.servers && config.servers.length > 0
174
- ? config.servers
175
- : [`${resolvedConfig.host}:${resolvedConfig.port}`]
195
+ const serverAddresses =
196
+ config.servers && config.servers.length > 0
197
+ ? config.servers
198
+ : [`${resolvedConfig.host}:${resolvedConfig.port}`]
176
199
 
177
200
  let currentServerIndex = 0
178
201
 
@@ -202,15 +225,29 @@ export function connectToAxonServer(config: AxonServerConnectionConfig): AxonSer
202
225
  let clients = createClients()
203
226
 
204
227
  const connection: AxonServerConnection = {
205
- get channel() { return channel },
206
- get platform() { return clients.platform },
207
- get commands() { return clients.commands },
208
- get queries() { return clients.queries },
209
- get eventStore() { return clients.eventStore },
210
- get snapshotStore() { return clients.snapshotStore },
228
+ get channel() {
229
+ return channel
230
+ },
231
+ get platform() {
232
+ return clients.platform
233
+ },
234
+ get commands() {
235
+ return clients.commands
236
+ },
237
+ get queries() {
238
+ return clients.queries
239
+ },
240
+ get eventStore() {
241
+ return clients.eventStore
242
+ },
243
+ get snapshotStore() {
244
+ return clients.snapshotStore
245
+ },
211
246
  config: resolvedConfig,
212
247
 
213
- get state() { return state },
248
+ get state() {
249
+ return state
250
+ },
214
251
 
215
252
  onReconnect(callback) {
216
253
  reconnectCallbacks.push(callback)
@@ -246,15 +283,17 @@ export function connectToAxonServer(config: AxonServerConnectionConfig): AxonSer
246
283
 
247
284
  // Notify listeners that we're back
248
285
  for (const cb of reconnectCallbacks) {
249
- try { cb() } catch { /* ignore listener errors */ }
286
+ try {
287
+ cb()
288
+ } catch {
289
+ /* ignore listener errors */
290
+ }
250
291
  }
251
292
  return
252
293
  } catch (err) {
253
294
  if (maxAttempts > 0 && attempt >= maxAttempts) {
254
295
  state = "disconnected"
255
- throw new Error(
256
- `Failed to reconnect after ${attempt} attempts: ${err}`,
257
- )
296
+ throw new Error(`Failed to reconnect after ${attempt} attempts: ${err}`)
258
297
  }
259
298
 
260
299
  // Exponential backoff: base interval * 2^attempt, capped at 30s
@@ -270,3 +309,218 @@ export function connectToAxonServer(config: AxonServerConnectionConfig): AxonSer
270
309
 
271
310
  return connection
272
311
  }
312
+
313
+ // ---------------------------------------------------------------------------
314
+ // The shared RESOURCE: one channel, the platform stream on it, start()/close()
315
+ // ---------------------------------------------------------------------------
316
+
317
+ /**
318
+ * Everything the shared Axon Server resource needs: where to dial, and how to
319
+ * put a payload on the wire.
320
+ *
321
+ * THE SERIALIZER IS A PROPERTY OF THIS CLIENT'S WIRE, not of a context and not
322
+ * of a bus. Every event, snapshot, command payload and query result this
323
+ * process exchanges with Axon Server goes through the same codec, so it is
324
+ * named once, here — which is also what leaves
325
+ * `axonServerEventStore(conn, context)` and `axonServerCommandBus(local, conn)`
326
+ * their honest two-argument shapes.
327
+ */
328
+ export type AxonServerConnectionOptions = AxonServerConnectionConfig & {
329
+ /** Payload codec for every message this client exchanges with Axon Server. */
330
+ serializer: Serializer
331
+ /** Retry / health-check policy for the initial connect and stream re-establishment. */
332
+ resilience?: Partial<ResilienceConfig>
333
+ /** Platform stream tuning — heartbeat and processor-status cadence. */
334
+ platformService?: PlatformServiceOptions
335
+ /**
336
+ * How long {@link AxonServerConnectionHandle.start} waits for Axon Server's
337
+ * routing tables to register the subscribe frames sent on the command/query
338
+ * streams. This is the entire data-path readiness barrier.
339
+ *
340
+ * It is a timed wait rather than an observed signal because nothing on the
341
+ * client can observe it: subscribes travel on the bus streams, and the
342
+ * platform stream — which is where an ack would arrive — is a different
343
+ * stream that Axon Server holds open silently after `register`. Default:
344
+ * 1000, matching the legacy enhancer.
345
+ */
346
+ busSubscriptionAckDelayMs?: number
347
+ }
348
+
349
+ /**
350
+ * What a context-scoped STORE borrows from the connection: the gRPC clients and
351
+ * the codec. Narrower than the handle on purpose — a test can drive a store
352
+ * with a fake `connection` and nothing else.
353
+ */
354
+ export type AxonServerStoreSource = {
355
+ readonly connection: AxonServerConnection
356
+ readonly serializer: Serializer
357
+ }
358
+
359
+ /** What a BUS borrows: a store's two, plus the drain latch and the retry policy. */
360
+ export type AxonServerBusSource = AxonServerStoreSource & {
361
+ /**
362
+ * The connection-wide drain latch. In-flight dispatches register on it and
363
+ * `close()` waits for them, so a bus never has the transport pulled out from
364
+ * under a call it is still awaiting.
365
+ */
366
+ readonly shutdown: ShutdownLatch
367
+ readonly resilience?: Partial<ResilienceConfig>
368
+ }
369
+
370
+ /** What the CONTROL PLANE borrows: the platform stream, and only that. */
371
+ export type AxonServerPlatformSource = {
372
+ readonly platform: PlatformConnection
373
+ }
374
+
375
+ /**
376
+ * The RESOURCE an Axon Server deployment shares: one gRPC channel, the platform
377
+ * stream riding on it, and the lifecycle pair.
378
+ *
379
+ * The stores and buses are NOT on it — `axonServerEventStore(conn, context)`,
380
+ * `axonServerCommandBus(local, conn)` and friends are plain functions over
381
+ * this, and a caller who wants only commands builds only that one. Multiple
382
+ * contexts share this ONE channel: the per-call `AxonIQ-Context` header is what
383
+ * separates them.
384
+ */
385
+ export type AxonServerConnectionHandle = AxonServerBusSource & AxonServerPlatformSource & {
386
+ /** Config after defaults — the resolved host, context, client id. */
387
+ readonly config: AxonServerConnection["config"]
388
+ /**
389
+ * DATA-PATH START. Two things, both data path:
390
+ *
391
+ * 1. arm heartbeat-driven reconnect detection on the platform stream, and
392
+ * 2. wait until Axon Server can route to the handlers subscribed on the bus
393
+ * streams.
394
+ *
395
+ * Call AFTER `kronos` — the subscribe frames must already be on the wire for
396
+ * the readiness wait to mean anything. Takes no arguments and arms no
397
+ * control-plane state.
398
+ */
399
+ start(): Promise<void>
400
+ /** Drain in-flight bus work, stop the platform stream, close the channel. */
401
+ close(): Promise<void>
402
+ }
403
+
404
+ /**
405
+ * Open the shared Axon Server connection.
406
+ *
407
+ * ```ts
408
+ * const axon = await axonServerConnection({
409
+ * componentName: "university-service",
410
+ * host, port,
411
+ * serializer: jsonSerializer(),
412
+ * })
413
+ * const eventStore = axonServerEventStore(axon, "default")
414
+ * const eventStore = axonServerSnapshottingEventStore(
415
+ * axonServerEventStore(axon, "default"), axon, "default")
416
+ * const commandBus = interceptingCommandBus(
417
+ * axonServerCommandBus(localCommandBus(unitOfWork), axon), correlation)
418
+ * const queryBus = interceptingQueryBus(
419
+ * axonServerQueryBus(localQueryBus(unitOfWork), axon), correlation)
420
+ *
421
+ * const app = kronos({ commandHandlers, queryHandlers })
422
+ * await axon.start() // readiness barrier: the server can route to us
423
+ * // opt in to remote administration
424
+ * const control = await axonServerControlPlane(axon, app.processors.values())
425
+ * // …
426
+ * await app.stop(); await control.close(); await axon.close()
427
+ * ```
428
+ *
429
+ * ASYNC ON PURPOSE. Connecting before anything is built is what removes the
430
+ * lazy proxies and subscribe-buffering the container version needed: by the
431
+ * time `kronos` subscribes a handler, the gRPC streams are already live.
432
+ *
433
+ * REMOTE ADMINISTRATION IS NOT IN HERE. Processor instructions and processor
434
+ * status reporting are the platform CONTROL PLANE — they are neither
435
+ * persistence nor transport, and lived on this object only because they share
436
+ * the gRPC connection. See `control-plane.ts`.
437
+ *
438
+ * Axon-specific protocol invariants are preserved byte-for-byte:
439
+ *
440
+ * - CLIENT_SUPPORTS_STREAMING capability advertised on every dispatched query;
441
+ * - AxonIQ-Context + AxonIQ-Access-Token gRPC metadata headers on every
442
+ * outbound stream/RPC (see `contextView`);
443
+ * - permits-AFTER-subscriptions stream ordering on the initial handshake AND
444
+ * on reconnect;
445
+ * - shutdown ordering: drain latch → platform.stop → connection.close.
446
+ */
447
+ export async function axonServerConnection(
448
+ options: AxonServerConnectionOptions,
449
+ ): Promise<AxonServerConnectionHandle> {
450
+ const {
451
+ serializer,
452
+ resilience,
453
+ platformService,
454
+ busSubscriptionAckDelayMs,
455
+ ...connectionConfig
456
+ } = options
457
+
458
+ const connection = await withRetry(async () => connectToAxonServer(connectionConfig), {
459
+ event: "initial-connect",
460
+ ...resilience,
461
+ })
462
+
463
+ // Health-check ping with warn-then-continue (D-100). AxonServerConnection has
464
+ // no dedicated probe surface today; the gRPC channel itself is created
465
+ // eagerly in connectToAxonServer so the meaningful probe is a round-trip — we
466
+ // approximate via a soft no-op promise that satisfies the threshold contract.
467
+ // Real network failure is surfaced by the first bus call against the channel.
468
+ await healthCheck(async () => undefined, {
469
+ thresholdMs: resilience?.healthCheckThresholdMs,
470
+ log: resilience?.log,
471
+ })
472
+
473
+ // ONE latch for the connection. Both buses ride the same channel, so "drain
474
+ // in-flight work before the transport goes away" is one question, not two.
475
+ const shutdown = shutdownLatch()
476
+
477
+ // Built here, started by the control plane (or by `start()` below for the data
478
+ // path's half). Constructing it eagerly is what lets the control plane be a
479
+ // separate object at all — and it keeps `platformService` tuning and `stop()`
480
+ // ownership in one place, so the documented shutdown order holds whether or
481
+ // not anyone opted in.
482
+ const platform = platformConnection(connection, platformService)
483
+
484
+ return {
485
+ connection,
486
+ config: connection.config,
487
+ serializer,
488
+ resilience,
489
+ shutdown,
490
+ platform,
491
+
492
+ async start() {
493
+ // RECONNECT DETECTION IS DATA PATH. The heartbeat on the platform stream
494
+ // is what notices a dead channel and calls `connection.reconnect()`; both
495
+ // buses hook `connection.onReconnect(...)` to rebuild their own streams.
496
+ // Arming it used to be a side effect of `platform.start()`, which only
497
+ // `axonServerControlPlane(...)` calls — so a service that never opted into
498
+ // remote administration had NO reconnect detection at all and would sit on
499
+ // a dead channel forever. It is armed here, unconditionally.
500
+ //
501
+ // `armConnectionMonitoring()` opens the stream and starts heartbeats but
502
+ // arms NO processor status reporting; that stays the control plane's, and
503
+ // a later `platform.start()` adds it to this same live stream. Both calls
504
+ // are idempotent, so either order works.
505
+ await platform.armConnectionMonitoring()
506
+
507
+ // The only thing the data path has to wait for: Axon Server's
508
+ // command/query routing tables registering the subscribe frames sent on
509
+ // the BUS streams. It cannot be derived from the platform stream, because
510
+ // subscribes travel on a different stream entirely — and the platform
511
+ // stream's own `subscriptionsAcked()` latch says nothing about them (it
512
+ // latches unconditionally once `register` has been flushed). So this
513
+ // barrier is the settle wait, deliberately independent of whether the
514
+ // platform stream is up at all. The legacy enhancer carried the same wait.
515
+ await new Promise((r) => setTimeout(r, busSubscriptionAckDelayMs ?? 1000))
516
+ },
517
+
518
+ async close() {
519
+ await shutdown.initiateShutdown()
520
+ // Idempotent, and independent of `control.close()` — a connection that was
521
+ // never administered still stops a platform stream someone else started.
522
+ platform.stop()
523
+ connection.close()
524
+ },
525
+ }
526
+ }
@@ -0,0 +1,46 @@
1
+ import { Metadata } from "nice-grpc"
2
+ import type { AxonServerStoreSource } from "./connection.js"
3
+
4
+ /**
5
+ * A context-scoped view of the ONE shared connection.
6
+ *
7
+ * Contexts are Axon Server's tenancy boundary, and they are a per-CALL header —
8
+ * not a per-channel property. `AxonIQ-Context` selects the context on every
9
+ * outbound stream and RPC, so `axonServerEventStore(conn, "tenant-a")` and
10
+ * `axonServerEventStore(conn, "tenant-b")` are two views over one gRPC channel,
11
+ * exactly as one connection served one context before. Nothing about the
12
+ * channel, the codec or the drain latch differs between them; only this header
13
+ * does, which is why the whole difference fits in one small record.
14
+ */
15
+ export type AxonServerContextView = AxonServerStoreSource & {
16
+ /** The Axon Server context every call through this view addresses. */
17
+ readonly context: string
18
+ /**
19
+ * The gRPC metadata headers Axon Server requires. `AxonIQ-Context` is
20
+ * mandatory (it identifies the context); `AxonIQ-Access-Token` is optional
21
+ * auth. Both must be attached to every outbound stream/RPC — preserved
22
+ * verbatim from the legacy enhancer.
23
+ *
24
+ * A function rather than a value so a caller that opens one long-lived stream
25
+ * can hoist it, and one that makes many unary calls can hand each its own.
26
+ */
27
+ metadata(): Metadata
28
+ }
29
+
30
+ /** Scope a connection to one Axon Server context. */
31
+ export function contextView(source: AxonServerStoreSource, context: string): AxonServerContextView {
32
+ const { connection, serializer } = source
33
+ return {
34
+ connection,
35
+ serializer,
36
+ context,
37
+ metadata() {
38
+ const metadata = new Metadata()
39
+ metadata.set("AxonIQ-Context", context)
40
+ if (connection.config.token) {
41
+ metadata.set("AxonIQ-Access-Token", connection.config.token)
42
+ }
43
+ return metadata
44
+ },
45
+ }
46
+ }
@@ -11,19 +11,19 @@
11
11
  * 2. outbound — the client periodically reports each processor's status so
12
12
  * the Axon Dashboard can render it.
13
13
  *
14
- * It lived inside `axonServer().start()` only because it shares the gRPC
14
+ * It lived inside the backend's `start()` only because it shares the gRPC
15
15
  * connection with the data path. It is now separate and OPT-IN: a service that
16
- * nobody administers remotely simply never builds one, and `axonServer()`
17
- * matches `postgres()` / `rabbitmq()` components plus an argument-less
18
- * `start()` readiness barrier.
16
+ * nobody administers remotely simply never builds one, and
17
+ * `axonServerConnection()` is left with an argument-less `start()` readiness
18
+ * barrier and nothing else.
19
19
  *
20
20
  * ```ts
21
- * const axon = await axonServer({ ... })
22
- * const app = kronos({ components: { ...axon.components }, modules })
21
+ * const axon = await axonServerConnection({ ... })
22
+ * const app = kronos({ commandHandlers, queryHandlers, eventHandlers })
23
23
  * await axon.start() // data path only
24
24
  *
25
25
  * // opt in to remote administration
26
- * const control = await axonServerControlPlane(axon.platform, app.processors.values())
26
+ * const control = await axonServerControlPlane(axon, app.processors.values())
27
27
  * // …
28
28
  * await app.stop(); await control.close(); await axon.close()
29
29
  * ```
@@ -38,7 +38,7 @@
38
38
  * start — all three inside one function, so there is no call order for a
39
39
  * caller to get wrong.
40
40
  */
41
- import type { PlatformConnection } from "./platform-service.js"
41
+ import type { AxonServerPlatformSource } from "./connection.js"
42
42
  import type { ProcessorStatus, SegmentStatus } from "./event-processor-info.js"
43
43
 
44
44
  /**
@@ -53,7 +53,7 @@ import type { ProcessorStatus, SegmentStatus } from "./event-processor-info.js"
53
53
  * may not implement (a subscribing processor has no segments), and the
54
54
  * instruction handler simply skips what is absent.
55
55
  */
56
- export interface ManagedEventProcessor {
56
+ export type ManagedEventProcessor = {
57
57
  readonly name: string
58
58
  readonly running?: boolean
59
59
  readonly replaying?: boolean
@@ -79,7 +79,7 @@ export interface ManagedEventProcessor {
79
79
  * A running control plane. The platform stream is live; instructions are being
80
80
  * routed and status is being reported until `close()`.
81
81
  */
82
- export interface AxonServerControlPlane {
82
+ export type AxonServerControlPlane = {
83
83
  /**
84
84
  * The processors this control plane addresses, keyed by name — the snapshot
85
85
  * taken at construction. Exposed for introspection and tests.
@@ -90,9 +90,7 @@ export interface AxonServerControlPlane {
90
90
  }
91
91
 
92
92
  /** Map the managed processors into the status shape the platform stream reports. */
93
- function processorStatuses(
94
- processors: Iterable<ManagedEventProcessor>,
95
- ): ProcessorStatus[] {
93
+ function processorStatuses(processors: Iterable<ManagedEventProcessor>): ProcessorStatus[] {
96
94
  return Array.from(processors, (proc) => {
97
95
  const isStreamingProcessor = proc.supportsReset?.() !== false
98
96
  const perSegment = proc.processingStatus?.()
@@ -132,9 +130,10 @@ function processorStatuses(
132
130
  /**
133
131
  * Wire remote administration onto an Axon Server platform stream and start it.
134
132
  *
135
- * `platform` is `axonServer(...).platform` the backend builds the platform
136
- * connection (it owns the gRPC connection and the `platformService` tuning) but
137
- * never starts it, because starting it is exactly what this function is for.
133
+ * `conn` is the shared connection from `axonServerConnection(...)`. It owns the
134
+ * gRPC channel and the `platformService` tuning, and it builds the platform
135
+ * stream but it never starts the control-plane half, because starting that is
136
+ * exactly what this function is for.
138
137
  *
139
138
  * `processors` are the LIVE processor instances, which only exist after
140
139
  * `kronos` has built them — that is why this cannot be folded back into the
@@ -143,7 +142,7 @@ function processorStatuses(
143
142
  * Caveat on that call: `kronos` types `processors` as
144
143
  * `ReadonlyMap<string, unknown>`, so `.values()` needs a cast today —
145
144
  * `app.processors.values() as Iterable<ManagedEventProcessor>`. Narrowing that
146
- * map in `@kronos-ts/app` would remove the cast; this package cannot.
145
+ * map in `@kronos-ts/core` would remove the cast; this package cannot.
147
146
  *
148
147
  * The collection is SNAPSHOTTED into a name-keyed map here, once. That is
149
148
  * deliberate: a one-shot iterator (`Map.values()` is one) would otherwise yield
@@ -152,9 +151,10 @@ function processorStatuses(
152
151
  * only the membership of the set is fixed at construction.
153
152
  */
154
153
  export async function axonServerControlPlane(
155
- platform: PlatformConnection,
154
+ conn: AxonServerPlatformSource,
156
155
  processors: Iterable<ManagedEventProcessor> = [],
157
156
  ): Promise<AxonServerControlPlane> {
157
+ const { platform } = conn
158
158
  // Name-keyed view so server-initiated instructions route to the right one.
159
159
  const byName = new Map<string, ManagedEventProcessor>()
160
160
  for (const proc of processors) byName.set(proc.name, proc)
@@ -181,7 +181,7 @@ export async function axonServerControlPlane(
181
181
 
182
182
  platform.registerProcessorStatusSupplier(() => processorStatuses(byName.values()))
183
183
 
184
- // ORDERING: strictly AFTER both registrations — see the file-level JSDoc.
184
+ // ORDERING: strictly AFTER both handlers — see the file-level JSDoc.
185
185
  // An instruction arriving in the gap would be dropped; an early status
186
186
  // request would find no supplier.
187
187
  await platform.start()
@@ -4,7 +4,7 @@ import type { EventProcessorInfo, EventProcessorInfo_SegmentStatus } from "./gen
4
4
  * Status of a single event processor, reported to Axon Server.
5
5
  * Aligned with Java's EventProcessorInfo proto message.
6
6
  */
7
- export interface ProcessorStatus {
7
+ export type ProcessorStatus = {
8
8
  readonly name: string
9
9
  readonly running: boolean
10
10
  readonly mode: "Tracking" | "Subscribing"
@@ -17,7 +17,7 @@ export interface ProcessorStatus {
17
17
  readonly segments: SegmentStatus[]
18
18
  }
19
19
 
20
- export interface SegmentStatus {
20
+ export type SegmentStatus = {
21
21
  readonly segmentId: number
22
22
  readonly caughtUp: boolean
23
23
  readonly replaying: boolean
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Aligned with Java's `FlowControlledResponseSender`.
9
9
  */
10
- export interface FlowControlledSender<T> {
10
+ export type FlowControlledSender<T> = {
11
11
  /** Send an update. Buffers if no permits available. */
12
12
  offer(value: T): boolean
13
13
  /** Grant additional permits to the sender. */
@@ -19,7 +19,7 @@ import {
19
19
  export const protobufPackage = "io.axoniq.axonserver.grpc.command";
20
20
 
21
21
  /** An instruction from the components that provides the Command Handler towards AxonServer. */
22
- export interface CommandProviderOutbound {
22
+ export type CommandProviderOutbound = {
23
23
  /** Instruction to subscribe this component as handler of a specific type of command */
24
24
  subscribe?:
25
25
  | CommandSubscription
@@ -45,7 +45,7 @@ export interface CommandProviderOutbound {
45
45
  }
46
46
 
47
47
  /** An instruction or confirmation from AxonServer towards the component that provides the Command Handler */
48
- export interface CommandProviderInbound {
48
+ export type CommandProviderInbound = {
49
49
  /** Acknowledgement of previously sent instruction via outbound stream */
50
50
  ack?:
51
51
  | InstructionAck
@@ -59,7 +59,7 @@ export interface CommandProviderInbound {
59
59
  }
60
60
 
61
61
  /** A message representing a Command that needs to be routed to a component capable of handling it */
62
- export interface Command {
62
+ export type Command = {
63
63
  /** The unique identifier of the Command Message */
64
64
  messageIdentifier: string;
65
65
  /** The name of the command, used for routing it to a destination capable of handling it */
@@ -80,13 +80,13 @@ export interface Command {
80
80
  componentName: string;
81
81
  }
82
82
 
83
- export interface Command_MetaDataEntry {
83
+ export type Command_MetaDataEntry = {
84
84
  key: string;
85
85
  value: MetaDataValue | undefined;
86
86
  }
87
87
 
88
88
  /** Message representing the result of Command Handler execution */
89
- export interface CommandResponse {
89
+ export type CommandResponse = {
90
90
  /** The unique identifier of the response message */
91
91
  messageIdentifier: string;
92
92
  /** An error code describing the error, if any */
@@ -107,13 +107,13 @@ export interface CommandResponse {
107
107
  requestIdentifier: string;
108
108
  }
109
109
 
110
- export interface CommandResponse_MetaDataEntry {
110
+ export type CommandResponse_MetaDataEntry = {
111
111
  key: string;
112
112
  value: MetaDataValue | undefined;
113
113
  }
114
114
 
115
115
  /** Message describing a component's capability of handling a command type */
116
- export interface CommandSubscription {
116
+ export type CommandSubscription = {
117
117
  /**
118
118
  * A unique identifier for this subscription. This identifier is returned in Acknowledgements to allow
119
119
  * pipelining of subscription messages
@@ -1183,7 +1183,7 @@ export const CommandServiceDefinition = {
1183
1183
  },
1184
1184
  } as const;
1185
1185
 
1186
- export interface CommandServiceImplementation<CallContextExt = {}> {
1186
+ export type CommandServiceImplementation<CallContextExt = {}> = {
1187
1187
  /** Opens a stream allowing clients to register command handlers and receive commands. */
1188
1188
  openStream(
1189
1189
  request: AsyncIterable<CommandProviderOutbound>,
@@ -1193,7 +1193,7 @@ export interface CommandServiceImplementation<CallContextExt = {}> {
1193
1193
  dispatch(request: Command, context: CallContext & CallContextExt): Promise<DeepPartial<CommandResponse>>;
1194
1194
  }
1195
1195
 
1196
- export interface CommandServiceClient<CallOptionsExt = {}> {
1196
+ export type CommandServiceClient<CallOptionsExt = {}> = {
1197
1197
  /** Opens a stream allowing clients to register command handlers and receive commands. */
1198
1198
  openStream(
1199
1199
  request: AsyncIterable<DeepPartial<CommandProviderOutbound>>,
@@ -1221,7 +1221,7 @@ function isSet(value: any): boolean {
1221
1221
 
1222
1222
  export type ServerStreamingMethodResult<Response> = { [Symbol.asyncIterator](): AsyncIterator<Response, void> };
1223
1223
 
1224
- export interface MessageFns<T> {
1224
+ export type MessageFns<T> = {
1225
1225
  encode(message: T, writer?: BinaryWriter): BinaryWriter;
1226
1226
  decode(input: BinaryReader | Uint8Array, length?: number): T;
1227
1227
  fromJSON(object: any): T;