@amqp-contract/core 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  }) : target, mod));
26
26
 
27
27
  //#endregion
28
+ let _swan_io_boxed = require("@swan-io/boxed");
28
29
  let amqp_connection_manager = require("amqp-connection-manager");
29
30
  amqp_connection_manager = __toESM(amqp_connection_manager);
30
31
  let _amqp_contract_contract = require("@amqp-contract/contract");
@@ -154,6 +155,24 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
154
155
  }
155
156
  };
156
157
 
158
+ //#endregion
159
+ //#region src/errors.ts
160
+ /**
161
+ * Error for technical/runtime failures that cannot be prevented by TypeScript.
162
+ *
163
+ * This includes AMQP connection failures, channel issues, validation failures,
164
+ * and other runtime errors. This error is shared across core, worker, and client packages.
165
+ */
166
+ var TechnicalError = class extends Error {
167
+ constructor(message, cause) {
168
+ super(message);
169
+ this.cause = cause;
170
+ this.name = "TechnicalError";
171
+ const ErrorConstructor = Error;
172
+ if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
173
+ }
174
+ };
175
+
157
176
  //#endregion
158
177
  //#region src/setup.ts
159
178
  /**
@@ -233,6 +252,8 @@ async function setupAmqpTopology(channel, contract) {
233
252
  * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
234
253
  * - Channel creation with JSON serialization enabled by default
235
254
  *
255
+ * All operations return `Future<Result<T, TechnicalError>>` for consistent error handling.
256
+ *
236
257
  * @example
237
258
  * ```typescript
238
259
  * const client = new AmqpClient(contract, {
@@ -240,16 +261,19 @@ async function setupAmqpTopology(channel, contract) {
240
261
  * connectionOptions: { heartbeatIntervalInSeconds: 30 }
241
262
  * });
242
263
  *
243
- * // Use the channel to publish messages
244
- * await client.channel.publish('exchange', 'routingKey', { data: 'value' });
264
+ * // Wait for connection
265
+ * await client.waitForConnect().resultToPromise();
266
+ *
267
+ * // Publish a message
268
+ * const result = await client.publish('exchange', 'routingKey', { data: 'value' }).resultToPromise();
245
269
  *
246
270
  * // Close when done
247
- * await client.close();
271
+ * await client.close().resultToPromise();
248
272
  * ```
249
273
  */
250
274
  var AmqpClient = class {
251
275
  connection;
252
- channel;
276
+ channelWrapper;
253
277
  urls;
254
278
  connectionOptions;
255
279
  /**
@@ -285,7 +309,7 @@ var AmqpClient = class {
285
309
  });
286
310
  else await userSetup(channel);
287
311
  };
288
- this.channel = this.connection.createChannel(channelOpts);
312
+ this.channelWrapper = this.connection.createChannel(channelOpts);
289
313
  }
290
314
  /**
291
315
  * Get the underlying connection manager
@@ -300,6 +324,89 @@ var AmqpClient = class {
300
324
  return this.connection;
301
325
  }
302
326
  /**
327
+ * Wait for the channel to be connected and ready.
328
+ *
329
+ * @returns A Future that resolves when the channel is connected
330
+ */
331
+ waitForConnect() {
332
+ return _swan_io_boxed.Future.fromPromise(this.channelWrapper.waitForConnect()).mapError((error) => new TechnicalError("Failed to connect to AMQP broker", error));
333
+ }
334
+ /**
335
+ * Publish a message to an exchange.
336
+ *
337
+ * @param exchange - The exchange name
338
+ * @param routingKey - The routing key
339
+ * @param content - The message content (will be JSON serialized if json: true)
340
+ * @param options - Optional publish options
341
+ * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
342
+ */
343
+ publish(exchange, routingKey, content, options) {
344
+ return _swan_io_boxed.Future.fromPromise(this.channelWrapper.publish(exchange, routingKey, content, options)).mapError((error) => new TechnicalError("Failed to publish message", error));
345
+ }
346
+ /**
347
+ * Start consuming messages from a queue.
348
+ *
349
+ * @param queue - The queue name
350
+ * @param callback - The callback to invoke for each message
351
+ * @param options - Optional consume options
352
+ * @returns A Future with `Result<string>` - the consumer tag
353
+ */
354
+ consume(queue, callback, options) {
355
+ return _swan_io_boxed.Future.fromPromise(this.channelWrapper.consume(queue, callback, options)).mapError((error) => new TechnicalError("Failed to start consuming messages", error)).mapOk((reply) => reply.consumerTag);
356
+ }
357
+ /**
358
+ * Cancel a consumer by its consumer tag.
359
+ *
360
+ * @param consumerTag - The consumer tag to cancel
361
+ * @returns A Future that resolves when the consumer is cancelled
362
+ */
363
+ cancel(consumerTag) {
364
+ return _swan_io_boxed.Future.fromPromise(this.channelWrapper.cancel(consumerTag)).mapError((error) => new TechnicalError("Failed to cancel consumer", error)).mapOk(() => void 0);
365
+ }
366
+ /**
367
+ * Acknowledge a message.
368
+ *
369
+ * @param msg - The message to acknowledge
370
+ * @param allUpTo - If true, acknowledge all messages up to and including this one
371
+ */
372
+ ack(msg, allUpTo = false) {
373
+ this.channelWrapper.ack(msg, allUpTo);
374
+ }
375
+ /**
376
+ * Negative acknowledge a message.
377
+ *
378
+ * @param msg - The message to nack
379
+ * @param allUpTo - If true, nack all messages up to and including this one
380
+ * @param requeue - If true, requeue the message(s)
381
+ */
382
+ nack(msg, allUpTo = false, requeue = true) {
383
+ this.channelWrapper.nack(msg, allUpTo, requeue);
384
+ }
385
+ /**
386
+ * Add a setup function to be called when the channel is created or reconnected.
387
+ *
388
+ * This is useful for setting up channel-level configuration like prefetch.
389
+ *
390
+ * @param setup - The setup function to add
391
+ */
392
+ addSetup(setup) {
393
+ this.channelWrapper.addSetup(setup);
394
+ }
395
+ /**
396
+ * Register an event listener on the channel wrapper.
397
+ *
398
+ * Available events:
399
+ * - 'connect': Emitted when the channel is (re)connected
400
+ * - 'close': Emitted when the channel is closed
401
+ * - 'error': Emitted when an error occurs
402
+ *
403
+ * @param event - The event name
404
+ * @param listener - The event listener
405
+ */
406
+ on(event, listener) {
407
+ this.channelWrapper.on(event, listener);
408
+ }
409
+ /**
303
410
  * Close the channel and release the connection reference.
304
411
  *
305
412
  * This will:
@@ -307,11 +414,10 @@ var AmqpClient = class {
307
414
  * - Decrease the reference count on the shared connection
308
415
  * - Close the connection if this was the last client using it
309
416
  *
310
- * @returns A promise that resolves when the channel and connection are closed
417
+ * @returns A Future that resolves when the channel and connection are closed
311
418
  */
312
- async close() {
313
- await this.channel.close();
314
- await ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions);
419
+ close() {
420
+ return _swan_io_boxed.Future.fromPromise(this.channelWrapper.close()).mapError((error) => new TechnicalError("Failed to close channel", error)).flatMapOk(() => _swan_io_boxed.Future.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions)).mapError((error) => new TechnicalError("Failed to release connection", error))).mapOk(() => void 0);
315
421
  }
316
422
  /**
317
423
  * Reset connection singleton cache (for testing only)
@@ -552,6 +658,7 @@ function _resetTelemetryCacheForTesting() {
552
658
  exports.AmqpClient = AmqpClient;
553
659
  exports.ConnectionManagerSingleton = ConnectionManagerSingleton;
554
660
  exports.MessagingSemanticConventions = MessagingSemanticConventions;
661
+ exports.TechnicalError = TechnicalError;
555
662
  exports._resetTelemetryCacheForTesting = _resetTelemetryCacheForTesting;
556
663
  exports.defaultTelemetryProvider = defaultTelemetryProvider;
557
664
  exports.endSpanError = endSpanError;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
- import { AmqpConnectionManager, AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
1
+ import { AmqpConnectionManager, AmqpConnectionManagerOptions, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
2
+ import { Channel, ConsumeMessage, Options } from "amqplib";
3
+ import { Future, Result } from "@swan-io/boxed";
2
4
  import { ContractDefinition } from "@amqp-contract/contract";
3
- import { Channel } from "amqplib";
4
5
  import { Attributes, Counter, Histogram, Span, Tracer } from "@opentelemetry/api";
5
6
 
6
7
  //#region src/logger.d.ts
@@ -60,6 +61,18 @@ type Logger = {
60
61
  error(message: string, context?: LoggerContext): void;
61
62
  };
62
63
  //#endregion
64
+ //#region src/errors.d.ts
65
+ /**
66
+ * Error for technical/runtime failures that cannot be prevented by TypeScript.
67
+ *
68
+ * This includes AMQP connection failures, channel issues, validation failures,
69
+ * and other runtime errors. This error is shared across core, worker, and client packages.
70
+ */
71
+ declare class TechnicalError extends Error {
72
+ readonly cause?: unknown | undefined;
73
+ constructor(message: string, cause?: unknown | undefined);
74
+ }
75
+ //#endregion
63
76
  //#region src/amqp-client.d.ts
64
77
  /**
65
78
  * Options for creating an AMQP client.
@@ -73,6 +86,10 @@ type AmqpClientOptions = {
73
86
  connectionOptions?: AmqpConnectionManagerOptions | undefined;
74
87
  channelOptions?: Partial<CreateChannelOpts> | undefined;
75
88
  };
89
+ /**
90
+ * Callback type for consuming messages.
91
+ */
92
+ type ConsumeCallback = (msg: ConsumeMessage | null) => void | Promise<void>;
76
93
  /**
77
94
  * AMQP client that manages connections and channels with automatic topology setup.
78
95
  *
@@ -82,6 +99,8 @@ type AmqpClientOptions = {
82
99
  * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
83
100
  * - Channel creation with JSON serialization enabled by default
84
101
  *
102
+ * All operations return `Future<Result<T, TechnicalError>>` for consistent error handling.
103
+ *
85
104
  * @example
86
105
  * ```typescript
87
106
  * const client = new AmqpClient(contract, {
@@ -89,17 +108,20 @@ type AmqpClientOptions = {
89
108
  * connectionOptions: { heartbeatIntervalInSeconds: 30 }
90
109
  * });
91
110
  *
92
- * // Use the channel to publish messages
93
- * await client.channel.publish('exchange', 'routingKey', { data: 'value' });
111
+ * // Wait for connection
112
+ * await client.waitForConnect().resultToPromise();
113
+ *
114
+ * // Publish a message
115
+ * const result = await client.publish('exchange', 'routingKey', { data: 'value' }).resultToPromise();
94
116
  *
95
117
  * // Close when done
96
- * await client.close();
118
+ * await client.close().resultToPromise();
97
119
  * ```
98
120
  */
