UNPKG

prostgles-client

Version:

Reactive client for Postgres

202 lines (201 loc) 7.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSubscriptionHandler = void 0; const prostgles_types_1 = require("prostgles-types"); const prostgles_1 = require("./prostgles"); const FunctionQueuer_1 = require("./FunctionQueuer"); const preffix = prostgles_types_1.CHANNELS._preffix; const getSubscriptionHandler = (initOpts) => { const { socket, onDebug } = initOpts; const subscriptions = {}; const removeServerSub = (unsubChannel) => { return new Promise((resolve, reject) => { socket.emit(unsubChannel, {}, (err, _res) => { if (err) { console.error(err); reject(err); } else { resolve(_res); } }); }); }; function _unsubscribe(channelName, unsubChannel, handler) { (0, prostgles_1.debug)("_unsubscribe", { channelName, handler }); return new Promise((resolve) => { const sub = subscriptions[channelName]; if (sub) { sub.handlers = sub.handlers.filter((h) => h !== handler); onDebug === null || onDebug === void 0 ? void 0 : onDebug({ type: "table", command: "unsubscribe", tableName: sub.tableName, unsubChannel, handlers: sub.handlers, }); if (!sub.handlers.length) { removeServerSub(unsubChannel); socket.removeListener(channelName, sub.onCall); delete subscriptions[channelName]; /* Not waiting for server confirmation to speed things up */ resolve(true); } else { resolve(true); } } else { resolve(true); } }); } /** * Obtaines subscribe channel from server */ function addServerSub({ tableName, command, param1, param2, }) { return new Promise((resolve, reject) => { socket.emit(preffix, { tableName, command, param1, param2 }, (err, res) => { if (err) { console.error(err); reject(err); } else if (res) { resolve(res); } }); }); } /** * Can be used concurrently */ const addSubQueuer = new FunctionQueuer_1.FunctionQueuer(_addSub, ([_, { tableName }]) => tableName); const addSub = async (dbo, params, onChange, _onError) => { return addSubQueuer.run([dbo, params, onChange, _onError]); }; /** * Do NOT use concurrently */ async function _addSub(dbo, { tableName, command, param1, param2 }, onChange, _onError) { const makeHandler = (channelName, unsubChannel) => { const unsubscribe = function () { return _unsubscribe(channelName, unsubChannel, onChange); }; let subHandlers = { unsubscribe, filter: { ...param1 } }; /* Some dbo sorting was done to make sure this will work */ if (dbo[tableName].update) { subHandlers = { ...subHandlers, update: function (newData, updateParams) { return dbo[tableName].update(param1, newData, updateParams); }, }; } if (dbo[tableName].delete) { subHandlers = { ...subHandlers, delete: function (deleteParams) { return dbo[tableName].delete(param1, deleteParams); }, }; } return Object.freeze(subHandlers); }; const existing = Object.entries(subscriptions).find(([ch, s]) => { return (s.tableName === tableName && s.command === command && (0, prostgles_types_1.isEqual)(s.param1 || {}, param1 || {}) && (0, prostgles_types_1.isEqual)(s.param2 || {}, param2 || {})); }); if (existing) { const existingCh = existing[0]; existing[1].handlers.push(onChange); existing[1].errorHandlers.push(_onError); setTimeout(() => { var _a, _b; if ((_a = subscriptions[existingCh]) === null || _a === void 0 ? void 0 : _a.lastData) { onChange((_b = subscriptions[existingCh]) === null || _b === void 0 ? void 0 : _b.lastData); } }, 10); return makeHandler(existingCh, existing[1].unsubChannel); } const { channelName, channelNameReady, channelNameUnsubscribe } = await addServerSub({ tableName, command, param1, param2, }); const onCall = function (data) { /* TO DO: confirm receiving data or server will unsubscribe */ // if(cb) cb(true); const sub = subscriptions[channelName]; if (sub) { if (data.data) { sub.lastData = data.data; sub.handlers.forEach((h) => { h(data.data); }); } else if (data.err) { sub.errorHandlers.forEach((h) => { h === null || h === void 0 ? void 0 : h(data.err); }); } else { console.error("INTERNAL ERROR: Unexpected data format from subscription: ", data); } } else { console.warn("Orphaned subscription: ", channelName); } }; const onError = _onError || function (err) { console.error(`Uncaught error within running subscription \n ${channelName}`, err); }; socket.on(channelName, onCall); subscriptions[channelName] = { lastData: undefined, tableName, command, param1, param2, onCall, unsubChannel: channelNameUnsubscribe, handlers: [onChange], errorHandlers: [onError], reAttach: async () => { await removeServerSub(channelNameUnsubscribe).catch(console.error); await addServerSub({ tableName, command, param1, param2 }); socket.emit(channelNameReady, { now: Date.now() }); }, }; socket.emit(channelNameReady, { now: Date.now() }); return makeHandler(channelName, channelNameUnsubscribe); } /** * Reconnect all subscriptions * Used when connection is lost and re-established or schema changes */ const reAttachAll = async () => { await (onDebug === null || onDebug === void 0 ? void 0 : onDebug({ type: "subscriptions", command: "reAttachAll.start", subscriptions })); for await (const s of Object.values(subscriptions)) { try { await s.reAttach(); } catch (err) { console.error("There was an issue reconnecting old subscriptions", err, s); Object.values(s.errorHandlers).forEach((h) => h === null || h === void 0 ? void 0 : h(err)); } } await (onDebug === null || onDebug === void 0 ? void 0 : onDebug({ type: "subscriptions", command: "reAttachAll.end", subscriptions })); }; return { addSub, subscriptions, addServerSub, _unsubscribe, reAttachAll, }; }; exports.getSubscriptionHandler = getSubscriptionHandler;