@mineflow/client-react
Слой L2 клиентского SDK (ADR-0042): React-провайдер плюс набор хуков на TanStack Query поверх ядра @mineflow/client-core. Даёт чтение (concrete/refs/generic), мутации с авто-Idempotency-Key и инвалидацией кэша, write-lifecycle сменного рапорта с offline-first очередью, async-саги, live-уведомления по SSE, RBAC- и FSM-гейты для UI.
Это основной пакет для фронтенда: в 95% случаев приложение работает напрямую только с client-react (плюс @mineflow/api-zod для форм, @mineflow/api-schemas для FSM-машин и @mineflow/auth-web / @mineflow/auth-native для токенов). Пакет платформо-агностичен — те же хуки работают в web и React Native, всё платформо-специфичное инъектируется через провайдер.
Архитектура слоёв и сквозной end-to-end пример — в обзоре SDK и в гайдах нижних слоёв @mineflow/client-core, @mineflow/api-client.
Установка
pnpm add @mineflow/client-react @mineflow/client-core @mineflow/auth-web
# peer-зависимости приложения:
pnpm add react @tanstack/react-query xstate
# опционально под формы и FSM:
pnpm add @mineflow/api-zod @mineflow/api-schemas @hookform/resolvers react-hook-form
Peer-зависимости пакета (из package.json): react ^18 || ^19, @tanstack/react-query ^5, xstate ^5. В монорепо MineFlow внутренние пакеты резолвятся как workspace:*.
1. Провайдер
Оберни приложение в MineflowProvider один раз. Провайдер строит аутентифицированный fetch (Bearer) и REST-клиент из конфига, поднимает QueryClientProvider и кладёт всё в контекст.
import Keycloak from 'keycloak-js';
import { KeycloakTokenProvider } from '@mineflow/auth-web';
import { MineflowProvider } from '@mineflow/client-react';
const keycloak = new Keycloak({
url: 'https://auth.mineflow.local',
realm: 'mineflow',
clientId: 'mineflow-web',
});
await keycloak.init({ onLoad: 'check-sso', pkceMethod: 'S256' });
// Адаптер создаётся ОДИН раз вне рендера (сравнивается по ссылке).
const tokenProvider = new KeycloakTokenProvider({ keycloak });
function App() {
return (
<MineflowProvider
baseUrl={import.meta.env.VITE_API_BASE} // origin; пути уже включают /api/v1
tokenProvider={tokenProvider}
generateId={() => crypto.randomUUID()} // RN: react-native-get-random-values
roles={tokenProvider.getRoles()} // канонические SystemRole
>
{/* ... */}
</MineflowProvider>
);
}
Полный список пропсов (MineflowProviderProps):
| Проп | Тип | Назначение |
|---|---|---|
baseUrl | string | Origin API (без пути; /api/v1 уже зашит в путях из api-client). |
tokenProvider | TokenProvider | Auth-адаптер (web — KeycloakTokenProvider, RN — ReactNativeTokenProvider). |
generateId | IdGenerator | Генератор Idempotency-Key (web — crypto.randomUUID, RN — polyfill). |
roles | readonly SystemRole[] | Канонические роли текущего пользователя (для useCan). |
queryClient | QueryClient? | Внешний QueryClient; если не передан — создаётся свой. |
fetchImpl | typeof fetch? | Базовый fetch для транспорта (REST + SSE). По умолчанию глобальный fetch. |
Доступ к контексту — useMineflow() (вернёт { rest, authFetch, baseUrl, roles }) и useMineflowClient() (только REST-клиент). Вне провайдера оба бросают ошибку.
Аутентифицированный fetch + REST-клиент пересобираются только при смене baseUrl, tokenProvider, generateId или fetchImpl. Смена roles НЕ реконнектит SSE (иначе useNotificationFeed ронял бы подписку при каждом обновлении ролей). roles сравниваются по содержимому (rolesSignature), поэтому inline-литерал roles={['Foreman']} не вызывает лишних перестроений. А вот tokenProvider/generateId сравниваются по ссылке — создавай адаптер один раз вне рендера, иначе транспорт пересоберётся и стрим переподключится.