57 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# API Envelope Runtime Contract
## Goal
Устранить расхождение между следующим API-контрактом и реальными runtime-ответами backend'а, чтобы каждый endpoint возвращал ровно один `{ data, meta }`:
```ts
// Единственный публичный контракт
ApiResponse<T> = { data: T, meta: { fromCache: boolean, cachedAt: string | null } }
```
Убрать поддержку broken double-wrapping на frontend и сделать `TransformInterceptor` единственной точкой формирования публичного envelope.
## Requirements
### Backend
1. **Единый владелец envelope**`TransformInterceptor` является единственной точкой, формирующей `{ data, meta }` в HTTP-ответе.
2. **Internal carrier** — Services и controllers, которым нужно передать cache metadata, используют внутренний carrier-тип (`ApiEnvelopePayload<T>`), а не создают `{ data, meta }` вручную.
3. **Controllers без meta возвращают чистое DTO** — Если endpoint не использует cache, controller возвращает только domain data, interceptor оборачивает её сам.
4. **Ни один endpoint не приводит к `data.data`** — Runtime-ответ каждого endpoint проверяется интеграционным тестом на одинарный envelope.
### Frontend
1. **Единый формат envelope**`normalizeEnvelope()` больше не поддерживает double-wrapped ответы.
2. **request()** остаётся `Promise<{ data: T; meta: ApiResponseMeta }>` — контракт не меняется, ясность не уменьшается.
### Testing
1. **HTTP contract tests** — минимальный набор проверяет, что ключевые endpoint'ы возвращают `{ data, meta }` без вложенности.
2. **Существующие тесты обновлены** — сервисные и контроллерные тесты проверяют новый carrier-механизм вместо ручного `{ data, meta }`.
3. **Regression-тесты** — endpoint'ы autentification, shares, bonds, securities, portfolios, T-Bank покрыты минимум одним contract-тестом на shape ответа.
## Acceptance Criteria
1. `GET /api/v1/health` возвращает `{ data: { ... }, meta: { fromCache, cachedAt } }` — без `data.data`.
2. `GET /api/v1/securities/shares/{secid}` — одинарный envelope.
3. `GET /api/v1/securities/shares/{secid}/marketdata` — одинарный envelope с корректным `meta` от cache.
4. `GET /api/v1/securities/bonds/{secid}` — одинарный envelope.
5. `GET /api/v1/securities/search?q=` — одинарный envelope.
6. `GET /api/v1/securities/screener?` — одинарный envelope.
7. `GET /api/v1/portfolios` … — одинарный envelope.
8. `GET /api/v1/broker/accounts` … — одинарный envelope.
9. `POST /api/v1/auth/register`, `login`, `refresh` — одинарный envelope.
10. Backend тесты проходят: `npm test -w apps/backend`.
11. Frontend тесты проходят: `npm test -w apps/frontend`.
12. `npm run build -w apps/frontend` проходит.
13. `normalizeEnvelope()` больше не проверяет `data.data`.
14. Swagger/OpenAPI артефакты не ломаются (`npm exec -w apps/backend -- vitest run openapi`).
## Non-goals
- Не менять форму DTO и Swagger-типы (они уже корректны).
- Не менять формат error-ответов.
- Не добавлять версионирование API.
- Не рефакторить бизнес-логику endpoint'ов.