import assert from '../internal/assert'
import Kefir from 'kefir'
import kefirEmitter from '../utils/kefirEmitter'
import isObservable from '../internal/isObservable'
import uuid from '../internal/uuid'
export const fakeAppDispatcher = kefirEmitter()
export const sagaMessageBus = kefirEmitter()
/**
* Call the action and return the result (as an observable)
*/
const callObservable = sagaMessageBus
.filter(action => action.action === 'CALL')
.map(action => action.payload)
.flatMap(action => {
const callId = action.callId
const result = action.fn(...action.args)
const resultObservable = isObservable(result) ? result : Kefir.constant(result)
return resultObservable.map(rslt => ({callId, rslt}))
})
.onValue(() => undefined)
/**
* @param {string} channel
* @param {Object} opts
* @param {Map} opts.ActionTypes - map whose keys are the names of the side effects
* @param {Map} opts.SagaActionFunctions - (optional) map of action functions
* @param {Map} opts.SagaHandlers - map of handler functions
* @returns {Object} redux middleware
*/
export function reduxSagaMiddleware(channel, {ActionTypes, SagaActionFunctions, SagaHandlers}) {
assert(typeof channel === 'string', 'Needs a channel and it needs to be a string')
assert(ActionTypes, 'Need ActionTypes')
assert(SagaHandlers, 'Need SagaHandlers')
every side effect must map to an action function and handler
Object.keys(ActionTypes).forEach(action => {
if (SagaActionFunctions) {
assert(SagaActionFunctions[action], `Channel ${channel} is missing side effect action function "${action}"`)
}
assert(SagaHandlers[action], `Channel ${channel} is missing side effect handler "${action}"`)
})
SagaActionFunctions = SagaActionFunctions || {}
return () => next => action => {
setTimeout(() => SagaHandlers[action.type](action), 0)
setTimeout(() => fakeAppDispatcher.emit(action), 0)
return next(action)
}
}
export class ReduxSaga {
constructor(channel) {
this.channel = channel
}
put(action) {
setTimeout(() => this.channel.dispatch(action), 0)
return Kefir.constant(action) // streamified so we can chain together
}
call(fn, ...args) {
const callId = uuid()
setTimeout(() => sagaMessageBus.emit({action: 'CALL', payload: {fn, args, callId}}), 0)
return callObservable.filter(fn => fn.callId === callId).map(fn => fn.rslt).take(1)
}
listen(channel, actionType) {
return fakeAppDispatcher
.filter(action => action.channel === channel && action.actionType === actionType)
.map(action => action.payload)
}
}