UNPKG

@dwwoelfel/lds

Version:

Logical decoding server for PostgreSQL, monitors for new/edited/deleted rows and announces them to interested clients.

114 lines 4.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /* tslint:disable no-console curly */ const pg_logical_decoding_1 = require("./pg-logical-decoding"); const fatal_error_1 = require("./fatal-error"); const DROP_STALE_SLOTS_INTERVAL = 15 * 60 * 1000; async function subscribeToLogicalDecoding(connectionString, callback, options = {}) { const { slotName = "postgraphile", tablePattern = "*.*", sleepDuration = 200, temporary = false, } = options; let lastLsn = null; const client = new pg_logical_decoding_1.default(connectionString, { tablePattern, slotName, temporary, }); // We must do this before we create the temporary slot, since errors will release a temporary slot immediately await client.dropStaleSlots(); try { await client.createSlot(); } catch (e) { if (e.fatal) { throw e; } else if (e.code === "42710") { // Slot already exists; ignore. } else if (e.code === "42602") { throw new fatal_error_1.default(`Invalid slot name '${slotName}'?`, e); } else { console.error("An unhandled error occurred when attempting to create the replication slot:"); console.trace(e); throw e; } } let loopTimeout; const ldSubscription = { close: async () => { clearTimeout(loopTimeout); await client.close(); }, }; let nextStaleCheck = Date.now() + DROP_STALE_SLOTS_INTERVAL; async function loop() { try { const rows = await client.getChanges(null, 500); if (rows.length) { for (const row of rows) { const { lsn, data: { change: changes }, } = row; lastLsn = lsn || lastLsn; for (const change of changes) { const { schema, table } = change; if (change.kind === "insert") { const announcement = { _: "insertC", schema, table, data: pg_logical_decoding_1.changeToRecord(change), }; callback(announcement); } else if (change.kind === "update") { const rowAnnouncement = { _: "update", schema, table, keys: pg_logical_decoding_1.changeToPk(change), data: pg_logical_decoding_1.changeToRecord(change), }; callback(rowAnnouncement); const collectionAnnouncement = { _: "updateC", schema, table, data: pg_logical_decoding_1.changeToRecord(change), }; callback(collectionAnnouncement); } else if (change.kind === "delete") { const announcement = { _: "delete", schema, table, keys: pg_logical_decoding_1.changeToPk(change), }; callback(announcement); } else { console.warn("Did not understand change: ", change); } } } } if (!temporary && nextStaleCheck < Date.now()) { // Roughly every 15 minutes, drop stale slots. nextStaleCheck = Date.now() + DROP_STALE_SLOTS_INTERVAL; client.dropStaleSlots().catch(e => { console.error("Failed to drop stale slots:", e.message); }); } } catch (e) { console.error("Error during LDS loop:", e.message); // Recovery time... loopTimeout = setTimeout(loop, sleepDuration * 10); return; } loopTimeout = setTimeout(loop, sleepDuration); } loop(); return ldSubscription; } exports.default = subscribeToLogicalDecoding; //# sourceMappingURL=index.js.map