@amqp-contract/core 0.21.0 → 0.23.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 +168 -18
- package/dist/index.d.cts +57 -78
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +57 -78
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +165 -19
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +120 -157
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ let _swan_io_boxed = require("@swan-io/boxed");
|
|
|
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");
|
|
28
|
+
let node_module = require("node:module");
|
|
28
29
|
//#region src/connection-manager.ts
|
|
29
30
|
/**
|
|
30
31
|
* Connection manager singleton for sharing AMQP connections across clients.
|
|
@@ -139,6 +140,14 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
|
|
|
139
140
|
return value;
|
|
140
141
|
}
|
|
141
142
|
/**
|
|
143
|
+
* Get the number of active pooled connections.
|
|
144
|
+
*
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
_getConnectionCountForTesting() {
|
|
148
|
+
return this.connections.size;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
142
151
|
* Reset all cached connections (for testing purposes)
|
|
143
152
|
* @internal
|
|
144
153
|
*/
|
|
@@ -149,6 +158,25 @@ var ConnectionManagerSingleton = class ConnectionManagerSingleton {
|
|
|
149
158
|
this.refCounts.clear();
|
|
150
159
|
}
|
|
151
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* Number of active pooled connections. Test-only helper — exposed in lieu of
|
|
163
|
+
* the underlying singleton, which is intentionally not part of the public API
|
|
164
|
+
* (mutating it from outside the library can break in-flight clients sharing a
|
|
165
|
+
* connection).
|
|
166
|
+
*
|
|
167
|
+
* @internal
|
|
168
|
+
*/
|
|
169
|
+
function _getConnectionCountForTesting() {
|
|
170
|
+
return ConnectionManagerSingleton.getInstance()._getConnectionCountForTesting();
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Close every pooled connection and clear ref-counts. Test-only helper.
|
|
174
|
+
*
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
function _resetConnectionsForTesting() {
|
|
178
|
+
return ConnectionManagerSingleton.getInstance()._resetForTesting();
|
|
179
|
+
}
|
|
152
180
|
//#endregion
|
|
153
181
|
//#region src/errors.ts
|
|
154
182
|
/**
|
|
@@ -208,13 +236,20 @@ var MessageValidationError = class extends Error {
|
|
|
208
236
|
* ```
|
|
209
237
|
*/
|
|
210
238
|
async function setupAmqpTopology(channel, contract) {
|
|
211
|
-
const
|
|
239
|
+
const exchanges = Object.values(contract.exchanges ?? {}).filter((e) => e.name !== "");
|
|
240
|
+
const exchangeErrors = (await Promise.allSettled(exchanges.map((exchange) => channel.assertExchange(exchange.name, exchange.type, {
|
|
212
241
|
durable: exchange.durable,
|
|
213
242
|
autoDelete: exchange.autoDelete,
|
|
214
243
|
internal: exchange.internal,
|
|
215
244
|
arguments: exchange.arguments
|
|
216
|
-
})))).
|
|
217
|
-
|
|
245
|
+
})))).map((result, i) => ({
|
|
246
|
+
result,
|
|
247
|
+
name: exchanges[i].name
|
|
248
|
+
})).filter((entry) => entry.result.status === "rejected");
|
|
249
|
+
if (exchangeErrors.length > 0) {
|
|
250
|
+
const names = exchangeErrors.map((e) => e.name).join(", ");
|
|
251
|
+
throw new AggregateError(exchangeErrors.map(({ result }) => result.reason), `Failed to setup exchanges: ${names}`);
|
|
252
|
+
}
|
|
218
253
|
for (const queueEntry of Object.values(contract.queues ?? {})) {
|
|
219
254
|
const queue = (0, _amqp_contract_contract.extractQueue)(queueEntry);
|
|
220
255
|
if (queue.deadLetter) {
|
|
@@ -222,7 +257,8 @@ async function setupAmqpTopology(channel, contract) {
|
|
|
222
257
|
if (!Object.values(contract.exchanges ?? {}).some((exchange) => exchange.name === dlxName)) throw new TechnicalError(`Queue "${queue.name}" references dead letter exchange "${dlxName}" which is not declared in the contract. Add the exchange to contract.exchanges to ensure it is created before the queue.`);
|
|
223
258
|
}
|
|
224
259
|
}
|
|
225
|
-
const
|
|
260
|
+
const queueEntries = Object.values(contract.queues ?? {});
|
|
261
|
+
const queueErrors = (await Promise.allSettled(queueEntries.map((queueEntry) => {
|
|
226
262
|
const queue = (0, _amqp_contract_contract.extractQueue)(queueEntry);
|
|
227
263
|
const queueArguments = { ...queue.arguments };
|
|
228
264
|
queueArguments["x-queue-type"] = queue.type;
|
|
@@ -241,13 +277,29 @@ async function setupAmqpTopology(channel, contract) {
|
|
|
241
277
|
autoDelete: queue.autoDelete,
|
|
242
278
|
arguments: queueArguments
|
|
243
279
|
});
|
|
244
|
-
}))).
|
|
245
|
-
|
|
246
|
-
|
|
280
|
+
}))).map((result, i) => ({
|
|
281
|
+
result,
|
|
282
|
+
name: (0, _amqp_contract_contract.extractQueue)(queueEntries[i]).name
|
|
283
|
+
})).filter((entry) => entry.result.status === "rejected");
|
|
284
|
+
if (queueErrors.length > 0) {
|
|
285
|
+
const names = queueErrors.map((e) => e.name).join(", ");
|
|
286
|
+
throw new AggregateError(queueErrors.map(({ result }) => result.reason), `Failed to setup queues: ${names}`);
|
|
287
|
+
}
|
|
288
|
+
const bindings = Object.values(contract.bindings ?? {});
|
|
289
|
+
const bindingErrors = (await Promise.allSettled(bindings.map((binding) => {
|
|
247
290
|
if (binding.type === "queue") return channel.bindQueue(binding.queue.name, binding.exchange.name, binding.routingKey ?? "", binding.arguments);
|
|
248
291
|
return channel.bindExchange(binding.destination.name, binding.source.name, binding.routingKey ?? "", binding.arguments);
|
|
249
|
-
}))).
|
|
250
|
-
|
|
292
|
+
}))).map((result, i) => {
|
|
293
|
+
const binding = bindings[i];
|
|
294
|
+
return {
|
|
295
|
+
result,
|
|
296
|
+
name: binding.type === "queue" ? `${binding.exchange.name} -> ${binding.queue.name}` : `${binding.source.name} -> ${binding.destination.name}`
|
|
297
|
+
};
|
|
298
|
+
}).filter((entry) => entry.result.status === "rejected");
|
|
299
|
+
if (bindingErrors.length > 0) {
|
|
300
|
+
const names = bindingErrors.map((e) => e.name).join(", ");
|
|
301
|
+
throw new AggregateError(bindingErrors.map(({ result }) => result.reason), `Failed to setup bindings: ${names}`);
|
|
302
|
+
}
|
|
251
303
|
}
|
|
252
304
|
//#endregion
|
|
253
305
|
//#region src/amqp-client.ts
|
|
@@ -266,6 +318,28 @@ function callSetupFunc(setup, channel) {
|
|
|
266
318
|
return setup(channel);
|
|
267
319
|
}
|
|
268
320
|
/**
|
|
321
|
+
* Default time `waitForConnect` will wait for the broker before erroring out.
|
|
322
|
+
* Defaulting to a finite value (rather than waiting forever) means a fail-fast
|
|
323
|
+
* developer experience: a misconfigured URL, a down broker, or wrong
|
|
324
|
+
* credentials surface as a Result.Error within 30 seconds. Pass `null`
|
|
325
|
+
* explicitly to disable the timeout — `Infinity` and other non-finite values
|
|
326
|
+
* are also coerced to "no timeout" because Node's `setTimeout` clamps large
|
|
327
|
+
* delays to ~24.8 days and silently fires near-immediately on `Infinity`.
|
|
328
|
+
*/
|
|
329
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
|
|
330
|
+
/**
|
|
331
|
+
* Normalise the user-supplied connect timeout to either a positive finite
|
|
332
|
+
* number of milliseconds, or `null` (no timeout). `Infinity`, `NaN`, and
|
|
333
|
+
* non-positive values all map to `null` rather than being passed to
|
|
334
|
+
* `setTimeout` — see {@link DEFAULT_CONNECT_TIMEOUT_MS}.
|
|
335
|
+
*/
|
|
336
|
+
function resolveConnectTimeoutMs(input) {
|
|
337
|
+
if (input === null) return null;
|
|
338
|
+
if (input === void 0) return DEFAULT_CONNECT_TIMEOUT_MS;
|
|
339
|
+
if (!Number.isFinite(input) || input <= 0) return null;
|
|
340
|
+
return input;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
269
343
|
* AMQP client that manages connections and channels with automatic topology setup.
|
|
270
344
|
*
|
|
271
345
|
* This class handles:
|
|
@@ -298,6 +372,8 @@ var AmqpClient = class {
|
|
|
298
372
|
channelWrapper;
|
|
299
373
|
urls;
|
|
300
374
|
connectionOptions;
|
|
375
|
+
/** Resolved timeout in ms; `null` means "wait forever". */
|
|
376
|
+
connectTimeoutMs;
|
|
301
377
|
/**
|
|
302
378
|
* Create a new AMQP client instance.
|
|
303
379
|
*
|
|
@@ -313,6 +389,7 @@ var AmqpClient = class {
|
|
|
313
389
|
this.contract = contract;
|
|
314
390
|
this.urls = options.urls;
|
|
315
391
|
if (options.connectionOptions !== void 0) this.connectionOptions = options.connectionOptions;
|
|
392
|
+
this.connectTimeoutMs = resolveConnectTimeoutMs(options.connectTimeoutMs);
|
|
316
393
|
const singleton = ConnectionManagerSingleton.getInstance();
|
|
317
394
|
this.connection = singleton.getConnection(options.urls, options.connectionOptions);
|
|
318
395
|
const defaultSetup = (channel) => setupAmqpTopology(channel, this.contract);
|
|
@@ -344,10 +421,36 @@ var AmqpClient = class {
|
|
|
344
421
|
/**
|
|
345
422
|
* Wait for the channel to be connected and ready.
|
|
346
423
|
*
|
|
347
|
-
*
|
|
424
|
+
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
425
|
+
* Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.
|
|
426
|
+
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
427
|
+
* connections indefinitely and never errors on its own.
|
|
428
|
+
*
|
|
429
|
+
* NOTE: When using `AmqpClient` directly (not via `TypedAmqpClient` /
|
|
430
|
+
* `TypedAmqpWorker`), the constructor has already incremented the pooled
|
|
431
|
+
* connection's reference count. Callers must invoke `close()` on the error
|
|
432
|
+
* path to release the connection — `waitForConnect` does not do this
|
|
433
|
+
* automatically. The typed factories handle this cleanup for you.
|
|
434
|
+
*
|
|
435
|
+
* @returns A Future resolving to `Result.Ok(void)` on connect, or
|
|
436
|
+
* `Result.Error(TechnicalError)` on timeout / connection failure.
|
|
348
437
|
*/
|
|
349
438
|
waitForConnect() {
|
|
350
|
-
|
|
439
|
+
const connectPromise = this.channelWrapper.waitForConnect();
|
|
440
|
+
const timeoutMs = this.connectTimeoutMs;
|
|
441
|
+
const racedPromise = timeoutMs === null ? connectPromise : new Promise((resolve, reject) => {
|
|
442
|
+
const handle = setTimeout(() => {
|
|
443
|
+
reject(/* @__PURE__ */ new Error(`Timed out waiting for AMQP connection after ${timeoutMs}ms`));
|
|
444
|
+
}, timeoutMs);
|
|
445
|
+
connectPromise.then(() => {
|
|
446
|
+
clearTimeout(handle);
|
|
447
|
+
resolve();
|
|
448
|
+
}, (error) => {
|
|
449
|
+
clearTimeout(handle);
|
|
450
|
+
reject(error);
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
return _swan_io_boxed.Future.fromPromise(racedPromise).mapError((error) => new TechnicalError("Failed to connect to AMQP broker", error));
|
|
351
454
|
}
|
|
352
455
|
/**
|
|
353
456
|
* Publish a message to an exchange.
|
|
@@ -446,7 +549,10 @@ var AmqpClient = class {
|
|
|
446
549
|
* @returns A Future that resolves when the channel and connection are closed
|
|
447
550
|
*/
|
|
448
551
|
close() {
|
|
449
|
-
return _swan_io_boxed.Future.fromPromise(this.channelWrapper.close()).mapError((error) => new TechnicalError("Failed to close channel", error)).
|
|
552
|
+
return _swan_io_boxed.Future.fromPromise(this.channelWrapper.close()).mapError((error) => new TechnicalError("Failed to close channel", error)).flatMap((channelResult) => _swan_io_boxed.Future.fromPromise(ConnectionManagerSingleton.getInstance().releaseConnection(this.urls, this.connectionOptions)).mapError((error) => new TechnicalError("Failed to release connection", error)).map((releaseResult) => {
|
|
553
|
+
if (channelResult.isError() && releaseResult.isError()) return _swan_io_boxed.Result.Error(new TechnicalError("Failed to close channel and release connection", new AggregateError([channelResult.error, releaseResult.error], "Failed to close channel and release connection")));
|
|
554
|
+
return channelResult.isError() ? channelResult : releaseResult;
|
|
555
|
+
}));
|
|
450
556
|
}
|
|
451
557
|
/**
|
|
452
558
|
* Reset connection singleton cache (for testing only)
|
|
@@ -464,7 +570,9 @@ var AmqpClient = class {
|
|
|
464
570
|
* @see https://opentelemetry.io/docs/specs/otel/trace/api/#spankind
|
|
465
571
|
*/
|
|
466
572
|
const SpanKind = {
|
|
573
|
+
/** Producer span represents a message producer */
|
|
467
574
|
PRODUCER: 3,
|
|
575
|
+
/** Consumer span represents a message consumer */
|
|
468
576
|
CONSUMER: 4
|
|
469
577
|
};
|
|
470
578
|
/**
|
|
@@ -491,13 +599,27 @@ const MessagingSemanticConventions = {
|
|
|
491
599
|
* Instrumentation scope name for amqp-contract.
|
|
492
600
|
*/
|
|
493
601
|
const INSTRUMENTATION_SCOPE_NAME = "@amqp-contract";
|
|
494
|
-
|
|
602
|
+
/**
|
|
603
|
+
* Instrumentation scope version, sourced from this package's package.json so
|
|
604
|
+
* the OTel meter version always tracks the released library version. We use
|
|
605
|
+
* `createRequire` rather than a JSON import attribute so the same source builds
|
|
606
|
+
* to ESM, CJS, and runs under bundlers that don't yet understand
|
|
607
|
+
* `import … with { type: "json" }`.
|
|
608
|
+
*/
|
|
609
|
+
const INSTRUMENTATION_SCOPE_VERSION = (() => {
|
|
610
|
+
try {
|
|
611
|
+
return (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("../package.json").version ?? "0.0.0";
|
|
612
|
+
} catch {
|
|
613
|
+
return "0.0.0";
|
|
614
|
+
}
|
|
615
|
+
})();
|
|
495
616
|
let otelApi;
|
|
496
617
|
let cachedTracer;
|
|
497
618
|
let cachedPublishCounter;
|
|
498
619
|
let cachedConsumeCounter;
|
|
499
620
|
let cachedPublishLatencyHistogram;
|
|
500
621
|
let cachedConsumeLatencyHistogram;
|
|
622
|
+
let cachedLateRpcReplyCounter;
|
|
501
623
|
/**
|
|
502
624
|
* Try to load the OpenTelemetry API module.
|
|
503
625
|
* Returns null if the module is not available.
|
|
@@ -528,14 +650,16 @@ function getMeterInstruments() {
|
|
|
528
650
|
publishCounter: cachedPublishCounter,
|
|
529
651
|
consumeCounter: cachedConsumeCounter,
|
|
530
652
|
publishLatencyHistogram: cachedPublishLatencyHistogram,
|
|
531
|
-
consumeLatencyHistogram: cachedConsumeLatencyHistogram
|
|
653
|
+
consumeLatencyHistogram: cachedConsumeLatencyHistogram,
|
|
654
|
+
lateRpcReplyCounter: cachedLateRpcReplyCounter
|
|
532
655
|
};
|
|
533
656
|
const api = tryLoadOpenTelemetryApi();
|
|
534
657
|
if (!api) return {
|
|
535
658
|
publishCounter: void 0,
|
|
536
659
|
consumeCounter: void 0,
|
|
537
660
|
publishLatencyHistogram: void 0,
|
|
538
|
-
consumeLatencyHistogram: void 0
|
|
661
|
+
consumeLatencyHistogram: void 0,
|
|
662
|
+
lateRpcReplyCounter: void 0
|
|
539
663
|
};
|
|
540
664
|
const meter = api.metrics.getMeter(INSTRUMENTATION_SCOPE_NAME, INSTRUMENTATION_SCOPE_VERSION);
|
|
541
665
|
cachedPublishCounter = meter.createCounter("amqp.client.messages.published", {
|
|
@@ -554,11 +678,16 @@ function getMeterInstruments() {
|
|
|
554
678
|
description: "Duration of message processing operations",
|
|
555
679
|
unit: "ms"
|
|
556
680
|
});
|
|
681
|
+
cachedLateRpcReplyCounter = meter.createCounter("amqp.client.rpc.late_reply", {
|
|
682
|
+
description: "RPC replies received after the caller stopped waiting (timeout, cancellation, or unknown correlationId)",
|
|
683
|
+
unit: "{message}"
|
|
684
|
+
});
|
|
557
685
|
return {
|
|
558
686
|
publishCounter: cachedPublishCounter,
|
|
559
687
|
consumeCounter: cachedConsumeCounter,
|
|
560
688
|
publishLatencyHistogram: cachedPublishLatencyHistogram,
|
|
561
|
-
consumeLatencyHistogram: cachedConsumeLatencyHistogram
|
|
689
|
+
consumeLatencyHistogram: cachedConsumeLatencyHistogram,
|
|
690
|
+
lateRpcReplyCounter: cachedLateRpcReplyCounter
|
|
562
691
|
};
|
|
563
692
|
}
|
|
564
693
|
/**
|
|
@@ -569,7 +698,8 @@ const defaultTelemetryProvider = {
|
|
|
569
698
|
getPublishCounter: () => getMeterInstruments().publishCounter,
|
|
570
699
|
getConsumeCounter: () => getMeterInstruments().consumeCounter,
|
|
571
700
|
getPublishLatencyHistogram: () => getMeterInstruments().publishLatencyHistogram,
|
|
572
|
-
getConsumeLatencyHistogram: () => getMeterInstruments().consumeLatencyHistogram
|
|
701
|
+
getConsumeLatencyHistogram: () => getMeterInstruments().consumeLatencyHistogram,
|
|
702
|
+
getLateRpcReplyCounter: () => getMeterInstruments().lateRpcReplyCounter
|
|
573
703
|
};
|
|
574
704
|
/**
|
|
575
705
|
* Create a span for a publish operation.
|
|
@@ -667,6 +797,22 @@ function recordConsumeMetric(provider, queueName, consumerName, success, duratio
|
|
|
667
797
|
consumeLatencyHistogram?.record(durationMs, attributes);
|
|
668
798
|
}
|
|
669
799
|
/**
|
|
800
|
+
* Record an RPC reply that arrived after the caller stopped waiting.
|
|
801
|
+
*
|
|
802
|
+
* @param reason - Why the reply was orphaned. `"unknown-correlation-id"` is
|
|
803
|
+
* the typical "caller already timed out" case; `"missing-correlation-id"`
|
|
804
|
+
* means the broker delivered a reply with no correlationId at all (a
|
|
805
|
+
* protocol violation by the responder).
|
|
806
|
+
*/
|
|
807
|
+
function recordLateRpcReply(provider, reason) {
|
|
808
|
+
const counter = provider.getLateRpcReplyCounter();
|
|
809
|
+
const attributes = {
|
|
810
|
+
[MessagingSemanticConventions.MESSAGING_SYSTEM]: MessagingSemanticConventions.MESSAGING_SYSTEM_RABBITMQ,
|
|
811
|
+
reason
|
|
812
|
+
};
|
|
813
|
+
counter?.add(1, attributes);
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
670
816
|
* Reset the cached OpenTelemetry API module and instruments.
|
|
671
817
|
* For testing purposes only.
|
|
672
818
|
* @internal
|
|
@@ -678,18 +824,22 @@ function _resetTelemetryCacheForTesting() {
|
|
|
678
824
|
cachedConsumeCounter = void 0;
|
|
679
825
|
cachedPublishLatencyHistogram = void 0;
|
|
680
826
|
cachedConsumeLatencyHistogram = void 0;
|
|
827
|
+
cachedLateRpcReplyCounter = void 0;
|
|
681
828
|
}
|
|
682
829
|
//#endregion
|
|
683
830
|
exports.AmqpClient = AmqpClient;
|
|
684
|
-
exports.
|
|
831
|
+
exports.DEFAULT_CONNECT_TIMEOUT_MS = DEFAULT_CONNECT_TIMEOUT_MS;
|
|
685
832
|
exports.MessageValidationError = MessageValidationError;
|
|
686
833
|
exports.MessagingSemanticConventions = MessagingSemanticConventions;
|
|
687
834
|
exports.TechnicalError = TechnicalError;
|
|
835
|
+
exports._getConnectionCountForTesting = _getConnectionCountForTesting;
|
|
836
|
+
exports._resetConnectionsForTesting = _resetConnectionsForTesting;
|
|
688
837
|
exports._resetTelemetryCacheForTesting = _resetTelemetryCacheForTesting;
|
|
689
838
|
exports.defaultTelemetryProvider = defaultTelemetryProvider;
|
|
690
839
|
exports.endSpanError = endSpanError;
|
|
691
840
|
exports.endSpanSuccess = endSpanSuccess;
|
|
692
841
|
exports.recordConsumeMetric = recordConsumeMetric;
|
|
842
|
+
exports.recordLateRpcReply = recordLateRpcReply;
|
|
693
843
|
exports.recordPublishMetric = recordPublishMetric;
|
|
694
844
|
exports.setupAmqpTopology = setupAmqpTopology;
|
|
695
845
|
exports.startConsumeSpan = startConsumeSpan;
|
package/dist/index.d.cts
CHANGED
|
@@ -31,17 +31,32 @@ declare class MessageValidationError extends Error {
|
|
|
31
31
|
}
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region src/amqp-client.d.ts
|
|
34
|
+
/**
|
|
35
|
+
* Default time `waitForConnect` will wait for the broker before erroring out.
|
|
36
|
+
* Defaulting to a finite value (rather than waiting forever) means a fail-fast
|
|
37
|
+
* developer experience: a misconfigured URL, a down broker, or wrong
|
|
38
|
+
* credentials surface as a Result.Error within 30 seconds. Pass `null`
|
|
39
|
+
* explicitly to disable the timeout — `Infinity` and other non-finite values
|
|
40
|
+
* are also coerced to "no timeout" because Node's `setTimeout` clamps large
|
|
41
|
+
* delays to ~24.8 days and silently fires near-immediately on `Infinity`.
|
|
42
|
+
*/
|
|
43
|
+
declare const DEFAULT_CONNECT_TIMEOUT_MS = 30000;
|
|
34
44
|
/**
|
|
35
45
|
* Options for creating an AMQP client.
|
|
36
46
|
*
|
|
37
47
|
* @property urls - AMQP broker URL(s). Multiple URLs provide failover support.
|
|
38
48
|
* @property connectionOptions - Optional connection configuration (heartbeat, reconnect settings, etc.).
|
|
39
49
|
* @property channelOptions - Optional channel configuration options.
|
|
50
|
+
* @property connectTimeoutMs - Maximum time in ms to wait for the channel to
|
|
51
|
+
* become ready in `waitForConnect`. Defaults to {@link DEFAULT_CONNECT_TIMEOUT_MS}.
|
|
52
|
+
* Pass `null` to disable the timeout entirely (amqp-connection-manager will
|
|
53
|
+
* retry indefinitely).
|
|
40
54
|
*/
|
|
41
55
|
type AmqpClientOptions = {
|
|
42
56
|
urls: ConnectionUrl[];
|
|
43
57
|
connectionOptions?: AmqpConnectionManagerOptions | undefined;
|
|
44
58
|
channelOptions?: Partial<CreateChannelOpts> | undefined;
|
|
59
|
+
connectTimeoutMs?: number | null | undefined;
|
|
45
60
|
};
|
|
46
61
|
/**
|
|
47
62
|
* Callback type for consuming messages.
|
|
@@ -93,6 +108,8 @@ declare class AmqpClient {
|
|
|
93
108
|
private readonly channelWrapper;
|
|
94
109
|
private readonly urls;
|
|
95
110
|
private readonly connectionOptions?;
|
|
111
|
+
/** Resolved timeout in ms; `null` means "wait forever". */
|
|
112
|
+
private readonly connectTimeoutMs;
|
|
96
113
|
/**
|
|
97
114
|
* Create a new AMQP client instance.
|
|
98
115
|
*
|
|
@@ -118,7 +135,19 @@ declare class AmqpClient {
|
|
|
118
135
|
/**
|
|
119
136
|
* Wait for the channel to be connected and ready.
|
|
120
137
|
*
|
|
121
|
-
*
|
|
138
|
+
* If `connectTimeoutMs` was provided in the constructor options, the returned
|
|
139
|
+
* Future resolves to `Result.Error<TechnicalError>` once the timeout elapses.
|
|
140
|
+
* Without a timeout, this waits forever — amqp-connection-manager retries
|
|
141
|
+
* connections indefinitely and never errors on its own.
|
|
142
|
+
*
|
|
143
|
+
* NOTE: When using `AmqpClient` directly (not via `TypedAmqpClient` /
|
|
144
|
+
* `TypedAmqpWorker`), the constructor has already incremented the pooled
|
|
145
|
+
* connection's reference count. Callers must invoke `close()` on the error
|
|
146
|
+
* path to release the connection — `waitForConnect` does not do this
|
|
147
|
+
* 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.
|
|
122
151
|
*/
|
|
123
152
|
waitForConnect(): Future<Result<void, TechnicalError>>;
|
|
124
153
|
/**
|
|
@@ -211,85 +240,20 @@ declare class AmqpClient {
|
|
|
211
240
|
//#endregion
|
|
212
241
|
//#region src/connection-manager.d.ts
|
|
213
242
|
/**
|
|
214
|
-
*
|
|
243
|
+
* Number of active pooled connections. Test-only helper — exposed in lieu of
|
|
244
|
+
* the underlying singleton, which is intentionally not part of the public API
|
|
245
|
+
* (mutating it from outside the library can break in-flight clients sharing a
|
|
246
|
+
* connection).
|
|
215
247
|
*
|
|
216
|
-
*
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
248
|
+
* @internal
|
|
249
|
+
*/
|
|
250
|
+
declare function _getConnectionCountForTesting(): number;
|
|
251
|
+
/**
|
|
252
|
+
* Close every pooled connection and clear ref-counts. Test-only helper.
|
|
220
253
|
*
|
|
221
|
-
* @
|
|
222
|
-
* ```typescript
|
|
223
|
-
* const manager = ConnectionManagerSingleton.getInstance();
|
|
224
|
-
* const connection = manager.getConnection(['amqp://localhost']);
|
|
225
|
-
* // ... use connection ...
|
|
226
|
-
* await manager.releaseConnection(['amqp://localhost']);
|
|
227
|
-
* ```
|
|
254
|
+
* @internal
|
|
228
255
|
*/
|
|
229
|
-
declare
|
|
230
|
-
private static instance;
|
|
231
|
-
private connections;
|
|
232
|
-
private refCounts;
|
|
233
|
-
private constructor();
|
|
234
|
-
/**
|
|
235
|
-
* Get the singleton instance of the connection manager.
|
|
236
|
-
*
|
|
237
|
-
* @returns The singleton instance
|
|
238
|
-
*/
|
|
239
|
-
static getInstance(): ConnectionManagerSingleton;
|
|
240
|
-
/**
|
|
241
|
-
* Get or create a connection for the given URLs and options.
|
|
242
|
-
*
|
|
243
|
-
* If a connection already exists with the same URLs and options, it is reused
|
|
244
|
-
* and its reference count is incremented. Otherwise, a new connection is created.
|
|
245
|
-
*
|
|
246
|
-
* @param urls - AMQP broker URL(s)
|
|
247
|
-
* @param connectionOptions - Optional connection configuration
|
|
248
|
-
* @returns The AMQP connection manager instance
|
|
249
|
-
*/
|
|
250
|
-
getConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): AmqpConnectionManager;
|
|
251
|
-
/**
|
|
252
|
-
* Release a connection reference.
|
|
253
|
-
*
|
|
254
|
-
* Decrements the reference count for the connection. If the count reaches zero,
|
|
255
|
-
* the connection is closed and removed from the pool.
|
|
256
|
-
*
|
|
257
|
-
* @param urls - AMQP broker URL(s) used to identify the connection
|
|
258
|
-
* @param connectionOptions - Optional connection configuration used to identify the connection
|
|
259
|
-
* @returns A promise that resolves when the connection is released (and closed if necessary)
|
|
260
|
-
*/
|
|
261
|
-
releaseConnection(urls: ConnectionUrl[], connectionOptions?: AmqpConnectionManagerOptions): Promise<void>;
|
|
262
|
-
/**
|
|
263
|
-
* Create a unique key for a connection based on URLs and options.
|
|
264
|
-
*
|
|
265
|
-
* The key is deterministic: same URLs and options always produce the same key,
|
|
266
|
-
* enabling connection reuse.
|
|
267
|
-
*
|
|
268
|
-
* @param urls - AMQP broker URL(s)
|
|
269
|
-
* @param connectionOptions - Optional connection configuration
|
|
270
|
-
* @returns A unique string key identifying the connection
|
|
271
|
-
*/
|
|
272
|
-
private createConnectionKey;
|
|
273
|
-
/**
|
|
274
|
-
* Serialize connection options to a deterministic string.
|
|
275
|
-
*
|
|
276
|
-
* @param options - Connection options to serialize
|
|
277
|
-
* @returns A JSON string with sorted keys for deterministic comparison
|
|
278
|
-
*/
|
|
279
|
-
private serializeOptions;
|
|
280
|
-
/**
|
|
281
|
-
* Deep sort an object's keys for deterministic serialization.
|
|
282
|
-
*
|
|
283
|
-
* @param value - The value to deep sort (can be object, array, or primitive)
|
|
284
|
-
* @returns The value with all object keys sorted alphabetically
|
|
285
|
-
*/
|
|
286
|
-
private deepSort;
|
|
287
|
-
/**
|
|
288
|
-
* Reset all cached connections (for testing purposes)
|
|
289
|
-
* @internal
|
|
290
|
-
*/
|
|
291
|
-
_resetForTesting(): Promise<void>;
|
|
292
|
-
}
|
|
256
|
+
declare function _resetConnectionsForTesting(): Promise<void>;
|
|
293
257
|
//#endregion
|
|
294
258
|
//#region src/logger.d.ts
|
|
295
259
|
/**
|
|
@@ -421,6 +385,12 @@ type TelemetryProvider = {
|
|
|
421
385
|
* Returns undefined if OpenTelemetry is not available.
|
|
422
386
|
*/
|
|
423
387
|
getConsumeLatencyHistogram: () => Histogram | undefined;
|
|
388
|
+
/**
|
|
389
|
+
* Get a counter for RPC replies that arrive after the caller has gone away
|
|
390
|
+
* (timeout, cancellation, or unknown correlationId). Returns undefined if
|
|
391
|
+
* OpenTelemetry is not available.
|
|
392
|
+
*/
|
|
393
|
+
getLateRpcReplyCounter: () => Counter | undefined;
|
|
424
394
|
};
|
|
425
395
|
/**
|
|
426
396
|
* Default telemetry provider that uses OpenTelemetry API if available.
|
|
@@ -452,6 +422,15 @@ declare function recordPublishMetric(provider: TelemetryProvider, exchangeName:
|
|
|
452
422
|
* Record a consume metric.
|
|
453
423
|
*/
|
|
454
424
|
declare function recordConsumeMetric(provider: TelemetryProvider, queueName: string, consumerName: string, success: boolean, durationMs: number): void;
|
|
425
|
+
/**
|
|
426
|
+
* Record an RPC reply that arrived after the caller stopped waiting.
|
|
427
|
+
*
|
|
428
|
+
* @param reason - Why the reply was orphaned. `"unknown-correlation-id"` is
|
|
429
|
+
* the typical "caller already timed out" case; `"missing-correlation-id"`
|
|
430
|
+
* means the broker delivered a reply with no correlationId at all (a
|
|
431
|
+
* protocol violation by the responder).
|
|
432
|
+
*/
|
|
433
|
+
declare function recordLateRpcReply(provider: TelemetryProvider, reason: "unknown-correlation-id" | "missing-correlation-id"): void;
|
|
455
434
|
/**
|
|
456
435
|
* Reset the cached OpenTelemetry API module and instruments.
|
|
457
436
|
* For testing purposes only.
|
|
@@ -459,5 +438,5 @@ declare function recordConsumeMetric(provider: TelemetryProvider, queueName: str
|
|
|
459
438
|
*/
|
|
460
439
|
declare function _resetTelemetryCacheForTesting(): void;
|
|
461
440
|
//#endregion
|
|
462
|
-
export { AmqpClient, type AmqpClientOptions,
|
|
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 };
|
|
463
442
|
//# 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":";;;;;;;;;;;;;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
|
|
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":"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"}
|