UNPKG

pg-promise-listener

Version:

Resilient notification listener for Postgres. Based on code from pg-promise module

170 lines (126 loc) 4.38 kB
# Deprecated Any pg-promise client should be using https://github.com/vitaly-t/pg-listener for this now. # pg-promise-listener Resilient notification listener for pg-promise The code in this module is mostly copied from Vitaly Tomilov's example code, which can be found here: https://github.com/vitaly-t/pg-promise/wiki/Robust-Listeners ## Install ``` npm install pg-promise-listener --save ``` ## Use Example: ```javascript const Listener = require('pg-promise-listener'); const pgp = require('pg-promise')(); const myConnection = pgp({ host: 'localhost', port: 5432, database: 'myDatabase', user: 'myUser', password: 'myPassword', }); function messageHandler(message) { console.log(message); } const listener = new Listener({ dbConnection: myConnection, onDatabaseNotification: messageHandler, channel: 'myChannel', }); ``` ## Sending notifications from Postgres ```sql SELECT pg_notify('myChannel', 'My message'); ``` ## Keeping connections alive on AWS Thank you @boromisp for reasearching and documenting a keep-alive problem (mostly on AWS). Link to write-up: https://github.com/vitaly-t/pg-promise/issues/593 ## SelfCheck If you do run your servers on AWS, you have probably already implemented a /health endpoint. In order to discover all kinds of problems pg-promise-listener has a selfCheck method. This method returns a promise, which is only resolved after a complete roundtrip to the Postgres server like this: 1. Tell the database to send a notification with a special message (actually just a guid) 2. Wait for a message with this guid to arrive from the database server 3. Resolve the promise with **true** if the notification arrives within `selfCheckTimeout` milliseconds (defaults to 2000ms) 4. Otherwise resolve the promise with **false** N.B. There will be no rejection of the promise, so you won't have to bother with `.catch` if you "then" the promise, or `try..catch` if you await the promise. In your request handler for the /health endpoint, you could do the following: ```javascript const isReady = await listener.selfCheck(); if (isReady) { res.writeHead(200); } else { res.writeHead(500); } return res.end(); ``` ## Sending and receiving JSON It's very convenient to have a single notification trigger connected to multiple tables. It's also convenient to use JSON as the payload format. In order to do this we need: - Dynamically read some field values from the updated/inserted table row. - Send the json in a stringified format (pg_notify can only send strings) ### The trigger function ```sql CREATE OR REPLACE FUNCTION main.tr_notify() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE _id int; _project_id int; _another_field varchar; _json jsonb; _row jsonb; BEGIN _row = row_to_json(NEW); --Try to read field values from the row --By using json functions we can do this in a "late binding"-fashion without --getting errors for non existing fields _id = _row ->> 'id'; _project_id = _row->> 'project_id'; _another_field = _row->> 'another_field'; _json = jsonb_build_object( 'tableName', TG_TABLE_NAME, 'op', TG_OP, 'id',_id, 'projectId', _project_id 'anotherField', _another_field ); --Strip null values from the json to get rid of fields that does not exist in this particular table _json = jsonb_strip_nulls(_json); --Send the message as a stringified json PERFORM pg_notify('myChannel', _json::varchar); RETURN NULL; END; $function$; ``` ### Creating triggers Our trigger function can now be used in triggers on multiple tables ```sql CREATE TRIGGER ai_au__my_table__notify AFTER UPDATE OR INSERT ON my_table FOR EACH ROW EXECUTE PROCEDURE tr_notify(); CREATE TRIGGER ai_au__my_other_table__notify AFTER UPDATE OR INSERT ON my_other_table FOR EACH ROW EXECUTE PROCEDURE tr_notify(); ``` ### Parsing JSON in the listener As a convenience, the listener can parse your JSON data. Just tell `Listener` that you want this. ```javascript const listener = new Listener({ dbConnection: myConnection, onDatabaseNotification: messageHandler, channel: 'myChannel', parseJson: true, }); ``` ### Output The parsed message might look like this after inserting a record in my_table: ```json { "id": 2, "op": "INSERT", "projectId": 1, "tableName": "my_table" } ```