# Единый протокол синхронизации Все клиенты (iOS, macOS, watchOS, Android, web) синхронизируются с `BalanceServer` через один и тот же HTTP(S) API и один и тот же набор инвариантов. Это сводка контракта; детальные описания на стороне сервера — [`BalanceServer/docs/API.md`](../BalanceServer/docs/API.md) и [`BalanceServer/docs/SYNC.md`](../BalanceServer/docs/SYNC.md). Клиентские реализации — [`Balance/docs/SYNC.md`](../Balance/docs/SYNC.md) и [`BalanceAndroid/docs/SYNC.md`](../BalanceAndroid/docs/SYNC.md). ## Эндпоинты | Метод | Путь | Назначение | | --- | --- | --- | | `GET` | `/health` | Health check | | `POST` | `/v1/auth/register` | Создание аккаунта | | `POST` | `/v1/auth/login` | Вход, выдача access/refresh токенов | | `POST` | `/v1/auth/refresh` | Ротация токенов | | `POST` | `/v1/auth/logout` | Отзыв refresh-токена | | `GET` | `/v1/me` | Текущий аккаунт | | `POST` | `/v1/sync` | Push и pull изменений | Нативные клиенты используют `Authorization: Bearer `. Web-клиент использует `HttpOnly`/`SameSite=Strict` cookie-сессию — это единственное отличие транспорта, сама sync-семантика идентична. ## Основные инварианты 1. **Cursor** — каждый клиент хранит курсор последнего успешно применённого изменения. Курсор сохраняется только после успешного применения соответствующего batch, не раньше. 2. **Sequence** — сервер поддерживает глобальный монотонный порядок изменений в change stream; порядок не должен уменьшаться и должен быть однозначным при конкурентных запросах. 3. **Push** — клиент отправляет свои изменения (`POST /v1/sync`); сервер валидирует, проверяет владение записью (ownership) и добавляет изменение в change stream. Повторный push (retry) не должен создавать вторую логическую запись из одного и того же client-change (идемпотентность). 4. **Pull** — клиент запрашивает изменения после своего курсора; сервер никогда не возвращает чужие данные (изоляция по аккаунту) и поддерживает постраничный pull с продолжением от нового курсора. 5. **Conflict resolution** — last-write-wins по `updatedAt` (`syncUpdatedAt` на Apple, `updatedAt` на Android/сервере). Сервер — единственный источник правды в этом сравнении, поэтому не должен произвольно перезаписывать `updatedAt` при каждом чтении. 6. **Удаления (tombstones)** — удаление сущности не является простым `DELETE`; оно создаёт запись изменения вида `{ "entity": ..., "id": ..., "deleted": true, "updatedAt": ... }`, которая проходит через тот же change stream, что и обычные изменения. На Apple это `SyncTombstone`/`modelContext.deleteForSync`, на Android — `deletion_journal` через DAO-методы `deleteTransaction` / `deleteCategory` / `deleteBudget`. 7. **Batch** — Apple-клиент отправляет батчи до 100 изменений и может делить крупные payload'ы; сервер не должен предполагать, что все изменения аккаунта приходят одним запросом. Android должен оставаться совместимым с этим же батчингом. 8. **Reset/full sync** — если клиент теряет курсор, сервер должен поддерживать предусмотренный API механизм полного pull или новый baseline-курсор. Неизвестный/просроченный курсор нельзя молча трактовать как актуальный. ## Синхронизируемые сущности - **Транзакция** (доход/расход, сумма, дата, заметка, snapshot категории); - **Пользовательская категория** (имя, иконка/emoji, цвет); - **Месячный бюджет** (категория, месяц, лимит); - **Tombstone / deletion journal** запись для удалений. Подробное пополевое сопоставление — [`docs/DATA_MODEL.md`](DATA_MODEL.md). ## Что нельзя менять без координации Любое из следующих изменений — breaking change для всех клиентов одновременно, даже если каждый подпроект по отдельности компилируется и проходит собственные тесты: - имена/типы JSON-полей в payload'ах `/v1/sync` и auth-эндпоинтов; - HTTP-методы или пути эндпоинтов; - семантика курсора/sequence; - правило разрешения конфликтов (last-write-wins по `updatedAt`); - формат tombstone-записи. При таком изменении обнови одновременно: `BalanceServer` (API + хендлеры + `docs/API.md`/`docs/SYNC.md`), `Balance/Shared/ServerSync.swift` (+ `Balance/docs/SYNC.md`), `BalanceAndroid/.../sync/*` (+ `BalanceAndroid/docs/SYNC.md`) и этот файл.