prostgles-client
Version:
Reactive client for Postgres
195 lines (194 loc) • 7.63 kB
JavaScript
;
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 = new Map();
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.get(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);
subscriptions.delete(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]);
};
/**
* Do NOT use concurrently
*/
async function _addSub(dbo, { tableName, command, param1, param2 }, onChange) {
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 = Array.from(subscriptions.entries()).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 [existingChannel, existingSub] = existing;
existingSub.handlers.push(onChange);
setTimeout(() => {
const sub = subscriptions.get(existingChannel);
if (sub === null || sub === void 0 ? void 0 : sub.lastData) {
onChange(sub.lastData);
}
}, 10);
return makeHandler(existingChannel, existingSub.unsubChannel);
}
const { channelName, channelNameReady, channelNameUnsubscribe } = await addServerSub({
tableName,
command,
param1,
param2,
});
const onCall = function (subData) {
/* TO DO: confirm receiving data or server will unsubscribe */
// if(cb) cb(true);
const sub = subscriptions.get(channelName);
const { data, err } = subData;
if (sub) {
if (data !== undefined || err !== undefined) {
sub.lastData = data;
sub.handlers.forEach((handler) => {
if (err !== undefined) {
console.error(`Error within running subscription \n ${channelName}`, err);
}
handler(data !== null && data !== void 0 ? data : [], err);
});
}
else {
console.error("INTERNAL ERROR: Unexpected data format from subscription: ", subData);
}
}
else {
console.warn("Orphaned subscription: ", channelName);
}
};
socket.on(channelName, onCall);
subscriptions.set(channelName, {
lastData: undefined,
tableName,
command,
param1,
param2,
onCall,
unsubChannel: channelNameUnsubscribe,
handlers: [onChange],
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 Array.from(subscriptions.values())) {
try {
await s.reAttach();
}
catch (err) {
console.error("There was an issue reconnecting old subscriptions", err, s);
s.onCall({ data: [], 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;