@amqp-contract/core 0.24.0 → 1.0.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 +6 -6
- package/dist/index.cjs +122 -41
- package/dist/index.d.cts +100 -27
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +100 -27
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +121 -41
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +211 -518
- package/package.json +16 -12
package/dist/index.d.mts
CHANGED
|
@@ -1,32 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AsyncResult, Result } from "unthrown";
|
|
2
2
|
import { AmqpConnectionManager, AmqpConnectionManagerOptions, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
|
|
3
3
|
import { ContractDefinition } from "@amqp-contract/contract";
|
|
4
4
|
import { Attributes, Counter, Histogram, Span, Tracer } from "@opentelemetry/api";
|
|
5
5
|
import { Channel, ConsumeMessage, Options } from "amqplib";
|
|
6
6
|
|
|
7
7
|
//#region src/errors.d.ts
|
|
8
|
+
declare const TechnicalError_base: import("unthrown").TaggedErrorConstructor<"@amqp-contract/TechnicalError">;
|
|
8
9
|
/**
|
|
9
10
|
* Error for technical/runtime failures that cannot be prevented by TypeScript.
|
|
10
11
|
*
|
|
11
12
|
* This includes AMQP connection failures, channel issues, validation failures,
|
|
12
13
|
* and other runtime errors. This error is shared across core, worker, and client packages.
|
|
14
|
+
*
|
|
15
|
+
* Built on unthrown's {@link TaggedError}, so it carries a `_tag` of
|
|
16
|
+
* `"@amqp-contract/TechnicalError"` for exhaustive dispatch via `matchTags`. The
|
|
17
|
+
* tag is namespaced to avoid colliding with other libraries' tags in a shared
|
|
18
|
+
* `matchTags`; the human-facing `Error.name` is kept bare (`"TechnicalError"`).
|
|
19
|
+
* Remains a real `Error` (and a *modeled* error — it lives in the `E` channel of
|
|
20
|
+
* a `Result`, never the `Defect` channel).
|
|
13
21
|
*/
|
|
14
|
-
declare class TechnicalError extends
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
declare class TechnicalError extends TechnicalError_base<{
|
|
23
|
+
message: string;
|
|
24
|
+
cause?: unknown;
|
|
25
|
+
}> {
|
|
26
|
+
constructor(message: string, cause?: unknown);
|
|
17
27
|
}
|
|
28
|
+
declare const MessageValidationError_base: import("unthrown").TaggedErrorConstructor<"@amqp-contract/MessageValidationError">;
|
|
18
29
|
/**
|
|
19
30
|
* Error thrown when message validation fails (payload or headers).
|
|
20
31
|
*
|
|
21
32
|
* Used by both the client (publish-time payload validation) and the worker
|
|
22
|
-
* (consume-time payload and headers validation).
|
|
33
|
+
* (consume-time payload and headers validation). Carries a `_tag` of
|
|
34
|
+
* `"@amqp-contract/MessageValidationError"` (namespaced to avoid collisions);
|
|
35
|
+
* the `Error.name` is kept bare (`"MessageValidationError"`).
|
|
23
36
|
*
|
|
24
37
|
* @param source - The name of the publisher or consumer that triggered the validation
|
|
25
38
|
* @param issues - The validation issues from the Standard Schema validation
|
|
26
39
|
*/
|
|
27
|
-
declare class MessageValidationError extends
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
declare class MessageValidationError extends MessageValidationError_base<{
|
|
41
|
+
message: string;
|
|
42
|
+
source: string;
|
|
43
|
+
issues: unknown;
|
|
44
|
+
}> {
|
|
30
45
|
constructor(source: string, issues: unknown);
|
|
31
46
|
}
|
|
32
47
|
//#endregion
|
|
@@ -63,16 +78,29 @@ type AmqpClientOptions = {
|
|
|
63
78
|
*/
|
|
64
79
|
type ConsumeCallback = (msg: ConsumeMessage | null) => void | Promise<void>;
|
|
65
80
|
/**
|
|
66
|
-
* Publish options
|
|
81
|
+
* Publish options for `AmqpClient.publish` / `AmqpClient.sendToQueue`.
|
|
82
|
+
*
|
|
83
|
+
* Currently a re-export of amqplib's `Options.Publish`. A previous version of
|
|
84
|
+
* this type also exposed a `timeout` field, but that field never had a
|
|
85
|
+
* meaningful AMQP-level effect in this codebase and has been removed to avoid
|
|
86
|
+
* suggesting behaviour we do not provide. (`amqp-connection-manager`'s own
|
|
87
|
+
* `publishTimeout` channel option is unrelated and is configured at channel
|
|
88
|
+
* creation, not per-publish.)
|
|
67
89
|
*/
|
|
68
|
-
type PublishOptions = Options.Publish
|
|
69
|
-
/** Message will be rejected after timeout ms */timeout?: number;
|
|
70
|
-
};
|
|
90
|
+
type PublishOptions = Options.Publish;
|
|
71
91
|
/**
|
|
72
|
-
* Consume options that extend amqplib's Options.Consume with optional
|
|
92
|
+
* Consume options that extend amqplib's `Options.Consume` with an optional
|
|
93
|
+
* per-consumer prefetch count.
|
|
94
|
+
*
|
|
95
|
+
* `prefetch` is intercepted by {@link AmqpClient.consume}: it is stripped from
|
|
96
|
+
* the options handed to the underlying `channelWrapper.consume(...)` call
|
|
97
|
+
* (since amqplib's `Options.Consume` does not include it) and applied via
|
|
98
|
+
* `channel.prefetch(count, false)` registered through `addSetup` *before* the
|
|
99
|
+
* consume so the value is in effect when the consumer starts and is reapplied
|
|
100
|
+
* automatically on channel reconnect.
|
|
73
101
|
*/
|
|
74
102
|
type ConsumerOptions = Options.Consume & {
|
|
75
|
-
/**
|
|
103
|
+
/** Per-consumer prefetch count. Applied before `channel.consume(...)`. */prefetch?: number;
|
|
76
104
|
};
|
|
77
105
|
/**
|
|
78
106
|
* AMQP client that manages connections and channels with automatic topology setup.
|
|
@@ -83,7 +111,7 @@ type ConsumerOptions = Options.Consume & {
|
|
|
83
111
|
* - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
|
|
84
112
|
* - Channel creation with JSON serialization enabled by default
|
|
85
113
|
*
|
|
86
|
-
* All operations return `
|
|
114
|
+
* All operations return `AsyncResult<T, TechnicalError>` for consistent error handling.
|
|
87
115
|
*
|
|
88
116
|
* @example
|
|
89
117
|
* ```typescript
|
|
@@ -92,7 +120,7 @@ type ConsumerOptions = Options.Consume & {
|
|
|
92
120
|
* connectionOptions: { heartbeatIntervalInSeconds: 30 }
|
|
93
121
|
* });
|
|
94
122
|
*
|
|
95
|
-
* // Wait for connection (
|
|
123
|
+
* // Wait for connection (AsyncResult is thenable)
|
|
96
124
|
* await client.waitForConnect();
|
|
97
125
|
*
|
|
98
126
|
* // Publish a message
|
|
@@ -110,6 +138,15 @@ declare class AmqpClient {
|
|
|
110
138
|
private readonly connectionOptions?;
|
|
111
139
|
/** Resolved timeout in ms; `null` means "wait forever". */
|
|
112
140
|
private readonly connectTimeoutMs;
|
|
141
|
+
/**
|
|
142
|
+
* Per-consumer prefetch setup functions registered via `addSetup` so they
|
|
143
|
+
* can be removed in {@link cancel} once the consumer is gone — otherwise
|
|
144
|
+
* the channel wrapper would replay the cancelled consumer's QoS on every
|
|
145
|
+
* reconnect and silently apply it to subsequent consumers.
|
|
146
|
+
*
|
|
147
|
+
* @internal
|
|
148
|
+
*/
|
|
149
|
+
private readonly prefetchSetups;
|
|
113
150
|
/**
|
|
114
151
|
* Create a new AMQP client instance.
|
|
115
152
|
*
|
|
@@ -136,7 +173,7 @@ declare class AmqpClient {
|
|
|
136
173
|
* Wait for the channel to be connected and ready.
|
|
137
174
|
*
|
|
138
175
|
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
139
|
-
*
|
|
176
|
+
* AsyncResult resolves to `err(TechnicalError)` once the timeout elapses.
|
|
140
177
|
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
141
178
|
* connections indefinitely and never errors on its own.
|
|
142
179
|
*
|
|
@@ -146,29 +183,41 @@ declare class AmqpClient {
|
|
|
146
183
|
* path to release the connection — `waitForConnect` does not do this
|
|
147
184
|
* automatically. The typed factories handle this cleanup for you.
|
|
148
185
|
*/
|
|
149
|
-
waitForConnect():
|
|
186
|
+
waitForConnect(): AsyncResult<void, TechnicalError>;
|
|
150
187
|
/**
|
|
151
188
|
* Publish a message to an exchange.
|
|
152
189
|
*
|
|
153
|
-
* @returns
|
|
190
|
+
* @returns AsyncResult resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
154
191
|
*/
|
|
155
|
-
publish(exchange: string, routingKey: string, content: Buffer | unknown, options?: PublishOptions):
|
|
192
|
+
publish(exchange: string, routingKey: string, content: Buffer | unknown, options?: PublishOptions): AsyncResult<boolean, TechnicalError>;
|
|
156
193
|
/**
|
|
157
194
|
* Publish a message directly to a queue.
|
|
158
195
|
*
|
|
159
|
-
* @returns
|
|
196
|
+
* @returns AsyncResult resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
160
197
|
*/
|
|
161
|
-
sendToQueue(queue: string, content: Buffer | unknown, options?: PublishOptions):
|
|
198
|
+
sendToQueue(queue: string, content: Buffer | unknown, options?: PublishOptions): AsyncResult<boolean, TechnicalError>;
|
|
162
199
|
/**
|
|
163
200
|
* Start consuming messages from a queue.
|
|
164
201
|
*
|
|
165
|
-
*
|
|
202
|
+
* If `options.prefetch` is set, a per-consumer prefetch count is applied via
|
|
203
|
+
* `channel.prefetch(count, false)` registered as a setup function on the
|
|
204
|
+
* channel wrapper *before* the underlying `consume` call. Registering it via
|
|
205
|
+
* `addSetup` ensures the prefetch is reapplied automatically on channel
|
|
206
|
+
* reconnect; using `global=false` scopes it to subsequent consumers on the
|
|
207
|
+
* channel (RabbitMQ semantics — opposite of intuition: `false` is per-
|
|
208
|
+
* consumer, `true` is channel-wide).
|
|
209
|
+
*
|
|
210
|
+
* `prefetch` is stripped from the options handed to `channelWrapper.consume`
|
|
211
|
+
* because it is not a valid `amqplib` `Options.Consume` field — leaving it
|
|
212
|
+
* in would just travel as a no-op key-value pair on the consume frame.
|
|
213
|
+
*
|
|
214
|
+
* @returns AsyncResult resolving to the consumer tag.
|
|
166
215
|
*/
|
|
167
|
-
consume(queue: string, callback: ConsumeCallback, options?: ConsumerOptions):
|
|
216
|
+
consume(queue: string, callback: ConsumeCallback, options?: ConsumerOptions): AsyncResult<string, TechnicalError>;
|
|
168
217
|
/**
|
|
169
218
|
* Cancel a consumer by its consumer tag.
|
|
170
219
|
*/
|
|
171
|
-
cancel(consumerTag: string):
|
|
220
|
+
cancel(consumerTag: string): AsyncResult<void, TechnicalError>;
|
|
172
221
|
/**
|
|
173
222
|
* Acknowledge a message.
|
|
174
223
|
*
|
|
@@ -215,7 +264,7 @@ declare class AmqpClient {
|
|
|
215
264
|
* Both steps run regardless of each other's outcome; if both fail, the
|
|
216
265
|
* errors are wrapped in an AggregateError.
|
|
217
266
|
*/
|
|
218
|
-
close():
|
|
267
|
+
close(): AsyncResult<void, TechnicalError>;
|
|
219
268
|
/**
|
|
220
269
|
* Reset connection singleton cache (for testing only)
|
|
221
270
|
* @internal
|
|
@@ -296,6 +345,30 @@ type Logger = {
|
|
|
296
345
|
error(message: string, context?: LoggerContext): void;
|
|
297
346
|
};
|
|
298
347
|
//#endregion
|
|
348
|
+
//#region src/parsing.d.ts
|
|
349
|
+
/**
|
|
350
|
+
* Parse a `Buffer` as JSON, mapping any `JSON.parse` exception to the
|
|
351
|
+
* caller-supplied error type.
|
|
352
|
+
*
|
|
353
|
+
* Use this in consume / reply paths where a parse failure is a typed value,
|
|
354
|
+
* not a thrown exception — the caller decides how to translate the raw error
|
|
355
|
+
* into a domain-level error (e.g. {@link TechnicalError}).
|
|
356
|
+
*
|
|
357
|
+
* @typeParam E - The error type produced by `errorFn`.
|
|
358
|
+
* @param buffer - The raw message body to parse.
|
|
359
|
+
* @param errorFn - Callback invoked with the underlying `JSON.parse` error.
|
|
360
|
+
* @returns A `Result` containing the parsed `unknown` value or the mapped error.
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```typescript
|
|
364
|
+
* const parsed = safeJsonParse(
|
|
365
|
+
* msg.content,
|
|
366
|
+
* (error) => new TechnicalError("Failed to parse JSON", error),
|
|
367
|
+
* );
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
declare function safeJsonParse<E>(buffer: Buffer, errorFn: (raw: unknown) => E): Result<unknown, E>;
|
|
371
|
+
//#endregion
|
|
299
372
|
//#region src/setup.d.ts
|
|
300
373
|
/**
|
|
301
374
|
* Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.
|
|
@@ -423,5 +496,5 @@ declare function recordLateRpcReply(provider: TelemetryProvider, reason: "unknow
|
|
|
423
496
|
*/
|
|
424
497
|
declare function _resetTelemetryCacheForTesting(): void;
|
|
425
498
|
//#endregion
|
|
426
|
-
export { AmqpClient, type AmqpClientOptions, type ConsumeCallback, type ConsumerOptions, DEFAULT_CONNECT_TIMEOUT_MS, type Logger, type LoggerContext, MessageValidationError, MessagingSemanticConventions, type PublishOptions, TechnicalError, type TelemetryProvider, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
|
|
499
|
+
export { AmqpClient, type AmqpClientOptions, type ConsumeCallback, type ConsumerOptions, DEFAULT_CONNECT_TIMEOUT_MS, type Logger, type LoggerContext, MessageValidationError, MessagingSemanticConventions, type PublishOptions, TechnicalError, type TelemetryProvider, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, safeJsonParse, setupAmqpTopology, startConsumeSpan, startPublishSpan };
|
|
427
500
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/logger.ts","../src/setup.ts","../src/telemetry.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/amqp-client.ts","../src/connection-manager.ts","../src/logger.ts","../src/parsing.ts","../src/setup.ts","../src/telemetry.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import {
|
|
2
|
+
import { TaggedError, err, fromPromise, fromSafePromise, fromThrowable, ok } from "unthrown";
|
|
3
3
|
import amqp from "amqp-connection-manager";
|
|
4
4
|
import { extractQueue } from "@amqp-contract/contract";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
6
|
-
var __require = /*
|
|
6
|
+
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/connection-manager.ts
|
|
9
9
|
/**
|
|
@@ -163,33 +163,40 @@ function _resetConnectionsForTesting() {
|
|
|
163
163
|
*
|
|
164
164
|
* This includes AMQP connection failures, channel issues, validation failures,
|
|
165
165
|
* and other runtime errors. This error is shared across core, worker, and client packages.
|
|
166
|
+
*
|
|
167
|
+
* Built on unthrown's {@link TaggedError}, so it carries a `_tag` of
|
|
168
|
+
* `"@amqp-contract/TechnicalError"` for exhaustive dispatch via `matchTags`. The
|
|
169
|
+
* tag is namespaced to avoid colliding with other libraries' tags in a shared
|
|
170
|
+
* `matchTags`; the human-facing `Error.name` is kept bare (`"TechnicalError"`).
|
|
171
|
+
* Remains a real `Error` (and a *modeled* error — it lives in the `E` channel of
|
|
172
|
+
* a `Result`, never the `Defect` channel).
|
|
166
173
|
*/
|
|
167
|
-
var TechnicalError = class extends
|
|
174
|
+
var TechnicalError = class extends TaggedError("@amqp-contract/TechnicalError", { name: "TechnicalError" }) {
|
|
168
175
|
constructor(message, cause) {
|
|
169
|
-
super(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
|
|
176
|
+
super({
|
|
177
|
+
message,
|
|
178
|
+
cause
|
|
179
|
+
});
|
|
174
180
|
}
|
|
175
181
|
};
|
|
176
182
|
/**
|
|
177
183
|
* Error thrown when message validation fails (payload or headers).
|
|
178
184
|
*
|
|
179
185
|
* Used by both the client (publish-time payload validation) and the worker
|
|
180
|
-
* (consume-time payload and headers validation).
|
|
186
|
+
* (consume-time payload and headers validation). Carries a `_tag` of
|
|
187
|
+
* `"@amqp-contract/MessageValidationError"` (namespaced to avoid collisions);
|
|
188
|
+
* the `Error.name` is kept bare (`"MessageValidationError"`).
|
|
181
189
|
*
|
|
182
190
|
* @param source - The name of the publisher or consumer that triggered the validation
|
|
183
191
|
* @param issues - The validation issues from the Standard Schema validation
|
|
184
192
|
*/
|
|
185
|
-
var MessageValidationError = class extends
|
|
193
|
+
var MessageValidationError = class extends TaggedError("@amqp-contract/MessageValidationError", { name: "MessageValidationError" }) {
|
|
186
194
|
constructor(source, issues) {
|
|
187
|
-
super(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
|
|
195
|
+
super({
|
|
196
|
+
message: `Message validation failed for "${source}"`,
|
|
197
|
+
source,
|
|
198
|
+
issues
|
|
199
|
+
});
|
|
193
200
|
}
|
|
194
201
|
};
|
|
195
202
|
//#endregion
|
|
@@ -217,10 +224,10 @@ var MessageValidationError = class extends Error {
|
|
|
217
224
|
async function setupAmqpTopology(channel, contract) {
|
|
218
225
|
const exchanges = Object.values(contract.exchanges ?? {}).filter((e) => e.name !== "");
|
|
219
226
|
const exchangeErrors = (await Promise.allSettled(exchanges.map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
|
|
220
|
-
durable: exchange.durable,
|
|
221
|
-
autoDelete: exchange.autoDelete,
|
|
222
|
-
internal: exchange.internal,
|
|
223
|
-
arguments: exchange.arguments
|
|
227
|
+
...exchange.durable !== void 0 && { durable: exchange.durable },
|
|
228
|
+
...exchange.autoDelete !== void 0 && { autoDelete: exchange.autoDelete },
|
|
229
|
+
...exchange.internal !== void 0 && { internal: exchange.internal },
|
|
230
|
+
...exchange.arguments !== void 0 && { arguments: exchange.arguments }
|
|
224
231
|
})))).map((result, i) => ({
|
|
225
232
|
result,
|
|
226
233
|
name: exchanges[i].name
|
|
@@ -251,9 +258,9 @@ async function setupAmqpTopology(channel, contract) {
|
|
|
251
258
|
});
|
|
252
259
|
if (queue.maxPriority !== void 0) queueArguments["x-max-priority"] = queue.maxPriority;
|
|
253
260
|
return channel.assertQueue(queue.name, {
|
|
254
|
-
durable: queue.durable,
|
|
255
|
-
exclusive: queue.exclusive,
|
|
256
|
-
autoDelete: queue.autoDelete,
|
|
261
|
+
...queue.durable !== void 0 && { durable: queue.durable },
|
|
262
|
+
...queue.exclusive !== void 0 && { exclusive: queue.exclusive },
|
|
263
|
+
...queue.autoDelete !== void 0 && { autoDelete: queue.autoDelete },
|
|
257
264
|
arguments: queueArguments
|
|
258
265
|
});
|
|
259
266
|
}))).map((result, i) => ({
|
|
@@ -327,7 +334,7 @@ function resolveConnectTimeoutMs(input) {
|
|
|
327
334
|
* - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
|
|
328
335
|
* - Channel creation with JSON serialization enabled by default
|
|
329
336
|
*
|
|
330
|
-
* All operations return `
|
|
337
|
+
* All operations return `AsyncResult<T, TechnicalError>` for consistent error handling.
|
|
331
338
|
*
|
|
332
339
|
* @example
|
|
333
340
|
* ```typescript
|
|
@@ -336,7 +343,7 @@ function resolveConnectTimeoutMs(input) {
|
|
|
336
343
|
* connectionOptions: { heartbeatIntervalInSeconds: 30 }
|
|
337
344
|
* });
|
|
338
345
|
*
|
|
339
|
-
* // Wait for connection (
|
|
346
|
+
* // Wait for connection (AsyncResult is thenable)
|
|
340
347
|
* await client.waitForConnect();
|
|
341
348
|
*
|
|
342
349
|
* // Publish a message
|
|
@@ -347,6 +354,7 @@ function resolveConnectTimeoutMs(input) {
|
|
|
347
354
|
* ```
|
|
348
355
|
*/
|
|
349
356
|
var AmqpClient = class {
|
|
357
|
+
contract;
|
|
350
358
|
connection;
|
|
351
359
|
channelWrapper;
|
|
352
360
|
urls;
|
|
@@ -354,6 +362,15 @@ var AmqpClient = class {
|
|
|
354
362
|
/** Resolved timeout in ms; `null` means "wait forever". */
|
|
355
363
|
connectTimeoutMs;
|
|
356
364
|
/**
|
|
365
|
+
* Per-consumer prefetch setup functions registered via `addSetup` so they
|
|
366
|
+
* can be removed in {@link cancel} once the consumer is gone — otherwise
|
|
367
|
+
* the channel wrapper would replay the cancelled consumer's QoS on every
|
|
368
|
+
* reconnect and silently apply it to subsequent consumers.
|
|
369
|
+
*
|
|
370
|
+
* @internal
|
|
371
|
+
*/
|
|
372
|
+
prefetchSetups = /* @__PURE__ */ new Map();
|
|
373
|
+
/**
|
|
357
374
|
* Create a new AMQP client instance.
|
|
358
375
|
*
|
|
359
376
|
* The client will automatically:
|
|
@@ -401,7 +418,7 @@ var AmqpClient = class {
|
|
|
401
418
|
* Wait for the channel to be connected and ready.
|
|
402
419
|
*
|
|
403
420
|
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
404
|
-
*
|
|
421
|
+
* AsyncResult resolves to `err(TechnicalError)` once the timeout elapses.
|
|
405
422
|
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
406
423
|
* connections indefinitely and never errors on its own.
|
|
407
424
|
*
|
|
@@ -414,7 +431,7 @@ var AmqpClient = class {
|
|
|
414
431
|
waitForConnect() {
|
|
415
432
|
const connectPromise = this.channelWrapper.waitForConnect();
|
|
416
433
|
const timeoutMs = this.connectTimeoutMs;
|
|
417
|
-
|
|
434
|
+
return fromPromise(timeoutMs === null ? connectPromise : new Promise((resolve, reject) => {
|
|
418
435
|
const handle = setTimeout(() => {
|
|
419
436
|
reject(/* @__PURE__ */ new Error(`Timed out waiting for AMQP connection after ${timeoutMs}ms`));
|
|
420
437
|
}, timeoutMs);
|
|
@@ -425,38 +442,75 @@ var AmqpClient = class {
|
|
|
425
442
|
clearTimeout(handle);
|
|
426
443
|
reject(error);
|
|
427
444
|
});
|
|
428
|
-
});
|
|
429
|
-
return ResultAsync.fromPromise(racedPromise, (error) => new TechnicalError("Failed to connect to AMQP broker", error));
|
|
445
|
+
}), (error) => new TechnicalError("Failed to connect to AMQP broker", error));
|
|
430
446
|
}
|
|
431
447
|
/**
|
|
432
448
|
* Publish a message to an exchange.
|
|
433
449
|
*
|
|
434
|
-
* @returns
|
|
450
|
+
* @returns AsyncResult resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
435
451
|
*/
|
|
436
452
|
publish(exchange, routingKey, content, options) {
|
|
437
|
-
return
|
|
453
|
+
return fromPromise(this.channelWrapper.publish(exchange, routingKey, content, options), (error) => new TechnicalError("Failed to publish message", error));
|
|
438
454
|
}
|
|
439
455
|
/**
|
|
440
456
|
* Publish a message directly to a queue.
|
|
441
457
|
*
|
|
442
|
-
* @returns
|
|
458
|
+
* @returns AsyncResult resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
443
459
|
*/
|
|
444
460
|
sendToQueue(queue, content, options) {
|
|
445
|
-
return
|
|
461
|
+
return fromPromise(this.channelWrapper.sendToQueue(queue, content, options), (error) => new TechnicalError("Failed to publish message to queue", error));
|
|
446
462
|
}
|
|
447
463
|
/**
|
|
448
464
|
* Start consuming messages from a queue.
|
|
449
465
|
*
|
|
450
|
-
*
|
|
466
|
+
* If `options.prefetch` is set, a per-consumer prefetch count is applied via
|
|
467
|
+
* `channel.prefetch(count, false)` registered as a setup function on the
|
|
468
|
+
* channel wrapper *before* the underlying `consume` call. Registering it via
|
|
469
|
+
* `addSetup` ensures the prefetch is reapplied automatically on channel
|
|
470
|
+
* reconnect; using `global=false` scopes it to subsequent consumers on the
|
|
471
|
+
* channel (RabbitMQ semantics — opposite of intuition: `false` is per-
|
|
472
|
+
* consumer, `true` is channel-wide).
|
|
473
|
+
*
|
|
474
|
+
* `prefetch` is stripped from the options handed to `channelWrapper.consume`
|
|
475
|
+
* because it is not a valid `amqplib` `Options.Consume` field — leaving it
|
|
476
|
+
* in would just travel as a no-op key-value pair on the consume frame.
|
|
477
|
+
*
|
|
478
|
+
* @returns AsyncResult resolving to the consumer tag.
|
|
451
479
|
*/
|
|
452
480
|
consume(queue, callback, options) {
|
|
453
|
-
|
|
481
|
+
const { prefetch, ...consumeOptions } = options ?? {};
|
|
482
|
+
if (prefetch !== void 0) {
|
|
483
|
+
if (!Number.isInteger(prefetch) || prefetch < 0 || prefetch > 65535) return err(new TechnicalError(`Invalid prefetch: expected a non-negative integer ≤ 65535, got ${String(prefetch)}`)).toAsync();
|
|
484
|
+
}
|
|
485
|
+
const prefetchSetup = typeof prefetch === "number" ? async (channel) => {
|
|
486
|
+
await channel.prefetch(prefetch, false);
|
|
487
|
+
} : void 0;
|
|
488
|
+
return fromPromise((async () => {
|
|
489
|
+
if (prefetchSetup) await this.channelWrapper.addSetup(prefetchSetup);
|
|
490
|
+
let reply;
|
|
491
|
+
try {
|
|
492
|
+
reply = await this.channelWrapper.consume(queue, callback, consumeOptions);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
if (prefetchSetup) await this.channelWrapper.removeSetup(prefetchSetup).catch(() => {});
|
|
495
|
+
throw error;
|
|
496
|
+
}
|
|
497
|
+
if (prefetchSetup) this.prefetchSetups.set(reply.consumerTag, prefetchSetup);
|
|
498
|
+
return reply;
|
|
499
|
+
})(), (error) => new TechnicalError("Failed to start consuming messages", error)).map((reply) => reply.consumerTag);
|
|
454
500
|
}
|
|
455
501
|
/**
|
|
456
502
|
* Cancel a consumer by its consumer tag.
|
|
457
503
|
*/
|
|
458
504
|
cancel(consumerTag) {
|
|
459
|
-
return
|
|
505
|
+
return fromPromise((async () => {
|
|
506
|
+
const setup = this.prefetchSetups.get(consumerTag);
|
|
507
|
+
this.prefetchSetups.delete(consumerTag);
|
|
508
|
+
try {
|
|
509
|
+
await this.channelWrapper.cancel(consumerTag);
|
|
510
|
+
} finally {
|
|
511
|
+
if (setup !== void 0) await this.channelWrapper.removeSetup(setup).catch(() => {});
|
|
512
|
+
}
|
|
513
|
+
})(), (error) => new TechnicalError("Failed to cancel consumer", error)).map(() => void 0);
|
|
460
514
|
}
|
|
461
515
|
/**
|
|
462
516
|
* Acknowledge a message.
|
|
@@ -513,14 +567,14 @@ var AmqpClient = class {
|
|
|
513
567
|
* errors are wrapped in an AggregateError.
|
|
514
568
|
*/
|
|
515
569
|
close() {
|
|
516
|
-
return
|
|
517
|
-
const channelResult = await
|
|
518
|
-
const releaseResult = await
|
|
570
|
+
return fromSafePromise((async () => {
|
|
571
|
+
const channelResult = await fromPromise(this.channelWrapper.close(), (error) => new TechnicalError("Failed to close channel", error));
|
|
572
|
+
const releaseResult = await fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions), (error) => new TechnicalError("Failed to release connection", error));
|
|
519
573
|
if (channelResult.isErr() && releaseResult.isErr()) return err(new TechnicalError("Failed to close channel and release connection", new AggregateError([channelResult.error, releaseResult.error], "Failed to close channel and release connection")));
|
|
520
574
|
if (channelResult.isErr()) return channelResult;
|
|
521
575
|
if (releaseResult.isErr()) return releaseResult;
|
|
522
576
|
return ok(void 0);
|
|
523
|
-
})());
|
|
577
|
+
})()).flatMap((result) => result);
|
|
524
578
|
}
|
|
525
579
|
/**
|
|
526
580
|
* Reset connection singleton cache (for testing only)
|
|
@@ -531,6 +585,32 @@ var AmqpClient = class {
|
|
|
531
585
|
}
|
|
532
586
|
};
|
|
533
587
|
//#endregion
|
|
588
|
+
//#region src/parsing.ts
|
|
589
|
+
/**
|
|
590
|
+
* Parse a `Buffer` as JSON, mapping any `JSON.parse` exception to the
|
|
591
|
+
* caller-supplied error type.
|
|
592
|
+
*
|
|
593
|
+
* Use this in consume / reply paths where a parse failure is a typed value,
|
|
594
|
+
* not a thrown exception — the caller decides how to translate the raw error
|
|
595
|
+
* into a domain-level error (e.g. {@link TechnicalError}).
|
|
596
|
+
*
|
|
597
|
+
* @typeParam E - The error type produced by `errorFn`.
|
|
598
|
+
* @param buffer - The raw message body to parse.
|
|
599
|
+
* @param errorFn - Callback invoked with the underlying `JSON.parse` error.
|
|
600
|
+
* @returns A `Result` containing the parsed `unknown` value or the mapped error.
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* ```typescript
|
|
604
|
+
* const parsed = safeJsonParse(
|
|
605
|
+
* msg.content,
|
|
606
|
+
* (error) => new TechnicalError("Failed to parse JSON", error),
|
|
607
|
+
* );
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
function safeJsonParse(buffer, errorFn) {
|
|
611
|
+
return fromThrowable(() => JSON.parse(buffer.toString()), errorFn)();
|
|
612
|
+
}
|
|
613
|
+
//#endregion
|
|
534
614
|
//#region src/telemetry.ts
|
|
535
615
|
/**
|
|
536
616
|
* SpanKind values from OpenTelemetry.
|
|
@@ -795,6 +875,6 @@ function _resetTelemetryCacheForTesting() {
|
|
|
795
875
|
cachedLateRpcReplyCounter = void 0;
|
|
796
876
|
}
|
|
797
877
|
//#endregion
|
|
798
|
-
export { AmqpClient, DEFAULT_CONNECT_TIMEOUT_MS, MessageValidationError, MessagingSemanticConventions, TechnicalError, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
|
|
878
|
+
export { AmqpClient, DEFAULT_CONNECT_TIMEOUT_MS, MessageValidationError, MessagingSemanticConventions, TechnicalError, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, safeJsonParse, setupAmqpTopology, startConsumeSpan, startPublishSpan };
|
|
799
879
|
|
|
800
880
|
//# sourceMappingURL=index.mjs.map
|