synapse-storage
Version:
Набор инструментов для управления состоянием и апи-запросами
70 lines (69 loc) • 5 kB
TypeScript
import { IEventEmitter, ILogger, StorageCapabilities, StorageType, WorkerStorageConfig } from '../storage.interface';
import { StorageKeyType } from '../utils/storage-key';
import { AsyncBaseStorage } from './async-base-storage.service';
/**
* Асинхронное хранилище поверх общего key-value стора внутри SharedWorker.
*
* Это тот же принцип, что у {@link MemoryStorage} (данные в `Map`), но `Map` живёт
* ВНУТРИ SharedWorker и потому разделяется между вкладками. Если SharedWorker
* недоступен (тесты/SSR/CSP), {@link WorkerChannel} прозрачно откатывается на
* in-process `Map` — round-trip идентичен MemoryStorage, но БЕЗ межвкладочного
* шеринга (это даёт только настоящий SharedWorker).
*
* Все `do*` реализованы через RPC к стору ({@link WorkerChannel.request}) и повторяют
* маппинг ключей/путей MemoryStorage (те же `getValueByPath`/`setValueByPath`/
* `removeValueByPath`), поэтому поведение совпадает с MemoryStorage «ключ-в-ключ».
*
* Записи атомарны на уровне ключа (per-key `set`/`update`/`delete`), поэтому
* конкурентные вкладки не затирают чужие ключи. После мутации из другой вкладки
* стор рассылает уведомление — адаптер обновляет свой кэш и будит подписчиков.
*/
export declare class WorkerCacheStorage<T extends Record<string, any>> extends AsyncBaseStorage<T> {
protected static readonly STORAGE_TYPE: StorageType;
readonly type: StorageType;
/** Живой кэш; `shared` честно отражает, реально ли работает кросс-табный SharedWorker. */
readonly capabilities: StorageCapabilities;
private readonly channelName;
private readonly backend;
private unsubscribeMutation?;
private refreshInFlight;
private refreshDirty;
constructor(config: WorkerStorageConfig<T>, eventEmitter?: IEventEmitter, logger?: ILogger);
static create<T extends Record<string, any>>(config: WorkerStorageConfig<T>, eventEmitter?: IEventEmitter, logger?: ILogger): WorkerCacheStorage<T>;
protected doInitialize(): Promise<this>;
/**
* Подписка на мутации из ДРУГИХ вкладок/экземпляров. Воркер рассылает STORE_MUTATION
* после каждой мутирующей операции; payload несёт только `{op}`, поэтому мы просто
* перечитываем полное состояние, обновляем `_stateCache` и будим подписчиков (как
* при локальной записи). Без этого хука `subscribe()` и tagIndex в QueryStorage
* устаревали бы для записей, сделанных другими вкладками.
*/
private subscribeToStoreMutations;
/**
* Сериализует refresh'и: пока один в полёте, следующие мутации лишь взводят `dirty`,
* и после завершения текущего прогоняется ровно один догоняющий refresh. Так каждый
* refresh диффит против актуального `_stateCache`, а не против устаревшего снапшота —
* нет дублей уведомлений подписчиков одними и теми же значениями.
*/
private scheduleRefresh;
private runRefreshLoop;
private refreshFromRemoteMutation;
protected doGet(key: StorageKeyType): Promise<any>;
protected doSet(key: StorageKeyType, value: any): Promise<void>;
protected doUpdate(updates: Array<{
key: StorageKeyType;
value: any;
}>): Promise<void>;
protected doDelete(key: StorageKeyType): Promise<boolean>;
protected doClear(): Promise<void>;
protected doKeys(): Promise<string[]>;
protected doHas(key: StorageKeyType): Promise<boolean>;
/**
* Живой кэш НЕ очищается при destroy: данные живут в воркере и разделяются между
* вкладками — уничтожение адаптера одной вкладкой не должно стирать общий кэш.
* Поэтому переопределяем performCleanup (как IndexedDBStorage), пропуская doClear(),
* но ОБЯЗАТЕЛЬНО прогоняя cleanup мидлвар (иначе их каналы/таймеры текут — R8).
*/
protected performCleanup(): Promise<void>;
protected doDestroy(): Promise<void>;
}