prostgles-client
Version:
Reactive client for Postgres
202 lines (201 loc) • 7.89 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 = {};
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;