UNPKG

pg-transactional-outbox

Version:

A PostgreSQL based transactional outbox and inbox pattern implementation to support exactly once message processing (with at least once message delivery).

229 lines (215 loc) 7.61 kB
import { BooleanSetting, Env, NumberSetting, StringSetting, getConfigSettings, getConfigSettingsEnvTemplate, getEnvVariableNumber, getEnvVariableString, } from '../common/env-settings'; import { ListenerConfig, ListenerSettings, applyDefaultListenerConfigValues, fallbackEnvPrefix, getInboxListenerEnvTemplate, getInboxListenerSettings, getOutboxListenerEnvTemplate, getOutboxListenerSettings, inboxEnvPrefix, outboxEnvPrefix, } from '../common/listener-config'; export type FullPollingListenerConfig = Required<PollingListenerConfig> & { settings: FullPollingListenerSettings; }; export type FullPollingListenerSettings = Required<PollingListenerSettings>; export interface PollingListenerConfig extends ListenerConfig { /** Polling listener specific settings */ settings: PollingListenerSettings; } export interface PollingListenerSettings extends ListenerSettings { /** * The name of the schema of the Postgres function to get the next batch of * messages. It defaults to the `dbSchema` if it is not provided. */ nextMessagesFunctionSchema?: string; /** * The name of the Postgres function to get the next batch of outbox or inbox * messages. */ nextMessagesFunctionName: string; /** The batch size for messages to load simultaneously. Default is 5. */ nextMessagesBatchSize?: number; /** How long should a message be locked for exclusive processing and error handling (in milliseconds). Default is 5 seconds. */ nextMessagesLockInMs?: number; /** Next polling interval. Default is 500ms. */ nextMessagesPollingIntervalInMs?: number; } const defaultSettings: Required< Omit< PollingListenerSettings, keyof ListenerSettings | 'nextMessagesFunctionName' > > = { nextMessagesFunctionSchema: 'public', nextMessagesBatchSize: 5, nextMessagesLockInMs: 5000, nextMessagesPollingIntervalInMs: 500, }; export const applyDefaultPollingListenerConfigValues = ( config: PollingListenerConfig, ): FullPollingListenerConfig => { const listenerConfig = applyDefaultListenerConfigValues(config); const filledConfig: FullPollingListenerConfig = { ...listenerConfig, ...config, settings: { ...listenerConfig.settings, ...defaultSettings, ...config.settings, }, }; return filledConfig; }; const basicSettingsMap: (StringSetting | NumberSetting | BooleanSetting)[] = [ { constantName: 'NEXT_MESSAGES_FUNCTION_SCHEMA', default: defaultSettings.nextMessagesFunctionSchema, func: getEnvVariableString, description: 'The database schema of the next messages function.', }, { constantName: 'NEXT_MESSAGES_BATCH_SIZE', default: defaultSettings.nextMessagesBatchSize, func: getEnvVariableNumber, description: 'The (maximum) amount of messages to retrieve in one query.', }, { constantName: 'NEXT_MESSAGES_LOCK_IN_MS', default: defaultSettings.nextMessagesLockInMs, func: getEnvVariableNumber, description: 'How long the retrieved messages should be locked before they can be retrieved again.', }, { constantName: 'NEXT_MESSAGES_POLLING_INTERVAL_IN_MS', default: defaultSettings.nextMessagesPollingIntervalInMs, func: getEnvVariableNumber, description: 'How often should the next messages function be executed.', }, ]; const outboxSettingsMap: (StringSetting | NumberSetting | BooleanSetting)[] = [ { constantName: 'NEXT_MESSAGES_FUNCTION_NAME', default: 'next_outbox_messages', func: getEnvVariableString, skipFallback: true, description: 'The database function name to get the next batch of outbox messages.', }, ]; const inboxSettingsMap: (StringSetting | NumberSetting | BooleanSetting)[] = [ { constantName: 'NEXT_MESSAGES_FUNCTION_NAME', default: 'next_inbox_messages', func: getEnvVariableString, skipFallback: true, description: 'The database function name to get the next batch of inbox messages.', }, ]; /** * Loads the environment variables into the polling listener settings object. It * supports reading an inbox specific setting or a general one. * Please use the `getInboxPollingListenerEnvTemplate` functions to get a * list of all the inbox relevant settings for the polling listener. * @example * TRX_DB_SCHEMA=trx_schema * TRX_INBOX_DB_TABLE=inbox_table * TRX_INBOX_NEXT_MESSAGES_FUNCTION_SCHEMA=next_inbox_messages * @param env The process.env variable or a custom object. * @returns The polling listener settings object with filled with the ENV variables */ export const getInboxPollingListenerSettings = ( env: Env = process.env, ): PollingListenerSettings => { const inboxSettings = getInboxListenerSettings(env); const pollSettings = getConfigSettings( [...basicSettingsMap, ...inboxSettingsMap], inboxEnvPrefix, fallbackEnvPrefix, env, ); return { ...inboxSettings, ...pollSettings, } as unknown as PollingListenerSettings; }; /** * Loads the environment variables into the polling listener settings object. It * supports reading an outbox specific setting or a general one. * Please use the `getOutboxPollingListenerEnvTemplate` functions to get a * list of all the outbox relevant settings for the polling listener. * @example * TRX_DB_SCHEMA=trx_schema * TRX_OUTBOX_DB_TABLE=outbox_table * TRX_OUTBOX_NEXT_MESSAGES_FUNCTION_SCHEMA=next_outbox_messages * @param env The process.env variable or a custom object. * @returns The polling listener settings object with filled with the ENV variables */ export const getOutboxPollingListenerSettings = ( env: Env = process.env, ): PollingListenerSettings => { const outboxSettings = getOutboxListenerSettings(env); const pollSettings = getConfigSettings( [...basicSettingsMap, ...outboxSettingsMap], outboxEnvPrefix, fallbackEnvPrefix, env, ); return { ...outboxSettings, ...pollSettings, } as unknown as PollingListenerSettings; }; /** * Shows the available env variables and their default values for the inbox * listener with the polling approach. * @param map A mapping of all the env variables to config settings. * @param envPrefix The prefix for the env variables to check first (e.g. "TRX_INBOX_" or "TRX_"). * @param envPrefixFallback The fallback prefix if the other is not found. Useful for defining settings that should be used for both outbox and inbox. * @returns */ export const getInboxPollingListenerEnvTemplate = ( defaultOverrides?: Record<string, string>, ): string => { const il = getInboxListenerEnvTemplate(defaultOverrides); const cfg = getConfigSettingsEnvTemplate( [...basicSettingsMap, ...inboxSettingsMap], inboxEnvPrefix, fallbackEnvPrefix, defaultOverrides, ); return `${il}${cfg}`; }; /** * Shows the available env variables and their default values for the outbox * listener with the polling approach. * @param map A mapping of all the env variables to config settings. * @param envPrefix The prefix for the env variables to check first (e.g. "TRX_OUTBOX_" or "TRX_"). * @param envPrefixFallback The fallback prefix if the other is not found. Useful for defining settings that should be used for both outbox and inbox. * @returns */ export const getOutboxPollingListenerEnvTemplate = ( defaultOverrides?: Record<string, string>, ): string => { const ol = getOutboxListenerEnvTemplate(defaultOverrides); const cfg = getConfigSettingsEnvTemplate( [...basicSettingsMap, ...outboxSettingsMap], outboxEnvPrefix, fallbackEnvPrefix, defaultOverrides, ); return `${ol}${cfg}`; };