@amqp-contract/core 0.7.0 → 0.8.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
@@ -30,19 +30,44 @@ amqp_connection_manager = __toESM(amqp_connection_manager);
30
30
 
31
31
  //#region src/connection-manager.ts
32
32
  /**
33
- * Connection manager singleton for sharing connections across clients
33
+ * Connection manager singleton for sharing AMQP connections across clients.
34
+ *
35
+ * This singleton implements connection pooling to avoid creating multiple connections
36
+ * to the same broker, which is a RabbitMQ best practice. Connections are identified
37
+ * by their URLs and connection options, and reference counting ensures connections
38
+ * are only closed when all clients have released them.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const manager = ConnectionManagerSingleton.getInstance();
43
+ * const connection = manager.getConnection(['amqp://localhost']);
44
+ * // ... use connection ...
45
+ * await manager.releaseConnection(['amqp://localhost']);
46
+ * ```
34
47
  */
35
48
  var ConnectionManagerSingleton = class ConnectionManagerSingleton {
36
49
  static instance;
37
50
  connections = /* @__PURE__ */ new Map();
38
51
  refCounts = /* @__PURE__ */ new Map();
39
52
  constructor() {}
53
+ /**
54
+ * Get the singleton instance of the connection manager.
55
+ *
56
+ * @returns The singleton instance
57
+ */
40
58
  static getInstance() {
41
59
  if (!ConnectionManagerSingleton.instance) ConnectionManagerSingleton.instance = new ConnectionManagerSingleton();
42
60
  return ConnectionManagerSingleton.instance;
43
61
  }
44
62
  /**
45
- * Get or create a connection for the given URLs and options
63
+ * Get or create a connection for the given URLs and options.
64
+ *
65
+ * If a connection already exists with the same URLs and options, it is reused
66
+ * and its reference count is incremented. Otherwise, a new connection is created.
67
+ *
68
+ * @param urls - AMQP broker URL(s)
69
+ * @param connectionOptions - Optional connection configuration
70
+ * @returns The AMQP connection manager instance
46
71
  */
47
72
  getConnection(urls, connectionOptions) {
48
73
  const key = this.createConnectionKey(urls, connectionOptions);
@@ -55,7 +80,14 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
55
80
  return this.connections.get(key);
56
81
  }
57
82
  /**
58
- * Release a connection reference. If no more references exist, close the connection.
83
+ * Release a connection reference.
84
+ *
85
+ * Decrements the reference count for the connection. If the count reaches zero,
86
+ * the connection is closed and removed from the pool.
87
+ *
88
+ * @param urls - AMQP broker URL(s) used to identify the connection
89
+ * @param connectionOptions - Optional connection configuration used to identify the connection
90
+ * @returns A promise that resolves when the connection is released (and closed if necessary)
59
91
  */
60
92
  async releaseConnection(urls, connectionOptions) {
61
93
  const key = this.createConnectionKey(urls, connectionOptions);
@@ -69,13 +101,35 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
69
101
  }
70
102
  } else this.refCounts.set(key, refCount - 1);
71
103
  }
104
+ /**
105
+ * Create a unique key for a connection based on URLs and options.
106
+ *
107
+ * The key is deterministic: same URLs and options always produce the same key,
108
+ * enabling connection reuse.
109
+ *
110
+ * @param urls - AMQP broker URL(s)
111
+ * @param connectionOptions - Optional connection configuration
112
+ * @returns A unique string key identifying the connection
113
+ */
72
114
  createConnectionKey(urls, connectionOptions) {
73
115
  return `${JSON.stringify(urls)}::${connectionOptions ? this.serializeOptions(connectionOptions) : ""}`;
74
116
  }
117
+ /**
118
+ * Serialize connection options to a deterministic string.
119
+ *
120
+ * @param options - Connection options to serialize
121
+ * @returns A JSON string with sorted keys for deterministic comparison
122
+ */
75
123
  serializeOptions(options) {
76
124
  const sorted = this.deepSort(options);
77
125
  return JSON.stringify(sorted);
78
126
  }
