@cloudamqp/amqp-client
Version:
AMQP 0-9-1 client, both for browsers (WebSocket) and node (TCP Socket)
207 lines • 8.11 kB
JavaScript
import { AMQPSubscription, AMQPGeneratorSubscription } from "./amqp-subscription.js";
import { serializeAndEncode, decodeMessage } from "./amqp-codec-registry.js";
export class AMQPQueue {
constructor(session, name) {
this.subscriptions = new Set();
this.session = session;
this.name = name;
}
async publish(body, options = {}) {
const { confirm = true, mandatory = false, ...properties } = options;
const defaults = {};
if (this.session.defaultContentType)
defaults.contentType = this.session.defaultContentType;
if (this.session.defaultContentEncoding)
defaults.contentEncoding = this.session.defaultContentEncoding;
const encoded = await serializeAndEncode(this.session.parsers ?? {}, this.session.coders ?? {}, body, properties, defaults);
if (encoded.properties.deliveryMode === undefined)
encoded.properties.deliveryMode = 2;
if (confirm) {
const ch = await this.session.getConfirmChannel();
await ch.basicPublish("", this.name, encoded.body, encoded.properties, mandatory);
}
else {
await this.session.withOpsChannel(async (ch) => {
await ch.basicPublish("", this.name, encoded.body, encoded.properties, mandatory);
});
}
return this;
}
async subscribe(params, callback) {
if (typeof params === "function")
[callback, params] = [params, undefined];
const { prefetch, requeueOnNack = true, ...consumeParams } = params ?? {};
const autoAck = !consumeParams.noAck;
if (autoAck)
consumeParams.noAck = false;
const parsers = this.session.parsers;
const coders = this.session.coders;
const logger = this.session.logger;
const wrappedCallback = wrapCallbackWithAutoDecodeAndAck(callback, {
parsers,
coders,
autoAck,
requeueOnNack,
logger,
});
const def = {
queueName: this.name,
consumeParams,
requeueOnNack,
...(wrappedCallback !== undefined && { callback: wrappedCallback }),
...(prefetch !== undefined && { prefetch }),
...(parsers && { parsers }),
...(coders && { coders }),
};
const consumer = await this.openConsumer(def);
const sub = wrappedCallback ? new AMQPSubscription(consumer, def) : new AMQPGeneratorSubscription(consumer, def);
this.subscriptions.add(sub);
sub.onCancel = () => {
this.subscriptions.delete(sub);
};
return sub;
}
async get(params) {
const msg = await this.session.withOpsChannel((ch) => ch.basicGet(this.name, params));
if (!msg)
return null;
if (this.session.parsers || this.session.coders) {
await decodeMessage(msg, this.session.parsers ?? {}, this.session.coders ?? {});
}
return msg;
}
async consumeOne(options = {}) {
const { timeout } = options;
const ch = await this.session.openChannel();
await ch.basicQos(1);
return new Promise((resolve, reject) => {
let timer;
let settled = false;
let consumer;
const cleanup = async () => {
if (timer)
clearTimeout(timer);
try {
if (consumer && !this.session.closed)
await consumer.cancel();
}
catch {
}
if (!ch.closed)
await ch.close().catch(() => { });
};
const finish = async (settle) => {
if (settled)
return;
settled = true;
await cleanup();
settle();
};
ch.basicConsume(this.name, { noAck: false }, async (msg) => {
try {
if (settled) {
await msg.nack(true).catch(() => { });
return;
}
if (this.session.parsers || this.session.coders) {
await decodeMessage(msg, this.session.parsers ?? {}, this.session.coders ?? {});
}
await msg.ack();
await finish(() => resolve(msg));
}
catch (err) {
await finish(() => reject(err));
}
})
.then((c) => {
consumer = c;
if (settled) {
void cleanup();
return;
}
c.wait()
.then(() => void finish(() => reject(new Error("consumeOne: consumer closed before delivery"))))
.catch((err) => void finish(() => reject(err)));
if (timeout !== undefined) {
timer = setTimeout(() => void finish(() => reject(new Error(`consumeOne timed out after ${timeout}ms`))), timeout);
}
})
.catch((err) => void finish(() => reject(err)));
});
}
async bind(exchange, routingKey = "", args = {}) {
const exchangeName = typeof exchange === "string" ? exchange : exchange.name;
await this.session.withOpsChannel((ch) => ch.queueBind(this.name, exchangeName, routingKey, args));
return this;
}
async unbind(exchange, routingKey = "", args = {}) {
const exchangeName = typeof exchange === "string" ? exchange : exchange.name;
await this.session.withOpsChannel((ch) => ch.queueUnbind(this.name, exchangeName, routingKey, args));
return this;
}
async purge() {
return this.session.withOpsChannel((ch) => ch.queuePurge(this.name));
}
async delete(params) {
const result = await this.session.withOpsChannel((ch) => ch.queueDelete(this.name, params));
this.cancelAll();
this.session.removeQueue(this.name);
return result;
}
async recover() {
for (const sub of this.subscriptions) {
try {
const consumer = await this.openConsumer(sub.def);
sub.setConsumer(consumer);
this.session.logger?.debug(`Recovered consumer for queue: ${this.name}`);
}
catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
this.session.logger?.warn(`Failed to recover consumer for queue ${this.name}:`, error.message);
}
}
}
cancelAll() {
for (const sub of this.subscriptions) {
sub.cancel().catch(() => { });
}
this.subscriptions.clear();
}
async openConsumer(def) {
const ch = await this.session.openChannel();
if (def.prefetch !== undefined) {
await ch.basicQos(def.prefetch);
}
return def.callback
? ch.basicConsume(def.queueName, def.consumeParams, def.callback)
: ch.basicConsume(def.queueName, def.consumeParams);
}
}
function wrapCallbackWithAutoDecodeAndAck(callback, opts) {
if (!callback)
return undefined;
if (!opts.autoAck) {
return async (msg) => {
try {
if (opts.parsers || opts.coders)
await decodeMessage(msg, opts.parsers ?? {}, opts.coders ?? {});
await callback(msg);
}
catch (err) {
opts.logger?.error("Consumer callback error", err);
}
};
}
return async (msg) => {
try {
if (opts.parsers || opts.coders)
await decodeMessage(msg, opts.parsers ?? {}, opts.coders ?? {});
await callback(msg);
await msg.ack();
}
catch {
await msg.nack(opts.requeueOnNack);
}
};
}
//# sourceMappingURL=amqp-queue.js.map