@kronos-ts/axon-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/axon-server-event-store.d.ts +16 -0
  2. package/dist/axon-server-event-store.d.ts.map +1 -0
  3. package/dist/axon-server-event-store.js +282 -0
  4. package/dist/axon-server-event-store.js.map +1 -0
  5. package/dist/axon-server-snapshot-store.d.ts +12 -0
  6. package/dist/axon-server-snapshot-store.d.ts.map +1 -0
  7. package/dist/axon-server-snapshot-store.js +88 -0
  8. package/dist/axon-server-snapshot-store.js.map +1 -0
  9. package/dist/axon-server.d.ts +115 -0
  10. package/dist/axon-server.d.ts.map +1 -0
  11. package/dist/axon-server.js +986 -0
  12. package/dist/axon-server.js.map +1 -0
  13. package/dist/connection-manager.d.ts +49 -0
  14. package/dist/connection-manager.d.ts.map +1 -0
  15. package/dist/connection-manager.js +37 -0
  16. package/dist/connection-manager.js.map +1 -0
  17. package/dist/connection.d.ts +129 -0
  18. package/dist/connection.d.ts.map +1 -0
  19. package/dist/connection.js +130 -0
  20. package/dist/connection.js.map +1 -0
  21. package/dist/errors.d.ts +96 -0
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/errors.js +189 -0
  24. package/dist/errors.js.map +1 -0
  25. package/dist/event-processor-info.d.ts +35 -0
  26. package/dist/event-processor-info.d.ts.map +1 -0
  27. package/dist/event-processor-info.js +28 -0
  28. package/dist/event-processor-info.js.map +1 -0
  29. package/dist/flow-controlled-sender.d.ts +30 -0
  30. package/dist/flow-controlled-sender.d.ts.map +1 -0
  31. package/dist/flow-controlled-sender.js +60 -0
  32. package/dist/flow-controlled-sender.js.map +1 -0
  33. package/dist/generated/command.d.ts +158 -0
  34. package/dist/generated/command.d.ts.map +1 -0
  35. package/dist/generated/command.js +970 -0
  36. package/dist/generated/command.js.map +1 -0
  37. package/dist/generated/common.d.ts +130 -0
  38. package/dist/generated/common.d.ts.map +1 -0
  39. package/dist/generated/common.js +908 -0
  40. package/dist/generated/common.js.map +1 -0
  41. package/dist/generated/control.d.ts +293 -0
  42. package/dist/generated/control.d.ts.map +1 -0
  43. package/dist/generated/control.js +1938 -0
  44. package/dist/generated/control.js.map +1 -0
  45. package/dist/generated/dcb.d.ts +650 -0
  46. package/dist/generated/dcb.d.ts.map +1 -0
  47. package/dist/generated/dcb.js +2943 -0
  48. package/dist/generated/dcb.js.map +1 -0
  49. package/dist/generated/event.d.ts +667 -0
  50. package/dist/generated/event.d.ts.map +1 -0
  51. package/dist/generated/event.js +3185 -0
  52. package/dist/generated/event.js.map +1 -0
  53. package/dist/generated/google/protobuf/empty.d.ts +30 -0
  54. package/dist/generated/google/protobuf/empty.d.ts.map +1 -0
  55. package/dist/generated/google/protobuf/empty.js +46 -0
  56. package/dist/generated/google/protobuf/empty.js.map +1 -0
  57. package/dist/generated/query.d.ts +300 -0
  58. package/dist/generated/query.d.ts.map +1 -0
  59. package/dist/generated/query.js +2183 -0
  60. package/dist/generated/query.js.map +1 -0
  61. package/dist/index.d.ts +12 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +12 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/message-size.d.ts +38 -0
  66. package/dist/message-size.d.ts.map +1 -0
  67. package/dist/message-size.js +57 -0
  68. package/dist/message-size.js.map +1 -0
  69. package/dist/metadata-conversion.d.ts +11 -0
  70. package/dist/metadata-conversion.d.ts.map +1 -0
  71. package/dist/metadata-conversion.js +51 -0
  72. package/dist/metadata-conversion.js.map +1 -0
  73. package/dist/outbound-stream.d.ts +15 -0
  74. package/dist/outbound-stream.d.ts.map +1 -0
  75. package/dist/outbound-stream.js +39 -0
  76. package/dist/outbound-stream.js.map +1 -0
  77. package/dist/platform-service.d.ts +119 -0
  78. package/dist/platform-service.d.ts.map +1 -0
  79. package/dist/platform-service.js +250 -0
  80. package/dist/platform-service.js.map +1 -0
  81. package/dist/shutdown-latch.d.ts +38 -0
  82. package/dist/shutdown-latch.d.ts.map +1 -0
  83. package/dist/shutdown-latch.js +51 -0
  84. package/dist/shutdown-latch.js.map +1 -0
  85. package/package.json +69 -0
  86. package/src/axon-server-event-store.ts +358 -0
  87. package/src/axon-server-snapshot-store.ts +118 -0
  88. package/src/axon-server.ts +1202 -0
  89. package/src/connection-manager.ts +88 -0
  90. package/src/connection.ts +272 -0
  91. package/src/errors.ts +223 -0
  92. package/src/event-processor-info.ts +62 -0
  93. package/src/flow-controlled-sender.ts +91 -0
  94. package/src/generated/command.ts +1231 -0
  95. package/src/generated/common.ts +1097 -0
  96. package/src/generated/control.ts +2419 -0
  97. package/src/generated/dcb.ts +3826 -0
  98. package/src/generated/event.ts +4076 -0
  99. package/src/generated/google/protobuf/empty.ts +84 -0
  100. package/src/generated/query.ts +2723 -0
  101. package/src/index.ts +75 -0
  102. package/src/message-size.ts +75 -0
  103. package/src/metadata-conversion.ts +46 -0
  104. package/src/outbound-stream.ts +52 -0
  105. package/src/platform-service.ts +361 -0
  106. package/src/shutdown-latch.ts +97 -0
