UNPKG

synapse-storage

Version:

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

288 lines (287 loc) 16.8 kB
import { IndexedDBConfig } from './adapters/indexed-DB.service'; import { BatchingMiddlewareOptions, LoggerMiddlewareOptions, ShallowCompareMiddlewareOptions } from './middlewares'; import { SingletonOptions } from './modules/singleton/models'; import { AsyncMiddleware, AsyncMiddlewareAPI, AsyncNextFunction, Middleware, MiddlewareAPI, NextFunction, StorageAction, SyncMiddleware, SyncMiddlewareAPI, SyncNextFunction } from './utils/middleware-module'; import { StorageKeyType } from './utils/storage-key'; export declare enum StorageStatus { IDLE = "idle", LOADING = "loading", READY = "ready", ERROR = "error" } export interface StorageInitStatus { status: StorageStatus; error?: Error; } export declare enum StorageEvents { STORAGE_UPDATE = "storage:update", STORAGE_SELECT = "storage:select", STORAGE_CLEAR = "storage:clear", STORAGE_DESTROY = "storage:destroy" } export interface StorageEvent<T = any> { type: string; payload?: T; metadata?: Record<string, any>; } export interface IEventEmitter { emit(event: StorageEvent): Promise<void>; } export interface ILogger { debug(message: string, meta?: Record<string, any>): void; info(message: string, meta?: Record<string, any>): void; warn(message: string, meta?: Record<string, any>): void; error(message: string, meta?: Record<string, any>): void; } export type { AsyncMiddleware, AsyncMiddlewareAPI, AsyncNextFunction, Middleware, MiddlewareAPI, NextFunction, StorageAction, SyncMiddleware, SyncMiddlewareAPI, SyncNextFunction }; /** * Базовый интерфейс для всех хранилищ. * Содержит lifecycle, sync-доступ к кешу, подписки и метаданные. */ export interface IStorageBase<T extends Record<string, any> = any> { /** Имя хранилища */ readonly name: string; /** Тип хранилища */ readonly type: StorageType; /** * Синхронное ли это хранилище (Memory/LocalStorage → `true`, IndexedDB и воркер-адаптеры → `false`). * * Явный флаг вместо проверки `type`: кастомные адаптеры (напр. `WorkerCacheStorage`) * не обязаны иметь известный `type`, но могут честно сообщить про sync/async, * иначе sync-fast-path (`getStateSync`) полезет туда, где его нет. * * ОПЦИОНАЛЬНЫЙ: внешние кастомные адаптеры, скомпилированные против прежней minor-версии, * не обязаны его объявлять. Когда флаг не задан, потребитель (напр. `QueryStorage`) * откатывается на эвристику по `type` (см. `isSyncStorage()`). */ readonly isSync?: boolean; /** * (Опц.) Возможности хранилища — для будущих оптимизаций и выбора бэкенда. * Не влияет на текущую логику; адаптеры могут не заполнять. */ readonly capabilities?: StorageCapabilities; /** Инициализация хранилища (всегда async — загрузка данных, миграции и т.д.) */ initialize(): Promise<this>; /** Уничтожение хранилища */ destroy(): Promise<void>; /** Ожидание готовности хранилища */ waitForReady(): Promise<this>; /** Синхронное получение состояния из кеша (доступно для всех типов хранилищ) */ getStateSync(): T; /** Подписка на изменение конкретного ключа */ subscribe(key: StorageKeyType, callback: (value: any) => void): VoidFunction; /** Подписка через path-selector */ subscribe<R>(pathSelector: (state: T) => R, callback: (value: R) => void): VoidFunction; /** Подписка на все изменения хранилища */ subscribeToAll(callback: (event: { type: string; changedPaths?: string[]; key?: StorageKeyType[] | StorageKeyType; value?: any; }) => void): VoidFunction; /** Текущий статус инициализации */ readonly initStatus: StorageInitStatus; /** Подписка на изменение статуса инициализации */ onStatusChange(callback: (status: StorageInitStatus) => void): VoidFunction; } /** * Синхронное хранилище. * Memory и LocalStorage реализуют этот интерфейс. * Все операции чтения/записи выполняются синхронно. */ export interface ISyncStorage<T extends Record<string, any> = any> extends IStorageBase<T> { /** * Синхронная инициализация: доводит хранилище до `READY` без `await` (в отличие от * async `initialize()`). Доступна только на sync-хранилищах (Memory/LocalStorage). * Нужна для SSR — синхронной сборки «пустого» стора из `initialState` на сервере. */ initializeSync(): this; get<R>(key: StorageKeyType): R | undefined; set<R>(key: StorageKeyType, value: R): void; update(updater: (state: T) => void): void; remove(key: StorageKeyType): void; has(key: StorageKeyType): boolean; clear(): void; /** Сброс состояния к initialState (или к {} если initialState не задан) */ reset(): void; /** * Гидрация: заменяет состояние переданным снапшотом (SSR/server-state). Вызванная ДО * `initialize()`, она засевает хранилище так, что инициализация не перезатрёт его * `initialState`. Вызванная после — заменяет состояние и уведомляет подписчиков. */ hydrate(state: T): void; keys(): string[]; getState(): T; } /** * Асинхронное хранилище. * IndexedDB реализует этот интерфейс. * Все операции чтения/записи возвращают Promise. */ export interface IAsyncStorage<T extends Record<string, any> = any> extends IStorageBase<T> { get<R>(key: StorageKeyType): Promise<R | undefined>; set<R>(key: StorageKeyType, value: R): Promise<void>; update(updater: (state: T) => void): Promise<void>; remove(key: StorageKeyType): Promise<void>; has(key: StorageKeyType): Promise<boolean>; clear(): Promise<void>; /** Сброс состояния к initialState (или к {} если initialState не задан) */ reset(): Promise<void>; /** * Гидрация: заменяет состояние переданным снапшотом (SSR/server-state). Вызванная ДО * `initialize()`, она засевает хранилище так, что инициализация не перезатрёт его * `initialState`. Вызванная после — заменяет состояние и уведомляет подписчиков. */ hydrate(state: T): Promise<void>; keys(): Promise<string[]>; getState(): Promise<T>; } /** * Union type — любое хранилище. * Используется когда тип хранилища неизвестен на этапе компиляции. */ export type IStorage<T extends Record<string, any> = any> = ISyncStorage<T> | IAsyncStorage<T>; /** * Тип хранилища — ЗАКРЫТЫЙ union реальных адаптеров. Закрыт намеренно: открытый * `(string & {})` пропускал опечатки (`'localstorage'`) и делал типобезопасным * `StorageFactory.create({ type: 'worker' })`, который в рантайме бросал (фабрика не * умеет лениво поднимать воркер). `'worker'` включён, чтобы `WorkerCacheStorage.type` * присваивался; фабрика при этом принимает только те 3 типа, что реально создаёт * (см. {@link StorageFactory.create}). */ export type StorageType = 'memory' | 'localStorage' | 'indexedDB' | 'worker'; /** * Возможности хранилища (все опциональны). Пока информативные — задел под выбор * бэкенда и оптимизации; текущая логика на них не завязана. */ export interface StorageCapabilities { /** Данные разделяются между вкладками/контекстами (SharedWorker и т.п.). */ shared?: boolean; /** Данные переживают сессию (IndexedDB и другие персистентные адаптеры). */ offline?: boolean; /** Поддерживает синхронное чтение/запись. */ sync?: boolean; } export interface SyncDefaultMiddlewares { batching: (options?: BatchingMiddlewareOptions) => SyncMiddleware; shallowCompare: (options?: ShallowCompareMiddlewareOptions) => SyncMiddleware; /** Dev-only логгер пишущих действий (тип/ключ/длительность, опц. prev/next состояние). */ logger: (options?: LoggerMiddlewareOptions) => SyncMiddleware; } export interface AsyncDefaultMiddlewares { batching: (options?: BatchingMiddlewareOptions) => AsyncMiddleware; shallowCompare: (options?: ShallowCompareMiddlewareOptions) => AsyncMiddleware; /** Dev-only логгер пишущих действий (тип/ключ/длительность, опц. prev/next состояние). */ logger: (options?: LoggerMiddlewareOptions) => AsyncMiddleware; } /** @deprecated Use SyncDefaultMiddlewares or AsyncDefaultMiddlewares */ export type DefaultMiddlewares = AsyncDefaultMiddlewares; export type GetSyncDefaultMiddleware = () => SyncDefaultMiddlewares; export type GetAsyncDefaultMiddleware = () => AsyncDefaultMiddlewares; /** @deprecated Use GetAsyncDefaultMiddleware */ export type GetDefaultMiddleware = GetAsyncDefaultMiddleware; export type ConfigureSyncMiddlewares = (getDefaultMiddleware: GetSyncDefaultMiddleware) => SyncMiddleware[]; export type ConfigureAsyncMiddlewares = (getDefaultMiddleware: GetAsyncDefaultMiddleware) => AsyncMiddleware[]; /** @deprecated Use ConfigureAsyncMiddlewares */ export type ConfigureMiddlewares = ConfigureAsyncMiddlewares; /** * Функция миграции персистентного состояния между версиями схемы. * Вызывается при `initialize()`, если в хранилище лежат данные с версией ниже текущей * (`config.version`). Получает сырое сохранённое состояние и его версию, должна вернуть * состояние, соответствующее текущей схеме. */ export type MigrateFn<T extends Record<string, any> = Record<string, any>> = (persistedState: any, persistedVersion: number) => T; /** Базовая конфигурация хранилища (общие поля) */ export interface BaseStorageConfig<T extends Record<string, any> = Record<string, any>> { name: string; initialState?: T; singleton?: SingletonOptions; /** * Версия схемы персистентного состояния. Задайте, когда форма `initialState` меняется * между релизами и в localStorage/IndexedDB могут лежать данные старой схемы. * * Версия сохраняется рядом с данными; при следующей инициализации сравнивается с этой. * Если сохранённая версия ниже — запускается {@link BaseStorageConfig.migrate}. * * Без `version` поведение не меняется (миграция выключена). Для `memory` игнорируется * (нечего персистить). */ version?: number; /** * Преобразует сохранённое состояние старой версии к текущей схеме. Вызывается, только * если задана {@link BaseStorageConfig.version} и сохранённая версия меньше текущей. * * @example * ```ts * new LocalStorage({ * name: 'settings', * version: 2, * initialState: { theme: 'light', locale: 'en' }, * migrate: (old, fromVersion) => * fromVersion < 1 ? { theme: old.dark ? 'dark' : 'light', locale: 'en' } : { ...old, locale: old.locale ?? 'en' }, * }) * ``` */ migrate?: MigrateFn<T>; } /** Конфигурация для sync-хранилищ (Memory, LocalStorage) */ export interface SyncStorageConfig<T extends Record<string, any> = Record<string, any>> extends BaseStorageConfig<T> { middlewares?: ConfigureSyncMiddlewares; /** * Очищать данные хранилища при `destroy()`. * - `memory` → по умолчанию `true` (эфемерное хранилище). * - `localStorage` → по умолчанию `false` (персистентное: данные переживают `destroy`, как у IndexedDB). * * Задайте явно, чтобы переопределить дефолт адаптера. */ clearOnDestroy?: boolean; } /** Конфигурация для async-хранилищ (IndexedDB) */ export interface AsyncStorageConfig<T extends Record<string, any> = Record<string, any>> extends BaseStorageConfig<T> { middlewares?: ConfigureAsyncMiddlewares; } /** * @deprecated Use SyncStorageConfig or AsyncStorageConfig * Обратная совместимость — общий StorageConfig */ export interface StorageConfig<T extends Record<string, any> = Record<string, any>> { name: string; initialState?: T; middlewares?: ConfigureMiddlewares; } /** @deprecated Use SyncStorageConfig */ export interface StorageSingletonConfig<T extends Record<string, any> = Record<string, any>> extends StorageConfig<T> { singleton?: SingletonOptions; } export type MemoryStorageConfig<T extends Record<string, any> = Record<string, any>> = SyncStorageConfig<T>; export type LocalStorageConfig<T extends Record<string, any> = Record<string, any>> = SyncStorageConfig<T>; export interface IndexedDBStorageConfig<T extends Record<string, any> = Record<string, any>> extends AsyncStorageConfig<T> { options: IndexedDBConfig; } /** * Опции воркер-хранилища ({@link WorkerCacheStorage}) — ЖИВОЙ кэш поверх SharedWorker, * разделяемый между вкладками (при недоступности SharedWorker — прозрачный in-process * фолбэк без кросс-табного шеринга). */ export interface WorkerStorageOptions { /** Имя канала стора. По умолчанию — `config.name`. Одинаковый channelName → общий кэш. */ channelName?: string; /** Кастомный URL воркера (напр. собранный build-time воркер вместо inline blob). */ workerUrl?: string | URL; /** * Таймаут одной RPC-операции к стору воркера, мс. По умолчанию 1000. Увеличьте, если * начальный `getAll` большого кэша на слабом устройстве не укладывается в дефолт и * `initialize()` падает по таймауту. */ requestTimeoutMs?: number; } /** Конфигурация для воркер-хранилища ({@link WorkerCacheStorage}). */ export interface WorkerStorageConfig<T extends Record<string, any> = Record<string, any>> extends AsyncStorageConfig<T> { options?: WorkerStorageOptions; } /** Для универсальных методов (factory) — конфиг с явным типом */ export interface UniversalStorageConfig<T extends Record<string, any> = Record<string, any>> extends BaseStorageConfig<T> { type: StorageType; middlewares?: ConfigureSyncMiddlewares | ConfigureAsyncMiddlewares; options?: IndexedDBConfig; }