@space-ai/contracts
v0.1.9
Published
Типы и функции HTTP-запросов к API (axios устанавливается вместе с пакетом)
Readme
@space-ai/contracts
npm-пакет с TypeScript-типами и функциями HTTP-запросов к API.
Axios устанавливается автоматически как зависимость пакета. Базовый URL API задаётся через BASE_API_URL на глобальном объекте: window (браузер) или global / globalThis (Node.js, тесты).
Содержание
- Установка
- Быстрый старт
- BASE_API_URL (window / global)
- Способы инициализации
- Использование API-функций
- TypeScript
- Локальная разработка пакета
- Публикация в npm
Установка
npm install @space-ai/contractsОтдельно ставить axios не нужно.
Быстрый старт
1. Задайте базовый URL API:
// браузер
window.BASE_API_URL = 'https://api.example.com';
// Node.js / тесты
global.BASE_API_URL = 'http://localhost:3000';2. Подключите автоинициализацию axios (один раз в точке входа):
import '@space-ai/contracts/axios';3. Используйте функции запросов где угодно:
import { getConnectors } from '@space-ai/contracts';
const { data: connectors } = await getConnectors();Axios создаётся с baseURL: BASE_API_URL, сохраняется в window.axios / global.axios и используется всеми API-функциями.
BASE_API_URL (window / global)
Пакет читает и пишет одни и те же поля на globalThis — в браузере это window, в Node.js — global.
| Свойство | Тип | Описание |
|----------|-----|----------|
| BASE_API_URL | string | Базовый URL API → axios.create({ baseURL }) |
| API_PREFIX | string | Опциональный префикс (api → {BASE_API_URL}/api) |
| axios | AxiosInstance | Экземпляр axios (создаётся пакетом) |
| CONTRACTS_AUTH | AuthProvider | Провайдер токена (альтернатива configureAuth) |
Примеры:
// браузер
window.BASE_API_URL = '/api';
// Node.js, Vitest, Jest
global.BASE_API_URL = 'http://localhost:3000';Инициализация axios и авторизация
Пакет создаёт axios как на фронтенде:
baseURLизBASE_API_URL+ опциональныйAPI_PREFIX- заголовки
Content-Type/Accept: application/json - request interceptor — подставляет
Authorization: Bearer <token> - response interceptor — проверка статуса и проброс ошибок
Провайдер токена
Keycloak, Zustand и прочее остаются в приложении. Пакет не знает про них — вы передаёте AuthProvider:
import { configureAuth, setupApiClient } from '@space-ai/contracts';
import { getAccessToken, updateToken } from '@/api/keycloak';
import { dispatchLogout, useAuth } from '@/stores/auth';
configureAuth({
getAccessToken: () => getAccessToken() ?? useAuth.getState().token ?? null,
updateToken: (minValiditySeconds = 30) => updateToken(minValiditySeconds),
onSessionExpired: () => dispatchLogout(),
});
window.BASE_API_URL = import.meta.env.VITE_API_URL;
window.API_PREFIX = 'api'; // опционально
setupApiClient();Логика interceptor (как в src/api/axios.ts на фронте):
getAccessToken()— если токена нет, запрос уходит безAuthorizationupdateToken(30)— обновление перед запросом; при ошибке →onSessionExpired()иSession expired- повторный
getAccessToken()— свежий токен в заголовок
Альтернатива — global.CONTRACTS_AUTH
global.CONTRACTS_AUTH = {
getAccessToken: () => getAccessToken() ?? useAuth.getState().token ?? null,
updateToken,
onSessionExpired: dispatchLogout,
};Полный пример main.tsx
import { configureAuth, setupApiClient } from '@space-ai/contracts';
import { getAccessToken, updateToken } from '@/api/keycloak';
import { dispatchLogout, useAuth } from '@/stores/auth';
configureAuth({
getAccessToken: () => getAccessToken() ?? useAuth.getState().token ?? null,
updateToken: (s = 30) => updateToken(s),
onSessionExpired: dispatchLogout,
});
window.BASE_API_URL = import.meta.env.VITE_API_URL;
window.API_PREFIX = 'api';
setupApiClient();
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')!).render(<App />);Способы инициализации
Способ 1 — Vite / React (рекомендуется)
Импорты в ES-модулях поднимаются (hoisting) и выполняются раньше остального кода файла. Поэтому window.BASE_API_URL нужно задать до загрузки приложения.
index.html — задайте URL до бандла:
<!DOCTYPE html>
<html lang="ru">
<head>...</head>
<body>
<div id="root"></div>
<script>
window.BASE_API_URL = '/api';
</script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>main.tsx:
import '@space-ai/contracts/axios';
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')!).render(<App />);Vite: URL из .env
В .env:
VITE_BASE_API_URL=https://api.example.comВ index.html (Vite подставляет %VITE_*% при сборке):
<script>
window.BASE_API_URL = '%VITE_BASE_API_URL%';
</script>Или через vite.config.ts — плагин transformIndexHtml, если нужна подстановка иначе.
Способ 2 — явный вызов setupApiClient()
Если BASE_API_URL задаётся в коде (например, из import.meta.env), вызовите setup после присвоения:
// main.tsx
window.BASE_API_URL = import.meta.env.VITE_BASE_API_URL;
import { setupApiClient } from '@space-ai/contracts';
setupApiClient();Side-effect импорт @space-ai/contracts/axios здесь не обязателен — достаточно setupApiClient().
Способ 3 — side-effect импорт + отложенная инициализация
Если используете только:
window.BASE_API_URL = import.meta.env.VITE_BASE_API_URL;
import '@space-ai/contracts/axios';из-за hoisting импорт выполнится раньше присвоения BASE_API_URL. Пакет это учитывает: при импорте @space-ai/contracts/axios включается auto-init, а axios создаётся при первом HTTP-запросе, когда window.BASE_API_URL уже задан.
// main.tsx — порядок строк в файле не важен для auto-init
import '@space-ai/contracts/axios';
window.BASE_API_URL = import.meta.env.VITE_BASE_API_URL;
// axios создастся при первом вызове getConnectors(), getGuardrailsList() и т.д.Для interceptors и baseURL до первого запроса надёжнее Способ 1 или Способ 2.
Способ 4 — полностью ручная настройка
Свой экземпляр axios с interceptors, таймаутами и т.д.:
import axios from 'axios';
import { initApiClient } from '@space-ai/contracts';
const api = axios.create({
baseURL: window.BASE_API_URL ?? '/api',
timeout: 10_000,
});
api.interceptors.request.use((config) => {
// auth token, headers...
return config;
});
initApiClient(api);Использование API-функций
import {
getConnectors,
postConnector,
getGuardrailsList,
} from '@space-ai/contracts';
const { data: connectors } = await getConnectors();
const { data: guardrails } = await getGuardrailsList();Доступные API-функции:
- Connectors:
getConnectors(),postConnector(payload),getConnector(id),patchConnector(id, body),deleteConnector(id),getConnectorHealth(id),postConnectorExecute(payload),runConnectorExecuteTest(payload) - Guardrails:
getGuardrailsList(),getGuardrail(id),postGuardrail(payload),putGuardrail(id, payload),deleteGuardrail(id),postGuardrailRegister(payload),postApplyGuardrail(payload),postRiskScoreGuardrail(payload),getGuardrailSubmissions(query?),getGuardrailSubmission(id),postGuardrailSubmissionApprove(id),postGuardrailSubmissionReject(id),getGuardrailMajorAirlines(),getGuardrailProviderSpecificParams() - Policies:
postPolicy(payload),getPoliciesList(),getPolicy(id),putPolicy(id, payload),deletePolicy(id),deletePolicyAllVersions(name),getPolicyVersionsByName(name),postPolicyVersion(name, payload),getPolicyResolvedGuardrails(id) - Policy validate/list:
getPolicyList(),getPolicyName(id),getPolicyTemplates(),postPolicyValidate(payload) - Access:
getAccessCheck() - Audit:
getAuditEvents(),getAuditViolations() - LLM Providers:
getLLMProviders(),postLLMProvider(payload),getLLMProvider(id),putLLMProvider(id, payload),deleteLLMProvider(id) - Users:
putUserRoles(userId, data) - Roles:
getRoles(),postRole(payload),patchRole(id, body),roleHasPermission(role, key, perm),mergeRolePermission(role, key, perm) - Organizations:
getOrganizations(),getOrganization(id),patchOrganization(data) - MCP:
postMcpProxy(body),getMcpServers(),postMcpServer(payload),getMcpServer(id),deleteMcpServer(id) - Applications:
getApplications(),postApplication(app),getApplication(id),patchApplication(id, app),deleteApplication(id)
Примеры:
import { getConnectors, putUserRoles, type IAssignUserRolesRequest } from '@space-ai/contracts';
const { data: connectors } = await getConnectors();
const body: IAssignUserRolesRequest = { roles: ['admin', 'user'] };
await putUserRoles('[email protected]', body);Прямой доступ к axios (после инициализации):
const client = globalThis.axios; // window.axios в браузере
await client.get('/health');TypeScript
Пакет расширяет глобальную область (global / window):
declare global {
var BASE_API_URL: string | undefined;
var axios: import('axios').AxiosInstance | undefined;
}Дополнительные декларации в проекте не нужны.
Локальная разработка пакета
git clone <repo>
cd contracts
npm install
npm run buildСтруктура:
src/
api/ — функции запросов к эндпоинтам
types/ — типы запросов/ответов
core/ — axios, init, setup
axios.ts — entry point @space-ai/contracts/axios
index.ts — публичное API @space-ai/contracts
dist/ — сборка для npm (не коммитится)Публикация в npm
- Организация
@space-aiна npmjs.com. - Granular Access Token (Read and write, Bypass 2FA) → GitHub Secret
NPM_TOKEN. - Поднять версию и запушить тег:
npm version patch
git push origin main --tagsCI (.github/workflows/publish.yml) выполнит npm publish при теге v*.*.*.
| Workflow | Назначение |
|----------|------------|
| ci.yml | сборка на PR и push в main |
| publish.yml | публикация по git-тегу |
Частые проблемы
| Симптом | Решение |
|---------|---------|
| Axios не найден | Задайте BASE_API_URL на window / global, импортируйте @space-ai/contracts/axios или вызовите setupApiClient() |
| Запросы без baseURL | BASE_API_URL не был задан до инициализации — используйте Способ 1 или Способ 2 |
| Двойной axios | Не вызывайте и setupApiClient(), и initApiClient() с разными инстансами без необходимости |
