UNPKG

@imqueue/pg-pubsub

Version:

Reliable PostgreSQL LISTEN/NOTIFY with inter-process lock support

437 lines (436 loc) 13.2 kB
/*! * I'm Queue Software Project * Copyright (C) 2025 imqueue.com <support@imqueue.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * * If you want to use this code in a closed source (commercial) project, you can * purchase a proprietary commercial license. Please contact us at * <support@imqueue.com> to get commercial licensing options. */ import { EventEmitter } from 'events'; import { AnyJson, AnyLogger, close, connect, end, error, listen, message, notify, PgClient, PgPubSubOptions, reconnect, unlisten } from '.'; import { PgChannelEmitter } from './PgChannelEmitter'; export declare interface PgPubSub { /** * Sets `'end'` event handler * * @param {'end'} event * @param {typeof end} listener * @return {PgPubSub} */ on(event: 'end', listener: typeof end): this; /** * Sets `'connect'` event handler * * @param {'connect'} event * @param {typeof connect} listener * @return {PgPubSub} */ on(event: 'connect', listener: typeof connect): this; /** * Sets `'close'` event handler * * @param {'close'} event * @param {typeof close} listener * @return {PgPubSub} */ on(event: 'close', listener: typeof close): this; /** * Sets `'listen'` event handler * * @param {'listen'} event * @param {typeof listen} listener * @return {PgPubSub} */ on(event: 'listen', listener: typeof listen): this; /** * Sets `'unlisten'` event handler * * @param {'unlisten'} event * @param {typeof unlisten} listener * @return {PgPubSub} */ on(event: 'unlisten', listener: typeof unlisten): this; /** * Sets `'error'` event handler * * @param {'error'} event * @param {typeof error} listener * @return {PgPubSub} */ on(event: 'error', listener: typeof error): this; /** * Sets `'reconnect'` event handler * * @param {'reconnect'} event * @param {typeof reconnect} listener * @return {PgPubSub} */ on(event: 'reconnect', listener: typeof reconnect): this; /** * Sets `'message'` event handler * * @param {'message'} event * @param {typeof message} listener * @return {PgPubSub} */ on(event: 'message', listener: typeof message): this; /** * Sets `'notify'` event handler * * @param {'notify'} event * @param {typeof notify} listener * @return {PgPubSub} */ on(event: 'notify', listener: typeof notify): this; /** * Sets any unknown or user-defined event handler * * @param {string | symbol} event - event name * @param {(...args: any[]) => void} listener - event handler */ on(event: string | symbol, listener: (...args: any[]) => void): this; /** * Sets `'end'` event handler, which fired only one single time * * @param {'end'} event * @param {typeof end} listener * @return {PgPubSub} */ once(event: 'end', listener: typeof end): this; /** * Sets `'connect'` event handler, which fired only one single time * * @param {'connect'} event * @param {typeof connect} listener * @return {PgPubSub} */ once(event: 'connect', listener: typeof connect): this; /** * Sets `'close'` event handler, which fired only one single time * * @param {'close'} event * @param {typeof close} listener * @return {PgPubSub} */ once(event: 'close', listener: typeof close): this; /** * Sets `'listen'` event handler, which fired only one single time * * @param {'listen'} event * @param {typeof listen} listener * @return {PgPubSub} */ once(event: 'listen', listener: typeof listen): this; /** * Sets `'unlisten'` event handler, which fired only one single time * * @param {'unlisten'} event * @param {typeof unlisten} listener * @return {PgPubSub} */ once(event: 'unlisten', listener: typeof unlisten): this; /** * Sets `'error'` event handler, which fired only one single time * * @param {'error'} event * @param {typeof error} listener * @return {PgPubSub} */ once(event: 'error', listener: typeof error): this; /** * Sets `'reconnect'` event handler, which fired only one single time * * @param {'reconnect'} event * @param {typeof reconnect} listener * @return {PgPubSub} */ once(event: 'reconnect', listener: typeof reconnect): this; /** * Sets `'message'` event handler, which fired only one single time * * @param {'message'} event * @param {typeof message} listener * @return {PgPubSub} */ once(event: 'message', listener: typeof message): this; /** * Sets `'notify'` event handler, which fired only one single time * * @param {'notify'} event * @param {typeof notify} listener * @return {PgPubSub} */ once(event: 'notify', listener: typeof notify): this; /** * Sets any unknown or user-defined event handler, which would fire only * one single time * * @param {string | symbol} event - event name * @param {(...args: any[]) => void} listener - event handler */ once(event: string | symbol, listener: (...args: any[]) => void): this; } /** * Implements LISTEN/NOTIFY client for PostgreSQL connections. * * It is a basic public interface of this library, so the end-user is going * to work with this class directly to solve his/her tasks. * * Importing: * ~~~typescript * import { AnyJson, PgPubSub } from '@imqueue/pg-pubsub'; * ~~~ * * Instantiation: * ~~~typescript * const pubSub = new PgPubSub(options) * ~~~ * @see PgPubSubOptions * * Connecting and listening: * ~~~typescript * pubSub.on('connect', async () => { * await pubSub.listen('ChannelOne'); * await pubSub.listen('ChannelTwo'); * }); * // or, even better: * pubSub.on('connect', async () => { * await Promise.all( * ['ChannelOne', 'ChannelTwo'].map(channel => channel.listen()), * ); * }); * // or. less reliable: * await pubSub.connect(); * await Promise.all( * ['ChannelOne', 'ChannelTwo'].map(channel => channel.listen()), * ); * ~~~ * * Handle messages: * ~~~typescript * pubSub.on('message', (channel: string, payload: AnyJson) => * console.log(channel, payload); * ); * // or, using channels * pubSub.channels.on('ChannelOne', (payload: AnyJson) => * console.log(1, payload), * ); * pubSub.channels.on('ChannelTwo', (payload: AnyJson) => * console.log(2, payload), * ); * ~~~ * * Destroying: * ~~~typescript * await pubSub.destroy(); * ~~~ * * Closing and re-using connection: * ~~~typescript * await pubSub.close(); * await pubSub.connect(); * ~~~ * * This close/connect technique may be used when doing some heavy message * handling, so while you close, another running copy may handle next * messages... */ export declare class PgPubSub extends EventEmitter { readonly logger: AnyLogger; readonly pgClient: PgClient; readonly options: PgPubSubOptions; readonly channels: PgChannelEmitter; private locks; private retry; private processId; /** * @constructor * @param {PgPubSubOptions} options - options * @param {AnyLogger} logger - logger */ constructor(options: Partial<PgPubSubOptions>, logger?: AnyLogger); /** * Establishes re-connectable database connection * * @return {Promise<void>} */ connect(): Promise<void>; /** * Safely closes this database connection * * @return {Promise<void>} */ close(): Promise<void>; /** * Starts listening given channel. If singleListener option is set to * true, it guarantees that only one process would be able to listen * this channel at a time. * * @param {string} channel - channel name to listen * @return {Promise<void>} */ listen(channel: string): Promise<void>; /** * Stops listening of the given channel, and, if singleListener option is * set to true - will release an acquired lock (if it was settled). * * @param {string} channel - channel name to unlisten * @return {Promise<void>} */ unlisten(channel: string): Promise<void>; /** * Stops listening all connected channels, and, if singleListener option * is set to true - will release all acquired locks (if any was settled). * * @return {Promise<void>} */ unlistenAll(): Promise<void>; /** * Performs NOTIFY to a given channel with a given payload to all * listening subscribers * * @param {string} channel - channel to publish to * @param {AnyJson} payload - payload to publish for subscribers * @return {Promise<void>} */ notify(channel: string, payload: AnyJson): Promise<void>; /** * Returns list of all active subscribed channels * * @return {string[]} */ activeChannels(): string[]; /** * Returns list of all inactive channels (those which are known, but * not actively listening at a time) * * @return {string[]} */ inactiveChannels(): string[]; /** * Returns list of all known channels, despite the fact they are listening * (active) or not (inactive). * * @return {string[]} */ allChannels(): string[]; /** * If channel argument passed will return true if channel is in active * state (listening by this pub/sub), false - otherwise. If channel is * not specified - will return true if there is at least one active channel * listened by this pub/sub, false - otherwise. * * @param {string} channel * @return {boolean} */ isActive(channel?: string): boolean; /** * Destroys this object properly, destroying all locks, * closing all connections and removing all event listeners to avoid * memory leaking. So whenever you need to destroy an object * programmatically - use this method. * Note, that after destroy it is broken and should be removed from memory. * * @return {Promise<void>} */ destroy(): Promise<void>; /** * Safely sets given handler for given pg client events, making sure * we won't flood events with non-fired same stack of handlers * * @access private * @param {string[]} events - list of events to set handler for * @param {(...args: any[]) => any} handler - handler reference * @return {PgPubSub} */ private setOnceHandler; /** * Clears all similar handlers under given event * * @param {string} event - event name * @param {(...args: any) => any} handler - handler reference */ private clearListeners; /** * Database notification event handler * * @access private * @param {Notification} notification - database message data * @return {Promise<void>} */ private onNotification; /** * Database notification event handler for execution lock * * @access private * @param {Notification} notification - database message data * @return {Promise<void>} */ private onNotificationLockExec; /** * On reconnect event emitter * * @access private * @return {Promise<void>} */ private onReconnect; /** * Reconnect routine, used for implementation of auto-reconnecting db * connection * * @access private * @return {number} */ private reconnect; /** * Instantiates and returns process lock for a given channel or returns * existing one * * @access private * @param {string} channel * @return {Promise<PgIpLock>} */ private lock; /** * Instantiates new lock, properly initializes it and returns * * @param {string} channel * @param {string} [uniqueKey] * @return {Promise<AnyLock>} */ private createLock; /** * Releases all acquired locks in current session * * @access private * @return {Promise<void>} */ private release; /** * Sets application_name for this connection as unique identifier * * @access private * @return {Promise<void>} */ private setAppName; /** * Retrieves process identifier from the database connection and sets it to * `this.processId`. * * @return {Promise<void>} */ private setProcessId; }