105 lines
7.8 KiB
Markdown
105 lines
7.8 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` запускает native `--check` того же CLI и падает при устаревших
|
||
generated types, не меняя 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.
|
||
|
||
Refresh, завершившийся `401`, очищает session. Сетевая ошибка и `5xx`
|
||
(например, деплой backend) не доказывают конец session: она сохраняется,
|
||
`unavailable` показывает retry-экран вместо login, а следующий `401` снова
|
||
запускает refresh. Retry/backoff внутри refresh не добавляется.
|
||
|
||
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-проверок.
|