synapse-storage
Version:
Набор инструментов для управления состоянием и апи-запросами
288 lines (287 loc) • 16.8 kB
TypeScript
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;
}