[{"data":1,"prerenderedAt":699},["ShallowReactive",2],{"navigation_docs_ru":3,"-ru-development-migration":167,"-ru-development-migration-surround":694},[4,61,101,146],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":60},"Руководство","i-lucide-book-open","\u002Fru\u002Fguide","ru\u002F1.guide",[10,15,20,25,30,35,40,45,50,55],{"title":11,"path":12,"stem":13,"icon":14},"Введение","\u002Fru\u002Fguide\u002Fintroduction","ru\u002F1.guide\u002F01.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Установка","\u002Fru\u002Fguide\u002Finstallation","ru\u002F1.guide\u002F02.installation","i-lucide-smartphone",{"title":21,"path":22,"stem":23,"icon":24},"Авторизация","\u002Fru\u002Fguide\u002Fauth","ru\u002F1.guide\u002F03.auth","i-lucide-lock",{"title":26,"path":27,"stem":28,"icon":29},"Кошельки","\u002Fru\u002Fguide\u002Fwallets","ru\u002F1.guide\u002F04.wallets","i-lucide-wallet",{"title":31,"path":32,"stem":33,"icon":34},"Категории","\u002Fru\u002Fguide\u002Fcategories","ru\u002F1.guide\u002F05.categories","i-lucide-tags",{"title":36,"path":37,"stem":38,"icon":39},"Транзакции","\u002Fru\u002Fguide\u002Ftransactions","ru\u002F1.guide\u002F06.transactions","i-lucide-receipt",{"title":41,"path":42,"stem":43,"icon":44},"Переводы","\u002Fru\u002Fguide\u002Ftransfers","ru\u002F1.guide\u002F07.transfers","i-lucide-arrow-left-right",{"title":46,"path":47,"stem":48,"icon":49},"Статистика","\u002Fru\u002Fguide\u002Fstatistics","ru\u002F1.guide\u002F08.statistics","i-lucide-bar-chart-3",{"title":51,"path":52,"stem":53,"icon":54},"Тема","\u002Fru\u002Fguide\u002Ftheme","ru\u002F1.guide\u002F09.theme","i-lucide-palette",{"title":56,"path":57,"stem":58,"icon":59},"Настройки","\u002Fru\u002Fguide\u002Fsettings","ru\u002F1.guide\u002F10.settings","i-lucide-settings",false,{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":60},"Разработка","i-lucide-code","\u002Fru\u002Fdevelopment","ru\u002F2.development",[67,71,76,81,86,91,96],{"title":16,"path":68,"stem":69,"icon":70},"\u002Fru\u002Fdevelopment\u002Finstallation","ru\u002F2.development\u002F01.installation","i-lucide-download",{"title":72,"path":73,"stem":74,"icon":75},"Граф кодовой базы","\u002Fru\u002Fdevelopment\u002Funderstand-anything","ru\u002F2.development\u002F02.understand-anything","i-lucide-network",{"title":77,"path":78,"stem":79,"icon":80},"Офлайн и PWA","\u002Fru\u002Fdevelopment\u002Foffline","ru\u002F2.development\u002F03.offline","i-lucide-wifi-off",{"title":82,"path":83,"stem":84,"icon":85},"История миграций данных","\u002Fru\u002Fdevelopment\u002Fmigration","ru\u002F2.development\u002F04.migration","i-lucide-database",{"title":87,"path":88,"stem":89,"icon":90},"Деплой","\u002Fru\u002Fdevelopment\u002Fdeployment","ru\u002F2.development\u002F05.deployment","i-lucide-rocket",{"title":92,"path":93,"stem":94,"icon":95},"Тестирование","\u002Fru\u002Fdevelopment\u002Ftesting","ru\u002F2.development\u002F06.testing","i-lucide-flask-conical",{"title":97,"path":98,"stem":99,"icon":100},"Дата-утилиты","\u002Fru\u002Fdevelopment\u002Fdate-utilities","ru\u002F2.development\u002F07.date-utilities","i-lucide-calendar",{"title":102,"icon":103,"path":104,"stem":105,"children":106,"page":60},"Справочник","i-lucide-file-code","\u002Fru\u002Freference","ru\u002F3.reference",[107,112,116,121,126,131,136,141],{"title":108,"path":109,"stem":110,"icon":111},"Архитектура","\u002Fru\u002Freference\u002Farchitecture","ru\u002F3.reference\u002F01.architecture","i-lucide-boxes",{"title":113,"path":114,"stem":115,"icon":44},"Типы транзакций","\u002Fru\u002Freference\u002Ftransaction-types","ru\u002F3.reference\u002F02.transaction-types",{"title":117,"path":118,"stem":119,"icon":120},"Синхронизация","\u002Fru\u002Freference\u002Fsync","ru\u002F3.reference\u002F03.sync","i-lucide-refresh-cw",{"title":122,"path":123,"stem":124,"icon":125},"Офлайн-first","\u002Fru\u002Freference\u002Foffline-first","ru\u002F3.reference\u002F04.offline-first","i-lucide-list-ordered",{"title":127,"path":128,"stem":129,"icon":130},"Тех. решения","\u002Fru\u002Freference\u002Ftech-decisions","ru\u002F3.reference\u002F05.tech-decisions","i-lucide-lightbulb",{"title":132,"path":133,"stem":134,"icon":135},"Валидация","\u002Fru\u002Freference\u002Fvalidation-strategy","ru\u002F3.reference\u002F06.validation-strategy","i-lucide-shield-check",{"title":137,"path":138,"stem":139,"icon":140},"Что изменилось со времён Firebase","\u002Fru\u002Freference\u002Ffirebase-migration","ru\u002F3.reference\u002F07.firebase-migration","i-lucide-hamburger",{"title":142,"path":143,"stem":144,"icon":145},"Производительность","\u002Fru\u002Freference\u002Fperformance","ru\u002F3.reference\u002F08.performance","i-lucide-gauge",{"title":147,"icon":148,"path":149,"stem":150,"children":151,"page":60},"Премиум","i-lucide-star","\u002Fru\u002Fpremium","ru\u002F4.premium",[152,157,162],{"title":153,"path":154,"stem":155,"icon":156},"Обзор","\u002Fru\u002Fpremium\u002Foverview","ru\u002F4.premium\u002F01.overview","i-lucide-layers",{"title":158,"path":159,"stem":160,"icon":161},"Telegram-бот","\u002Fru\u002Fpremium\u002Ftelegram-bot","ru\u002F4.premium\u002F02.telegram-bot","i-lucide-send",{"title":163,"path":164,"stem":165,"icon":166},"AI Chat","\u002Fru\u002Fpremium\u002Fai-chat","ru\u002F4.premium\u002F03.ai-chat","i-lucide-sparkles",{"id":168,"title":82,"body":169,"description":686,"extension":687,"links":688,"meta":689,"navigation":690,"path":83,"seo":691,"stem":84,"__hash__":693},"docs_ru\u002Fru\u002F2.development\u002F04.migration.md",{"type":170,"value":171,"toc":679},"minimark",[172,189,203,208,330,334,345,440,517,522,657,660,664,675],[173,174,175,176,180,181,184,185,188],"p",{},"Бэкенд Финапки был мигрирован с ",[177,178,179],"strong",{},"Firebase Realtime Database"," на ",[177,182,183],{},"Supabase Postgres + PowerSync"," (offline-first синхронизация) с ",[177,186,187],{},"Supabase Auth (email\u002Fpassword)",". На этой странице описана и сама миграция, и скрипт, который импортирует Firebase-экспорт в Supabase.",[173,190,191,192,202],{},"Исходный код Firebase (v6) хранится на ветке ",[193,194,198],"a",{"href":195,"rel":196},"https:\u002F\u002Fgithub.com\u002Filkome\u002Ffinapp\u002Ftree\u002Ffirebase",[197],"nofollow",[199,200,201],"code",{},"firebase",".",[204,205,207],"h2",{"id":206},"что-изменилось","Что изменилось",[209,210,211,227],"table",{},[212,213,214],"thead",{},[215,216,217,221,224],"tr",{},[218,219,220],"th",{},"Сущность",[218,222,223],{},"Firebase (v6)",[218,225,226],{},"Текущее (Supabase + PowerSync)",[228,229,230,253,276,288,314],"tbody",{},[215,231,232,236,242],{},[233,234,235],"td",{},"Счета",[233,237,238,241],{},[199,239,240],{},"accounts"," со строковыми ключами",[233,243,244,245,248,249,252],{},"Таблица ",[199,246,247],{},"wallets",", ",[199,250,251],{},"text","-идентификаторы",[215,254,255,257,264],{},[233,256,31],{},[233,258,259,260,263],{},"Строковые ID, ",[199,261,262],{},"parentId"," строкой",[233,265,266,268,269,271,272,275],{},[199,267,251],{},"-идентификаторы, ",[199,270,262],{}," = id категории или ",[199,273,274],{},"null"," (корень)",[215,277,278,280,283],{},[233,279,36],{},[233,281,282],{},"Строковые ID, ссылки старыми ключами",[233,284,285,287],{},[199,286,251],{},"-идентификаторы, id из Firebase переиспользуются (ссылки сохраняются)",[215,289,290,292,305],{},[233,291,56],{},[233,293,294,297,298,248,301,304],{},[199,295,296],{},"settings.lang"," (",[199,299,300],{},"en",[199,302,303],{},"ru",")",[233,306,307,297,310,248,312,304],{},[199,308,309],{},"user_settings.locale",[199,311,300],{},[199,313,303],{},[215,315,316,319,325],{},[233,317,318],{},"Иконки",[233,320,321,322],{},"Формат ",[199,323,324],{},"mdi mdi-xxx",[233,326,321,327],{},[199,328,329],{},"mdi:xxx",[204,331,333],{"id":332},"инструмент-импорта","Инструмент импорта",[173,335,336,337,340,341,344],{},"Импортёр лежит в ",[199,338,339],{},"app\u002Fscripts\u002Fimport-firebase.mjs",". Он читает Firebase-экспорт (структура ",[199,342,343],{},"{ accounts, categories, trns, settings, user }",") и записывает его в проект Supabase для одного пользователя.",[346,347,353],"pre",{"className":348,"code":349,"filename":350,"language":351,"meta":352,"style":352},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","cd app\nSUPABASE_URL=https:\u002F\u002F\u003Cref>.supabase.co \\\nSUPABASE_SERVICE_ROLE_KEY=\u003Cservice_role> \\\nnode scripts\u002Fimport-firebase.mjs \u003Cbackup.json> --email \u003Cowner-email> --wipe\n","Terminal","bash","",[199,354,355,368,398,404],{"__ignoreMap":352},[356,357,360,364],"span",{"class":358,"line":359},"line",1,[356,361,363],{"class":362},"s2Zo4","cd",[356,365,367],{"class":366},"sfazB"," app\n",[356,369,371,375,379,382,385,388,391,394],{"class":358,"line":370},2,[356,372,374],{"class":373},"sTEyZ","SUPABASE_URL",[356,376,378],{"class":377},"sMK4o","=",[356,380,381],{"class":366},"https:\u002F\u002F",[356,383,384],{"class":377},"\u003C",[356,386,387],{"class":366},"ref",[356,389,390],{"class":377},">",[356,392,393],{"class":366},".supabase.co",[356,395,397],{"class":396},"sBMFI"," \\\n",[356,399,401],{"class":358,"line":400},3,[356,402,403],{"class":373},"SUPABASE_SERVICE_ROLE_KEY=\u003Cservice_role> \\\n",[356,405,407,410,413,416,419,422,424,427,429,432,435,437],{"class":358,"line":406},4,[356,408,409],{"class":373},"node ",[356,411,412],{"class":366},"scripts\u002Fimport-firebase.mjs",[356,414,415],{"class":377}," \u003C",[356,417,418],{"class":366},"backup.jso",[356,420,421],{"class":373},"n",[356,423,390],{"class":377},[356,425,426],{"class":366}," --email",[356,428,415],{"class":377},[356,430,431],{"class":366},"owner-emai",[356,433,434],{"class":373},"l",[356,436,390],{"class":377},[356,438,439],{"class":366}," --wipe\n",[441,442,443,466,487,511],"ul",{},[444,445,446,449,450,453,454,457,458,461,462,465],"li",{},[177,447,448],{},"Владелец"," - определяется по ",[199,451,452],{},"--email"," (по умолчанию ",[199,455,456],{},"user.email"," из экспорта) в существующий Supabase auth uid через GoTrue admin API, либо задаётся напрямую через ",[199,459,460],{},"--user-id",". Пользователь должен хотя бы раз войти (например, через Google), чтобы ",[199,463,464],{},"userId"," импортированных строк совпал с uid, под которым он логинится.",[444,467,468,471,472,474,475,478,479,482,483,486],{},[177,469,470],{},"Конфиг"," - читает ",[199,473,374],{}," + ",[199,476,477],{},"SUPABASE_SERVICE_ROLE_KEY"," из env; если их нет, откатывается на локальный стек через ",[199,480,481],{},"supabase status",". Запись идёт через PostgREST с ключом ",[199,484,485],{},"service_role",", поэтому обходит RLS и работает по HTTPS (ограничение «только IPv6» прямого подключения к Postgres здесь не применяется).",[444,488,489,494,495,498,499,498,502,504,505,510],{},[177,490,491],{},[199,492,493],{},"--wipe"," сначала удаляет существующие ",[199,496,497],{},"trns"," \u002F ",[199,500,501],{},"categories",[199,503,247],{}," пользователя; ",[177,506,507],{},[199,508,509],{},"--dry-run"," печатает преобразованные счётчики и примеры без записи.",[444,512,513,516],{},[177,514,515],{},"Идемпотентность"," - строки upsert'ятся по первичному ключу, а id из Firebase переиспользуются дословно (ниже), поэтому повторный запуск безопасен.",[518,519,521],"h3",{"id":520},"какие-преобразования-применяются","Какие преобразования применяются",[441,523,524,542,563,576,607],{},[444,525,526,529,530,532,533,498,535,498,538,541],{},[177,527,528],{},"Id переиспользуются, не ремапятся"," - id-колонки в Supabase это обычный ",[199,531,251],{}," без FK-ограничений, поэтому скрипт сохраняет исходные ключи Firebase как id; ссылки ",[199,534,262],{},[199,536,537],{},"walletId",[199,539,540],{},"categoryId"," остаются валидными без отдельного прохода ремаппинга.",[444,543,544,547,548,547,555,202],{},[177,545,546],{},"Accounts -> wallets","; ",[177,549,550,552,553],{},[199,551,296],{}," -> ",[199,554,309],{},[177,556,557,552,560],{},[199,558,559],{},"settings.baseCurrency",[199,561,562],{},"user_settings.baseCurrency",[444,564,565,567,568,552,570,572,573,202],{},[177,566,318],{}," - ",[199,569,324],{},[199,571,329],{},"; пустая -> ",[199,574,575],{},"mdi:category-outline",[444,577,578,581,582,498,585,498,588,591,592,595,596,498,599,602,603,606],{},[177,579,580],{},"Transfer \u002F adjustment"," - категории с именами ",[199,583,584],{},"Transfer",[199,586,587],{},"Перевод",[199,589,590],{},"Adjustment"," не являются настоящими категориями и пропускаются; их транзакции получают ",[199,593,594],{},"categoryId = 'transfer'"," (тип 2, с полями ",[199,597,598],{},"expense*",[199,600,601],{},"income*",") или ",[199,604,605],{},"categoryId = 'adjustment'"," (иначе).",[444,608,609,567,612,498,615,552,618,498,621,547,624,498,627,552,630,498,633,547,636,552,639,642,643,646,647,248,650,248,653,656],{},[177,610,611],{},"Легаси-поля",[199,613,614],{},"walletFromId",[199,616,617],{},"walletToId",[199,619,620],{},"expenseWalletId",[199,622,623],{},"incomeWalletId",[199,625,626],{},"amountFrom",[199,628,629],{},"amountTo",[199,631,632],{},"expenseAmount",[199,634,635],{},"incomeAmount",[199,637,638],{},"description",[199,640,641],{},"desc",". Поля только из Firebase, которым нет колонки (",[199,644,645],{},"order"," у категорий, ",[199,648,649],{},"showStat",[199,651,652],{},"budgets",[199,654,655],{},"groups","), отбрасываются.",[173,658,659],{},"В схеме нет FK-ограничений, поэтому порядок импорта не имеет значения. RLS-политики и правила синхронизации PowerSync обеспечивают изоляцию по пользователям; при следующем входе владельца PowerSync синхронизирует импортированные строки на его устройство.",[204,661,663],{"id":662},"текущий-бэкенд","Текущий бэкенд",[173,665,666,667,669,670,672,673,202],{},"Текущий бэкенд - ",[177,668,183],{},". Полное описание - в разделе ",[193,671,108],{"href":109},", настройка локальной среды - в разделе ",[193,674,16],{"href":68},[676,677,678],"style",{},"html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":352,"searchDepth":370,"depth":370,"links":680},[681,682,685],{"id":206,"depth":370,"text":207},{"id":332,"depth":370,"text":333,"children":683},[684],{"id":520,"depth":400,"text":521},{"id":662,"depth":370,"text":663},"Исторический обзор миграции бэкенда Финапки - с Firebase на Supabase + PowerSync.","md",null,{},{"icon":85},{"title":82,"description":692},"Историческая справка о миграции бэкенда Финапки - из Firebase Realtime Database на Supabase Postgres с PowerSync.","_zRmV9kW7HJCjRCtS_sq-Haxd5yI-fXSb6w3UL9uJQ8",[695,697],{"title":77,"path":78,"stem":79,"description":696,"icon":80,"children":-1},"Как Финапка работает офлайн как прогрессивное веб-приложение.",{"title":87,"path":88,"stem":89,"description":698,"icon":90,"children":-1},"Деплой Finapp и документации на Vercel из монорепы с Supabase и PowerSync в качестве бэкенда.",1782114340059]