diff --git a/nodejs/utils/model_pubsub.js b/nodejs/utils/model_pubsub.js index e935ab8..bd41039 100644 --- a/nodejs/utils/model_pubsub.js +++ b/nodejs/utils/model_pubsub.js @@ -1,55 +1,91 @@ 'use strict'; + +/** + * PubSub controller dependency to handle message broadcasting + */ const ps = require('../controller/pubsub'); - -function ModelPs(model){ - const Model = model.constructor.name === 'Function' ? model : model.constructor +/** + * Wraps a model in a Proxy to automatically publish events on specific method calls. + * @param {Object|Function} model - The data model or instance to be proxied. + * @returns {Proxy} - The proxied model. + */ +function ModelPs(model) { + // Ensure we have a reference to the class constructor regardless of whether an instance or class was passed + const Model = model.constructor.name === 'Function' ? model : model.constructor; - function getIndex(req, res){ - if(model[Model._key]) return model[Model._key]; - if(req && req[Model._key]) return req[Model._key]; - if(res && res[Model._key]) return res[Model._key]; + /** + * Extracts the unique identifier (primary key) from the model or request/response objects. + */ + function getIndex(req, res) { + if (model[Model._key]) return model[Model._key]; + if (req && req[Model._key]) return req[Model._key]; + if (res && res[Model._key]) return res[Model._key]; } - function publish(prop, res, req){ - try{ - if(!['add', 'create', 'update', 'remove'].includes(prop)) return; + /** + * Formats and broadcasts the message via PubSub. + * Topic format: model:ClassName:Action:ID + */ + function publish(prop, res, req) { + try { + // Only trigger for specific mutation keywords + if (!['add', 'create', 'update', 'remove'].includes(prop)) return; ps.publish(`model:${Model.name}:${prop}:${getIndex(res, req)}`, res); - }catch(error){ - console.log('ModelPs.publish ERROR', error) + } catch (error) { + console.log('ModelPs.publish ERROR', error); + } + } + + /** + * Standardized error logger that ignores common/non-critical HTTP errors + */ + function handleError(error, model, propKey) { + if (![401, 404, 429].includes(error.status)) { + console.error("Error PS", model.name, propKey, error); } } return new Proxy(model, { + /** + * Intercepts 'new' keyword calls to ensure instances are also proxied. + */ construct(target, args, newTarget) { - return ModelPs(Reflect.construct(target, args, newTarget)) + return ModelPs(Reflect.construct(target, args, newTarget)); }, - get(target, propKey, receiver) { - if(propKey == 'constructor') return target.constructor; - const targetValue = Reflect.get(target, propKey, receiver); - if (typeof targetValue === 'function') { - return function(...args){ - try{ - // let res = targetValue.apply(this, args); // (A) - var res = Reflect.apply(targetValue, this, args); - if(targetValue.constructor.name === 'AsyncFunction'){ - res.then(function(res){ - publish(propKey, res, ...args); - }).catch(function(error){ - console.error("Error async PS", model.name, propKey, error) - // console.log('toDo, publish errors...'); - }); - }else{ + /** + * Intercepts property/method access. + */ + get(target, propKey, receiver) { + // Ensure constructor access remains direct + if (propKey == 'constructor') return target.constructor; + + const targetValue = Reflect.get(target, propKey, receiver); + + // If the property accessed is a function, wrap it to inject the PubSub logic + if (typeof targetValue === 'function') { + return function(...args) { + try { + // Execute the original method + var res = Reflect.apply(targetValue, this, args); + + // Handle Asynchronous results (Promises) + if (targetValue.constructor.name === 'AsyncFunction') { + res.then(function(res) { + publish(propKey, res, ...args); + }).catch((error) => handleError(error, model, propKey)); + + } else { + // Handle Synchronous results publish(propKey, res, ...args); } return res; - }catch(error){ - console.error("Error PS", model.name, propKey, error) - // console.log("toDo, publish errors..."); + } catch (error) { + handleError(error, model, propKey); } - } + }; } else { return targetValue; } @@ -57,4 +93,4 @@ function ModelPs(model){ }); } -module.exports = ModelPs; +module.exports = ModelPs; \ No newline at end of file