@@ -0,0 +1,88 @@
1
+ import type { AxonServerConnectionConfig, AxonServerConnection } from "./connection.js"
2
+ import { connectToAxonServer } from "./connection.js"
3
+
4
+ /**
5
+ * Manages Axon Server connections across multiple contexts.
6
+ *
7
+ * Creates and caches one connection per context. Used by the multi-tenancy
8
+ * extension to maintain separate channels for each tenant's context.
9
+ *
10
+ * Aligned with Java's `AxonServerConnectionManager`.
11
+ *
12
+ * ```typescript
13
+ * const manager = createConnectionManager({
14
+ * componentName: "my-app",
15
+ * host: "axon-server",
16
+ * port: 8124,
17
+ * })
18
+ *
19
+ * const defaultConn = manager.getConnection("default")
20
+ * const tenantConn = manager.getConnection("tenant-a")
21
+ *
22
+ * await manager.disconnectAll()
23
+ * ```
24
+ */
25
+ export interface AxonServerConnectionManager {
26
+ /**
27
+ * Get or create a connection for the given context.
28
+ * Connections are created lazily and cached.
29
+ */
30
+ getConnection(context: string): AxonServerConnection
31
+
32
+ /**
33
+ * Disconnect a specific context's connection.
34
+ */
35
+ disconnect(context: string): void
36
+
37
+ /**
38
+ * Disconnect all cached connections.
39
+ */
40
+ disconnectAll(): void
41
+
42
+ /**
43
+ * List all active context names.
44
+ */
45
+ activeContexts(): string[]
46
+ }
47
+
48
+ /**
49
+ * Creates a connection manager that lazily creates per-context connections.
50
+ *
51
+ * The base config (host, port, SSL, etc.) is shared across all contexts.
52
+ * Only the `context` field varies per connection.
53
+ */
54
+ export function createConnectionManager(
55
+ baseConfig: Omit<AxonServerConnectionConfig, "context">,
56
+ ): AxonServerConnectionManager {
57
+ const connections = new Map<string, AxonServerConnection>()
58
+
59
+ return {
60
+ getConnection(context: string): AxonServerConnection {
61
+ let connection = connections.get(context)
62
+ if (!connection) {
63
+ connection = connectToAxonServer({ ...baseConfig, context })
64
+ connections.set(context, connection)
65
+ }
66
+ return connection
67
+ },
68
+
69
+ disconnect(context: string): void {
70
+ const connection = connections.get(context)
71
+ if (connection) {
72
+ connection.close()
73
+ connections.delete(context)
74
+ }
75
+ },
76
+
77
+ disconnectAll(): void {
78
+ for (const connection of connections.values()) {
79
+ connection.close()
80
+ }
81
+ connections.clear()
82
+ },
83
+
84
+ activeContexts(): string[] {
85
+ return [...connections.keys()]
86
+ },
87
+ }
88
+ }
@@ -0,0 +1,272 @@
1
+ import { createChannel, createClient, type Channel, type Client, type ChannelCredentials } from "nice-grpc"
2
+ import { ChannelCredentials as GrpcChannelCredentials } from "@grpc/grpc-js"
3
+ import { Metadata } from "nice-grpc"
4
+ import { readFileSync } from "node:fs"
5
+ import { PlatformServiceDefinition } from "./generated/control.js"
6
+ import { CommandServiceDefinition } from "./generated/command.js"
7
+ import { QueryServiceDefinition } from "./generated/query.js"
8
+ import { DcbEventStoreDefinition, DcbSnapshotStoreDefinition } from "./generated/dcb.js"
9
+
10
+ /**
11
+ * Configuration for connecting to Axon Server.
12
+ */
13
+ export interface AxonServerConnectionConfig {
14
+ /**
15
+ * Host of the Axon Server. Defaults to "localhost".
16
+ * For single-server setups.
17
+ */
18
+ host?: string
19
+ /** gRPC port of the Axon Server. Defaults to 8124. */
20
+ port?: number
21
+ /**
22
+ * Multiple server addresses for cluster deployments.
23
+ * Each entry is `"host:port"`. When provided, overrides `host` and `port`.
24
+ * The connector tries servers in order until one connects, and fails over
25
+ * to the next on connection failure.
26
+ *
27
+ * Aligned with Java's `axon.axonserver.servers` property.
28
+ */
29
+ servers?: string[]
30
+ /** The context to connect to. Defaults to "default". */
31
+ context?: string
32
+ /** Name identifying this component to Axon Server. */
33
+ componentName: string
34
+ /** Unique client identifier. Defaults to a generated UUID. */
35
+ clientId?: string
36
+ /** Access token for authentication. Optional. */
37
+ token?: string
38
+ /** Reconnection interval in ms. Defaults to 2000. */
39
+ reconnectIntervalMs?: number
40
+ /** Maximum reconnection attempts before giving up. 0 = unlimited. Defaults to 0. */
41
+ maxReconnectAttempts?: number
42
+
43
+ /**
44
+ * gRPC keepalive ping interval in ms.
45
+ * Sends a ping after this period of inactivity to keep the connection alive.
46
+ * Default: 30000. Java's default is 1000ms but @grpc/grpc-js requires
47
+ * a minimum of 10000ms to avoid server-side RST_STREAM rejections.
48
+ */
49
+ keepAliveTimeMs?: number
50
+ /**
51
+ * gRPC keepalive timeout in ms.
52
+ * Connection is considered dead if no response within this window.
53
+ * Default: 10000.
54
+ */
55
+ keepAliveTimeoutMs?: number
56
+ /**
57
+ * Allow keepalive pings even when there are no active RPCs.
58
+ * Default: true.
59
+ */
60
+ keepAlivePermitWithoutCalls?: boolean
61
+
62
+ /**
63
+ * TLS/SSL configuration. When enabled, the connection uses a secure gRPC channel.
64
+ * Aligned with Java's `axon.axonserver.ssl-enabled` and `axon.axonserver.cert-file`.
65
+ */
66
+ ssl?: {
67
+ /** Enable TLS. When true, a secure channel is created. */
68
+ enabled: boolean
69
+ /**
70
+ * Path to the CA certificate file (PEM format) for server verification.
71
+ * If omitted, the system's default trust store is used.
72
+ */
73
+ certFile?: string
74
+ /**
75
+ * Path to the client certificate file (PEM) for mutual TLS.
76
+ * Only needed if Axon Server requires client certificates.
77
+ */
78
+ clientCertFile?: string
79
+ /**
80
+ * Path to the client private key file (PEM) for mutual TLS.
81
+ * Only needed if Axon Server requires client certificates.
82
+ */
83
+ clientKeyFile?: string
84
+ }
85
+ }
86
+
87
+ export type ConnectionState = "disconnected" | "connecting" | "connected" | "reconnecting" | "closed"
88
+
89
+ /**
90
+ * An active connection to Axon Server, providing typed gRPC clients
91
+ * for all services. Supports reconnection on failure.
92
+ */
93
+ export interface AxonServerConnection {
94
+ /** The underlying gRPC channel. */
95
+ readonly channel: Channel
96
+ /** Platform service — connection management, topology. */
97
+ readonly platform: Client<typeof PlatformServiceDefinition>
98
+ /** Command service — dispatch and handle commands. */
99
+ readonly commands: Client<typeof CommandServiceDefinition>
100
+ /** Query service — dispatch and handle queries. */
101
+ readonly queries: Client<typeof QueryServiceDefinition>
102
+ /** Event store — event sourcing with Dynamic Consistency Boundaries. */
103
+ readonly eventStore: Client<typeof DcbEventStoreDefinition>
104
+ /** Snapshot store — state snapshots. */
105
+ readonly snapshotStore: Client<typeof DcbSnapshotStoreDefinition>
106
+ /** The resolved configuration. `servers` and `ssl` stay optional — they have no defaults. */
107
+ readonly config: Omit<Required<AxonServerConnectionConfig>, "servers" | "ssl"> & {
108
+ servers?: AxonServerConnectionConfig["servers"]
109
+ ssl?: AxonServerConnectionConfig["ssl"]
110
+ }
111
+ /** Current connection state. */
112
+ readonly state: ConnectionState
113
+ /**
114
+ * Register a callback invoked when the connection is (re)established.
115
+ * Used by buses to re-subscribe handlers after reconnection.
116
+ */
117
+ onReconnect(callback: () => void): void
118
+ /**
119
+ * Register a callback invoked when the connection is lost.
120
+ */
121
+ onDisconnect(callback: (error?: Error) => void): void
122
+ /** Close the connection permanently. No reconnection after this. */
123
+ close(): void
124
+ /**
125
+ * Attempt to reconnect if disconnected.
126
+ * Returns a promise that resolves when reconnected or rejects on failure.
127
+ */
128
+ reconnect(): Promise<void>
129
+ }
130
+
131
+ /**
132
+ * Connects to Axon Server and returns typed gRPC clients for all services.
133
+ *
134
+ * Configures gRPC channel-level keepalive to maintain persistent connections,
135
+ * aligned with Java's ManagedChannel keepalive settings.
136
+ */
137
+ export function connectToAxonServer(config: AxonServerConnectionConfig): AxonServerConnection {
138
+ const resolvedConfig = {
139
+ host: config.host ?? "localhost",
140
+ port: config.port ?? 8124,
141
+ context: config.context ?? "default",
142
+ componentName: config.componentName,
143
+ clientId: config.clientId ?? crypto.randomUUID(),
144
+ token: config.token ?? "",
145
+ reconnectIntervalMs: config.reconnectIntervalMs ?? 2000,
146
+ maxReconnectAttempts: config.maxReconnectAttempts ?? 0,
147
+ keepAliveTimeMs: config.keepAliveTimeMs ?? 30000,
148
+ keepAliveTimeoutMs: config.keepAliveTimeoutMs ?? 10000,
149
+ keepAlivePermitWithoutCalls: config.keepAlivePermitWithoutCalls ?? true,
150
+ servers: config.servers,
151
+ ssl: config.ssl,
152
+ }
153
+
154
+ // Build gRPC channel credentials
155
+ const sslConfig = config.ssl
156
+ let credentials: ChannelCredentials | undefined
157
+
158
+ if (sslConfig?.enabled) {
159
+ const rootCerts = sslConfig.certFile ? readFileSync(sslConfig.certFile) : null
160
+ const clientKey = sslConfig.clientKeyFile ? readFileSync(sslConfig.clientKeyFile) : null
161
+ const clientCert = sslConfig.clientCertFile ? readFileSync(sslConfig.clientCertFile) : null
162
+ credentials = GrpcChannelCredentials.createSsl(rootCerts, clientKey, clientCert) as ChannelCredentials
163
+ }
164
+
165
+ // gRPC channel options — keepalive to maintain persistent connections
166
+ const channelOptions = {
167
+ "grpc.keepalive_time_ms": config.keepAliveTimeMs ?? 30000,
168
+ "grpc.keepalive_timeout_ms": config.keepAliveTimeoutMs ?? 10000,
169
+ "grpc.keepalive_permit_without_calls": (config.keepAlivePermitWithoutCalls ?? true) ? 1 : 0,
170
+ }
171
+
172
+ // Build server address list for failover
173
+ const serverAddresses = config.servers && config.servers.length > 0
174
+ ? config.servers
175
+ : [`${resolvedConfig.host}:${resolvedConfig.port}`]
176
+
177
+ let currentServerIndex = 0
178
+
179
+ function createGrpcChannel(): Channel {
180
+ const address = serverAddresses[currentServerIndex % serverAddresses.length]!
181
+ return credentials
182
+ ? createChannel(address, credentials, channelOptions)
183
+ : createChannel(address, undefined, channelOptions)
184
+ }
185
+
186
+ let channel = createGrpcChannel()
187
+ let state: ConnectionState = "connected"
188
+
189
+ const reconnectCallbacks: Array<() => void> = []
190
+ const disconnectCallbacks: Array<(error?: Error) => void> = []
191
+
192
+ function createClients() {
193
+ return {
194
+ platform: createClient(PlatformServiceDefinition, channel),
195
+ commands: createClient(CommandServiceDefinition, channel),
196
+ queries: createClient(QueryServiceDefinition, channel),
197
+ eventStore: createClient(DcbEventStoreDefinition, channel),
198
+ snapshotStore: createClient(DcbSnapshotStoreDefinition, channel),
199
+ }
200
+ }
201
+
202
+ let clients = createClients()
203
+
204
+ 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 },
211
+ config: resolvedConfig,
212
+
213
+ get state() { return state },
214
+
215
+ onReconnect(callback) {
216
+ reconnectCallbacks.push(callback)
217
+ },
218
+
219
+ onDisconnect(callback) {
220
+ disconnectCallbacks.push(callback)
221
+ },
222
+
223
+ close() {
224
+ state = "closed"
225
+ channel.close()
226
+ },
227
+
228
+ async reconnect() {
229
+ if (state === "closed") {
230
+ throw new Error("Connection is permanently closed")
231
+ }
232
+ if (state === "connected" || state === "connecting") return
233
+
234
+ state = "reconnecting"
235
+ const maxAttempts = resolvedConfig.maxReconnectAttempts
236
+ let attempt = 0
237
+
238
+ while (state === "reconnecting") {
239
+ attempt++
240
+ try {
241
+ // Try the next server in the list on each reconnect attempt
242
+ currentServerIndex++
243
+ channel = createGrpcChannel()
244
+ clients = createClients()
245
+ state = "connected"
246
+
247
+ // Notify listeners that we're back
248
+ for (const cb of reconnectCallbacks) {
249
+ try { cb() } catch { /* ignore listener errors */ }
250
+ }
251
+ return
252
+ } catch (err) {
253
+ if (maxAttempts > 0 && attempt >= maxAttempts) {
254
+ state = "disconnected"
255
+ throw new Error(
256
+ `Failed to reconnect after ${attempt} attempts: ${err}`,
257
+ )
258
+ }
259
+
260
+ // Exponential backoff: base interval * 2^attempt, capped at 30s
261
+ const delay = Math.min(
262
+ resolvedConfig.reconnectIntervalMs * Math.pow(2, attempt - 1),
263
+ 30000,
264
+ )
265
+ await new Promise((r) => setTimeout(r, delay))
266
+ }
267
+ }
268
+ },
269
+ }
270
+
271
+ return connection
272
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Axon Server error codes — mapped from the Java framework's ErrorCode enum.
3
+ *
4
+ * Error codes follow the AXONIQ-XXXX pattern where the first digit
5
+ * indicates the category:
6
+ * - 1xxx: Authentication/instruction errors
7
+ * - 2xxx: Event publishing errors
8
+ * - 3xxx: Communication errors
9
+ * - 4xxx: Command errors
10
+ * - 5xxx: Query errors
11
+ * - 9xxx: Internal/storage errors
12
+ */
13
+ export const AxonServerErrorCode = {
14
+ // Authentication & instructions
15
+ AUTHENTICATION_TOKEN_MISSING: "AXONIQ-1000",
16
+ AUTHENTICATION_INVALID_TOKEN: "AXONIQ-1001",
17
+ UNSUPPORTED_INSTRUCTION: "AXONIQ-1002",
18
+ INSTRUCTION_ACK_ERROR: "AXONIQ-1003",
19
+ INSTRUCTION_EXECUTION_ERROR: "AXONIQ-1004",
20
+
21
+ // Event publishing
22
+ EVENT_PAYLOAD_TOO_LARGE: "AXONIQ-2001",
23
+ NO_EVENT_STORE_MASTER_AVAILABLE: "AXONIQ-2100",
24
+ CONCURRENCY_EXCEPTION: "AXONIQ-2000",
25
+
26
+ // Communication
27
+ CONNECTION_FAILED: "AXONIQ-3001",
28
+ GRPC_MESSAGE_TOO_LARGE: "AXONIQ-3002",
29
+
30
+ // Commands
31
+ NO_HANDLER_FOR_COMMAND: "AXONIQ-4000",
32
+ COMMAND_EXECUTION_ERROR: "AXONIQ-4002",
33
+ COMMAND_DISPATCH_ERROR: "AXONIQ-4003",
34
+ COMMAND_CONCURRENCY_ERROR: "AXONIQ-4004",
35
+ COMMAND_EXECUTION_NON_TRANSIENT_ERROR: "AXONIQ-4005",
36
+
37
+ // Queries
38
+ NO_HANDLER_FOR_QUERY: "AXONIQ-5000",
39
+ QUERY_EXECUTION_ERROR: "AXONIQ-5001",
40
+ QUERY_DISPATCH_ERROR: "AXONIQ-5002",
41
+ QUERY_EXECUTION_NON_TRANSIENT_ERROR: "AXONIQ-5003",
42
+
43
+ // Internal/storage
44
+ DATAFILE_READ_ERROR: "AXONIQ-9000",
45
+ INDEX_READ_ERROR: "AXONIQ-9001",
46
+ DATAFILE_WRITE_ERROR: "AXONIQ-9100",
47
+ INDEX_WRITE_ERROR: "AXONIQ-9101",
48
+ DIRECTORY_CREATION_FAILED: "AXONIQ-9102",
49
+ VALIDATION_FAILED: "AXONIQ-9200",
50
+ TRANSACTION_ROLLED_BACK: "AXONIQ-9900",
51
+
52
+ // Default
53
+ OTHER: "AXONIQ-0001",
54
+ } as const
55
+
56
+ export type AxonServerErrorCodeValue = typeof AxonServerErrorCode[keyof typeof AxonServerErrorCode]
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Exception hierarchy
60
+ // ---------------------------------------------------------------------------
61
+
62
+ /**
63
+ * Base error for all Axon Server errors. Carries the error code
64
+ * and whether the error is transient (retryable).
65
+ */
66
+ export class AxonServerError extends Error {
67
+ readonly errorCode: string
68
+ readonly transient: boolean
69
+
70
+ constructor(message: string, errorCode: string, transient: boolean) {
71
+ super(message)
72
+ this.name = "AxonServerError"
73
+ this.errorCode = errorCode
74
+ this.transient = transient
75
+ }
76
+ }
77
+
78
+ /** No handler registered for the dispatched command. */
79
+ export class NoHandlerForCommandError extends AxonServerError {
80
+ constructor(message: string) {
81
+ super(message, AxonServerErrorCode.NO_HANDLER_FOR_COMMAND, false)
82
+ this.name = "NoHandlerForCommandError"
83
+ }
84
+ }
85
+
86
+ /** No handler registered for the dispatched query. */
87
+ export class NoHandlerForQueryError extends AxonServerError {
88
+ constructor(message: string) {
89
+ super(message, AxonServerErrorCode.NO_HANDLER_FOR_QUERY, false)
90
+ this.name = "NoHandlerForQueryError"
91
+ }
92
+ }
93
+
94
+ /** Command handler execution failed (transient — may succeed on retry). */
95
+ export class CommandExecutionError extends AxonServerError {
96
+ constructor(message: string, transient: boolean = true) {
97
+ super(
98
+ message,
99
+ transient
100
+ ? AxonServerErrorCode.COMMAND_EXECUTION_ERROR
101
+ : AxonServerErrorCode.COMMAND_EXECUTION_NON_TRANSIENT_ERROR,
102
+ transient,
103
+ )
104
+ this.name = "CommandExecutionError"
105
+ }
106
+ }
107
+
108
+ /** Query handler execution failed (transient — may succeed on retry). */
109
+ export class QueryExecutionError extends AxonServerError {
110
+ constructor(message: string, transient: boolean = true) {
111
+ super(
112
+ message,
113
+ transient
114
+ ? AxonServerErrorCode.QUERY_EXECUTION_ERROR
115
+ : AxonServerErrorCode.QUERY_EXECUTION_NON_TRANSIENT_ERROR,
116
+ transient,
117
+ )
118
+ this.name = "QueryExecutionError"
119
+ }
120
+ }
121
+
122
+ /** Command dispatch failed (infrastructure error). */
123
+ export class CommandDispatchError extends AxonServerError {
124
+ constructor(message: string) {
125
+ super(message, AxonServerErrorCode.COMMAND_DISPATCH_ERROR, true)
126
+ this.name = "CommandDispatchError"
127
+ }
128
+ }
129
+
130
+ /** Query dispatch failed (infrastructure error). */
131
+ export class QueryDispatchError extends AxonServerError {
132
+ constructor(message: string) {
133
+ super(message, AxonServerErrorCode.QUERY_DISPATCH_ERROR, true)
134
+ this.name = "QueryDispatchError"
135
+ }
136
+ }
137
+
138
+ /** Optimistic concurrency violation (transient — retry with fresh state). */
139
+ export class ConcurrencyError extends AxonServerError {
140
+ constructor(message: string) {
141
+ super(message, AxonServerErrorCode.CONCURRENCY_EXCEPTION, true)
142
+ this.name = "ConcurrencyError"
143
+ }
144
+ }
145
+
146
+ /** Connection to Axon Server failed. */
147
+ export class ConnectionFailedError extends AxonServerError {
148
+ constructor(message: string) {
149
+ super(message, AxonServerErrorCode.CONNECTION_FAILED, true)
150
+ this.name = "ConnectionFailedError"
151
+ }
152
+ }
153
+
154
+ /** Authentication/authorization failed. */
155
+ export class AuthenticationError extends AxonServerError {
156
+ constructor(message: string, code: string) {
157
+ super(message, code, false)
158
+ this.name = "AuthenticationError"
159
+ }
160
+ }
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Error code → exception mapping
164
+ // ---------------------------------------------------------------------------
165
+
166
+ const TRANSIENT_CODES = new Set<string>([
167
+ AxonServerErrorCode.COMMAND_EXECUTION_ERROR,
168
+ AxonServerErrorCode.QUERY_EXECUTION_ERROR,
169
+ AxonServerErrorCode.COMMAND_DISPATCH_ERROR,
170
+ AxonServerErrorCode.QUERY_DISPATCH_ERROR,
171
+ AxonServerErrorCode.CONNECTION_FAILED,
172
+ AxonServerErrorCode.GRPC_MESSAGE_TOO_LARGE,
173
+ AxonServerErrorCode.CONCURRENCY_EXCEPTION,
174
+ AxonServerErrorCode.COMMAND_CONCURRENCY_ERROR,
175
+ AxonServerErrorCode.NO_EVENT_STORE_MASTER_AVAILABLE,
176
+ ])
177
+
178
+ /**
179
+ * Convert an Axon Server error code + message into a typed exception.
180
+ */
181
+ export function mapErrorCode(errorCode: string, message: string): AxonServerError {
182
+ switch (errorCode) {
183
+ case AxonServerErrorCode.NO_HANDLER_FOR_COMMAND:
184
+ return new NoHandlerForCommandError(message)
185
+ case AxonServerErrorCode.NO_HANDLER_FOR_QUERY:
186
+ return new NoHandlerForQueryError(message)
187
+ case AxonServerErrorCode.COMMAND_EXECUTION_ERROR:
188
+ return new CommandExecutionError(message, true)
189
+ case AxonServerErrorCode.COMMAND_EXECUTION_NON_TRANSIENT_ERROR:
190
+ return new CommandExecutionError(message, false)
191
+ case AxonServerErrorCode.QUERY_EXECUTION_ERROR:
192
+ return new QueryExecutionError(message, true)
193
+ case AxonServerErrorCode.QUERY_EXECUTION_NON_TRANSIENT_ERROR:
194
+ return new QueryExecutionError(message, false)
195
+ case AxonServerErrorCode.COMMAND_DISPATCH_ERROR:
196
+ return new CommandDispatchError(message)
197
+ case AxonServerErrorCode.QUERY_DISPATCH_ERROR:
198
+ return new QueryDispatchError(message)
199
+ case AxonServerErrorCode.CONCURRENCY_EXCEPTION:
200
+ case AxonServerErrorCode.COMMAND_CONCURRENCY_ERROR:
201
+ return new ConcurrencyError(message)
202
+ case AxonServerErrorCode.CONNECTION_FAILED:
203
+ case AxonServerErrorCode.GRPC_MESSAGE_TOO_LARGE:
204
+ return new ConnectionFailedError(message)
205
+ case AxonServerErrorCode.AUTHENTICATION_TOKEN_MISSING:
206
+ case AxonServerErrorCode.AUTHENTICATION_INVALID_TOKEN:
207
+ return new AuthenticationError(message, errorCode)
208
+ default:
209
+ return new AxonServerError(
210
+ message,
211
+ errorCode,
212
+ TRANSIENT_CODES.has(errorCode),
213
+ )
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Check if an error is transient (retryable).
219
+ */
220
+ export function isTransientError(error: unknown): boolean {
221
+ if (error instanceof AxonServerError) return error.transient
222
+ return false
223
+ }
@@ -0,0 +1,62 @@
1
+ import type { EventProcessorInfo, EventProcessorInfo_SegmentStatus } from "./generated/control.js"
2
+
3
+ /**
4
+ * Status of a single event processor, reported to Axon Server.
5
+ * Aligned with Java's EventProcessorInfo proto message.
6
+ */
7
+ export interface ProcessorStatus {
8
+ readonly name: string
9
+ readonly running: boolean
10
+ readonly mode: "Tracking" | "Subscribing"
11
+ readonly isStreamingProcessor: boolean
12
+ readonly activeThreads: number
13
+ readonly availableThreads: number
14
+ readonly error: boolean
15
+ readonly errorMessage?: string
16
+ readonly tokenStoreIdentifier: string
17
+ readonly segments: SegmentStatus[]
18
+ }
19
+
20
+ export interface SegmentStatus {
21
+ readonly segmentId: number
22
+ readonly caughtUp: boolean
23
+ readonly replaying: boolean
24
+ readonly onePartOf: number
25
+ readonly tokenPosition: bigint
26
+ readonly errorState: string
27
+ }
28
+
29
+ /**
30
+ * Converts a ProcessorStatus to the proto EventProcessorInfo format.
31
+ */
32
+ export function toEventProcessorInfo(status: ProcessorStatus): EventProcessorInfo {
33
+ return {
34
+ processorName: status.name,
35
+ mode: status.mode,
36
+ activeThreads: status.activeThreads,
37
+ running: status.running,
38
+ error: status.error,
39
+ segmentStatus: status.segments.map(toSegmentStatus),
40
+ availableThreads: status.availableThreads,
41
+ tokenStoreIdentifier: status.tokenStoreIdentifier,
42
+ isStreamingProcessor: status.isStreamingProcessor,
43
+ loadBalancingStrategyName: "",
44
+ }
45
+ }
46
+
47
+ function toSegmentStatus(seg: SegmentStatus): EventProcessorInfo_SegmentStatus {
48
+ return {
49
+ segmentId: seg.segmentId,
50
+ caughtUp: seg.caughtUp,
51
+ replaying: seg.replaying,
52
+ onePartOf: seg.onePartOf,
53
+ tokenPosition: seg.tokenPosition,
54
+ errorState: seg.errorState,
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Supplier function that returns the current status of all event processors.
60
+ * Registered with the platform connection for periodic reporting.
61
+ */
62
+ export type ProcessorStatusSupplier = () => ProcessorStatus[]