@imqueue/pg-pubsub
Version:
Reliable PostgreSQL LISTEN/NOTIFY with inter-process lock support
437 lines (436 loc) • 13.2 kB
TypeScript
/*!
* 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;
}