@message-queue-toolkit/amqp
Version:
AMQP adapter for message-queue-toolkit
110 lines • 4.56 kB
JavaScript
import { InternalError, copyWithoutUndefined } from '@lokalise/node-core';
import { objectToBuffer } from '@message-queue-toolkit/core';
import { AbstractAmqpService } from "./AbstractAmqpService.js";
export class AbstractAmqpPublisher extends AbstractAmqpService {
exchange;
messageSchemaContainer;
isLazyInitEnabled;
initPromise;
constructor(dependencies, options) {
super(dependencies, options);
this.messageSchemaContainer = this.resolvePublisherMessageSchemaContainer(options);
this.exchange = options.exchange;
this.isLazyInitEnabled = options.isLazyInitEnabled ?? true;
}
publish(message, options) {
const resolveSchemaResult = this.resolveSchema(message);
if (resolveSchemaResult.error) {
throw resolveSchemaResult.error;
}
resolveSchemaResult.result.parse(message);
const messageProcessingStartTimestamp = Date.now();
// If it's not initted yet, do the lazy init
if (!this.isInitted && this.isLazyInitEnabled) {
// avoid multiple concurrent inits
if (!this.initPromise) {
this.initPromise = this.init();
}
/**
* it is intentional that this promise is not awaited, that's how we keep the method invocation synchronous
* RabbitMQ publish by itself doesn't guarantee that your message is delivered successfully, so this kind of fire-and-forget is not strongly different from how amqp-lib behaves in the first place.
*/
this.initPromise
.then(() => {
this.publish(message, options);
})
.catch((err) => {
this.handleError(err);
});
return;
}
if (this.logMessages) {
// @ts-ignore
const resolvedLogMessage = this.resolveMessageLog(message, message[this.messageTypeField]);
this.logMessage(resolvedLogMessage);
}
// biome-ignore lint/style/noParameterAssign: it's ok
message = this.updateInternalProperties(message);
try {
this.publishInternal(objectToBuffer(message), options);
this.handleMessageProcessed({
message,
messageProcessingStartTimestamp,
// @ts-expect-error it will throw an error before if id field is not set
messageId: message[this.messageIdField],
processingResult: { status: 'published' },
queueName: this.resolveTopicOrQueue(),
});
}
catch (err) {
// Unfortunately, reliable retry mechanism can't be implemented with try-catch block,
// as not all failures end up here. If connection is closed programmatically, it works fine,
// but if server closes connection unexpectedly (e. g. RabbitMQ is shut down), then we don't land here
// @ts-ignore
if (err.message === 'Channel closed') {
this.logger.error('AMQP channel closed');
void this.reconnect();
}
else {
throw new InternalError({
message: `Error while publishing to AMQP ${err.message}`,
errorCode: 'AMQP_PUBLISH_ERROR',
details: copyWithoutUndefined({
publisher: this.constructor.name,
// @ts-ignore
queueName: this.queueName,
exchange: this.exchange,
// @ts-ignore
messageType: message[this.messageTypeField] ?? 'unknown',
}),
cause: err,
});
}
}
}
resolveSchema(message) {
return this.messageSchemaContainer.resolveSchema(message);
}
/* c8 ignore start */
resolveMessage() {
throw new Error('Not implemented for publisher');
}
/* c8 ignore start */
processPrehandlers() {
throw new Error('Not implemented for publisher');
}
preHandlerBarrier() {
throw new Error('Not implemented for publisher');
}
resolveNextFunction() {
throw new Error('Not implemented for publisher');
}
async close() {
this.initPromise = undefined;
await super.close();
}
processMessage() {
throw new Error('Not implemented for publisher');
}
}
//# sourceMappingURL=AbstractAmqpPublisher.js.map