UNPKG

@message-queue-toolkit/core

Version:

Useful utilities, interfaces and base classes for message queue handling. Supports AMQP and SQS with a common abstraction on top currently

176 lines (175 loc) 12 kB
import { type CommonLogger, type Either, type ErrorReporter, type ErrorResolver } from '@lokalise/node-core'; import type { MakeRequired } from '@lokalise/universal-ts-utils/node'; import type { ZodSchema, ZodType } from 'zod/v4'; import type { MessageInvalidFormatError, MessageValidationError } from '../errors/Errors.ts'; import { type AcquireLockTimeoutError } from '../message-deduplication/AcquireLockTimeoutError.ts'; import { type DeduplicationRequester, type MessageDeduplicationConfig, type ReleasableLock } from '../message-deduplication/messageDeduplicationTypes.ts'; import { type OffloadedPayloadPointerPayload } from '../payload-store/offloadedPayloadMessageSchemas.ts'; import type { MultiPayloadStoreConfig, SinglePayloadStoreConfig } from '../payload-store/payloadStoreTypes.ts'; import type { MessageProcessingResult } from '../types/MessageQueueTypes.ts'; import type { DeletionConfig, MessageMetricsManager, ProcessedMessageMetadata, QueueDependencies, QueueOptions } from '../types/queueOptionsTypes.ts'; import type { BarrierCallback, BarrierResult, MessageHandlerConfig, PreHandlingOutputs, Prehandler, PrehandlerResult } from './HandlerContainer.ts'; import type { HandlerSpy, PublicHandlerSpy } from './HandlerSpy.ts'; import { MessageSchemaContainer } from './MessageSchemaContainer.ts'; import { type MessageTypeResolverConfig } from './MessageTypeResolver.ts'; export type Deserializer<MessagePayloadType extends object> = (message: unknown, type: ZodType<MessagePayloadType>, errorProcessor: ErrorResolver) => Either<MessageInvalidFormatError | MessageValidationError, MessagePayloadType>; type CommonQueueLocator = { queueName: string; }; export type ResolvedMessage = { body: unknown; attributes?: Record<string, unknown>; }; export declare abstract class AbstractQueueService<MessagePayloadSchemas extends object, MessageEnvelopeType extends object, DependenciesType extends QueueDependencies, QueueConfiguration extends object, QueueLocatorType extends object = CommonQueueLocator, OptionsType extends QueueOptions<QueueConfiguration, QueueLocatorType> = QueueOptions<QueueConfiguration, QueueLocatorType>, ExecutionContext = undefined, PrehandlerOutput = undefined> { /** * Used to keep track of the number of `retryLater` results received for a message to be able to * calculate the delay for the next retry */ private readonly messageRetryLaterCountField; /** * Used to know when the message was sent initially so we can have a max retry date and avoid * a infinite `retryLater` loop */ protected readonly messageTimestampField: string; /** * Used to know the message deduplication id */ protected readonly messageDeduplicationIdField: string; /** * Used to know the store-based message deduplication options */ protected readonly messageDeduplicationOptionsField: string; /** * Used to know where metadata is stored - for debug logging purposes only */ protected readonly messageMetadataField: string; protected readonly errorReporter: ErrorReporter; readonly logger: CommonLogger; protected readonly messageIdField: string; /** * Configuration for resolving message types. */ protected readonly messageTypeResolver?: MessageTypeResolverConfig; protected readonly logMessages: boolean; protected readonly creationConfig?: QueueConfiguration; protected readonly locatorConfig?: QueueLocatorType; protected readonly deletionConfig?: DeletionConfig; protected readonly payloadStoreConfig?: MakeRequired<SinglePayloadStoreConfig, 'serializer'> | MakeRequired<MultiPayloadStoreConfig, 'serializer'>; protected readonly messageDeduplicationConfig?: MessageDeduplicationConfig; protected readonly messageMetricsManager?: MessageMetricsManager<MessagePayloadSchemas>; protected readonly _handlerSpy?: HandlerSpy<MessagePayloadSchemas>; protected isInitted: boolean; get handlerSpy(): PublicHandlerSpy<MessagePayloadSchemas>; constructor({ errorReporter, logger, messageMetricsManager }: DependenciesType, options: OptionsType); protected resolveConsumerMessageSchemaContainer(options: { handlers: MessageHandlerConfig<MessagePayloadSchemas, ExecutionContext, PrehandlerOutput>[]; messageTypeResolver?: MessageTypeResolverConfig; }): MessageSchemaContainer<MessagePayloadSchemas>; protected resolvePublisherMessageSchemaContainer(options: { messageSchemas: readonly ZodSchema<MessagePayloadSchemas>[]; messageTypeResolver?: MessageTypeResolverConfig; }): MessageSchemaContainer<MessagePayloadSchemas>; /** * Resolves message type from message data and optional attributes using messageTypeResolver. * * @param messageData - The parsed message data * @param messageAttributes - Optional message-level attributes (e.g., PubSub attributes) * @returns The resolved message type, or undefined if not configured */ protected resolveMessageTypeFromMessage(messageData: unknown, messageAttributes?: Record<string, unknown>): string | undefined; protected abstract resolveSchema(message: MessagePayloadSchemas): Either<Error, ZodSchema<MessagePayloadSchemas>>; protected abstract resolveMessage(message: MessageEnvelopeType): Either<MessageInvalidFormatError | MessageValidationError, ResolvedMessage>; /** * Format message for logging */ protected resolveMessageLog(_processedMessageMetadata: ProcessedMessageMetadata<MessagePayloadSchemas>): unknown | null; protected logMessageProcessed(processedMessageMetadata: ProcessedMessageMetadata<MessagePayloadSchemas>): void; protected handleError(err: unknown, context?: Record<string, unknown>): void; protected handleMessageProcessed(params: { message: MessagePayloadSchemas | null; processingResult: MessageProcessingResult; messageProcessingStartTimestamp: number; queueName: string; messageId?: string; }): void; private resolveProcessedMessageMetadata; protected processPrehandlersInternal(preHandlers: Prehandler<MessagePayloadSchemas, ExecutionContext, PrehandlerOutput>[], message: MessagePayloadSchemas): Promise<PrehandlerOutput>; protected preHandlerBarrierInternal<BarrierOutput>(barrier: BarrierCallback<MessagePayloadSchemas, ExecutionContext, PrehandlerOutput, BarrierOutput> | undefined, message: MessagePayloadSchemas, executionContext: ExecutionContext, preHandlerOutput: PrehandlerOutput): Promise<BarrierResult<BarrierOutput>>; shouldBeRetried(message: MessagePayloadSchemas, maxRetryDuration: number): boolean; protected getMessageRetryDelayInSeconds(message: MessagePayloadSchemas): number; protected updateInternalProperties(message: MessagePayloadSchemas): MessagePayloadSchemas; private tryToExtractTimestamp; private tryToExtractNumberOfRetries; protected abstract resolveNextFunction(preHandlers: Prehandler<MessagePayloadSchemas, ExecutionContext, PrehandlerOutput>[], message: MessagePayloadSchemas, index: number, preHandlerOutput: PrehandlerOutput, resolve: (value: PrehandlerOutput | PromiseLike<PrehandlerOutput>) => void, reject: (err: Error) => void): (preHandlerResult: PrehandlerResult) => void; protected resolveNextPreHandlerFunctionInternal(preHandlers: Prehandler<MessagePayloadSchemas, ExecutionContext, PrehandlerOutput>[], executionContext: ExecutionContext, message: MessagePayloadSchemas, index: number, preHandlerOutput: PrehandlerOutput, resolve: (value: PrehandlerOutput | PromiseLike<PrehandlerOutput>) => void, reject: (err: Error) => void): (preHandlerResult: PrehandlerResult) => void; protected abstract processPrehandlers(message: MessagePayloadSchemas, messageType: string): Promise<PrehandlerOutput>; protected abstract preHandlerBarrier<BarrierOutput>(message: MessagePayloadSchemas, messageType: string, preHandlerOutput: PrehandlerOutput): Promise<BarrierResult<BarrierOutput>>; protected abstract processMessage(message: MessagePayloadSchemas, messageType: string, preHandlingOutputs: PreHandlingOutputs<PrehandlerOutput, any>): Promise<Either<'retryLater', 'success'>>; abstract close(): Promise<unknown>; /** * Resolves the store and store name for outgoing (publishing) messages. * For multi-store: uses outgoingStore from config. * For single-store: uses the configured store and storeName. * @throws Error if payloadStoreConfig is not configured or the named store is not found. */ private resolveOutgoingStore; /** * Resolves store from payloadRef (new format). */ private resolveStoreFromPayloadRef; /** * Resolves store from legacy pointer (old format). */ private resolveStoreFromLegacyPointer; /** * Resolves the store for incoming (consuming) messages based on payload reference. * For multi-store with payloadRef: uses the store specified in payloadRef. * For multi-store with legacy format: uses defaultIncomingStore. * For single-store: always uses the configured store. */ private resolveIncomingStore; /** * Offload message payload to an external store if it exceeds the threshold. * Returns a special type that contains a pointer to the offloaded payload or the original payload if it was not offloaded. * Requires message size as only the implementation knows how to calculate it. * * For multi-store configuration, uses the configured outgoingStore. * For single-store configuration, uses the single store. * * The returned payload includes both the new payloadRef format and legacy fields for backward compatibility. */ protected offloadMessagePayloadIfNeeded(message: MessagePayloadSchemas, messageSizeFn: () => number): Promise<MessagePayloadSchemas | OffloadedPayloadPointerPayload>; /** * Retrieve previously offloaded message payload using provided pointer payload. * Returns the original payload or an error if the payload was not found or could not be parsed. * * Supports both new multi-store format (payloadRef) and legacy format (offloadedPayloadPointer). */ protected retrieveOffloadedMessagePayload(maybeOffloadedPayloadPointerPayload: unknown): Promise<Either<Error, unknown>>; /** * Checks if the message is duplicated against the deduplication store. * Returns true if the message is duplicated. * Returns false if message is not duplicated or deduplication config is missing. */ protected isMessageDuplicated(message: MessagePayloadSchemas, requester: DeduplicationRequester): Promise<boolean>; /** * Checks if the message is duplicated. * If it's not, stores the deduplication key in the deduplication store and returns false. * If it is, returns true. * If deduplication config is not provided, always returns false to allow further processing of the message. */ protected deduplicateMessage(message: MessagePayloadSchemas, requester: DeduplicationRequester): Promise<{ isDuplicated: boolean; }>; /** * Acquires exclusive lock for the message to prevent concurrent processing. * If lock was acquired successfully, returns a lock object that should be released after processing. * If lock couldn't be acquired due to timeout (meaning another process acquired it earlier), returns AcquireLockTimeoutError * If lock couldn't be acquired for any other reasons or if deduplication config is not provided, always returns a lock object that does nothing, so message processing can continue. */ protected acquireLockForMessage(message: MessagePayloadSchemas): Promise<Either<AcquireLockTimeoutError, ReleasableLock>>; protected isDeduplicationEnabledForMessage(message: MessagePayloadSchemas): boolean; protected getMessageDeduplicationId(message: MessagePayloadSchemas): string | undefined; private getParsedMessageDeduplicationOptions; } export {};