UNPKG

synapse-storage

Version:

Набор инструментов для управления состоянием и апи-запросами

188 lines (183 loc) 8.8 kB
import { loggerConsole } from "../../../_utils/index.js"; // Определяем типы для переводов и опций // Функция для вычисления разницы между двумя объектами состояния const MAX_DIFF_DEPTH = 10; function getStateDiff(prevState, nextState, seen = new WeakSet(), depth = 0) { // Guard для null/undefined if (prevState == null && nextState == null) return {}; if (prevState == null) return { _root: { PREV: prevState, NEXT: nextState } }; if (nextState == null) return { _root: { PREV: prevState, NEXT: nextState } }; // Лимит глубины рекурсии if (depth >= MAX_DIFF_DEPTH) { return prevState !== nextState ? { _truncated: { PREV: '[max depth]', NEXT: '[max depth]' } } : {}; } // Защита от циклических ссылок if (typeof prevState === 'object') { if (seen.has(prevState)) return { _circular: '[circular reference]' }; seen.add(prevState); } if (typeof nextState === 'object') { if (seen.has(nextState)) return { _circular: '[circular reference]' }; seen.add(nextState); } const diff = {}; // Проверяем изменения в свойствах верхнего уровня const allKeys = [ ...new Set([ ...Object.keys(prevState), ...Object.keys(nextState) ]) ]; allKeys.forEach((key)=>{ // Если ключ существует в обоих состояниях if (key in prevState && key in nextState) { // Если это объекты, рекурсивно проверяем их if (typeof prevState[key] === 'object' && prevState[key] !== null && typeof nextState[key] === 'object' && nextState[key] !== null && !Array.isArray(prevState[key]) && !Array.isArray(nextState[key])) { const nestedDiff = getStateDiff(prevState[key], nextState[key], seen, depth + 1); if (Object.keys(nestedDiff).length > 0) { diff[key] = nestedDiff; } } else if (prevState[key] !== nextState[key]) { try { if (JSON.stringify(prevState[key]) !== JSON.stringify(nextState[key])) { diff[key] = { PREV: prevState[key], NEXT: nextState[key] }; } } catch { // JSON.stringify может упасть на циклических ссылках внутри значений diff[key] = { PREV: prevState[key], NEXT: nextState[key] }; } } } else if (key in prevState) { diff[key] = { PREV: prevState[key], NEXT: undefined }; } else { diff[key] = { PREV: undefined, NEXT: nextState[key] }; } }); return diff; } /** * Dev-only middleware для логирования действий dispatcher'а. * * **Внимание:** вызывает `getState()` дважды (до и после действия) для получения prev/next состояния. * Это добавляет overhead и не рекомендуется для production. Используйте только в dev-окружении. */ const loggerDispatcherMiddleware = (options = {})=>{ const defaultTranslations = { action: 'Действие', prevState: 'Предыдущее состояние', nextState: 'Следующее состояние', duration: 'Длительность', error: 'Ошибка в действии', diff: 'Изменения', changesCount: 'Количество изменений', showFullState: 'Полное состояние' }; const defaultOptions = { collapsed: false, duration: true, diff: false, showFullState: true, translations: defaultTranslations, colors: { title: '#3498db', prevState: '#9E9E9E', fullState: '#008a15', action: '#03A9F4', nextState: '#4CAF50', error: '#F20404', diff: '#9C27B0' } }; // Объединяем пользовательские настройки с настройками по умолчанию const mergedOptions = { ...defaultOptions, ...options, // Объединяем переводы отдельно, чтобы позволить частичное переопределение translations: { ...defaultTranslations, ...options.translations || {} }, // Объединяем цвета отдельно, чтобы позволить частичное переопределение colors: { ...defaultOptions.colors, ...options.colors || {} } }; const { collapsed, duration, colors, translations } = mergedOptions; return (api)=>(next)=>async (action)=>{ // Собираем информацию перед выполнением const started = Date.now(); const prevState = await api.getState(); // Выполняем действие без группировки логов try { // Выполняем действие const result = await next(action); // Собираем информацию после выполнения const nextState = await api.getState(); const ended = Date.now(); const time = ended - started; // Теперь выводим всю информацию в группе const title = `${action.type}`; const groupMethod = collapsed ? loggerConsole.groupCollapsed : loggerConsole.group; groupMethod(`%c ${title}`, `color: ${colors.title}; font-weight: bold`); // Выводим информацию о действии loggerConsole.log(`%c ${translations.action}:`, `color: ${colors.action}; font-weight: bold`, action); // Если включена опция diff, вычисляем и показываем изменения if (mergedOptions.diff) { const stateDiff = getStateDiff(prevState, nextState); const changesCount = Object.keys(stateDiff).length; loggerConsole.log(`%c ${translations.diff} (${translations.changesCount}: ${changesCount}):`, `color: ${colors.diff}; font-weight: bold`, stateDiff); } // Если showFullState включен, показываем полные состояния if (mergedOptions.showFullState) { // Создаем подгруппу для полного состояния loggerConsole.groupCollapsed(`%c ${translations.showFullState}`, `color: ${colors.fullState}; font-weight: bold`); loggerConsole.log(`%c ${translations.prevState}:`, `color: ${colors.prevState}; font-weight: bold`, prevState); loggerConsole.log(`%c ${translations.nextState}:`, `color: ${colors.nextState}; font-weight: bold`, nextState); loggerConsole.groupEnd(); } if (duration) { loggerConsole.log(`%c ${translations.duration}: %c ${time}ms`, 'font-weight: bold', 'color: #4CAF50'); } loggerConsole.groupEnd(); return result; } catch (error) { // В случае ошибки логируем её отдельно, не в группе loggerConsole.error(`%c ${translations.error}:`, `color: ${colors.error}; font-weight: bold`, action.type, error); // Пробрасываем ошибку дальше throw error; } }; }; export { loggerDispatcherMiddleware }; //# sourceMappingURL=logger.middleware.js.map