UNPKG

@message-queue-toolkit/sns

Version:
167 lines 7.31 kB
import { PublishCommand } from '@aws-sdk/client-sns'; import { InternalError } from '@lokalise/node-core'; import { DeduplicationRequesterEnum, } from '@message-queue-toolkit/core'; import { resolveOutgoingMessageAttributes } from '@message-queue-toolkit/sqs'; import { calculateOutgoingMessageSize, validateFifoTopicName } from "../utils/snsUtils.js"; import { AbstractSnsService } from "./AbstractSnsService.js"; export class AbstractSnsPublisher extends AbstractSnsService { messageSchemaContainer; isDeduplicationEnabled; isFifoTopic; messageGroupIdField; defaultMessageGroupId; initPromise; constructor(dependencies, options) { super(dependencies, options); this.messageSchemaContainer = this.resolvePublisherMessageSchemaContainer(options); this.isDeduplicationEnabled = !!options.enablePublisherDeduplication; this.isFifoTopic = options.fifoTopic ?? false; this.messageGroupIdField = options.messageGroupIdField; this.defaultMessageGroupId = options.defaultMessageGroupId; } async init() { await super.init(); // Validate FIFO topic naming conventions if (this.isFifoTopic) { const topicName = this.locatorConfig?.topicName ?? this.creationConfig?.topic?.Name; if (topicName) { validateFifoTopicName(topicName, this.isFifoTopic); } } } async publish(message, options = {}) { const messageSchemaResult = this.resolveSchema(message); if (messageSchemaResult.error) { throw messageSchemaResult.error; } // If it's not initted yet, do the lazy init if (!this.isInitted) { // avoid multiple concurrent inits if (!this.initPromise) { this.initPromise = this.init(); } await this.initPromise; this.initPromise = undefined; } try { const messageProcessingStartTimestamp = Date.now(); const parsedMessage = messageSchemaResult.result.parse(message); const topicName = this.locatorConfig?.topicName ?? this.creationConfig?.topic?.Name ?? 'unknown'; if (this.logMessages) { // @ts-expect-error const resolvedLogMessage = this.resolveMessageLog(message, message[this.messageTypeField]); this.logMessage(resolvedLogMessage); } const updatedMessage = this.updateInternalProperties(message); // Resolve FIFO options from original message BEFORE offloading // (offloaded payload won't have user fields needed for messageGroupIdField) const resolvedOptions = this.resolveFifoOptions(updatedMessage, options); const maybeOffloadedPayloadMessage = await this.offloadMessagePayloadIfNeeded(updatedMessage, () => calculateOutgoingMessageSize(updatedMessage)); if (this.isDeduplicationEnabledForMessage(parsedMessage) && (await this.deduplicateMessage(parsedMessage, DeduplicationRequesterEnum.Publisher)) .isDuplicated) { this.handleMessageProcessed({ message: parsedMessage, processingResult: { status: 'published', skippedAsDuplicate: true }, messageProcessingStartTimestamp, queueName: topicName, }); return; } await this.sendMessage(maybeOffloadedPayloadMessage, resolvedOptions); this.handleMessageProcessed({ message: parsedMessage, processingResult: { status: 'published' }, messageProcessingStartTimestamp, queueName: topicName, }); } catch (error) { const err = error; this.handleError(err); throw new InternalError({ message: `Error while publishing to SNS: ${err.message}`, errorCode: 'SNS_PUBLISH_ERROR', details: { publisher: this.constructor.name, topic: this.topicArn, // @ts-expect-error messageType: message[this.messageTypeField] ?? 'unknown', }, cause: err, }); } } resolveMessage() { throw new Error('Not implemented for publisher'); } resolveSchema(message) { return this.messageSchemaContainer.resolveSchema(message); } /* c8 ignore start */ resolveNextFunction() { throw new Error('Not implemented for publisher'); } processPrehandlers() { throw new Error('Not implemented for publisher'); } preHandlerBarrier() { throw new Error('Not implemented for publisher'); } processMessage() { throw new Error('Not implemented for publisher'); } isDeduplicationEnabledForMessage(message) { return this.isDeduplicationEnabled && super.isDeduplicationEnabledForMessage(message); } async sendMessage(payload, options) { const attributes = resolveOutgoingMessageAttributes(payload); const command = new PublishCommand({ Message: JSON.stringify(payload), MessageAttributes: attributes, TopicArn: this.topicArn, ...options, }); await this.snsClient.send(command); } /** * Resolves FIFO-specific options (MessageGroupId) from the message payload. * This must be called BEFORE payload offloading, as the offloaded payload * won't contain the user fields needed for messageGroupIdField resolution. * * @param payload - The original (non-offloaded) message payload * @param options - The SNS message options to augment with FIFO settings * @returns The options with resolved MessageGroupId for FIFO topics */ resolveFifoOptions(payload, options) { if (!this.isFifoTopic) { return options; } const resolvedOptions = { ...options }; // Resolve MessageGroupId if not provided if (!resolvedOptions.MessageGroupId) { if (this.messageGroupIdField) { const messageGroupId = payload[this.messageGroupIdField]; if (typeof messageGroupId === 'string') { resolvedOptions.MessageGroupId = messageGroupId; } } // Fallback to default if still not set if (!resolvedOptions.MessageGroupId && this.defaultMessageGroupId) { resolvedOptions.MessageGroupId = this.defaultMessageGroupId; } } // Validate MessageGroupId is present for FIFO topics if (!resolvedOptions.MessageGroupId) { throw new InternalError({ message: 'MessageGroupId is required for FIFO topics. Provide it in publish options, configure messageGroupIdField, or set defaultMessageGroupId.', errorCode: 'FIFO_MESSAGE_GROUP_ID_REQUIRED', details: { topicArn: this.topicArn, }, }); } return resolvedOptions; } } //# sourceMappingURL=AbstractSnsPublisher.js.map