@amqp-contract/core 0.23.1 → 0.25.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 +8 -6
- package/dist/index.cjs +101 -37
- package/dist/index.d.cts +84 -41
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +84 -41
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +101 -38
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +186 -124
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Result, ResultAsync } from "neverthrow";
|
|
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";
|
|
@@ -35,7 +35,7 @@ declare class MessageValidationError extends Error {
|
|
|
35
35
|
* Default time `waitForConnect` will wait for the broker before erroring out.
|
|
36
36
|
* Defaulting to a finite value (rather than waiting forever) means a fail-fast
|
|
37
37
|
* developer experience: a misconfigured URL, a down broker, or wrong
|
|
38
|
-
* credentials surface as
|
|
38
|
+
* credentials surface as an `err` within 30 seconds. Pass `null`
|
|
39
39
|
* explicitly to disable the timeout — `Infinity` and other non-finite values
|
|
40
40
|
* are also coerced to "no timeout" because Node's `setTimeout` clamps large
|
|
41
41
|
* delays to ~24.8 days and silently fires near-immediately on `Infinity`.
|
|
@@ -63,16 +63,29 @@ type AmqpClientOptions = {
|
|
|
63
63
|
*/
|
|
64
64
|
type ConsumeCallback = (msg: ConsumeMessage | null) => void | Promise<void>;
|
|
65
65
|
/**
|
|
66
|
-
* Publish options
|
|
66
|
+
* Publish options for `AmqpClient.publish` / `AmqpClient.sendToQueue`.
|
|
67
|
+
*
|
|
68
|
+
* Currently a re-export of amqplib's `Options.Publish`. A previous version of
|
|
69
|
+
* this type also exposed a `timeout` field, but that field never had a
|
|
70
|
+
* meaningful AMQP-level effect in this codebase and has been removed to avoid
|
|
71
|
+
* suggesting behaviour we do not provide. (`amqp-connection-manager`'s own
|
|
72
|
+
* `publishTimeout` channel option is unrelated and is configured at channel
|
|
73
|
+
* creation, not per-publish.)
|
|
67
74
|
*/
|
|
68
|
-
type PublishOptions = Options.Publish
|
|
69
|
-
/** Message will be rejected after timeout ms */timeout?: number;
|
|
70
|
-
};
|
|
75
|
+
type PublishOptions = Options.Publish;
|
|
71
76
|
/**
|
|
72
|
-
* Consume options that extend amqplib's Options.Consume with optional
|
|
77
|
+
* Consume options that extend amqplib's `Options.Consume` with an optional
|
|
78
|
+
* per-consumer prefetch count.
|
|
79
|
+
*
|
|
80
|
+
* `prefetch` is intercepted by {@link AmqpClient.consume}: it is stripped from
|
|
81
|
+
* the options handed to the underlying `channelWrapper.consume(...)` call
|
|
82
|
+
* (since amqplib's `Options.Consume` does not include it) and applied via
|
|
83
|
+
* `channel.prefetch(count, false)` registered through `addSetup` *before* the
|
|
84
|
+
* consume so the value is in effect when the consumer starts and is reapplied
|
|
85
|
+
* automatically on channel reconnect.
|
|
73
86
|
*/
|
|
74
87
|
type ConsumerOptions = Options.Consume & {
|
|
75
|
-
/**
|
|
88
|
+
/** Per-consumer prefetch count. Applied before `channel.consume(...)`. */prefetch?: number;
|
|
76
89
|
};
|
|
77
90
|
/**
|
|
78
91
|
* AMQP client that manages connections and channels with automatic topology setup.
|
|
@@ -83,7 +96,7 @@ type ConsumerOptions = Options.Consume & {
|
|
|
83
96
|
* - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
|
|
84
97
|
* - Channel creation with JSON serialization enabled by default
|
|
85
98
|
*
|
|
86
|
-
* All operations return `
|
|
99
|
+
* All operations return `ResultAsync<T, TechnicalError>` for consistent error handling.
|
|
87
100
|
*
|
|
88
101
|
* @example
|
|
89
102
|
* ```typescript
|
|
@@ -92,14 +105,14 @@ type ConsumerOptions = Options.Consume & {
|
|
|
92
105
|
* connectionOptions: { heartbeatIntervalInSeconds: 30 }
|
|
93
106
|
* });
|
|
94
107
|
*
|
|
95
|
-
* // Wait for connection
|
|
96
|
-
* await client.waitForConnect()
|
|
108
|
+
* // Wait for connection (ResultAsync is thenable)
|
|
109
|
+
* await client.waitForConnect();
|
|
97
110
|
*
|
|
98
111
|
* // Publish a message
|
|
99
|
-
* const result = await client.publish('exchange', 'routingKey', { data: 'value' })
|
|
112
|
+
* const result = await client.publish('exchange', 'routingKey', { data: 'value' });
|
|
100
113
|
*
|
|
101
114
|
* // Close when done
|
|
102
|
-
* await client.close()
|
|
115
|
+
* await client.close();
|
|
103
116
|
* ```
|
|
104
117
|
*/
|
|
105
118
|
declare class AmqpClient {
|
|
@@ -110,6 +123,15 @@ declare class AmqpClient {
|
|
|
110
123
|
private readonly connectionOptions?;
|
|
111
124
|
/** Resolved timeout in ms; `null` means "wait forever". */
|
|
112
125
|
private readonly connectTimeoutMs;
|
|
126
|
+
/**
|
|
127
|
+
* Per-consumer prefetch setup functions registered via `addSetup` so they
|
|
128
|
+
* can be removed in {@link cancel} once the consumer is gone — otherwise
|
|
129
|
+
* the channel wrapper would replay the cancelled consumer's QoS on every
|
|
130
|
+
* reconnect and silently apply it to subsequent consumers.
|
|
131
|
+
*
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
private readonly prefetchSetups;
|
|
113
135
|
/**
|
|
114
136
|
* Create a new AMQP client instance.
|
|
115
137
|
*
|
|
@@ -136,7 +158,7 @@ declare class AmqpClient {
|
|
|
136
158
|
* Wait for the channel to be connected and ready.
|
|
137
159
|
*
|
|
138
160
|
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
139
|
-
*
|
|
161
|
+
* ResultAsync resolves to `err(TechnicalError)` once the timeout elapses.
|
|
140
162
|
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
141
163
|
* connections indefinitely and never errors on its own.
|
|
142
164
|
*
|
|
@@ -145,46 +167,42 @@ declare class AmqpClient {
|
|
|
145
167
|
* connection's reference count. Callers must invoke `close()` on the error
|
|
146
168
|
* path to release the connection — `waitForConnect` does not do this
|
|
147
169
|
* automatically. The typed factories handle this cleanup for you.
|
|
148
|
-
*
|
|
149
|
-
* @returns A Future resolving to `Result.Ok(void)` on connect, or
|
|
150
|
-
* `Result.Error(TechnicalError)` on timeout / connection failure.
|
|
151
170
|
*/
|
|
152
|
-
waitForConnect():
|
|
171
|
+
waitForConnect(): ResultAsync<void, TechnicalError>;
|
|
153
172
|
/**
|
|
154
173
|
* Publish a message to an exchange.
|
|
155
174
|
*
|
|
156
|
-
* @
|
|
157
|
-
* @param routingKey - The routing key
|
|
158
|
-
* @param content - The message content (will be JSON serialized if json: true)
|
|
159
|
-
* @param options - Optional publish options
|
|
160
|
-
* @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
|
|
175
|
+
* @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
161
176
|
*/
|
|
162
|
-
publish(exchange: string, routingKey: string, content: Buffer | unknown, options?: PublishOptions):
|
|
177
|
+
publish(exchange: string, routingKey: string, content: Buffer | unknown, options?: PublishOptions): ResultAsync<boolean, TechnicalError>;
|
|
163
178
|
/**
|
|
164
179
|
* Publish a message directly to a queue.
|
|
165
180
|
*
|
|
166
|
-
* @
|
|
167
|
-
* @param content - The message content (will be JSON serialized if json: true)
|
|
168
|
-
* @param options - Optional publish options
|
|
169
|
-
* @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
|
|
181
|
+
* @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
170
182
|
*/
|
|
171
|
-
sendToQueue(queue: string, content: Buffer | unknown, options?: PublishOptions):
|
|
183
|
+
sendToQueue(queue: string, content: Buffer | unknown, options?: PublishOptions): ResultAsync<boolean, TechnicalError>;
|
|
172
184
|
/**
|
|
173
185
|
* Start consuming messages from a queue.
|
|
174
186
|
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
187
|
+
* If `options.prefetch` is set, a per-consumer prefetch count is applied via
|
|
188
|
+
* `channel.prefetch(count, false)` registered as a setup function on the
|
|
189
|
+
* channel wrapper *before* the underlying `consume` call. Registering it via
|
|
190
|
+
* `addSetup` ensures the prefetch is reapplied automatically on channel
|
|
191
|
+
* reconnect; using `global=false` scopes it to subsequent consumers on the
|
|
192
|
+
* channel (RabbitMQ semantics — opposite of intuition: `false` is per-
|
|
193
|
+
* consumer, `true` is channel-wide).
|
|
194
|
+
*
|
|
195
|
+
* `prefetch` is stripped from the options handed to `channelWrapper.consume`
|
|
196
|
+
* because it is not a valid `amqplib` `Options.Consume` field — leaving it
|
|
197
|
+
* in would just travel as a no-op key-value pair on the consume frame.
|
|
198
|
+
*
|
|
199
|
+
* @returns ResultAsync resolving to the consumer tag.
|
|
179
200
|
*/
|
|
180
|
-
consume(queue: string, callback: ConsumeCallback, options?: ConsumerOptions):
|
|
201
|
+
consume(queue: string, callback: ConsumeCallback, options?: ConsumerOptions): ResultAsync<string, TechnicalError>;
|
|
181
202
|
/**
|
|
182
203
|
* Cancel a consumer by its consumer tag.
|
|
183
|
-
*
|
|
184
|
-
* @param consumerTag - The consumer tag to cancel
|
|
185
|
-
* @returns A Future that resolves when the consumer is cancelled
|
|
186
204
|
*/
|
|
187
|
-
cancel(consumerTag: string):
|
|
205
|
+
cancel(consumerTag: string): ResultAsync<void, TechnicalError>;
|
|
188
206
|
/**
|
|
189
207
|
* Acknowledge a message.
|
|
190
208
|
*
|
|
@@ -228,9 +246,10 @@ declare class AmqpClient {
|
|
|
228
246
|
* - Decrease the reference count on the shared connection
|
|
229
247
|
* - Close the connection if this was the last client using it
|
|
230
248
|
*
|
|
231
|
-
*
|
|
249
|
+
* Both steps run regardless of each other's outcome; if both fail, the
|
|
250
|
+
* errors are wrapped in an AggregateError.
|
|
232
251
|
*/
|
|
233
|
-
close():
|
|
252
|
+
close(): ResultAsync<void, TechnicalError>;
|
|
234
253
|
/**
|
|
235
254
|
* Reset connection singleton cache (for testing only)
|
|
236
255
|
* @internal
|
|
@@ -311,6 +330,30 @@ type Logger = {
|
|
|
311
330
|
error(message: string, context?: LoggerContext): void;
|
|
312
331
|
};
|
|
313
332
|
//#endregion
|
|
333
|
+
//#region src/parsing.d.ts
|
|
334
|
+
/**
|
|
335
|
+
* Parse a `Buffer` as JSON, mapping any `JSON.parse` exception to the
|
|
336
|
+
* caller-supplied error type.
|
|
337
|
+
*
|
|
338
|
+
* Use this in consume / reply paths where a parse failure is a typed value,
|
|
339
|
+
* not a thrown exception — the caller decides how to translate the raw error
|
|
340
|
+
* into a domain-level error (e.g. {@link TechnicalError}).
|
|
341
|
+
*
|
|
342
|
+
* @typeParam E - The error type produced by `errorFn`.
|
|
343
|
+
* @param buffer - The raw message body to parse.
|
|
344
|
+
* @param errorFn - Callback invoked with the underlying `JSON.parse` error.
|
|
345
|
+
* @returns A `Result` containing the parsed `unknown` value or the mapped error.
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```typescript
|
|
349
|
+
* const parsed = safeJsonParse(
|
|
350
|
+
* msg.content,
|
|
351
|
+
* (error) => new TechnicalError("Failed to parse JSON", error),
|
|
352
|
+
* );
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
declare function safeJsonParse<E>(buffer: Buffer, errorFn: (raw: unknown) => E): Result<unknown, E>;
|
|
356
|
+
//#endregion
|
|
314
357
|
//#region src/setup.d.ts
|
|
315
358
|
/**
|
|
316
359
|
* Setup AMQP topology (exchanges, queues, and bindings) from a contract definition.
|
|
@@ -438,5 +481,5 @@ declare function recordLateRpcReply(provider: TelemetryProvider, reason: "unknow
|
|
|
438
481
|
*/
|
|
439
482
|
declare function _resetTelemetryCacheForTesting(): void;
|
|
440
483
|
//#endregion
|
|
441
|
-
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 };
|
|
484
|
+
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 };
|
|
442
485
|
//# 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":";;;;;;;;;;;;;cAMa,cAAA,SAAuB,KAAA;EAAA,SAGP,KAAA;cADzB,OAAA,UACyB,KAAA;AAAA;;;;;;;;;AAuB7B;cAAa,sBAAA,SAA+B,KAAA;EAAA,SAExB,MAAA;EAAA,SACA,MAAA;cADA,MAAA,UACA,MAAA;AAAA;;;;;AA7BpB;;;;;;;cCwCa,0BAAA;;;;ADdb;;;;;;;;KCwCY,iBAAA;EACV,IAAA,EAAM,aAAA;EACN,iBAAA,GAAoB,4BAAA;EACpB,cAAA,GAAiB,OAAA,CAAQ,iBAAA;EACzB,gBAAA;AAAA;;AA9BF;;KAoCY,eAAA,IAAmB,GAAA,EAAK,cAAA,mBAAiC,OAAA;;;AAVrE
|
|
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,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import {
|
|
2
|
+
import { Result, ResultAsync, err, errAsync, ok } from "neverthrow";
|
|
3
3
|
import amqp from "amqp-connection-manager";
|
|
4
4
|
import { extractQueue } from "@amqp-contract/contract";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
@@ -300,7 +300,7 @@ function callSetupFunc(setup, channel) {
|
|
|
300
300
|
* Default time `waitForConnect` will wait for the broker before erroring out.
|
|
301
301
|
* Defaulting to a finite value (rather than waiting forever) means a fail-fast
|
|
302
302
|
* developer experience: a misconfigured URL, a down broker, or wrong
|
|
303
|
-
* credentials surface as
|
|
303
|
+
* credentials surface as an `err` within 30 seconds. Pass `null`
|
|
304
304
|
* explicitly to disable the timeout — `Infinity` and other non-finite values
|
|
305
305
|
* are also coerced to "no timeout" because Node's `setTimeout` clamps large
|
|
306
306
|
* delays to ~24.8 days and silently fires near-immediately on `Infinity`.
|
|
@@ -327,7 +327,7 @@ function resolveConnectTimeoutMs(input) {
|
|
|
327
327
|
* - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
|
|
328
328
|
* - Channel creation with JSON serialization enabled by default
|
|
329
329
|
*
|
|
330
|
-
* All operations return `
|
|
330
|
+
* All operations return `ResultAsync<T, TechnicalError>` for consistent error handling.
|
|
331
331
|
*
|
|
332
332
|
* @example
|
|
333
333
|
* ```typescript
|
|
@@ -336,14 +336,14 @@ function resolveConnectTimeoutMs(input) {
|
|
|
336
336
|
* connectionOptions: { heartbeatIntervalInSeconds: 30 }
|
|
337
337
|
* });
|
|
338
338
|
*
|
|
339
|
-
* // Wait for connection
|
|
340
|
-
* await client.waitForConnect()
|
|
339
|
+
* // Wait for connection (ResultAsync is thenable)
|
|
340
|
+
* await client.waitForConnect();
|
|
341
341
|
*
|
|
342
342
|
* // Publish a message
|
|
343
|
-
* const result = await client.publish('exchange', 'routingKey', { data: 'value' })
|
|
343
|
+
* const result = await client.publish('exchange', 'routingKey', { data: 'value' });
|
|
344
344
|
*
|
|
345
345
|
* // Close when done
|
|
346
|
-
* await client.close()
|
|
346
|
+
* await client.close();
|
|
347
347
|
* ```
|
|
348
348
|
*/
|
|
349
349
|
var AmqpClient = class {
|
|
@@ -354,6 +354,15 @@ var AmqpClient = class {
|
|
|
354
354
|
/** Resolved timeout in ms; `null` means "wait forever". */
|
|
355
355
|
connectTimeoutMs;
|
|
356
356
|
/**
|
|
357
|
+
* Per-consumer prefetch setup functions registered via `addSetup` so they
|
|
358
|
+
* can be removed in {@link cancel} once the consumer is gone — otherwise
|
|
359
|
+
* the channel wrapper would replay the cancelled consumer's QoS on every
|
|
360
|
+
* reconnect and silently apply it to subsequent consumers.
|
|
361
|
+
*
|
|
362
|
+
* @internal
|
|
363
|
+
*/
|
|
364
|
+
prefetchSetups = /* @__PURE__ */ new Map();
|
|
365
|
+
/**
|
|
357
366
|
* Create a new AMQP client instance.
|
|
358
367
|
*
|
|
359
368
|
* The client will automatically:
|
|
@@ -401,7 +410,7 @@ var AmqpClient = class {
|
|
|
401
410
|
* Wait for the channel to be connected and ready.
|
|
402
411
|
*
|
|
403
412
|
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
404
|
-
*
|
|
413
|
+
* ResultAsync resolves to `err(TechnicalError)` once the timeout elapses.
|
|
405
414
|
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
406
415
|
* connections indefinitely and never errors on its own.
|
|
407
416
|
*
|
|
@@ -410,9 +419,6 @@ var AmqpClient = class {
|
|
|
410
419
|
* connection's reference count. Callers must invoke `close()` on the error
|
|
411
420
|
* path to release the connection — `waitForConnect` does not do this
|
|
412
421
|
* automatically. The typed factories handle this cleanup for you.
|
|
413
|
-
*
|
|
414
|
-
* @returns A Future resolving to `Result.Ok(void)` on connect, or
|
|
415
|
-
* `Result.Error(TechnicalError)` on timeout / connection failure.
|
|
416
422
|
*/
|
|
417
423
|
waitForConnect() {
|
|
418
424
|
const connectPromise = this.channelWrapper.waitForConnect();
|
|
@@ -429,50 +435,76 @@ var AmqpClient = class {
|
|
|
429
435
|
reject(error);
|
|
430
436
|
});
|
|
431
437
|
});
|
|
432
|
-
return
|
|
438
|
+
return ResultAsync.fromPromise(racedPromise, (error) => new TechnicalError("Failed to connect to AMQP broker", error));
|
|
433
439
|
}
|
|
434
440
|
/**
|
|
435
441
|
* Publish a message to an exchange.
|
|
436
442
|
*
|
|
437
|
-
* @
|
|
438
|
-
* @param routingKey - The routing key
|
|
439
|
-
* @param content - The message content (will be JSON serialized if json: true)
|
|
440
|
-
* @param options - Optional publish options
|
|
441
|
-
* @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
|
|
443
|
+
* @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
442
444
|
*/
|
|
443
445
|
publish(exchange, routingKey, content, options) {
|
|
444
|
-
return
|
|
446
|
+
return ResultAsync.fromPromise(this.channelWrapper.publish(exchange, routingKey, content, options), (error) => new TechnicalError("Failed to publish message", error));
|
|
445
447
|
}
|
|
446
448
|
/**
|
|
447
449
|
* Publish a message directly to a queue.
|
|
448
450
|
*
|
|
449
|
-
* @
|
|
450
|
-
* @param content - The message content (will be JSON serialized if json: true)
|
|
451
|
-
* @param options - Optional publish options
|
|
452
|
-
* @returns A Future with `Result<boolean>` - true if message was sent, false if channel buffer is full
|
|
451
|
+
* @returns ResultAsync resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
453
452
|
*/
|
|
454
453
|
sendToQueue(queue, content, options) {
|
|
455
|
-
return
|
|
454
|
+
return ResultAsync.fromPromise(this.channelWrapper.sendToQueue(queue, content, options), (error) => new TechnicalError("Failed to publish message to queue", error));
|
|
456
455
|
}
|
|
457
456
|
/**
|
|
458
457
|
* Start consuming messages from a queue.
|
|
459
458
|
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
459
|
+
* If `options.prefetch` is set, a per-consumer prefetch count is applied via
|
|
460
|
+
* `channel.prefetch(count, false)` registered as a setup function on the
|
|
461
|
+
* channel wrapper *before* the underlying `consume` call. Registering it via
|
|
462
|
+
* `addSetup` ensures the prefetch is reapplied automatically on channel
|
|
463
|
+
* reconnect; using `global=false` scopes it to subsequent consumers on the
|
|
464
|
+
* channel (RabbitMQ semantics — opposite of intuition: `false` is per-
|
|
465
|
+
* consumer, `true` is channel-wide).
|
|
466
|
+
*
|
|
467
|
+
* `prefetch` is stripped from the options handed to `channelWrapper.consume`
|
|
468
|
+
* because it is not a valid `amqplib` `Options.Consume` field — leaving it
|
|
469
|
+
* in would just travel as a no-op key-value pair on the consume frame.
|
|
470
|
+
*
|
|
471
|
+
* @returns ResultAsync resolving to the consumer tag.
|
|
464
472
|
*/
|
|
465
473
|
consume(queue, callback, options) {
|
|
466
|
-
|
|
474
|
+
const { prefetch, ...consumeOptions } = options ?? {};
|
|
475
|
+
if (prefetch !== void 0) {
|
|
476
|
+
if (!Number.isInteger(prefetch) || prefetch < 0 || prefetch > 65535) return errAsync(new TechnicalError(`Invalid prefetch: expected a non-negative integer ≤ 65535, got ${String(prefetch)}`));
|
|
477
|
+
}
|
|
478
|
+
const prefetchSetup = typeof prefetch === "number" ? async (channel) => {
|
|
479
|
+
await channel.prefetch(prefetch, false);
|
|
480
|
+
} : void 0;
|
|
481
|
+
const consumePromise = (async () => {
|
|
482
|
+
if (prefetchSetup) await this.channelWrapper.addSetup(prefetchSetup);
|
|
483
|
+
let reply;
|
|
484
|
+
try {
|
|
485
|
+
reply = await this.channelWrapper.consume(queue, callback, consumeOptions);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if (prefetchSetup) await this.channelWrapper.removeSetup(prefetchSetup).catch(() => {});
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
if (prefetchSetup) this.prefetchSetups.set(reply.consumerTag, prefetchSetup);
|
|
491
|
+
return reply;
|
|
492
|
+
})();
|
|
493
|
+
return ResultAsync.fromPromise(consumePromise, (error) => new TechnicalError("Failed to start consuming messages", error)).map((reply) => reply.consumerTag);
|
|
467
494
|
}
|
|
468
495
|
/**
|
|
469
496
|
* Cancel a consumer by its consumer tag.
|
|
470
|
-
*
|
|
471
|
-
* @param consumerTag - The consumer tag to cancel
|
|
472
|
-
* @returns A Future that resolves when the consumer is cancelled
|
|
473
497
|
*/
|
|
474
498
|
cancel(consumerTag) {
|
|
475
|
-
return
|
|
499
|
+
return ResultAsync.fromPromise((async () => {
|
|
500
|
+
const setup = this.prefetchSetups.get(consumerTag);
|
|
501
|
+
this.prefetchSetups.delete(consumerTag);
|
|
502
|
+
try {
|
|
503
|
+
await this.channelWrapper.cancel(consumerTag);
|
|
504
|
+
} finally {
|
|
505
|
+
if (setup !== void 0) await this.channelWrapper.removeSetup(setup).catch(() => {});
|
|
506
|
+
}
|
|
507
|
+
})(), (error) => new TechnicalError("Failed to cancel consumer", error)).map(() => void 0);
|
|
476
508
|
}
|
|
477
509
|
/**
|
|
478
510
|
* Acknowledge a message.
|
|
@@ -525,13 +557,18 @@ var AmqpClient = class {
|
|
|
525
557
|
* - Decrease the reference count on the shared connection
|
|
526
558
|
* - Close the connection if this was the last client using it
|
|
527
559
|
*
|
|
528
|
-
*
|
|
560
|
+
* Both steps run regardless of each other's outcome; if both fail, the
|
|
561
|
+
* errors are wrapped in an AggregateError.
|
|
529
562
|
*/
|
|
530
563
|
close() {
|
|
531
|
-
return
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
564
|
+
return new ResultAsync((async () => {
|
|
565
|
+
const channelResult = await ResultAsync.fromPromise(this.channelWrapper.close(), (error) => new TechnicalError("Failed to close channel", error));
|
|
566
|
+
const releaseResult = await ResultAsync.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions), (error) => new TechnicalError("Failed to release connection", error));
|
|
567
|
+
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")));
|
|
568
|
+
if (channelResult.isErr()) return channelResult;
|
|
569
|
+
if (releaseResult.isErr()) return releaseResult;
|
|
570
|
+
return ok(void 0);
|
|
571
|
+
})());
|
|
535
572
|
}
|
|
536
573
|
/**
|
|
537
574
|
* Reset connection singleton cache (for testing only)
|
|
@@ -542,6 +579,32 @@ var AmqpClient = class {
|
|
|
542
579
|
}
|
|
543
580
|
};
|
|
544
581
|
//#endregion
|
|
582
|
+
//#region src/parsing.ts
|
|
583
|
+
/**
|
|
584
|
+
* Parse a `Buffer` as JSON, mapping any `JSON.parse` exception to the
|
|
585
|
+
* caller-supplied error type.
|
|
586
|
+
*
|
|
587
|
+
* Use this in consume / reply paths where a parse failure is a typed value,
|
|
588
|
+
* not a thrown exception — the caller decides how to translate the raw error
|
|
589
|
+
* into a domain-level error (e.g. {@link TechnicalError}).
|
|
590
|
+
*
|
|
591
|
+
* @typeParam E - The error type produced by `errorFn`.
|
|
592
|
+
* @param buffer - The raw message body to parse.
|
|
593
|
+
* @param errorFn - Callback invoked with the underlying `JSON.parse` error.
|
|
594
|
+
* @returns A `Result` containing the parsed `unknown` value or the mapped error.
|
|
595
|
+
*
|
|
596
|
+
* @example
|
|
597
|
+
* ```typescript
|
|
598
|
+
* const parsed = safeJsonParse(
|
|
599
|
+
* msg.content,
|
|
600
|
+
* (error) => new TechnicalError("Failed to parse JSON", error),
|
|
601
|
+
* );
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
function safeJsonParse(buffer, errorFn) {
|
|
605
|
+
return Result.fromThrowable(() => JSON.parse(buffer.toString()), errorFn)();
|
|
606
|
+
}
|
|
607
|
+
//#endregion
|
|
545
608
|
//#region src/telemetry.ts
|
|
546
609
|
/**
|
|
547
610
|
* SpanKind values from OpenTelemetry.
|
|
@@ -806,6 +869,6 @@ function _resetTelemetryCacheForTesting() {
|
|
|
806
869
|
cachedLateRpcReplyCounter = void 0;
|
|
807
870
|
}
|
|
808
871
|
//#endregion
|
|
809
|
-
export { AmqpClient, DEFAULT_CONNECT_TIMEOUT_MS, MessageValidationError, MessagingSemanticConventions, TechnicalError, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, setupAmqpTopology, startConsumeSpan, startPublishSpan };
|
|
872
|
+
export { AmqpClient, DEFAULT_CONNECT_TIMEOUT_MS, MessageValidationError, MessagingSemanticConventions, TechnicalError, _getConnectionCountForTesting, _resetConnectionsForTesting, _resetTelemetryCacheForTesting, defaultTelemetryProvider, endSpanError, endSpanSuccess, recordConsumeMetric, recordLateRpcReply, recordPublishMetric, safeJsonParse, setupAmqpTopology, startConsumeSpan, startPublishSpan };
|
|
810
873
|
|
|
811
874
|
//# sourceMappingURL=index.mjs.map
|