UNPKG

@message-queue-toolkit/amqp

Version:
110 lines 4.56 kB
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