patronum
Version:
☄️ Effector utility library delivering modularity and convenience
432 lines (423 loc) • 11.3 kB
JavaScript
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;
}
;