Use Query cancellation and native mutation results instead of HTTP registries, access epochs and copied scope state. Keep local auth ordering and mutation identity fences. Allow staged generated files to remain excluded from ESLint. Backend contract: WealthLedger/backend@d698ed9 BREAKING CHANGE: drop investment stateVersion/progress/resources from the client contract and use data/metadata readiness instead of rewritten query status. Cross-tab auth may require re-login.
99 lines
7.3 KiB
Markdown
99 lines
7.3 KiB
Markdown
# Frontend data
|
||
|
||
## Выбор owner
|
||
|
||
Server state принадлежит TanStack Query, form state — React Hook Form, локальное
|
||
UI — React state. Не копируй успешный query result в useState/context для второго
|
||
кэша. Domain-specific queries, keys и policies принадлежат вызывающему slice;
|
||
общий transport — `shared/api`. Page VM выбирает inputs и собирает presentation data.
|
||
|
||
Используй `apiClient`/`apiQueryClient` из shared public API. Прямой fetch в
|
||
pages/features/entities и прямой T-Bank call запрещены. Сначала найди
|
||
существующий query того же endpoint; расширяй его owner, не создавай второй key
|
||
или глобальную query policy для одной страницы.
|
||
|
||
## Типы и contract
|
||
|
||
Backend-owned OpenAPI contract поступает во frontend как предоставленный
|
||
актуальный snapshot; generated `schema.d.ts` производен от него. Тип
|
||
request/response бери из generated `paths`, например:
|
||
|
||
```ts
|
||
type User = paths["/api/v1/auth/me"]["get"]["responses"][200]["content"]["application/json"];
|
||
```
|
||
|
||
Domain/UI type допустим, когда он отличается по смыслу, а не копирует transport DTO.
|
||
Generated TS вручную не исправляется. Не добавляй параллельный fetch/axios
|
||
client, runtime schema, hash/checksum registry или дополнительный drift checker
|
||
без отдельной текущей потребности; existing OpenAPI flow уже владеет генерацией. Frontend-команды не читают соседний
|
||
backend checkout и не проверяют backend runtime OpenAPI; изменение wire
|
||
shape/status/route сохраняет
|
||
публичный контракт предоставленного snapshot.
|
||
Одна UI-модель не требует менять API. `openapi:generate` работает offline от snapshot;
|
||
`openapi:check` генерирует во временный каталог и сравнивает, не меняя tracked files.
|
||
|
||
## Auth и invalidation
|
||
|
||
Bearer header принадлежит shared authenticated fetch; query/mutation не задают
|
||
Authorization. Auth endpoints исключены из Bearer/retry middleware. Refresh
|
||
координируется single-flight; после `401` с `UNAUTHORIZED` возможен один retry.
|
||
Не добавляй собственный refresh/retry loop в caller.
|
||
|
||
Cookie-writing requests упорядочены только в текущей вкладке. Cross-tab auth
|
||
synchronization не требуется; допустим reload/re-login. Web Locks и отдельные
|
||
token stores не добавляются.
|
||
|
||
Protected queries используют existing session-aware boundary; logout очищает
|
||
session-scoped cache. Access token хранится только в памяти, refresh передаётся
|
||
httpOnly cookie с `credentials: include`. Не сохраняй tokens в localStorage,
|
||
URL, логах или fixtures. Logout очищает локальную session/cache и при ошибке
|
||
сети; до окончания auth bootstrap показывай neutral state. Перед auth change
|
||
прочитай shared client, session owner и их tests.
|
||
Успешная connection mutation коммитит подтверждённый status и сбрасывает financial
|
||
GET subtree; ошибка mutation не меняет generation/cache. Это отдельная граница
|
||
от обычного фонового refetch. Даже повторное сохранение того же token начинает
|
||
новую generation; поздний ответ прежней generation не попадает в новый кэш.
|
||
|
||
Отмена/reset принадлежат TanStack Query и generated queryFn с AbortSignal,
|
||
без HTTP request registry, URL-wide latest-read counters и access epochs.
|
||
Успешный HTTP с `data: null` остаётся query success; readiness выбирается по
|
||
data/metadata, без подмены query status и отдельного transport state.
|
||
Status синхронизации содержит только состояние работы. Resources читаются
|
||
через `sync=observe` во время работы и после завершения, без hash comparison.
|
||
Согласованность income entries/groups обеспечивает backend; frontend не
|
||
дублирует её runtime validator.
|
||
|
||
Mutations используют существующий generated client и query-key owner;
|
||
после успеха обновляй/invalidируй только связанные данные. Optimistic update
|
||
нужен конкретному UX и обязан иметь rollback/error behavior.
|
||
|
||
Default focus refetch выключен; freshness и retry задаются существующими
|
||
query options у entity/feature owner. После `staleTime` повторное открытие
|
||
экрана может запустить background refetch; focus сам по себе его не запускает.
|
||
При обычном refetch сохраняй Client Cached Snapshot, при session/connection
|
||
смене не показывай данные прежнего владельца. UI presentation — в UI convention.
|
||
|
||
## Errors
|
||
|
||
Извлекай code через existing `apiErrorCode` из shared/api. Backend Problem
|
||
Details code задаёт machine behavior; user-facing текст принадлежит i18n.
|
||
Network/unknown error получает безопасное локальное сообщение и retry,
|
||
не raw exception text. Input/domain failure сохраняет ввод и показывает
|
||
ошибку у ответственного form/section owner; не маскируй её успешным результатом.
|
||
|
||
Если одна группа API codes требует одинакового recovery в нескольких views,
|
||
сведи классификацию у ближайшего общего owner. Не размножай magic comparisons
|
||
и используй `ApiErrorCode` для известных значений: generated `code` остаётся `string`.
|
||
Неизвестный новый code должен сохранять fallback. Рефакторинг существующих
|
||
разных recovery paths требует behavior proof, а не общей обработки всех ошибок.
|
||
|
||
## Примеры и проверка
|
||
|
||
[usePortfolio](../../src/entities/portfolio/api/usePortfolio.ts) — entity query;
|
||
[shared client](../../src/shared/api/client.ts) — transport/auth boundary.
|
||
Изменяя header/retry/invalidation, проверяй наблюдаемое поведение через MSW и
|
||
[Testing](testing.md); не обращайся к реальному T-Bank.
|
||
`npm run openapi:check`, `npm run typecheck` и targeted tests проверяют разные
|
||
части решения. Для поведения API-клиента используй сценарии MSW на публичной
|
||
HTTP-границе; реализация сервера не является входом frontend-проверок.
|