3.9 KiB
Raw Permalink Blame History

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

  1. Единый владелец envelopeTransformInterceptor является единственной точкой, формирующей { 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. Единый формат envelopenormalizeEnvelope() больше не поддерживает 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'ов.