@amqp-contract/client 0.2.0 → 0.3.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
@@ -1,6 +1,12 @@
1
1
  # @amqp-contract/client
2
2
 
3
- Type-safe AMQP client for publishing messages using amqp-contract with explicit error handling via `Result` types.
3
+ **Type-safe AMQP client for publishing messages using amqp-contract with explicit error handling via `Result` types.**
4
+
5
+ [![CI](https://github.com/btravers/amqp-contract/actions/workflows/ci.yml/badge.svg)](https://github.com/btravers/amqp-contract/actions/workflows/ci.yml)
6
+ [![npm version](https://img.shields.io/npm/v/@amqp-contract/client.svg?logo=npm)](https://www.npmjs.com/package/@amqp-contract/client)
7
+ [![npm downloads](https://img.shields.io/npm/dm/@amqp-contract/client.svg)](https://www.npmjs.com/package/@amqp-contract/client)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?logo=typescript)](https://www.typescriptlang.org/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
10
 
5
11
  📖 **[Full documentation →](https://btravers.github.io/amqp-contract/api/client)**
6
12
 
@@ -16,14 +22,22 @@ pnpm add @amqp-contract/client
16
22
  import { TypedAmqpClient } from '@amqp-contract/client';
17
23
  import { contract } from './contract';
18
24
 
19
- // Create client from contract (automatically connects)
20
- const client = await TypedAmqpClient.create({
25
+ // Create client from contract (automatically connects and waits for connection)
26
+ const clientResult = await TypedAmqpClient.create({
21
27
  contract,
22
- connection: 'amqp://localhost'
28
+ urls: ['amqp://localhost']
23
29
  });
24
30
 
31
+ // Handle connection errors
32
+ if (clientResult.isError()) {
33
+ console.error('Failed to create client:', clientResult.error);
34
+ throw clientResult.error; // or handle appropriately
35
+ }
36
+
37
+ const client = clientResult.get();
38
+
25
39
  // Publish message with explicit error handling
26
- const result = client.publish('orderCreated', {
40
+ const result = await client.publish('orderCreated', {
27
41
  orderId: 'ORD-123',
28
42
  amount: 99.99,
29
43
  });
@@ -60,16 +74,31 @@ publish(): Result<boolean, TechnicalError | MessageValidationError>
60
74
 
61
75
  ### `TypedAmqpClient.create(options)`
62
76
 
63
- Create a type-safe AMQP client from a contract. Automatically connects to RabbitMQ.
77
+ Create a type-safe AMQP client from a contract. Automatically connects to RabbitMQ and waits for the connection to be ready.
64
78
 
65
79
  **Parameters:**
66
80
 
67
81
  - `options.contract` - Contract definition
68
- - `options.connection` - AMQP connection URL (string) or connection options (Options.Connect)
82
+ - `options.urls` - Array of AMQP connection URLs (e.g., `['amqp://localhost']`)
83
+ - `options.connectionOptions` - Optional connection manager options
69
84
 
70
- **Returns:** `Promise<TypedAmqpClient>`
85
+ **Returns:** `Future<Result<TypedAmqpClient, TechnicalError>>`
71
86
 
72
- **Throws:** Connection errors (programming errors)
87
+ The method returns a Future that resolves to a Result. You must:
88
+
89
+ 1. Await the Future to get the Result
90
+ 2. Check if the Result is Ok or Error
91
+ 3. Extract the client using `.get()` if successful
92
+
93
+ **Example:**
94
+
95
+ ```typescript
96
+ const clientResult = await TypedAmqpClient.create({ contract, urls: ['amqp://localhost'] });
97
+ if (clientResult.isError()) {
98
+ throw clientResult.error; // Handle connection error
99
+ }
100
+ const client = clientResult.get();
101
+ ```
73
102
 
74
103
  ### `TypedAmqpClient.publish(publisherName, message, options?)`
75
104
 
@@ -107,6 +136,10 @@ Close the channel and connection.
107
136
 
108
137
  **Returns:** `Promise<void>`
109
138
 
139
+ ## Documentation
140
+
141
+ 📖 **[Read the full documentation →](https://btravers.github.io/amqp-contract)**
142
+
110
143
  ## License
111
144
 
112
145
  MIT
package/dist/index.cjs CHANGED
@@ -1,6 +1,5 @@
1
- let amqplib = require("amqplib");
2
- let _amqp_contract_core = require("@amqp-contract/core");
3
1
  let _swan_io_boxed = require("@swan-io/boxed");
2
+ let _amqp_contract_core = require("@amqp-contract/core");
4
3
 
5
4
  //#region src/errors.ts
6
5
  /**
@@ -43,61 +42,56 @@ var MessageValidationError = class extends ClientError {
43
42
  * Type-safe AMQP client for publishing messages
44
43
  */
45
44
  var TypedAmqpClient = class TypedAmqpClient {
46
- channel = null;
47
- connection = null;
48
- constructor(contract, connectionOptions) {
45
+ constructor(contract, amqpClient) {
49
46
  this.contract = contract;
50
- this.connectionOptions = connectionOptions;
47
+ this.amqpClient = amqpClient;
51
48
  }
52
49
  /**
53
- * Create a type-safe AMQP client from a contract
54
- * The client will automatically connect to the AMQP broker
50
+ * Create a type-safe AMQP client from a contract.
51
+ *
52
+ * Connection management (including automatic reconnection) is handled internally
53
+ * by amqp-connection-manager via the {@link AmqpClient}. The client establishes
54
+ * infrastructure asynchronously in the background once the connection is ready.
55
55
  */
56
- static async create(options) {
57
- const client = new TypedAmqpClient(options.contract, options.connection);
58
- await client.init();
59
- return client;
56
+ static create({ contract, urls, connectionOptions }) {
57
+ const client = new TypedAmqpClient(contract, new _amqp_contract_core.AmqpClient(contract, {
58
+ urls,
59
+ connectionOptions
60
+ }));
61
+ return client.waitForConnectionReady().mapOk(() => client);
60
62
  }
61
63
  /**
62
64
  * Publish a message using a defined publisher
63
65
  * Returns Result.Ok(true) on success, or Result.Error with specific error on failure
64
66
  */
65
67
  publish(publisherName, message, options) {
66
- if (!this.channel) throw new Error("Client not initialized. Create the client using TypedAmqpClient.create() to establish a connection.");
67
68
  const publishers = this.contract.publishers;
68
- if (!publishers) throw new Error("No publishers defined in contract");
69
+ if (!publishers) return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(new TechnicalError("No publishers defined in contract")));
69
70
  const publisher = publishers[publisherName];
70
- if (!publisher || typeof publisher !== "object") throw new Error(`Publisher "${String(publisherName)}" not found in contract`);
71
- const publisherDef = publisher;
72
- const validation = publisherDef.message.payload["~standard"].validate(message);
73
- if (typeof validation === "object" && validation !== null && "issues" in validation && validation.issues) return _swan_io_boxed.Result.Error(new MessageValidationError(String(publisherName), validation.issues));
74
- const validatedMessage = typeof validation === "object" && validation !== null && "value" in validation ? validation.value : message;
75
- const routingKey = options?.routingKey ?? publisherDef.routingKey ?? "";
76
- const content = Buffer.from(JSON.stringify(validatedMessage));
77
- const published = this.channel.publish(publisherDef.exchange.name, routingKey, content, options?.options);
78
- if (!published) return _swan_io_boxed.Result.Error(new TechnicalError(`Failed to publish message for publisher "${String(publisherName)}": Channel rejected the message (buffer full or other channel issue)`));
79
- return _swan_io_boxed.Result.Ok(published);
71
+ if (!publisher) return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(new TechnicalError(`Publisher "${String(publisherName)}" not found in contract`)));
72
+ const validateMessage = () => {
73
+ const validationResult = publisher.message.payload["~standard"].validate(message);
74
+ return _swan_io_boxed.Future.fromPromise(validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult)).mapError((error) => new TechnicalError(`Validation failed`, error)).mapOkToResult((validation) => {
75
+ if (validation.issues) return _swan_io_boxed.Result.Error(new MessageValidationError(String(publisherName), validation.issues));
76
+ return _swan_io_boxed.Result.Ok(validation.value);
77
+ });
78
+ };
79
+ const publishMessage = (validatedMessage) => {
80
+ return _swan_io_boxed.Future.fromPromise(this.amqpClient.channel.publish(publisher.exchange.name, publisher.routingKey ?? "", validatedMessage, options)).mapError((error) => new TechnicalError(`Failed to publish message`, error)).mapOkToResult((published) => {
81
+ if (!published) return _swan_io_boxed.Result.Error(new TechnicalError(`Failed to publish message for publisher "${String(publisherName)}": Channel rejected the message (buffer full or other channel issue)`));
82
+ return _swan_io_boxed.Result.Ok(published);
83
+ });
84
+ };
85
+ return validateMessage().flatMapOk((validatedMessage) => publishMessage(validatedMessage));
80
86
  }
81
87
  /**
82
88
  * Close the channel and connection
83
89
  */
84
- async close() {
85
- if (this.channel) {
86
- await this.channel.close();
87
- this.channel = null;
88
- }
89
- if (this.connection) {
90
- await this.connection.close();
91
- this.connection = null;
92
- }
90
+ close() {
91
+ return _swan_io_boxed.Future.fromPromise(this.amqpClient.close()).mapError((error) => new TechnicalError("Failed to close AMQP connection", error)).mapOk(() => void 0);
93
92
  }
94
- /**
95
- * Connect to AMQP broker
96
- */
97
- async init() {
98
- this.connection = await (0, amqplib.connect)(this.connectionOptions);
99
- this.channel = await this.connection.createChannel();
100
- await (0, _amqp_contract_core.setupInfra)(this.channel, this.contract);
93
+ waitForConnectionReady() {
94
+ return _swan_io_boxed.Future.fromPromise(this.amqpClient.channel.waitForConnect()).mapError((error) => new TechnicalError("Failed to wait for connection ready", error));
101
95
  }
102
96
  };
103
97
 
package/dist/index.d.cts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Options } from "amqplib";
2
- import { ClientInferPublisherInput, ContractDefinition, InferPublisherNames } from "@amqp-contract/contract";
3
- import { Result } from "@swan-io/boxed";
2
+ import { ContractDefinition, InferPublisherNames, PublisherDefinition } from "@amqp-contract/contract";
3
+ import { Future, Result } from "@swan-io/boxed";
4
+ import { StandardSchemaV1 } from "@standard-schema/spec";
5
+ import { AmqpConnectionManagerOptions, ConnectionUrl } from "amqp-connection-manager";
4
6
 
5
7
  //#region src/errors.d.ts
6
8
  /**
@@ -26,49 +28,67 @@ declare class MessageValidationError extends ClientError {
26
28
  constructor(publisherName: string, issues: unknown);
27
29
  }
28
30
  //#endregion
31
+ //#region src/types.d.ts
32
+ /**
33
+ * Infer the TypeScript type from a schema
34
+ */
35
+ type InferSchemaInput<TSchema extends StandardSchemaV1> = TSchema extends StandardSchemaV1<infer TInput> ? TInput : never;
36
+ /**
37
+ * Infer publisher message input type
38
+ */
39
+ type PublisherInferInput<TPublisher extends PublisherDefinition> = InferSchemaInput<TPublisher["message"]["payload"]>;
40
+ /**
41
+ * Infer all publishers from contract
42
+ */
43
+ type InferPublishers<TContract extends ContractDefinition> = NonNullable<TContract["publishers"]>;
44
+ /**
45
+ * Get specific publisher definition from contract
46
+ */
47
+ type InferPublisher<TContract extends ContractDefinition, TName extends InferPublisherNames<TContract>> = InferPublishers<TContract>[TName];
48
+ /**
49
+ * Infer publisher input type (message payload) for a specific publisher in a contract
50
+ */
51
+ type ClientInferPublisherInput<TContract extends ContractDefinition, TName extends InferPublisherNames<TContract>> = PublisherInferInput<InferPublisher<TContract, TName>>;
52
+ //#endregion
29
53
  //#region src/client.d.ts
30
54
  /**
31
55
  * Options for creating a client
32
56
  */
33
- interface CreateClientOptions<TContract extends ContractDefinition> {
57
+ type CreateClientOptions<TContract extends ContractDefinition> = {
34
58
  contract: TContract;
35
- connection: string | Options.Connect;
36
- }
37
- /**
38
- * Options for publishing a message
39
- */
40
- interface PublishOptions {
41
- routingKey?: string;
42
- options?: Options.Publish;
43
- }
59
+ urls: ConnectionUrl[];
60
+ connectionOptions?: AmqpConnectionManagerOptions | undefined;
61
+ };
44
62
  /**
45
63
  * Type-safe AMQP client for publishing messages
46
64
  */
47
65
  declare class TypedAmqpClient<TContract extends ContractDefinition> {
48
66
  private readonly contract;
49
- private readonly connectionOptions;
50
- private channel;
51
- private connection;
67
+ private readonly amqpClient;
52
68
  private constructor();
53
69
  /**
54
- * Create a type-safe AMQP client from a contract
55
- * The client will automatically connect to the AMQP broker
70
+ * Create a type-safe AMQP client from a contract.
71
+ *
72
+ * Connection management (including automatic reconnection) is handled internally
73
+ * by amqp-connection-manager via the {@link AmqpClient}. The client establishes
74
+ * infrastructure asynchronously in the background once the connection is ready.
56
75
  */
57
- static create<TContract extends ContractDefinition>(options: CreateClientOptions<TContract>): Promise<TypedAmqpClient<TContract>>;
76
+ static create<TContract extends ContractDefinition>({
77
+ contract,
78
+ urls,
79
+ connectionOptions
80
+ }: CreateClientOptions<TContract>): Future<Result<TypedAmqpClient<TContract>, TechnicalError>>;
58
81
  /**
59
82
  * Publish a message using a defined publisher
60
83
  * Returns Result.Ok(true) on success, or Result.Error with specific error on failure
61
84
  */
62
- publish<TName extends InferPublisherNames<TContract>>(publisherName: TName, message: ClientInferPublisherInput<TContract, TName>, options?: PublishOptions): Result<boolean, TechnicalError | MessageValidationError>;
85
+ publish<TName extends InferPublisherNames<TContract>>(publisherName: TName, message: ClientInferPublisherInput<TContract, TName>, options?: Options.Publish): Future<Result<boolean, TechnicalError | MessageValidationError>>;
63
86
  /**
64
87
  * Close the channel and connection
65
88
  */
66
- close(): Promise<void>;
67
- /**
68
- * Connect to AMQP broker
69
- */
70
- private init;
89
+ close(): Future<Result<void, TechnicalError>>;
90
+ private waitForConnectionReady;
71
91
  }
72
92
  //#endregion
73
- export { type CreateClientOptions, MessageValidationError, type PublishOptions, TechnicalError, TypedAmqpClient };
93
+ export { type ClientInferPublisherInput, type CreateClientOptions, MessageValidationError, TechnicalError, TypedAmqpClient };
74
94
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":[],"mappings":";;;;;;;;uBAGe,WAAA,SAAoB,KAAA;;;AAkBnC;AAaA;;;cAba,cAAA,SAAuB,WAAA;ECPnB,SAAA,KAAA,CAAA,EAAA,OAAmB,GAAA,SAAA;EAAmB,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,OAAA,GAAA,SAAA;;;;AAQvD;AAQa,cDIA,sBAAA,SAA+B,WAAA,CCJhB;EAAmB,SAAA,aAAA,EAAA,MAAA;EAaP,SAAA,MAAA,EAAA,OAAA;EACP,WAAA,CAAA,aAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA;;;;;;ADvBjC;AAaa,UCpBI,mBDoBmB,CAAA,kBCpBmB,kBDoBA,CAAA,CAAA;YCnB3C;uBACW,OAAA,CAAQ;;AAF/B;;;AAEuB,UAMN,cAAA,CANc;EAAO,UAAA,CAAA,EAAA,MAAA;EAMrB,OAAA,CAAA,EAEL,OAAA,CAAQ,OAFW;AAQ/B;;;;AAca,cAdA,eAcA,CAAA,kBAdkC,kBAclC,CAAA,CAAA;EACgB,iBAAA,QAAA;EAAhB,iBAAA,iBAAA;EAAR,QAAA,OAAA;EAUuC,QAAA,UAAA;EAApB,QAAA,WAAA,CAAA;EACL;;;;EAEL,OAAA,MAAA,CAAA,kBAf0B,kBAe1B,CAAA,CAAA,OAAA,EAdD,mBAcC,CAdmB,SAcnB,CAAA,CAAA,EAbT,OAaS,CAbD,eAaC,CAbe,SAaf,CAAA,CAAA;EACO;;;;EAgEG,OAAA,CAAA,cApEA,mBAoEA,CApEoB,SAoEpB,CAAA,CAAA,CAAA,aAAA,EAnEL,KAmEK,EAAA,OAAA,EAlEX,yBAkEW,CAlEe,SAkEf,EAlE0B,KAkE1B,CAAA,EAAA,OAAA,CAAA,EAjEV,cAiEU,CAAA,EAhEnB,MAgEmB,CAAA,OAAA,EAhEH,cAgEG,GAhEc,sBAgEd,CAAA;;;;WAAP"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;uBAGe,WAAA,SAAoB,KAAA;;;;;AAkBnC;AAaA;cAba,cAAA,SAAuB,WAAA;;;AChBH;;;;AAMC,cDuBrB,sBAAA,SAA+B,WAAA,CCvBV;EAK7B,SAAA,aAAmB,EAAA,MAAA;EAAoB,SAAA,MAAA,EAAA,OAAA;EAC1C,WAAA,CAAA,aAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA;;;;;;;KAPG,iCAAiC,oBACpC,gBAAgB;;;ADUlB;AAaA,KClBK,mBDkBQ,CAAA,mBClB+B,mBDkBW,CAAA,GClBY,gBDkBZ,CCjBrD,UDiBqD,CAAA,SAAA,CAAA,CAAA,SAAA,CAAA,CAAA;;;;AC7BtB,KAkB5B,eAbA,CAAA,kBAakC,kBAblB,CAAA,GAawC,WAbxC,CAaoD,SAbpD,CAAA,YAAA,CAAA,CAAA;;;;KAkBhB,cAjB6B,CAAA,kBAkBd,kBAlBc,EAAA,cAmBlB,mBAnBkB,CAmBE,SAnBF,CAAA,CAAA,GAoB9B,eApB8B,CAoBd,SApBc,CAAA,CAoBH,KApBG,CAAA;AAAA;;;AAKiC,KAoBvD,yBApBuD,CAAA,kBAqB/C,kBArB+C,EAAA,cAsBnD,mBAtBmD,CAsB/B,SAtB+B,CAAA,CAAA,GAuB/D,mBAvB+D,CAuB3C,cAvB2C,CAuB5B,SAvB4B,EAuBjB,KAvBiB,CAAA,CAAA;;;;;ADKnE;AAaa,KEvBD,mBFuBwB,CAAA,kBEvBc,kBFuBK,CAAA,GAAA;YEtB3C;QACJ;sBACc;ADTW,CAAA;;;;AAMC,cCSrB,eDTqB,CAAA,kBCSa,kBDTb,CAAA,CAAA;EAK7B,iBAAA,QAAmB;EAAoB,iBAAA,UAAA;EAC1C,QAAA,WAAA,CAAA;EADiE;;AAAgB;;;;;EAY9E,OAAA,MAAA,CAAA,kBCK6B,kBDLf,CAAA,CAAA;IAAA,QAAA;IAAA,IAAA;IAAA;EAAA,CAAA,ECSd,mBDTc,CCSM,SDTN,CAAA,CAAA,ECSmB,MDTnB,CCS0B,MDT1B,CCSiC,eDTjC,CCSiD,SDTjD,CAAA,ECS6D,cDT7D,CAAA,CAAA;EACC;;;;EAEhB,OAAA,CAAA,cCmBoB,mBDnBpB,CCmBwC,SDnBxC,CAAA,CAAA,CAAA,aAAA,ECoBe,KDpBf,EAAA,OAAA,ECqBS,yBDrBT,CCqBmC,SDrBnC,ECqB8C,KDrB9C,CAAA,EAAA,OAAA,CAAA,ECsBU,OAAA,CAAQ,ODtBlB,CAAA,ECuBC,MDvBD,CCuBQ,MDvBR,CAAA,OAAA,ECuBwB,cDvBxB,GCuByC,sBDvBzC,CAAA,CAAA;EAA2B;;AAK/B;EACoB,KAAA,CAAA,CAAA,EC+ET,MD/ES,CC+EF,MD/EE,CAAA,IAAA,EC+EW,cD/EX,CAAA,CAAA;EACgB,QAAA,sBAAA"}
package/dist/index.d.mts CHANGED
@@ -1,6 +1,8 @@
1
+ import { Future, Result } from "@swan-io/boxed";
1
2
  import { Options } from "amqplib";
2
- import { Result } from "@swan-io/boxed";
3
- import { ClientInferPublisherInput, ContractDefinition, InferPublisherNames } from "@amqp-contract/contract";
3
+ import { ContractDefinition, InferPublisherNames, PublisherDefinition } from "@amqp-contract/contract";
4
+ import { StandardSchemaV1 } from "@standard-schema/spec";
5
+ import { AmqpConnectionManagerOptions, ConnectionUrl } from "amqp-connection-manager";
4
6
 
5
7
  //#region src/errors.d.ts
6
8
  /**
@@ -26,49 +28,67 @@ declare class MessageValidationError extends ClientError {
26
28
  constructor(publisherName: string, issues: unknown);
27
29
  }
28
30
  //#endregion
31
+ //#region src/types.d.ts
32
+ /**
33
+ * Infer the TypeScript type from a schema
34
+ */
35
+ type InferSchemaInput<TSchema extends StandardSchemaV1> = TSchema extends StandardSchemaV1<infer TInput> ? TInput : never;
36
+ /**
37
+ * Infer publisher message input type
38
+ */
39
+ type PublisherInferInput<TPublisher extends PublisherDefinition> = InferSchemaInput<TPublisher["message"]["payload"]>;
40
+ /**
41
+ * Infer all publishers from contract
42
+ */
43
+ type InferPublishers<TContract extends ContractDefinition> = NonNullable<TContract["publishers"]>;
44
+ /**
45
+ * Get specific publisher definition from contract
46
+ */
47
+ type InferPublisher<TContract extends ContractDefinition, TName extends InferPublisherNames<TContract>> = InferPublishers<TContract>[TName];
48
+ /**
49
+ * Infer publisher input type (message payload) for a specific publisher in a contract
50
+ */
51
+ type ClientInferPublisherInput<TContract extends ContractDefinition, TName extends InferPublisherNames<TContract>> = PublisherInferInput<InferPublisher<TContract, TName>>;
52
+ //#endregion
29
53
  //#region src/client.d.ts
30
54
  /**
31
55
  * Options for creating a client
32
56
  */
33
- interface CreateClientOptions<TContract extends ContractDefinition> {
57
+ type CreateClientOptions<TContract extends ContractDefinition> = {
34
58
  contract: TContract;
35
- connection: string | Options.Connect;
36
- }
37
- /**
38
- * Options for publishing a message
39
- */
40
- interface PublishOptions {
41
- routingKey?: string;
42
- options?: Options.Publish;
43
- }
59
+ urls: ConnectionUrl[];
60
+ connectionOptions?: AmqpConnectionManagerOptions | undefined;
61
+ };
44
62
  /**
45
63
  * Type-safe AMQP client for publishing messages
46
64
  */
47
65
  declare class TypedAmqpClient<TContract extends ContractDefinition> {
48
66
  private readonly contract;
49
- private readonly connectionOptions;
50
- private channel;
51
- private connection;
67
+ private readonly amqpClient;
52
68
  private constructor();
53
69
  /**
54
- * Create a type-safe AMQP client from a contract
55
- * The client will automatically connect to the AMQP broker
70
+ * Create a type-safe AMQP client from a contract.
71
+ *
72
+ * Connection management (including automatic reconnection) is handled internally
73
+ * by amqp-connection-manager via the {@link AmqpClient}. The client establishes
74
+ * infrastructure asynchronously in the background once the connection is ready.
56
75
  */
57
- static create<TContract extends ContractDefinition>(options: CreateClientOptions<TContract>): Promise<TypedAmqpClient<TContract>>;
76
+ static create<TContract extends ContractDefinition>({
77
+ contract,
78
+ urls,
79
+ connectionOptions
80
+ }: CreateClientOptions<TContract>): Future<Result<TypedAmqpClient<TContract>, TechnicalError>>;
58
81
  /**
59
82
  * Publish a message using a defined publisher
60
83
  * Returns Result.Ok(true) on success, or Result.Error with specific error on failure
61
84
  */
62
- publish<TName extends InferPublisherNames<TContract>>(publisherName: TName, message: ClientInferPublisherInput<TContract, TName>, options?: PublishOptions): Result<boolean, TechnicalError | MessageValidationError>;
85
+ publish<TName extends InferPublisherNames<TContract>>(publisherName: TName, message: ClientInferPublisherInput<TContract, TName>, options?: Options.Publish): Future<Result<boolean, TechnicalError | MessageValidationError>>;
63
86
  /**
64
87
  * Close the channel and connection
65
88
  */
66
- close(): Promise<void>;
67
- /**
68
- * Connect to AMQP broker
69
- */
70
- private init;
89
+ close(): Future<Result<void, TechnicalError>>;
90
+ private waitForConnectionReady;
71
91
  }
72
92
  //#endregion
73
- export { type CreateClientOptions, MessageValidationError, type PublishOptions, TechnicalError, TypedAmqpClient };
93
+ export { type ClientInferPublisherInput, type CreateClientOptions, MessageValidationError, TechnicalError, TypedAmqpClient };
74
94
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":[],"mappings":";;;;;;;;uBAGe,WAAA,SAAoB,KAAA;;;AAkBnC;AAaA;;;cAba,cAAA,SAAuB,WAAA;ECPnB,SAAA,KAAA,CAAA,EAAA,OAAmB,GAAA,SAAA;EAAmB,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,OAAA,GAAA,SAAA;;;;AAQvD;AAQa,cDIA,sBAAA,SAA+B,WAAA,CCJhB;EAAmB,SAAA,aAAA,EAAA,MAAA;EAaP,SAAA,MAAA,EAAA,OAAA;EACP,WAAA,CAAA,aAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA;;;;;;ADvBjC;AAaa,UCpBI,mBDoBmB,CAAA,kBCpBmB,kBDoBA,CAAA,CAAA;YCnB3C;uBACW,OAAA,CAAQ;;AAF/B;;;AAEuB,UAMN,cAAA,CANc;EAAO,UAAA,CAAA,EAAA,MAAA;EAMrB,OAAA,CAAA,EAEL,OAAA,CAAQ,OAFW;AAQ/B;;;;AAca,cAdA,eAcA,CAAA,kBAdkC,kBAclC,CAAA,CAAA;EACgB,iBAAA,QAAA;EAAhB,iBAAA,iBAAA;EAAR,QAAA,OAAA;EAUuC,QAAA,UAAA;EAApB,QAAA,WAAA,CAAA;EACL;;;;EAEL,OAAA,MAAA,CAAA,kBAf0B,kBAe1B,CAAA,CAAA,OAAA,EAdD,mBAcC,CAdmB,SAcnB,CAAA,CAAA,EAbT,OAaS,CAbD,eAaC,CAbe,SAaf,CAAA,CAAA;EACO;;;;EAgEG,OAAA,CAAA,cApEA,mBAoEA,CApEoB,SAoEpB,CAAA,CAAA,CAAA,aAAA,EAnEL,KAmEK,EAAA,OAAA,EAlEX,yBAkEW,CAlEe,SAkEf,EAlE0B,KAkE1B,CAAA,EAAA,OAAA,CAAA,EAjEV,cAiEU,CAAA,EAhEnB,MAgEmB,CAAA,OAAA,EAhEH,cAgEG,GAhEc,sBAgEd,CAAA;;;;WAAP"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;uBAGe,WAAA,SAAoB,KAAA;;;;;AAkBnC;AAaA;cAba,cAAA,SAAuB,WAAA;;;AChBH;;;;AAMC,cDuBrB,sBAAA,SAA+B,WAAA,CCvBV;EAK7B,SAAA,aAAmB,EAAA,MAAA;EAAoB,SAAA,MAAA,EAAA,OAAA;EAC1C,WAAA,CAAA,aAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA;;;;;;;KAPG,iCAAiC,oBACpC,gBAAgB;;;ADUlB;AAaA,KClBK,mBDkBQ,CAAA,mBClB+B,mBDkBW,CAAA,GClBY,gBDkBZ,CCjBrD,UDiBqD,CAAA,SAAA,CAAA,CAAA,SAAA,CAAA,CAAA;;;;AC7BtB,KAkB5B,eAbA,CAAA,kBAakC,kBAblB,CAAA,GAawC,WAbxC,CAaoD,SAbpD,CAAA,YAAA,CAAA,CAAA;;;;KAkBhB,cAjB6B,CAAA,kBAkBd,kBAlBc,EAAA,cAmBlB,mBAnBkB,CAmBE,SAnBF,CAAA,CAAA,GAoB9B,eApB8B,CAoBd,SApBc,CAAA,CAoBH,KApBG,CAAA;AAAA;;;AAKiC,KAoBvD,yBApBuD,CAAA,kBAqB/C,kBArB+C,EAAA,cAsBnD,mBAtBmD,CAsB/B,SAtB+B,CAAA,CAAA,GAuB/D,mBAvB+D,CAuB3C,cAvB2C,CAuB5B,SAvB4B,EAuBjB,KAvBiB,CAAA,CAAA;;;;;ADKnE;AAaa,KEvBD,mBFuBwB,CAAA,kBEvBc,kBFuBK,CAAA,GAAA;YEtB3C;QACJ;sBACc;ADTW,CAAA;;;;AAMC,cCSrB,eDTqB,CAAA,kBCSa,kBDTb,CAAA,CAAA;EAK7B,iBAAA,QAAmB;EAAoB,iBAAA,UAAA;EAC1C,QAAA,WAAA,CAAA;EADiE;;AAAgB;;;;;EAY9E,OAAA,MAAA,CAAA,kBCK6B,kBDLf,CAAA,CAAA;IAAA,QAAA;IAAA,IAAA;IAAA;EAAA,CAAA,ECSd,mBDTc,CCSM,SDTN,CAAA,CAAA,ECSmB,MDTnB,CCS0B,MDT1B,CCSiC,eDTjC,CCSiD,SDTjD,CAAA,ECS6D,cDT7D,CAAA,CAAA;EACC;;;;EAEhB,OAAA,CAAA,cCmBoB,mBDnBpB,CCmBwC,SDnBxC,CAAA,CAAA,CAAA,aAAA,ECoBe,KDpBf,EAAA,OAAA,ECqBS,yBDrBT,CCqBmC,SDrBnC,ECqB8C,KDrB9C,CAAA,EAAA,OAAA,CAAA,ECsBU,OAAA,CAAQ,ODtBlB,CAAA,ECuBC,MDvBD,CCuBQ,MDvBR,CAAA,OAAA,ECuBwB,cDvBxB,GCuByC,sBDvBzC,CAAA,CAAA;EAA2B;;AAK/B;EACoB,KAAA,CAAA,CAAA,EC+ET,MD/ES,CC+EF,MD/EE,CAAA,IAAA,EC+EW,cD/EX,CAAA,CAAA;EACgB,QAAA,sBAAA"}
package/dist/index.mjs CHANGED
@@ -1,6 +1,5 @@
1
- import { connect } from "amqplib";
2
- import { setupInfra } from "@amqp-contract/core";
3
- import { Result } from "@swan-io/boxed";
1
+ import { Future, Result } from "@swan-io/boxed";
2
+ import { AmqpClient } from "@amqp-contract/core";
4
3
 
5
4
  //#region src/errors.ts
6
5
  /**
@@ -43,61 +42,56 @@ var MessageValidationError = class extends ClientError {
43
42
  * Type-safe AMQP client for publishing messages
44
43
  */
45
44
  var TypedAmqpClient = class TypedAmqpClient {
46
- channel = null;
47
- connection = null;
48
- constructor(contract, connectionOptions) {
45
+ constructor(contract, amqpClient) {
49
46
  this.contract = contract;
50
- this.connectionOptions = connectionOptions;
47
+ this.amqpClient = amqpClient;
51
48
  }
52
49
  /**
53
- * Create a type-safe AMQP client from a contract
54
- * The client will automatically connect to the AMQP broker
50
+ * Create a type-safe AMQP client from a contract.
51
+ *
52
+ * Connection management (including automatic reconnection) is handled internally
53
+ * by amqp-connection-manager via the {@link AmqpClient}. The client establishes
54
+ * infrastructure asynchronously in the background once the connection is ready.
55
55
  */
56
- static async create(options) {
57
- const client = new TypedAmqpClient(options.contract, options.connection);
58
- await client.init();
59
- return client;
56
+ static create({ contract, urls, connectionOptions }) {
57
+ const client = new TypedAmqpClient(contract, new AmqpClient(contract, {
58
+ urls,
59
+ connectionOptions
60
+ }));
61
+ return client.waitForConnectionReady().mapOk(() => client);
60
62
  }
61
63
  /**
62
64
  * Publish a message using a defined publisher
63
65
  * Returns Result.Ok(true) on success, or Result.Error with specific error on failure
64
66
  */
65
67
  publish(publisherName, message, options) {
66
- if (!this.channel) throw new Error("Client not initialized. Create the client using TypedAmqpClient.create() to establish a connection.");
67
68
  const publishers = this.contract.publishers;
68
- if (!publishers) throw new Error("No publishers defined in contract");
69
+ if (!publishers) return Future.value(Result.Error(new TechnicalError("No publishers defined in contract")));
69
70
  const publisher = publishers[publisherName];
70
- if (!publisher || typeof publisher !== "object") throw new Error(`Publisher "${String(publisherName)}" not found in contract`);
71
- const publisherDef = publisher;
72
- const validation = publisherDef.message.payload["~standard"].validate(message);
73
- if (typeof validation === "object" && validation !== null && "issues" in validation && validation.issues) return Result.Error(new MessageValidationError(String(publisherName), validation.issues));
74
- const validatedMessage = typeof validation === "object" && validation !== null && "value" in validation ? validation.value : message;
75
- const routingKey = options?.routingKey ?? publisherDef.routingKey ?? "";
76
- const content = Buffer.from(JSON.stringify(validatedMessage));
77
- const published = this.channel.publish(publisherDef.exchange.name, routingKey, content, options?.options);
78
- if (!published) return Result.Error(new TechnicalError(`Failed to publish message for publisher "${String(publisherName)}": Channel rejected the message (buffer full or other channel issue)`));
79
- return Result.Ok(published);
71
+ if (!publisher) return Future.value(Result.Error(new TechnicalError(`Publisher "${String(publisherName)}" not found in contract`)));
72
+ const validateMessage = () => {
73
+ const validationResult = publisher.message.payload["~standard"].validate(message);
74
+ return Future.fromPromise(validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult)).mapError((error) => new TechnicalError(`Validation failed`, error)).mapOkToResult((validation) => {
75
+ if (validation.issues) return Result.Error(new MessageValidationError(String(publisherName), validation.issues));
76
+ return Result.Ok(validation.value);
77
+ });
78
+ };
79
+ const publishMessage = (validatedMessage) => {
80
+ return Future.fromPromise(this.amqpClient.channel.publish(publisher.exchange.name, publisher.routingKey ?? "", validatedMessage, options)).mapError((error) => new TechnicalError(`Failed to publish message`, error)).mapOkToResult((published) => {
81
+ if (!published) return Result.Error(new TechnicalError(`Failed to publish message for publisher "${String(publisherName)}": Channel rejected the message (buffer full or other channel issue)`));
82
+ return Result.Ok(published);
83
+ });
84
+ };
85
+ return validateMessage().flatMapOk((validatedMessage) => publishMessage(validatedMessage));
80
86
  }
81
87
  /**
82
88
  * Close the channel and connection
83
89
  */
84
- async close() {
85
- if (this.channel) {
86
- await this.channel.close();
87
- this.channel = null;
88
- }
89
- if (this.connection) {
90
- await this.connection.close();
91
- this.connection = null;
92
- }
90
+ close() {
91
+ return Future.fromPromise(this.amqpClient.close()).mapError((error) => new TechnicalError("Failed to close AMQP connection", error)).mapOk(() => void 0);
93
92
  }
94
- /**
95
- * Connect to AMQP broker
96
- */
97
- async init() {
98
- this.connection = await connect(this.connectionOptions);
99
- this.channel = await this.connection.createChannel();
100
- await setupInfra(this.channel, this.contract);
93
+ waitForConnectionReady() {
94
+ return Future.fromPromise(this.amqpClient.channel.waitForConnect()).mapError((error) => new TechnicalError("Failed to wait for connection ready", error));
101
95
  }
102
96
  };
103
97
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["cause?: unknown","publisherName: string","issues: unknown","contract: TContract","connectionOptions: string | Options.Connect"],"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["/**\n * Base error class for client errors\n */\nabstract class ClientError extends Error {\n protected constructor(message: string) {\n super(message);\n this.name = \"ClientError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error for technical/runtime failures that cannot be prevented by TypeScript\n * This includes validation failures and AMQP channel issues\n */\nexport class TechnicalError extends ClientError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"TechnicalError\";\n }\n}\n\n/**\n * Error thrown when message validation fails\n */\nexport class MessageValidationError extends ClientError {\n constructor(\n public readonly publisherName: string,\n public readonly issues: unknown,\n ) {\n super(`Message validation failed for publisher \"${publisherName}\"`);\n this.name = \"MessageValidationError\";\n }\n}\n","import { connect } from \"amqplib\";\nimport type { Channel, ChannelModel, Options } from \"amqplib\";\nimport type {\n ClientInferPublisherInput,\n ContractDefinition,\n InferPublisherNames,\n} from \"@amqp-contract/contract\";\nimport { setupInfra } from \"@amqp-contract/core\";\nimport { Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\n\n/**\n * Options for creating a client\n */\nexport interface CreateClientOptions<TContract extends ContractDefinition> {\n contract: TContract;\n connection: string | Options.Connect;\n}\n\n/**\n * Options for publishing a message\n */\nexport interface PublishOptions {\n routingKey?: string;\n options?: Options.Publish;\n}\n\n/**\n * Type-safe AMQP client for publishing messages\n */\nexport class TypedAmqpClient<TContract extends ContractDefinition> {\n private channel: Channel | null = null;\n private connection: ChannelModel | null = null;\n\n private constructor(\n private readonly contract: TContract,\n private readonly connectionOptions: string | Options.Connect,\n ) {}\n\n /**\n * Create a type-safe AMQP client from a contract\n * The client will automatically connect to the AMQP broker\n */\n static async create<TContract extends ContractDefinition>(\n options: CreateClientOptions<TContract>,\n ): Promise<TypedAmqpClient<TContract>> {\n const client = new TypedAmqpClient(options.contract, options.connection);\n await client.init();\n return client;\n }\n\n /**\n * Publish a message using a defined publisher\n * Returns Result.Ok(true) on success, or Result.Error with specific error on failure\n */\n publish<TName extends InferPublisherNames<TContract>>(\n publisherName: TName,\n message: ClientInferPublisherInput<TContract, TName>,\n options?: PublishOptions,\n ): Result<boolean, TechnicalError | MessageValidationError> {\n if (!this.channel) {\n throw new Error(\n \"Client not initialized. Create the client using TypedAmqpClient.create() to establish a connection.\",\n );\n }\n\n const publishers = this.contract.publishers as Record<string, unknown>;\n if (!publishers) {\n throw new Error(\"No publishers defined in contract\");\n }\n\n const publisher = publishers[publisherName as string];\n if (!publisher || typeof publisher !== \"object\") {\n throw new Error(`Publisher \"${String(publisherName)}\" not found in contract`);\n }\n\n const publisherDef = publisher as {\n exchange: { name: string; type: string };\n routingKey?: string;\n message: { payload: { \"~standard\": { validate: (value: unknown) => unknown } } };\n };\n\n // Validate message using schema\n const validation = publisherDef.message.payload[\"~standard\"].validate(message);\n if (\n typeof validation === \"object\" &&\n validation !== null &&\n \"issues\" in validation &&\n validation.issues\n ) {\n return Result.Error(new MessageValidationError(String(publisherName), validation.issues));\n }\n\n const validatedMessage =\n typeof validation === \"object\" && validation !== null && \"value\" in validation\n ? validation.value\n : message;\n\n // Publish message\n const routingKey = options?.routingKey ?? publisherDef.routingKey ?? \"\";\n const content = Buffer.from(JSON.stringify(validatedMessage));\n\n const published = this.channel.publish(\n publisherDef.exchange.name,\n routingKey,\n content,\n options?.options,\n );\n\n if (!published) {\n return Result.Error(\n new TechnicalError(\n `Failed to publish message for publisher \"${String(publisherName)}\": Channel rejected the message (buffer full or other channel issue)`,\n ),\n );\n }\n\n return Result.Ok(published);\n }\n\n /**\n * Close the channel and connection\n */\n async close(): Promise<void> {\n if (this.channel) {\n await this.channel.close();\n this.channel = null;\n }\n if (this.connection) {\n await this.connection.close();\n this.connection = null;\n }\n }\n\n /**\n * Connect to AMQP broker\n */\n private async init(): Promise<void> {\n this.connection = await connect(this.connectionOptions);\n this.channel = await this.connection.createChannel();\n\n // Setup exchanges, queues, and bindings\n await setupInfra(this.channel, this.contract);\n }\n}\n"],"mappings":";;;;;;;;AAGA,IAAe,cAAf,cAAmC,MAAM;CACvC,AAAU,YAAY,SAAiB;AACrC,QAAM,QAAQ;AACd,OAAK,OAAO;EAEZ,MAAM,mBAAmB;AAGzB,MAAI,OAAO,iBAAiB,sBAAsB,WAChD,kBAAiB,kBAAkB,MAAM,KAAK,YAAY;;;;;;;AAShE,IAAa,iBAAb,cAAoC,YAAY;CAC9C,YACE,SACA,AAAyBA,OACzB;AACA,QAAM,QAAQ;EAFW;AAGzB,OAAK,OAAO;;;;;;AAOhB,IAAa,yBAAb,cAA4C,YAAY;CACtD,YACE,AAAgBC,eAChB,AAAgBC,QAChB;AACA,QAAM,4CAA4C,cAAc,GAAG;EAHnD;EACA;AAGhB,OAAK,OAAO;;;;;;;;;ACVhB,IAAa,kBAAb,MAAa,gBAAsD;CACjE,AAAQ,UAA0B;CAClC,AAAQ,aAAkC;CAE1C,AAAQ,YACN,AAAiBC,UACjB,AAAiBC,mBACjB;EAFiB;EACA;;;;;;CAOnB,aAAa,OACX,SACqC;EACrC,MAAM,SAAS,IAAI,gBAAgB,QAAQ,UAAU,QAAQ,WAAW;AACxE,QAAM,OAAO,MAAM;AACnB,SAAO;;;;;;CAOT,QACE,eACA,SACA,SAC0D;AAC1D,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MACR,sGACD;EAGH,MAAM,aAAa,KAAK,SAAS;AACjC,MAAI,CAAC,WACH,OAAM,IAAI,MAAM,oCAAoC;EAGtD,MAAM,YAAY,WAAW;AAC7B,MAAI,CAAC,aAAa,OAAO,cAAc,SACrC,OAAM,IAAI,MAAM,cAAc,OAAO,cAAc,CAAC,yBAAyB;EAG/E,MAAM,eAAe;EAOrB,MAAM,aAAa,aAAa,QAAQ,QAAQ,aAAa,SAAS,QAAQ;AAC9E,MACE,OAAO,eAAe,YACtB,eAAe,QACf,YAAY,cACZ,WAAW,OAEX,QAAO,OAAO,MAAM,IAAI,uBAAuB,OAAO,cAAc,EAAE,WAAW,OAAO,CAAC;EAG3F,MAAM,mBACJ,OAAO,eAAe,YAAY,eAAe,QAAQ,WAAW,aAChE,WAAW,QACX;EAGN,MAAM,aAAa,SAAS,cAAc,aAAa,cAAc;EACrE,MAAM,UAAU,OAAO,KAAK,KAAK,UAAU,iBAAiB,CAAC;EAE7D,MAAM,YAAY,KAAK,QAAQ,QAC7B,aAAa,SAAS,MACtB,YACA,SACA,SAAS,QACV;AAED,MAAI,CAAC,UACH,QAAO,OAAO,MACZ,IAAI,eACF,4CAA4C,OAAO,cAAc,CAAC,sEACnE,CACF;AAGH,SAAO,OAAO,GAAG,UAAU;;;;;CAM7B,MAAM,QAAuB;AAC3B,MAAI,KAAK,SAAS;AAChB,SAAM,KAAK,QAAQ,OAAO;AAC1B,QAAK,UAAU;;AAEjB,MAAI,KAAK,YAAY;AACnB,SAAM,KAAK,WAAW,OAAO;AAC7B,QAAK,aAAa;;;;;;CAOtB,MAAc,OAAsB;AAClC,OAAK,aAAa,MAAM,QAAQ,KAAK,kBAAkB;AACvD,OAAK,UAAU,MAAM,KAAK,WAAW,eAAe;AAGpD,QAAM,WAAW,KAAK,SAAS,KAAK,SAAS"}
1
+ {"version":3,"file":"index.mjs","names":["cause?: unknown","publisherName: string","issues: unknown","contract: TContract","amqpClient: AmqpClient"],"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["/**\n * Base error class for client errors\n */\nabstract class ClientError extends Error {\n protected constructor(message: string) {\n super(message);\n this.name = \"ClientError\";\n // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error for technical/runtime failures that cannot be prevented by TypeScript\n * This includes validation failures and AMQP channel issues\n */\nexport class TechnicalError extends ClientError {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"TechnicalError\";\n }\n}\n\n/**\n * Error thrown when message validation fails\n */\nexport class MessageValidationError extends ClientError {\n constructor(\n public readonly publisherName: string,\n public readonly issues: unknown,\n ) {\n super(`Message validation failed for publisher \"${publisherName}\"`);\n this.name = \"MessageValidationError\";\n }\n}\n","import type { Options } from \"amqplib\";\nimport type { ContractDefinition, InferPublisherNames } from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\nimport type { ClientInferPublisherInput } from \"./types.js\";\nimport { AmqpClient } from \"@amqp-contract/core\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\n\n/**\n * Options for creating a client\n */\nexport type CreateClientOptions<TContract extends ContractDefinition> = {\n contract: TContract;\n urls: ConnectionUrl[];\n connectionOptions?: AmqpConnectionManagerOptions | undefined;\n};\n\n/**\n * Type-safe AMQP client for publishing messages\n */\nexport class TypedAmqpClient<TContract extends ContractDefinition> {\n private constructor(\n private readonly contract: TContract,\n private readonly amqpClient: AmqpClient,\n ) {}\n\n /**\n * Create a type-safe AMQP client from a contract.\n *\n * Connection management (including automatic reconnection) is handled internally\n * by amqp-connection-manager via the {@link AmqpClient}. The client establishes\n * infrastructure asynchronously in the background once the connection is ready.\n */\n static create<TContract extends ContractDefinition>({\n contract,\n urls,\n connectionOptions,\n }: CreateClientOptions<TContract>): Future<Result<TypedAmqpClient<TContract>, TechnicalError>> {\n const client = new TypedAmqpClient(\n contract,\n new AmqpClient(contract, { urls, connectionOptions }),\n );\n\n return client.waitForConnectionReady().mapOk(() => client);\n }\n\n /**\n * Publish a message using a defined publisher\n * Returns Result.Ok(true) on success, or Result.Error with specific error on failure\n */\n publish<TName extends InferPublisherNames<TContract>>(\n publisherName: TName,\n message: ClientInferPublisherInput<TContract, TName>,\n options?: Options.Publish,\n ): Future<Result<boolean, TechnicalError | MessageValidationError>> {\n const publishers = this.contract.publishers;\n if (!publishers) {\n return Future.value(Result.Error(new TechnicalError(\"No publishers defined in contract\")));\n }\n\n const publisher = publishers[publisherName as string];\n if (!publisher) {\n return Future.value(\n Result.Error(\n new TechnicalError(`Publisher \"${String(publisherName)}\" not found in contract`),\n ),\n );\n }\n\n const validateMessage = () => {\n const validationResult = publisher.message.payload[\"~standard\"].validate(message);\n return Future.fromPromise(\n validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult),\n )\n .mapError((error) => new TechnicalError(`Validation failed`, error))\n .mapOkToResult((validation) => {\n if (validation.issues) {\n return Result.Error(\n new MessageValidationError(String(publisherName), validation.issues),\n );\n }\n\n return Result.Ok(validation.value);\n });\n };\n\n const publishMessage = (validatedMessage: unknown) => {\n return Future.fromPromise(\n this.amqpClient.channel.publish(\n publisher.exchange.name,\n publisher.routingKey ?? \"\",\n validatedMessage,\n options,\n ),\n )\n .mapError((error) => new TechnicalError(`Failed to publish message`, error))\n .mapOkToResult((published) => {\n if (!published) {\n return Result.Error(\n new TechnicalError(\n `Failed to publish message for publisher \"${String(publisherName)}\": Channel rejected the message (buffer full or other channel issue)`,\n ),\n );\n }\n\n return Result.Ok(published);\n });\n };\n\n // Validate message using schema\n return validateMessage().flatMapOk((validatedMessage) => publishMessage(validatedMessage));\n }\n\n /**\n * Close the channel and connection\n */\n close(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.amqpClient.close())\n .mapError((error) => new TechnicalError(\"Failed to close AMQP connection\", error))\n .mapOk(() => undefined);\n }\n\n private waitForConnectionReady(): Future<Result<void, TechnicalError>> {\n return Future.fromPromise(this.amqpClient.channel.waitForConnect()).mapError(\n (error) => new TechnicalError(\"Failed to wait for connection ready\", error),\n );\n }\n}\n"],"mappings":";;;;;;;AAGA,IAAe,cAAf,cAAmC,MAAM;CACvC,AAAU,YAAY,SAAiB;AACrC,QAAM,QAAQ;AACd,OAAK,OAAO;EAEZ,MAAM,mBAAmB;AAGzB,MAAI,OAAO,iBAAiB,sBAAsB,WAChD,kBAAiB,kBAAkB,MAAM,KAAK,YAAY;;;;;;;AAShE,IAAa,iBAAb,cAAoC,YAAY;CAC9C,YACE,SACA,AAAyBA,OACzB;AACA,QAAM,QAAQ;EAFW;AAGzB,OAAK,OAAO;;;;;;AAOhB,IAAa,yBAAb,cAA4C,YAAY;CACtD,YACE,AAAgBC,eAChB,AAAgBC,QAChB;AACA,QAAM,4CAA4C,cAAc,GAAG;EAHnD;EACA;AAGhB,OAAK,OAAO;;;;;;;;;ACpBhB,IAAa,kBAAb,MAAa,gBAAsD;CACjE,AAAQ,YACN,AAAiBC,UACjB,AAAiBC,YACjB;EAFiB;EACA;;;;;;;;;CAUnB,OAAO,OAA6C,EAClD,UACA,MACA,qBAC6F;EAC7F,MAAM,SAAS,IAAI,gBACjB,UACA,IAAI,WAAW,UAAU;GAAE;GAAM;GAAmB,CAAC,CACtD;AAED,SAAO,OAAO,wBAAwB,CAAC,YAAY,OAAO;;;;;;CAO5D,QACE,eACA,SACA,SACkE;EAClE,MAAM,aAAa,KAAK,SAAS;AACjC,MAAI,CAAC,WACH,QAAO,OAAO,MAAM,OAAO,MAAM,IAAI,eAAe,oCAAoC,CAAC,CAAC;EAG5F,MAAM,YAAY,WAAW;AAC7B,MAAI,CAAC,UACH,QAAO,OAAO,MACZ,OAAO,MACL,IAAI,eAAe,cAAc,OAAO,cAAc,CAAC,yBAAyB,CACjF,CACF;EAGH,MAAM,wBAAwB;GAC5B,MAAM,mBAAmB,UAAU,QAAQ,QAAQ,aAAa,SAAS,QAAQ;AACjF,UAAO,OAAO,YACZ,4BAA4B,UAAU,mBAAmB,QAAQ,QAAQ,iBAAiB,CAC3F,CACE,UAAU,UAAU,IAAI,eAAe,qBAAqB,MAAM,CAAC,CACnE,eAAe,eAAe;AAC7B,QAAI,WAAW,OACb,QAAO,OAAO,MACZ,IAAI,uBAAuB,OAAO,cAAc,EAAE,WAAW,OAAO,CACrE;AAGH,WAAO,OAAO,GAAG,WAAW,MAAM;KAClC;;EAGN,MAAM,kBAAkB,qBAA8B;AACpD,UAAO,OAAO,YACZ,KAAK,WAAW,QAAQ,QACtB,UAAU,SAAS,MACnB,UAAU,cAAc,IACxB,kBACA,QACD,CACF,CACE,UAAU,UAAU,IAAI,eAAe,6BAA6B,MAAM,CAAC,CAC3E,eAAe,cAAc;AAC5B,QAAI,CAAC,UACH,QAAO,OAAO,MACZ,IAAI,eACF,4CAA4C,OAAO,cAAc,CAAC,sEACnE,CACF;AAGH,WAAO,OAAO,GAAG,UAAU;KAC3B;;AAIN,SAAO,iBAAiB,CAAC,WAAW,qBAAqB,eAAe,iBAAiB,CAAC;;;;;CAM5F,QAA8C;AAC5C,SAAO,OAAO,YAAY,KAAK,WAAW,OAAO,CAAC,CAC/C,UAAU,UAAU,IAAI,eAAe,mCAAmC,MAAM,CAAC,CACjF,YAAY,OAAU;;CAG3B,AAAQ,yBAA+D;AACrE,SAAO,OAAO,YAAY,KAAK,WAAW,QAAQ,gBAAgB,CAAC,CAAC,UACjE,UAAU,IAAI,eAAe,uCAAuC,MAAM,CAC5E"}
package/docs/index.md ADDED
@@ -0,0 +1,447 @@
1
+ **@amqp-contract/client**
2
+
3
+ ***
4
+
5
+ # @amqp-contract/client
6
+
7
+ ## Classes
8
+
9
+ ### MessageValidationError
10
+
11
+ Defined in: [packages/client/src/errors.ts:35](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/errors.ts#L35)
12
+
13
+ Error thrown when message validation fails
14
+
15
+ #### Extends
16
+
17
+ - `ClientError`
18
+
19
+ #### Constructors
20
+
21
+ ##### Constructor
22
+
23
+ ```ts
24
+ new MessageValidationError(publisherName, issues): MessageValidationError;
25
+ ```
26
+
27
+ Defined in: [packages/client/src/errors.ts:36](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/errors.ts#L36)
28
+
29
+ ###### Parameters
30
+
31
+ | Parameter | Type |
32
+ | ------ | ------ |
33
+ | `publisherName` | `string` |
34
+ | `issues` | `unknown` |
35
+
36
+ ###### Returns
37
+
38
+ [`MessageValidationError`](#messagevalidationerror)
39
+
40
+ ###### Overrides
41
+
42
+ ```ts
43
+ ClientError.constructor
44
+ ```
45
+
46
+ #### Properties
47
+
48
+ | Property | Modifier | Type | Description | Inherited from | Defined in |
49
+ | ------ | ------ | ------ | ------ | ------ | ------ |
50
+ | <a id="cause"></a> `cause?` | `public` | `unknown` | - | `ClientError.cause` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 |
51
+ | <a id="issues"></a> `issues` | `readonly` | `unknown` | - | - | [packages/client/src/errors.ts:38](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/errors.ts#L38) |
52
+ | <a id="message"></a> `message` | `public` | `string` | - | `ClientError.message` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 |
53
+ | <a id="name"></a> `name` | `public` | `string` | - | `ClientError.name` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
54
+ | <a id="publishername"></a> `publisherName` | `readonly` | `string` | - | - | [packages/client/src/errors.ts:37](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/errors.ts#L37) |
55
+ | <a id="stack"></a> `stack?` | `public` | `string` | - | `ClientError.stack` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 |
56
+ | <a id="stacktracelimit"></a> `stackTraceLimit` | `static` | `number` | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured _after_ the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. | `ClientError.stackTraceLimit` | node\_modules/.pnpm/@types+node@25.0.3/node\_modules/@types/node/globals.d.ts:67 |
57
+
58
+ #### Methods
59
+
60
+ ##### captureStackTrace()
61
+
62
+ ```ts
63
+ static captureStackTrace(targetObject, constructorOpt?): void;
64
+ ```
65
+
66
+ Defined in: node\_modules/.pnpm/@types+node@25.0.3/node\_modules/@types/node/globals.d.ts:51
67
+
68
+ Creates a `.stack` property on `targetObject`, which when accessed returns
69
+ a string representing the location in the code at which
70
+ `Error.captureStackTrace()` was called.
71
+
72
+ ```js
73
+ const myObject = {};
74
+ Error.captureStackTrace(myObject);
75
+ myObject.stack; // Similar to `new Error().stack`
76
+ ```
77
+
78
+ The first line of the trace will be prefixed with
79
+ `${myObject.name}: ${myObject.message}`.
80
+
81
+ The optional `constructorOpt` argument accepts a function. If given, all frames
82
+ above `constructorOpt`, including `constructorOpt`, will be omitted from the
83
+ generated stack trace.
84
+
85
+ The `constructorOpt` argument is useful for hiding implementation
86
+ details of error generation from the user. For instance:
87
+
88
+ ```js
89
+ function a() {
90
+ b();
91
+ }
92
+
93
+ function b() {
94
+ c();
95
+ }
96
+
97
+ function c() {
98
+ // Create an error without stack trace to avoid calculating the stack trace twice.
99
+ const { stackTraceLimit } = Error;
100
+ Error.stackTraceLimit = 0;
101
+ const error = new Error();
102
+ Error.stackTraceLimit = stackTraceLimit;
103
+
104
+ // Capture the stack trace above function b
105
+ Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
106
+ throw error;
107
+ }
108
+
109
+ a();
110
+ ```
111
+
112
+ ###### Parameters
113
+
114
+ | Parameter | Type |
115
+ | ------ | ------ |
116
+ | `targetObject` | `object` |
117
+ | `constructorOpt?` | `Function` |
118
+
119
+ ###### Returns
120
+
121
+ `void`
122
+
123
+ ###### Inherited from
124
+
125
+ ```ts
126
+ ClientError.captureStackTrace
127
+ ```
128
+
129
+ ##### prepareStackTrace()
130
+
131
+ ```ts
132
+ static prepareStackTrace(err, stackTraces): any;
133
+ ```
134
+
135
+ Defined in: node\_modules/.pnpm/@types+node@25.0.3/node\_modules/@types/node/globals.d.ts:55
136
+
137
+ ###### Parameters
138
+
139
+ | Parameter | Type |
140
+ | ------ | ------ |
141
+ | `err` | `Error` |
142
+ | `stackTraces` | `CallSite`[] |
143
+
144
+ ###### Returns
145
+
146
+ `any`
147
+
148
+ ###### See
149
+
150
+ https://v8.dev/docs/stack-trace-api#customizing-stack-traces
151
+
152
+ ###### Inherited from
153
+
154
+ ```ts
155
+ ClientError.prepareStackTrace
156
+ ```
157
+
158
+ ***
159
+
160
+ ### TechnicalError
161
+
162
+ Defined in: [packages/client/src/errors.ts:22](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/errors.ts#L22)
163
+
164
+ Error for technical/runtime failures that cannot be prevented by TypeScript
165
+ This includes validation failures and AMQP channel issues
166
+
167
+ #### Extends
168
+
169
+ - `ClientError`
170
+
171
+ #### Constructors
172
+
173
+ ##### Constructor
174
+
175
+ ```ts
176
+ new TechnicalError(message, cause?): TechnicalError;
177
+ ```
178
+
179
+ Defined in: [packages/client/src/errors.ts:23](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/errors.ts#L23)
180
+
181
+ ###### Parameters
182
+
183
+ | Parameter | Type |
184
+ | ------ | ------ |
185
+ | `message` | `string` |
186
+ | `cause?` | `unknown` |
187
+
188
+ ###### Returns
189
+
190
+ [`TechnicalError`](#technicalerror)
191
+
192
+ ###### Overrides
193
+
194
+ ```ts
195
+ ClientError.constructor
196
+ ```
197
+
198
+ #### Properties
199
+
200
+ | Property | Modifier | Type | Description | Inherited from | Defined in |
201
+ | ------ | ------ | ------ | ------ | ------ | ------ |
202
+ | <a id="cause-1"></a> `cause?` | `readonly` | `unknown` | - | `ClientError.cause` | [packages/client/src/errors.ts:25](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/errors.ts#L25) |
203
+ | <a id="message-1"></a> `message` | `public` | `string` | - | `ClientError.message` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 |
204
+ | <a id="name-1"></a> `name` | `public` | `string` | - | `ClientError.name` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 |
205
+ | <a id="stack-1"></a> `stack?` | `public` | `string` | - | `ClientError.stack` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 |
206
+ | <a id="stacktracelimit-1"></a> `stackTraceLimit` | `static` | `number` | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured _after_ the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. | `ClientError.stackTraceLimit` | node\_modules/.pnpm/@types+node@25.0.3/node\_modules/@types/node/globals.d.ts:67 |
207
+
208
+ #### Methods
209
+
210
+ ##### captureStackTrace()
211
+
212
+ ```ts
213
+ static captureStackTrace(targetObject, constructorOpt?): void;
214
+ ```
215
+
216
+ Defined in: node\_modules/.pnpm/@types+node@25.0.3/node\_modules/@types/node/globals.d.ts:51
217
+
218
+ Creates a `.stack` property on `targetObject`, which when accessed returns
219
+ a string representing the location in the code at which
220
+ `Error.captureStackTrace()` was called.
221
+
222
+ ```js
223
+ const myObject = {};
224
+ Error.captureStackTrace(myObject);
225
+ myObject.stack; // Similar to `new Error().stack`
226
+ ```
227
+
228
+ The first line of the trace will be prefixed with
229
+ `${myObject.name}: ${myObject.message}`.
230
+
231
+ The optional `constructorOpt` argument accepts a function. If given, all frames
232
+ above `constructorOpt`, including `constructorOpt`, will be omitted from the
233
+ generated stack trace.
234
+
235
+ The `constructorOpt` argument is useful for hiding implementation
236
+ details of error generation from the user. For instance:
237
+
238
+ ```js
239
+ function a() {
240
+ b();
241
+ }
242
+
243
+ function b() {
244
+ c();
245
+ }
246
+
247
+ function c() {
248
+ // Create an error without stack trace to avoid calculating the stack trace twice.
249
+ const { stackTraceLimit } = Error;
250
+ Error.stackTraceLimit = 0;
251
+ const error = new Error();
252
+ Error.stackTraceLimit = stackTraceLimit;
253
+
254
+ // Capture the stack trace above function b
255
+ Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
256
+ throw error;
257
+ }
258
+
259
+ a();
260
+ ```
261
+
262
+ ###### Parameters
263
+
264
+ | Parameter | Type |
265
+ | ------ | ------ |
266
+ | `targetObject` | `object` |
267
+ | `constructorOpt?` | `Function` |
268
+
269
+ ###### Returns
270
+
271
+ `void`
272
+
273
+ ###### Inherited from
274
+
275
+ ```ts
276
+ ClientError.captureStackTrace
277
+ ```
278
+
279
+ ##### prepareStackTrace()
280
+
281
+ ```ts
282
+ static prepareStackTrace(err, stackTraces): any;
283
+ ```
284
+
285
+ Defined in: node\_modules/.pnpm/@types+node@25.0.3/node\_modules/@types/node/globals.d.ts:55
286
+
287
+ ###### Parameters
288
+
289
+ | Parameter | Type |
290
+ | ------ | ------ |
291
+ | `err` | `Error` |
292
+ | `stackTraces` | `CallSite`[] |
293
+
294
+ ###### Returns
295
+
296
+ `any`
297
+
298
+ ###### See
299
+
300
+ https://v8.dev/docs/stack-trace-api#customizing-stack-traces
301
+
302
+ ###### Inherited from
303
+
304
+ ```ts
305
+ ClientError.prepareStackTrace
306
+ ```
307
+
308
+ ***
309
+
310
+ ### TypedAmqpClient
311
+
312
+ Defined in: [packages/client/src/client.ts:21](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/client.ts#L21)
313
+
314
+ Type-safe AMQP client for publishing messages
315
+
316
+ #### Type Parameters
317
+
318
+ | Type Parameter |
319
+ | ------ |
320
+ | `TContract` *extends* `ContractDefinition` |
321
+
322
+ #### Methods
323
+
324
+ ##### close()
325
+
326
+ ```ts
327
+ close(): Future<Result<void, TechnicalError>>;
328
+ ```
329
+
330
+ Defined in: [packages/client/src/client.ts:117](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/client.ts#L117)
331
+
332
+ Close the channel and connection
333
+
334
+ ###### Returns
335
+
336
+ `Future`\<`Result`\<`void`, [`TechnicalError`](#technicalerror)\>\>
337
+
338
+ ##### publish()
339
+
340
+ ```ts
341
+ publish<TName>(
342
+ publisherName,
343
+ message,
344
+ options?): Future<Result<boolean,
345
+ | TechnicalError
346
+ | MessageValidationError>>;
347
+ ```
348
+
349
+ Defined in: [packages/client/src/client.ts:51](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/client.ts#L51)
350
+
351
+ Publish a message using a defined publisher
352
+ Returns Result.Ok(true) on success, or Result.Error with specific error on failure
353
+
354
+ ###### Type Parameters
355
+
356
+ | Type Parameter |
357
+ | ------ |
358
+ | `TName` *extends* `string` \| `number` \| `symbol` |
359
+
360
+ ###### Parameters
361
+
362
+ | Parameter | Type |
363
+ | ------ | ------ |
364
+ | `publisherName` | `TName` |
365
+ | `message` | [`ClientInferPublisherInput`](#clientinferpublisherinput)\<`TContract`, `TName`\> |
366
+ | `options?` | `Publish` |
367
+
368
+ ###### Returns
369
+
370
+ `Future`\<`Result`\<`boolean`,
371
+ \| [`TechnicalError`](#technicalerror)
372
+ \| [`MessageValidationError`](#messagevalidationerror)\>\>
373
+
374
+ ##### create()
375
+
376
+ ```ts
377
+ static create<TContract>(__namedParameters): Future<Result<TypedAmqpClient<TContract>, TechnicalError>>;
378
+ ```
379
+
380
+ Defined in: [packages/client/src/client.ts:34](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/client.ts#L34)
381
+
382
+ Create a type-safe AMQP client from a contract.
383
+
384
+ Connection management (including automatic reconnection) is handled internally
385
+ by amqp-connection-manager via the AmqpClient. The client establishes
386
+ infrastructure asynchronously in the background once the connection is ready.
387
+
388
+ ###### Type Parameters
389
+
390
+ | Type Parameter |
391
+ | ------ |
392
+ | `TContract` *extends* `ContractDefinition` |
393
+
394
+ ###### Parameters
395
+
396
+ | Parameter | Type |
397
+ | ------ | ------ |
398
+ | `__namedParameters` | [`CreateClientOptions`](#createclientoptions)\<`TContract`\> |
399
+
400
+ ###### Returns
401
+
402
+ `Future`\<`Result`\<[`TypedAmqpClient`](#typedamqpclient)\<`TContract`\>, [`TechnicalError`](#technicalerror)\>\>
403
+
404
+ ## Type Aliases
405
+
406
+ ### ClientInferPublisherInput
407
+
408
+ ```ts
409
+ type ClientInferPublisherInput<TContract, TName> = PublisherInferInput<InferPublisher<TContract, TName>>;
410
+ ```
411
+
412
+ Defined in: [packages/client/src/types.ts:37](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/types.ts#L37)
413
+
414
+ Infer publisher input type (message payload) for a specific publisher in a contract
415
+
416
+ #### Type Parameters
417
+
418
+ | Type Parameter |
419
+ | ------ |
420
+ | `TContract` *extends* `ContractDefinition` |
421
+ | `TName` *extends* `InferPublisherNames`\<`TContract`\> |
422
+
423
+ ***
424
+
425
+ ### CreateClientOptions
426
+
427
+ ```ts
428
+ type CreateClientOptions<TContract> = object;
429
+ ```
430
+
431
+ Defined in: [packages/client/src/client.ts:12](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/client.ts#L12)
432
+
433
+ Options for creating a client
434
+
435
+ #### Type Parameters
436
+
437
+ | Type Parameter |
438
+ | ------ |
439
+ | `TContract` *extends* `ContractDefinition` |
440
+
441
+ #### Properties
442
+
443
+ | Property | Type | Defined in |
444
+ | ------ | ------ | ------ |
445
+ | <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | [packages/client/src/client.ts:15](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/client.ts#L15) |
446
+ | <a id="contract"></a> `contract` | `TContract` | [packages/client/src/client.ts:13](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/client.ts#L13) |
447
+ | <a id="urls"></a> `urls` | `ConnectionUrl`[] | [packages/client/src/client.ts:14](https://github.com/btravers/amqp-contract/blob/63bb54252f8a9109544152686427ef223964c15c/packages/client/src/client.ts#L14) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amqp-contract/client",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Client utilities for publishing messages using amqp-contract",
5
5
  "keywords": [
6
6
  "amqp",
@@ -38,30 +38,34 @@
38
38
  "module": "./dist/index.mjs",
39
39
  "types": "./dist/index.d.mts",
40
40
  "files": [
41
- "dist"
41
+ "dist",
42
+ "docs"
42
43
  ],
43
44
  "dependencies": {
45
+ "@standard-schema/spec": "1.1.0",
44
46
  "@swan-io/boxed": "3.2.1",
45
- "@amqp-contract/contract": "0.2.0",
46
- "@amqp-contract/core": "0.2.0"
47
+ "@amqp-contract/contract": "0.3.0",
48
+ "@amqp-contract/core": "0.3.0"
47
49
  },
48
50
  "devDependencies": {
49
51
  "@types/amqplib": "0.10.8",
50
52
  "@types/node": "25.0.3",
51
53
  "@vitest/coverage-v8": "4.0.16",
54
+ "amqp-connection-manager": "5.0.0",
52
55
  "amqplib": "0.10.9",
53
56
  "tsdown": "0.18.2",
57
+ "typedoc": "0.28.3",
58
+ "typedoc-plugin-markdown": "4.9.0",
54
59
  "typescript": "5.9.3",
55
60
  "vitest": "4.0.16",
56
61
  "zod": "4.2.1",
57
- "@amqp-contract/testing": "0.2.0",
58
- "@amqp-contract/tsconfig": "0.0.0"
59
- },
60
- "peerDependencies": {
61
- "amqplib": ">=0.10.0"
62
+ "@amqp-contract/testing": "0.3.0",
63
+ "@amqp-contract/tsconfig": "0.0.0",
64
+ "@amqp-contract/typedoc": "0.0.1"
62
65
  },
63
66
  "scripts": {
64
67
  "build": "tsdown src/index.ts --format cjs,esm --dts --clean",
68
+ "build:docs": "typedoc",
65
69
  "dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
66
70
  "test": "vitest run --project unit",
67
71
  "test:integration": "vitest run --project integration",