@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
JavaScript
;
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