import { catchError, map, Observable, Subject, tap, type OperatorFunction } from 'rxjs' import type { OrchModel } from '../model' import { performer } from './performer' export type ValidEpicAction = null | (() => void) export type EpicFactory

= (params: { payload$: Observable

action: EpicAction state$: ToObservableState }) => Observable export type EpicConfig = { factoryToLog?: unknown } export type EpicAction = {

(func: (...params: P) => void, ...params: P): Exclude map

(func: (payload: P) => void): OperatorFunction } export type ToObservableState = (model: OrchModel) => Observable const action: EpicAction = Object.assign( function action

(func: (...params: P) => void, ...params: P) { return () => func(...params) }, { map

(func: (payload: P) => void): OperatorFunction { return map((value) => action(func, value)) }, }, ) const state$: ToObservableState = (model) => { return new Observable((observer) => { observer.next(model.getState()) return model.on.change(() => { observer.next(model.getState()) }) }) } export function epic

( model: OrchModel, factory: EpicFactory

, config?: EpicConfig, ) { function init() { const subject = new Subject

() const subscription = factory({ payload$: subject.asObservable(), action, state$ }) .pipe( tap((epicAction) => (Array.isArray(epicAction) ? epicAction : [epicAction]).forEach((fn) => fn?.()), ), logAngIgnoreError(config?.factoryToLog ?? factory), ) .subscribe() return { subject, subscription } } return performer(model, () => { const { subject, subscription } = init() return { next(payload) { subject.next(payload) }, dispose() { subject.complete() subscription.unsubscribe() }, } }) } function logAngIgnoreError(factory: unknown) { return catchError((err, caught) => { /* eslint-disable no-console */ console.group('[Orch]: Epic error') console.log(factory) console.error(err) console.groupEnd() /* eslint-enable no-console */ return caught }) }