@equinor/fusion-observable
Version:
156 lines (155 loc) • 6.06 kB
TypeScript
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;