@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
TypeScript
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 {};