UNPKG

synapse-storage

Version:

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

145 lines (138 loc) 8.19 kB
import { handleCallbackError } from "../_utils/error-handling.util.js"; import { StorageStatus } from "../core/index.js"; /** Похож ли вход на thenable (handle/Promise), а не на уже готовый synapse. */ const isThenable = (value)=>typeof value?.then === 'function'; /** * Пытается достать ГОТОВЫЙ стор прямо сейчас, без ожидания. Два случая: * - вход — handle: берём собранный стор через getSnapshot() (после dehydrate он READY); * - вход — уже готовый стор напрямую (не Promise) с READY-хранилищем. * Не вышло → undefined → пойдём асинхронным путём. Это и есть SSR sync-fast-path: * так серверный рендер получает main синхронно, без спиннера. */ const resolveSyncReady = (input)=>{ // Вход — handle: берём уже собранный стор (сервер после dehydrate / повторный mount на клиенте). const snapshot = input?.getSnapshot?.(); if (snapshot && snapshot.storage.initStatus.status === StorageStatus.READY) return snapshot; // Вход — готовый стор передали напрямую (не Promise), с READY-хранилищем. if (!isThenable(input) && input.storage?.initStatus?.status === StorageStatus.READY) { return input; } return undefined; }; /** * Создает фреймворк-независимую утилиту для ожидания готовности Synapse. * Принимает `SynapseModule`-handle (PromiseLike), Promise готового synapse либо сам * готовый synapse. Работает в любом JS окружении: Node.js, браузер, React Native. */ function createSynapseAwaiter(synapseStorePromise) { // Состояние автомата: статус + сам стор + ошибка. Меняются либо синхронно (fast-path ниже), // либо асинхронно (storeInitPromise), либо гасятся в destroy(). let status = 'pending'; let store; let error = null; let destroyed = false; // Подписчики на готовность/ошибку (их наполняет onReady/onError, дёргает async-путь). const readyCallbacks = new Set(); const errorCallbacks = new Set(); // Sync-fast-path: если стор готов уже сейчас — выставляем 'ready' ДО возврата, чтобы // getStoreIfReady() отдал его на первом же синхронном рендере (сервер / клиентская гидрация). const syncReady = resolveSyncReady(synapseStorePromise); if (syncReady) { store = syncReady; status = 'ready'; } // Async-путь: ждём, пока стор соберётся и хранилище станет READY, затем 'ready' + зовём onReady // (или onError). Нужен, когда синхронно готового не было (async-стор на клиенте): компонент // подписался в useEffect и перерисуется, когда стор достроится. const storeInitPromise = (async ()=>{ try { const resolvedStore = await Promise.resolve(synapseStorePromise); // Ждём готовности хранилища (storage.initialize() может быть асинхронным). await resolvedStore.storage.waitForReady(); // awaiter уничтожили пока ждали — состояние не трогаем. if (destroyed) return resolvedStore; store = resolvedStore; status = 'ready'; // Уведомляем всех подписчиков о готовности readyCallbacks.forEach((callback)=>{ try { callback(store); } catch (err) { handleCallbackError('SynapseAwaiter: error in ready callback', err); } }); return resolvedStore; } catch (err) { const errorObj = err instanceof Error ? err : new Error(String(err)); // Если awaiter был уничтожен во время инициализации — не обновляем состояние if (destroyed) throw errorObj; error = errorObj; status = 'error'; // Уведомляем всех подписчиков об ошибке errorCallbacks.forEach((callback)=>{ try { callback(errorObj); } catch (callbackErr) { handleCallbackError('SynapseAwaiter: error in error callback', callbackErr); } }); throw errorObj; } })(); // Защита от unhandled rejection: ошибка сборки доставляется через onError/getError (channel // подписок), но сам storeInitPromise при этом отклоняется. Если его никто не await'ит (типичный // случай: createSynapseCtx работает через onReady/onError, не через waitForReady), отклонение // всплыло бы как unhandledRejection (шум на сервере, падение strict-процессов). Гасим отдельным // .catch — waitForReady() по-прежнему отдаёт тот же промис, и его потребитель получит throw. storeInitPromise.catch(()=>{}); // Публичный API: синхронные геттеры + подписки. Из createSynapseCtx зовут getStoreIfReady() // (в useState/useEffect) и onReady/onError (подписка в useEffect на async-готовность). return { waitForReady: ()=>storeInitPromise, isReady: ()=>status === 'ready', getStoreIfReady: ()=>store, onReady: (callback)=>{ // Если уже готов, вызываем callback немедленно if (status === 'ready' && store) { try { callback(store); } catch (err) { handleCallbackError('SynapseAwaiter: error in immediate ready callback', err); } } else { // Иначе добавляем в список ожидания readyCallbacks.add(callback); } return ()=>{ readyCallbacks.delete(callback); }; }, onError: (callback)=>{ // Если уже есть ошибка, вызываем callback немедленно if (status === 'error' && error) { try { callback(error); } catch (err) { handleCallbackError('SynapseAwaiter: error in immediate error callback', err); } } else { // Иначе добавляем в список ожидания errorCallbacks.add(callback); } return ()=>{ errorCallbacks.delete(callback); }; }, getStatus: ()=>status, getError: ()=>error, // Сброс: отписываем всех и обнуляем состояние. Флаг destroyed не даёт async-пути записать // готовность после уничтожения (напр. размонтировали клиентский awaiter в cleanupSynapse). destroy: ()=>{ destroyed = true; readyCallbacks.clear(); errorCallbacks.clear(); store = undefined; error = null; status = 'pending'; } }; } export { createSynapseAwaiter }; //# sourceMappingURL=createSynapseAwaiter.js.map