Anti-drift и кодген
Главный риск любого клиентского SDK — рассинхрон с бэком: бэкенд переименовал поле, поменял enum статуса, удалил эндпоинт, а фронт об этом узнаёт в проде. MineFlow Client SDK снимает этот риск одним принципом (ADR-0042 § Anti-drift contract):
Один источник истины на бэке → в SDK попадает кодогеном или re-export'ом, НИКОГДА копией → CI-гейт валит тот же PR, что вносит дрейф.
SDK живёт в том же монорепо и релизится из одного тега — нет version skew через сеть. Рассинхрон становится ошибкой компиляции в том же PR, а не рантайм-сюрпризом у пользователя.
Если ты видишь в коде SDK захардкоженный тип ответа, список статусов или регекс эндпоинта — это баг. Каждый такой артефакт должен либо генерироваться из live-OpenAPI, либо реэкспортироваться из исходной либы. Копия = форк = гарантированный дрейф.
Карта артефактов: что откуда берётся
Для фронтенд-разработчика важно понимать, что трогать руками нельзя — оно регенерируется и любая ручная правка затрётся:
| Артефакт | Источник истины (бэк) | Доставка в SDK | Гейт от дрейфа |
|---|---|---|---|
API-типы (@mineflow/api-client) | NestJS controllers/DTO | codegen pnpm openapi:client | openapi:diff + api-client drift gate (blocking) + typecheck |
Zod-схемы форм (@mineflow/api-zod) | *.schema.ts (pure-часть) | codegen pnpm openapi:zod | typecheck |
FSM-машины (@mineflow/api-schemas) | libs/*/state-machine/*.machine.ts | re-export | fsm:check (blocking) + typecheck |
Роли (SystemRole) | libs/auth SYSTEM_ROLES | import type (zero-runtime) | typecheck |
| Error-коды | домен *.errors.ts + filter (RFC 7807) | нормализация по code в client-core | typecheck + контрактные тесты |
| Тривиальные read-хуки | OpenAPI paths | codegen pnpm gen:hooks | сверка пути с live-спекой (fail-loud) |
Подробности по каждому пакету — в гайдах: api-client, api-zod, api-schemas, client-react, auth-web.
Откуда что: типы, схемы, FSM, роли
Все четыре доменных артефакта подтягиваются автоматически — ты просто импортируешь готовое:
import type { paths } from '@mineflow/api-client'; // типы REST (codegen, zero-runtime)
import { zCreateAssetDto } from '@mineflow/api-zod'; // Zod-схема формы (codegen)
import { assetMachine } from '@mineflow/api-schemas'; // FSM-машина (re-export)
import { useCan } from '@mineflow/client-react'; // RBAC поверх SystemRole (import type)
- Типы (
api-client) — генерируются изapps/api/openapi.json. Стираются при компиляции (zero-runtime), поэтому безопасны для браузера и Hermes. Если бэк сменил форму DTO — поменяется тип, и компиляция SDK упадёт в том же PR. - Zod-схемы (
api-zod) — генерируются из той же OpenAPI. Бэк оборачивает схему вcreateZodDto, фронт получает чистую.strict()-версию дляreact-hook-form. См. рецепт по формам. - FSM (
api-schemas) — реэкспорт xstate-машин изlibs/*/state-machine.useAvailableActions(machine, status)показывает кнопки строго по машине бэка — см. рецепт по FSM. - Роли —
import type { SystemRole }: 7 канонических ролей напрямую изlibs/auth, без копии. См. рецепт по RBAC.
Read-хуки генерятся (gen:hooks), fail-loud
Тривиальные read-хуки (list / detail / sub-коллекция) — useAssets, useAsset, usePersonnel, useShiftReports, useShiftReportEntries и т.д. — не пишутся руками. Это чистый boilerplate useApiQuery(key, c => unwrap(c.GET(path, …))), поэтому он генерируется из OpenAPI:
pnpm gen:hooks # standalone; также входит в pnpm openapi:client
Генератор (tools/scripts/gen-domain-hooks.ts) пишет файлы packages/client-react/src/domain-hooks/<area>.generated.ts (eam / hr / prd).
*.generated.tsФайлы с суффиксом .generated.ts помечены баннером AUTO-GENERATED. Любая ручная правка затрётся при следующем pnpm gen:hooks. Рукописные хуки (write / саги / offline) живут в соседнем <area>.ts и реэкспортят свой .generated.
Fail-loud — главное свойство генератора. Каждый путь манифеста сверяется с живой OpenAPI-спекой на этапе генерации: метод существует, есть ли query/path-параметры, есть ли обязательные query. Если бэк переименовал или удалил эндпоинт — pnpm gen:hooks падает с понятной ошибкой и не пишет частичный файл, вместо того чтобы молча сгенерировать битый хук:
✗ gen-domain-hooks: путь /api/v1/eam/assets отсутствует в OpenAPI-спеке (дрейф бэка?).
То же касается обязательных query-параметров: для /hr/timesheet (требует dateFrom/dateTo) генератор выпускает хук без default-аргумента — забыть параметр станет ошибкой типа, а не пустым ответом в рантайме.
Добавить read-хуки нового агрегата
Это задача для мейнтейнеров SDK, но знать механику полезно. Чтобы добавить, например, scm/fuel:
- одна строка в манифесте
HOOKSвtools/scripts/gen-domain-hooks.ts(hook/path/keyGroup/kind); - группа ключей в
packages/client-react/src/query-keys.ts; pnpm gen:hooks.
Код самого генератора править не нужно — area выводится из пути (/api/v1/<area>/...). Сложные хуки (write с инвалидацией, саги, offline) остаются рукописными на дженериках useDomainMutation / useSagaMutation — см. гайд client-react.
Команды кодгена
pnpm openapi:export # выгрузить live-спеку NestJS → apps/api/openapi.json
pnpm openapi:client # типы api-client + gen:hooks (запускает openapi:export внутри)
pnpm openapi:zod # Zod-с хемы api-zod из той же openapi.json
pnpm gen:hooks # только read-хуки (требует свежий openapi.json)
Конвейер: openapi:client сам делает openapi:export, генерит @mineflow/api-client и в конце дёргает gen:hooks. То есть после изменения контроллера/DTO на бэке достаточно одного pnpm openapi:client, чтобы и типы, и read-хуки пришли в согласие со спекой. openapi:zod запускается отдельно (читает уже выгруженный apps/api/openapi.json).
pnpm openapi:client && pnpm openapi:zod && pnpm typecheck
typecheck через turbo накрывает все пакеты @mineflow/client-* — breaking-изменение DTO ломает компиляцию SDK сразу, в том же месте.
CI-гейты от дрейфа
Anti-drift работает не на дисциплине, а на CI. Каждый источник дрейфа имеет блокирующий гейт:
pnpm openapi:diff—oasdiffbreaking-check live-спеки против baseline. Silent breaking-изменение API не проходит.- api-client drift gate (blocking) — «забыл перегенерить
api-client» = красный PR. Раньше был advisory из-за флапаopenapi:exportбез Redis — закрыто добавлением Redis в job. pnpm typecheck(turbo) — SDK типится против сгенерированных типов; breaking-изменение DTO или ролей ломает компиляцию пакетов@mineflow/client-*.pnpm fsm:check— ADR-0022 gate: сверяет xstate-машины с фикстурами. FSM вapi-schemasне разойдётся с бэком.pnpm gen:hooks— fail-loud при дрейфе путей (см. выше); в CI запускается как частьopenapi:client.
Кодген закрывает compile-time дрейф. На рантайме SDK и API всё равно разнесены по деплою: спасает префикс /api/v1 + expand–contract эволюция контракта + openapi:diff, который не пускает silent breaking. baseUrl — это origin без пути; пути из api-client уже содержат /api/v1.
Что делать, если что-то всё-таки разъехалось
Симптомы и лечение:
pnpm gen:hooksпадает с «путь … отсутствует в OpenAPI-спеке» — бэк переименовал/удалил эндпоинт. Сначалаpnpm openapi:export, потом скорректируй строку в манифестеHOOKSпод новый путь.typecheckругается на поле/тип после pull'а — устарелapi-client. Перегенери:pnpm openapi:client.- Форма валидируется не теми правилами — устарел
api-zod.pnpm openapi:zod. useAvailableActionsпоказывает не те кнопки — машина вapi-schemasмогла разойтись; запустиpnpm fsm:checkи пересобери.- Ошибки гейтишь по тексту, а не по
code— это и есть дрейф:error.codeстабилен (ASSET_INVALID_STATUS_TRANSITION), текст — нет. См. рецепт по ошибкам.
Полный обзор слоёв SDK и быстрый старт — в обзорном гайде. Полная карта anti-drift контракта — ADR-0042. REST-референс того же OpenAPI — /rest/.