99
121
  declare class AmqpClient {
100
122
  private readonly contract;
101
123
  private readonly connection;
102
- readonly channel: ChannelWrapper;
124
+ private readonly channelWrapper;
103
125
  private readonly urls;
104
126
  private readonly connectionOptions?;
105
127
  /**
@@ -124,6 +146,73 @@ declare class AmqpClient {
124
146
  * @returns The AmqpConnectionManager instance used by this client
125
147
  */
126
148
  getConnection(): AmqpConnectionManager;
149
+ /**
150
+ * Wait for the channel to be connected and ready.
151
+ *
152
+ * @returns A Future that resolves when the channel is connected
153
+ */
154
+ waitForConnect(): Future<Result<void, TechnicalError>>;
155
+ /**
156
+ * Publish a message to an exchange.
157
+ *
158
+ * @param exchange - The exchange name
159
+ * @param routingKey - The routing key
160
+ * @param content - The message content (will be JSON serialized if json: true)
161
+ * @param options - Optional publish options
162
+ * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
163
+ */
164
+ publish(exchange: string, routingKey: string, content: Buffer | unknown, options?: Options.Publish): Future<Result<boolean, TechnicalError>>;
165
+ /**
166
+ * Start consuming messages from a queue.
167
+ *
168
+ * @param queue - The queue name
169
+ * @param callback - The callback to invoke for each message
170
+ * @param options - Optional consume options
171
+ * @returns A Future with `Result<string>` - the consumer tag
172
+ */
173
+ consume(queue: string, callback: ConsumeCallback, options?: Options.Consume): Future<Result<string, TechnicalError>>;
174
+ /**
175
+ * Cancel a consumer by its consumer tag.
176
+ *
177
+ * @param consumerTag - The consumer tag to cancel
178
+ * @returns A Future that resolves when the consumer is cancelled
179
+ */
180
+ cancel(consumerTag: string): Future<Result<void, TechnicalError>>;
181
+ /**
182
+ * Acknowledge a message.
183
+ *
184
+ * @param msg - The message to acknowledge
185
+ * @param allUpTo - If true, acknowledge all messages up to and including this one
186
+ */
187
+ ack(msg: ConsumeMessage, allUpTo?: boolean): void;
188
+ /**
189
+ * Negative acknowledge a message.
190
+ *
191
+ * @param msg - The message to nack
192
+ * @param allUpTo - If true, nack all messages up to and including this one
193
+ * @param requeue - If true, requeue the message(s)
194
+ */
195
+ nack(msg: ConsumeMessage, allUpTo?: boolean, requeue?: boolean): void;
196
+ /**
197
+ * Add a setup function to be called when the channel is created or reconnected.
198
+ *
199
+ * This is useful for setting up channel-level configuration like prefetch.
200
+ *
201
+ * @param setup - The setup function to add
202
+ */
203
+ addSetup(setup: (channel: Channel) => void | Promise<void>): void;
204
+ /**
205
+ * Register an event listener on the channel wrapper.
206
+ *
207
+ * Available events:
208
+ * - 'connect': Emitted when the channel is (re)connected
209
+ * - 'close': Emitted when the channel is closed
210
+ * - 'error': Emitted when an error occurs
211
+ *
212
+ * @param event - The event name
213
+ * @param listener - The event listener
214
+ */
215
+ on(event: string, listener: (...args: unknown[]) => void): void;
127
216
  /**
128
217
  * Close the channel and release the connection reference.
129
218
  *
@@ -132,9 +221,9 @@ declare class AmqpClient {
132
221
  * - Decrease the reference count on the shared connection
133
222
  * - Close the connection if this was the last client using it
134
223
  *
135
- * @returns A promise that resolves when the channel and connection are closed
224
+ * @returns A Future that resolves when the channel and connection are closed
136
225
  */
137
- close(): Promise<void>;
226
+ close(): Future<Result<void, TechnicalError>>;
138
227
  /**
139
228
  * Reset connection singleton cache (for testing only)
140
229
  * @internal
@@ -338,5 +427,5 @@ declare function recordConsumeMetric(provider: TelemetryProvider, queueName: str
338
427
  */
339
428
  declare function _resetTelemetryCacheForTesting(): void;
340
429
  //#endregion
341
- export { AmqpClient, type AmqpClientOptions, ConnectionManagerSingleton, type Logger, type LoggerContext, MessagingSemanticConventions, type TelemetryProvider, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
430
+ export { AmqpClient, type AmqpClientOptions, ConnectionManagerSingleton, type ConsumeCallback, type Logger, type LoggerContext, MessagingSemanticConventions, TechnicalError, type TelemetryProvider, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
342
431
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/logger.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/setup.ts","../src/telemetry.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAQA;AAqBA;AAMmC,KA3BvB,aAAA,GAAgB,MA2BO,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA;EAOD,KAAA,CAAA,EAAA,OAAA;CAOA;;;;;;AC9BlC;;;;;;AA6BA;;;;;;AAyGkD,KD5HtC,MAAA,GC4HsC;EAAO;;;;ACnIzD;EAYwB,KAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EFCW,aEDX,CAAA,EAAA,IAAA;EAkBd;;;;;EA+BL,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EFzC6B,aEyC7B,CAAA,EAAA,IAAA;EAkFuB;;;;;EC7IN,IAAA,CAAA,OAAA,EAAA,MAAiB,EAAA,OAAA,CAAA,EHyBL,aGzBK,CAAA,EAAA,IAAA;EAC5B;;;;;mCH+BwB;;;;;;;;AAhDnC;AAqBA;;AAakC,KCvBtB,iBAAA,GDuBsB;EAOA,IAAA,EC7B1B,aD6B0B,EAAA;EAOC,iBAAA,CAAA,ECnCb,4BDmCa,GAAA,SAAA;EAAa,cAAA,CAAA,EClC7B,ODkC6B,CClCrB,iBDkCqB,CAAA,GAAA,SAAA;;;;ACrChD;;;;;;AA6BA;;;;;;;;;;;AC1BA;;;;AAgCK,cDNQ,UAAA,CCMR;EA2BK,iBAAA,QAAA;EACc,iBAAA,UAAA;EACnB,SAAA,OAAA,EDjCsB,cCiCtB;EAkFuB,iBAAA,IAAA;EAAO,iBAAA,iBAAA;;;;AC7InC;;;;;;;;ECAa,WAAA,CAAA,QAAA,EH0CkB,kBGlBrB,EAAA,OAAA,EHmBG,iBGnBH;EAME;;;;;;;AA6IZ;AAYA;EACY,aAAA,CAAA,CAAA,EHhFO,qBGgFP;EAGG;;;AA+Bf;;;;;AAgCA;AAeA;EAiBgB,KAAA,CAAA,CAAA,EHpKC,OGoKD,CAAA,IAAA,CAAmB;EA2BnB;AA2BhB;;;4CH/MkD;;;;;;;;;ADjJlD;AAqBA;;;;;;;;;ACVA;AACQ,cCEK,0BAAA,CDFL;EACc,eAAA,QAAA;EACK,QAAA,WAAA;EAAR,QAAA,SAAA;EAAO,QAAA,WAAA,CAAA;EA0Bb;;;;;EA8FI,OAAA,WAAA,CAAA,CAAA,EC5GO,0BD4GP;EAWiC;;;;;ACnIlD;;;;;EA2DU,aAAA,CAAA,IAAA,EA7BA,aA6BA,EAAA,EAAA,iBAAA,CAAA,EA5Bc,4BA4Bd,CAAA,EA3BL,qBA2BK;EACc;;;;;;;AC1DxB;;;EAGG,iBAAA,CAAA,IAAA,EDsDO,aCtDP,EAAA,EAAA,iBAAA,CAAA,EDuDqB,4BCvDrB,CAAA,EDwDE,OCxDF,CAAA,IAAA,CAAA;EAAO;;;;ACHV;AA8BA;;;;;EA6BoC,QAAA,mBAAA;EAAS;AAgH7C;AAYA;;;;EAKO,QAAA,gBAAA;EA8BS;;;;;AAgChB;EAegB,QAAA,QAAY;EAiBZ;AA2BhB;AA2BA;;sBFnM4B;;;;;;;;AF7J5B;AAqBA;;;;;;;;;ACVA;;;;;;AA6Ba,iBExBS,iBAAA,CFwBC,OAAA,EEvBZ,OFuBY,EAAA,QAAA,EEtBX,kBFsBW,CAAA,EErBpB,OFqBoB,CAAA,IAAA,CAAA;;;;;;;cGxBV;EJhBD,SAAA,gBAAa,EAAA,kBAAS;EAqBtB,SAAM,qBAAA,EAAA,4BAAA;EAMiB,SAAA,0BAAA,EAAA,4BAAA;EAOD,SAAA,mBAAA,EAAA,qBAAA;EAOA,SAAA,oBAAA,EAAA,sBAAA;EAOC,SAAA,8BAAA,EAAA,6BAAA;EAAa,SAAA,iCAAA,EAAA,mCAAA;;;;ECrCpC,SAAA,yBAAiB,EAAA,UAAA;EACrB,SAAA,mCAAA,EAAA,UAAA;EACc,SAAA,gCAAA,EAAA,OAAA;EACK,SAAA,2BAAA,EAAA,SAAA;EAAR,SAAA,2BAAA,EAAA,SAAA;EAAO,SAAA,2BAAA,EAAA,SAAA;AA0B1B,CAAA;;;;;AA8FiB,KGxFL,iBAAA,GHwFK;EAWiC;;;;mBG9F/B;EFrCN;;;;EAgCR,iBAAA,EAAA,GAAA,GEWsB,OFXtB,GAAA,SAAA;EA2BK;;;;EAoFyB,iBAAA,EAAA,GAAA,GE9FR,OF8FQ,GAAA,SAAA;;;;AC7InC;EACW,0BAAA,EAAA,GAAA,GCoDyB,SDpDzB,GAAA,SAAA;EACC;;;;oCCyDwB;;AA3DpC;AA8BA;;AAW2B,cAkId,wBAlIc,EAkIY,iBAlIZ;;;;;AAkId,iBAYG,gBAAA,CANf,QANsC,EAa3B,iBAPX,EAAA,YAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,GAAA,SAAA,EAAA,UAAA,CAAA,EAUc,UAVd,CAAA,EAWE,IAXF,GAAA,SAAA;AAMD;;;;AAKO,iBA8BS,gBAAA,CA9BT,QAAA,EA+BK,iBA/BL,EAAA,SAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,UAAA,CAAA,EAkCQ,UAlCR,CAAA,EAmCJ,IAnCI,GAAA,SAAA;AA8BP;;;AAKG,iBA2Ba,cAAA,CA3Bb,IAAA,EA2BkC,IA3BlC,GAAA,SAAA,CAAA,EAAA,IAAA;;AA2BH;AAeA;AAiBgB,iBAjBA,YAAA,CAkBJ,IAAA,EAlBuB,IAkBvB,GAAA,SAAiB,EAAA,KAAA,EAlB+B,KAkB/B,CAAA,EAAA,IAAA;AA0B7B;AA2BA;;iBAtDgB,mBAAA,WACJ;;;;iBA0BI,mBAAA,WACJ;;;;;;iBA0BI,8BAAA,CAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/logger.ts","../src/errors.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/setup.ts","../src/telemetry.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,7 +1,8 @@
1
- import { AmqpConnectionManager, AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
1
+ import { Future, Result } from "@swan-io/boxed";
2
+ import { AmqpConnectionManager, AmqpConnectionManagerOptions, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
2
3
  import { ContractDefinition } from "@amqp-contract/contract";
3
4
  import { Attributes, Counter, Histogram, Span, Tracer } from "@opentelemetry/api";
4
- import { Channel } from "amqplib";
5
+ import { Channel, ConsumeMessage, Options } from "amqplib";
5
6
 
6
7
  //#region src/logger.d.ts
7
8
 
@@ -60,6 +61,18 @@ type Logger = {
60
61
  error(message: string, context?: LoggerContext): void;
61
62
  };
62
63
  //#endregion
64
+ //#region src/errors.d.ts
65
+ /**
66
+ * Error for technical/runtime failures that cannot be prevented by TypeScript.
67
+ *
68
+ * This includes AMQP connection failures, channel issues, validation failures,
69
+ * and other runtime errors. This error is shared across core, worker, and client packages.
70
+ */
71
+ declare class TechnicalError extends Error {
72
+ readonly cause?: unknown | undefined;
73
+ constructor(message: string, cause?: unknown | undefined);
74
+ }
75
+ //#endregion
63
76
  //#region src/amqp-client.d.ts
64
77
  /**
65
78
  * Options for creating an AMQP client.
@@ -73,6 +86,10 @@ type AmqpClientOptions = {
73
86
  connectionOptions?: AmqpConnectionManagerOptions | undefined;
74
87
  channelOptions?: Partial<CreateChannelOpts> | undefined;
75
88
  };
89
+ /**
90
+ * Callback type for consuming messages.
91
+ */
92
+ type ConsumeCallback = (msg: ConsumeMessage | null) => void | Promise<void>;
76
93
  /**
77
94
  * AMQP client that manages connections and channels with automatic topology setup.
78
95
  *
@@ -82,6 +99,8 @@ type AmqpClientOptions = {
82
99
  * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
83
100
  * - Channel creation with JSON serialization enabled by default
84
101
  *
102
+ * All operations return `Future<Result<T, TechnicalError>>` for consistent error handling.
103
+ *
85
104
  * @example
86
105
  * ```typescript
87
106
  * const client = new AmqpClient(contract, {
@@ -89,17 +108,20 @@ type AmqpClientOptions = {
89
108
  * connectionOptions: { heartbeatIntervalInSeconds: 30 }
90
109
  * });
91
110
  *
92
- * // Use the channel to publish messages
93
- * await client.channel.publish('exchange', 'routingKey', { data: 'value' });
111
+ * // Wait for connection
112
+ * await client.waitForConnect().resultToPromise();
113
+ *
114
+ * // Publish a message
115
+ * const result = await client.publish('exchange', 'routingKey', { data: 'value' }).resultToPromise();
94
116
  *
95
117
  * // Close when done
96
- * await client.close();
118
+ * await client.close().resultToPromise();
97
119
  * ```
98
120
  */
99
121
  declare class AmqpClient {
100
122
  private readonly contract;
101
123
  private readonly connection;
102
- readonly channel: ChannelWrapper;
124
+ private readonly channelWrapper;
103
125
  private readonly urls;
104
126
  private readonly connectionOptions?;
105
127
  /**
@@ -124,6 +146,73 @@ declare class AmqpClient {
124
146
  * @returns The AmqpConnectionManager instance used by this client
125
147
  */
126
148
  getConnection(): AmqpConnectionManager;
149
+ /**
150
+ * Wait for the channel to be connected and ready.
151
+ *
152
+ * @returns A Future that resolves when the channel is connected
153
+ */
154
+ waitForConnect(): Future<Result<void, TechnicalError>>;
155
+ /**
156
+ * Publish a message to an exchange.
157
+ *
158
+ * @param exchange - The exchange name
159
+ * @param routingKey - The routing key
160
+ * @param content - The message content (will be JSON serialized if json: true)
161
+ * @param options - Optional publish options
162
+ * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
163
+ */
164
+ publish(exchange: string, routingKey: string, content: Buffer | unknown, options?: Options.Publish): Future<Result<boolean, TechnicalError>>;
165
+ /**
166
+ * Start consuming messages from a queue.
167
+ *
168
+ * @param queue - The queue name
169
+ * @param callback - The callback to invoke for each message
170
+ * @param options - Optional consume options
171
+ * @returns A Future with `Result<string>` - the consumer tag
172
+ */
173
+ consume(queue: string, callback: ConsumeCallback, options?: Options.Consume): Future<Result<string, TechnicalError>>;
174
+ /**
175
+ * Cancel a consumer by its consumer tag.
176
+ *
177
+ * @param consumerTag - The consumer tag to cancel
178
+ * @returns A Future that resolves when the consumer is cancelled
179
+ */
180
+ cancel(consumerTag: string): Future<Result<void, TechnicalError>>;
181
+ /**
182
+ * Acknowledge a message.
183
+ *
184
+ * @param msg - The message to acknowledge
185
+ * @param allUpTo - If true, acknowledge all messages up to and including this one
186
+ */
187
+ ack(msg: ConsumeMessage, allUpTo?: boolean): void;
188
+ /**
189
+ * Negative acknowledge a message.
190
+ *
191
+ * @param msg - The message to nack
192
+ * @param allUpTo - If true, nack all messages up to and including this one
193
+ * @param requeue - If true, requeue the message(s)
194
+ */
195
+ nack(msg: ConsumeMessage, allUpTo?: boolean, requeue?: boolean): void;
196
+ /**
197
+ * Add a setup function to be called when the channel is created or reconnected.
198
+ *
199
+ * This is useful for setting up channel-level configuration like prefetch.
200
+ *
201
+ * @param setup - The setup function to add
202
+ */
203
+ addSetup(setup: (channel: Channel) => void | Promise<void>): void;
204
+ /**
205
+ * Register an event listener on the channel wrapper.
206
+ *
207
+ * Available events:
208
+ * - 'connect': Emitted when the channel is (re)connected
209
+ * - 'close': Emitted when the channel is closed
210
+ * - 'error': Emitted when an error occurs
211
+ *
212
+ * @param event - The event name
213
+ * @param listener - The event listener
214
+ */
215
+ on(event: string, listener: (...args: unknown[]) => void): void;
127
216
  /**
128
217
  * Close the channel and release the connection reference.
129
218
  *
@@ -132,9 +221,9 @@ declare class AmqpClient {
132
221
  * - Decrease the reference count on the shared connection
133
222
  * - Close the connection if this was the last client using it
134
223
  *
135
- * @returns A promise that resolves when the channel and connection are closed
224
+ * @returns A Future that resolves when the channel and connection are closed
136
225
  */
137
- close(): Promise<void>;
226
+ close(): Future<Result<void, TechnicalError>>;
138
227
  /**
139
228
  * Reset connection singleton cache (for testing only)
140
229
  * @internal
@@ -338,5 +427,5 @@ declare function recordConsumeMetric(provider: TelemetryProvider, queueName: str
338
427
  */
339
428
  declare function _resetTelemetryCacheForTesting(): void;
340
429
  //#endregion
341
- export { AmqpClient, type AmqpClientOptions, ConnectionManagerSingleton, type Logger, type LoggerContext, MessagingSemanticConventions, type TelemetryProvider, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
430
+ export { AmqpClient, type AmqpClientOptions, ConnectionManagerSingleton, type ConsumeCallback, type Logger, type LoggerContext, MessagingSemanticConventions, TechnicalError, type TelemetryProvider, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
342
431
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/logger.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/setup.ts","../src/telemetry.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/logger.ts","../src/errors.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/setup.ts","../src/telemetry.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
+ import { Future } from "@swan-io/boxed";
2
3
  import amqp from "amqp-connection-manager";
3
4
  import { extractQueue } from "@amqp-contract/contract";
4
5
 
@@ -131,6 +132,24 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
131
132
  }
132
133
  };
133
134
 
135
+ //#endregion
136
+ //#region src/errors.ts
137
+ /**
138
+ * Error for technical/runtime failures that cannot be prevented by TypeScript.
139
+ *
140
+ * This includes AMQP connection failures, channel issues, validation failures,
141
+ * and other runtime errors. This error is shared across core, worker, and client packages.
142
+ */
143
+ var TechnicalError = class extends Error {
144
+ constructor(message, cause) {
145
+ super(message);
146
+ this.cause = cause;
147
+ this.name = "TechnicalError";
148
+ const ErrorConstructor = Error;
149
+ if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
150
+ }
151
+ };
152
+
134
153
  //#endregion
135
154
  //#region src/setup.ts
136
155
  /**
@@ -210,6 +229,8 @@ async function setupAmqpTopology(channel, contract) {
210
229
  * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
211
230
  * - Channel creation with JSON serialization enabled by default
212
231
  *
232
+ * All operations return `Future<Result<T, TechnicalError>>` for consistent error handling.
233
+ *
213
234
  * @example
214
235
  * ```typescript
215
236
  * const client = new AmqpClient(contract, {
@@ -217,16 +238,19 @@ async function setupAmqpTopology(channel, contract) {
217
238
  * connectionOptions: { heartbeatIntervalInSeconds: 30 }
218
239
  * });
219
240
  *
220
- * // Use the channel to publish messages
221
- * await client.channel.publish('exchange', 'routingKey', { data: 'value' });
241
+ * // Wait for connection
242
+ * await client.waitForConnect().resultToPromise();
243
+ *
244
+ * // Publish a message
245
+ * const result = await client.publish('exchange', 'routingKey', { data: 'value' }).resultToPromise();
222
246
  *
223
247
  * // Close when done
224
- * await client.close();
248
+ * await client.close().resultToPromise();
225
249
  * ```
226
250
  */
227
251
  var AmqpClient = class {
228
252
  connection;
229
- channel;
253
+ channelWrapper;
230
254
  urls;
231
255
  connectionOptions;
232
256
  /**
@@ -262,7 +286,7 @@ var AmqpClient = class {
262
286
  });
263
287
  else await userSetup(channel);
264
288
  };
265
- this.channel = this.connection.createChannel(channelOpts);
289
+ this.channelWrapper = this.connection.createChannel(channelOpts);
266
290
  }
267
291
  /**
268
292
  * Get the underlying connection manager
@@ -277,6 +301,89 @@ var AmqpClient = class {
277
301
  return this.connection;
278
302
  }
279
303
  /**
304
+ * Wait for the channel to be connected and ready.
305
+ *
306
+ * @returns A Future that resolves when the channel is connected
307
+ */
308
+ waitForConnect() {
309
+ return Future.fromPromise(this.channelWrapper.waitForConnect()).mapError((error) => new TechnicalError("Failed to connect to AMQP broker", error));
310
+ }
311
+ /**
312
+ * Publish a message to an exchange.
313
+ *
314
+ * @param exchange - The exchange name
315
+ * @param routingKey - The routing key
316
+ * @param content - The message content (will be JSON serialized if json: true)
317
+ * @param options - Optional publish options
318
+ * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
319
+ */
320
+ publish(exchange, routingKey, content, options) {
321
+ return Future.fromPromise(this.channelWrapper.publish(exchange, routingKey, content, options)).mapError((error) => new TechnicalError("Failed to publish message", error));
322
+ }
323
+ /**
324
+ * Start consuming messages from a queue.
325
+ *
326
+ * @param queue - The queue name
327
+ * @param callback - The callback to invoke for each message
328
+ * @param options - Optional consume options
329
+ * @returns A Future with `Result<string>` - the consumer tag
330
+ */
331
+ consume(queue, callback, options) {
332
+ return Future.fromPromise(this.channelWrapper.consume(queue, callback, options)).mapError((error) => new TechnicalError("Failed to start consuming messages", error)).mapOk((reply) => reply.consumerTag);
333
+ }
334
+ /**
335
+ * Cancel a consumer by its consumer tag.
336
+ *
337
+ * @param consumerTag - The consumer tag to cancel
338
+ * @returns A Future that resolves when the consumer is cancelled
339
+ */
340
+ cancel(consumerTag) {
341
+ return Future.fromPromise(this.channelWrapper.cancel(consumerTag)).mapError((error) => new TechnicalError("Failed to cancel consumer", error)).mapOk(() => void 0);
342
+ }
343
+ /**
344
+ * Acknowledge a message.
345
+ *
346
+ * @param msg - The message to acknowledge
347
+ * @param allUpTo - If true, acknowledge all messages up to and including this one
348
+ */
349
+ ack(msg, allUpTo = false) {
350
+ this.channelWrapper.ack(msg, allUpTo);
351
+ }
352
+ /**
353
+ * Negative acknowledge a message.
354
+ *
355
+ * @param msg - The message to nack
356
+ * @param allUpTo - If true, nack all messages up to and including this one
357
+ * @param requeue - If true, requeue the message(s)
358
+ */
359
+ nack(msg, allUpTo = false, requeue = true) {
360
+ this.channelWrapper.nack(msg, allUpTo, requeue);
361
+ }
362
+ /**
363
+ * Add a setup function to be called when the channel is created or reconnected.
364
+ *
365
+ * This is useful for setting up channel-level configuration like prefetch.
366
+ *
367
+ * @param setup - The setup function to add
368
+ */
369
+ addSetup(setup) {
370
+ this.channelWrapper.addSetup(setup);
371
+ }
372
+ /**
373
+ * Register an event listener on the channel wrapper.
374
+ *
375
+ * Available events:
376
+ * - 'connect': Emitted when the channel is (re)connected
377
+ * - 'close': Emitted when the channel is closed
378
+ * - 'error': Emitted when an error occurs
379
+ *
380
+ * @param event - The event name
381
+ * @param listener - The event listener
382
+ */
383
+ on(event, listener) {
384
+ this.channelWrapper.on(event, listener);
385
+ }
386
+ /**
280
387
  * Close the channel and release the connection reference.
281
388
  *
282
389
  * This will:
@@ -284,11 +391,10 @@ var AmqpClient = class {
284
391
  * - Decrease the reference count on the shared connection
285
392
  * - Close the connection if this was the last client using it
286
393
  *
287
- * @returns A promise that resolves when the channel and connection are closed
394
+ * @returns A Future that resolves when the channel and connection are closed
288
395
  */
289
- async close() {
290
- await this.channel.close();
291
- await ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions);
396
+ close() {
397
+ return Future.fromPromise(this.channelWrapper.close()).mapError((error) => new TechnicalError("Failed to close channel", error)).flatMapOk(() => Future.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions)).mapError((error) => new TechnicalError("Failed to release connection", error))).mapOk(() => void 0);
292
398
  }
293
399
  /**
294
400
  * Reset connection singleton cache (for testing only)
@@ -526,5 +632,5 @@ function _resetTelemetryCacheForTesting() {
526
632
  }
527
633
 
528
634
  //#endregion
529
- export { AmqpClient, ConnectionManagerSingleton, MessagingSemanticConventions, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
635
+ export { AmqpClient, ConnectionManagerSingleton, MessagingSemanticConventions, TechnicalError, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
530
636
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/connection-manager.ts","../src/setup.ts","../src/amqp-client.ts","../src/telemetry.ts"],"sourcesContent":["import amqp, {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ConnectionUrl,\n} from \"amqp-connection-manager\";\n\n/**\n * Connection manager singleton for sharing AMQP connections across clients.\n *\n * This singleton implements connection pooling to avoid creating multiple connections\n * to the same broker, which is a RabbitMQ best practice. Connections are identified\n * by their URLs and connection options, and reference counting ensures connections\n * are only closed when all clients have released them.\n *\n * @example\n * ```typescript\n * const manager = ConnectionManagerSingleton.getInstance();\n * const connection = manager.getConnection(['amqp://localhost']);\n * // ... use connection ...\n * await manager.releaseConnection(['amqp://localhost']);\n * ```\n */\nexport class ConnectionManagerSingleton {\n private static instance: ConnectionManagerSingleton;\n private connections: Map<string, AmqpConnectionManager> = new Map();\n private refCounts: Map<string, number> = new Map();\n\n private constructor() {}\n\n /**\n * Get the singleton instance of the connection manager.\n *\n * @returns The singleton instance\n */\n static getInstance(): ConnectionManagerSingleton {\n if (!ConnectionManagerSingleton.instance) {\n ConnectionManagerSingleton.instance = new ConnectionManagerSingleton();\n }\n return ConnectionManagerSingleton.instance;\n }\n\n /**\n * Get or create a connection for the given URLs and options.\n *\n * If a connection already exists with the same URLs and options, it is reused\n * and its reference count is incremented. Otherwise, a new connection is created.\n *\n * @param urls - AMQP broker URL(s)\n * @param connectionOptions - Optional connection configuration\n * @returns The AMQP connection manager instance\n */\n getConnection(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): AmqpConnectionManager {\n // Create a key based on URLs and connection options\n const key = this.createConnectionKey(urls, connectionOptions);\n\n if (!this.connections.has(key)) {\n const connection = amqp.connect(urls, connectionOptions);\n this.connections.set(key, connection);\n this.refCounts.set(key, 0);\n }\n\n // Increment reference count\n this.refCounts.set(key, (this.refCounts.get(key) ?? 0) + 1);\n\n return this.connections.get(key)!;\n }\n\n /**\n * Release a connection reference.\n *\n * Decrements the reference count for the connection. If the count reaches zero,\n * the connection is closed and removed from the pool.\n *\n * @param urls - AMQP broker URL(s) used to identify the connection\n * @param connectionOptions - Optional connection configuration used to identify the connection\n * @returns A promise that resolves when the connection is released (and closed if necessary)\n */\n async releaseConnection(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): Promise<void> {\n const key = this.createConnectionKey(urls, connectionOptions);\n const refCount = this.refCounts.get(key) ?? 0;\n\n if (refCount <= 1) {\n // Last reference - close and remove connection\n const connection = this.connections.get(key);\n if (connection) {\n await connection.close();\n this.connections.delete(key);\n this.refCounts.delete(key);\n }\n } else {\n // Decrement reference count\n this.refCounts.set(key, refCount - 1);\n }\n }\n\n /**\n * Create a unique key for a connection based on URLs and options.\n *\n * The key is deterministic: same URLs and options always produce the same key,\n * enabling connection reuse.\n *\n * @param urls - AMQP broker URL(s)\n * @param connectionOptions - Optional connection configuration\n * @returns A unique string key identifying the connection\n */\n private createConnectionKey(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): string {\n // Create a deterministic key from URLs and options\n // Use JSON.stringify for URLs to avoid ambiguity (e.g., ['a,b'] vs ['a', 'b'])\n const urlsStr = JSON.stringify(urls);\n // Sort object keys for deterministic serialization of connection options\n const optsStr = connectionOptions ? this.serializeOptions(connectionOptions) : \"\";\n return `${urlsStr}::${optsStr}`;\n }\n\n /**\n * Serialize connection options to a deterministic string.\n *\n * @param options - Connection options to serialize\n * @returns A JSON string with sorted keys for deterministic comparison\n */\n private serializeOptions(options: AmqpConnectionManagerOptions): string {\n // Create a deterministic string representation by deeply sorting all object keys\n const sorted = this.deepSort(options);\n return JSON.stringify(sorted);\n }\n\n /**\n * Deep sort an object's keys for deterministic serialization.\n *\n * @param value - The value to deep sort (can be object, array, or primitive)\n * @returns The value with all object keys sorted alphabetically\n */\n private deepSort(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => this.deepSort(item));\n }\n\n if (value !== null && typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n const sortedKeys = Object.keys(obj).sort();\n const result: Record<string, unknown> = {};\n\n for (const key of sortedKeys) {\n result[key] = this.deepSort(obj[key]);\n }\n\n return result;\n }\n\n return value;\n }\n\n /**\n * Reset all cached connections (for testing purposes)\n * @internal\n */\n async _resetForTesting(): Promise<void> {\n // Close all connections before clearing\n const closePromises = Array.from(this.connections.values()).map((conn) => conn.close());\n await Promise.all(closePromises);\n this.connections.clear();\n this.refCounts.clear();\n }\n}\n","import type { Channel } from \"amqplib\";\nimport type { ContractDefinition } from \"@amqp-contract/contract\";\nimport { extractQueue } from \"@amqp-contract/contract\";\n\n/**\n * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.\n *\n * This function sets up the complete AMQP topology in the correct order:\n * 1. Assert all exchanges defined in the contract\n * 2. Validate dead letter exchanges are declared before referencing them\n * 3. Assert all queues with their configurations (including dead letter settings)\n * 4. Create all bindings (queue-to-exchange and exchange-to-exchange)\n *\n * @param channel - The AMQP channel to use for topology setup\n * @param contract - The contract definition containing the topology specification\n * @throws {AggregateError} If any exchanges, queues, or bindings fail to be created\n * @throws {Error} If a queue references a dead letter exchange not declared in the contract\n *\n * @example\n * ```typescript\n * const channel = await connection.createChannel();\n * await setupAmqpTopology(channel, contract);\n * ```\n */\nexport async function setupAmqpTopology(\n channel: Channel,\n contract: ContractDefinition,\n): Promise<void> {\n // Setup exchanges\n const exchangeResults = await Promise.allSettled(\n Object.values(contract.exchanges ?? {}).map((exchange) =>\n channel.assertExchange(exchange.name, exchange.type, {\n durable: exchange.durable,\n autoDelete: exchange.autoDelete,\n internal: exchange.internal,\n arguments: exchange.arguments,\n }),\n ),\n );\n const exchangeErrors = exchangeResults.filter(\n (result): result is PromiseRejectedResult => result.status === \"rejected\",\n );\n if (exchangeErrors.length > 0) {\n throw new AggregateError(\n exchangeErrors.map(({ reason }) => reason),\n \"Failed to setup exchanges\",\n );\n }\n\n // Validate dead letter exchanges before setting up queues\n for (const queueEntry of Object.values(contract.queues ?? {})) {\n const queue = extractQueue(queueEntry);\n if (queue.deadLetter) {\n const dlxName = queue.deadLetter.exchange.name;\n const exchangeExists = Object.values(contract.exchanges ?? {}).some(\n (exchange) => exchange.name === dlxName,\n );\n\n if (!exchangeExists) {\n throw new Error(\n `Queue \"${queue.name}\" references dead letter exchange \"${dlxName}\" which is not declared in the contract. ` +\n `Add the exchange to contract.exchanges to ensure it is created before the queue.`,\n );\n }\n }\n }\n\n // Setup queues\n const queueResults = await Promise.allSettled(\n Object.values(contract.queues ?? {}).map((queueEntry) => {\n const queue = extractQueue(queueEntry);\n // Build queue arguments, merging dead letter configuration and queue type\n const queueArguments: Record<string, unknown> = { ...queue.arguments };\n\n // Set queue type\n queueArguments[\"x-queue-type\"] = queue.type;\n\n if (queue.deadLetter) {\n queueArguments[\"x-dead-letter-exchange\"] = queue.deadLetter.exchange.name;\n if (queue.deadLetter.routingKey) {\n queueArguments[\"x-dead-letter-routing-key\"] = queue.deadLetter.routingKey;\n }\n }\n\n // Handle type-specific properties using discriminated union\n if (queue.type === \"quorum\") {\n // Set delivery limit for quorum queues (native retry support)\n if (queue.deliveryLimit !== undefined) {\n queueArguments[\"x-delivery-limit\"] = queue.deliveryLimit;\n }\n\n // Quorum queues are always durable\n return channel.assertQueue(queue.name, {\n durable: true,\n autoDelete: queue.autoDelete,\n arguments: queueArguments,\n });\n }\n\n // Classic queue\n return channel.assertQueue(queue.name, {\n durable: queue.durable,\n exclusive: queue.exclusive,\n autoDelete: queue.autoDelete,\n arguments: queueArguments,\n });\n }),\n );\n const queueErrors = queueResults.filter(\n (result): result is PromiseRejectedResult => result.status === \"rejected\",\n );\n if (queueErrors.length > 0) {\n throw new AggregateError(\n queueErrors.map(({ reason }) => reason),\n \"Failed to setup queues\",\n );\n }\n\n // Setup bindings\n const bindingResults = await Promise.allSettled(\n Object.values(contract.bindings ?? {}).map((binding) => {\n if (binding.type === \"queue\") {\n return channel.bindQueue(\n binding.queue.name,\n binding.exchange.name,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }\n\n return channel.bindExchange(\n binding.destination.name,\n binding.source.name,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }),\n );\n const bindingErrors = bindingResults.filter(\n (result): result is PromiseRejectedResult => result.status === \"rejected\",\n );\n if (bindingErrors.length > 0) {\n throw new AggregateError(\n bindingErrors.map(({ reason }) => reason),\n \"Failed to setup bindings\",\n );\n }\n}\n","import type {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ChannelWrapper,\n ConnectionUrl,\n CreateChannelOpts,\n} from \"amqp-connection-manager\";\nimport type { Channel } from \"amqplib\";\nimport { ConnectionManagerSingleton } from \"./connection-manager.js\";\nimport type { ContractDefinition } from \"@amqp-contract/contract\";\nimport { setupAmqpTopology } from \"./setup.js\";\n\n/**\n * Options for creating an AMQP client.\n *\n * @property urls - AMQP broker URL(s). Multiple URLs provide failover support.\n * @property connectionOptions - Optional connection configuration (heartbeat, reconnect settings, etc.).\n * @property channelOptions - Optional channel configuration options.\n */\nexport type AmqpClientOptions = {\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n channelOptions?: Partial<CreateChannelOpts> | undefined;\n};\n\n/**\n * AMQP client that manages connections and channels with automatic topology setup.\n *\n * This class handles:\n * - Connection management with automatic reconnection via amqp-connection-manager\n * - Connection pooling and sharing across instances with the same URLs\n * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract\n * - Channel creation with JSON serialization enabled by default\n *\n * @example\n * ```typescript\n * const client = new AmqpClient(contract, {\n * urls: ['amqp://localhost'],\n * connectionOptions: { heartbeatIntervalInSeconds: 30 }\n * });\n *\n * // Use the channel to publish messages\n * await client.channel.publish('exchange', 'routingKey', { data: 'value' });\n *\n * // Close when done\n * await client.close();\n * ```\n */\nexport class AmqpClient {\n private readonly connection: AmqpConnectionManager;\n public readonly channel: ChannelWrapper;\n private readonly urls: ConnectionUrl[];\n private readonly connectionOptions?: AmqpConnectionManagerOptions;\n\n /**\n * Create a new AMQP client instance.\n *\n * The client will automatically:\n * - Get or create a shared connection using the singleton pattern\n * - Set up AMQP topology (exchanges, queues, bindings) from the contract\n * - Create a channel with JSON serialization enabled\n *\n * @param contract - The contract definition specifying the AMQP topology\n * @param options - Client configuration options\n */\n constructor(\n private readonly contract: ContractDefinition,\n options: AmqpClientOptions,\n ) {\n // Store for cleanup\n this.urls = options.urls;\n if (options.connectionOptions !== undefined) {\n this.connectionOptions = options.connectionOptions;\n }\n\n // Always use singleton to get/create connection\n const singleton = ConnectionManagerSingleton.getInstance();\n this.connection = singleton.getConnection(options.urls, options.connectionOptions);\n\n // Create default setup function that calls setupAmqpTopology\n const defaultSetup = (channel: Channel) => setupAmqpTopology(channel, this.contract);\n\n // Destructure setup from channelOptions to handle it separately\n const { setup: userSetup, ...otherChannelOptions } = options.channelOptions ?? {};\n\n // Merge user-provided channel options with defaults\n const channelOpts: CreateChannelOpts = {\n json: true,\n setup: defaultSetup,\n ...otherChannelOptions,\n };\n\n // If user provided a custom setup, wrap it to call both\n if (userSetup) {\n channelOpts.setup = async (channel: Channel) => {\n // First run the topology setup\n await defaultSetup(channel);\n // Then run user's setup - check arity to determine if it expects a callback\n if (userSetup.length === 2) {\n // Callback-based setup function\n await new Promise<void>((resolve, reject) => {\n (userSetup as (channel: Channel, callback: (error?: Error) => void) => void)(\n channel,\n (error?: Error) => {\n if (error) reject(error);\n else resolve();\n },\n );\n });\n } else {\n // Promise-based setup function\n await (userSetup as (channel: Channel) => Promise<void>)(channel);\n }\n };\n }\n\n this.channel = this.connection.createChannel(channelOpts);\n }\n\n /**\n * Get the underlying connection manager\n *\n * This method exposes the AmqpConnectionManager instance that this client uses.\n * The connection is automatically shared across all AmqpClient instances that\n * use the same URLs and connection options.\n *\n * @returns The AmqpConnectionManager instance used by this client\n */\n getConnection(): AmqpConnectionManager {\n return this.connection;\n }\n\n /**\n * Close the channel and release the connection reference.\n *\n * This will:\n * - Close the channel wrapper\n * - Decrease the reference count on the shared connection\n * - Close the connection if this was the last client using it\n *\n * @returns A promise that resolves when the channel and connection are closed\n */\n async close(): Promise<void> {\n await this.channel.close();\n // Release connection reference - will close connection if this was the last reference\n const singleton = ConnectionManagerSingleton.getInstance();\n await singleton.releaseConnection(this.urls, this.connectionOptions);\n }\n\n /**\n * Reset connection singleton cache (for testing only)\n * @internal\n */\n static async _resetConnectionCacheForTesting(): Promise<void> {\n await ConnectionManagerSingleton.getInstance()._resetForTesting();\n }\n}\n","import {\n type Attributes,\n type Counter,\n type Histogram,\n type Span,\n type Tracer,\n} from \"@opentelemetry/api\";\n\n/**\n * SpanKind values from OpenTelemetry.\n * Defined as constants to avoid runtime dependency when types are used.\n * @see https://opentelemetry.io/docs/specs/otel/trace/api/#spankind\n */\nconst SpanKind = {\n /** Producer span represents a message producer */\n PRODUCER: 3,\n /** Consumer span represents a message consumer */\n CONSUMER: 4,\n} as const;\n\n/**\n * Semantic conventions for AMQP messaging following OpenTelemetry standards.\n * @see https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/\n */\nexport const MessagingSemanticConventions = {\n // Messaging attributes\n MESSAGING_SYSTEM: \"messaging.system\",\n MESSAGING_DESTINATION: \"messaging.destination.name\",\n MESSAGING_DESTINATION_KIND: \"messaging.destination.kind\",\n MESSAGING_OPERATION: \"messaging.operation\",\n MESSAGING_MESSAGE_ID: \"messaging.message.id\",\n MESSAGING_MESSAGE_PAYLOAD_SIZE: \"messaging.message.body.size\",\n MESSAGING_MESSAGE_CONVERSATION_ID: \"messaging.message.conversation_id\",\n\n // AMQP specific attributes\n MESSAGING_RABBITMQ_ROUTING_KEY: \"messaging.rabbitmq.destination.routing_key\",\n MESSAGING_RABBITMQ_MESSAGE_DELIVERY_TAG: \"messaging.rabbitmq.message.delivery_tag\",\n\n // Error attributes\n ERROR_TYPE: \"error.type\",\n\n // Values\n MESSAGING_SYSTEM_RABBITMQ: \"rabbitmq\",\n MESSAGING_DESTINATION_KIND_EXCHANGE: \"exchange\",\n MESSAGING_DESTINATION_KIND_QUEUE: \"queue\",\n MESSAGING_OPERATION_PUBLISH: \"publish\",\n MESSAGING_OPERATION_RECEIVE: \"receive\",\n MESSAGING_OPERATION_PROCESS: \"process\",\n} as const;\n\n/**\n * Telemetry provider for AMQP operations.\n * Uses lazy loading to gracefully handle cases where OpenTelemetry is not installed.\n */\nexport type TelemetryProvider = {\n /**\n * Get a tracer instance for creating spans.\n * Returns undefined if OpenTelemetry is not available.\n */\n getTracer: () => Tracer | undefined;\n\n /**\n * Get a counter for messages published.\n * Returns undefined if OpenTelemetry is not available.\n */\n getPublishCounter: () => Counter | undefined;\n\n /**\n * Get a counter for messages consumed.\n * Returns undefined if OpenTelemetry is not available.\n */\n getConsumeCounter: () => Counter | undefined;\n\n /**\n * Get a histogram for publish latency.\n * Returns undefined if OpenTelemetry is not available.\n */\n getPublishLatencyHistogram: () => Histogram | undefined;\n\n /**\n * Get a histogram for consume/process latency.\n * Returns undefined if OpenTelemetry is not available.\n */\n getConsumeLatencyHistogram: () => Histogram | undefined;\n};\n\n/**\n * Instrumentation scope name for amqp-contract.\n */\nconst INSTRUMENTATION_SCOPE_NAME = \"@amqp-contract\";\nconst INSTRUMENTATION_SCOPE_VERSION = \"0.1.0\";\n\n// Cache for OpenTelemetry API module and instruments\nlet otelApi: typeof import(\"@opentelemetry/api\") | null | undefined;\nlet cachedTracer: Tracer | undefined;\nlet cachedPublishCounter: Counter | undefined;\nlet cachedConsumeCounter: Counter | undefined;\nlet cachedPublishLatencyHistogram: Histogram | undefined;\nlet cachedConsumeLatencyHistogram: Histogram | undefined;\n\n/**\n * Try to load the OpenTelemetry API module.\n * Returns null if the module is not available.\n */\nfunction tryLoadOpenTelemetryApi(): typeof import(\"@opentelemetry/api\") | null {\n if (otelApi === undefined) {\n try {\n // Dynamic import using require to avoid bundler issues\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n otelApi = require(\"@opentelemetry/api\") as typeof import(\"@opentelemetry/api\");\n } catch {\n otelApi = null;\n }\n }\n return otelApi;\n}\n\n/**\n * Get or create a tracer instance.\n */\nfunction getTracer(): Tracer | undefined {\n if (cachedTracer !== undefined) {\n return cachedTracer;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (!api) {\n return undefined;\n }\n\n cachedTracer = api.trace.getTracer(INSTRUMENTATION_SCOPE_NAME, INSTRUMENTATION_SCOPE_VERSION);\n return cachedTracer;\n}\n\n/**\n * Get or create a meter and its instruments.\n */\nfunction getMeterInstruments(): {\n publishCounter: Counter | undefined;\n consumeCounter: Counter | undefined;\n publishLatencyHistogram: Histogram | undefined;\n consumeLatencyHistogram: Histogram | undefined;\n} {\n if (cachedPublishCounter !== undefined) {\n return {\n publishCounter: cachedPublishCounter,\n consumeCounter: cachedConsumeCounter,\n publishLatencyHistogram: cachedPublishLatencyHistogram,\n consumeLatencyHistogram: cachedConsumeLatencyHistogram,\n };\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (!api) {\n return {\n publishCounter: undefined,\n consumeCounter: undefined,\n publishLatencyHistogram: undefined,\n consumeLatencyHistogram: undefined,\n };\n }\n\n const meter = api.metrics.getMeter(INSTRUMENTATION_SCOPE_NAME, INSTRUMENTATION_SCOPE_VERSION);\n\n cachedPublishCounter = meter.createCounter(\"amqp.client.messages.published\", {\n description: \"Number of messages published to AMQP broker\",\n unit: \"{message}\",\n });\n\n cachedConsumeCounter = meter.createCounter(\"amqp.worker.messages.consumed\", {\n description: \"Number of messages consumed from AMQP broker\",\n unit: \"{message}\",\n });\n\n cachedPublishLatencyHistogram = meter.createHistogram(\"amqp.client.publish.duration\", {\n description: \"Duration of message publish operations\",\n unit: \"ms\",\n });\n\n cachedConsumeLatencyHistogram = meter.createHistogram(\"amqp.worker.process.duration\", {\n description: \"Duration of message processing operations\",\n unit: \"ms\",\n });\n\n return {\n publishCounter: cachedPublishCounter,\n consumeCounter: cachedConsumeCounter,\n publishLatencyHistogram: cachedPublishLatencyHistogram,\n consumeLatencyHistogram: cachedConsumeLatencyHistogram,\n };\n}\n\n/**\n * Default telemetry provider that uses OpenTelemetry API if available.\n */\nexport const defaultTelemetryProvider: TelemetryProvider = {\n getTracer,\n getPublishCounter: () => getMeterInstruments().publishCounter,\n getConsumeCounter: () => getMeterInstruments().consumeCounter,\n getPublishLatencyHistogram: () => getMeterInstruments().publishLatencyHistogram,\n getConsumeLatencyHistogram: () => getMeterInstruments().consumeLatencyHistogram,\n};\n\n/**\n * Create a span for a publish operation.\n * Returns undefined if OpenTelemetry is not available.\n */\nexport function startPublishSpan(\n provider: TelemetryProvider,\n exchangeName: string,\n routingKey: string | undefined,\n attributes?: Attributes,\n): Span | undefined {\n const tracer = provider.getTracer();\n if (!tracer) {\n return undefined;\n }\n\n const spanName = `${exchangeName} publish`;\n\n return tracer.startSpan(spanName, {\n kind: SpanKind.PRODUCER,\n attributes: {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: exchangeName,\n [MessagingSemanticConventions.MESSAGING_DESTINATION_KIND]:\n MessagingSemanticConventions.MESSAGING_DESTINATION_KIND_EXCHANGE,\n [MessagingSemanticConventions.MESSAGING_OPERATION]:\n MessagingSemanticConventions.MESSAGING_OPERATION_PUBLISH,\n ...(routingKey\n ? { [MessagingSemanticConventions.MESSAGING_RABBITMQ_ROUTING_KEY]: routingKey }\n : {}),\n ...attributes,\n },\n });\n}\n\n/**\n * Create a span for a consume/process operation.\n * Returns undefined if OpenTelemetry is not available.\n */\nexport function startConsumeSpan(\n provider: TelemetryProvider,\n queueName: string,\n consumerName: string,\n attributes?: Attributes,\n): Span | undefined {\n const tracer = provider.getTracer();\n if (!tracer) {\n return undefined;\n }\n\n const spanName = `${queueName} process`;\n\n return tracer.startSpan(spanName, {\n kind: SpanKind.CONSUMER,\n attributes: {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: queueName,\n [MessagingSemanticConventions.MESSAGING_DESTINATION_KIND]:\n MessagingSemanticConventions.MESSAGING_DESTINATION_KIND_QUEUE,\n [MessagingSemanticConventions.MESSAGING_OPERATION]:\n MessagingSemanticConventions.MESSAGING_OPERATION_PROCESS,\n \"amqp.consumer.name\": consumerName,\n ...attributes,\n },\n });\n}\n\n/**\n * End a span with success status.\n */\nexport function endSpanSuccess(span: Span | undefined): void {\n if (!span) {\n return;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (api) {\n span.setStatus({ code: api.SpanStatusCode.OK });\n }\n span.end();\n}\n\n/**\n * End a span with error status.\n */\nexport function endSpanError(span: Span | undefined, error: Error): void {\n if (!span) {\n return;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (api) {\n span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });\n span.recordException(error);\n span.setAttribute(MessagingSemanticConventions.ERROR_TYPE, error.name);\n }\n span.end();\n}\n\n/**\n * Record a publish metric.\n */\nexport function recordPublishMetric(\n provider: TelemetryProvider,\n exchangeName: string,\n routingKey: string | undefined,\n success: boolean,\n durationMs: number,\n): void {\n const publishCounter = provider.getPublishCounter();\n const publishLatencyHistogram = provider.getPublishLatencyHistogram();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: exchangeName,\n ...(routingKey\n ? { [MessagingSemanticConventions.MESSAGING_RABBITMQ_ROUTING_KEY]: routingKey }\n : {}),\n success: success,\n };\n\n publishCounter?.add(1, attributes);\n publishLatencyHistogram?.record(durationMs, attributes);\n}\n\n/**\n * Record a consume metric.\n */\nexport function recordConsumeMetric(\n provider: TelemetryProvider,\n queueName: string,\n consumerName: string,\n success: boolean,\n durationMs: number,\n): void {\n const consumeCounter = provider.getConsumeCounter();\n const consumeLatencyHistogram = provider.getConsumeLatencyHistogram();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: queueName,\n \"amqp.consumer.name\": consumerName,\n success: success,\n };\n\n consumeCounter?.add(1, attributes);\n consumeLatencyHistogram?.record(durationMs, attributes);\n}\n\n/**\n * Reset the cached OpenTelemetry API module and instruments.\n * For testing purposes only.\n * @internal\n */\nexport function _resetTelemetryCacheForTesting(): void {\n otelApi = undefined;\n cachedTracer = undefined;\n cachedPublishCounter = undefined;\n cachedConsumeCounter = undefined;\n cachedPublishLatencyHistogram = undefined;\n cachedConsumeLatencyHistogram = undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/connection-manager.ts","../src/errors.ts","../src/setup.ts","../src/amqp-client.ts","../src/telemetry.ts"],"sourcesContent":["import amqp, {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ConnectionUrl,\n} from \"amqp-connection-manager\";\n\n/**\n * Connection manager singleton for sharing AMQP connections across clients.\n *\n * This singleton implements connection pooling to avoid creating multiple connections\n * to the same broker, which is a RabbitMQ best practice. Connections are identified\n * by their URLs and connection options, and reference counting ensures connections\n * are only closed when all clients have released them.\n *\n * @example\n * ```typescript\n * const manager = ConnectionManagerSingleton.getInstance();\n * const connection = manager.getConnection(['amqp://localhost']);\n * // ... use connection ...\n * await manager.releaseConnection(['amqp://localhost']);\n * ```\n */\nexport class ConnectionManagerSingleton {\n private static instance: ConnectionManagerSingleton;\n private connections: Map<string, AmqpConnectionManager> = new Map();\n private refCounts: Map<string, number> = new Map();\n\n private constructor() {}\n\n /**\n * Get the singleton instance of the connection manager.\n *\n * @returns The singleton instance\n */\n static getInstance(): ConnectionManagerSingleton {\n if (!ConnectionManagerSingleton.instance) {\n ConnectionManagerSingleton.instance = new ConnectionManagerSingleton();\n }\n return ConnectionManagerSingleton.instance;\n }\n\n /**\n * Get or create a connection for the given URLs and options.\n *\n * If a connection already exists with the same URLs and options, it is reused\n * and its reference count is incremented. Otherwise, a new connection is created.\n *\n * @param urls - AMQP broker URL(s)\n * @param connectionOptions - Optional connection configuration\n * @returns The AMQP connection manager instance\n */\n getConnection(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): AmqpConnectionManager {\n // Create a key based on URLs and connection options\n const key = this.createConnectionKey(urls, connectionOptions);\n\n if (!this.connections.has(key)) {\n const connection = amqp.connect(urls, connectionOptions);\n this.connections.set(key, connection);\n this.refCounts.set(key, 0);\n }\n\n // Increment reference count\n this.refCounts.set(key, (this.refCounts.get(key) ?? 0) + 1);\n\n return this.connections.get(key)!;\n }\n\n /**\n * Release a connection reference.\n *\n * Decrements the reference count for the connection. If the count reaches zero,\n * the connection is closed and removed from the pool.\n *\n * @param urls - AMQP broker URL(s) used to identify the connection\n * @param connectionOptions - Optional connection configuration used to identify the connection\n * @returns A promise that resolves when the connection is released (and closed if necessary)\n */\n async releaseConnection(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): Promise<void> {\n const key = this.createConnectionKey(urls, connectionOptions);\n const refCount = this.refCounts.get(key) ?? 0;\n\n if (refCount <= 1) {\n // Last reference - close and remove connection\n const connection = this.connections.get(key);\n if (connection) {\n await connection.close();\n this.connections.delete(key);\n this.refCounts.delete(key);\n }\n } else {\n // Decrement reference count\n this.refCounts.set(key, refCount - 1);\n }\n }\n\n /**\n * Create a unique key for a connection based on URLs and options.\n *\n * The key is deterministic: same URLs and options always produce the same key,\n * enabling connection reuse.\n *\n * @param urls - AMQP broker URL(s)\n * @param connectionOptions - Optional connection configuration\n * @returns A unique string key identifying the connection\n */\n private createConnectionKey(\n urls: ConnectionUrl[],\n connectionOptions?: AmqpConnectionManagerOptions,\n ): string {\n // Create a deterministic key from URLs and options\n // Use JSON.stringify for URLs to avoid ambiguity (e.g., ['a,b'] vs ['a', 'b'])\n const urlsStr = JSON.stringify(urls);\n // Sort object keys for deterministic serialization of connection options\n const optsStr = connectionOptions ? this.serializeOptions(connectionOptions) : \"\";\n return `${urlsStr}::${optsStr}`;\n }\n\n /**\n * Serialize connection options to a deterministic string.\n *\n * @param options - Connection options to serialize\n * @returns A JSON string with sorted keys for deterministic comparison\n */\n private serializeOptions(options: AmqpConnectionManagerOptions): string {\n // Create a deterministic string representation by deeply sorting all object keys\n const sorted = this.deepSort(options);\n return JSON.stringify(sorted);\n }\n\n /**\n * Deep sort an object's keys for deterministic serialization.\n *\n * @param value - The value to deep sort (can be object, array, or primitive)\n * @returns The value with all object keys sorted alphabetically\n */\n private deepSort(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => this.deepSort(item));\n }\n\n if (value !== null && typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n const sortedKeys = Object.keys(obj).sort();\n const result: Record<string, unknown> = {};\n\n for (const key of sortedKeys) {\n result[key] = this.deepSort(obj[key]);\n }\n\n return result;\n }\n\n return value;\n }\n\n /**\n * Reset all cached connections (for testing purposes)\n * @internal\n */\n async _resetForTesting(): Promise<void> {\n // Close all connections before clearing\n const closePromises = Array.from(this.connections.values()).map((conn) => conn.close());\n await Promise.all(closePromises);\n this.connections.clear();\n this.refCounts.clear();\n }\n}\n","/**\n * Error for technical/runtime failures that cannot be prevented by TypeScript.\n *\n * This includes AMQP connection failures, channel issues, validation failures,\n * and other runtime errors. This error is shared across core, worker, and client packages.\n */\nexport class TechnicalError extends Error {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"TechnicalError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n","import type { Channel } from \"amqplib\";\nimport type { ContractDefinition } from \"@amqp-contract/contract\";\nimport { extractQueue } from \"@amqp-contract/contract\";\n\n/**\n * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.\n *\n * This function sets up the complete AMQP topology in the correct order:\n * 1. Assert all exchanges defined in the contract\n * 2. Validate dead letter exchanges are declared before referencing them\n * 3. Assert all queues with their configurations (including dead letter settings)\n * 4. Create all bindings (queue-to-exchange and exchange-to-exchange)\n *\n * @param channel - The AMQP channel to use for topology setup\n * @param contract - The contract definition containing the topology specification\n * @throws {AggregateError} If any exchanges, queues, or bindings fail to be created\n * @throws {Error} If a queue references a dead letter exchange not declared in the contract\n *\n * @example\n * ```typescript\n * const channel = await connection.createChannel();\n * await setupAmqpTopology(channel, contract);\n * ```\n */\nexport async function setupAmqpTopology(\n channel: Channel,\n contract: ContractDefinition,\n): Promise<void> {\n // Setup exchanges\n const exchangeResults = await Promise.allSettled(\n Object.values(contract.exchanges ?? {}).map((exchange) =>\n channel.assertExchange(exchange.name, exchange.type, {\n durable: exchange.durable,\n autoDelete: exchange.autoDelete,\n internal: exchange.internal,\n arguments: exchange.arguments,\n }),\n ),\n );\n const exchangeErrors = exchangeResults.filter(\n (result): result is PromiseRejectedResult => result.status === \"rejected\",\n );\n if (exchangeErrors.length > 0) {\n throw new AggregateError(\n exchangeErrors.map(({ reason }) => reason),\n \"Failed to setup exchanges\",\n );\n }\n\n // Validate dead letter exchanges before setting up queues\n for (const queueEntry of Object.values(contract.queues ?? {})) {\n const queue = extractQueue(queueEntry);\n if (queue.deadLetter) {\n const dlxName = queue.deadLetter.exchange.name;\n const exchangeExists = Object.values(contract.exchanges ?? {}).some(\n (exchange) => exchange.name === dlxName,\n );\n\n if (!exchangeExists) {\n throw new Error(\n `Queue \"${queue.name}\" references dead letter exchange \"${dlxName}\" which is not declared in the contract. ` +\n `Add the exchange to contract.exchanges to ensure it is created before the queue.`,\n );\n }\n }\n }\n\n // Setup queues\n const queueResults = await Promise.allSettled(\n Object.values(contract.queues ?? {}).map((queueEntry) => {\n const queue = extractQueue(queueEntry);\n // Build queue arguments, merging dead letter configuration and queue type\n const queueArguments: Record<string, unknown> = { ...queue.arguments };\n\n // Set queue type\n queueArguments[\"x-queue-type\"] = queue.type;\n\n if (queue.deadLetter) {\n queueArguments[\"x-dead-letter-exchange\"] = queue.deadLetter.exchange.name;\n if (queue.deadLetter.routingKey) {\n queueArguments[\"x-dead-letter-routing-key\"] = queue.deadLetter.routingKey;\n }\n }\n\n // Handle type-specific properties using discriminated union\n if (queue.type === \"quorum\") {\n // Set delivery limit for quorum queues (native retry support)\n if (queue.deliveryLimit !== undefined) {\n queueArguments[\"x-delivery-limit\"] = queue.deliveryLimit;\n }\n\n // Quorum queues are always durable\n return channel.assertQueue(queue.name, {\n durable: true,\n autoDelete: queue.autoDelete,\n arguments: queueArguments,\n });\n }\n\n // Classic queue\n return channel.assertQueue(queue.name, {\n durable: queue.durable,\n exclusive: queue.exclusive,\n autoDelete: queue.autoDelete,\n arguments: queueArguments,\n });\n }),\n );\n const queueErrors = queueResults.filter(\n (result): result is PromiseRejectedResult => result.status === \"rejected\",\n );\n if (queueErrors.length > 0) {\n throw new AggregateError(\n queueErrors.map(({ reason }) => reason),\n \"Failed to setup queues\",\n );\n }\n\n // Setup bindings\n const bindingResults = await Promise.allSettled(\n Object.values(contract.bindings ?? {}).map((binding) => {\n if (binding.type === \"queue\") {\n return channel.bindQueue(\n binding.queue.name,\n binding.exchange.name,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }\n\n return channel.bindExchange(\n binding.destination.name,\n binding.source.name,\n binding.routingKey ?? \"\",\n binding.arguments,\n );\n }),\n );\n const bindingErrors = bindingResults.filter(\n (result): result is PromiseRejectedResult => result.status === \"rejected\",\n );\n if (bindingErrors.length > 0) {\n throw new AggregateError(\n bindingErrors.map(({ reason }) => reason),\n \"Failed to setup bindings\",\n );\n }\n}\n","import type {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ChannelWrapper,\n ConnectionUrl,\n CreateChannelOpts,\n} from \"amqp-connection-manager\";\nimport type { Channel, ConsumeMessage, Options } from \"amqplib\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { ConnectionManagerSingleton } from \"./connection-manager.js\";\nimport type { ContractDefinition } from \"@amqp-contract/contract\";\nimport { TechnicalError } from \"./errors.js\";\nimport { setupAmqpTopology } from \"./setup.js\";\n\n/**\n * Options for creating an AMQP client.\n *\n * @property urls - AMQP broker URL(s). Multiple URLs provide failover support.\n * @property connectionOptions - Optional connection configuration (heartbeat, reconnect settings, etc.).\n * @property channelOptions - Optional channel configuration options.\n */\nexport type AmqpClientOptions = {\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n channelOptions?: Partial<CreateChannelOpts> | undefined;\n};\n\n/**\n * Callback type for consuming messages.\n */\nexport type ConsumeCallback = (msg: ConsumeMessage | null) => void | Promise<void>;\n\n/**\n * AMQP client that manages connections and channels with automatic topology setup.\n *\n * This class handles:\n * - Connection management with automatic reconnection via amqp-connection-manager\n * - Connection pooling and sharing across instances with the same URLs\n * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract\n * - Channel creation with JSON serialization enabled by default\n *\n * All operations return `Future<Result<T, TechnicalError>>` for consistent error handling.\n *\n * @example\n * ```typescript\n * const client = new AmqpClient(contract, {\n * urls: ['amqp://localhost'],\n * connectionOptions: { heartbeatIntervalInSeconds: 30 }\n * });\n *\n * // Wait for connection\n * await client.waitForConnect().resultToPromise();\n *\n * // Publish a message\n * const result = await client.publish('exchange', 'routingKey', { data: 'value' }).resultToPromise();\n *\n * // Close when done\n * await client.close().resultToPromise();\n * ```\n */\nexport class AmqpClient {\n private readonly connection: AmqpConnectionManager;\n private readonly channelWrapper: ChannelWrapper;\n private readonly urls: ConnectionUrl[];\n private readonly connectionOptions?: AmqpConnectionManagerOptions;\n\n /**\n * Create a new AMQP client instance.\n *\n * The client will automatically:\n * - Get or create a shared connection using the singleton pattern\n * - Set up AMQP topology (exchanges, queues, bindings) from the contract\n * - Create a channel with JSON serialization enabled\n *\n * @param contract - The contract definition specifying the AMQP topology\n * @param options - Client configuration options\n */\n constructor(\n private readonly contract: ContractDefinition,\n options: AmqpClientOptions,\n ) {\n // Store for cleanup\n this.urls = options.urls;\n if (options.connectionOptions !== undefined) {\n this.connectionOptions = options.connectionOptions;\n }\n\n // Always use singleton to get/create connection\n const singleton = ConnectionManagerSingleton.getInstance();\n this.connection = singleton.getConnection(options.urls, options.connectionOptions);\n\n // Create default setup function that calls setupAmqpTopology\n const defaultSetup = (channel: Channel) => setupAmqpTopology(channel, this.contract);\n\n // Destructure setup from channelOptions to handle it separately\n const { setup: userSetup, ...otherChannelOptions } = options.channelOptions ?? {};\n\n // Merge user-provided channel options with defaults\n const channelOpts: CreateChannelOpts = {\n json: true,\n setup: defaultSetup,\n ...otherChannelOptions,\n };\n\n // If user provided a custom setup, wrap it to call both\n if (userSetup) {\n channelOpts.setup = async (channel: Channel) => {\n // First run the topology setup\n await defaultSetup(channel);\n // Then run user's setup - check arity to determine if it expects a callback\n if (userSetup.length === 2) {\n // Callback-based setup function\n await new Promise<void>((resolve, reject) => {\n (userSetup as (channel: Channel, callback: (error?: Error) => void) => void)(\n channel,\n (error?: Error) => {\n if (error) reject(error);\n else resolve();\n },\n );\n });\n } else {\n // Promise-based setup function\n await (userSetup as (channel: Channel) => Promise<void>)(channel);\n }\n };\n }\n\n this.channelWrapper = this.connection.createChannel(channelOpts);\n }\n\n /**\n * Get the underlying connection manager\n *\n * This method exposes the AmqpConnectionManager instance that this client uses.\n * The connection is automatically shared across all AmqpClient instances that\n * use the same URLs and connection options.\n *\n * @returns The AmqpConnectionManager instance used by this client\n */\n getConnection(): AmqpConnectionManager {\n return this.connection;\n }\n\n /**\n * Wait for the channel to be connected and ready.\n *\n * @returns A Future that resolves when the channel is connected\n */\n waitForConnect(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.channelWrapper.waitForConnect()).mapError(\n (error: unknown) => new TechnicalError(\"Failed to connect to AMQP broker\", error),\n );\n }\n\n /**\n * Publish a message to an exchange.\n *\n * @param exchange - The exchange name\n * @param routingKey - The routing key\n * @param content - The message content (will be JSON serialized if json: true)\n * @param options - Optional publish options\n * @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full\n */\n publish(\n exchange: string,\n routingKey: string,\n content: Buffer | unknown,\n options?: Options.Publish,\n ): Future<Result<boolean, TechnicalError>> {\n return Future.fromPromise(\n this.channelWrapper.publish(exchange, routingKey, content, options),\n ).mapError((error: unknown) => new TechnicalError(\"Failed to publish message\", error));\n }\n\n /**\n * Start consuming messages from a queue.\n *\n * @param queue - The queue name\n * @param callback - The callback to invoke for each message\n * @param options - Optional consume options\n * @returns A Future with `Result<string>` - the consumer tag\n */\n consume(\n queue: string,\n callback: ConsumeCallback,\n options?: Options.Consume,\n ): Future<Result<string, TechnicalError>> {\n return Future.fromPromise(this.channelWrapper.consume(queue, callback, options))\n .mapError((error: unknown) => new TechnicalError(\"Failed to start consuming messages\", error))\n .mapOk((reply: { consumerTag: string }) => reply.consumerTag);\n }\n\n /**\n * Cancel a consumer by its consumer tag.\n *\n * @param consumerTag - The consumer tag to cancel\n * @returns A Future that resolves when the consumer is cancelled\n */\n cancel(consumerTag: string): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.channelWrapper.cancel(consumerTag))\n .mapError((error: unknown) => new TechnicalError(\"Failed to cancel consumer\", error))\n .mapOk(() => undefined);\n }\n\n /**\n * Acknowledge a message.\n *\n * @param msg - The message to acknowledge\n * @param allUpTo - If true, acknowledge all messages up to and including this one\n */\n ack(msg: ConsumeMessage, allUpTo = false): void {\n this.channelWrapper.ack(msg, allUpTo);\n }\n\n /**\n * Negative acknowledge a message.\n *\n * @param msg - The message to nack\n * @param allUpTo - If true, nack all messages up to and including this one\n * @param requeue - If true, requeue the message(s)\n */\n nack(msg: ConsumeMessage, allUpTo = false, requeue = true): void {\n this.channelWrapper.nack(msg, allUpTo, requeue);\n }\n\n /**\n * Add a setup function to be called when the channel is created or reconnected.\n *\n * This is useful for setting up channel-level configuration like prefetch.\n *\n * @param setup - The setup function to add\n */\n addSetup(setup: (channel: Channel) => void | Promise<void>): void {\n this.channelWrapper.addSetup(setup);\n }\n\n /**\n * Register an event listener on the channel wrapper.\n *\n * Available events:\n * - 'connect': Emitted when the channel is (re)connected\n * - 'close': Emitted when the channel is closed\n * - 'error': Emitted when an error occurs\n *\n * @param event - The event name\n * @param listener - The event listener\n */\n on(event: string, listener: (...args: unknown[]) => void): void {\n this.channelWrapper.on(event, listener);\n }\n\n /**\n * Close the channel and release the connection reference.\n *\n * This will:\n * - Close the channel wrapper\n * - Decrease the reference count on the shared connection\n * - Close the connection if this was the last client using it\n *\n * @returns A Future that resolves when the channel and connection are closed\n */\n close(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.channelWrapper.close())\n .mapError((error: unknown) => new TechnicalError(\"Failed to close channel\", error))\n .flatMapOk(() =>\n Future.fromPromise(\n ConnectionManagerSingleton.getInstance().releaseConnection(\n this.urls,\n this.connectionOptions,\n ),\n ).mapError((error: unknown) => new TechnicalError(\"Failed to release connection\", error)),\n )\n .mapOk(() => undefined);\n }\n\n /**\n * Reset connection singleton cache (for testing only)\n * @internal\n */\n static async _resetConnectionCacheForTesting(): Promise<void> {\n await ConnectionManagerSingleton.getInstance()._resetForTesting();\n }\n}\n","import {\n type Attributes,\n type Counter,\n type Histogram,\n type Span,\n type Tracer,\n} from \"@opentelemetry/api\";\n\n/**\n * SpanKind values from OpenTelemetry.\n * Defined as constants to avoid runtime dependency when types are used.\n * @see https://opentelemetry.io/docs/specs/otel/trace/api/#spankind\n */\nconst SpanKind = {\n /** Producer span represents a message producer */\n PRODUCER: 3,\n /** Consumer span represents a message consumer */\n CONSUMER: 4,\n} as const;\n\n/**\n * Semantic conventions for AMQP messaging following OpenTelemetry standards.\n * @see https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/\n */\nexport const MessagingSemanticConventions = {\n // Messaging attributes\n MESSAGING_SYSTEM: \"messaging.system\",\n MESSAGING_DESTINATION: \"messaging.destination.name\",\n MESSAGING_DESTINATION_KIND: \"messaging.destination.kind\",\n MESSAGING_OPERATION: \"messaging.operation\",\n MESSAGING_MESSAGE_ID: \"messaging.message.id\",\n MESSAGING_MESSAGE_PAYLOAD_SIZE: \"messaging.message.body.size\",\n MESSAGING_MESSAGE_CONVERSATION_ID: \"messaging.message.conversation_id\",\n\n // AMQP specific attributes\n MESSAGING_RABBITMQ_ROUTING_KEY: \"messaging.rabbitmq.destination.routing_key\",\n MESSAGING_RABBITMQ_MESSAGE_DELIVERY_TAG: \"messaging.rabbitmq.message.delivery_tag\",\n\n // Error attributes\n ERROR_TYPE: \"error.type\",\n\n // Values\n MESSAGING_SYSTEM_RABBITMQ: \"rabbitmq\",\n MESSAGING_DESTINATION_KIND_EXCHANGE: \"exchange\",\n MESSAGING_DESTINATION_KIND_QUEUE: \"queue\",\n MESSAGING_OPERATION_PUBLISH: \"publish\",\n MESSAGING_OPERATION_RECEIVE: \"receive\",\n MESSAGING_OPERATION_PROCESS: \"process\",\n} as const;\n\n/**\n * Telemetry provider for AMQP operations.\n * Uses lazy loading to gracefully handle cases where OpenTelemetry is not installed.\n */\nexport type TelemetryProvider = {\n /**\n * Get a tracer instance for creating spans.\n * Returns undefined if OpenTelemetry is not available.\n */\n getTracer: () => Tracer | undefined;\n\n /**\n * Get a counter for messages published.\n * Returns undefined if OpenTelemetry is not available.\n */\n getPublishCounter: () => Counter | undefined;\n\n /**\n * Get a counter for messages consumed.\n * Returns undefined if OpenTelemetry is not available.\n */\n getConsumeCounter: () => Counter | undefined;\n\n /**\n * Get a histogram for publish latency.\n * Returns undefined if OpenTelemetry is not available.\n */\n getPublishLatencyHistogram: () => Histogram | undefined;\n\n /**\n * Get a histogram for consume/process latency.\n * Returns undefined if OpenTelemetry is not available.\n */\n getConsumeLatencyHistogram: () => Histogram | undefined;\n};\n\n/**\n * Instrumentation scope name for amqp-contract.\n */\nconst INSTRUMENTATION_SCOPE_NAME = \"@amqp-contract\";\nconst INSTRUMENTATION_SCOPE_VERSION = \"0.1.0\";\n\n// Cache for OpenTelemetry API module and instruments\nlet otelApi: typeof import(\"@opentelemetry/api\") | null | undefined;\nlet cachedTracer: Tracer | undefined;\nlet cachedPublishCounter: Counter | undefined;\nlet cachedConsumeCounter: Counter | undefined;\nlet cachedPublishLatencyHistogram: Histogram | undefined;\nlet cachedConsumeLatencyHistogram: Histogram | undefined;\n\n/**\n * Try to load the OpenTelemetry API module.\n * Returns null if the module is not available.\n */\nfunction tryLoadOpenTelemetryApi(): typeof import(\"@opentelemetry/api\") | null {\n if (otelApi === undefined) {\n try {\n // Dynamic import using require to avoid bundler issues\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n otelApi = require(\"@opentelemetry/api\") as typeof import(\"@opentelemetry/api\");\n } catch {\n otelApi = null;\n }\n }\n return otelApi;\n}\n\n/**\n * Get or create a tracer instance.\n */\nfunction getTracer(): Tracer | undefined {\n if (cachedTracer !== undefined) {\n return cachedTracer;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (!api) {\n return undefined;\n }\n\n cachedTracer = api.trace.getTracer(INSTRUMENTATION_SCOPE_NAME, INSTRUMENTATION_SCOPE_VERSION);\n return cachedTracer;\n}\n\n/**\n * Get or create a meter and its instruments.\n */\nfunction getMeterInstruments(): {\n publishCounter: Counter | undefined;\n consumeCounter: Counter | undefined;\n publishLatencyHistogram: Histogram | undefined;\n consumeLatencyHistogram: Histogram | undefined;\n} {\n if (cachedPublishCounter !== undefined) {\n return {\n publishCounter: cachedPublishCounter,\n consumeCounter: cachedConsumeCounter,\n publishLatencyHistogram: cachedPublishLatencyHistogram,\n consumeLatencyHistogram: cachedConsumeLatencyHistogram,\n };\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (!api) {\n return {\n publishCounter: undefined,\n consumeCounter: undefined,\n publishLatencyHistogram: undefined,\n consumeLatencyHistogram: undefined,\n };\n }\n\n const meter = api.metrics.getMeter(INSTRUMENTATION_SCOPE_NAME, INSTRUMENTATION_SCOPE_VERSION);\n\n cachedPublishCounter = meter.createCounter(\"amqp.client.messages.published\", {\n description: \"Number of messages published to AMQP broker\",\n unit: \"{message}\",\n });\n\n cachedConsumeCounter = meter.createCounter(\"amqp.worker.messages.consumed\", {\n description: \"Number of messages consumed from AMQP broker\",\n unit: \"{message}\",\n });\n\n cachedPublishLatencyHistogram = meter.createHistogram(\"amqp.client.publish.duration\", {\n description: \"Duration of message publish operations\",\n unit: \"ms\",\n });\n\n cachedConsumeLatencyHistogram = meter.createHistogram(\"amqp.worker.process.duration\", {\n description: \"Duration of message processing operations\",\n unit: \"ms\",\n });\n\n return {\n publishCounter: cachedPublishCounter,\n consumeCounter: cachedConsumeCounter,\n publishLatencyHistogram: cachedPublishLatencyHistogram,\n consumeLatencyHistogram: cachedConsumeLatencyHistogram,\n };\n}\n\n/**\n * Default telemetry provider that uses OpenTelemetry API if available.\n */\nexport const defaultTelemetryProvider: TelemetryProvider = {\n getTracer,\n getPublishCounter: () => getMeterInstruments().publishCounter,\n getConsumeCounter: () => getMeterInstruments().consumeCounter,\n getPublishLatencyHistogram: () => getMeterInstruments().publishLatencyHistogram,\n getConsumeLatencyHistogram: () => getMeterInstruments().consumeLatencyHistogram,\n};\n\n/**\n * Create a span for a publish operation.\n * Returns undefined if OpenTelemetry is not available.\n */\nexport function startPublishSpan(\n provider: TelemetryProvider,\n exchangeName: string,\n routingKey: string | undefined,\n attributes?: Attributes,\n): Span | undefined {\n const tracer = provider.getTracer();\n if (!tracer) {\n return undefined;\n }\n\n const spanName = `${exchangeName} publish`;\n\n return tracer.startSpan(spanName, {\n kind: SpanKind.PRODUCER,\n attributes: {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: exchangeName,\n [MessagingSemanticConventions.MESSAGING_DESTINATION_KIND]:\n MessagingSemanticConventions.MESSAGING_DESTINATION_KIND_EXCHANGE,\n [MessagingSemanticConventions.MESSAGING_OPERATION]:\n MessagingSemanticConventions.MESSAGING_OPERATION_PUBLISH,\n ...(routingKey\n ? { [MessagingSemanticConventions.MESSAGING_RABBITMQ_ROUTING_KEY]: routingKey }\n : {}),\n ...attributes,\n },\n });\n}\n\n/**\n * Create a span for a consume/process operation.\n * Returns undefined if OpenTelemetry is not available.\n */\nexport function startConsumeSpan(\n provider: TelemetryProvider,\n queueName: string,\n consumerName: string,\n attributes?: Attributes,\n): Span | undefined {\n const tracer = provider.getTracer();\n if (!tracer) {\n return undefined;\n }\n\n const spanName = `${queueName} process`;\n\n return tracer.startSpan(spanName, {\n kind: SpanKind.CONSUMER,\n attributes: {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: queueName,\n [MessagingSemanticConventions.MESSAGING_DESTINATION_KIND]:\n MessagingSemanticConventions.MESSAGING_DESTINATION_KIND_QUEUE,\n [MessagingSemanticConventions.MESSAGING_OPERATION]:\n MessagingSemanticConventions.MESSAGING_OPERATION_PROCESS,\n \"amqp.consumer.name\": consumerName,\n ...attributes,\n },\n });\n}\n\n/**\n * End a span with success status.\n */\nexport function endSpanSuccess(span: Span | undefined): void {\n if (!span) {\n return;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (api) {\n span.setStatus({ code: api.SpanStatusCode.OK });\n }\n span.end();\n}\n\n/**\n * End a span with error status.\n */\nexport function endSpanError(span: Span | undefined, error: Error): void {\n if (!span) {\n return;\n }\n\n const api = tryLoadOpenTelemetryApi();\n if (api) {\n span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });\n span.recordException(error);\n span.setAttribute(MessagingSemanticConventions.ERROR_TYPE, error.name);\n }\n span.end();\n}\n\n/**\n * Record a publish metric.\n */\nexport function recordPublishMetric(\n provider: TelemetryProvider,\n exchangeName: string,\n routingKey: string | undefined,\n success: boolean,\n durationMs: number,\n): void {\n const publishCounter = provider.getPublishCounter();\n const publishLatencyHistogram = provider.getPublishLatencyHistogram();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: exchangeName,\n ...(routingKey\n ? { [MessagingSemanticConventions.MESSAGING_RABBITMQ_ROUTING_KEY]: routingKey }\n : {}),\n success: success,\n };\n\n publishCounter?.add(1, attributes);\n publishLatencyHistogram?.record(durationMs, attributes);\n}\n\n/**\n * Record a consume metric.\n */\nexport function recordConsumeMetric(\n provider: TelemetryProvider,\n queueName: string,\n consumerName: string,\n success: boolean,\n durationMs: number,\n): void {\n const consumeCounter = provider.getConsumeCounter();\n const consumeLatencyHistogram = provider.getConsumeLatencyHistogram();\n\n const attributes: Attributes = {\n [MessagingSemanticConventions.MESSAGING_SYSTEM]:\n MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,\n [MessagingSemanticConventions.MESSAGING_DESTINATION]: queueName,\n \"amqp.consumer.name\": consumerName,\n success: success,\n };\n\n consumeCounter?.add(1, attributes);\n consumeLatencyHistogram?.record(durationMs, attributes);\n}\n\n/**\n * Reset the cached OpenTelemetry API module and instruments.\n * For testing purposes only.\n * @internal\n */\nexport function _resetTelemetryCacheForTesting(): void {\n otelApi = undefined;\n cachedTracer = undefined;\n cachedPublishCounter = undefined;\n cachedConsumeCounter = undefined;\n cachedPublishLatencyHistogram = undefined;\n cachedConsumeLatencyHistogram = undefined;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amqp-contract/core",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Core utilities for AMQP setup and management in amqp-contract",
5
5
  "keywords": [
6
6
  "amqp",
@@ -49,9 +49,10 @@
49
49
  "dist"
50
50
  ],
51
51
  "dependencies": {
52
+ "@swan-io/boxed": "3.2.1",
52
53
  "amqp-connection-manager": "5.0.0",
53
54
  "amqplib": "0.10.9",
54
- "@amqp-contract/contract": "0.11.0"
55
+ "@amqp-contract/contract": "0.13.0"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@opentelemetry/api": "1.9.0",
@@ -62,7 +63,7 @@
62
63
  "typescript": "5.9.3",
63
64
  "vitest": "4.0.17",
64
65
  "zod": "4.3.5",
65
- "@amqp-contract/testing": "0.11.0",
66
+ "@amqp-contract/testing": "0.13.0",
66
67
  "@amqp-contract/tsconfig": "0.1.0",
67
68
  "@amqp-contract/typedoc": "0.1.0"
68
69
  },