import assert from '../internal/assert'
import checkUnique from '../internal/checkUnique'
import {bindActionFunctions} from './createChannels'
export function bindSagaHandler(channel, sagaName, sagaHandler) {
return AppDispatcher =>
AppDispatcher
.filter(x => x.channel === channel && x.actionType === sagaName)
.flatMap(x => sagaHandler(x.payload))
.map(result => {
emit the result back to the app dispatcher for time travel.
setTimeout(() => AppDispatcher.emit({channel, actionType: `${sagaName}Result`, payload: result}), 0)
return result
})
}
function _bindSagaHandlers(channel, ActionTypes, SagaHandlers) {
return AppDispatcher =>
Object.keys(ActionTypes).reduce(
(observables, saga) => {
const handler = SagaHandlers[saga]
const observable = bindSagaHandler(channel, saga, handler)(AppDispatcher)
return Object.assign(observables, {[saga]: observable})
},
{}
)
}
function _bindSagaResultObservables(sagas) {
return Object.keys(sagas).reduce(
(observables, saga) => Object.assign(observables, {[`${saga}ResultObservable`]: sagas[saga]}),
{}
)
}
/**
* Sagas are primarily used for handling ajax workflows.
*
* @param {Object} opts
* @param {string} opts.channel - the name of the saga collection
* @param {Map<string,*>} opts.ActionTypes - the names of the action types (aka side
* effects) these sagas handle
* @param {Map<ActionType,Function>} opts.SagaActionFunctions - (optional) map of action
* functions indexed by ActionType. Like a channel's action functions, these functions
* can be used to initiate a saga. Note that if you include one action function, then
* every ActionType must have a corresponding action function.
* @param {Function} opts.SagaHandlersFn - higher order function with signature
* `({...sagaInterface})=>SagaHandlers` that accepts the `sagas` interface object and
* returns the SagaHandlers. The SagaHandlers are a map of functions indexed by
* ActionType i.e, Map<ActionType,Function>.
* @return {Saga} higher order function that creates the saga.
* @private
*/
export function _createSagas({channel, ActionTypes, SagaActionFunctions = {}, SagaHandlersFn}) {
assert(typeof channel === 'string', 'Needs a channel and it needs to be a string')
assert(ActionTypes, 'Need ActionTypes')
assert(SagaHandlersFn, 'Need SagaHandlersFn')
assert(typeof SagaHandlersFn === 'function', 'SagaHandlersFn should be a higher order function')
return ({AppDispatcher, sagaInterface}) => {
/* eslint-disable new-cap */
const SagaHandlers = SagaHandlersFn({...sagaInterface})
/* eslint-enable */
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}"`)
})
const observables = _bindSagaHandlers(channel, ActionTypes, SagaHandlers)(AppDispatcher)
return {
name: channel,
observables,
actionFunctions: bindActionFunctions(ActionTypes, SagaActionFunctions)(AppDispatcher),
resultObservables: _bindSagaResultObservables(observables)
}
}
}
export default function createSagas({rawSagas, ...args}) {
checkUnique(rawSagas, 'channel', 'Cannot have two sagas with the same name')
return rawSagas.map(s => _createSagas(s)({...args}))
}