[{"data":1,"prerenderedAt":830},["ShallowReactive",2],{"navigation_docs_ru":3,"-ru-reference-sync":167,"-ru-reference-sync-surround":825},[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":117,"body":169,"description":817,"extension":818,"links":819,"meta":820,"navigation":821,"path":118,"seo":822,"stem":119,"__hash__":824},"docs_ru\u002Fru\u002F3.reference\u002F03.sync.md",{"type":170,"value":171,"toc":805},"minimark",[172,176,181,192,195,199,211,220,253,267,276,282,286,296,330,333,337,355,377,381,391,447,454,458,514,524,544,548,551,572,576,603,607,784,788,801],[173,174,175],"p",{},"PowerSync - это слой синхронизации между клиентом (локальная SQLite через wa-sqlite \u002F IndexedDB) и сервером (Supabase Postgres). Локальная SQLite является единственным источником истины для UI - хранилища читают только из SQLite, никогда напрямую из сети.",[177,178,180],"h2",{"id":179},"как-это-работает","Как это работает",[182,183,188],"pre",{"className":184,"code":186,"language":187},[185],"language-text","Supabase Postgres (сервер)\n  ↕  логическая репликация (powersync_role, публикация powersync)\nСервис PowerSync (self-hosted, :8080)\n  ↕  WebSocket sync stream (Sync Streams издание 3, per-user + глобальный)\nЛокальная SQLite (клиент, IDBBatchAtomicVFS \u002F IndexedDB)\n  ↕  подписки watchTable\nPinia-хранилища (UI)\n","text",[189,190,186],"code",{"__ignoreMap":191},"",[173,193,194],{},"При подключении PowerSync воспроизводит серверное состояние в локальную SQLite. После этого каждое серверное изменение передаётся по открытому WebSocket. Локальные записи идут в SQLite немедленно (оптимистично) и ставятся в очередь на загрузку.",[177,196,198],{"id":197},"потоки-синхронизации","Потоки синхронизации",[173,200,201,202,205,206,210],{},"Правила синхронизации определены в ",[189,203,204],{},"app\u002Fpowersync\u002Fconfig\u002Fsync-config.yaml"," с использованием ",[207,208,209],"strong",{},"Sync Streams издания 3",".",[173,212,213],{},[207,214,215,216,219],{},"Поток на пользователя (",[189,217,218],{},"user_data",", auto_subscribe):",[182,221,225],{"className":222,"code":223,"language":224,"meta":191,"style":191},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","SELECT * FROM categories    WHERE \"userId\" = auth.user_id()\nSELECT * FROM wallets       WHERE \"userId\" = auth.user_id()\nSELECT * FROM trns          WHERE \"userId\" = auth.user_id()\nSELECT * FROM user_settings WHERE \"userId\" = auth.user_id()\n","sql",[189,226,227,235,241,247],{"__ignoreMap":191},[228,229,232],"span",{"class":230,"line":231},"line",1,[228,233,234],{},"SELECT * FROM categories    WHERE \"userId\" = auth.user_id()\n",[228,236,238],{"class":230,"line":237},2,[228,239,240],{},"SELECT * FROM wallets       WHERE \"userId\" = auth.user_id()\n",[228,242,244],{"class":230,"line":243},3,[228,245,246],{},"SELECT * FROM trns          WHERE \"userId\" = auth.user_id()\n",[228,248,250],{"class":230,"line":249},4,[228,251,252],{},"SELECT * FROM user_settings WHERE \"userId\" = auth.user_id()\n",[173,254,255,262,263,266],{},[207,256,257,258,261],{},"Глобальный поток (",[189,259,260],{},"rates","):"," все строки доступны каждому авторизованному пользователю; строки пишет серверная edge-функция ",[189,264,265],{},"fetch-rates",", не клиент.",[182,268,270],{"className":222,"code":269,"language":224,"meta":191,"style":191},"SELECT * FROM rates\n",[189,271,272],{"__ignoreMap":191},[228,273,274],{"class":230,"line":231},[228,275,269],{},[173,277,278,281],{},[189,279,280],{},"auth.user_id()"," разрешается в UUID пользователя Supabase из проверенного JWT. Сервис PowerSync проверяет JWT Supabase (ES256) через JWKS-endpoint, поэтому дополнительная логика токенов на клиенте не требуется.",[177,283,285],{"id":284},"watchtable-паттерн-единственной-подписки","watchTable - паттерн единственной подписки",[173,287,288,291,292,295],{},[189,289,290],{},"watchTable(sql, params, onRows, throttleMs?)"," в ",[189,293,294],{},"app\u002Fservices\u002Fpowersync\u002Fdb.ts",":",[297,298,299,307,313,319],"ul",{},[300,301,302,303,306],"li",{},"Выполняет SQL-запрос немедленно и вызывает ",[189,304,305],{},"onRows"," с текущими локальными строками.",[300,308,309,310,312],{},"Подписывается на события изменения таблицы. При изменении любой строки в таблице (локальная запись или входящая синхронизация) вызывает ",[189,311,305],{}," со свежими строками.",[300,314,315,318],{},[189,316,317],{},"throttleMs"," объединяет быстрые всплески изменений (trailing edge). По умолчанию: 30мс. Транзакции используют 120мс для обработки большого потока строк при начальной синхронизации без лишних перерисовок UI.",[300,320,321,322,325,326,329],{},"Возвращает ",[189,323,324],{},"AbortController"," - вызов ",[189,327,328],{},".abort()"," отменяет подписку (хранилища делают это при разрушении).",[173,331,332],{},"Этот единственный механизм заменяет разделение на «инициализацию + подписку на реалтайм». Отдельного шага «загрузить из кеша, затем подписаться» не существует.",[177,334,336],{"id":335},"подавление-эха-транзакции","Подавление эха (транзакции)",[173,338,339,340,343,344,347,348,351,352,261],{},"Когда хранилище делает оптимистичную запись, а затем ",[189,341,342],{},"watchTable"," срабатывает для этой же записи, UI мог бы мерцать. ",[189,345,346],{},"useTrnsStore"," избегает этого с помощью ",[189,349,350],{},"reconcileTrns(prev, rows)"," (",[189,353,354],{},"app\u002Fapp\u002Fcomponents\u002Ftrns\u002Freconcile.ts",[297,356,357,363,370],{},[300,358,359,360,210],{},"Сравнивает входящие строки с предыдущим состоянием по ",[189,361,362],{},"updatedAt",[300,364,365,366,369],{},"Если ничего не изменилось - возвращает тот же ",[189,367,368],{},"prev","-ref (Vue не видит изменений, перерисовки нет).",[300,371,372,373,376],{},"Для изменённых строк запускает ",[189,374,375],{},"rowToTrn()"," только для дельты - неизменённые объекты строк переиспользуются.",[177,378,380],{"id":379},"очередь-загрузки","Очередь загрузки",[173,382,383,384,351,387,390],{},"Локальные записи автоматически ставятся в очередь PowerSync. ",[189,385,386],{},"SupabaseConnector.uploadData()",[189,388,389],{},"app\u002Fservices\u002Fpowersync\u002Fconnector.ts",") опустошает очередь:",[392,393,394,407],"table",{},[395,396,397],"thead",{},[398,399,400,404],"tr",{},[401,402,403],"th",{},"Операция PowerSync",[401,405,406],{},"Вызов Supabase",[408,409,410,423,435],"tbody",{},[398,411,412,418],{},[413,414,415],"td",{},[189,416,417],{},"PUT",[413,419,420],{},[189,421,422],{},"supabase.from(table).upsert(row)",[398,424,425,430],{},[413,426,427],{},[189,428,429],{},"PATCH",[413,431,432],{},[189,433,434],{},"supabase.from(table).update(data).eq('id', id)",[398,436,437,442],{},[413,438,439],{},[189,440,441],{},"DELETE",[413,443,444],{},[189,445,446],{},"supabase.from(table).delete().eq('id', id)",[173,448,449,450,453],{},"Коннектор работает от имени авторизованного пользователя Supabase, поэтому RLS-политики применяются при каждой загрузке. Это слой безопасности пути записи - путь чтения использует ",[189,451,452],{},"powersync_role"," (BYPASSRLS) для репликации, разграничение по пользователям обеспечивается правилами синхронизации.",[177,455,457],{"id":456},"фатальные-и-повторяемые-ошибки-загрузки","Фатальные и повторяемые ошибки загрузки",[392,459,460,473],{},[395,461,462],{},[398,463,464,467,470],{},[401,465,466],{},"Класс ошибки",[401,468,469],{},"Коды Postgres",[401,471,472],{},"Поведение",[408,474,475,499],{},[398,476,477,480,492],{},[413,478,479],{},"Фатальные (данные \u002F ограничение \u002F RLS)",[413,481,482,485,486,485,489],{},[189,483,484],{},"22xxx",", ",[189,487,488],{},"23xxx",[189,490,491],{},"42501",[413,493,494,495,498],{},"Транзакция ",[207,496,497],{},"отбрасывается"," - очередь разблокируется; отброшенные операции авто-согласуются (см. ниже)",[398,500,501,504,507],{},[413,502,503],{},"Повторяемые (сеть, таймаут)",[413,505,506],{},"всё остальное",[413,508,509,510,513],{},"Ошибка ",[207,511,512],{},"пробрасывается"," - PowerSync повторяет попытку с backoff",[173,515,516,517,351,520,523],{},"Отброшенные операции классифицируются ",[189,518,519],{},"planDivergence()",[189,521,522],{},"app\u002Fservices\u002Fpowersync\u002FuploadReconcile.ts",") и согласуются автоматически:",[297,525,526,535],{},[300,527,528,531,532,210],{},[207,529,530],{},"Отклонённые INSERT"," - локальные строки удаляются (id генерируются на клиенте, сервер их так и не принял), с каскадом на локальные транзакции, ссылающиеся на отменённый кошелёк\u002Fкатегорию. Toast: ",[189,533,534],{},"sync.errors.uploadReverted",[300,536,537,540,541,210],{},[207,538,539],{},"Отклонённые UPDATE\u002FDELETE"," - на сервере осталась предыдущая версия, которую PowerSync не может повторно вытянуть для отдельной строки, поэтому пользователю предлагается полная повторная синхронизация (очистка + повторная загрузка). Toast: ",[189,542,543],{},"sync.errors.uploadDiverged",[177,545,547],{"id":546},"обработка-удалений","Обработка удалений",[173,549,550],{},"PowerSync синхронизирует жёсткие удаления нативно - когда строка удаляется на сервере, сервис PowerSync передаёт событие удаления клиенту и SQLite удаляет строку. Никакого сравнения хешей или полного обновления не требуется.",[173,552,553,556,557,560,561,564,565,567,568,571],{},[207,554,555],{},"Каскадные удаления (на клиенте):"," ",[189,558,559],{},"deleteWallet"," и ",[189,562,563],{},"deleteCategory"," также удаляют транзакции сущности из локальной SQLite до возврата. Без этого подписка ",[189,566,342],{}," на ",[189,569,570],{},"trns"," вернула бы осиротевшие строки в UI после удаления родительской сущности.",[177,573,575],{"id":574},"первая-синхронизация","Первая синхронизация",[173,577,578,351,581,583,584,587,588,591,592,595,596,291,599,602],{},[189,579,580],{},"waitForFirstSync(timeoutMs=30000)",[189,582,294],{},") разрешается в ",[189,585,586],{},"true"," после полной передачи начального серверного состояния в локальную SQLite и в ",[189,589,590],{},"false"," по таймауту. Загрузка ",[207,593,594],{},"не"," блокируется на нём - кешированные\u002Fлокальные данные отрисовываются сразу, а первая синхронизация завершается в фоне (",[189,597,598],{},"awaitInitialSync",[189,600,601],{},"useInitApp.ts","). Его результат лишь определяет показ экранов онбординга\u002Fошибки, поэтому при свежем входе онбординг никогда не мелькает, пока данные ещё приходят.",[177,604,606],{"id":605},"ключевые-файлы","Ключевые файлы",[392,608,609,619],{},[395,610,611],{},[398,612,613,616],{},[401,614,615],{},"Файл",[401,617,618],{},"Роль",[408,620,621,641,660,670,695,713,729,740,750,759,769],{},[398,622,623,627],{},[413,624,625],{},[189,626,294],{},[413,628,629,630,485,633,485,636,485,638],{},"Синглтон ",[189,631,632],{},"PowerSyncDatabase",[189,634,635],{},"connectPowerSync",[189,637,342],{},[189,639,640],{},"waitForFirstSync",[398,642,643,647],{},[413,644,645],{},[189,646,389],{},[413,648,649,652,653,656,657],{},[189,650,651],{},"SupabaseConnector"," - ",[189,654,655],{},"fetchCredentials"," + ",[189,658,659],{},"uploadData",[398,661,662,667],{},[413,663,664],{},[189,665,666],{},"app\u002Fservices\u002Fpowersync\u002FAppSchema.ts",[413,668,669],{},"SQLite-схема клиента",[398,671,672,677],{},[413,673,674],{},[189,675,676],{},"app\u002Fservices\u002Fpowersync\u002Ftransforms.ts",[413,678,679,485,682,485,685,485,688,485,691,694],{},[189,680,681],{},"rowToTrn",[189,683,684],{},"rowToWallet",[189,686,687],{},"rowToCategory",[189,689,690],{},"rowToRates",[189,692,693],{},"trnToRow"," и др.",[398,696,697,702],{},[413,698,699],{},[189,700,701],{},"app\u002Fservices\u002Fpowersync\u002Fmutations.ts",[413,703,704,485,707,485,710],{},[189,705,706],{},"upsertRow",[189,708,709],{},"deleteRow",[189,711,712],{},"upsertRows",[398,714,715,720],{},[413,716,717],{},[189,718,719],{},"app\u002Fapp\u002Fcomponents\u002Ftrns\u002FuseTrnsStore.ts",[413,721,722,723,725,726],{},"Подписка ",[189,724,342],{}," + подавление эха ",[189,727,728],{},"reconcileTrns",[398,730,731,735],{},[413,732,733],{},[189,734,354],{},[413,736,737,739],{},[189,738,728],{}," - дедупликация и переиспользование неизменённых объектов строк",[398,741,742,747],{},[413,743,744],{},[189,745,746],{},"app\u002Fapp\u002Fplugins\u002Fpowersync.client.ts",[413,748,749],{},"Подключает PowerSync при входе; ставит на паузу (сохраняя локальные данные и очередь) при непроизвольной потере сессии; явный выход стирает отдельно",[398,751,752,756],{},[413,753,754],{},[189,755,204],{},[413,757,758],{},"Правила Sync Streams (per-user + глобальные rates)",[398,760,761,766],{},[413,762,763],{},[189,764,765],{},"app\u002Fpowersync\u002Fconfig\u002Fservice.yaml",[413,767,768],{},"Конфиг сервиса PowerSync (репликация, JWT, порт 8080)",[398,770,771,776],{},[413,772,773],{},[189,774,775],{},"app\u002Fsupabase\u002Fpowersync_setup.sql",[413,777,778,780,781],{},[189,779,452],{}," + публикация ",[189,782,783],{},"powersync",[177,785,787],{"id":786},"следующие-шаги","Следующие шаги",[297,789,790,796],{},[300,791,792,795],{},[793,794,122],"a",{"href":123}," - офлайн-записи, очередь загрузки и обработка фатальных ошибок",[300,797,798,800],{},[793,799,108],{"href":109}," - инициализация приложения и паттерн хранилищ",[802,803,804],"style",{},"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":191,"searchDepth":237,"depth":237,"links":806},[807,808,809,810,811,812,813,814,815,816],{"id":179,"depth":237,"text":180},{"id":197,"depth":237,"text":198},{"id":284,"depth":237,"text":285},{"id":335,"depth":237,"text":336},{"id":379,"depth":237,"text":380},{"id":456,"depth":237,"text":457},{"id":546,"depth":237,"text":547},{"id":574,"depth":237,"text":575},{"id":605,"depth":237,"text":606},{"id":786,"depth":237,"text":787},"Как PowerSync синхронизирует локальную SQLite с Supabase Postgres.","md",null,{},{"icon":120},{"title":117,"description":823},"Как работает синхронизация данных в Финапке через PowerSync - локальная SQLite как источник истины, потоки синхронизации, очередь загрузки, объединение изменений и подавление эха.","dlx0dloyNpvtmFUUNDMiGhd66iOydwQdqmI-1ZsxeHs",[826,828],{"title":113,"path":114,"stem":115,"description":827,"icon":44,"children":-1},"Типы транзакций, логика корректировок, статистика.",{"title":122,"path":123,"stem":124,"description":829,"icon":125,"children":-1},"Как PowerSync обеспечивает офлайн-записи, очередь загрузки и вывод ошибок.",1782114341002]