UNPKG

proxy-tracker

Version:

Nato per separare in modo semplice il controllo degli errori e il logging dalla logica dell'applicazione. Facilita l'uso di Proxy con handler complessi scrivendoli in una forma standardizzata. Permette di inserire funzioni "spia" tramite Proxy

166 lines (151 loc) 7.24 kB
/* global Reflect, Function, process */ const ENVIRONMENT = process.env.NODE_ENV; const assert = require('assert').strict; const EACH = Symbol('EACH'); const default_trapList = function returnEndingTrapFromList(metodo){ const ending_of_trap_list = { apply(target, thisArg, args){return Reflect.apply(...arguments);}, construct(target, args, newtarget){return Reflect.construct(...arguments);}, defineProperty(target, property, descriptor){return Reflect.defineProperty(...arguments);}, deleteProperty(target, prop){return Reflect.deleteProperty(...arguments);}, get(target, prop, receiver){return Reflect.get(...arguments);}, getOwnPropertyDescriptor(target, prop){return Reflect.getOwnPropertyDescriptor(...arguments);}, getPrototypeOf(target){return Reflect.getPrototypeOf(...arguments);}, has(target, prop){return Reflect.has(...arguments);}, isExtensible(target){return Reflect.isExtensible(...arguments);}, ownKeys(target){return Reflect.ownKeys(...arguments);}, preventExtensions(target){return Reflect.preventExtensions(...arguments);}, set(target, property, value, receiver){return Reflect.set(...arguments);}, setPrototypeOf(target, prototype){return Reflect.setPrototypeOf(...arguments);} }; let ending_trap = ending_of_trap_list[metodo]; return ending_trap; }; function generaHandlerForProxy(handler_of_track_type, {NAME}, entity = undefined, modifiesHandler = undefined, trapList = default_trapList){ checkHandler({trapList, handler_of_track_type, modifiesHandler, NAME}); const handler_generato = creaHandlerRicorsivo(handler_of_track_type, {NAME}, trapListWithCheck(trapList), modifiesHandler); if(modifiesHandler !== undefined) return modifiesHandler(handler_generato, entity); else return handler_generato; } function checkHandler({handler_of_track_type, trapList, modifiesHandler, NAME}){ assert(typeof trapList === 'function', 'traplist must to be a function'); assert(typeof handler_of_track_type === 'object', 'handler non è stato inserito'); assert(modifiesHandler === undefined || typeof modifiesHandler === 'function', 'callback for changing handler must to be a function'); assert(typeof NAME === 'symbol', 'CONST.NAME must to be a Symbol'); } function trapListWithCheck(trap_list){ return function(trap_name){ const returning_value_by_trap_callback = trap_list(trap_name); if(returning_value_by_trap_callback === undefined) throw new TypeError(`La trappola non è del tipo previsto da Proxy, ma è ${trap_name}`); return returning_value_by_trap_callback; }; } function creaHandlerRicorsivo(handler_of_track_type, {NAME}, trapList, modifiesHandler){ const handler = {}; for(let name in handler_of_track_type){ if(handler_of_track_type[name] === undefined) continue; const {cbs, hds, ret, FOR} = splitCallbackObject(handler_of_track_type[name]); let trappola; let returning_value_callback = (ret===undefined?trapList(name):ret); const sub_handler = {}; if(typeof hds === 'object'){ sub_handler.hds = creaHandlerRicorsivo(hds, {NAME}, trapList, modifiesHandler); } if(Array.isArray(FOR)){ sub_handler.FOR = extractReturningTrapsFromFOR({NAME}, FOR, trapList, modifiesHandler); } const sub_handler_all = Object.assign({}, {[EACH]: sub_handler.hds}, sub_handler.FOR); let returning = returnEndingTrap(name, returning_value_callback, sub_handler_all, modifiesHandler); trappola = template_trap(cbs, returning); handler[name] = trappola; } return handler; } function splitCallbackObject(list){ return {cbs: list.cbs, hds: list.hds, ret: list.ret, FOR: list.FOR}; } function extractReturningTrapsFromFOR({NAME}, handlers_FOR_list, trapList, modifiesHandler){ assert(Array.isArray(handlers_FOR_list), 'FOR property must to be an array'); const sub_handlers = {}; for(let handler_in_FOR of handlers_FOR_list){ let name_prop_that_must_to_have_subhandler = handler_in_FOR[NAME]; let sub_handler_without_NAME = Object.assign({}, handler_in_FOR, {[NAME]: undefined}); sub_handlers[name_prop_that_must_to_have_subhandler] = creaHandlerRicorsivo(sub_handler_without_NAME, {NAME}, trapList, modifiesHandler); } return sub_handlers; } function returnEndingTrap(trap_name, returning_value_callback, handler, modifiesHandler){ return (...args)=>{ const handler_choosed = chooseHandler(trap_name, args, handler); let value_returned_by_trap = returning_value_callback(...args); let handler_modified = undefined; if(typeof handler_choosed === 'object'){ if(typeof modifiesHandler === 'function') handler_modified = modifiesHandler(handler_choosed, value_returned_by_trap); else handler_modified = handler_choosed; } return returnProxyOrValue(value_returned_by_trap, handler_modified);}; } function chooseHandler(trap_name, args, handler){ let name_prop = extractPropertyNameFromArgsTrap(trap_name, args); if(name_prop === undefined) return handler[EACH]; if(name_prop in handler) return joinTrapsHandler(handler[name_prop], handler[EACH]); else return handler[EACH]; } function extractPropertyNameFromArgsTrap(trap_name, args){ let prop_name; switch(trap_name){ case 'apply': [prop_name] = args; prop_name = prop_name.name; break; case 'get': [,prop_name] = args; break; case 'defineProperty': [,prop_name] = args; break; case 'deleteProperty': [,prop_name] = args; break; case 'getOwnPropertyDescriptor': [,prop_name] = args; break; case 'has': [,prop_name] = args; break; case 'set': [,prop_name] = args; break; default: prop_name = undefined; } return prop_name; } function joinTrapsHandler(handler_master, handler_slave){ if(handler_slave === undefined) return handler_master; const handler_joined = {}; for(let trap in handler_master){ handler_joined[trap] = function(...args){ if(handler_slave[trap] !== undefined) handler_slave[trap](...args); return handler_master[trap](...args);}; } return handler_joined; } function returnProxyOrValue(value, handler){ const logger = require('./logger.js'); if((value instanceof Function || typeof value === 'object') && typeof handler === 'object'){ try{ return new Proxy(value, handler);} catch(e){ logger.error({exception: e, value, handler}); ifExceptionIsntForValueThenThrow(e); return value; } } else return value; } function ifExceptionIsntForValueThenThrow(e){ if(e.message === 'Cannot create proxy with a non-object') throw e; } function template_trap(callbacks, returning){ return (...args)=>{ let value = returning(...args); for(let cb of callbacks) cb(value, ...args); return value; }; } module.exports = generaHandlerForProxy; module.exports.CONST = {EACH: EACH}; module.exports.extractReturningTrapsFromFOR = extractReturningTrapsFromFOR; module.exports.default_trapList = default_trapList;