UNPKG

patronum

Version:

☄️ Effector utility library delivering modularity and convenience

432 lines (423 loc) 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.debug = debug; var _effector = require("effector"); const defaultConfig = { trace: false, // default logger to console.info handler: context => { if (isEffectChild(context.node) && context.node.meta.named === 'finally') { // skip effect.finally logs, it can be useful for other custom handlers // but not for default console.info logger return; } const { scope, scopeName, name, kind, value, loc, trace, node, logType } = context; const scopeLog = scope ? ` (scope: ${scopeName})` : ''; const logName = name !== null && name !== void 0 ? name : loc ? `${loc.file}:${loc.line}:${loc.column}` : ''; const logPrintType = logType === 'initial' ? ' [getState]' : ''; console.info(`[${kind}]${scopeLog} ${logName}${logPrintType}`, value); if ( // logging trace only if there is something to log trace && trace.length > 0 && // do not log trace for effect children, as it is always the same effect internals !isEffectChild(node)) { console.groupCollapsed(`[${kind}]${scopeLog} ${logName} trace`); trace.forEach(update => { const { name: traceName, kind, value, loc } = update; const logTraceName = traceName !== null && traceName !== void 0 ? traceName : loc ? `${loc.file}:${loc.line}:${loc.column}` : ''; console.info(`<- [${kind}] ${logTraceName}`, value); }); console.groupEnd(); } } }; function debug(...entries) { const { config, units } = resolveParams(...entries); units.forEach(unit => { if (_effector.is.store(unit, { sid: "-foynff" }) || _effector.is.event(unit, { sid: "apy28p" }) || _effector.is.effect(unit, { sid: "apy3od" })) { watchUnit(unit, config); } else if (_effector.is.domain(unit, { sid: "-rsqe9t" })) { watchDomain(unit, config); } else { /** * Let unknown stuff pass through as noop * * It's useful for debug of custom entities: * debug(myFarfetchedQuery) */ } }); } // Log node function watchDomain(domain, config) { domain.onCreateStore(store => watchUnit(store, config)); domain.onCreateEvent(event => watchUnit(event, config)); domain.onCreateEffect(effect => watchUnit(effect, config)); domain.onCreateDomain(domain => watchDomain(domain, config)); } function watchUnit(unit, config) { if (_effector.is.store(unit, { sid: "w7ds2s" })) { // store has its initial/current value - we can log it right away watchStoreInit(unit, config); watch(unit, config); } else if (_effector.is.event(unit, { sid: "fretyd" })) { watch(unit, config); } else if (_effector.is.effect(unit, { sid: "gpi2qr" })) { watch(unit, config); watch(unit.finally, config); watch(unit.done, config); watch(unit.fail, config); } } function watch(unit, config) { const watcher = (0, _effector.createNode)({ parent: [unit], // debug watchers should behave like normal watchers meta: { op: 'watch' }, family: { owners: unit }, regional: true, // node only gets all required data node: [_effector.step.run({ fn(value, _internal, stack) { var _stack$scope; const scope = (_stack$scope = stack?.scope) !== null && _stack$scope !== void 0 ? _stack$scope : null; const context = { logType: 'update', scope, scopeName: getScopeName(scope), node: getNode(unit), kind: getType(unit), value, name: getName(unit), loc: getLoc(unit), // Use stack meta of actual unit, not of debug node stackMeta: getStackMeta(stack.parent), trace: config.trace ? collectTrace(stack) : [] }; if (!config.handler) { throw Error('patronum/debug must have the handler'); } config.handler(context); } })] }); return () => (0, _effector.clearNode)(watcher); } function collectTrace(stack) { const trace = []; let parent = stack?.parent; while (parent) { const { node, value } = parent; const entry = { node, value, name: getName(node), loc: getLoc(node), kind: getType(node), stackMeta: getStackMeta(parent) }; trace.push(entry); parent = parent.parent; } return trace; } function watchStoreInit(store, config) { if (!config.handler) { throw Error('patronum/debug must have the handler'); } const node = getNode(store); // current state const context = { logType: 'initial', scope: null, scopeName: null, node, kind: getType(store), value: store.getState(), name: getName(store), loc: getLoc(store), // nothing to trace for store.getState() - it is one-step call trace: [], // no stackMeta for initial state stackMeta: {} }; config.handler(context); // current state in every known scope scopes.forEach(scope => watchStoreInitInScope(store, config, scope)); // subscribe to new scopes watchScopeRegister(newScope => watchStoreInitInScope(store, config, newScope)); } function watchStoreInitInScope(store, config, scope) { if (!config.handler) { throw Error('patronum/debug must have the handler'); } const node = getNode(store); // current state const context = { logType: 'initial', scope, scopeName: getScopeName(scope), node, kind: getType(store), value: scope.getState(store), name: getName(store), loc: getLoc(store), // nothing to trace for scope.getState(store) - it is one-step call trace: [], // no stackMeta for initial state stackMeta: {} }; config.handler(context); } // Config function resolveParams(...entry) { let config = defaultConfig; const [maybeConfig, ...restUnits] = entry; const units = []; if (isConfig(maybeConfig)) { config = { ...defaultConfig, ...maybeConfig }; } else if (!_effector.is.unit(maybeConfig)) { for (const [name, unit] of Object.entries(maybeConfig)) { customNames.set(getGraph(unit).id, name); units.push(unit); } } else { units.push(maybeConfig); } for (const maybeUnit of restUnits) { if (_effector.is.unit(maybeUnit)) { units.push(maybeUnit); } else { for (const [name, unit] of Object.entries(maybeUnit)) { customNames.set(getGraph(unit).id, name); units.push(unit); } } } return { config, units }; } function isConfig(maybeConfig) { if (!_effector.is.unit(maybeConfig)) { return !Object.values(maybeConfig).every(_effector.is.unit); } return false; } // Scopes const watchers = new Set(); const watchScopeRegister = cb => { watchers.add(cb); return () => { watchers.delete(cb); }; }; function registerScope(scope, config) { scopes.save(scope, { name: config.name }); watchers.forEach(cb => cb(scope)); return () => { scopes.delete(scope); }; } function unregisterAllScopes() { scopes.clear(); } let unknownScopes = 0; function getDefaultScopeName() { unknownScopes += 1; return `unknown_${unknownScopes}`; } const cache = new Map(); const scopes = { save(scope, meta) { if (!scopes.get(scope)) { cache.set(scope, meta); } }, get(scope) { var _cache$get; if (!scope) return null; return (_cache$get = cache.get(scope)) !== null && _cache$get !== void 0 ? _cache$get : null; }, delete(scope) { cache.delete(scope); }, forEach(callback) { cache.forEach((meta, scope) => callback(scope, meta)); }, clear() { cache.clear(); } }; debug.registerScope = registerScope; debug.unregisterAllScopes = unregisterAllScopes; function getScopeName(scope) { if (!scope) return null; const meta = scopes.get(scope); if (!meta) { // @ts-expect-error const fallbackId = scope._debugId || (scope._debugId = getDefaultScopeName()); return fallbackId; } return meta.name; } // Utils function isEffectChild(node) { const actualNode = getNode(node); const { sid, named } = actualNode.meta; return Boolean(!sid && (named === 'finally' || named === 'done' || named === 'doneData' || named === 'fail' || named === 'failData' || named === 'inFlight' || named === 'pending')); } function isStoreOn(node) { const actualNode = getNode(node); const { op } = actualNode.meta; if (op === 'on') return true; return false; } function getType(unit) { if (_effector.is.store(unit, { sid: "4o7s04" })) { return 'store'; } if (_effector.is.effect(unit, { sid: "4pv5s7" }) || isEffectChild(unit)) { return 'effect'; } if (_effector.is.event(unit, { sid: "4rijka" })) { return 'event'; } if (_effector.is.domain(unit, { sid: "54plsy" })) { return 'domain'; } if (_effector.is.unit(unit)) { return 'unit'; } const node = getNode(unit); if (node.meta.op) { return node.meta.op; } return 'unknown'; } const getGraph = graph => graph.graphite || graph; const customNames = new Map(); function getName(unit) { const custom = customNames.get(getGraph(unit).id); if (custom) { return custom; } if (isEffectChild(unit)) { const node = getNode(unit); const parentEffect = node.family.owners.find(n => n.meta.op === 'effect'); if (parentEffect) { const closestParentDomainName = getOwningDomainName(parentEffect); const formattedDomainName = closestParentDomainName ? `${closestParentDomainName}/` : ''; return `${formattedDomainName}${getName(parentEffect)}.${node.meta.named}`; } return node.meta.named; } if (isStoreOn(unit)) { const node = getNode(unit); const targetStoreName = getName(node.next[0]); const triggerEventName = getName(node.family.owners[0]); return `${targetStoreName}.on(${triggerEventName})`; } if (_effector.is.unit(unit)) { if (unit?.compositeName?.fullName) { return unit.compositeName.fullName; } const closestParentDomainName = getOwningDomainName(unit); const formattedDomainName = closestParentDomainName ? `${closestParentDomainName}/` : ''; if (unit?.shortName) { return `${formattedDomainName}${unit.shortName}`; } if (unit?.name) { return `${formattedDomainName}${unit.name}`; } } if (getNode(unit)?.meta?.name) { return getNode(unit).meta.name; } return null; } function getOwningDomainName(unit) { const closestParentDomain = getNode(unit).family.owners.find(n => n.meta.op === 'domain'); if (!closestParentDomain) return null; return getName(closestParentDomain); } function readLoc({ meta }) { const loc = 'config' in meta ? meta.config.loc : meta.loc; return loc; } function getLoc(unit) { const loc = readLoc(getNode(unit)); if (!loc) return undefined; return loc; } function getNode(node) { const actualNode = 'graphite' in node ? node.graphite : node; return actualNode; } function getStackMeta(stack) { if (!stack) return {}; const meta = stack.meta || {}; return meta; }