From redux-observable
redux-observable epics are RxJS pipelines over the action stream. The most common shape — “filter by type, do an async thing, emit a result action” — maps directly to a Triggery handler. Anything that combines multiple streams reactively (combineLatest, withLatestFrom) is where Triggery’s pull-only model and RxJS’s push-only model start to disagree.
Mental model mapping
Section titled “Mental model mapping”| redux-observable / RxJS | Triggery |
|---|---|
ofType('foo') filter | events: ['foo'] |
ofType('foo', 'bar') | events: ['foo', 'bar'] |
debounceTime(ms) on the source | actions.debounce(ms).out?.(p) for outputs; manual for input gating |
throttleTime(ms) | actions.throttle(ms).out?.(p) |
switchMap | concurrency: 'take-latest' (default) |
mergeMap | concurrency: 'take-every' |
exhaustMap | concurrency: 'exhaust' |
concatMap | concurrency: 'queue' |
map(action => …) emit-action | actions.someAction?.(…) |
withLatestFrom(state$, sel) | conditions.someName registered via useReduxCondition |
combineLatest([a$, b$, c$]) | No direct mapping — see below |
takeUntil(stop$) | signal.aborted checks in async handler |
Pattern 1 — ofType + switchMap
Section titled “Pattern 1 — ofType + switchMap”switchMap = take-latest. They have the same semantics: a new input cancels the previous in-flight task.
Pattern 2 — debounceTime on input
Section titled “Pattern 2 — debounceTime on input”Triggery does not gate inputs with debounce — the convention is to gate the output:
The reactor on the other end (useAction(trigger, 'runSearch', q => /* fetch */)) receives only the last call within the window.
Pattern 3 — withLatestFrom
Section titled “Pattern 3 — withLatestFrom”What doesn’t map cleanly
Section titled “What doesn’t map cleanly”combineLatestof multiple streams. Triggers respond to one event at a time and pull conditions lazily — there’s no “fire when any of these streams emits”. Either pick one as the trigger event and treat the rest as conditions, or keep that piece as an epic / RxJS pipeline andfireEventfrom asubscribe.- Multicasting with
shareReplay. RxJS’s hot/cold semantics have no analogue in Triggery; if you need them, keep RxJS. - Backpressure operators (
bufferTime,windowToggle). Triggery’s scheduler batches per-tick microtasks, but it doesn’t implement RxJS’s backpressure toolkit.
When to keep RxJS
Section titled “When to keep RxJS”Keep RxJS for the truly stream-heavy parts: animation pipelines, complex multi-source synchronisation, anywhere combineLatest / merge / partition are doing real work. The two libraries cohabit fine — a trigger’s handler can subscribe to a one-shot observable, or an epic can dispatch an action that Triggery happens to listen to.