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