@amqp-contract/client 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +21 -43
- package/dist/index.d.cts +16 -26
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +16 -26
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +21 -42
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +41 -202
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -6,36 +6,16 @@ let node_util = require("node:util");
|
|
|
6
6
|
|
|
7
7
|
//#region src/errors.ts
|
|
8
8
|
/**
|
|
9
|
-
* Base error class for client errors
|
|
10
|
-
*/
|
|
11
|
-
var ClientError = class extends Error {
|
|
12
|
-
constructor(message) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = "ClientError";
|
|
15
|
-
const ErrorConstructor = Error;
|
|
16
|
-
if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Error for technical/runtime failures that cannot be prevented by TypeScript
|
|
21
|
-
* This includes validation failures and AMQP channel issues
|
|
22
|
-
*/
|
|
23
|
-
var TechnicalError = class extends ClientError {
|
|
24
|
-
constructor(message, cause) {
|
|
25
|
-
super(message);
|
|
26
|
-
this.cause = cause;
|
|
27
|
-
this.name = "TechnicalError";
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
9
|
* Error thrown when message validation fails
|
|
32
10
|
*/
|
|
33
|
-
var MessageValidationError = class extends
|
|
11
|
+
var MessageValidationError = class extends Error {
|
|
34
12
|
constructor(publisherName, issues) {
|
|
35
13
|
super(`Message validation failed for publisher "${publisherName}"`);
|
|
36
14
|
this.publisherName = publisherName;
|
|
37
15
|
this.issues = issues;
|
|
38
16
|
this.name = "MessageValidationError";
|
|
17
|
+
const ErrorConstructor = Error;
|
|
18
|
+
if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
|
|
39
19
|
}
|
|
40
20
|
};
|
|
41
21
|
|
|
@@ -48,13 +28,12 @@ const deflateAsync = (0, node_util.promisify)(node_zlib.deflate);
|
|
|
48
28
|
*
|
|
49
29
|
* @param buffer - The buffer to compress
|
|
50
30
|
* @param algorithm - The compression algorithm to use
|
|
51
|
-
* @returns A
|
|
52
|
-
* @throws Error if compression fails
|
|
31
|
+
* @returns A Future with the compressed buffer or a TechnicalError
|
|
53
32
|
*
|
|
54
33
|
* @internal
|
|
55
34
|
*/
|
|
56
|
-
|
|
57
|
-
return (0, ts_pattern.match)(algorithm).with("gzip", () => gzipAsync(buffer)).with("deflate", () => deflateAsync(buffer)).exhaustive();
|
|
35
|
+
function compressBuffer(buffer, algorithm) {
|
|
36
|
+
return (0, ts_pattern.match)(algorithm).with("gzip", () => _swan_io_boxed.Future.fromPromise(gzipAsync(buffer)).mapError((error) => new _amqp_contract_core.TechnicalError("Failed to compress with gzip", error))).with("deflate", () => _swan_io_boxed.Future.fromPromise(deflateAsync(buffer)).mapError((error) => new _amqp_contract_core.TechnicalError("Failed to compress with deflate", error))).exhaustive();
|
|
58
37
|
}
|
|
59
38
|
|
|
60
39
|
//#endregion
|
|
@@ -100,18 +79,18 @@ var TypedAmqpClient = class TypedAmqpClient {
|
|
|
100
79
|
*
|
|
101
80
|
* @returns Result.Ok(void) on success, or Result.Error with specific error on failure
|
|
102
81
|
*/
|
|
82
|
+
/**
|
|
83
|
+
* Publish a message using a defined publisher.
|
|
84
|
+
* TypeScript guarantees publisher exists for valid publisher names.
|
|
85
|
+
*/
|
|
103
86
|
publish(publisherName, message, options) {
|
|
104
87
|
const startTime = Date.now();
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
if (!publisher) return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Error(new TechnicalError(`Publisher "${String(publisherName)}" not found in contract`)));
|
|
109
|
-
const exchangeName = publisher.exchange.name;
|
|
110
|
-
const routingKey = publisher.routingKey;
|
|
111
|
-
const span = (0, _amqp_contract_core.startPublishSpan)(this.telemetry, exchangeName, routingKey, { "amqp.publisher.name": String(publisherName) });
|
|
88
|
+
const publisher = this.contract.publishers[publisherName];
|
|
89
|
+
const { exchange, routingKey } = publisher;
|
|
90
|
+
const span = (0, _amqp_contract_core.startPublishSpan)(this.telemetry, exchange.name, routingKey, { "amqp.publisher.name": String(publisherName) });
|
|
112
91
|
const validateMessage = () => {
|
|
113
92
|
const validationResult = publisher.message.payload["~standard"].validate(message);
|
|
114
|
-
return _swan_io_boxed.Future.fromPromise(validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult)).mapError((error) => new TechnicalError(`Validation failed`, error)).mapOkToResult((validation) => {
|
|
93
|
+
return _swan_io_boxed.Future.fromPromise(validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult)).mapError((error) => new _amqp_contract_core.TechnicalError(`Validation failed`, error)).mapOkToResult((validation) => {
|
|
115
94
|
if (validation.issues) return _swan_io_boxed.Result.Error(new MessageValidationError(String(publisherName), validation.issues));
|
|
116
95
|
return _swan_io_boxed.Result.Ok(validation.value);
|
|
117
96
|
});
|
|
@@ -123,12 +102,12 @@ var TypedAmqpClient = class TypedAmqpClient {
|
|
|
123
102
|
if (compression) {
|
|
124
103
|
const messageBuffer = Buffer.from(JSON.stringify(validatedMessage));
|
|
125
104
|
publishOptions.contentEncoding = compression;
|
|
126
|
-
return
|
|
105
|
+
return compressBuffer(messageBuffer, compression);
|
|
127
106
|
}
|
|
128
107
|
return _swan_io_boxed.Future.value(_swan_io_boxed.Result.Ok(validatedMessage));
|
|
129
108
|
};
|
|
130
|
-
return preparePayload().flatMapOk((payload) =>
|
|
131
|
-
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)`));
|
|
109
|
+
return preparePayload().flatMapOk((payload) => this.amqpClient.publish(publisher.exchange.name, publisher.routingKey ?? "", payload, publishOptions).mapOkToResult((published) => {
|
|
110
|
+
if (!published) return _swan_io_boxed.Result.Error(new _amqp_contract_core.TechnicalError(`Failed to publish message for publisher "${String(publisherName)}": Channel rejected the message (buffer full or other channel issue)`));
|
|
132
111
|
this.logger?.info("Message published successfully", {
|
|
133
112
|
publisherName: String(publisherName),
|
|
134
113
|
exchange: publisher.exchange.name,
|
|
@@ -141,25 +120,24 @@ var TypedAmqpClient = class TypedAmqpClient {
|
|
|
141
120
|
return validateMessage().flatMapOk((validatedMessage) => publishMessage(validatedMessage)).tapOk(() => {
|
|
142
121
|
const durationMs = Date.now() - startTime;
|
|
143
122
|
(0, _amqp_contract_core.endSpanSuccess)(span);
|
|
144
|
-
(0, _amqp_contract_core.recordPublishMetric)(this.telemetry,
|
|
123
|
+
(0, _amqp_contract_core.recordPublishMetric)(this.telemetry, exchange.name, routingKey, true, durationMs);
|
|
145
124
|
}).tapError((error) => {
|
|
146
125
|
const durationMs = Date.now() - startTime;
|
|
147
126
|
(0, _amqp_contract_core.endSpanError)(span, error);
|
|
148
|
-
(0, _amqp_contract_core.recordPublishMetric)(this.telemetry,
|
|
127
|
+
(0, _amqp_contract_core.recordPublishMetric)(this.telemetry, exchange.name, routingKey, false, durationMs);
|
|
149
128
|
});
|
|
150
129
|
}
|
|
151
130
|
/**
|
|
152
131
|
* Close the channel and connection
|
|
153
132
|
*/
|
|
154
133
|
close() {
|
|
155
|
-
return
|
|
134
|
+
return this.amqpClient.close().mapOk(() => void 0);
|
|
156
135
|
}
|
|
157
136
|
waitForConnectionReady() {
|
|
158
|
-
return
|
|
137
|
+
return this.amqpClient.waitForConnect();
|
|
159
138
|
}
|
|
160
139
|
};
|
|
161
140
|
|
|
162
141
|
//#endregion
|
|
163
142
|
exports.MessageValidationError = MessageValidationError;
|
|
164
|
-
exports.TechnicalError = TechnicalError;
|
|
165
143
|
exports.TypedAmqpClient = TypedAmqpClient;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,34 +1,10 @@
|
|
|
1
|
-
import { Logger, TelemetryProvider } from "@amqp-contract/core";
|
|
1
|
+
import { Logger, TechnicalError, TelemetryProvider } from "@amqp-contract/core";
|
|
2
2
|
import { AmqpConnectionManagerOptions, ConnectionUrl } from "amqp-connection-manager";
|
|
3
3
|
import { CompressionAlgorithm, ContractDefinition, InferPublisherNames, PublisherDefinition } from "@amqp-contract/contract";
|
|
4
4
|
import { Future, Result } from "@swan-io/boxed";
|
|
5
5
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
6
6
|
import { Options } from "amqplib";
|
|
7
7
|
|
|
8
|
-
//#region src/errors.d.ts
|
|
9
|
-
/**
|
|
10
|
-
* Base error class for client errors
|
|
11
|
-
*/
|
|
12
|
-
declare abstract class ClientError extends Error {
|
|
13
|
-
protected constructor(message: string);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Error for technical/runtime failures that cannot be prevented by TypeScript
|
|
17
|
-
* This includes validation failures and AMQP channel issues
|
|
18
|
-
*/
|
|
19
|
-
declare class TechnicalError extends ClientError {
|
|
20
|
-
readonly cause?: unknown | undefined;
|
|
21
|
-
constructor(message: string, cause?: unknown | undefined);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Error thrown when message validation fails
|
|
25
|
-
*/
|
|
26
|
-
declare class MessageValidationError extends ClientError {
|
|
27
|
-
readonly publisherName: string;
|
|
28
|
-
readonly issues: unknown;
|
|
29
|
-
constructor(publisherName: string, issues: unknown);
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
8
|
//#region src/types.d.ts
|
|
33
9
|
/**
|
|
34
10
|
* Infer the TypeScript type from a schema
|
|
@@ -51,6 +27,16 @@ type InferPublisher<TContract extends ContractDefinition, TName extends InferPub
|
|
|
51
27
|
*/
|
|
52
28
|
type ClientInferPublisherInput<TContract extends ContractDefinition, TName extends InferPublisherNames<TContract>> = PublisherInferInput<InferPublisher<TContract, TName>>;
|
|
53
29
|
//#endregion
|
|
30
|
+
//#region src/errors.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Error thrown when message validation fails
|
|
33
|
+
*/
|
|
34
|
+
declare class MessageValidationError extends Error {
|
|
35
|
+
readonly publisherName: string;
|
|
36
|
+
readonly issues: unknown;
|
|
37
|
+
constructor(publisherName: string, issues: unknown);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
54
40
|
//#region src/client.d.ts
|
|
55
41
|
/**
|
|
56
42
|
* Publish options that extend amqplib's Options.Publish with optional compression support.
|
|
@@ -118,6 +104,10 @@ declare class TypedAmqpClient<TContract extends ContractDefinition> {
|
|
|
118
104
|
*
|
|
119
105
|
* @returns Result.Ok(void) on success, or Result.Error with specific error on failure
|
|
120
106
|
*/
|
|
107
|
+
/**
|
|
108
|
+
* Publish a message using a defined publisher.
|
|
109
|
+
* TypeScript guarantees publisher exists for valid publisher names.
|
|
110
|
+
*/
|
|
121
111
|
publish<TName extends InferPublisherNames<TContract>>(publisherName: TName, message: ClientInferPublisherInput<TContract, TName>, options?: PublishOptions): Future<Result<void, TechnicalError | MessageValidationError>>;
|
|
122
112
|
/**
|
|
123
113
|
* Close the channel and connection
|
|
@@ -126,5 +116,5 @@ declare class TypedAmqpClient<TContract extends ContractDefinition> {
|
|
|
126
116
|
private waitForConnectionReady;
|
|
127
117
|
}
|
|
128
118
|
//#endregion
|
|
129
|
-
export { type ClientInferPublisherInput, type CreateClientOptions, MessageValidationError, type PublishOptions,
|
|
119
|
+
export { type ClientInferPublisherInput, type CreateClientOptions, MessageValidationError, type PublishOptions, TypedAmqpClient };
|
|
130
120
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;KAUK,iCAAiC,oBACpC,gBAAgB;;;AAN4C;KAWzD,mBANiC,CAAA,mBAMM,mBANN,CAAA,GAM6B,gBAN7B,CAOpC,UAPoC,CAAA,SAAA,CAAA,CAAA,SAAA,CAAA,CAAA;;;;AACJ,KAY7B,eAPA,CAAA,kBAOkC,kBAPf,CAAA,GAOqC,WAPrC,CAOiD,SAPjD,CAAA,YAAA,CAAA,CAAA;;;;KAYnB,cAZ8E,CAAA,kBAa/D,kBAb+D,EAAA,cAcnE,mBAdmE,CAc/C,SAd+C,CAAA,CAAA,GAe/E,eAf+E,CAe/D,SAf+D,CAAA,CAepD,KAfoD,CAAA;AAAA;;;AAOtB,KAajD,yBAbiD,CAAA,kBAczC,kBAdyC,EAAA,cAe7C,mBAf6C,CAezB,SAfyB,CAAA,CAAA,GAgBzD,mBAhByD,CAgBrC,cAhBqC,CAgBtB,SAhBsB,EAgBX,KAhBW,CAAA,CAAA;;;;;;cCpBhD,sBAAA,SAA+B,KAAA;;;;;;;;ADEkB;;AAM5D,KEeU,cAAA,GAAiB,OAAA,CAAQ,OFfnC,GAAA;EAAgB;;AAAgB;;;EAKiC,WAAA,CAAA,EEgBnD,oBFhBmD,GAAA,SAAA;CAAgB;AAAA;;;AAOtB,KEejD,mBFfiD,CAAA,kBEeX,kBFfW,CAAA,GAAA;EAAW,QAAA,EEgB5D,SFhB4D;EAKnE,IAAA,EEYG,aFZW,EAAA;EACC,iBAAA,CAAA,EEYE,4BFZF,GAAA,SAAA;EACgB,MAAA,CAAA,EEYzB,MFZyB,GAAA,SAAA;EAApB;;;;;EAMJ,SAAA,CAAA,EEYE,iBFZuB,GAAA,SAAA;CACjB;;;;AAE8B,cEerC,eFfqC,CAAA,kBEeH,kBFfG,CAAA,CAAA;EAA1B,iBAAA,QAAA;EAApB,iBAAA,UAAA;EAAmB,iBAAA,MAAA;;;;ACpCvB;;;;ACuBA;AAYA;;;;EAGsB,OAAA,MAAA,CAAA,kBA+BY,kBA/BZ,CAAA,CAAA;IAAA,QAAA;IAAA,IAAA;IAAA,iBAAA;IAAA,MAAA;IAAA;EAAA,CAAA,EAqCjB,mBArCiB,CAqCG,SArCH,CAAA,CAAA,EAqCgB,MArChB,CAqCuB,MArCvB,CAqC8B,eArC9B,CAqC8C,SArC9C,CAAA,EAqC0D,cArC1D,CAAA,CAAA;EACX;;;AAYX;;;;;;;;;;;EAwBoD;;;;EA6BR,OAAA,CAAA,cAApB,mBAAoB,CAAA,SAAA,CAAA,CAAA,CAAA,aAAA,EACzB,KADyB,EAAA,OAAA,EAE/B,yBAF+B,CAEL,SAFK,EAEM,KAFN,CAAA,EAAA,OAAA,CAAA,EAG9B,cAH8B,CAAA,EAIvC,MAJuC,CAIhC,MAJgC,CAAA,IAAA,EAInB,cAJmB,GAIF,sBAJE,CAAA,CAAA;EAApB;;;EAE0B,KAAA,CAAA,CAAA,EA4FvC,MA5FuC,CA4FhC,MA5FgC,CAAA,IAAA,EA4FnB,cA5FmB,CAAA,CAAA;EAArC,QAAA,sBAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,34 +1,10 @@
|
|
|
1
|
-
import { Logger, TelemetryProvider } from "@amqp-contract/core";
|
|
1
|
+
import { Logger, TechnicalError, TelemetryProvider } from "@amqp-contract/core";
|
|
2
2
|
import { Future, Result } from "@swan-io/boxed";
|
|
3
3
|
import { AmqpConnectionManagerOptions, ConnectionUrl } from "amqp-connection-manager";
|
|
4
4
|
import { CompressionAlgorithm, ContractDefinition, InferPublisherNames, PublisherDefinition } from "@amqp-contract/contract";
|
|
5
5
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
6
6
|
import { Options } from "amqplib";
|
|
7
7
|
|
|
8
|
-
//#region src/errors.d.ts
|
|
9
|
-
/**
|
|
10
|
-
* Base error class for client errors
|
|
11
|
-
*/
|
|
12
|
-
declare abstract class ClientError extends Error {
|
|
13
|
-
protected constructor(message: string);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Error for technical/runtime failures that cannot be prevented by TypeScript
|
|
17
|
-
* This includes validation failures and AMQP channel issues
|
|
18
|
-
*/
|
|
19
|
-
declare class TechnicalError extends ClientError {
|
|
20
|
-
readonly cause?: unknown | undefined;
|
|
21
|
-
constructor(message: string, cause?: unknown | undefined);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Error thrown when message validation fails
|
|
25
|
-
*/
|
|
26
|
-
declare class MessageValidationError extends ClientError {
|
|
27
|
-
readonly publisherName: string;
|
|
28
|
-
readonly issues: unknown;
|
|
29
|
-
constructor(publisherName: string, issues: unknown);
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
8
|
//#region src/types.d.ts
|
|
33
9
|
/**
|
|
34
10
|
* Infer the TypeScript type from a schema
|
|
@@ -51,6 +27,16 @@ type InferPublisher<TContract extends ContractDefinition, TName extends InferPub
|
|
|
51
27
|
*/
|
|
52
28
|
type ClientInferPublisherInput<TContract extends ContractDefinition, TName extends InferPublisherNames<TContract>> = PublisherInferInput<InferPublisher<TContract, TName>>;
|
|
53
29
|
//#endregion
|
|
30
|
+
//#region src/errors.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Error thrown when message validation fails
|
|
33
|
+
*/
|
|
34
|
+
declare class MessageValidationError extends Error {
|
|
35
|
+
readonly publisherName: string;
|
|
36
|
+
readonly issues: unknown;
|
|
37
|
+
constructor(publisherName: string, issues: unknown);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
54
40
|
//#region src/client.d.ts
|
|
55
41
|
/**
|
|
56
42
|
* Publish options that extend amqplib's Options.Publish with optional compression support.
|
|
@@ -118,6 +104,10 @@ declare class TypedAmqpClient<TContract extends ContractDefinition> {
|
|
|
118
104
|
*
|
|
119
105
|
* @returns Result.Ok(void) on success, or Result.Error with specific error on failure
|
|
120
106
|
*/
|
|
107
|
+
/**
|
|
108
|
+
* Publish a message using a defined publisher.
|
|
109
|
+
* TypeScript guarantees publisher exists for valid publisher names.
|
|
110
|
+
*/
|
|
121
111
|
publish<TName extends InferPublisherNames<TContract>>(publisherName: TName, message: ClientInferPublisherInput<TContract, TName>, options?: PublishOptions): Future<Result<void, TechnicalError | MessageValidationError>>;
|
|
122
112
|
/**
|
|
123
113
|
* Close the channel and connection
|
|
@@ -126,5 +116,5 @@ declare class TypedAmqpClient<TContract extends ContractDefinition> {
|
|
|
126
116
|
private waitForConnectionReady;
|
|
127
117
|
}
|
|
128
118
|
//#endregion
|
|
129
|
-
export { type ClientInferPublisherInput, type CreateClientOptions, MessageValidationError, type PublishOptions,
|
|
119
|
+
export { type ClientInferPublisherInput, type CreateClientOptions, MessageValidationError, type PublishOptions, TypedAmqpClient };
|
|
130
120
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;KAUK,iCAAiC,oBACpC,gBAAgB;;;AAN4C;KAWzD,mBANiC,CAAA,mBAMM,mBANN,CAAA,GAM6B,gBAN7B,CAOpC,UAPoC,CAAA,SAAA,CAAA,CAAA,SAAA,CAAA,CAAA;;;;AACJ,KAY7B,eAPA,CAAA,kBAOkC,kBAPf,CAAA,GAOqC,WAPrC,CAOiD,SAPjD,CAAA,YAAA,CAAA,CAAA;;;;KAYnB,cAZ8E,CAAA,kBAa/D,kBAb+D,EAAA,cAcnE,mBAdmE,CAc/C,SAd+C,CAAA,CAAA,GAe/E,eAf+E,CAe/D,SAf+D,CAAA,CAepD,KAfoD,CAAA;AAAA;;;AAOtB,KAajD,yBAbiD,CAAA,kBAczC,kBAdyC,EAAA,cAe7C,mBAf6C,CAezB,SAfyB,CAAA,CAAA,GAgBzD,mBAhByD,CAgBrC,cAhBqC,CAgBtB,SAhBsB,EAgBX,KAhBW,CAAA,CAAA;;;;;;cCpBhD,sBAAA,SAA+B,KAAA;;;;;;;;ADEkB;;AAM5D,KEeU,cAAA,GAAiB,OAAA,CAAQ,OFfnC,GAAA;EAAgB;;AAAgB;;;EAKiC,WAAA,CAAA,EEgBnD,oBFhBmD,GAAA,SAAA;CAAgB;AAAA;;;AAOtB,KEejD,mBFfiD,CAAA,kBEeX,kBFfW,CAAA,GAAA;EAAW,QAAA,EEgB5D,SFhB4D;EAKnE,IAAA,EEYG,aFZW,EAAA;EACC,iBAAA,CAAA,EEYE,4BFZF,GAAA,SAAA;EACgB,MAAA,CAAA,EEYzB,MFZyB,GAAA,SAAA;EAApB;;;;;EAMJ,SAAA,CAAA,EEYE,iBFZuB,GAAA,SAAA;CACjB;;;;AAE8B,cEerC,eFfqC,CAAA,kBEeH,kBFfG,CAAA,CAAA;EAA1B,iBAAA,QAAA;EAApB,iBAAA,UAAA;EAAmB,iBAAA,MAAA;;;;ACpCvB;;;;ACuBA;AAYA;;;;EAGsB,OAAA,MAAA,CAAA,kBA+BY,kBA/BZ,CAAA,CAAA;IAAA,QAAA;IAAA,IAAA;IAAA,iBAAA;IAAA,MAAA;IAAA;EAAA,CAAA,EAqCjB,mBArCiB,CAqCG,SArCH,CAAA,CAAA,EAqCgB,MArChB,CAqCuB,MArCvB,CAqC8B,eArC9B,CAqC8C,SArC9C,CAAA,EAqC0D,cArC1D,CAAA,CAAA;EACX;;;AAYX;;;;;;;;;;;EAwBoD;;;;EA6BR,OAAA,CAAA,cAApB,mBAAoB,CAAA,SAAA,CAAA,CAAA,CAAA,aAAA,EACzB,KADyB,EAAA,OAAA,EAE/B,yBAF+B,CAEL,SAFK,EAEM,KAFN,CAAA,EAAA,OAAA,CAAA,EAG9B,cAH8B,CAAA,EAIvC,MAJuC,CAIhC,MAJgC,CAAA,IAAA,EAInB,cAJmB,GAIF,sBAJE,CAAA,CAAA;EAApB;;;EAE0B,KAAA,CAAA,CAAA,EA4FvC,MA5FuC,CA4FhC,MA5FgC,CAAA,IAAA,EA4FnB,cA5FmB,CAAA,CAAA;EAArC,QAAA,sBAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AmqpClient, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordPublishMetric, startPublishSpan } from "@amqp-contract/core";
|
|
1
|
+
import { AmqpClient, TechnicalError, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordPublishMetric, startPublishSpan } from "@amqp-contract/core";
|
|
2
2
|
import { Future, Result } from "@swan-io/boxed";
|
|
3
3
|
import { deflate, gzip } from "node:zlib";
|
|
4
4
|
import { match } from "ts-pattern";
|
|
@@ -6,36 +6,16 @@ import { promisify } from "node:util";
|
|
|
6
6
|
|
|
7
7
|
//#region src/errors.ts
|
|
8
8
|
/**
|
|
9
|
-
* Base error class for client errors
|
|
10
|
-
*/
|
|
11
|
-
var ClientError = class extends Error {
|
|
12
|
-
constructor(message) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = "ClientError";
|
|
15
|
-
const ErrorConstructor = Error;
|
|
16
|
-
if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Error for technical/runtime failures that cannot be prevented by TypeScript
|
|
21
|
-
* This includes validation failures and AMQP channel issues
|
|
22
|
-
*/
|
|
23
|
-
var TechnicalError = class extends ClientError {
|
|
24
|
-
constructor(message, cause) {
|
|
25
|
-
super(message);
|
|
26
|
-
this.cause = cause;
|
|
27
|
-
this.name = "TechnicalError";
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
9
|
* Error thrown when message validation fails
|
|
32
10
|
*/
|
|
33
|
-
var MessageValidationError = class extends
|
|
11
|
+
var MessageValidationError = class extends Error {
|
|
34
12
|
constructor(publisherName, issues) {
|
|
35
13
|
super(`Message validation failed for publisher "${publisherName}"`);
|
|
36
14
|
this.publisherName = publisherName;
|
|
37
15
|
this.issues = issues;
|
|
38
16
|
this.name = "MessageValidationError";
|
|
17
|
+
const ErrorConstructor = Error;
|
|
18
|
+
if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
|
|
39
19
|
}
|
|
40
20
|
};
|
|
41
21
|
|
|
@@ -48,13 +28,12 @@ const deflateAsync = promisify(deflate);
|
|
|
48
28
|
*
|
|
49
29
|
* @param buffer - The buffer to compress
|
|
50
30
|
* @param algorithm - The compression algorithm to use
|
|
51
|
-
* @returns A
|
|
52
|
-
* @throws Error if compression fails
|
|
31
|
+
* @returns A Future with the compressed buffer or a TechnicalError
|
|
53
32
|
*
|
|
54
33
|
* @internal
|
|
55
34
|
*/
|
|
56
|
-
|
|
57
|
-
return match(algorithm).with("gzip", () => gzipAsync(buffer)).with("deflate", () => deflateAsync(buffer)).exhaustive();
|
|
35
|
+
function compressBuffer(buffer, algorithm) {
|
|
36
|
+
return match(algorithm).with("gzip", () => Future.fromPromise(gzipAsync(buffer)).mapError((error) => new TechnicalError("Failed to compress with gzip", error))).with("deflate", () => Future.fromPromise(deflateAsync(buffer)).mapError((error) => new TechnicalError("Failed to compress with deflate", error))).exhaustive();
|
|
58
37
|
}
|
|
59
38
|
|
|
60
39
|
//#endregion
|
|
@@ -100,15 +79,15 @@ var TypedAmqpClient = class TypedAmqpClient {
|
|
|
100
79
|
*
|
|
101
80
|
* @returns Result.Ok(void) on success, or Result.Error with specific error on failure
|
|
102
81
|
*/
|
|
82
|
+
/**
|
|
83
|
+
* Publish a message using a defined publisher.
|
|
84
|
+
* TypeScript guarantees publisher exists for valid publisher names.
|
|
85
|
+
*/
|
|
103
86
|
publish(publisherName, message, options) {
|
|
104
87
|
const startTime = Date.now();
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
if (!publisher) return Future.value(Result.Error(new TechnicalError(`Publisher "${String(publisherName)}" not found in contract`)));
|
|
109
|
-
const exchangeName = publisher.exchange.name;
|
|
110
|
-
const routingKey = publisher.routingKey;
|
|
111
|
-
const span = startPublishSpan(this.telemetry, exchangeName, routingKey, { "amqp.publisher.name": String(publisherName) });
|
|
88
|
+
const publisher = this.contract.publishers[publisherName];
|
|
89
|
+
const { exchange, routingKey } = publisher;
|
|
90
|
+
const span = startPublishSpan(this.telemetry, exchange.name, routingKey, { "amqp.publisher.name": String(publisherName) });
|
|
112
91
|
const validateMessage = () => {
|
|
113
92
|
const validationResult = publisher.message.payload["~standard"].validate(message);
|
|
114
93
|
return Future.fromPromise(validationResult instanceof Promise ? validationResult : Promise.resolve(validationResult)).mapError((error) => new TechnicalError(`Validation failed`, error)).mapOkToResult((validation) => {
|
|
@@ -123,11 +102,11 @@ var TypedAmqpClient = class TypedAmqpClient {
|
|
|
123
102
|
if (compression) {
|
|
124
103
|
const messageBuffer = Buffer.from(JSON.stringify(validatedMessage));
|
|
125
104
|
publishOptions.contentEncoding = compression;
|
|
126
|
-
return
|
|
105
|
+
return compressBuffer(messageBuffer, compression);
|
|
127
106
|
}
|
|
128
107
|
return Future.value(Result.Ok(validatedMessage));
|
|
129
108
|
};
|
|
130
|
-
return preparePayload().flatMapOk((payload) =>
|
|
109
|
+
return preparePayload().flatMapOk((payload) => this.amqpClient.publish(publisher.exchange.name, publisher.routingKey ?? "", payload, publishOptions).mapOkToResult((published) => {
|
|
131
110
|
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)`));
|
|
132
111
|
this.logger?.info("Message published successfully", {
|
|
133
112
|
publisherName: String(publisherName),
|
|
@@ -141,24 +120,24 @@ var TypedAmqpClient = class TypedAmqpClient {
|
|
|
141
120
|
return validateMessage().flatMapOk((validatedMessage) => publishMessage(validatedMessage)).tapOk(() => {
|
|
142
121
|
const durationMs = Date.now() - startTime;
|
|
143
122
|
endSpanSuccess(span);
|
|
144
|
-
recordPublishMetric(this.telemetry,
|
|
123
|
+
recordPublishMetric(this.telemetry, exchange.name, routingKey, true, durationMs);
|
|
145
124
|
}).tapError((error) => {
|
|
146
125
|
const durationMs = Date.now() - startTime;
|
|
147
126
|
endSpanError(span, error);
|
|
148
|
-
recordPublishMetric(this.telemetry,
|
|
127
|
+
recordPublishMetric(this.telemetry, exchange.name, routingKey, false, durationMs);
|
|
149
128
|
});
|
|
150
129
|
}
|
|
151
130
|
/**
|
|
152
131
|
* Close the channel and connection
|
|
153
132
|
*/
|
|
154
133
|
close() {
|
|
155
|
-
return
|
|
134
|
+
return this.amqpClient.close().mapOk(() => void 0);
|
|
156
135
|
}
|
|
157
136
|
waitForConnectionReady() {
|
|
158
|
-
return
|
|
137
|
+
return this.amqpClient.waitForConnect();
|
|
159
138
|
}
|
|
160
139
|
};
|
|
161
140
|
|
|
162
141
|
//#endregion
|
|
163
|
-
export { MessageValidationError,
|
|
142
|
+
export { MessageValidationError, TypedAmqpClient };
|
|
164
143
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/errors.ts","../src/compression.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 { deflate, gzip } from \"node:zlib\";\nimport type { CompressionAlgorithm } from \"@amqp-contract/contract\";\nimport { match } from \"ts-pattern\";\nimport { promisify } from \"node:util\";\n\nconst gzipAsync = promisify(gzip);\nconst deflateAsync = promisify(deflate);\n\n/**\n * Compress a buffer using the specified compression algorithm.\n *\n * @param buffer - The buffer to compress\n * @param algorithm - The compression algorithm to use\n * @returns A promise that resolves to the compressed buffer\n * @throws Error if compression fails\n *\n * @internal\n */\nexport async function compressBuffer(\n buffer: Buffer,\n algorithm: CompressionAlgorithm,\n): Promise<Buffer> {\n return match(algorithm)\n .with(\"gzip\", () => gzipAsync(buffer))\n .with(\"deflate\", () => deflateAsync(buffer))\n .exhaustive();\n}\n","import {\n AmqpClient,\n type Logger,\n type TelemetryProvider,\n defaultTelemetryProvider,\n endSpanError,\n endSpanSuccess,\n recordPublishMetric,\n startPublishSpan,\n} from \"@amqp-contract/core\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\nimport type {\n CompressionAlgorithm,\n ContractDefinition,\n InferPublisherNames,\n} from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport { MessageValidationError, TechnicalError } from \"./errors.js\";\nimport type { ClientInferPublisherInput } from \"./types.js\";\nimport type { Options } from \"amqplib\";\nimport { compressBuffer } from \"./compression.js\";\n\n/**\n * Publish options that extend amqplib's Options.Publish with optional compression support.\n */\nexport type PublishOptions = Options.Publish & {\n /**\n * Optional compression algorithm to use for the message payload.\n * When specified, the message will be compressed using the chosen algorithm\n * and the contentEncoding header will be set automatically.\n */\n compression?: CompressionAlgorithm | undefined;\n};\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 logger?: Logger | undefined;\n /**\n * Optional telemetry provider for tracing and metrics.\n * If not provided, uses the default provider which attempts to load OpenTelemetry.\n * OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed.\n */\n telemetry?: TelemetryProvider | 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 private readonly logger?: Logger,\n private readonly telemetry: TelemetryProvider = defaultTelemetryProvider,\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 * Connections are automatically shared across clients with the same URLs and\n * connection options, following RabbitMQ best practices.\n */\n static create<TContract extends ContractDefinition>({\n contract,\n urls,\n connectionOptions,\n logger,\n telemetry,\n }: CreateClientOptions<TContract>): Future<Result<TypedAmqpClient<TContract>, TechnicalError>> {\n const client = new TypedAmqpClient(\n contract,\n new AmqpClient(contract, { urls, connectionOptions }),\n logger,\n telemetry ?? defaultTelemetryProvider,\n );\n\n return client.waitForConnectionReady().mapOk(() => client);\n }\n\n /**\n * Publish a message using a defined publisher\n *\n * @param publisherName - The name of the publisher to use\n * @param message - The message to publish\n * @param options - Optional publish options including compression, headers, priority, etc.\n *\n * @remarks\n * If `options.compression` is specified, the message will be compressed before publishing\n * and the `contentEncoding` property will be set automatically. Any `contentEncoding`\n * value already in options will be overwritten by the compression algorithm.\n *\n * @returns Result.Ok(void) 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 ): Future<Result<void, TechnicalError | MessageValidationError>> {\n const startTime = Date.now();\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 exchangeName = publisher.exchange.name;\n const routingKey = publisher.routingKey;\n\n // Start telemetry span\n const span = startPublishSpan(this.telemetry, exchangeName, routingKey, {\n \"amqp.publisher.name\": String(publisherName),\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): Future<Result<void, TechnicalError>> => {\n // Extract compression from options and create publish options without it\n const { compression, ...restOptions } = options ?? {};\n const publishOptions: Options.Publish = { ...restOptions };\n\n // Prepare payload and options based on compression configuration\n const preparePayload = (): Future<Result<Buffer | unknown, TechnicalError>> => {\n if (compression) {\n // Compress the message payload\n const messageBuffer = Buffer.from(JSON.stringify(validatedMessage));\n publishOptions.contentEncoding = compression;\n\n return Future.fromPromise(compressBuffer(messageBuffer, compression))\n .mapError((error) => new TechnicalError(`Failed to compress message`, error))\n .map((compressedBuffer) => Result.Ok(compressedBuffer));\n }\n\n // No compression: use the channel's built-in JSON serialization\n return Future.value(Result.Ok(validatedMessage));\n };\n\n // Publish the prepared payload\n return preparePayload().flatMapOk((payload) =>\n Future.fromPromise(\n this.amqpClient.channel.publish(\n publisher.exchange.name,\n publisher.routingKey ?? \"\",\n payload,\n publishOptions,\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 this.logger?.info(\"Message published successfully\", {\n publisherName: String(publisherName),\n exchange: publisher.exchange.name,\n routingKey: publisher.routingKey,\n compressed: !!compression,\n });\n\n return Result.Ok(undefined);\n }),\n );\n };\n\n // Validate message using schema\n return validateMessage()\n .flatMapOk((validatedMessage) => publishMessage(validatedMessage))\n .tapOk(() => {\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordPublishMetric(this.telemetry, exchangeName, routingKey, true, durationMs);\n })\n .tapError((error) => {\n const durationMs = Date.now() - startTime;\n endSpanError(span, error);\n recordPublishMetric(this.telemetry, exchangeName, routingKey, false, durationMs);\n });\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":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/errors.ts","../src/compression.ts","../src/client.ts"],"sourcesContent":["/**\n * Error thrown when message validation fails\n */\nexport class MessageValidationError extends Error {\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 // Node.js specific stack trace capture\n const ErrorConstructor = Error as unknown as {\n captureStackTrace?: (target: object, constructor: Function) => void;\n };\n if (typeof ErrorConstructor.captureStackTrace === \"function\") {\n ErrorConstructor.captureStackTrace(this, this.constructor);\n }\n }\n}\n","import { Future, Result } from \"@swan-io/boxed\";\nimport { deflate, gzip } from \"node:zlib\";\nimport type { CompressionAlgorithm } from \"@amqp-contract/contract\";\nimport { TechnicalError } from \"@amqp-contract/core\";\nimport { match } from \"ts-pattern\";\nimport { promisify } from \"node:util\";\n\nconst gzipAsync = promisify(gzip);\nconst deflateAsync = promisify(deflate);\n\n/**\n * Compress a buffer using the specified compression algorithm.\n *\n * @param buffer - The buffer to compress\n * @param algorithm - The compression algorithm to use\n * @returns A Future with the compressed buffer or a TechnicalError\n *\n * @internal\n */\nexport function compressBuffer(\n buffer: Buffer,\n algorithm: CompressionAlgorithm,\n): Future<Result<Buffer, TechnicalError>> {\n return match(algorithm)\n .with(\"gzip\", () =>\n Future.fromPromise(gzipAsync(buffer)).mapError(\n (error) => new TechnicalError(\"Failed to compress with gzip\", error),\n ),\n )\n .with(\"deflate\", () =>\n Future.fromPromise(deflateAsync(buffer)).mapError(\n (error) => new TechnicalError(\"Failed to compress with deflate\", error),\n ),\n )\n .exhaustive();\n}\n","import {\n AmqpClient,\n type Logger,\n TechnicalError,\n type TelemetryProvider,\n defaultTelemetryProvider,\n endSpanError,\n endSpanSuccess,\n recordPublishMetric,\n startPublishSpan,\n} from \"@amqp-contract/core\";\nimport type { AmqpConnectionManagerOptions, ConnectionUrl } from \"amqp-connection-manager\";\nimport type {\n CompressionAlgorithm,\n ContractDefinition,\n InferPublisherNames,\n} from \"@amqp-contract/contract\";\nimport { Future, Result } from \"@swan-io/boxed\";\nimport type { ClientInferPublisherInput } from \"./types.js\";\nimport { MessageValidationError } from \"./errors.js\";\nimport type { Options } from \"amqplib\";\nimport { compressBuffer } from \"./compression.js\";\n\n/**\n * Publish options that extend amqplib's Options.Publish with optional compression support.\n */\nexport type PublishOptions = Options.Publish & {\n /**\n * Optional compression algorithm to use for the message payload.\n * When specified, the message will be compressed using the chosen algorithm\n * and the contentEncoding header will be set automatically.\n */\n compression?: CompressionAlgorithm | undefined;\n};\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 logger?: Logger | undefined;\n /**\n * Optional telemetry provider for tracing and metrics.\n * If not provided, uses the default provider which attempts to load OpenTelemetry.\n * OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed.\n */\n telemetry?: TelemetryProvider | 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 private readonly logger?: Logger,\n private readonly telemetry: TelemetryProvider = defaultTelemetryProvider,\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 * Connections are automatically shared across clients with the same URLs and\n * connection options, following RabbitMQ best practices.\n */\n static create<TContract extends ContractDefinition>({\n contract,\n urls,\n connectionOptions,\n logger,\n telemetry,\n }: CreateClientOptions<TContract>): Future<Result<TypedAmqpClient<TContract>, TechnicalError>> {\n const client = new TypedAmqpClient(\n contract,\n new AmqpClient(contract, { urls, connectionOptions }),\n logger,\n telemetry ?? defaultTelemetryProvider,\n );\n\n return client.waitForConnectionReady().mapOk(() => client);\n }\n\n /**\n * Publish a message using a defined publisher\n *\n * @param publisherName - The name of the publisher to use\n * @param message - The message to publish\n * @param options - Optional publish options including compression, headers, priority, etc.\n *\n * @remarks\n * If `options.compression` is specified, the message will be compressed before publishing\n * and the `contentEncoding` property will be set automatically. Any `contentEncoding`\n * value already in options will be overwritten by the compression algorithm.\n *\n * @returns Result.Ok(void) on success, or Result.Error with specific error on failure\n */\n /**\n * Publish a message using a defined publisher.\n * TypeScript guarantees publisher exists for valid publisher names.\n */\n publish<TName extends InferPublisherNames<TContract>>(\n publisherName: TName,\n message: ClientInferPublisherInput<TContract, TName>,\n options?: PublishOptions,\n ): Future<Result<void, TechnicalError | MessageValidationError>> {\n const startTime = Date.now();\n // Non-null assertions safe: TypeScript guarantees these exist for valid TName\n const publisher = this.contract.publishers![publisherName as string]!;\n const { exchange, routingKey } = publisher;\n\n // Start telemetry span\n const span = startPublishSpan(this.telemetry, exchange.name, routingKey, {\n \"amqp.publisher.name\": String(publisherName),\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): Future<Result<void, TechnicalError>> => {\n // Extract compression from options and create publish options without it\n const { compression, ...restOptions } = options ?? {};\n const publishOptions: Options.Publish = { ...restOptions };\n\n // Prepare payload and options based on compression configuration\n const preparePayload = (): Future<Result<Buffer | unknown, TechnicalError>> => {\n if (compression) {\n // Compress the message payload\n const messageBuffer = Buffer.from(JSON.stringify(validatedMessage));\n publishOptions.contentEncoding = compression;\n\n return compressBuffer(messageBuffer, compression);\n }\n\n // No compression: use the channel's built-in JSON serialization\n return Future.value(Result.Ok(validatedMessage));\n };\n\n // Publish the prepared payload\n return preparePayload().flatMapOk((payload) =>\n this.amqpClient\n .publish(publisher.exchange.name, publisher.routingKey ?? \"\", payload, publishOptions)\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 this.logger?.info(\"Message published successfully\", {\n publisherName: String(publisherName),\n exchange: publisher.exchange.name,\n routingKey: publisher.routingKey,\n compressed: !!compression,\n });\n\n return Result.Ok(undefined);\n }),\n );\n };\n\n // Validate message using schema\n return validateMessage()\n .flatMapOk((validatedMessage) => publishMessage(validatedMessage))\n .tapOk(() => {\n const durationMs = Date.now() - startTime;\n endSpanSuccess(span);\n recordPublishMetric(this.telemetry, exchange.name, routingKey, true, durationMs);\n })\n .tapError((error) => {\n const durationMs = Date.now() - startTime;\n endSpanError(span, error);\n recordPublishMetric(this.telemetry, exchange.name, routingKey, false, durationMs);\n });\n }\n\n /**\n * Close the channel and connection\n */\n close(): Future<Result<void, TechnicalError>> {\n return this.amqpClient.close().mapOk(() => undefined);\n }\n\n private waitForConnectionReady(): Future<Result<void, TechnicalError>> {\n return this.amqpClient.waitForConnect();\n }\n}\n"],"mappings":"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"}
|
package/docs/index.md
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
### MessageValidationError
|
|
10
10
|
|
|
11
|
-
Defined in: [packages/client/src/errors.ts:
|
|
11
|
+
Defined in: [packages/client/src/errors.ts:4](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/errors.ts#L4)
|
|
12
12
|
|
|
13
13
|
Error thrown when message validation fails
|
|
14
14
|
|
|
15
15
|
#### Extends
|
|
16
16
|
|
|
17
|
-
- `
|
|
17
|
+
- `Error`
|
|
18
18
|
|
|
19
19
|
#### Constructors
|
|
20
20
|
|
|
@@ -24,7 +24,7 @@ Error thrown when message validation fails
|
|
|
24
24
|
new MessageValidationError(publisherName, issues): MessageValidationError;
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Defined in: [packages/client/src/errors.ts:
|
|
27
|
+
Defined in: [packages/client/src/errors.ts:5](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/errors.ts#L5)
|
|
28
28
|
|
|
29
29
|
###### Parameters
|
|
30
30
|
|
|
@@ -40,20 +40,20 @@ Defined in: [packages/client/src/errors.ts:36](https://github.com/btravers/amqp-
|
|
|
40
40
|
###### Overrides
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
|
-
|
|
43
|
+
Error.constructor
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
#### Properties
|
|
47
47
|
|
|
48
48
|
| Property | Modifier | Type | Description | Inherited from | Defined in |
|
|
49
49
|
| ------ | ------ | ------ | ------ | ------ | ------ |
|
|
50
|
-
| <a id="cause"></a> `cause?` | `public` | `unknown` | - | `
|
|
51
|
-
| <a id="issues"></a> `issues` | `readonly` | `unknown` | - | - | [packages/client/src/errors.ts:
|
|
52
|
-
| <a id="message"></a> `message` | `public` | `string` | - | `
|
|
53
|
-
| <a id="name"></a> `name` | `public` | `string` | - | `
|
|
54
|
-
| <a id="publishername"></a> `publisherName` | `readonly` | `string` | - | - | [packages/client/src/errors.ts:
|
|
55
|
-
| <a id="stack"></a> `stack?` | `public` | `string` | - | `
|
|
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. | `
|
|
50
|
+
| <a id="cause"></a> `cause?` | `public` | `unknown` | - | `Error.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:7](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/errors.ts#L7) |
|
|
52
|
+
| <a id="message"></a> `message` | `public` | `string` | - | `Error.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` | - | `Error.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:6](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/errors.ts#L6) |
|
|
55
|
+
| <a id="stack"></a> `stack?` | `public` | `string` | - | `Error.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. | `Error.stackTraceLimit` | node\_modules/.pnpm/@types+node@25.0.9/node\_modules/@types/node/globals.d.ts:67 |
|
|
57
57
|
|
|
58
58
|
#### Methods
|
|
59
59
|
|
|
@@ -63,7 +63,7 @@ ClientError.constructor
|
|
|
63
63
|
static captureStackTrace(targetObject, constructorOpt?): void;
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
Defined in: node\_modules/.pnpm/@types+node@25.0.
|
|
66
|
+
Defined in: node\_modules/.pnpm/@types+node@25.0.9/node\_modules/@types/node/globals.d.ts:51
|
|
67
67
|
|
|
68
68
|
Creates a `.stack` property on `targetObject`, which when accessed returns
|
|
69
69
|
a string representing the location in the code at which
|
|
@@ -123,7 +123,7 @@ a();
|
|
|
123
123
|
###### Inherited from
|
|
124
124
|
|
|
125
125
|
```ts
|
|
126
|
-
|
|
126
|
+
Error.captureStackTrace
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
##### prepareStackTrace()
|
|
@@ -132,7 +132,7 @@ ClientError.captureStackTrace
|
|
|
132
132
|
static prepareStackTrace(err, stackTraces): any;
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
Defined in: node\_modules/.pnpm/@types+node@25.0.
|
|
135
|
+
Defined in: node\_modules/.pnpm/@types+node@25.0.9/node\_modules/@types/node/globals.d.ts:55
|
|
136
136
|
|
|
137
137
|
###### Parameters
|
|
138
138
|
|
|
@@ -152,164 +152,14 @@ https://v8.dev/docs/stack-trace-api#customizing-stack-traces
|
|
|
152
152
|
###### Inherited from
|
|
153
153
|
|
|
154
154
|
```ts
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
***
|
|
159
|
-
|
|
160
|
-
### TechnicalError
|
|
161
|
-
|
|
162
|
-
Defined in: [packages/client/src/errors.ts:22](https://github.com/btravers/amqp-contract/blob/00468eb63a019d6161c43f07dab8b68232bbb383/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/00468eb63a019d6161c43f07dab8b68232bbb383/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/00468eb63a019d6161c43f07dab8b68232bbb383/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.5/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.5/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.5/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
|
|
155
|
+
Error.prepareStackTrace
|
|
306
156
|
```
|
|
307
157
|
|
|
308
158
|
***
|
|
309
159
|
|
|
310
160
|
### TypedAmqpClient
|
|
311
161
|
|
|
312
|
-
Defined in: [packages/client/src/client.ts:
|
|
162
|
+
Defined in: [packages/client/src/client.ts:55](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L55)
|
|
313
163
|
|
|
314
164
|
Type-safe AMQP client for publishing messages
|
|
315
165
|
|
|
@@ -327,13 +177,13 @@ Type-safe AMQP client for publishing messages
|
|
|
327
177
|
close(): Future<Result<void, TechnicalError>>;
|
|
328
178
|
```
|
|
329
179
|
|
|
330
|
-
Defined in: [packages/client/src/client.ts:
|
|
180
|
+
Defined in: [packages/client/src/client.ts:202](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L202)
|
|
331
181
|
|
|
332
182
|
Close the channel and connection
|
|
333
183
|
|
|
334
184
|
###### Returns
|
|
335
185
|
|
|
336
|
-
`Future
|
|
186
|
+
`Future`<`Result`<`void`, `TechnicalError`>>
|
|
337
187
|
|
|
338
188
|
##### publish()
|
|
339
189
|
|
|
@@ -341,14 +191,13 @@ Close the channel and connection
|
|
|
341
191
|
publish<TName>(
|
|
342
192
|
publisherName,
|
|
343
193
|
message,
|
|
344
|
-
options?): Future<Result<void,
|
|
345
|
-
| TechnicalError
|
|
346
|
-
| MessageValidationError>>;
|
|
194
|
+
options?): Future<Result<void, TechnicalError | MessageValidationError>>;
|
|
347
195
|
```
|
|
348
196
|
|
|
349
|
-
Defined in: [packages/client/src/client.ts:
|
|
197
|
+
Defined in: [packages/client/src/client.ts:108](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L108)
|
|
350
198
|
|
|
351
|
-
Publish a message using a defined publisher
|
|
199
|
+
Publish a message using a defined publisher.
|
|
200
|
+
TypeScript guarantees publisher exists for valid publisher names.
|
|
352
201
|
|
|
353
202
|
###### Type Parameters
|
|
354
203
|
|
|
@@ -358,25 +207,15 @@ Publish a message using a defined publisher
|
|
|
358
207
|
|
|
359
208
|
###### Parameters
|
|
360
209
|
|
|
361
|
-
| Parameter | Type |
|
|
362
|
-
| ------ | ------ |
|
|
363
|
-
| `publisherName` | `TName` |
|
|
364
|
-
| `message` | [`ClientInferPublisherInput`](#clientinferpublisherinput)
|
|
365
|
-
| `options?` | [`PublishOptions`](#publishoptions) |
|
|
210
|
+
| Parameter | Type |
|
|
211
|
+
| ------ | ------ |
|
|
212
|
+
| `publisherName` | `TName` |
|
|
213
|
+
| `message` | [`ClientInferPublisherInput`](#clientinferpublisherinput)<`TContract`, `TName`> |
|
|
214
|
+
| `options?` | [`PublishOptions`](#publishoptions) |
|
|
366
215
|
|
|
367
216
|
###### Returns
|
|
368
217
|
|
|
369
|
-
`Future
|
|
370
|
-
\| [`TechnicalError`](#technicalerror)
|
|
371
|
-
\| [`MessageValidationError`](#messagevalidationerror)\>\>
|
|
372
|
-
|
|
373
|
-
Result.Ok(void) on success, or Result.Error with specific error on failure
|
|
374
|
-
|
|
375
|
-
###### Remarks
|
|
376
|
-
|
|
377
|
-
If `options.compression` is specified, the message will be compressed before publishing
|
|
378
|
-
and the `contentEncoding` property will be set automatically. Any `contentEncoding`
|
|
379
|
-
value already in options will be overwritten by the compression algorithm.
|
|
218
|
+
`Future`<`Result`<`void`, `TechnicalError` \| [`MessageValidationError`](#messagevalidationerror)>>
|
|
380
219
|
|
|
381
220
|
##### create()
|
|
382
221
|
|
|
@@ -384,7 +223,7 @@ value already in options will be overwritten by the compression algorithm.
|
|
|
384
223
|
static create<TContract>(__namedParameters): Future<Result<TypedAmqpClient<TContract>, TechnicalError>>;
|
|
385
224
|
```
|
|
386
225
|
|
|
387
|
-
Defined in: [packages/client/src/client.ts:
|
|
226
|
+
Defined in: [packages/client/src/client.ts:73](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L73)
|
|
388
227
|
|
|
389
228
|
Create a type-safe AMQP client from a contract.
|
|
390
229
|
|
|
@@ -405,11 +244,11 @@ connection options, following RabbitMQ best practices.
|
|
|
405
244
|
|
|
406
245
|
| Parameter | Type |
|
|
407
246
|
| ------ | ------ |
|
|
408
|
-
| `__namedParameters` | [`CreateClientOptions`](#createclientoptions)
|
|
247
|
+
| `__namedParameters` | [`CreateClientOptions`](#createclientoptions)<`TContract`> |
|
|
409
248
|
|
|
410
249
|
###### Returns
|
|
411
250
|
|
|
412
|
-
`Future
|
|
251
|
+
`Future`<`Result`<[`TypedAmqpClient`](#typedamqpclient)<`TContract`>, `TechnicalError`>>
|
|
413
252
|
|
|
414
253
|
## Type Aliases
|
|
415
254
|
|
|
@@ -419,7 +258,7 @@ connection options, following RabbitMQ best practices.
|
|
|
419
258
|
type ClientInferPublisherInput<TContract, TName> = PublisherInferInput<InferPublisher<TContract, TName>>;
|
|
420
259
|
```
|
|
421
260
|
|
|
422
|
-
Defined in: [packages/client/src/types.ts:37](https://github.com/btravers/amqp-contract/blob/
|
|
261
|
+
Defined in: [packages/client/src/types.ts:37](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/types.ts#L37)
|
|
423
262
|
|
|
424
263
|
Infer publisher input type (message payload) for a specific publisher in a contract
|
|
425
264
|
|
|
@@ -428,7 +267,7 @@ Infer publisher input type (message payload) for a specific publisher in a contr
|
|
|
428
267
|
| Type Parameter |
|
|
429
268
|
| ------ |
|
|
430
269
|
| `TContract` *extends* `ContractDefinition` |
|
|
431
|
-
| `TName` *extends* `InferPublisherNames
|
|
270
|
+
| `TName` *extends* `InferPublisherNames`<`TContract`> |
|
|
432
271
|
|
|
433
272
|
***
|
|
434
273
|
|
|
@@ -438,7 +277,7 @@ Infer publisher input type (message payload) for a specific publisher in a contr
|
|
|
438
277
|
type CreateClientOptions<TContract> = object;
|
|
439
278
|
```
|
|
440
279
|
|
|
441
|
-
Defined in: [packages/client/src/client.ts:
|
|
280
|
+
Defined in: [packages/client/src/client.ts:39](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L39)
|
|
442
281
|
|
|
443
282
|
Options for creating a client
|
|
444
283
|
|
|
@@ -452,11 +291,11 @@ Options for creating a client
|
|
|
452
291
|
|
|
453
292
|
| Property | Type | Description | Defined in |
|
|
454
293
|
| ------ | ------ | ------ | ------ |
|
|
455
|
-
| <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | - | [packages/client/src/client.ts:
|
|
456
|
-
| <a id="contract"></a> `contract` | `TContract` | - | [packages/client/src/client.ts:
|
|
457
|
-
| <a id="logger"></a> `logger?` | `Logger` | - | [packages/client/src/client.ts:
|
|
458
|
-
| <a id="telemetry"></a> `telemetry?` | `TelemetryProvider` | Optional telemetry provider for tracing and metrics. If not provided, uses the default provider which attempts to load OpenTelemetry. OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed. | [packages/client/src/client.ts:
|
|
459
|
-
| <a id="urls"></a> `urls` | `ConnectionUrl`[] | - | [packages/client/src/client.ts:
|
|
294
|
+
| <a id="connectionoptions"></a> `connectionOptions?` | `AmqpConnectionManagerOptions` | - | [packages/client/src/client.ts:42](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L42) |
|
|
295
|
+
| <a id="contract"></a> `contract` | `TContract` | - | [packages/client/src/client.ts:40](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L40) |
|
|
296
|
+
| <a id="logger"></a> `logger?` | `Logger` | - | [packages/client/src/client.ts:43](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L43) |
|
|
297
|
+
| <a id="telemetry"></a> `telemetry?` | `TelemetryProvider` | Optional telemetry provider for tracing and metrics. If not provided, uses the default provider which attempts to load OpenTelemetry. OpenTelemetry instrumentation is automatically enabled if @opentelemetry/api is installed. | [packages/client/src/client.ts:49](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L49) |
|
|
298
|
+
| <a id="urls"></a> `urls` | `ConnectionUrl`[] | - | [packages/client/src/client.ts:41](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L41) |
|
|
460
299
|
|
|
461
300
|
***
|
|
462
301
|
|
|
@@ -466,7 +305,7 @@ Options for creating a client
|
|
|
466
305
|
type PublishOptions = Options.Publish & object;
|
|
467
306
|
```
|
|
468
307
|
|
|
469
|
-
Defined in: [packages/client/src/client.ts:
|
|
308
|
+
Defined in: [packages/client/src/client.ts:27](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L27)
|
|
470
309
|
|
|
471
310
|
Publish options that extend amqplib's Options.Publish with optional compression support.
|
|
472
311
|
|
|
@@ -474,4 +313,4 @@ Publish options that extend amqplib's Options.Publish with optional compression
|
|
|
474
313
|
|
|
475
314
|
| Name | Type | Description | Defined in |
|
|
476
315
|
| ------ | ------ | ------ | ------ |
|
|
477
|
-
| `compression?` | `CompressionAlgorithm` | Optional compression algorithm to use for the message payload. When specified, the message will be compressed using the chosen algorithm and the contentEncoding header will be set automatically. | [packages/client/src/client.ts:
|
|
316
|
+
| `compression?` | `CompressionAlgorithm` | Optional compression algorithm to use for the message payload. When specified, the message will be compressed using the chosen algorithm and the contentEncoding header will be set automatically. | [packages/client/src/client.ts:33](https://github.com/btravers/amqp-contract/blob/c50b9d5f661ce899d34d7eebcfafa47844bd1087/packages/client/src/client.ts#L33) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amqp-contract/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Client utilities for publishing messages using amqp-contract",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"amqp",
|
|
@@ -53,22 +53,22 @@
|
|
|
53
53
|
"@standard-schema/spec": "1.1.0",
|
|
54
54
|
"@swan-io/boxed": "3.2.1",
|
|
55
55
|
"ts-pattern": "5.9.0",
|
|
56
|
-
"@amqp-contract/contract": "0.
|
|
57
|
-
"@amqp-contract/core": "0.
|
|
56
|
+
"@amqp-contract/contract": "0.12.0",
|
|
57
|
+
"@amqp-contract/core": "0.12.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/amqplib": "0.10.8",
|
|
61
|
-
"@types/node": "25.0.
|
|
62
|
-
"@vitest/coverage-v8": "4.0.
|
|
61
|
+
"@types/node": "25.0.9",
|
|
62
|
+
"@vitest/coverage-v8": "4.0.17",
|
|
63
63
|
"amqp-connection-manager": "5.0.0",
|
|
64
64
|
"amqplib": "0.10.9",
|
|
65
65
|
"tsdown": "0.19.0",
|
|
66
|
-
"typedoc": "0.28.
|
|
66
|
+
"typedoc": "0.28.16",
|
|
67
67
|
"typedoc-plugin-markdown": "4.9.0",
|
|
68
68
|
"typescript": "5.9.3",
|
|
69
|
-
"vitest": "4.0.
|
|
69
|
+
"vitest": "4.0.17",
|
|
70
70
|
"zod": "4.3.5",
|
|
71
|
-
"@amqp-contract/testing": "0.
|
|
71
|
+
"@amqp-contract/testing": "0.12.0",
|
|
72
72
|
"@amqp-contract/tsconfig": "0.1.0",
|
|
73
73
|
"@amqp-contract/typedoc": "0.1.0"
|
|
74
74
|
},
|