UNPKG

synapse-storage

Version:

Набор инструментов для управления состоянием и апи-запросами

165 lines (156 loc) 7.68 kB
import { EndpointClass } from "./components/endpoint.js"; import { QueryStorage } from "./components/query-storage.js"; import { apiLogger } from "./utils/api-helpers.js"; class ApiClient { /** Хранилище запросов */ queryStorage; /** Флаг завершённой инициализации */ _initialized = false; /** Промис текущей инициализации (для дедупликации параллельных вызовов) */ _initPromise = null; /** Снапшот для гидрации, отложенный до завершения init (если hydrate вызван раньше) */ _pendingHydration = null; cacheableHeaderKeys; globalCacheConfig; baseQueryConfig; storageExternal; globalRetryConfig; createEndpoints; /** Реестр эндпоинтов */ endpoints = {}; constructor(options){ // Сохраняем переданные параметры this.cacheableHeaderKeys = options.cacheableHeaderKeys; this.globalCacheConfig = options.cache; this.baseQueryConfig = options.baseQuery; this.storageExternal = options.storage; this.globalRetryConfig = options.retry; this.createEndpoints = options.endpoints; } async init() { if (this._initialized) return this; if (this._initPromise) return this._initPromise; this._initPromise = this._doInit(); return this._initPromise; } async _doInit() { try { // 1. Создаем кэшированное хранилище запросов (storage инициализируется внутри QueryStorage) this.queryStorage = await new QueryStorage(this.storageExternal, this.globalCacheConfig).initialize(); // 2. Применяем отложенную гидрацию (если hydrate был вызван до init) if (this._pendingHydration) { await this.queryStorage.hydrate(this._pendingHydration); this._pendingHydration = null; } // 3. Создаем эндпоинты await this.initializeEndpoints(); this._initialized = true; return this; } catch (error) { this._initPromise = null; throw error; } } async initializeEndpoints() { // Получаем конфигурацию будущих эндпоинтов const create = (config)=>config; // Создаем объект с конфигурациями для эндпоинтов const endpointsConfig = await this.createEndpoints(create); // Создаем эндпоинты for (const [endpointKey, endpointConfig] of Object.entries(endpointsConfig)){ const key = endpointKey; this.endpoints[key] = new EndpointClass({ name: endpointKey, queryStorage: this.queryStorage, config: endpointConfig, cacheableHeaderKeys: this.cacheableHeaderKeys, globalCacheConfig: this.globalCacheConfig, globalRetryConfig: this.globalRetryConfig, baseQueryConfig: this.baseQueryConfig }); } } ensureInitialized() { if (!this._initialized) { throw new Error('ApiClient не инициализирован. Вызовите await api.init() перед использованием.'); } } /** * Получает все эндпоинты с улучшенной типизацией * @returns Типизированный объект эндпоинтов */ getEndpoints() { this.ensureInitialized(); return this.endpoints; } /** * Выполняет запрос к API с типизацией и обработкой ошибок * @param endpointName Имя эндпоинта (с подсказками TypeScript) * @param params Параметры запроса (с типизацией) * @param options Опции запроса * @returns Promise с типизированным результатом запроса */ async request(endpointName, params, options) { this.ensureInitialized(); const endpoints = this.getEndpoints(); const endpoint = endpoints[endpointName]; if (!endpoint) { throw new Error(`Эндпоинт ${String(endpointName)} не найден`); } try { const stateRequest = endpoint.request(params, options); return await stateRequest.wait(); } catch (error) { apiLogger.error(`Ошибка запроса к ${String(endpointName)}`, { error, params }); throw error; } } /** * Снимок кэша для дегидрации (SSR: сервер → HTML → клиент). * * Возвращает всё состояние кэш-хранилища (записи API с абсолютными `expiresAt`, * поэтому TTL переживает перенос на клиент). Симметрично `dehydrateModule` для * synapse-модулей. Типичный серверный рецепт: `await api.request(...)` для * прогрева → `await api.dehydrate()` → сериализовать в HTML. */ async dehydrate() { this.ensureInitialized(); const storage = this.queryStorage.getStorage(); if (!storage) return {}; // getState у sync-хранилищ возвращает значение, у async — Promise; await покрывает оба return await storage.getState(); } /** * Гидрация кэша снапшотом с сервера. * * - Вызванная ДО `init()` — снапшот запоминается и применяется сразу после * создания хранилища (init не перезатрёт серверное состояние). * - Вызванная ПОСЛЕ `init()` — немедленно заменяет состояние кэша и * перестраивает индекс тегов. * * После гидрации первый `request()`/`getCachedSync()` с теми же параметрами * попадёт в кэш — на клиенте не будет повторного сетевого запроса. */ async hydrate(state) { if (this._initialized) { await this.queryStorage.hydrate(state); } else { this._pendingHydration = state; } return this; } /** * Доступ к экземпляру хранилища кэша (для ручных сценариев SSR/отладки). */ getStorage() { this.ensureInitialized(); return this.queryStorage.getStorage(); } async destroy() { if (!this._initialized) return; // 1. Уничтожаем каждый эндпоинт Object.values(this.endpoints).forEach((endpoint)=>endpoint.destroy()); // 2. Очищаем коллекцию эндпоинтов this.endpoints = {}; // 3. Уничтожаем хранилище await this.queryStorage.destroy(); // 4. Сбрасываем состояние инициализации (позволяет повторный init) this._initialized = false; this._initPromise = null; } } export { ApiClient }; //# sourceMappingURL=api.module.js.map