@amqp-contract/core 0.4.0 → 0.6.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/README.md CHANGED
@@ -65,6 +65,8 @@ const amqpClient = new AmqpClient(contract, {
65
65
  await amqpClient.close();
66
66
  ```
67
67
 
68
+ For advanced channel configuration options (custom setup, prefetch, publisher confirms), see the [Channel Configuration Guide](https://btravers.github.io/amqp-contract/guide/channel-configuration).
69
+
68
70
  ### Logger Interface
69
71
 
70
72
  The core package exports a `Logger` interface that can be used to implement custom logging for AMQP operations:
package/dist/index.cjs CHANGED
@@ -28,7 +28,7 @@ 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/amqp-client.ts
31
+ //#region src/connection-manager.ts
32
32
  /**
33
33
  * Connection manager singleton for sharing connections across clients
34
34
  */
@@ -98,6 +98,47 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
98
98
  this.refCounts.clear();
99
99
  }
100
100
  };
101
+
102
+ //#endregion
103
+ //#region src/setup.ts
104
+ /**
105
+ * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition
106
+ */
107
+ async function setupAmqpTopology(channel, contract) {
108
+ const exchangeErrors = (await Promise.allSettled(Object.values(contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
109
+ durable: exchange.durable,
110
+ autoDelete: exchange.autoDelete,
111
+ internal: exchange.internal,
112
+ arguments: exchange.arguments
113
+ })))).filter((result) => result.status === "rejected");
114
+ if (exchangeErrors.length > 0) throw new AggregateError(exchangeErrors.map(({ reason }) => reason), "Failed to setup exchanges");
115
+ for (const queue of Object.values(contract.queues ?? {})) if (queue.deadLetter) {
116
+ const dlxName = queue.deadLetter.exchange.name;
117
+ if (!Object.values(contract.exchanges ?? {}).some((exchange) => exchange.name === dlxName)) throw new Error(`Queue "${queue.name}" references dead letter exchange "${dlxName}" which is not declared in the contract. Add the exchange to contract.exchanges to ensure it is created before the queue.`);
118
+ }
119
+ const queueErrors = (await Promise.allSettled(Object.values(contract.queues ?? {}).map((queue) => {
120
+ const queueArguments = { ...queue.arguments };
121
+ if (queue.deadLetter) {
122
+ queueArguments["x-dead-letter-exchange"] = queue.deadLetter.exchange.name;
123
+ if (queue.deadLetter.routingKey) queueArguments["x-dead-letter-routing-key"] = queue.deadLetter.routingKey;
124
+ }
125
+ return channel.assertQueue(queue.name, {
126
+ durable: queue.durable,
127
+ exclusive: queue.exclusive,
128
+ autoDelete: queue.autoDelete,
129
+ arguments: queueArguments
130
+ });
131
+ }))).filter((result) => result.status === "rejected");
132
+ if (queueErrors.length > 0) throw new AggregateError(queueErrors.map(({ reason }) => reason), "Failed to setup queues");
133
+ const bindingErrors = (await Promise.allSettled(Object.values(contract.bindings ?? {}).map((binding) => {
134
+ if (binding.type === "queue") return channel.bindQueue(binding.queue.name, binding.exchange.name, binding.routingKey ?? "", binding.arguments);
135
+ return channel.bindExchange(binding.destination.name, binding.source.name, binding.routingKey ?? "", binding.arguments);
136
+ }))).filter((result) => result.status === "rejected");
137
+ if (bindingErrors.length > 0) throw new AggregateError(bindingErrors.map(({ reason }) => reason), "Failed to setup bindings");
138
+ }
139
+
140
+ //#endregion
141
+ //#region src/amqp-client.ts
101
142
  var AmqpClient = class {
102
143
  connection;
103
144
  channel;
@@ -108,10 +149,24 @@ var AmqpClient = class {
108
149
  this.urls = options.urls;
109
150
  if (options.connectionOptions !== void 0) this.connectionOptions = options.connectionOptions;
110
151
  this.connection = ConnectionManagerSingleton.getInstance().getConnection(options.urls, options.connectionOptions);
111
- this.channel = this.connection.createChannel({
152
+ const defaultSetup = (channel) => setupAmqpTopology(channel, this.contract);
153
+ const { setup: userSetup, ...otherChannelOptions } = options.channelOptions ?? {};
154
+ const channelOpts = {
112
155
  json: true,
113
- setup: (channel) => this.setup(channel)
114
- });
156
+ setup: defaultSetup,
157
+ ...otherChannelOptions
158
+ };
159
+ if (userSetup) channelOpts.setup = async (channel) => {
160
+ await defaultSetup(channel);
161
+ if (userSetup.length === 2) await new Promise((resolve, reject) => {
162
+ userSetup(channel, (error) => {
163
+ if (error) reject(error);
164
+ else resolve();
165
+ });
166
+ });
167
+ else await userSetup(channel);
168
+ };
169
+ this.channel = this.connection.createChannel(channelOpts);
115
170
  }
116
171
  /**
117
172
  * Get the underlying connection manager
@@ -136,28 +191,9 @@ var AmqpClient = class {
136
191
  static async _resetConnectionCacheForTesting() {
137
192
  await ConnectionManagerSingleton.getInstance()._resetForTesting();
138
193
  }
139
- async setup(channel) {
140
- const exchangeErrors = (await Promise.allSettled(Object.values(this.contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
141
- durable: exchange.durable,
142
- autoDelete: exchange.autoDelete,
143
- internal: exchange.internal,
144
- arguments: exchange.arguments
145
- })))).filter((result) => result.status === "rejected");
146
- if (exchangeErrors.length > 0) throw new AggregateError(exchangeErrors.map(({ reason }) => reason), "Failed to setup exchanges");
147
- const queueErrors = (await Promise.allSettled(Object.values(this.contract.queues ?? {}).map((queue) => channel.assertQueue(queue.name, {
148
- durable: queue.durable,
149
- exclusive: queue.exclusive,
150
- autoDelete: queue.autoDelete,
151
- arguments: queue.arguments
152
- })))).filter((result) => result.status === "rejected");
153
- if (queueErrors.length > 0) throw new AggregateError(queueErrors.map(({ reason }) => reason), "Failed to setup queues");
154
- const bindingErrors = (await Promise.allSettled(Object.values(this.contract.bindings ?? {}).map((binding) => {
155
- if (binding.type === "queue") return channel.bindQueue(binding.queue.name, binding.exchange.name, binding.routingKey ?? "", binding.arguments);
156
- return channel.bindExchange(binding.destination.name, binding.source.name, binding.routingKey ?? "", binding.arguments);
157
- }))).filter((result) => result.status === "rejected");
158
- if (bindingErrors.length > 0) throw new AggregateError(bindingErrors.map(({ reason }) => reason), "Failed to setup bindings");
159
- }
160
194
  };
161
195
 
162
196
  //#endregion
163
- exports.AmqpClient = AmqpClient;
197
+ exports.AmqpClient = AmqpClient;
198
+ exports.ConnectionManagerSingleton = ConnectionManagerSingleton;
199
+ exports.setupAmqpTopology = setupAmqpTopology;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { AmqpConnectionManager, AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl } from "amqp-connection-manager";
1
+ import { AmqpConnectionManager, AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
2
2
  import { ContractDefinition } from "@amqp-contract/contract";
3
+ import { Channel } from "amqplib";
3
4
 
4
5
  //#region src/logger.d.ts
5
6
 
@@ -62,6 +63,7 @@ type Logger = {
62
63
  type AmqpClientOptions = {
63
64
  urls: ConnectionUrl[];
64
65
  connectionOptions?: AmqpConnectionManagerOptions | undefined;
66
+ channelOptions?: Partial<CreateChannelOpts> | undefined;
65
67
  };
66
68
  declare class AmqpClient {
67
69
  private readonly contract;
@@ -86,8 +88,41 @@ declare class AmqpClient {
86
88
  * @internal
87
89
  */
88
90
  static _resetConnectionCacheForTesting(): Promise<void>;
89
- private setup;
90
91
  }
91
92
  //#endregion
92
- export { AmqpClient, type AmqpClientOptions, type Logger, type LoggerContext };
93
+ //#region src/connection-manager.d.ts
94
+ /**
95
+ * Connection manager singleton for sharing connections across clients
96
+ */
97
+ declare class ConnectionManagerSingleton {
98
+ private static instance;
99
+ private connections;
100
+ private refCounts;
101
+ private constructor();
102
+ static getInstance(): ConnectionManagerSingleton;
103
+ /**
104
+ * Get or create a connection for the given URLs and options
105
+ */
106
+ getConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): AmqpConnectionManager;
107
+ /**
108
+ * Release a connection reference. If no more references exist, close the connection.
109
+ */
110
+ releaseConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): Promise<void>;
111
+ private createConnectionKey;
112
+ private serializeOptions;
113
+ private deepSort;
114
+ /**
115
+ * Reset all cached connections (for testing purposes)
116
+ * @internal
117
+ */
118
+ _resetForTesting(): Promise<void>;
119
+ }
120
+ //#endregion
121
+ //#region src/setup.d.ts
122
+ /**
123
+ * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition
124
+ */
125
+ declare function setupAmqpTopology(channel: Channel, contract: ContractDefinition): Promise<void>;
126
+ //#endregion
127
+ export { AmqpClient, type AmqpClientOptions, ConnectionManagerSingleton, type Logger, type LoggerContext, setupAmqpTopology };
93
128
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
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"}
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"}
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
- import { AmqpConnectionManager, AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl } from "amqp-connection-manager";
1
+ import { AmqpConnectionManager, AmqpConnectionManagerOptions, ChannelWrapper, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
2
2
  import { ContractDefinition } from "@amqp-contract/contract";
3
+ import { Channel } from "amqplib";
3
4
 
4
5
  //#region src/logger.d.ts
5
6
 
@@ -62,6 +63,7 @@ type Logger = {
62
63
  type AmqpClientOptions = {
63
64
  urls: ConnectionUrl[];
64
65
  connectionOptions?: AmqpConnectionManagerOptions | undefined;
66
+ channelOptions?: Partial<CreateChannelOpts> | undefined;
65
67
  };
66
68
  declare class AmqpClient {
67
69
  private readonly contract;
@@ -86,8 +88,41 @@ declare class AmqpClient {
86
88
  * @internal
87
89
  */
88
90
  static _resetConnectionCacheForTesting(): Promise<void>;
89
- private setup;
90
91
  }
91
92
  //#endregion
92
- export { AmqpClient, type AmqpClientOptions, type Logger, type LoggerContext };
93
+ //#region src/connection-manager.d.ts
94
+ /**
95
+ * Connection manager singleton for sharing connections across clients
96
+ */
97
+ declare class ConnectionManagerSingleton {
98
+ private static instance;
99
+ private connections;
100
+ private refCounts;
101
+ private constructor();
102
+ static getInstance(): ConnectionManagerSingleton;
103
+ /**
104
+ * Get or create a connection for the given URLs and options
105
+ */
106
+ getConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): AmqpConnectionManager;
107
+ /**
108
+ * Release a connection reference. If no more references exist, close the connection.
109
+ */
110
+ releaseConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): Promise<void>;
111
+ private createConnectionKey;
112
+ private serializeOptions;
113
+ private deepSort;
114
+ /**
115
+ * Reset all cached connections (for testing purposes)
116
+ * @internal
117
+ */
118
+ _resetForTesting(): Promise<void>;
119
+ }
120
+ //#endregion
121
+ //#region src/setup.d.ts
122
+ /**
123
+ * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition
124
+ */
125
+ declare function setupAmqpTopology(channel: Channel, contract: ContractDefinition): Promise<void>;
126
+ //#endregion
127
+ export { AmqpClient, type AmqpClientOptions, ConnectionManagerSingleton, type Logger, type LoggerContext, setupAmqpTopology };
93
128
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
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"}
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"}
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import amqp from "amqp-connection-manager";
2
2
 
3
- //#region src/amqp-client.ts
3
+ //#region src/connection-manager.ts
4
4
  /**
5
5
  * Connection manager singleton for sharing connections across clients
6
6
  */
@@ -70,6 +70,47 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
70
70
  this.refCounts.clear();
71
71
  }
72
72
  };
73
+
74
+ //#endregion
75
+ //#region src/setup.ts
76
+ /**
77
+ * Setup AMQP topology (exchanges, queues, and bindings) from a contract definition
78
+ */
79
+ async function setupAmqpTopology(channel, contract) {
80
+ const exchangeErrors = (await Promise.allSettled(Object.values(contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
81
+ durable: exchange.durable,
82
+ autoDelete: exchange.autoDelete,
83
+ internal: exchange.internal,
84
+ arguments: exchange.arguments
85
+ })))).filter((result) => result.status === "rejected");
86
+ if (exchangeErrors.length > 0) throw new AggregateError(exchangeErrors.map(({ reason }) => reason), "Failed to setup exchanges");
87
+ for (const queue of Object.values(contract.queues ?? {})) if (queue.deadLetter) {
88
+ const dlxName = queue.deadLetter.exchange.name;
89
+ if (!Object.values(contract.exchanges ?? {}).some((exchange) => exchange.name === dlxName)) throw new Error(`Queue "${queue.name}" references dead letter exchange "${dlxName}" which is not declared in the contract. Add the exchange to contract.exchanges to ensure it is created before the queue.`);
90
+ }
91
+ const queueErrors = (await Promise.allSettled(Object.values(contract.queues ?? {}).map((queue) => {
92
+ const queueArguments = { ...queue.arguments };
93
+ if (queue.deadLetter) {
94
+ queueArguments["x-dead-letter-exchange"] = queue.deadLetter.exchange.name;
95
+ if (queue.deadLetter.routingKey) queueArguments["x-dead-letter-routing-key"] = queue.deadLetter.routingKey;
96
+ }
97
+ return channel.assertQueue(queue.name, {
98
+ durable: queue.durable,
99
+ exclusive: queue.exclusive,
100
+ autoDelete: queue.autoDelete,
101
+ arguments: queueArguments
102
+ });
103
+ }))).filter((result) => result.status === "rejected");
104
+ if (queueErrors.length > 0) throw new AggregateError(queueErrors.map(({ reason }) => reason), "Failed to setup queues");
105
+ const bindingErrors = (await Promise.allSettled(Object.values(contract.bindings ?? {}).map((binding) => {
106
+ if (binding.type === "queue") return channel.bindQueue(binding.queue.name, binding.exchange.name, binding.routingKey ?? "", binding.arguments);
107
+ return channel.bindExchange(binding.destination.name, binding.source.name, binding.routingKey ?? "", binding.arguments);
108
+ }))).filter((result) => result.status === "rejected");
109
+ if (bindingErrors.length > 0) throw new AggregateError(bindingErrors.map(({ reason }) => reason), "Failed to setup bindings");
110
+ }
111
+
112
+ //#endregion
113
+ //#region src/amqp-client.ts
73
114
  var AmqpClient = class {
74
115
  connection;
75
116
  channel;
@@ -80,10 +121,24 @@ var AmqpClient = class {
80
121
  this.urls = options.urls;
81
122
  if (options.connectionOptions !== void 0) this.connectionOptions = options.connectionOptions;
82
123
  this.connection = ConnectionManagerSingleton.getInstance().getConnection(options.urls, options.connectionOptions);
83
- this.channel = this.connection.createChannel({
124
+ const defaultSetup = (channel) => setupAmqpTopology(channel, this.contract);
125
+ const { setup: userSetup, ...otherChannelOptions } = options.channelOptions ?? {};
126
+ const channelOpts = {
84
127
  json: true,
85
- setup: (channel) => this.setup(channel)
86
- });
128
+ setup: defaultSetup,
129
+ ...otherChannelOptions
130
+ };
131
+ if (userSetup) channelOpts.setup = async (channel) => {
132
+ await defaultSetup(channel);
133
+ if (userSetup.length === 2) await new Promise((resolve, reject) => {
134
+ userSetup(channel, (error) => {
135
+ if (error) reject(error);
136
+ else resolve();
137
+ });
138
+ });
139
+ else await userSetup(channel);
140
+ };
141
+ this.channel = this.connection.createChannel(channelOpts);
87
142
  }
88
143
  /**
89
144
  * Get the underlying connection manager
@@ -108,29 +163,8 @@ var AmqpClient = class {
108
163
  static async _resetConnectionCacheForTesting() {
109
164
  await ConnectionManagerSingleton.getInstance()._resetForTesting();
110
165
  }
111
- async setup(channel) {
112
- const exchangeErrors = (await Promise.allSettled(Object.values(this.contract.exchanges ?? {}).map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
113
- durable: exchange.durable,
114
- autoDelete: exchange.autoDelete,
115
- internal: exchange.internal,
116
- arguments: exchange.arguments
117
- })))).filter((result) => result.status === "rejected");
118
- if (exchangeErrors.length > 0) throw new AggregateError(exchangeErrors.map(({ reason }) => reason), "Failed to setup exchanges");
119
- const queueErrors = (await Promise.allSettled(Object.values(this.contract.queues ?? {}).map((queue) => channel.assertQueue(queue.name, {
120
- durable: queue.durable,
121
- exclusive: queue.exclusive,
122
- autoDelete: queue.autoDelete,
123
- arguments: queue.arguments
124
- })))).filter((result) => result.status === "rejected");
125
- if (queueErrors.length > 0) throw new AggregateError(queueErrors.map(({ reason }) => reason), "Failed to setup queues");
126
- const bindingErrors = (await Promise.allSettled(Object.values(this.contract.bindings ?? {}).map((binding) => {
127
- if (binding.type === "queue") return channel.bindQueue(binding.queue.name, binding.exchange.name, binding.routingKey ?? "", binding.arguments);
128
- return channel.bindExchange(binding.destination.name, binding.source.name, binding.routingKey ?? "", binding.arguments);
129
- }))).filter((result) => result.status === "rejected");
130
- if (bindingErrors.length > 0) throw new AggregateError(bindingErrors.map(({ reason }) => reason), "Failed to setup bindings");
131
- }
132
166
  };
133
167
 
134
168
  //#endregion
135
- export { AmqpClient };
169
+ export { AmqpClient, ConnectionManagerSingleton, setupAmqpTopology };
136
170
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
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":"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"}
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amqp-contract/core",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Core utilities for AMQP setup and management in amqp-contract",
5
5
  "keywords": [
6
6
  "amqp",
@@ -43,21 +43,26 @@
43
43
  "dependencies": {
44
44
  "amqp-connection-manager": "5.0.0",
45
45
  "amqplib": "0.10.9",
46
- "@amqp-contract/contract": "0.4.0"
46
+ "@amqp-contract/contract": "0.6.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/amqplib": "0.10.8",
50
50
  "@vitest/coverage-v8": "4.0.16",
51
- "tsdown": "0.18.2",
51
+ "tsdown": "0.18.3",
52
+ "typedoc": "0.28.15",
52
53
  "typescript": "5.9.3",
53
54
  "vitest": "4.0.16",
54
- "@amqp-contract/tsconfig": "0.0.0"
55
+ "@amqp-contract/testing": "0.6.0",
56
+ "@amqp-contract/tsconfig": "0.0.0",
57
+ "@amqp-contract/typedoc": "0.0.1"
55
58
  },
56
59
  "scripts": {
57
60
  "build": "tsdown src/index.ts --format cjs,esm --dts --clean",
61
+ "build:docs": "typedoc",
58
62
  "dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
59
- "test": "vitest run",
60
- "test:watch": "vitest",
63
+ "test": "vitest run --project unit",
64
+ "test:integration": "vitest run --project integration",
65
+ "test:watch": "vitest --project unit",
61
66
  "typecheck": "tsc --noEmit"
62
67
  }
63
68
  }