@amqp-contract/core 0.3.5 → 0.4.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
@@ -28,22 +28,113 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  let amqp_connection_manager = require("amqp-connection-manager");
29
29
  amqp_connection_manager = __toESM(amqp_connection_manager);
30
30
 
31
- //#region src/index.ts
31
+ //#region src/amqp-client.ts
32
+ /**
33
+ * Connection manager singleton for sharing connections across clients
34
+ */
35
+ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
36
+ static instance;
37
+ connections = /* @__PURE__ */ new Map();
38
+ refCounts = /* @__PURE__ */ new Map();
39
+ constructor() {}
40
+ static getInstance() {
41
+ if (!ConnectionManagerSingleton.instance) ConnectionManagerSingleton.instance = new ConnectionManagerSingleton();
42
+ return ConnectionManagerSingleton.instance;
43
+ }
44
+ /**
45
+ * Get or create a connection for the given URLs and options
46
+ */
47
+ getConnection(urls, connectionOptions) {
48
+ const key = this.createConnectionKey(urls, connectionOptions);
49
+ if (!this.connections.has(key)) {
50
+ const connection = amqp_connection_manager.default.connect(urls, connectionOptions);
51
+ this.connections.set(key, connection);
52
+ this.refCounts.set(key, 0);
53
+ }
54
+ this.refCounts.set(key, (this.refCounts.get(key) ?? 0) + 1);
55
+ return this.connections.get(key);
56
+ }
57
+ /**
58
+ * Release a connection reference. If no more references exist, close the connection.
59
+ */
60
+ async releaseConnection(urls, connectionOptions) {
61
+ const key = this.createConnectionKey(urls, connectionOptions);
62
+ const refCount = this.refCounts.get(key) ?? 0;
63
+ if (refCount <= 1) {
64
+ const connection = this.connections.get(key);
65
+ if (connection) {
66
+ await connection.close();
67
+ this.connections.delete(key);
68
+ this.refCounts.delete(key);
69
+ }
70
+ } else this.refCounts.set(key, refCount - 1);
71
+ }
72
+ createConnectionKey(urls, connectionOptions) {
73
+ return `${JSON.stringify(urls)}::${connectionOptions ? this.serializeOptions(connectionOptions) : ""}`;
74
+ }
75
+ serializeOptions(options) {
76
+ const sorted = this.deepSort(options);
77
+ return JSON.stringify(sorted);
78
+ }
79
+ deepSort(value) {
80
+ if (Array.isArray(value)) return value.map((item) => this.deepSort(item));
81
+ if (value !== null && typeof value === "object") {
82
+ const obj = value;
83
+ const sortedKeys = Object.keys(obj).sort();
84
+ const result = {};
85
+ for (const key of sortedKeys) result[key] = this.deepSort(obj[key]);
86
+ return result;
87
+ }
88
+ return value;
89
+ }
90
+ /**
91
+ * Reset all cached connections (for testing purposes)
92
+ * @internal
93
+ */
94
+ async _resetForTesting() {
95
+ const closePromises = Array.from(this.connections.values()).map((conn) => conn.close());
96
+ await Promise.all(closePromises);
97
+ this.connections.clear();
98
+ this.refCounts.clear();
99
+ }
100
+ };
32
101
  var AmqpClient = class {
33
102
  connection;
34
103
  channel;
104
+ urls;
105
+ connectionOptions;
35
106
  constructor(contract, options) {
36
107
  this.contract = contract;
37
- this.options = options;
38
- this.connection = amqp_connection_manager.default.connect(this.options.urls, this.options.connectionOptions);
108
+ this.urls = options.urls;
109
+ if (options.connectionOptions !== void 0) this.connectionOptions = options.connectionOptions;
110
+ this.connection = ConnectionManagerSingleton.getInstance().getConnection(options.urls, options.connectionOptions);
39
111
  this.channel = this.connection.createChannel({
40
112
  json: true,
41
113
  setup: (channel) => this.setup(channel)
42
114
  });
43
115
  }
116
+ /**
117
+ * Get the underlying connection manager
118
+ *
119
+ * This method exposes the AmqpConnectionManager instance that this client uses.
120
+ * The connection is automatically shared across all AmqpClient instances that
121
+ * use the same URLs and connection options.
122
+ *
123
+ * @returns The AmqpConnectionManager instance used by this client
124
+ */
125
+ getConnection() {
126
+ return this.connection;
127
+ }
44
128
  async close() {
45
129
  await this.channel.close();
46
- await this.connection.close();
130
+ await ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions);
131
+ }
132
+ /**
133
+ * Reset connection singleton cache (for testing only)
134
+ * @internal
135
+ */
136
+ static async _resetConnectionCacheForTesting() {
137
+ await ConnectionManagerSingleton.getInstance()._resetForTesting();
47
138
  }
