@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/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Core utilities for AMQP setup and management in amqp-contract.**
|
|
4
4
|
|
|
5
|
-
[](https://github.com/btravstack/amqp-contract/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/@amqp-contract/core)
|
|
7
7
|
[](https://www.npmjs.com/package/@amqp-contract/core)
|
|
8
8
|
[](https://www.typescriptlang.org/)
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
This package provides centralized functionality for establishing AMQP topology (exchanges, queues, and bindings) from contract definitions, and defines the `Logger` interface used across amqp-contract packages.
|
|
12
12
|
|
|
13
|
-
📖 **[Full documentation →](https://
|
|
13
|
+
📖 **[Full documentation →](https://btravstack.github.io/amqp-contract)**
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
@@ -68,7 +68,7 @@ const amqpClient = new AmqpClient(contract, {
|
|
|
68
68
|
await amqpClient.close();
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
For advanced channel configuration options (custom setup, prefetch, publisher confirms), see the [Channel Configuration Guide](https://
|
|
71
|
+
For advanced channel configuration options (custom setup, prefetch, publisher confirms), see the [Channel Configuration Guide](https://btravstack.github.io/amqp-contract/guide/channel-configuration).
|
|
72
72
|
|
|
73
73
|
### Logger Interface
|
|
74
74
|
|
|
@@ -93,16 +93,16 @@ const client = (
|
|
|
93
93
|
urls: ["amqp://localhost"],
|
|
94
94
|
logger, // Optional: logs published messages
|
|
95
95
|
})
|
|
96
|
-
).
|
|
96
|
+
).unwrap();
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
## API
|
|
100
100
|
|
|
101
|
-
For complete API documentation, see the [@amqp-contract/core API Reference](https://
|
|
101
|
+
For complete API documentation, see the [@amqp-contract/core API Reference](https://btravstack.github.io/amqp-contract/api/core).
|
|
102
102
|
|
|
103
103
|
## Documentation
|
|
104
104
|
|
|
105
|
-
📖 **[Read the full documentation →](https://
|
|
105
|
+
📖 **[Read the full documentation →](https://btravstack.github.io/amqp-contract)**
|
|
106
106
|
|
|
107
107
|
## License
|
|
108
108
|
|
package/dist/index.cjs
CHANGED
|
@@ -21,7 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
-
let
|
|
24
|
+
let unthrown = require("unthrown");
|
|
25
25
|
let amqp_connection_manager = require("amqp-connection-manager");
|
|
26
26
|
amqp_connection_manager = __toESM(amqp_connection_manager, 1);
|
|
27
27
|
let _amqp_contract_contract = require("@amqp-contract/contract");
|
|
@@ -184,33 +184,40 @@ function _resetConnectionsForTesting() {
|
|
|
184
184
|
*
|
|
185
185
|
* This includes AMQP connection failures, channel issues, validation failures,
|
|
186
186
|
* and other runtime errors. This error is shared across core, worker, and client packages.
|
|
187
|
+
*
|
|
188
|
+
* Built on unthrown's {@link TaggedError}, so it carries a `_tag` of
|
|
189
|
+
* `"@amqp-contract/TechnicalError"` for exhaustive dispatch via `matchTags`. The
|
|
190
|
+
* tag is namespaced to avoid colliding with other libraries' tags in a shared
|
|
191
|
+
* `matchTags`; the human-facing `Error.name` is kept bare (`"TechnicalError"`).
|
|
192
|
+
* Remains a real `Error` (and a *modeled* error — it lives in the `E` channel of
|
|
193
|
+
* a `Result`, never the `Defect` channel).
|
|
187
194
|
*/
|
|
188
|
-
var TechnicalError = class extends
|
|
195
|
+
var TechnicalError = class extends (0, unthrown.TaggedError)("@amqp-contract/TechnicalError", { name: "TechnicalError" }) {
|
|
189
196
|
constructor(message, cause) {
|
|
190
|
-
super(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
|
|
197
|
+
super({
|
|
198
|
+
message,
|
|
199
|
+
cause
|
|
200
|
+
});
|
|
195
201
|
}
|
|
196
202
|
};
|
|
197
203
|
/**
|
|
198
204
|
* Error thrown when message validation fails (payload or headers).
|
|
199
205
|
*
|
|
200
206
|
* Used by both the client (publish-time payload validation) and the worker
|
|
201
|
-
* (consume-time payload and headers validation).
|
|
207
|
+
* (consume-time payload and headers validation). Carries a `_tag` of
|
|
208
|
+
* `"@amqp-contract/MessageValidationError"` (namespaced to avoid collisions);
|
|
209
|
+
* the `Error.name` is kept bare (`"MessageValidationError"`).
|
|
202
210
|
*
|
|
203
211
|
* @param source - The name of the publisher or consumer that triggered the validation
|
|
204
212
|
* @param issues - The validation issues from the Standard Schema validation
|
|
205
213
|
*/
|
|
206
|
-
var MessageValidationError = class extends
|
|
214
|
+
var MessageValidationError = class extends (0, unthrown.TaggedError)("@amqp-contract/MessageValidationError", { name: "MessageValidationError" }) {
|
|
207
215
|
constructor(source, issues) {
|
|
208
|
-
super(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (typeof ErrorConstructor.captureStackTrace === "function") ErrorConstructor.captureStackTrace(this, this.constructor);
|
|
216
|
+
super({
|
|
217
|
+
message: `Message validation failed for "${source}"`,
|
|
218
|
+
source,
|
|
219
|
+
issues
|
|
220
|
+
});
|
|
214
221
|
}
|
|
215
222
|
};
|
|
216
223
|
//#endregion
|
|
@@ -238,10 +245,10 @@ var MessageValidationError = class extends Error {
|
|
|
238
245
|
async function setupAmqpTopology(channel, contract) {
|
|
239
246
|
const exchanges = Object.values(contract.exchanges ?? {}).filter((e) => e.name !== "");
|
|
240
247
|
const exchangeErrors = (await Promise.allSettled(exchanges.map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
|
|
241
|
-
durable: exchange.durable,
|
|
242
|
-
autoDelete: exchange.autoDelete,
|
|
243
|
-
internal: exchange.internal,
|
|
244
|
-
arguments: exchange.arguments
|
|
248
|
+
...exchange.durable !== void 0 && { durable: exchange.durable },
|
|
249
|
+
...exchange.autoDelete !== void 0 && { autoDelete: exchange.autoDelete },
|
|
250
|
+
...exchange.internal !== void 0 && { internal: exchange.internal },
|
|
251
|
+
...exchange.arguments !== void 0 && { arguments: exchange.arguments }
|
|
245
252
|
})))).map((result, i) => ({
|
|
246
253
|
result,
|
|
247
254
|
name: exchanges[i].name
|
|
@@ -272,9 +279,9 @@ async function setupAmqpTopology(channel, contract) {
|
|
|
272
279
|
});
|
|
273
280
|
if (queue.maxPriority !== void 0) queueArguments["x-max-priority"] = queue.maxPriority;
|
|
274
281
|
return channel.assertQueue(queue.name, {
|
|
275
|
-
durable: queue.durable,
|
|
276
|
-
exclusive: queue.exclusive,
|
|
277
|
-
autoDelete: queue.autoDelete,
|
|
282
|
+
...queue.durable !== void 0 && { durable: queue.durable },
|
|
283
|
+
...queue.exclusive !== void 0 && { exclusive: queue.exclusive },
|
|
284
|
+
...queue.autoDelete !== void 0 && { autoDelete: queue.autoDelete },
|
|
278
285
|
arguments: queueArguments
|
|
279
286
|
});
|
|
280
287
|
}))).map((result, i) => ({
|
|
@@ -348,7 +355,7 @@ function resolveConnectTimeoutMs(input) {
|
|
|
348
355
|
* - Automatic AMQP topology setup (exchanges, queues, bindings) from contract
|
|
349
356
|
* - Channel creation with JSON serialization enabled by default
|
|
350
357
|
*
|
|
351
|
-
* All operations return `
|
|
358
|
+
* All operations return `AsyncResult<T, TechnicalError>` for consistent error handling.
|
|
352
359
|
*
|
|
353
360
|
* @example
|
|
354
361
|
* ```typescript
|
|
@@ -357,7 +364,7 @@ function resolveConnectTimeoutMs(input) {
|
|
|
357
364
|
* connectionOptions: { heartbeatIntervalInSeconds: 30 }
|
|
358
365
|
* });
|
|
359
366
|
*
|
|
360
|
-
* // Wait for connection (
|
|
367
|
+
* // Wait for connection (AsyncResult is thenable)
|
|
361
368
|
* await client.waitForConnect();
|
|
362
369
|
*
|
|
363
370
|
* // Publish a message
|
|
@@ -368,6 +375,7 @@ function resolveConnectTimeoutMs(input) {
|
|
|
368
375
|
* ```
|
|
369
376
|
*/
|
|
370
377
|
var AmqpClient = class {
|
|
378
|
+
contract;
|
|
371
379
|
connection;
|
|
372
380
|
channelWrapper;
|
|
373
381
|
urls;
|
|
@@ -375,6 +383,15 @@ var AmqpClient = class {
|
|
|
375
383
|
/** Resolved timeout in ms; `null` means "wait forever". */
|
|
376
384
|
connectTimeoutMs;
|
|
377
385
|
/**
|
|
386
|
+
* Per-consumer prefetch setup functions registered via `addSetup` so they
|
|
387
|
+
* can be removed in {@link cancel} once the consumer is gone — otherwise
|
|
388
|
+
* the channel wrapper would replay the cancelled consumer's QoS on every
|
|
389
|
+
* reconnect and silently apply it to subsequent consumers.
|
|
390
|
+
*
|
|
391
|
+
* @internal
|
|
392
|
+
*/
|
|
393
|
+
prefetchSetups = /* @__PURE__ */ new Map();
|
|
394
|
+
/**
|
|
378
395
|
* Create a new AMQP client instance.
|
|
379
396
|
*
|
|
380
397
|
* The client will automatically:
|
|
@@ -422,7 +439,7 @@ var AmqpClient = class {
|
|
|
422
439
|
* Wait for the channel to be connected and ready.
|
|
423
440
|
*
|
|
424
441
|
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
425
|
-
*
|
|
442
|
+
* AsyncResult resolves to `err(TechnicalError)` once the timeout elapses.
|
|
426
443
|
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
427
444
|
* connections indefinitely and never errors on its own.
|
|
428
445
|
*
|
|
@@ -435,7 +452,7 @@ var AmqpClient = class {
|
|
|
435
452
|
waitForConnect() {
|
|
436
453
|
const connectPromise = this.channelWrapper.waitForConnect();
|
|
437
454
|
const timeoutMs = this.connectTimeoutMs;
|
|
438
|
-
|
|
455
|
+
return (0, unthrown.fromPromise)(timeoutMs === null ? connectPromise : new Promise((resolve, reject) => {
|
|
439
456
|
const handle = setTimeout(() => {
|
|
440
457
|
reject(/* @__PURE__ */ new Error(`Timed out waiting for AMQP connection after ${timeoutMs}ms`));
|
|
441
458
|
}, timeoutMs);
|
|
@@ -446,38 +463,75 @@ var AmqpClient = class {
|
|
|
446
463
|
clearTimeout(handle);
|
|
447
464
|
reject(error);
|
|
448
465
|
});
|
|
449
|
-
});
|
|
450
|
-
return neverthrow.ResultAsync.fromPromise(racedPromise, (error) => new TechnicalError("Failed to connect to AMQP broker", error));
|
|
466
|
+
}), (error) => new TechnicalError("Failed to connect to AMQP broker", error));
|
|
451
467
|
}
|
|
452
468
|
/**
|
|
453
469
|
* Publish a message to an exchange.
|
|
454
470
|
*
|
|
455
|
-
* @returns
|
|
471
|
+
* @returns AsyncResult resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
456
472
|
*/
|
|
457
473
|
publish(exchange, routingKey, content, options) {
|
|
458
|
-
return
|
|
474
|
+
return (0, unthrown.fromPromise)(this.channelWrapper.publish(exchange, routingKey, content, options), (error) => new TechnicalError("Failed to publish message", error));
|
|
459
475
|
}
|
|
460
476
|
/**
|
|
461
477
|
* Publish a message directly to a queue.
|
|
462
478
|
*
|
|
463
|
-
* @returns
|
|
479
|
+
* @returns AsyncResult resolving to `true` if the message was sent, `false` if the channel buffer is full.
|
|
464
480
|
*/
|
|
465
481
|
sendToQueue(queue, content, options) {
|
|
466
|
-
return
|
|
482
|
+
return (0, unthrown.fromPromise)(this.channelWrapper.sendToQueue(queue, content, options), (error) => new TechnicalError("Failed to publish message to queue", error));
|
|
467
483
|
}
|
|
468
484
|
/**
|
|
469
485
|
* Start consuming messages from a queue.
|
|
470
486
|
*
|
|
471
|
-
*
|
|
487
|
+
* If `options.prefetch` is set, a per-consumer prefetch count is applied via
|
|
488
|
+
* `channel.prefetch(count, false)` registered as a setup function on the
|
|
489
|
+
* channel wrapper *before* the underlying `consume` call. Registering it via
|
|
490
|
+
* `addSetup` ensures the prefetch is reapplied automatically on channel
|
|
491
|
+
* reconnect; using `global=false` scopes it to subsequent consumers on the
|
|
492
|
+
* channel (RabbitMQ semantics — opposite of intuition: `false` is per-
|
|
493
|
+
* consumer, `true` is channel-wide).
|
|
494
|
+
*
|
|
495
|
+
* `prefetch` is stripped from the options handed to `channelWrapper.consume`
|
|
496
|
+
* because it is not a valid `amqplib` `Options.Consume` field — leaving it
|
|
497
|
+
* in would just travel as a no-op key-value pair on the consume frame.
|
|
498
|
+
*
|
|
499
|
+
* @returns AsyncResult resolving to the consumer tag.
|
|
472
500
|
*/
|
|
473
501
|
consume(queue, callback, options) {
|
|
474
|
-
|
|
502
|
+
const { prefetch, ...consumeOptions } = options ?? {};
|
|
503
|
+
if (prefetch !== void 0) {
|
|
504
|
+
if (!Number.isInteger(prefetch) || prefetch < 0 || prefetch > 65535) return (0, unthrown.err)(new TechnicalError(`Invalid prefetch: expected a non-negative integer ≤ 65535, got ${String(prefetch)}`)).toAsync();
|
|
505
|
+
}
|
|
506
|
+
const prefetchSetup = typeof prefetch === "number" ? async (channel) => {
|
|
507
|
+
await channel.prefetch(prefetch, false);
|
|
508
|
+
} : void 0;
|
|
509
|
+
return (0, unthrown.fromPromise)((async () => {
|
|
510
|
+
if (prefetchSetup) await this.channelWrapper.addSetup(prefetchSetup);
|
|
511
|
+
let reply;
|
|
512
|
+
try {
|
|
513
|
+
reply = await this.channelWrapper.consume(queue, callback, consumeOptions);
|
|
514
|
+
} catch (error) {
|
|
515
|
+
if (prefetchSetup) await this.channelWrapper.removeSetup(prefetchSetup).catch(() => {});
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
if (prefetchSetup) this.prefetchSetups.set(reply.consumerTag, prefetchSetup);
|
|
519
|
+
return reply;
|
|
520
|
+
})(), (error) => new TechnicalError("Failed to start consuming messages", error)).map((reply) => reply.consumerTag);
|
|
475
521
|
}
|
|
476
522
|
/**
|
|
477
523
|
* Cancel a consumer by its consumer tag.
|
|
478
524
|
*/
|
|
479
525
|
cancel(consumerTag) {
|
|
480
|
-
return
|
|
526
|
+
return (0, unthrown.fromPromise)((async () => {
|
|
527
|
+
const setup = this.prefetchSetups.get(consumerTag);
|
|
528
|
+
this.prefetchSetups.delete(consumerTag);
|
|
529
|
+
try {
|
|
530
|
+
await this.channelWrapper.cancel(consumerTag);
|
|
531
|
+
} finally {
|
|
532
|
+
if (setup !== void 0) await this.channelWrapper.removeSetup(setup).catch(() => {});
|
|
533
|
+
}
|
|
534
|
+
})(), (error) => new TechnicalError("Failed to cancel consumer", error)).map(() => void 0);
|
|
481
535
|
}
|
|
482
536
|
/**
|
|
483
537
|
* Acknowledge a message.
|
|
@@ -534,14 +588,14 @@ var AmqpClient = class {
|
|
|
534
588
|
* errors are wrapped in an AggregateError.
|
|
535
589
|
*/
|
|
536
590
|
close() {
|
|
537
|
-
return
|
|
538
|
-
const channelResult = await
|
|
539
|
-
const releaseResult = await
|
|
540
|
-
if (channelResult.isErr() && releaseResult.isErr()) return (0,
|
|
591
|
+
return (0, unthrown.fromSafePromise)((async () => {
|
|
592
|
+
const channelResult = await (0, unthrown.fromPromise)(this.channelWrapper.close(), (error) => new TechnicalError("Failed to close channel", error));
|
|
593
|
+
const releaseResult = await (0, unthrown.fromPromise)(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions), (error) => new TechnicalError("Failed to release connection", error));
|
|
594
|
+
if (channelResult.isErr() && releaseResult.isErr()) return (0, unthrown.err)(new TechnicalError("Failed to close channel and release connection", new AggregateError([channelResult.error, releaseResult.error], "Failed to close channel and release connection")));
|
|
541
595
|
if (channelResult.isErr()) return channelResult;
|
|
542
596
|
if (releaseResult.isErr()) return releaseResult;
|
|
543
|
-
return (0,
|
|
544
|
-
})());
|
|
597
|
+
return (0, unthrown.ok)(void 0);
|
|
598
|
+
})()).flatMap((result) => result);
|
|
545
599
|
}
|
|
546
600
|
/**
|
|
547
601
|
* Reset connection singleton cache (for testing only)
|
|
@@ -552,6 +606,32 @@ var AmqpClient = class {
|
|
|
552
606
|
}
|
|
553
607
|
};
|
|
554
608
|
//#endregion
|
|
609
|
+
//#region src/parsing.ts
|
|
610
|
+
/**
|
|
611
|
+
* Parse a `Buffer` as JSON, mapping any `JSON.parse` exception to the
|
|
612
|
+
* caller-supplied error type.
|
|
613
|
+
*
|
|
614
|
+
* Use this in consume / reply paths where a parse failure is a typed value,
|
|
615
|
+
* not a thrown exception — the caller decides how to translate the raw error
|
|
616
|
+
* into a domain-level error (e.g. {@link TechnicalError}).
|
|
617
|
+
*
|
|
618
|
+
* @typeParam E - The error type produced by `errorFn`.
|
|
619
|
+
* @param buffer - The raw message body to parse.
|
|
620
|
+
* @param errorFn - Callback invoked with the underlying `JSON.parse` error.
|
|
621
|
+
* @returns A `Result` containing the parsed `unknown` value or the mapped error.
|
|
622
|
+
*
|
|
623
|
+
* @example
|
|
624
|
+
* ```typescript
|
|
625
|
+
* const parsed = safeJsonParse(
|
|
626
|
+
* msg.content,
|
|
627
|
+
* (error) => new TechnicalError("Failed to parse JSON", error),
|
|
628
|
+
* );
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
631
|
+
function safeJsonParse(buffer, errorFn) {
|
|
632
|
+
return (0, unthrown.fromThrowable)(() => JSON.parse(buffer.toString()), errorFn)();
|
|
633
|
+
}
|
|
634
|
+
//#endregion
|
|
555
635
|
//#region src/telemetry.ts
|
|
556
636
|
/**
|
|
557
637
|
* SpanKind values from OpenTelemetry.
|
|
@@ -830,6 +910,7 @@ exports.endSpanSuccess = endSpanSuccess;
|
|
|
830
910
|
exports.recordConsumeMetric = recordConsumeMetric;
|
|
831
911
|
exports.recordLateRpcReply = recordLateRpcReply;
|
|
832
912
|
exports.recordPublishMetric = recordPublishMetric;
|
|
913
|
+
exports.safeJsonParse = safeJsonParse;
|
|
833
914
|
exports.setupAmqpTopology = setupAmqpTopology;
|
|
834
915
|
exports.startConsumeSpan = startConsumeSpan;
|
|
835
916
|
exports.startPublishSpan = startPublishSpan;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,32 +1,47 @@
|
|
|
1
1
|
import { ContractDefinition } from "@amqp-contract/contract";
|
|
2
2
|
import { AmqpConnectionManager, AmqpConnectionManagerOptions, ConnectionUrl, CreateChannelOpts } from "amqp-connection-manager";
|
|
3
3
|
import { Channel, ConsumeMessage, Options } from "amqplib";
|
|
4
|
-
import {
|
|
4
|
+
import { AsyncResult, Result } from "unthrown";
|
|
5
5
|
import { Attributes, Counter, Histogram, Span, Tracer } from "@opentelemetry/api";
|
|
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.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","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.cts","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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"}
|