UNPKG

@equinor/fusion-observable

Version:
156 lines (155 loc) 6.06 kB
import type { Reducer } from 'react'; import { Observable, type Subscription } from 'rxjs'; import type { Action, ActionType, ExtractAction } from './types/actions'; import type { Flow, Effect } from './types/flow'; import type { ReducerWithInitialState } from './types/reducers'; /** * A specialized Observable that maintains internal state mutated by dispatching actions. * * Actions are processed sequentially by a reducer function to produce new state values. * Extends `Observable` so subscribers react to state changes over time. * * Inspired by Redux-style state management but built on top of RxJS, `FlowSubject` * combines a `BehaviorSubject` for state with a `Subject` for actions, providing * a reactive, observable state container. * * @template S - The state type managed by this subject. * @template A - The action type dispatched to this subject. * * @example * ```ts * import { FlowSubject, createReducer } from '@equinor/fusion-observable'; * * type State = { count: number }; * type Action = { type: 'increment' } | { type: 'decrement' }; * * const reducer = createReducer<State, Action>({ count: 0 }, (builder) => * builder * .addCase('increment', (state) => { state.count += 1; }) * .addCase('decrement', (state) => { state.count -= 1; }), * ); * * const subject = new FlowSubject(reducer); * subject.subscribe((state) => console.log(state.count)); * subject.next({ type: 'increment' }); // logs: 1 * ``` */ export declare class FlowSubject<S, A extends Action = Action> extends Observable<S> { #private; /** * Observable stream of actions dispatched to the subject. */ get action$(): Observable<A>; /** * The current value of state. */ get value(): S; /** * Flag to indicate if the observable is closed. */ get closed(): boolean; /** * Creates a new instance of the FlowSubject class with an initial state reducer. * @param reducer A reducer with an initial state or a reducer function. */ constructor(reducer: ReducerWithInitialState<S, A>); /** * Create a new instance of the FlowSubject class with a reducer function and initial state. * @param reducer state reducer * @param initialState initial state */ constructor(reducer: Reducer<S, A>, initialState: S); /** * Resets the state to the initial value. */ reset: VoidFunction; /** * Dispatches an action to the subject. * * @param action The action to dispatch. */ next(action: A): void; /** * Selects a derived state from the observable state and emits only when the selected state changes. * * @param selector - A function that takes the current state and returns a derived state. * @param comparator - An optional function that compares the previous and current derived states to determine if a change has occurred. * @returns An observable that emits the derived state whenever it changes. */ select<T>(selector: (state: S) => T, comparator?: (previous: T, current: T) => boolean): Observable<T>; /** * Adds an effect that listens for actions and performs side effects. * * @param fn The effect function to execute. * @returns A subscription to the effect. */ addEffect(fn: Effect<A, S>): Subscription; /** * Adds an effect that listens for a specific action type and performs side effects. * * @param actionType The type of action to listen for. * @param cb The effect function to execute when the action is dispatched. * @returns A subscription to the effect. */ addEffect<TType extends ActionType<A>>(actionType: TType, cb: Effect<ExtractAction<A, NoInfer<TType>>, S>): Subscription; /** * Adds an effect that listens for an array of action types and performs side effects. * * @param actionType The array of action types to listen for. * @param cb The effect function to execute when one of the actions is dispatched. * @returns A subscription to the effect. */ addEffect<TType extends ActionType<A>>(actionType: Array<TType>, cb: Effect<ExtractAction<A, NoInfer<TType>>, S>): Subscription; /** * @deprecated Use {@link FlowSubject.addFlow} instead. * * @param fn - The flow function to execute. * @returns A subscription to the flow. */ addEpic(fn: Flow<A, S>): Subscription; /** * Adds a flow (epic-style) that transforms the action stream and optionally * reads state, then emits new actions back into the subject. * * Flows receive the full `action$` stream and the state observable, and must * return an `Observable<A>`. This is useful for complex async orchestration. * * @param fn - The flow function that receives `(action$, state$)` and returns an action observable. * @returns A subscription to the flow. Unsubscribe to remove the flow. * @throws {TypeError} If the flow function does not return an observable. * * @example * ```ts * subject.addFlow((action$) => * action$.pipe( * filterAction('fetchData'), * switchMap((action) => * fetchApi(action.payload).pipe( * map((data) => ({ type: 'fetchSuccess', payload: data })), * ), * ), * ), * ); * ``` */ addFlow(fn: Flow<A, S>): Subscription; /** * Tears down the subject by unsubscribing both the action and state subjects. * * After calling this method, the subject is no longer usable. */ unsubscribe(): void; /** * Completes both the action and state subjects, signalling to all * subscribers that no further values will be emitted. */ complete(): void; /** * Returns a plain `Observable` of the state, hiding the subject's * `next`, `complete`, and other mutation methods. * * @returns A read-only observable of the state. */ asObservable(): Observable<S>; } export default FlowSubject;