127
+ /**
128
+ * Deep sort an object's keys for deterministic serialization.
129
+ *
130
+ * @param value - The value to deep sort (can be object, array, or primitive)
131
+ * @returns The value with all object keys sorted alphabetically
132
+ */
79
133
  deepSort(value) {
80
134
  if (Array.isArray(value)) return value.map((item) => this.deepSort(item));
81
135
  if (value !== null && typeof value === "object") {
@@ -102,7 +156,24 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
102
156
  //#endregion
103
157
  //#region src/setup.ts
104
158
  /**
105
- * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition
159
+ * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.
160
+ *
161
+ * This function sets up the complete AMQP topology in the correct order:
162
+ * 1. Assert all exchanges defined in the contract
163
+ * 2. Validate dead letter exchanges are declared before referencing them
164
+ * 3. Assert all queues with their configurations (including dead letter settings)
165
+ * 4. Create all bindings (queue-to-exchange and exchange-to-exchange)
166
+ *
167
+ * @param channel - The AMQP channel to use for topology setup
168
+ * @param contract - The contract definition containing the topology specification
169
+ * @throws {AggregateError} If any exchanges, queues, or bindings fail to be created
170
+ * @throws {Error} If a queue references a dead letter exchange not declared in the contract
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * const channel = await connection.createChannel();
175
+ * await setupAmqpTopology(channel, contract);
176
+ * ```
106
177
  */
107
178
  async function setupAmqpTopology(channel, contract) {
108
179
  const exchangeErrors = (await Promise.allSettled(Object.values(contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
@@ -139,11 +210,45 @@ async function setupAmqpTopology(channel, contract) {
139
210
 
140
211
  //#endregion
141
212
  //#region src/amqp-client.ts
213
+ /**
214
+ * AMQP client that manages connections and channels with automatic topology setup.
215
+ *
216
+ * This class handles:
217
+ * - Connection management with automatic reconnection via amqp-connection-manager
218
+ * - Connection pooling and sharing across instances with the same URLs
219
+ * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
220
+ * - Channel creation with JSON serialization enabled by default
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * const client = new AmqpClient(contract, {
225
+ * urls: ['amqp://localhost'],
226
+ * connectionOptions: { heartbeatIntervalInSeconds: 30 }
227
+ * });
228
+ *
229
+ * // Use the channel to publish messages
230
+ * await client.channel.publish('exchange', 'routingKey', { data: 'value' });
231
+ *
232
+ * // Close when done
233
+ * await client.close();
234
+ * ```
235
+ */
142
236
  var AmqpClient = class {
143
237
  connection;
144
238
  channel;
145
239
  urls;
146
240
  connectionOptions;
241
+ /**
242
+ * Create a new AMQP client instance.
243
+ *
244
+ * The client will automatically:
245
+ * - Get or create a shared connection using the singleton pattern
246
+ * - Set up AMQP topology (exchanges, queues, bindings) from the contract
247
+ * - Create a channel with JSON serialization enabled
248
+ *
249
+ * @param contract - The contract definition specifying the AMQP topology
250
+ * @param options - Client configuration options
251
+ */
147
252
  constructor(contract, options) {
148
253
  this.contract = contract;
149
254
  this.urls = options.urls;
@@ -180,6 +285,16 @@ var AmqpClient = class {
180
285
  getConnection() {
181
286
  return this.connection;
182
287
  }
288
+ /**
289
+ * Close the channel and release the connection reference.
290
+ *
291
+ * This will:
292
+ * - Close the channel wrapper
293
+ * - Decrease the reference count on the shared connection
294
+ * - Close the connection if this was the last client using it
295
+ *
296
+ * @returns A promise that resolves when the channel and connection are closed
297
+ */
183
298
  async close() {
184
299
  await this.channel.close();
185
300
  await ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions);
package/dist/index.d.cts CHANGED
@@ -60,17 +60,58 @@ type Logger = {
60
60
  };
61
61
  //#endregion
62
62
  //#region src/amqp-client.d.ts
63
+ /**
64
+ * Options for creating an AMQP client.
65
+ *
66
+ * @property urls - AMQP broker URL(s). Multiple URLs provide failover support.
67
+ * @property connectionOptions - Optional connection configuration (heartbeat, reconnect settings, etc.).
68
+ * @property channelOptions - Optional channel configuration options.
69
+ */
63
70
  type AmqpClientOptions = {
64
71
  urls: ConnectionUrl[];
65
72
  connectionOptions?: AmqpConnectionManagerOptions | undefined;
66
73
  channelOptions?: Partial<CreateChannelOpts> | undefined;
67
74
  };
75
+ /**
76
+ * AMQP client that manages connections and channels with automatic topology setup.
77
+ *
78
+ * This class handles:
79
+ * - Connection management with automatic reconnection via amqp-connection-manager
80
+ * - Connection pooling and sharing across instances with the same URLs
81
+ * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
82
+ * - Channel creation with JSON serialization enabled by default
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const client = new AmqpClient(contract, {
87
+ * urls: ['amqp://localhost'],
88
+ * connectionOptions: { heartbeatIntervalInSeconds: 30 }
89
+ * });
90
+ *
91
+ * // Use the channel to publish messages
92
+ * await client.channel.publish('exchange', 'routingKey', { data: 'value' });
93
+ *
94
+ * // Close when done
95
+ * await client.close();
96
+ * ```
97
+ */
68
98
  declare class AmqpClient {
69
99
  private readonly contract;
70
100
  private readonly connection;
71
101
  readonly channel: ChannelWrapper;
72
102
  private readonly urls;
73
103
  private readonly connectionOptions?;
104
+ /**
105
+ * Create a new AMQP client instance.
106
+ *
107
+ * The client will automatically:
108
+ * - Get or create a shared connection using the singleton pattern
109
+ * - Set up AMQP topology (exchanges, queues, bindings) from the contract
110
+ * - Create a channel with JSON serialization enabled
111
+ *
112
+ * @param contract - The contract definition specifying the AMQP topology
113
+ * @param options - Client configuration options
114
+ */
74
115
  constructor(contract: ContractDefinition, options: AmqpClientOptions);
75
116
  /**
76
117
  * Get the underlying connection manager
@@ -82,6 +123,16 @@ declare class AmqpClient {
82
123
  * @returns The AmqpConnectionManager instance used by this client
83
124
  */
84
125
  getConnection(): AmqpConnectionManager;
126
+ /**
127
+ * Close the channel and release the connection reference.
128
+ *
129
+ * This will:
130
+ * - Close the channel wrapper
131
+ * - Decrease the reference count on the shared connection
132
+ * - Close the connection if this was the last client using it
133
+ *
134
+ * @returns A promise that resolves when the channel and connection are closed
135
+ */
85
136
  close(): Promise<void>;
86
137
  /**
87
138
  * Reset connection singleton cache (for testing only)
@@ -92,24 +143,78 @@ declare class AmqpClient {
92
143
  //#endregion
93
144
  //#region src/connection-manager.d.ts
94
145
  /**
95
- * Connection manager singleton for sharing connections across clients
146
+ * Connection manager singleton for sharing AMQP connections across clients.
147
+ *
148
+ * This singleton implements connection pooling to avoid creating multiple connections
149
+ * to the same broker, which is a RabbitMQ best practice. Connections are identified
150
+ * by their URLs and connection options, and reference counting ensures connections
151
+ * are only closed when all clients have released them.
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const manager = ConnectionManagerSingleton.getInstance();
156
+ * const connection = manager.getConnection(['amqp://localhost']);
157
+ * // ... use connection ...
158
+ * await manager.releaseConnection(['amqp://localhost']);
159
+ * ```
96
160
  */
97
161
  declare class ConnectionManagerSingleton {
98
162
  private static instance;
99
163
  private connections;
100
164
  private refCounts;
101
165
  private constructor();
166
+ /**
167
+ * Get the singleton instance of the connection manager.
168
+ *
169
+ * @returns The singleton instance
170
+ */
102
171
  static getInstance(): ConnectionManagerSingleton;
103
172
  /**
104
- * Get or create a connection for the given URLs and options
173
+ * Get or create a connection for the given URLs and options.
174
+ *
175
+ * If a connection already exists with the same URLs and options, it is reused
176
+ * and its reference count is incremented. Otherwise, a new connection is created.
177
+ *
178
+ * @param urls - AMQP broker URL(s)
179
+ * @param connectionOptions - Optional connection configuration
180
+ * @returns The AMQP connection manager instance
105
181
  */
106
182
  getConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): AmqpConnectionManager;
107
183
  /**
108
- * Release a connection reference. If no more references exist, close the connection.
184
+ * Release a connection reference.
185
+ *
186
+ * Decrements the reference count for the connection. If the count reaches zero,
187
+ * the connection is closed and removed from the pool.
188
+ *
189
+ * @param urls - AMQP broker URL(s) used to identify the connection
190
+ * @param connectionOptions - Optional connection configuration used to identify the connection
191
+ * @returns A promise that resolves when the connection is released (and closed if necessary)
109
192
  */
110
193
  releaseConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): Promise<void>;
194
+ /**
195
+ * Create a unique key for a connection based on URLs and options.
196
+ *
197
+ * The key is deterministic: same URLs and options always produce the same key,
198
+ * enabling connection reuse.
199
+ *
200
+ * @param urls - AMQP broker URL(s)
201
+ * @param connectionOptions - Optional connection configuration
202
+ * @returns A unique string key identifying the connection
203
+ */
111
204
  private createConnectionKey;
205
+ /**
206
+ * Serialize connection options to a deterministic string.
207
+ *
208
+ * @param options - Connection options to serialize
209
+ * @returns A JSON string with sorted keys for deterministic comparison
210
+ */
112
211
  private serializeOptions;
212
+ /**
213
+ * Deep sort an object's keys for deterministic serialization.
214
+ *
215
+ * @param value - The value to deep sort (can be object, array, or primitive)
216
+ * @returns The value with all object keys sorted alphabetically
217
+ */
113
218
  private deepSort;
114
219
  /**
115
220
  * Reset all cached connections (for testing purposes)
@@ -120,7 +225,24 @@ declare class ConnectionManagerSingleton {
120
225
  //#endregion
121
226
  //#region src/setup.d.ts
122
227
  /**
123
- * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition
228
+ * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.
229
+ *
230
+ * This function sets up the complete AMQP topology in the correct order:
231
+ * 1. Assert all exchanges defined in the contract
232
+ * 2. Validate dead letter exchanges are declared before referencing them
233
+ * 3. Assert all queues with their configurations (including dead letter settings)
234
+ * 4. Create all bindings (queue-to-exchange and exchange-to-exchange)
235
+ *
236
+ * @param channel - The AMQP channel to use for topology setup
237
+ * @param contract - The contract definition containing the topology specification
238
+ * @throws {AggregateError} If any exchanges, queues, or bindings fail to be created
239
+ * @throws {Error} If a queue references a dead letter exchange not declared in the contract
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * const channel = await connection.createChannel();
244
+ * await setupAmqpTopology(channel, contract);
245
+ * ```
124
246
  */
125
247
  declare function setupAmqpTopology(channel: Channel, contract: ContractDefinition): Promise<void>;
126
248
  //#endregion
@@ -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"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAQA;AAqBA;;AAakC,KAlCtB,aAAA,GAAgB,MAkCM,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA;EAOA,KAAA,CAAA,EAAA,OAAA;CAOC;;;;;AC5CnC;;;;;;AAMA;;;;;;;AAoFyD,KDzE7C,MAAA,GCyE6C;;;;AC7FzD;;EAkBU,KAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EFQyB,aERzB,CAAA,EAAA,IAAA;EACc;;;;;EAmFI,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EFrEM,aEqEN,CAAA,EAAA,IAAA;EAAO;;;;ACzGnC;EACW,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EH0CuB,aG1CvB,CAAA,EAAA,IAAA;EACC;;;;;mCHgDuB;;;;KC5CvB,iBAAA;QACJ;sBACc;EDNV,cAAA,CAAA,ECOO,ODPM,CCOE,iBDPO,CAAA,GAAA,SAAA;AAqBlC,CAAA;AAMmC,cCjBtB,UAAA,CDiBsB;EAOD,iBAAA,QAAA;EAOA,iBAAA,UAAA;EAOC,SAAA,OAAA,ECpCR,cDoCQ;EAAa,iBAAA,IAAA;;wBC/BjB,6BAClB;;AAdb;;;;;;AAMA;;EAO+B,aAAA,CAAA,CAAA,EA8DZ,qBA9DY;EAClB,KAAA,CAAA,CAAA,EAiEI,OAjEJ,CAAA,IAAA,CAAA;EA6DM;;;;4CAe+B;;;;;;;cC7FrC,0BAAA;EFDD,eAAA,QAAa;EAqBb,QAAA,WAAM;EAMiB,QAAA,SAAA;EAOD,QAAA,WAAA,CAAA;EAOA,OAAA,WAAA,CAAA,CAAA,EEjCV,0BFiCU;EAOC;;;sBE7BzB,qCACc,+BACnB;;ADjBL;;EAEsB,iBAAA,CAAA,IAAA,ECmCZ,aDnCY,EAAA,EAAA,iBAAA,CAAA,ECoCE,4BDpCF,CAAA,ECqCjB,ODrCiB,CAAA,IAAA,CAAA;EACK,QAAA,mBAAA;EAAR,QAAA,gBAAA;EAAO,QAAA,QAAA;EAGb;;;;EAqEM,gBAAA,CAAA,CAAA,ECwBS,ODxBT,CAAA,IAAA,CAAA;;;;;;;AD/EP,iBGFU,iBAAA,CHEY,OAAA,EGDvB,OHCuB,EAAA,QAAA,EGAtB,kBHAsB,CAAA,EGC/B,OHD+B,CAAA,IAAA,CAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/logger.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/setup.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAQA;AAqBA;;AAakC,KAlCtB,aAAA,GAAgB,MAkCM,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA;EAOA,KAAA,CAAA,EAAA,OAAA;CAOC;;;;;ACrCnC;;;;;;AA6BA;;;;;;;AAyGyD,KD5H7C,MAAA,GC4H6C;;;;ACnIzD;;EA8BU,KAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EFjByB,aEiBzB,CAAA,EAAA,IAAA;EACc;;;;;EAgHI,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EF3HM,aE2HN,CAAA,EAAA,IAAA;EAAO;;;;AC9InC;EACW,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EHyBuB,aGzBvB,CAAA,EAAA,IAAA;EACC;;;;;mCH+BuB;;;;;;;AAhDnC;AAqBA;;;AAoBkC,KC9BtB,iBAAA,GD8BsB;EAOC,IAAA,ECpC3B,aDoC2B,EAAA;EAAa,iBAAA,CAAA,ECnC1B,4BDmC0B,GAAA,SAAA;mBClC7B,QAAQ;;;AAH3B;;;;;;AA6BA;;;;;;;;;;;AC1BA;;;;;AA2DU,cDjCG,UAAA,CCiCH;EACc,iBAAA,QAAA;EACnB,iBAAA,UAAA;EAkFuB,SAAA,OAAA,EDnHD,cCmHC;EAAO,iBAAA,IAAA;;;;AC9InC;;;;;;;;;wBF2C+B,6BAClB;;;;;;;;;;mBA6DM;;;;;;;;;;;WAcF;;;;;4CAWiC;;;;;;;;ADjJlD;AAqBA;;;;;;;;;ACVA;;AAEsB,cCCT,0BAAA,CDDS;EACK,eAAA,QAAA;EAAR,QAAA,WAAA;EAAO,QAAA,SAAA;EA0Bb,QAAA,WAAU,CAAA;EAEI;;;;;EAuGuB,OAAA,WAAA,CAAA,CAAA,ECvH1B,0BDuH0B;EAAO;;;;ACnIzD;;;;;;EA4DwB,aAAA,CAAA,IAAA,EA9Bd,aA8Bc,EAAA,EAAA,iBAAA,CAAA,EA7BA,4BA6BA,CAAA,EA5BnB,qBA4BmB;EACnB;;;;;;AC5DL;;;;EAGU,iBAAA,CAAA,IAAA,EDuDA,aCvDA,EAAA,EAAA,iBAAA,CAAA,EDwDc,4BCxDd,CAAA,EDyDL,OCzDK,CAAA,IAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBD2IkB;;;;;;;AF7J5B;AAqBA;;;;;;;;;ACVA;;;;;;AA6BA;AAE2B,iBE3BL,iBAAA,CF2BK,OAAA,EE1BhB,OF0BgB,EAAA,QAAA,EEzBf,kBFyBe,CAAA,EExBxB,OFwBwB,CAAA,IAAA,CAAA"}
package/dist/index.d.mts CHANGED
@@ -60,17 +60,58 @@ type Logger = {
60
60
  };
61
61
  //#endregion
62
62
  //#region src/amqp-client.d.ts
63
+ /**
64
+ * Options for creating an AMQP client.
65
+ *
66
+ * @property urls - AMQP broker URL(s). Multiple URLs provide failover support.
67
+ * @property connectionOptions - Optional connection configuration (heartbeat, reconnect settings, etc.).
68
+ * @property channelOptions - Optional channel configuration options.
69
+ */
63
70
  type AmqpClientOptions = {
64
71
  urls: ConnectionUrl[];
65
72
  connectionOptions?: AmqpConnectionManagerOptions | undefined;
66
73
  channelOptions?: Partial<CreateChannelOpts> | undefined;
67
74
  };
75
+ /**
76
+ * AMQP client that manages connections and channels with automatic topology setup.
77
+ *
78
+ * This class handles:
79
+ * - Connection management with automatic reconnection via amqp-connection-manager
80
+ * - Connection pooling and sharing across instances with the same URLs
81
+ * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
82
+ * - Channel creation with JSON serialization enabled by default
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const client = new AmqpClient(contract, {
87
+ * urls: ['amqp://localhost'],
88
+ * connectionOptions: { heartbeatIntervalInSeconds: 30 }
89
+ * });
90
+ *
91
+ * // Use the channel to publish messages
92
+ * await client.channel.publish('exchange', 'routingKey', { data: 'value' });
93
+ *
94
+ * // Close when done
95
+ * await client.close();
96
+ * ```
97
+ */
68
98
  declare class AmqpClient {
69
99
  private readonly contract;
70
100
  private readonly connection;
71
101
  readonly channel: ChannelWrapper;
72
102
  private readonly urls;
73
103
  private readonly connectionOptions?;
104
+ /**
105
+ * Create a new AMQP client instance.
106
+ *
107
+ * The client will automatically:
108
+ * - Get or create a shared connection using the singleton pattern
109
+ * - Set up AMQP topology (exchanges, queues, bindings) from the contract
110
+ * - Create a channel with JSON serialization enabled
111
+ *
112
+ * @param contract - The contract definition specifying the AMQP topology
113
+ * @param options - Client configuration options
114
+ */
74
115
  constructor(contract: ContractDefinition, options: AmqpClientOptions);
75
116
  /**
76
117
  * Get the underlying connection manager
@@ -82,6 +123,16 @@ declare class AmqpClient {
82
123
  * @returns The AmqpConnectionManager instance used by this client
83
124
  */
84
125
  getConnection(): AmqpConnectionManager;
126
+ /**
127
+ * Close the channel and release the connection reference.
128
+ *
129
+ * This will:
130
+ * - Close the channel wrapper
131
+ * - Decrease the reference count on the shared connection
132
+ * - Close the connection if this was the last client using it
133
+ *
134
+ * @returns A promise that resolves when the channel and connection are closed
135
+ */
85
136
  close(): Promise<void>;
86
137
  /**
87
138
  * Reset connection singleton cache (for testing only)
@@ -92,24 +143,78 @@ declare class AmqpClient {
92
143
  //#endregion
93
144
  //#region src/connection-manager.d.ts
94
145
  /**
95
- * Connection manager singleton for sharing connections across clients
146
+ * Connection manager singleton for sharing AMQP connections across clients.
147
+ *
148
+ * This singleton implements connection pooling to avoid creating multiple connections
149
+ * to the same broker, which is a RabbitMQ best practice. Connections are identified
150
+ * by their URLs and connection options, and reference counting ensures connections
151
+ * are only closed when all clients have released them.
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const manager = ConnectionManagerSingleton.getInstance();
156
+ * const connection = manager.getConnection(['amqp://localhost']);
157
+ * // ... use connection ...
158
+ * await manager.releaseConnection(['amqp://localhost']);
159
+ * ```
96
160
  */
97
161
  declare class ConnectionManagerSingleton {
98
162
  private static instance;
99
163
  private connections;
100
164
  private refCounts;
101
165
  private constructor();
166
+ /**
167
+ * Get the singleton instance of the connection manager.
168
+ *
169
+ * @returns The singleton instance
170
+ */
102
171
  static getInstance(): ConnectionManagerSingleton;
103
172
  /**
104
- * Get or create a connection for the given URLs and options
173
+ * Get or create a connection for the given URLs and options.
174
+ *
175
+ * If a connection already exists with the same URLs and options, it is reused
176
+ * and its reference count is incremented. Otherwise, a new connection is created.
177
+ *
178
+ * @param urls - AMQP broker URL(s)
179
+ * @param connectionOptions - Optional connection configuration
180
+ * @returns The AMQP connection manager instance
105
181
  */
106
182
  getConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): AmqpConnectionManager;
107
183
  /**
108
- * Release a connection reference. If no more references exist, close the connection.
184
+ * Release a connection reference.
185
+ *
186
+ * Decrements the reference count for the connection. If the count reaches zero,
187
+ * the connection is closed and removed from the pool.
188
+ *
189
+ * @param urls - AMQP broker URL(s) used to identify the connection
190
+ * @param connectionOptions - Optional connection configuration used to identify the connection
191
+ * @returns A promise that resolves when the connection is released (and closed if necessary)
109
192
  */
110
193
  releaseConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): Promise<void>;
194
+ /**
195
+ * Create a unique key for a connection based on URLs and options.
196
+ *
197
+ * The key is deterministic: same URLs and options always produce the same key,
198
+ * enabling connection reuse.
199
+ *
200
+ * @param urls - AMQP broker URL(s)
201
+ * @param connectionOptions - Optional connection configuration
202
+ * @returns A unique string key identifying the connection
203
+ */
111
204
  private createConnectionKey;
205
+ /**
206
+ * Serialize connection options to a deterministic string.
207
+ *
208
+ * @param options - Connection options to serialize
209
+ * @returns A JSON string with sorted keys for deterministic comparison
210
+ */
112
211
  private serializeOptions;
212
+ /**
213
+ * Deep sort an object's keys for deterministic serialization.
214
+ *
215
+ * @param value - The value to deep sort (can be object, array, or primitive)
216
+ * @returns The value with all object keys sorted alphabetically
217
+ */
113
218
  private deepSort;
114
219
  /**
115
220
  * Reset all cached connections (for testing purposes)
@@ -120,7 +225,24 @@ declare class ConnectionManagerSingleton {
120
225
  //#endregion
121
226
  //#region src/setup.d.ts
122
227
  /**
123
- * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition
228
+ * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.
229
+ *
230
+ * This function sets up the complete AMQP topology in the correct order:
231
+ * 1. Assert all exchanges defined in the contract
232
+ * 2. Validate dead letter exchanges are declared before referencing them
233
+ * 3. Assert all queues with their configurations (including dead letter settings)
234
+ * 4. Create all bindings (queue-to-exchange and exchange-to-exchange)
235
+ *
236
+ * @param channel - The AMQP channel to use for topology setup
237
+ * @param contract - The contract definition containing the topology specification
238
+ * @throws {AggregateError} If any exchanges, queues, or bindings fail to be created
239
+ * @throws {Error} If a queue references a dead letter exchange not declared in the contract
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * const channel = await connection.createChannel();
244
+ * await setupAmqpTopology(channel, contract);
245
+ * ```
124
246
  */
125
247
  declare function setupAmqpTopology(channel: Channel, contract: ContractDefinition): Promise<void>;
126
248
  //#endregion
@@ -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"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAQA;AAqBA;;AAakC,KAlCtB,aAAA,GAAgB,MAkCM,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA;EAOA,KAAA,CAAA,EAAA,OAAA;CAOC;;;;;AC5CnC;;;;;;AAMA;;;;;;;AAoFyD,KDzE7C,MAAA,GCyE6C;;;;AC7FzD;;EAkBU,KAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EFQyB,aERzB,CAAA,EAAA,IAAA;EACc;;;;;EAmFI,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EFrEM,aEqEN,CAAA,EAAA,IAAA;EAAO;;;;ACzGnC;EACW,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EH0CuB,aG1CvB,CAAA,EAAA,IAAA;EACC;;;;;mCHgDuB;;;;KC5CvB,iBAAA;QACJ;sBACc;EDNV,cAAA,CAAA,ECOO,ODPM,CCOE,iBDPO,CAAA,GAAA,SAAA;AAqBlC,CAAA;AAMmC,cCjBtB,UAAA,CDiBsB;EAOD,iBAAA,QAAA;EAOA,iBAAA,UAAA;EAOC,SAAA,OAAA,ECpCR,cDoCQ;EAAa,iBAAA,IAAA;;wBC/BjB,6BAClB;;AAdb;;;;;;AAMA;;EAO+B,aAAA,CAAA,CAAA,EA8DZ,qBA9DY;EAClB,KAAA,CAAA,CAAA,EAiEI,OAjEJ,CAAA,IAAA,CAAA;EA6DM;;;;4CAe+B;;;;;;;cC7FrC,0BAAA;EFDD,eAAA,QAAa;EAqBb,QAAA,WAAM;EAMiB,QAAA,SAAA;EAOD,QAAA,WAAA,CAAA;EAOA,OAAA,WAAA,CAAA,CAAA,EEjCV,0BFiCU;EAOC;;;sBE7BzB,qCACc,+BACnB;;ADjBL;;EAEsB,iBAAA,CAAA,IAAA,ECmCZ,aDnCY,EAAA,EAAA,iBAAA,CAAA,ECoCE,4BDpCF,CAAA,ECqCjB,ODrCiB,CAAA,IAAA,CAAA;EACK,QAAA,mBAAA;EAAR,QAAA,gBAAA;EAAO,QAAA,QAAA;EAGb;;;;EAqEM,gBAAA,CAAA,CAAA,ECwBS,ODxBT,CAAA,IAAA,CAAA;;;;;;;AD/EP,iBGFU,iBAAA,CHEY,OAAA,EGDvB,OHCuB,EAAA,QAAA,EGAtB,kBHAsB,CAAA,EGC/B,OHD+B,CAAA,IAAA,CAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/logger.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/setup.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAQA;AAqBA;;AAakC,KAlCtB,aAAA,GAAgB,MAkCM,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA;EAOA,KAAA,CAAA,EAAA,OAAA;CAOC;;;;;ACrCnC;;;;;;AA6BA;;;;;;;AAyGyD,KD5H7C,MAAA,GC4H6C;;;;ACnIzD;;EA8BU,KAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EFjByB,aEiBzB,CAAA,EAAA,IAAA;EACc;;;;;EAgHI,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EF3HM,aE2HN,CAAA,EAAA,IAAA;EAAO;;;;AC9InC;EACW,IAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EHyBuB,aGzBvB,CAAA,EAAA,IAAA;EACC;;;;;mCH+BuB;;;;;;;AAhDnC;AAqBA;;;AAoBkC,KC9BtB,iBAAA,GD8BsB;EAOC,IAAA,ECpC3B,aDoC2B,EAAA;EAAa,iBAAA,CAAA,ECnC1B,4BDmC0B,GAAA,SAAA;mBClC7B,QAAQ;;;AAH3B;;;;;;AA6BA;;;;;;;;;;;AC1BA;;;;;AA2DU,cDjCG,UAAA,CCiCH;EACc,iBAAA,QAAA;EACnB,iBAAA,UAAA;EAkFuB,SAAA,OAAA,EDnHD,cCmHC;EAAO,iBAAA,IAAA;;;;AC9InC;;;;;;;;;wBF2C+B,6BAClB;;;;;;;;;;mBA6DM;;;;;;;;;;;WAcF;;;;;4CAWiC;;;;;;;;ADjJlD;AAqBA;;;;;;;;;ACVA;;AAEsB,cCCT,0BAAA,CDDS;EACK,eAAA,QAAA;EAAR,QAAA,WAAA;EAAO,QAAA,SAAA;EA0Bb,QAAA,WAAU,CAAA;EAEI;;;;;EAuGuB,OAAA,WAAA,CAAA,CAAA,ECvH1B,0BDuH0B;EAAO;;;;ACnIzD;;;;;;EA4DwB,aAAA,CAAA,IAAA,EA9Bd,aA8Bc,EAAA,EAAA,iBAAA,CAAA,EA7BA,4BA6BA,CAAA,EA5BnB,qBA4BmB;EACnB;;;;;;AC5DL;;;;EAGU,iBAAA,CAAA,IAAA,EDuDA,aCvDA,EAAA,EAAA,iBAAA,CAAA,EDwDc,4BCxDd,CAAA,EDyDL,OCzDK,CAAA,IAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBD2IkB;;;;;;;AF7J5B;AAqBA;;;;;;;;;ACVA;;;;;;AA6BA;AAE2B,iBE3BL,iBAAA,CF2BK,OAAA,EE1BhB,OF0BgB,EAAA,QAAA,EEzBf,kBFyBe,CAAA,EExBxB,OFwBwB,CAAA,IAAA,CAAA"}
package/dist/index.mjs CHANGED
@@ -2,19 +2,44 @@ import amqp from "amqp-connection-manager";
2
2
 
3
3
  //#region src/connection-manager.ts
4
4
  /**
5
- * Connection manager singleton for sharing connections across clients
5
+ * Connection manager singleton for sharing AMQP connections across clients.
6
+ *
7
+ * This singleton implements connection pooling to avoid creating multiple connections
8
+ * to the same broker, which is a RabbitMQ best practice. Connections are identified
9
+ * by their URLs and connection options, and reference counting ensures connections
10
+ * are only closed when all clients have released them.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const manager = ConnectionManagerSingleton.getInstance();
15
+ * const connection = manager.getConnection(['amqp://localhost']);
16
+ * // ... use connection ...
17
+ * await manager.releaseConnection(['amqp://localhost']);
18
+ * ```
6
19
  */
7
20
  var ConnectionManagerSingleton = class ConnectionManagerSingleton {
8
21
  static instance;
9
22
  connections = /* @__PURE__ */ new Map();
10
23
  refCounts = /* @__PURE__ */ new Map();
11
24
  constructor() {}
25
+ /**
26
+ * Get the singleton instance of the connection manager.
27
+ *
28
+ * @returns The singleton instance
29
+ */
12
30
  static getInstance() {
13
31
  if (!ConnectionManagerSingleton.instance) ConnectionManagerSingleton.instance = new ConnectionManagerSingleton();
14
32
  return ConnectionManagerSingleton.instance;
15
33
  }
16
34
  /**
17
- * Get or create a connection for the given URLs and options
35
+ * Get or create a connection for the given URLs and options.
36
+ *
37
+ * If a connection already exists with the same URLs and options, it is reused
38
+ * and its reference count is incremented. Otherwise, a new connection is created.
39
+ *
40
+ * @param urls - AMQP broker URL(s)
41
+ * @param connectionOptions - Optional connection configuration
42
+ * @returns The AMQP connection manager instance
18
43
  */
19
44
  getConnection(urls, connectionOptions) {
20
45
  const key = this.createConnectionKey(urls, connectionOptions);
@@ -27,7 +52,14 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
27
52
  return this.connections.get(key);
28
53
  }
29
54
  /**
30
- * Release a connection reference. If no more references exist, close the connection.
55
+ * Release a connection reference.
56
+ *
57
+ * Decrements the reference count for the connection. If the count reaches zero,
58
+ * the connection is closed and removed from the pool.
59
+ *
60
+ * @param urls - AMQP broker URL(s) used to identify the connection
61
+ * @param connectionOptions - Optional connection configuration used to identify the connection
62
+ * @returns A promise that resolves when the connection is released (and closed if necessary)
31
63
  */
32
64
  async releaseConnection(urls, connectionOptions) {
33
65
  const key = this.createConnectionKey(urls, connectionOptions);
@@ -41,13 +73,35 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
41
73
  }
42
74
  } else this.refCounts.set(key, refCount - 1);
43
75
  }
76
+ /**
77
+ * Create a unique key for a connection based on URLs and options.
78
+ *
79
+ * The key is deterministic: same URLs and options always produce the same key,
80
+ * enabling connection reuse.
81
+ *
82
+ * @param urls - AMQP broker URL(s)
83
+ * @param connectionOptions - Optional connection configuration
84
+ * @returns A unique string key identifying the connection
85
+ */
44
86
  createConnectionKey(urls, connectionOptions) {
45
87
  return `${JSON.stringify(urls)}::${connectionOptions ? this.serializeOptions(connectionOptions) : ""}`;
46
88
  }
89
+ /**
90
+ * Serialize connection options to a deterministic string.
91
+ *
92
+ * @param options - Connection options to serialize
93
+ * @returns A JSON string with sorted keys for deterministic comparison
94
+ */
47
95
  serializeOptions(options) {
48
96
  const sorted = this.deepSort(options);
49
97
  return JSON.stringify(sorted);
50
98
  }
99
+ /**
100
+ * Deep sort an object's keys for deterministic serialization.
101
+ *
102
+ * @param value - The value to deep sort (can be object, array, or primitive)
103
+ * @returns The value with all object keys sorted alphabetically
104
+ */
51
105
  deepSort(value) {
52
106
  if (Array.isArray(value)) return value.map((item) => this.deepSort(item));
53
107
  if (value !== null && typeof value === "object") {
@@ -74,7 +128,24 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
74
128
  //#endregion
75
129
  //#region src/setup.ts
76
130
  /**
77
- * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition
131
+ * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.
132
+ *
133
+ * This function sets up the complete AMQP topology in the correct order:
134
+ * 1. Assert all exchanges defined in the contract
135
+ * 2. Validate dead letter exchanges are declared before referencing them
136
+ * 3. Assert all queues with their configurations (including dead letter settings)
137
+ * 4. Create all bindings (queue-to-exchange and exchange-to-exchange)
138
+ *
139
+ * @param channel - The AMQP channel to use for topology setup
140
+ * @param contract - The contract definition containing the topology specification
141
+ * @throws {AggregateError} If any exchanges, queues, or bindings fail to be created
142
+ * @throws {Error} If a queue references a dead letter exchange not declared in the contract
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * const channel = await connection.createChannel();
147
+ * await setupAmqpTopology(channel, contract);
148
+ * ```
78
149
  */
79
150
  async function setupAmqpTopology(channel, contract) {
80
151
  const exchangeErrors = (await Promise.allSettled(Object.values(contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
@@ -111,11 +182,45 @@ async function setupAmqpTopology(channel, contract) {
111
182
 
112
183
  //#endregion
113
184
  //#region src/amqp-client.ts
185
+ /**
186
+ * AMQP client that manages connections and channels with automatic topology setup.
187
+ *
188
+ * This class handles:
189
+ * - Connection management with automatic reconnection via amqp-connection-manager
190
+ * - Connection pooling and sharing across instances with the same URLs
191
+ * - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
192
+ * - Channel creation with JSON serialization enabled by default
193
+ *
194
+ * @example
195
+ * ```typescript
196
+ * const client = new AmqpClient(contract, {
197
+ * urls: ['amqp://localhost'],
198
+ * connectionOptions: { heartbeatIntervalInSeconds: 30 }
199
+ * });
200
+ *
201
+ * // Use the channel to publish messages
202
+ * await client.channel.publish('exchange', 'routingKey', { data: 'value' });
203
+ *
204
+ * // Close when done
205
+ * await client.close();
206
+ * ```
207
+ */
114
208
  var AmqpClient = class {
115
209
  connection;
116
210
  channel;
117
211
  urls;
118
212
  connectionOptions;
213
+ /**
214
+ * Create a new AMQP client instance.
215
+ *
216
+ * The client will automatically:
217
+ * - Get or create a shared connection using the singleton pattern
218
+ * - Set up AMQP topology (exchanges, queues, bindings) from the contract
219
+ * - Create a channel with JSON serialization enabled
220
+ *
221
+ * @param contract - The contract definition specifying the AMQP topology
222
+ * @param options - Client configuration options
223
+ */
119
224
  constructor(contract, options) {
120
225
  this.contract = contract;
121
226
  this.urls = options.urls;
@@ -152,6 +257,16 @@ var AmqpClient = class {
152
257
  getConnection() {
153
258
  return this.connection;
154
259
  }
260
+ /**
261
+ * Close the channel and release the connection reference.
262
+ *
263
+ * This will:
264
+ * - Close the channel wrapper
265
+ * - Decrease the reference count on the shared connection
266
+ * - Close the connection if this was the last client using it
267
+ *
268
+ * @returns A promise that resolves when the channel and connection are closed
269
+ */
155
270
  async close() {
156
271
  await this.channel.close();
157
272
  await ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["result: Record<string, unknown>","contract: ContractDefinition","channelOpts: CreateChannelOpts"],"sources":["../src/connection-manager.ts","../src/setup.ts","../src/amqp-client.ts"],"sourcesContent":["import amqp, {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ConnectionUrl,\n} from \"amqp-connection-manager\";\n\n/**\n * Connection manager singleton for sharing connections across clients\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 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 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. If no more references exist, close the connection.\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 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 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 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\";\n\n/**\n * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition\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 queue of Object.values(contract.queues ?? {})) {\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((queue) => {\n // Build queue arguments, merging dead letter configuration if present\n const queueArguments = { ...queue.arguments };\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 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\nexport type AmqpClientOptions = {\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n channelOptions?: Partial<CreateChannelOpts> | undefined;\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 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 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"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/connection-manager.ts","../src/setup.ts","../src/amqp-client.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\";\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 queue of Object.values(contract.queues ?? {})) {\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((queue) => {\n // Build queue arguments, merging dead letter configuration if present\n const queueArguments = { ...queue.arguments };\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 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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amqp-contract/core",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Core utilities for AMQP setup and management in amqp-contract",
5
5
  "keywords": [
6
6
  "amqp",
@@ -48,19 +48,19 @@
48
48
  "dependencies": {
49
49
  "amqp-connection-manager": "5.0.0",
50
50
  "amqplib": "0.10.9",
51
- "@amqp-contract/contract": "0.7.0"
51
+ "@amqp-contract/contract": "0.8.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/amqplib": "0.10.8",
55
55
  "@vitest/coverage-v8": "4.0.16",
56
- "tsdown": "0.18.4",
56
+ "tsdown": "0.19.0",
57
57
  "typedoc": "0.28.15",
58
58
  "typescript": "5.9.3",
59
59
  "vitest": "4.0.16",
60
60
  "zod": "4.3.5",
61
- "@amqp-contract/testing": "0.7.0",
62
- "@amqp-contract/tsconfig": "0.0.0",
63
- "@amqp-contract/typedoc": "0.0.1"
61
+ "@amqp-contract/testing": "0.8.0",
62
+ "@amqp-contract/tsconfig": "0.1.0",
63
+ "@amqp-contract/typedoc": "0.1.0"
64
64
  },
65
65
  "scripts": {
66
66
  "build": "tsdown src/index.ts --format cjs,esm --dts --clean",