[{"data":1,"prerenderedAt":724},["ShallowReactive",2],{"navigation_docs_en":3,"-en-development-migration":191,"-en-development-migration-surround":719},[4,61,127,171],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":60},"Guide","i-lucide-book-open","\u002Fen\u002Fguide","en\u002F1.guide",[10,15,20,25,30,35,40,45,50,55],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fguide\u002Fintroduction","en\u002F1.guide\u002F01.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Install the App","\u002Fen\u002Fguide\u002Finstallation","en\u002F1.guide\u002F02.installation","i-lucide-smartphone",{"title":21,"path":22,"stem":23,"icon":24},"Authentication","\u002Fen\u002Fguide\u002Fauth","en\u002F1.guide\u002F03.auth","i-lucide-lock",{"title":26,"path":27,"stem":28,"icon":29},"Wallets","\u002Fen\u002Fguide\u002Fwallets","en\u002F1.guide\u002F04.wallets","i-lucide-wallet",{"title":31,"path":32,"stem":33,"icon":34},"Categories","\u002Fen\u002Fguide\u002Fcategories","en\u002F1.guide\u002F05.categories","i-lucide-tags",{"title":36,"path":37,"stem":38,"icon":39},"Transactions","\u002Fen\u002Fguide\u002Ftransactions","en\u002F1.guide\u002F06.transactions","i-lucide-receipt",{"title":41,"path":42,"stem":43,"icon":44},"Transfers","\u002Fen\u002Fguide\u002Ftransfers","en\u002F1.guide\u002F07.transfers","i-lucide-arrow-left-right",{"title":46,"path":47,"stem":48,"icon":49},"Statistics","\u002Fen\u002Fguide\u002Fstatistics","en\u002F1.guide\u002F08.statistics","i-lucide-bar-chart-3",{"title":51,"path":52,"stem":53,"icon":54},"Theme","\u002Fen\u002Fguide\u002Ftheme","en\u002F1.guide\u002F09.theme","i-lucide-palette",{"title":56,"path":57,"stem":58,"icon":59},"Settings","\u002Fen\u002Fguide\u002Fsettings","en\u002F1.guide\u002F10.settings","i-lucide-settings",false,{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":60},"Development","i-lucide-code","\u002Fen\u002Fdevelopment","en\u002F2.development",[67,72,77,82,87,92,97,102,122],{"title":68,"path":69,"stem":70,"icon":71},"Installation","\u002Fen\u002Fdevelopment\u002Finstallation","en\u002F2.development\u002F01.installation","i-lucide-download",{"title":73,"path":74,"stem":75,"icon":76},"Codebase Graph","\u002Fen\u002Fdevelopment\u002Funderstand-anything","en\u002F2.development\u002F02.understand-anything","i-lucide-network",{"title":78,"path":79,"stem":80,"icon":81},"Offline & PWA","\u002Fen\u002Fdevelopment\u002Foffline","en\u002F2.development\u002F03.offline","i-lucide-wifi-off",{"title":83,"path":84,"stem":85,"icon":86},"Data Migration History","\u002Fen\u002Fdevelopment\u002Fmigration","en\u002F2.development\u002F04.migration","i-lucide-database",{"title":88,"path":89,"stem":90,"icon":91},"Deployment","\u002Fen\u002Fdevelopment\u002Fdeployment","en\u002F2.development\u002F05.deployment","i-lucide-rocket",{"title":93,"path":94,"stem":95,"icon":96},"Testing","\u002Fen\u002Fdevelopment\u002Ftesting","en\u002F2.development\u002F06.testing","i-lucide-flask-conical",{"title":98,"path":99,"stem":100,"icon":101},"Date Utilities","\u002Fen\u002Fdevelopment\u002Fdate-utilities","en\u002F2.development\u002F07.date-utilities","i-lucide-calendar",{"title":103,"path":104,"stem":105,"children":106,"page":60},"Ai Workflow","\u002Fen\u002Fdevelopment\u002Fai-workflow","en\u002F2.development\u002F08.ai-workflow",[107,112,117],{"title":108,"path":109,"stem":110,"icon":111},"Overview","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Foverview","en\u002F2.development\u002F08.ai-workflow\u002F01.overview","i-lucide-bot",{"title":113,"path":114,"stem":115,"icon":116},"Agents","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Fagents","en\u002F2.development\u002F08.ai-workflow\u002F02.agents","i-lucide-users",{"title":118,"path":119,"stem":120,"icon":121},"Skills","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Fskills","en\u002F2.development\u002F08.ai-workflow\u002F03.skills","i-lucide-lightbulb",{"title":123,"path":124,"stem":125,"icon":126},"Troubleshooting","\u002Fen\u002Fdevelopment\u002Ftroubleshooting","en\u002F2.development\u002F09.troubleshooting","i-lucide-life-buoy",{"title":128,"icon":129,"path":130,"stem":131,"children":132,"page":60},"Reference","i-lucide-file-code","\u002Fen\u002Freference","en\u002F3.reference",[133,138,142,147,152,156,161,166],{"title":134,"path":135,"stem":136,"icon":137},"Architecture","\u002Fen\u002Freference\u002Farchitecture","en\u002F3.reference\u002F01.architecture","i-lucide-boxes",{"title":139,"path":140,"stem":141,"icon":44},"Transaction Types","\u002Fen\u002Freference\u002Ftransaction-types","en\u002F3.reference\u002F02.transaction-types",{"title":143,"path":144,"stem":145,"icon":146},"Sync","\u002Fen\u002Freference\u002Fsync","en\u002F3.reference\u002F03.sync","i-lucide-refresh-cw",{"title":148,"path":149,"stem":150,"icon":151},"Offline-first","\u002Fen\u002Freference\u002Foffline-first","en\u002F3.reference\u002F04.offline-first","i-lucide-list-ordered",{"title":153,"path":154,"stem":155,"icon":121},"Technical Decisions","\u002Fen\u002Freference\u002Ftech-decisions","en\u002F3.reference\u002F05.tech-decisions",{"title":157,"path":158,"stem":159,"icon":160},"Validation Strategy","\u002Fen\u002Freference\u002Fvalidation-strategy","en\u002F3.reference\u002F06.validation-strategy","i-lucide-shield-check",{"title":162,"path":163,"stem":164,"icon":165},"What Changed Since Firebase","\u002Fen\u002Freference\u002Ffirebase-migration","en\u002F3.reference\u002F07.firebase-migration","i-lucide-hamburger",{"title":167,"path":168,"stem":169,"icon":170},"Performance","\u002Fen\u002Freference\u002Fperformance","en\u002F3.reference\u002F08.performance","i-lucide-gauge",{"title":172,"icon":173,"path":174,"stem":175,"children":176,"page":60},"Premium","i-lucide-star","\u002Fen\u002Fpremium","en\u002F4.premium",[177,181,186],{"title":108,"path":178,"stem":179,"icon":180},"\u002Fen\u002Fpremium\u002Foverview","en\u002F4.premium\u002F01.overview","i-lucide-layers",{"title":182,"path":183,"stem":184,"icon":185},"Telegram Bot","\u002Fen\u002Fpremium\u002Ftelegram-bot","en\u002F4.premium\u002F02.telegram-bot","i-lucide-send",{"title":187,"path":188,"stem":189,"icon":190},"AI Chat","\u002Fen\u002Fpremium\u002Fai-chat","en\u002F4.premium\u002F03.ai-chat","i-lucide-sparkles",{"id":192,"title":83,"body":193,"description":711,"extension":712,"links":713,"meta":714,"navigation":715,"path":84,"seo":716,"stem":85,"__hash__":718},"docs_en\u002Fen\u002F2.development\u002F04.migration.md",{"type":194,"value":195,"toc":704},"minimark",[196,213,227,232,354,358,369,464,541,546,681,684,688,700],[197,198,199,200,204,205,208,209,212],"p",{},"Finapp's backend was migrated from ",[201,202,203],"strong",{},"Firebase Realtime Database"," to ",[201,206,207],{},"Supabase Postgres + PowerSync"," (offline-first sync) with ",[201,210,211],{},"Supabase Auth (email\u002Fpassword)",". This page documents that migration and the script that imports a Firebase JSON export into Supabase.",[197,214,215,216,226],{},"The legacy Firebase (v6) source code lives on the ",[217,218,222],"a",{"href":219,"rel":220},"https:\u002F\u002Fgithub.com\u002Filkome\u002Ffinapp\u002Ftree\u002Ffirebase",[221],"nofollow",[223,224,225],"code",{},"firebase"," branch.",[228,229,231],"h2",{"id":230},"what-changed","What changed",[233,234,235,251],"table",{},[236,237,238],"thead",{},[239,240,241,245,248],"tr",{},[242,243,244],"th",{},"Entity",[242,246,247],{},"Firebase (v6)",[242,249,250],{},"Current (Supabase + PowerSync)",[252,253,254,276,299,311,338],"tbody",{},[239,255,256,260,266],{},[257,258,259],"td",{},"Accounts",[257,261,262,265],{},[223,263,264],{},"accounts"," with string keys",[257,267,268,271,272,275],{},[223,269,270],{},"wallets"," table, ",[223,273,274],{},"text"," ids",[239,277,278,280,287],{},[257,279,31],{},[257,281,282,283,286],{},"String IDs, ",[223,284,285],{},"parentId"," as string",[257,288,289,291,292,294,295,298],{},[223,290,274],{}," ids, ",[223,293,285],{}," = a category id or ",[223,296,297],{},"null"," (root)",[239,300,301,303,306],{},[257,302,36],{},[257,304,305],{},"String IDs, references by old keys",[257,307,308,310],{},[223,309,274],{}," ids, Firebase ids reused (references preserved)",[239,312,313,315,329],{},[257,314,56],{},[257,316,317,320,321,324,325,328],{},[223,318,319],{},"settings.lang"," (",[223,322,323],{},"en",", ",[223,326,327],{},"ru",")",[257,330,331,320,334,324,336,328],{},[223,332,333],{},"user_settings.locale",[223,335,323],{},[223,337,327],{},[239,339,340,343,349],{},[257,341,342],{},"Icons",[257,344,345,348],{},[223,346,347],{},"mdi mdi-xxx"," format",[257,350,351,348],{},[223,352,353],{},"mdi:xxx",[228,355,357],{"id":356},"import-tooling","Import tooling",[197,359,360,361,364,365,368],{},"The importer lives at ",[223,362,363],{},"app\u002Fscripts\u002Fimport-firebase.mjs",". It reads a Firebase JSON export (the ",[223,366,367],{},"{ accounts, categories, trns, settings, user }"," shape) and writes it into a Supabase project for one user.",[370,371,377],"pre",{"className":372,"code":373,"filename":374,"language":375,"meta":376,"style":376},"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","",[223,378,379,392,422,428],{"__ignoreMap":376},[380,381,384,388],"span",{"class":382,"line":383},"line",1,[380,385,387],{"class":386},"s2Zo4","cd",[380,389,391],{"class":390},"sfazB"," app\n",[380,393,395,399,403,406,409,412,415,418],{"class":382,"line":394},2,[380,396,398],{"class":397},"sTEyZ","SUPABASE_URL",[380,400,402],{"class":401},"sMK4o","=",[380,404,405],{"class":390},"https:\u002F\u002F",[380,407,408],{"class":401},"\u003C",[380,410,411],{"class":390},"ref",[380,413,414],{"class":401},">",[380,416,417],{"class":390},".supabase.co",[380,419,421],{"class":420},"sBMFI"," \\\n",[380,423,425],{"class":382,"line":424},3,[380,426,427],{"class":397},"SUPABASE_SERVICE_ROLE_KEY=\u003Cservice_role> \\\n",[380,429,431,434,437,440,443,446,448,451,453,456,459,461],{"class":382,"line":430},4,[380,432,433],{"class":397},"node ",[380,435,436],{"class":390},"scripts\u002Fimport-firebase.mjs",[380,438,439],{"class":401}," \u003C",[380,441,442],{"class":390},"backup.jso",[380,444,445],{"class":397},"n",[380,447,414],{"class":401},[380,449,450],{"class":390}," --email",[380,452,439],{"class":401},[380,454,455],{"class":390},"owner-emai",[380,457,458],{"class":397},"l",[380,460,414],{"class":401},[380,462,463],{"class":390}," --wipe\n",[465,466,467,490,511,535],"ul",{},[468,469,470,473,474,477,478,481,482,485,486,489],"li",{},[201,471,472],{},"Owner"," - resolved from ",[223,475,476],{},"--email"," (defaults to the export's ",[223,479,480],{},"user.email",") to an existing Supabase auth uid via the GoTrue admin API, or passed directly with ",[223,483,484],{},"--user-id",". The user must have signed in at least once (e.g. via Google) so the imported rows' ",[223,487,488],{},"userId"," matches the uid they log in as.",[468,491,492,495,496,498,499,502,503,506,507,510],{},[201,493,494],{},"Config"," - reads ",[223,497,398],{}," + ",[223,500,501],{},"SUPABASE_SERVICE_ROLE_KEY"," from the env; with neither set it falls back to the local stack via ",[223,504,505],{},"supabase status",". Writes go through PostgREST with the ",[223,508,509],{},"service_role"," key, so they bypass RLS and run over HTTPS (the direct Postgres connection's IPv6-only limitation does not apply).",[468,512,513,518,519,522,523,522,526,528,529,534],{},[201,514,515],{},[223,516,517],{},"--wipe"," deletes the user's existing ",[223,520,521],{},"trns"," \u002F ",[223,524,525],{},"categories",[223,527,270],{}," first; ",[201,530,531],{},[223,532,533],{},"--dry-run"," prints the transformed counts and samples without writing.",[468,536,537,540],{},[201,538,539],{},"Idempotent"," - rows upsert on their primary key and Firebase ids are reused verbatim (below), so re-running is safe.",[542,543,545],"h3",{"id":544},"transforms-it-applies","Transforms it applies",[465,547,548,566,588,601,632],{},[468,549,550,553,554,556,557,522,559,522,562,565],{},[201,551,552],{},"Ids reused, not remapped"," - Supabase id columns are plain ",[223,555,274],{}," with no FK constraints, so the script keeps the original Firebase keys as ids; ",[223,558,285],{},[223,560,561],{},"walletId",[223,563,564],{},"categoryId"," references stay valid with no remap pass.",[468,567,568,571,572,571,579,587],{},[201,569,570],{},"Accounts -> wallets","; ",[201,573,574,576,577],{},[223,575,319],{}," -> ",[223,578,333],{},[201,580,581,576,584],{},[223,582,583],{},"settings.baseCurrency",[223,585,586],{},"user_settings.baseCurrency",".",[468,589,590,592,593,576,595,597,598,587],{},[201,591,342],{}," - ",[223,594,347],{},[223,596,353],{},"; empty -> ",[223,599,600],{},"mdi:category-outline",[468,602,603,606,607,522,610,522,613,616,617,620,621,522,624,627,628,631],{},[201,604,605],{},"Transfer \u002F adjustment"," - categories named ",[223,608,609],{},"Transfer",[223,611,612],{},"Перевод",[223,614,615],{},"Adjustment"," are not real categories and are skipped; their transactions become ",[223,618,619],{},"categoryId = 'transfer'"," (type 2, with the ",[223,622,623],{},"expense*",[223,625,626],{},"income*"," fields) or ",[223,629,630],{},"categoryId = 'adjustment'"," (otherwise).",[468,633,634,592,637,522,640,576,643,522,646,571,649,522,652,576,655,522,658,571,661,576,664,667,668,324,671,324,674,324,677,680],{},[201,635,636],{},"Legacy fields",[223,638,639],{},"walletFromId",[223,641,642],{},"walletToId",[223,644,645],{},"expenseWalletId",[223,647,648],{},"incomeWalletId",[223,650,651],{},"amountFrom",[223,653,654],{},"amountTo",[223,656,657],{},"expenseAmount",[223,659,660],{},"incomeAmount",[223,662,663],{},"description",[223,665,666],{},"desc",". Firebase-only fields with no column (category ",[223,669,670],{},"order",[223,672,673],{},"showStat",[223,675,676],{},"budgets",[223,678,679],{},"groups",") are dropped.",[197,682,683],{},"No FK constraints exist in the schema, so import order does not matter. RLS policies and PowerSync sync rules ensure per-user isolation; on the owner's next login PowerSync syncs the imported rows to their device.",[228,685,687],{"id":686},"current-backend","Current backend",[197,689,690,691,693,694,696,697,699],{},"The current backend is ",[201,692,207],{},". See ",[217,695,134],{"href":135}," for a full description and ",[217,698,68],{"href":69}," for local setup.",[701,702,703],"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":376,"searchDepth":394,"depth":394,"links":705},[706,707,710],{"id":230,"depth":394,"text":231},{"id":356,"depth":394,"text":357,"children":708},[709],{"id":544,"depth":424,"text":545},{"id":686,"depth":394,"text":687},"Historical overview of the Finapp backend migration - from Firebase to Supabase + PowerSync.","md",null,{},{"icon":86},{"title":83,"description":717},"Historical note on the Finapp backend migration - from Firebase Realtime Database to Supabase Postgres with PowerSync.","3cbaMPf0sdglaBIufgaPYGa64_5I4AVu-fpZ4NC9Lys",[720,722],{"title":78,"path":79,"stem":80,"description":721,"icon":81,"children":-1},"Working offline and PWA installation.",{"title":88,"path":89,"stem":90,"description":723,"icon":91,"children":-1},"Deploy Finapp and docs to Vercel from a monorepo, with Supabase and PowerSync as the backend.",1782114343383]