48
139
  async setup(channel) {
49
140
  const exchangeErrors = (await Promise.allSettled(Object.values(this.contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
+ import { AmqpConnectionManager, AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl } from "amqp-connection-manager";
1
2
  import { ContractDefinition } from "@amqp-contract/contract";
2
- import { AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl } from "amqp-connection-manager";
3
3
 
4
4
  //#region src/logger.d.ts
5
5
 
@@ -31,7 +31,7 @@ type LoggerContext = Record<string, unknown> & {
31
31
  * };
32
32
  * ```
33
33
  */
34
- interface Logger {
34
+ type Logger = {
35
35
  /**
36
36
  * Log debug level messages
37
37
  * @param message - The log message
@@ -56,22 +56,38 @@ interface Logger {
56
56
  * @param context - Optional context to include with the log
57
57
  */
58
58
  error(message: string, context?: LoggerContext): void;
59
- }
59
+ };
60
60
  //#endregion
61
- //#region src/index.d.ts
61
+ //#region src/amqp-client.d.ts
62
62
  type AmqpClientOptions = {
63
63
  urls: ConnectionUrl[];
64
64
  connectionOptions?: AmqpConnectionManagerOptions | undefined;
65
65
  };
66
66
  declare class AmqpClient {
67
67
  private readonly contract;
68
- private readonly options;
69
68
  private readonly connection;
70
69
  readonly channel: ChannelWrapper;
70
+ private readonly urls;
71
+ private readonly connectionOptions?;
71
72
  constructor(contract: ContractDefinition, options: AmqpClientOptions);
73
+ /**
74
+ * Get the underlying connection manager
75
+ *
76
+ * This method exposes the AmqpConnectionManager instance that this client uses.
77
+ * The connection is automatically shared across all AmqpClient instances that
78
+ * use the same URLs and connection options.
79
+ *
80
+ * @returns The AmqpConnectionManager instance used by this client
81
+ */
82
+ getConnection(): AmqpConnectionManager;
72
83
  close(): Promise<void>;
84
+ /**
85
+ * Reset connection singleton cache (for testing only)
86
+ * @internal
87
+ */
88
+ static _resetConnectionCacheForTesting(): Promise<void>;
73
89
  private setup;
74
90
  }
75
91
  //#endregion
76
- export { AmqpClient, AmqpClientOptions, type Logger, type LoggerContext };
92
+ export { AmqpClient, type AmqpClientOptions, type Logger, type LoggerContext };
77
93
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/logger.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAQA;AAqBA;;;AAoBkC,KAzCtB,aAAA,GAAgB,MAyCM,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA;EAOC,KAAA,CAAA,EAAA,OAAA;CAAa;;;;AC7ChD;AAKA;;;;;;;;;;;;;UDaiB,MAAA;;;;;;mCAMkB;;;;;;kCAOD;;;;;;kCAOA;;;;;;mCAOC;;;;KC7CvB,iBAAA;EDHA,IAAA,ECIJ,aDJiB,EAAA;EAqBR,iBAAM,CAAA,EChBD,4BDgBC,GAAA,SAAA;CAMY;AAOD,cC1BrB,UAAA,CD0BqB;EAOA,iBAAA,QAAA;EAOC,iBAAA,OAAA;EAAa,iBAAA,UAAA;oBCtCrB;wBAGI,6BACD;WASb;EApBL,QAAA,KAAA;AAKZ"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/logger.ts","../src/amqp-client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAQA;AAqBA;;;AAoBkC,KAzCtB,aAAA,GAAgB,MAyCM,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA;EAOC,KAAA,CAAA,EAAA,OAAA;CAAa;;;;AC/ChD;AAuHA;;;;;;;;;;;;;KDnGY,MAAA;;;;;;mCAMuB;;;;;;kCAOD;;;;;;kCAOA;;;;;;mCAOC;;;;KC/CvB,iBAAA;QACJ;EDFI,iBAAa,CAAA,ECGH,4BDHY,GAAA,SAAA;AAqBlC,CAAA;AAMmC,cC6FtB,UAAA,CD7FsB;EAOD,iBAAA,QAAA;EAOA,iBAAA,UAAA;EAOC,SAAA,OAAA,EC0ER,cD1EQ;EAAa,iBAAA,IAAA;;wBC+EjB,6BAClB;;AA/Hb;AAuHA;;;;;;;EAkDyD,aAAA,CAAA,CAAA,EAftC,qBAesC;WAXxC;;;;;4CAWiC"}
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl } from "amqp-connection-manager";
1
+ import { AmqpConnectionManager, AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl } from "amqp-connection-manager";
2
2
  import { ContractDefinition } from "@amqp-contract/contract";
3
3
 
4
4
  //#region src/logger.d.ts
@@ -31,7 +31,7 @@ type LoggerContext = Record<string, unknown> & {
31
31
  * };
32
32
  * ```
33
33
  */
34
- interface Logger {
34
+ type Logger = {
35
35
  /**
36
36
  * Log debug level messages
37
37
  * @param message - The log message
@@ -56,22 +56,38 @@ interface Logger {
56
56
  * @param context - Optional context to include with the log
57
57
  */
58
58
  error(message: string, context?: LoggerContext): void;
59
- }
59
+ };
60
60
  //#endregion
61
- //#region src/index.d.ts
61
+ //#region src/amqp-client.d.ts
62
62
  type AmqpClientOptions = {
63
63
  urls: ConnectionUrl[];
64
64
  connectionOptions?: AmqpConnectionManagerOptions | undefined;
65
65
  };
66
66
  declare class AmqpClient {
67
67
  private readonly contract;
68
- private readonly options;
69
68
  private readonly connection;
70
69
  readonly channel: ChannelWrapper;
70
+ private readonly urls;
71
+ private readonly connectionOptions?;
71
72
  constructor(contract: ContractDefinition, options: AmqpClientOptions);
73
+ /**
74
+ * Get the underlying connection manager
75
+ *
76
+ * This method exposes the AmqpConnectionManager instance that this client uses.
77
+ * The connection is automatically shared across all AmqpClient instances that
78
+ * use the same URLs and connection options.
79
+ *
80
+ * @returns The AmqpConnectionManager instance used by this client
81
+ */
82
+ getConnection(): AmqpConnectionManager;
72
83
  close(): Promise<void>;
84
+ /**
85
+ * Reset connection singleton cache (for testing only)
86
+ * @internal
87
+ */
88
+ static _resetConnectionCacheForTesting(): Promise<void>;
73
89
  private setup;
74
90
  }
75
91
  //#endregion
76
- export { AmqpClient, AmqpClientOptions, type Logger, type LoggerContext };
92
+ export { AmqpClient, type AmqpClientOptions, type Logger, type LoggerContext };
77
93
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/logger.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAQA;AAqBA;;;AAoBkC,KAzCtB,aAAA,GAAgB,MAyCM,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA;EAOC,KAAA,CAAA,EAAA,OAAA;CAAa;;;;AC7ChD;AAKA;;;;;;;;;;;;;UDaiB,MAAA;;;;;;mCAMkB;;;;;;kCAOD;;;;;;kCAOA;;;;;;mCAOC;;;;KC7CvB,iBAAA;EDHA,IAAA,ECIJ,aDJiB,EAAA;EAqBR,iBAAM,CAAA,EChBD,4BDgBC,GAAA,SAAA;CAMY;AAOD,cC1BrB,UAAA,CD0BqB;EAOA,iBAAA,QAAA;EAOC,iBAAA,OAAA;EAAa,iBAAA,UAAA;oBCtCrB;wBAGI,6BACD;WASb;EApBL,QAAA,KAAA;AAKZ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/logger.ts","../src/amqp-client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAQA;AAqBA;;;AAoBkC,KAzCtB,aAAA,GAAgB,MAyCM,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA;EAOC,KAAA,CAAA,EAAA,OAAA;CAAa;;;;AC/ChD;AAuHA;;;;;;;;;;;;;KDnGY,MAAA;;;;;;mCAMuB;;;;;;kCAOD;;;;;;kCAOA;;;;;;mCAOC;;;;KC/CvB,iBAAA;QACJ;EDFI,iBAAa,CAAA,ECGH,4BDHY,GAAA,SAAA;AAqBlC,CAAA;AAMmC,cC6FtB,UAAA,CD7FsB;EAOD,iBAAA,QAAA;EAOA,iBAAA,UAAA;EAOC,SAAA,OAAA,EC0ER,cD1EQ;EAAa,iBAAA,IAAA;;wBC+EjB,6BAClB;;AA/Hb;AAuHA;;;;;;;EAkDyD,aAAA,CAAA,CAAA,EAftC,qBAesC;WAXxC;;;;;4CAWiC"}
package/dist/index.mjs CHANGED
@@ -1,21 +1,112 @@
1
1
  import amqp from "amqp-connection-manager";
2
2
 
3
- //#region src/index.ts
3
+ //#region src/amqp-client.ts
4
+ /**
5
+ * Connection manager singleton for sharing connections across clients
6
+ */
7
+ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
8
+ static instance;
9
+ connections = /* @__PURE__ */ new Map();
10
+ refCounts = /* @__PURE__ */ new Map();
11
+ constructor() {}
12
+ static getInstance() {
13
+ if (!ConnectionManagerSingleton.instance) ConnectionManagerSingleton.instance = new ConnectionManagerSingleton();
14
+ return ConnectionManagerSingleton.instance;
15
+ }
16
+ /**
17
+ * Get or create a connection for the given URLs and options
18
+ */
19
+ getConnection(urls, connectionOptions) {
20
+ const key = this.createConnectionKey(urls, connectionOptions);
21
+ if (!this.connections.has(key)) {
22
+ const connection = amqp.connect(urls, connectionOptions);
23
+ this.connections.set(key, connection);
24
+ this.refCounts.set(key, 0);
25
+ }
26
+ this.refCounts.set(key, (this.refCounts.get(key) ?? 0) + 1);
27
+ return this.connections.get(key);
28
+ }
29
+ /**
30
+ * Release a connection reference. If no more references exist, close the connection.
31
+ */
32
+ async releaseConnection(urls, connectionOptions) {
33
+ const key = this.createConnectionKey(urls, connectionOptions);
34
+ const refCount = this.refCounts.get(key) ?? 0;
35
+ if (refCount <= 1) {
36
+ const connection = this.connections.get(key);
37
+ if (connection) {
38
+ await connection.close();
39
+ this.connections.delete(key);
40
+ this.refCounts.delete(key);
41
+ }
42
+ } else this.refCounts.set(key, refCount - 1);
43
+ }
44
+ createConnectionKey(urls, connectionOptions) {
45
+ return `${JSON.stringify(urls)}::${connectionOptions ? this.serializeOptions(connectionOptions) : ""}`;
46
+ }
47
+ serializeOptions(options) {
48
+ const sorted = this.deepSort(options);
49
+ return JSON.stringify(sorted);
50
+ }
51
+ deepSort(value) {
52
+ if (Array.isArray(value)) return value.map((item) => this.deepSort(item));
53
+ if (value !== null && typeof value === "object") {
54
+ const obj = value;
55
+ const sortedKeys = Object.keys(obj).sort();
56
+ const result = {};
57
+ for (const key of sortedKeys) result[key] = this.deepSort(obj[key]);
58
+ return result;
59
+ }
60
+ return value;
61
+ }
62
+ /**
63
+ * Reset all cached connections (for testing purposes)
64
+ * @internal
65
+ */
66
+ async _resetForTesting() {
67
+ const closePromises = Array.from(this.connections.values()).map((conn) => conn.close());
68
+ await Promise.all(closePromises);
69
+ this.connections.clear();
70
+ this.refCounts.clear();
71
+ }
72
+ };
4
73
  var AmqpClient = class {
5
74
  connection;
6
75
  channel;
76
+ urls;
77
+ connectionOptions;
7
78
  constructor(contract, options) {
8
79
  this.contract = contract;
9
- this.options = options;
10
- this.connection = amqp.connect(this.options.urls, this.options.connectionOptions);
80
+ this.urls = options.urls;
81
+ if (options.connectionOptions !== void 0) this.connectionOptions = options.connectionOptions;
82
+ this.connection = ConnectionManagerSingleton.getInstance().getConnection(options.urls, options.connectionOptions);
11
83
  this.channel = this.connection.createChannel({
12
84
  json: true,
13
85
  setup: (channel) => this.setup(channel)
14
86
  });
15
87
  }
88
+ /**
89
+ * Get the underlying connection manager
90
+ *
91
+ * This method exposes the AmqpConnectionManager instance that this client uses.
92
+ * The connection is automatically shared across all AmqpClient instances that
93
+ * use the same URLs and connection options.
94
+ *
95
+ * @returns The AmqpConnectionManager instance used by this client
96
+ */
97
+ getConnection() {
98
+ return this.connection;
99
+ }
16
100
  async close() {
17
101
  await this.channel.close();
18
- await this.connection.close();
102
+ await ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions);
103
+ }
104
+ /**
105
+ * Reset connection singleton cache (for testing only)
106
+ * @internal
107
+ */
108
+ static async _resetConnectionCacheForTesting() {
109
+ await ConnectionManagerSingleton.getInstance()._resetForTesting();
19
110
  }
20
111
  async setup(channel) {
21
112
  const exchangeErrors = (await Promise.allSettled(Object.values(this.contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["contract: ContractDefinition","options: AmqpClientOptions"],"sources":["../src/index.ts"],"sourcesContent":["import type { Channel } from \"amqplib\";\nimport type { ContractDefinition } from \"@amqp-contract/contract\";\nimport amqp, {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ChannelWrapper,\n ConnectionUrl,\n} from \"amqp-connection-manager\";\n\nexport type { Logger, LoggerContext } from \"./logger.js\";\n\nexport type AmqpClientOptions = {\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n};\n\nexport class AmqpClient {\n private readonly connection: AmqpConnectionManager;\n public readonly channel: ChannelWrapper;\n\n constructor(\n private readonly contract: ContractDefinition,\n private readonly options: AmqpClientOptions,\n ) {\n this.connection = amqp.connect(this.options.urls, this.options.connectionOptions);\n this.channel = this.connection.createChannel({\n json: true,\n setup: (channel: Channel) => this.setup(channel),\n });\n }\n\n async close(): Promise<void> {\n await this.channel.close();\n await this.connection.close();\n }\n\n private async setup(channel: Channel): Promise<void> {\n // Setup exchanges\n const exchangeResults = await Promise.allSettled(\n Object.values(this.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 // Setup queues\n const queueResults = await Promise.allSettled(\n Object.values(this.contract.queues ?? {}).map((queue) =>\n channel.assertQueue(queue.name, {\n durable: queue.durable,\n exclusive: queue.exclusive,\n autoDelete: queue.autoDelete,\n arguments: queue.arguments,\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(this.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}\n"],"mappings":";;;AAgBA,IAAa,aAAb,MAAwB;CACtB,AAAiB;CACjB,AAAgB;CAEhB,YACE,AAAiBA,UACjB,AAAiBC,SACjB;EAFiB;EACA;AAEjB,OAAK,aAAa,KAAK,QAAQ,KAAK,QAAQ,MAAM,KAAK,QAAQ,kBAAkB;AACjF,OAAK,UAAU,KAAK,WAAW,cAAc;GAC3C,MAAM;GACN,QAAQ,YAAqB,KAAK,MAAM,QAAQ;GACjD,CAAC;;CAGJ,MAAM,QAAuB;AAC3B,QAAM,KAAK,QAAQ,OAAO;AAC1B,QAAM,KAAK,WAAW,OAAO;;CAG/B,MAAc,MAAM,SAAiC;EAYnD,MAAM,kBAVkB,MAAM,QAAQ,WACpC,OAAO,OAAO,KAAK,SAAS,aAAa,EAAE,CAAC,CAAC,KAAK,aAChD,QAAQ,eAAe,SAAS,MAAM,SAAS,MAAM;GACnD,SAAS,SAAS;GAClB,YAAY,SAAS;GACrB,UAAU,SAAS;GACnB,WAAW,SAAS;GACrB,CAAC,CACH,CACF,EACsC,QACpC,WAA4C,OAAO,WAAW,WAChE;AACD,MAAI,eAAe,SAAS,EAC1B,OAAM,IAAI,eACR,eAAe,KAAK,EAAE,aAAa,OAAO,EAC1C,4BACD;EAcH,MAAM,eAVe,MAAM,QAAQ,WACjC,OAAO,OAAO,KAAK,SAAS,UAAU,EAAE,CAAC,CAAC,KAAK,UAC7C,QAAQ,YAAY,MAAM,MAAM;GAC9B,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,YAAY,MAAM;GAClB,WAAW,MAAM;GAClB,CAAC,CACH,CACF,EACgC,QAC9B,WAA4C,OAAO,WAAW,WAChE;AACD,MAAI,YAAY,SAAS,EACvB,OAAM,IAAI,eACR,YAAY,KAAK,EAAE,aAAa,OAAO,EACvC,yBACD;EAuBH,MAAM,iBAnBiB,MAAM,QAAQ,WACnC,OAAO,OAAO,KAAK,SAAS,YAAY,EAAE,CAAC,CAAC,KAAK,YAAY;AAC3D,OAAI,QAAQ,SAAS,QACnB,QAAO,QAAQ,UACb,QAAQ,MAAM,MACd,QAAQ,SAAS,MACjB,QAAQ,cAAc,IACtB,QAAQ,UACT;AAGH,UAAO,QAAQ,aACb,QAAQ,YAAY,MACpB,QAAQ,OAAO,MACf,QAAQ,cAAc,IACtB,QAAQ,UACT;IACD,CACH,EACoC,QAClC,WAA4C,OAAO,WAAW,WAChE;AACD,MAAI,cAAc,SAAS,EACzB,OAAM,IAAI,eACR,cAAc,KAAK,EAAE,aAAa,OAAO,EACzC,2BACD"}
1
+ {"version":3,"file":"index.mjs","names":["result: Record<string, unknown>","contract: ContractDefinition"],"sources":["../src/amqp-client.ts"],"sourcesContent":["import amqp, {\n AmqpConnectionManager,\n AmqpConnectionManagerOptions,\n ChannelWrapper,\n ConnectionUrl,\n} from \"amqp-connection-manager\";\nimport type { Channel } from \"amqplib\";\nimport type { ContractDefinition } from \"@amqp-contract/contract\";\n\nexport type AmqpClientOptions = {\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n};\n\n/**\n * Connection manager singleton for sharing connections across clients\n */\nclass 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\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 this.channel = this.connection.createChannel({\n json: true,\n setup: (channel: Channel) => this.setup(channel),\n });\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 private async setup(channel: Channel): Promise<void> {\n // Setup exchanges\n const exchangeResults = await Promise.allSettled(\n Object.values(this.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 // Setup queues\n const queueResults = await Promise.allSettled(\n Object.values(this.contract.queues ?? {}).map((queue) =>\n channel.assertQueue(queue.name, {\n durable: queue.durable,\n exclusive: queue.exclusive,\n autoDelete: queue.autoDelete,\n arguments: queue.arguments,\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(this.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}\n"],"mappings":";;;;;;AAiBA,IAAM,6BAAN,MAAM,2BAA2B;CAC/B,OAAe;CACf,AAAQ,8BAAkD,IAAI,KAAK;CACnE,AAAQ,4BAAiC,IAAI,KAAK;CAElD,AAAQ,cAAc;CAEtB,OAAO,cAA0C;AAC/C,MAAI,CAAC,2BAA2B,SAC9B,4BAA2B,WAAW,IAAI,4BAA4B;AAExE,SAAO,2BAA2B;;;;;CAMpC,cACE,MACA,mBACuB;EAEvB,MAAM,MAAM,KAAK,oBAAoB,MAAM,kBAAkB;AAE7D,MAAI,CAAC,KAAK,YAAY,IAAI,IAAI,EAAE;GAC9B,MAAM,aAAa,KAAK,QAAQ,MAAM,kBAAkB;AACxD,QAAK,YAAY,IAAI,KAAK,WAAW;AACrC,QAAK,UAAU,IAAI,KAAK,EAAE;;AAI5B,OAAK,UAAU,IAAI,MAAM,KAAK,UAAU,IAAI,IAAI,IAAI,KAAK,EAAE;AAE3D,SAAO,KAAK,YAAY,IAAI,IAAI;;;;;CAMlC,MAAM,kBACJ,MACA,mBACe;EACf,MAAM,MAAM,KAAK,oBAAoB,MAAM,kBAAkB;EAC7D,MAAM,WAAW,KAAK,UAAU,IAAI,IAAI,IAAI;AAE5C,MAAI,YAAY,GAAG;GAEjB,MAAM,aAAa,KAAK,YAAY,IAAI,IAAI;AAC5C,OAAI,YAAY;AACd,UAAM,WAAW,OAAO;AACxB,SAAK,YAAY,OAAO,IAAI;AAC5B,SAAK,UAAU,OAAO,IAAI;;QAI5B,MAAK,UAAU,IAAI,KAAK,WAAW,EAAE;;CAIzC,AAAQ,oBACN,MACA,mBACQ;AAMR,SAAO,GAHS,KAAK,UAAU,KAAK,CAGlB,IADF,oBAAoB,KAAK,iBAAiB,kBAAkB,GAAG;;CAIjF,AAAQ,iBAAiB,SAA+C;EAEtE,MAAM,SAAS,KAAK,SAAS,QAAQ;AACrC,SAAO,KAAK,UAAU,OAAO;;CAG/B,AAAQ,SAAS,OAAyB;AACxC,MAAI,MAAM,QAAQ,MAAM,CACtB,QAAO,MAAM,KAAK,SAAS,KAAK,SAAS,KAAK,CAAC;AAGjD,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;GAC/C,MAAM,MAAM;GACZ,MAAM,aAAa,OAAO,KAAK,IAAI,CAAC,MAAM;GAC1C,MAAMA,SAAkC,EAAE;AAE1C,QAAK,MAAM,OAAO,WAChB,QAAO,OAAO,KAAK,SAAS,IAAI,KAAK;AAGvC,UAAO;;AAGT,SAAO;;;;;;CAOT,MAAM,mBAAkC;EAEtC,MAAM,gBAAgB,MAAM,KAAK,KAAK,YAAY,QAAQ,CAAC,CAAC,KAAK,SAAS,KAAK,OAAO,CAAC;AACvF,QAAM,QAAQ,IAAI,cAAc;AAChC,OAAK,YAAY,OAAO;AACxB,OAAK,UAAU,OAAO;;;AAI1B,IAAa,aAAb,MAAwB;CACtB,AAAiB;CACjB,AAAgB;CAChB,AAAiB;CACjB,AAAiB;CAEjB,YACE,AAAiBC,UACjB,SACA;EAFiB;AAIjB,OAAK,OAAO,QAAQ;AACpB,MAAI,QAAQ,sBAAsB,OAChC,MAAK,oBAAoB,QAAQ;AAKnC,OAAK,aADa,2BAA2B,aAAa,CAC9B,cAAc,QAAQ,MAAM,QAAQ,kBAAkB;AAElF,OAAK,UAAU,KAAK,WAAW,cAAc;GAC3C,MAAM;GACN,QAAQ,YAAqB,KAAK,MAAM,QAAQ;GACjD,CAAC;;;;;;;;;;;CAYJ,gBAAuC;AACrC,SAAO,KAAK;;CAGd,MAAM,QAAuB;AAC3B,QAAM,KAAK,QAAQ,OAAO;AAG1B,QADkB,2BAA2B,aAAa,CAC1C,kBAAkB,KAAK,MAAM,KAAK,kBAAkB;;;;;;CAOtE,aAAa,kCAAiD;AAC5D,QAAM,2BAA2B,aAAa,CAAC,kBAAkB;;CAGnE,MAAc,MAAM,SAAiC;EAYnD,MAAM,kBAVkB,MAAM,QAAQ,WACpC,OAAO,OAAO,KAAK,SAAS,aAAa,EAAE,CAAC,CAAC,KAAK,aAChD,QAAQ,eAAe,SAAS,MAAM,SAAS,MAAM;GACnD,SAAS,SAAS;GAClB,YAAY,SAAS;GACrB,UAAU,SAAS;GACnB,WAAW,SAAS;GACrB,CAAC,CACH,CACF,EACsC,QACpC,WAA4C,OAAO,WAAW,WAChE;AACD,MAAI,eAAe,SAAS,EAC1B,OAAM,IAAI,eACR,eAAe,KAAK,EAAE,aAAa,OAAO,EAC1C,4BACD;EAcH,MAAM,eAVe,MAAM,QAAQ,WACjC,OAAO,OAAO,KAAK,SAAS,UAAU,EAAE,CAAC,CAAC,KAAK,UAC7C,QAAQ,YAAY,MAAM,MAAM;GAC9B,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,YAAY,MAAM;GAClB,WAAW,MAAM;GAClB,CAAC,CACH,CACF,EACgC,QAC9B,WAA4C,OAAO,WAAW,WAChE;AACD,MAAI,YAAY,SAAS,EACvB,OAAM,IAAI,eACR,YAAY,KAAK,EAAE,aAAa,OAAO,EACvC,yBACD;EAuBH,MAAM,iBAnBiB,MAAM,QAAQ,WACnC,OAAO,OAAO,KAAK,SAAS,YAAY,EAAE,CAAC,CAAC,KAAK,YAAY;AAC3D,OAAI,QAAQ,SAAS,QACnB,QAAO,QAAQ,UACb,QAAQ,MAAM,MACd,QAAQ,SAAS,MACjB,QAAQ,cAAc,IACtB,QAAQ,UACT;AAGH,UAAO,QAAQ,aACb,QAAQ,YAAY,MACpB,QAAQ,OAAO,MACf,QAAQ,cAAc,IACtB,QAAQ,UACT;IACD,CACH,EACoC,QAClC,WAA4C,OAAO,WAAW,WAChE;AACD,MAAI,cAAc,SAAS,EACzB,OAAM,IAAI,eACR,cAAc,KAAK,EAAE,aAAa,OAAO,EACzC,2BACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amqp-contract/core",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "Core utilities for AMQP setup and management in amqp-contract",
5
5
  "keywords": [
6
6
  "amqp",
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "amqp-connection-manager": "5.0.0",
45
45
  "amqplib": "0.10.9",
46
- "@amqp-contract/contract": "0.3.5"
46
+ "@amqp-contract/contract": "0.4.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/amqplib": "0.10.8",