UNPKG

synapse-storage

Version:

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

123 lines (122 loc) 6.83 kB
import type { Observable } from 'rxjs'; import type { IStorage } from '../../core'; import type { Action, DispatchFunction, EnhancedMiddleware, WatcherFunction } from './dispatcher.module'; import { type ApiRequestState } from './standalone'; /** * Маркер метода финализации диспетчера. * * Имя экшена/вотчера берётся из имени поля класса, но прочитать имена полей можно * только ПОСЛЕ полного конструирования инстанса (инициализаторы полей derived-класса * выполняются после конструктора базового класса). Поэтому имена назначаются отдельным * шагом-финализацией: * * 1. **Сборщик `createSynapse(factory)`** вызывает `dispatcher[FINALIZE]()` до старта * эффектов (эффекты читают `actionType` при сборке пайплайна). * 2. **Ленивая само-финализация** — страховка для standalone-использования и тестов: * первый dispatch экшена или первое обращение к реестру `dispatch`/`watchers` * финализирует инстанс, если это ещё не сделано. */ export declare const FINALIZE: unique symbol; /** * Вызываемая группа жизненного цикла API-запроса. * * Сам вызов группы — это `init` (намерение): сбрасывает статус в `idle` и пробрасывает * payload намерения дальше эффектам. Жизненный цикл — через методы-поля. * * `ofType(d.loadPosts)` ловит ТОЛЬКО init; чтобы среагировать на успех — * `ofType(d.loadPosts.success)`. */ export interface ApiActions<TInitPayload = void> extends DispatchFunction<TInitPayload, TInitPayload> { loading: DispatchFunction<void, void>; success: DispatchFunction<void, void>; failure: DispatchFunction<string, void>; reset: DispatchFunction<void, void>; } /** * Keyed-вариант: статус хранится по ключу. `init`/`loading`/`success`/`reset` принимают * `key`, `failure` — `{ key, error }`. */ export interface KeyedApiActions<TInitPayload extends { key: string; } = { key: string; }> extends DispatchFunction<TInitPayload, TInitPayload> { loading: DispatchFunction<string, string>; success: DispatchFunction<string, string>; failure: DispatchFunction<{ key: string; error: string; }, { key: string; error: string; }>; reset: DispatchFunction<string, string>; } /** Опции конструктора базового диспетчера. */ export interface DispatcherBaseOptions<TState extends Record<string, any>> { middlewares?: EnhancedMiddleware<TState>[]; } /** * Публичный class-based слой диспетчера. Экшены объявляются как поля класса через * фабрики `this.action` / `this.signal` / `this.apiActions` / `this.keyedApiActions` * / `this.watcher`. Имя экшена = имя поля. * * Внутреннее состояние базы — hard-private (`#`-поля/методы): их имена в отдельном * namespace и НЕ конфликтуют с полями-экшенами подкласса. Запрещённые имена экшенов — * только `protected`/публичная поверхность из `RESERVED_NAMES`. * * @example * ```ts * class PostsDispatcher extends Dispatcher<PostsState> { * readonly loadPosts = this.apiActions<PostsFindAllParams>((s) => s.api.postsRequest) * readonly mounted = this.signal<FeedLifecyclePayload>('Лента смонтирована') * readonly applyPosts = this.action((store, page: PostsFeedResponseDto) => * store.update((s) => { s.list = page.data })) * } * ``` */ export declare abstract class Dispatcher<TState extends Record<string, any>> { #private; protected readonly storage: IStorage<TState>; /** Поток всех экшенов модуля (его потребляет EffectsModule). */ readonly action$: Observable<Action>; constructor(storage: IStorage<TState>, options?: DispatcherBaseOptions<TState>); /** Реестр экшенов по имени — для middleware/devtools. */ get dispatch(): Record<string, DispatchFunction<any, any>>; get watchers(): Record<string, WatcherFunction<any>>; /** Алиас потока экшенов для совместимости с EffectsModule (`dispatcher.actions`). */ get actions(): Observable<Action>; /** * Экшен: handler в «рецептной» сигнатуре `(storage, params) => result`. * payload экшена = возвращаемое значение handler'а. */ protected action<TParams = void, TResult = void>(handler: (storage: IStorage<TState>, params: TParams) => TResult | Promise<TResult>, options?: { type?: string; meta?: Record<string, any>; memoize?: (cur: TParams, prev: TParams, prevResult: TResult) => boolean; }): DispatchFunction<TParams, TResult>; /** Чистый сигнал: `(_store, p) => p`. `description` уходит в meta. */ protected signal<TPayload = void>(description?: string): DispatchFunction<TPayload, TPayload>; /** Вызываемая группа жизненного цикла API-запроса. Сам вызов = init (намерение). */ protected apiActions<TInitPayload = void>(accessor: (state: TState) => ApiRequestState): ApiActions<TInitPayload>; /** То же для статусов по ключу (`Record<string, ApiRequestState>`). */ protected keyedApiActions<TInitPayload extends { key: string; } = { key: string; }>(accessor: (state: TState) => Record<string, ApiRequestState>): KeyedApiActions<TInitPayload>; protected watcher<R>(config: { selector: (state: TState) => R; shouldTrigger?: (prev: R | undefined, current: R) => boolean; notifyAfterSubscribe?: boolean; type?: string; meta?: Record<string, any>; }): WatcherFunction<R>; use(...middlewares: EnhancedMiddleware<TState>[]): this; destroy(): void; /** * Финализация: скан own enumerable полей, назначение имён (`_assignType(имя поля)`) * и регистрация в реестрах `dispatch`/`watchers`. Идемпотентна. */ [FINALIZE](): void; }