UNPKG

@johngw/stream

Version:

Reactive programming tools using the WHATWG Streams API.

132 lines (127 loc) 3.79 kB
import { ForkableRecallStream } from '@johngw/stream/sinks/ForkableRecallStream' import { stateReducer, StateReducerInput, StateReducerOutput, StateReducers, } from '@johngw/stream/transformers/stateReducer' import { Subject, SubjectOptions } from '@johngw/stream/subjects/Subject' import { ControllableStream } from '@johngw/stream/sources/ControllableStream' /** * The constructor options for a {@link StatefulSubject}. * * @group Subjects */ interface StatefulSubjectOptions<Actions extends Record<string, unknown>, State> extends Omit< SubjectOptions< StateReducerInput<Actions>, StateReducerOutput<Actions, State> >, 'forkable' | 'transform' > { pipeThroughOptions?: StreamPipeOptions } /** * Another form of the {@link Subject:class} that reduces a piece * of state with actions and queues changes to the stream. * * It's important to note that if the result of an action does not change * the state then nothing is queued to the stream. * * @group Subjects * @see {@link stateReducer:function} * @example * ``` * // The State will be the type of data you want to manipulate. * interface State { * authors: string[] * } * * // The Actions are a record of "action name" to a parameter. * // If the action doesn't require a parameter, use `void`. * type Actions = { * 'add author': string * 'action that doesnt need a parameter': void * } * * // To get the best out of this API, pass the Actions type and State * // during construction. * const subject = new StatefulSubject<Actions, State>( * { * __INIT__: () => ({ authors: [] }), * * 'add author': (state, author) => state.authors.includes(author) ? state : { * ...state, * authors: [...state.authors, author], * }, * * 'action that doesnt need a parameter': (state) => state * } * ) * * subject.fork().pipeTo(write(console.info)) * // { action: '__INIT__', state: { authors: [] } } * * subject.dispatch('add author', 'Jane Austin') * // { action: 'add author', param: 'Jane Austin', state: { authors: ['Jane Austin'] } } * * subject.dispatch('add author', 'Jane Austin') * // **Nothing will be queued as the state did not change.** * ``` */ export class StatefulSubject< Actions extends Record<string, unknown>, State > extends Subject< StateReducerInput<Actions>, StateReducerOutput<Actions, State> > { constructor( reducers: StateReducers<Actions, State>, options: StatefulSubjectOptions<Actions, State> = {} ) { super({ ...options, forkable: new ForkableRecallStream(), transform: stateReducer(reducers), }) } /** * Returns a new {@link StatefulControllableStream}. Once all controllers * have been closed, then the source is also closed. */ override control() { const subjectController = super.control() return Object.assign(subjectController, { /** * Parameterised version of enqueue for simpliclity. * * @example * ``` * subject.dispatch('action', 'param') * // Instead of * subject.enqueue({ action: 'action', param: 'param' }) * ``` */ dispatch<Action extends keyof Actions>( ...args: Actions[Action] extends void ? [action: Action] : [action: Action, param: Actions[Action]] ) { subjectController.enqueue({ action: args[0], param: args[1], } as StateReducerInput<Actions>) }, }) as StatefulSubjectController<Actions> } } export interface StatefulSubjectController< Actions extends Record<string, unknown> > extends ControllableStream<StateReducerInput<Actions>> { dispatch<Action extends keyof Actions>( action: Action, param?: Actions[Action] ): void }