Skip to content

Latest commit

 

History

History
616 lines (472 loc) · 34.6 KB

api.md

File metadata and controls

616 lines (472 loc) · 34.6 KB

redux-logic API

Contents:

Installation

redux-logic uses rxjs@6 under the covers and to prevent multiple copies (of different versions) from being installed, it is recommended to install rxjs first before redux-logic. That way you can use the same copy of rxjs elsewhere.

If you are never using rxjs outside of redux-logic and don't plan to use Observables directly in your logic then you can skip the rxjs install and it will be installed as a redux-logic dependency. However if you think you might use Observables directly in the future (possibly creating Observables in your logic), it is still recommended to install rxjs separately first just to help ensure that only one copy will be in the project.

The rxjs install below npm install rxjs@^6 installs the lastest 6.x.x version of rxjs.

npm install rxjs@^6 --save  # optional see note above
npm install redux-logic --save

Main usage

createLogic

import { createLogic } from 'redux-logic';

/* returns a logic object that resembles the same structure of the
   input except that some defaults are applied and values were
   validated. You can directly access your hook functions from the
   object to perform isolated testing. Use the validate, transform,
   and process properties of the returned logic object */
const fooLogic = createLogic({
  // optional name - used in monitor$ and error messages, the default name assigned is L(TYPE)-N where TYPE is the action type(s) and N is the index in the logic array
  name: 'someLogicName',

  // filtering/canceling
  type: T, // required string, regex, Symbol, array of str/regex/Symbol, use '*' for all
  cancelType: CT, // string, regex, Symbol, array of str/regex/Symbol
  // type and cancelType also support redux-actions fns for which
  //   the fn.toString() returns the associated action type

  // limiting - optionally define any of these
  debounce: 0, // debounce for N ms, default 0
  throttle: 0, // throttle for N ms, default 0
  latest: true, // only take latest, default false

  // In non-production mode only, write to console.error if logic does
  // not complete by this time in milliseconds.
  // Set to `0` for non-ending logic.
  warnTimeout: 60000, // default: 60000 (one minute)

  // Put your business logic into one or more of these
  // execution phase hooks: validate, transform, process
  //
  // Note: If you provided any optional dependencies in your
  // createLogicMiddleware call, then these will be provided to
  // your code in the first argument along with getState and action
  // See advanced section for more details
  validate({ getState, action }, allow, reject) {
    // run your verification logic and then call allow or reject
    // with the action to pass along. You may pass the original action
    // or a modified/different action. Use undefined to prevent any
    // action from being propagated like allow() or reject()
    // If reject is used then the process hook will not be executed
    allow(action); // OR reject(action)
  }),

  // Note: transform is just an alias for the validate hook (next = allow)
  // to communicate clearer intent, you can do the same things in either
  transform({ getState, action }, next /*, reject */) {
    // perform any transformation and provide the new action to next
    next(action);
  }),


  // options influencing the process hook, defaults to {}
  processOptions: {
    // dispatch return value, or if returns promise/observable, dispatch resolved/next values
    // default false unless dispatch cb is omitted from process signature
    dispatchReturn: false,
    // enable multi-dispatch mode until done cb called or cancelled
    // default is false unless done is included in process signature
    dispatchMultiple: false,
    // string or action creator fn wrapping dispatched value
    successType: undefined, // default undefined
    // string or action creator fn wrapping dispatched, rejected, or thrown errors
    failType: undefined // default undefined
  },

  // If validate/transform reject was used then this hook will not be
  // executed. Including dispatch and/or done callbacks will influence
  // the default dispatching mode:
  // 1. Neither dispatch, nor done - dispatches the returned/resolved val
  // 2. Only dispatch - single dispatch mode, call dispatch exactly once (deprecated)
  // 3. Both dispatch and done - multi-dispatch mode, call done when finished
  // More details on dispatching modes are in the advanced API docs
  process({ getState, action, cancelled$ }, ?dispatch, ?done) {
    // Perform your processing then call dispatch with an action
    // Empty or undefined dispatch can be used if nothing to dispatch
    // See advanced API for discussion of dispatching modes
    dispatch(myNewAction); // in single dispatch mode this also completes
    done(); // only when performing multiple dispatches (done in signature)
  })
});

configureLogic

import { configureLogic } from 'redux-logic';

// globally configure ALL Logic instance defaults instead of overriding these
// properties on each call to `createLogic`. These defaults will not be applied
// to `Logic` instances that have already been created.
//
// specify all or a subset of the following properties:
configureLogic({

  // provide different default values than the package defaults.
  warnTimeout: 10000,
})

createLogicMiddleware

import { createLogicMiddleware } from 'redux-logic';

const logicMiddleware = createLogicMiddleware(
  arrLogic, // array of logic items, no duplicate refs to same logic
  deps   // optional injected deps/config, supplied to logic
);

// dynamically add injected deps at runtime after createStore has been
// called. These are made available to the logic hooks. The properties
// of the object `additionalDeps` will be added to the existing deps.
// For safety, this method does not allow existing deps to be overridden
// with a new value. It will throw an error if you try to override
// with a new value (setting to the same value/instance is fine).
logicMiddleware.addDeps(additionalDeps);

// dynamically add logic later at runtime, keeping logic state
// new logic should be unique and not refs to previous logic
// it will be added to the end of the existing chain
logicMiddleware.addLogic(arrNewLogic);

// dynamically add new logic later at runtime, keeping logic state
// only adds logic that it doesn't already have, so it is safe to
// use with split route bundles loading logic on the fly.
// New logic is added to the end of the existing chain.
logicMiddleware.mergeNewLogic(arrMergeLogic);

// replacing logic, logic state is reset but in-flight logic
// should still complete properly
// If arrReplacementLogic should be unique
logicMiddleware.replaceLogic(arrReplacementLogic);

// for server-side use, runs optional fn and returns promise
// when all in-flight processing/loading has completed.
// Use it after performing any required store.dispatch
store.dispatch(ROUTE_CHANGE); // triggers async loading in our logic
store.dispatch(FOO); // any number of additional dispatches
return logicMiddleware.whenComplete(() => { // returns promise
  return store.getState(); // can serialize store, loading was done
});

// observable for monitoring the internal operations, see advanced
logicMiddleware.monitor$

// A logicMiddleware instance can only be used with one store, it
// will throw an error if you try to reassociate it with another store.
// Simply create a new instance for each store that you are using.

Execution phase hooks - validate, transform, process

The execution phase hooks are the places where you can hook your business logic code to be run during execution.

The validate and transform hooks are just aliases for the same hook internally named validtrans. The two names are just to help convey intent, you can do the same things in either including validation and transformation.

validate/transform happen before the other middleware and reducers have had a chance to see the action so you may check the state of your app and decide to allow the action as is, modify or augment it, change it to a different action, or choose to pass nothing along. You call allow, reject, or next once with the action to pass along or empty to pass nothing (like allow()). If you call reject then the process execution hook will not be executed.

The process hook only runs if allow or next was called. It happens after the action from allow/next was passed onto the other logic, middleware, and reducers. Assuming there was no delays or asynchronicity created from other logic or middleware then the getState() will return the state after the actions have been processed by the reducers.

You may implement one or more hooks depending on your business logic. The lifecycle is as follows:

  1. validate(depObj, allow, reject) OR transform(depObj, next /*, reject */)

  2. Action from allow/reject/next is passed along to other logic, middleware, and reducers

  3. process(depObj, dispatch?, done?) // only called if allow/next was called

Each depObj contains getState, action, along with any user injected deps and a few other advanced properties. See advanced section for full details.

validate hook

This hook is run first and it allows your business logic to access the full state (prior to reducers updating state for this action), getState() and the action.

You may perform sync or async business logic and when ready call allow or reject. allow(action) or reject(action). allow or reject must be called exactly once when you are done validating or verifying.

Calling allow or reject influences whether the process hook will be run. If reject was called then process hook will not be run.

Note that you pass an action to allow and reject as the first argument. You may also call it with undefined if you do not wish to have anything propagate further. allow() or reject(). Process hook would still be executed for allow() but not reject().

You can augment, modify, change actions however you want in your allow or reject call.

By default, allow and reject will pass whatever action they are given to the next middleware or reducer unless the type has changed from the original action type. If it has changed then the action will instead be dispatched so that all middleware will have an opportunity to see it prior to it hitting reducers. This should be fine for most types of uses, however if you need to override this automatic mechanism, see the advanced section.

The default validation hook if not provided is:

validate({ getState, action }, allow, reject) {
  allow(action); // allow all
}

transform hook (alias for validate hook)

The transform hook is just another alias for the validate hook which helps you convey the intent of your business logic. Since it is the same step it can do the same things validating, transforming, etc. allow is named next since that is what makes more sense in a transformation type logic. Normally you wouldn't need the reject but it is also available.

You might use transformations to modify or augument actions (like adding a unique ID, timestamp, looking up data and including, etc).

Just like with the validate hook you are expected to call the next function (or reject function) one time passing it the modified action or empty next() to pass nothing. Calling next will enable the process hook to eventually run, but calling reject would prevent process hook from running the same as explained in the validate hook. See the validate hook for the details about how it decides whether to pass along the action or to dispatch it. The advanced section discusses how that can be overridden.

The default transform hook is (same as default validation hook):

transform({ getState, action }, next /*, reject */) {
  next(action); // passes original
}

process hook

The process hook is run asynchronously after the middlware calls next, so typically this means that the state will have been updated by the reducers (unless there are any other async middleware or logic delaying execution). Thus the process hook's getState will refer to recently updated state.

The process hook is only executed if the validate/transform hook allow was called. If reject was called then the process hook will not be executed.

The process hook is an ideal place to make async requests and then dispatch the results or an error.

The dispatch function returns the value passed into it to make it easy to use in promise chaining.

If you set the processOptions object, you can further influence how process behaves streamlining your code.

  • processOptions.dispatchReturn - if true, then process will use the returned value to dispatch. If you return a promise then it will use the resolve/reject values for dispatching. If you return an observable then it will use its values or error for dispatching. Returning an undefined, promise that resolves to undefined, or observable value of undefined will cause no dispatch. Default varies based on whether the process fn signature includes dispatch or not. If dispatch is included then dispatchReturn defaults to false and vice versa.
  • processOptions.dispatchMultiple - if true, then dispatch function will not end the underlying observable until the done callback is called or it is cancelled. The default value for dispatchMultiple is determined by the process fn signature, if it includes the done callback then the default is true otherwise it is false. Note: in a future version, this option will be always on. If your logic does not end, set the top level option warnTimeout: 0.
  • processOptions.successType - if set to an action type string or an action creator function it will use this to create the action from the dispatched value. If the successType was a string then it will create an action of this type and set the payload to the dispatched value (following FSA format). If it was an action creator function then it will pass the value to the action creator and then dispatch that. Default undefined.
  • processOptions.failType - if set to an action type string or an action creator function it will use this to create the action from the dispatched error or rejected promise value or errored observable similar to how successType works. If failType was a string then it uses the FSA error format otherwise it calls the action creator provided. If failType is not defined and an error is thrown or dispatched that does not itself have a type (action type), then an UNHANDLED_LOGIC_ERROR will be dispatched with the error as the payload. Default undefined.

The process hook's full signature is process(deps, dispatch?, done?).

If the done callback is provided in your code then it is assumed that you want to do multiple dispatching and thus you need to call done when you are finished.

You can omit the done and the process hook switches into single dispatch mode (this is deprecated), expecting that dispatch would be called exactly once. If you determine you don't need to dispatch anything then you can make an empty dispatch(). This mode is deprecated and will be removed in a future version.

If you prefer to use promises, async/await, or observables then redux-logic makes it easy to use those as well (see discussion later).

The default process hook if none is provided is:

process({ getState, action }, dispatch, done) {
  dispatch(); // dispatch nothing and complete
  done(); // done dispatching
}

An example of using processOptions:

import { createLogic } from 'redux-logic';

const logic = createLogic({
  type: FOO,
  processOptions: {
    // if your process fn below omits the dispatch cb then
    // dispatchReturn will default to true, so you could skip
    // this line, but it is left for clarity
    dispatchReturn: true,       // use my return for dispatch
    successType: 'FOO_SUCCESS', // my action type for success
    failType: 'FOO_ERROR',      // my action type for failure
  },
  process({ getState, action }) {
    // since we didn't include dispatch cb in our signature
    // dispatchReturn will default to true,  actions are created
    // from the returned obj, resolved/rejected promise or obs
    return axios.get('https://server/api/users')
      .then(resp => resp.data.users); // select the data
  }
}

Advanced Usage

dispatch and return (when dispatchReturn:true) handling of process hook

When you call your process hook dispatch function with a value or when you return a value from the process hook (when dispatchReturn:true or omitting dispatch/done in process hook):

  • undefined - nothing will be dispatched
  • Error - check failType OR if has type, dispatch it, otherwise wrap with UNHANDLED_LOGIC_ERROR action with the error as the payload.
  • Promise - once promise resolves or rejects, apply the rules:
    • resolving - check successType OR if truthy dispatch
    • rejecting - check failType OR handle as Error
  • Observable - as observable emits next or error, apply the rules:
    • next - check successType OR if truthy dispatch
    • error - check failType OR handle as Error
  • null - check successType OR dispatch nothing
  • Object, Function, or anything else truthy - check successType OR dispatch

The processOptions section next explain how successType and/or failType options can wrap your values into an action object.

processOptions - influencing process hook

Simplifying code by declaring additional properties in processOptions.

  • dispatchReturn - the returned value of the process function will be dispatched or if it is a promise or observable then the resolve, reject, or observable values will be dispatched applying any successType or failType logic if defined. Default is determined by arity of process fn, true if dispatch not provided, false otherwise. Most users will simply omit or include dispatch/done in the process hook to enable or disable this property. Details

  • successType - dispatch this action type using contents of dispatch as the payload (also would work with with promise or observable). You may alternatively provide an action creator function to use instead and it will receive the value as only parameter. Default: undefined.

    • if successType is a string action type

      • create action using successType and provide value as payload. ex: with successType:'FOO', result would be { type: 'FOO', payload: value }
    • if successType is an action creator fn receiving the value as only parameter

      • use the return value from the action creator fn for dispatching ex: successType: x => ({ type: 'FOO', payload: x })
      • if the action creator fn returns a falsey value like undefined then nothing will be dispatched. This allows your action creator to control whether something is actually dispatched based on the value provided to it.
  • failType - dispatch this action type using contents of error as the payload, sets error: true (would also work for rejects of promises or error from observable). You may alternatively provide an action creator function to use instead which will receive the error as the only parameter. Default: undefined.

    • if failType is a string action type

      • create action using failType, provide value as the payload, and set error to true. ex: with failType:'BAR', result would be { type: 'BAR', payload: errorValue, error: true }
    • if failType is an action creator function receiving the error value as its only parameter

      • use the return value from the action creator fn for dispatching. ex: failType: x => ({ type: 'BAR', payload: x, error: true })
      • if the action creator fn returns a falsey value like undefined then nothing will be dispatched. This allows your action creator to control whether something is actually dispatched based on teh value provided to it.

The successType and failType would enable clean code, where you can simply return a promise or observable that resolves to the payload and rejects on error. The resulting code doesn't have to deal with dispatch and actions directly.

  • dispatchMultiple - Normally this is set automatically based on the arity of the function provided as the process hook. This governs whether process expects a single or multiple dispatches. Details. It is likely that a future version of redux-logic will drop the single dispatch mode to prevent confusion and misuse after which case this property will no longer be needed.

Additional properties available to execution hooks

The first argument of each execution phase hook, depObj, contains any dependencies that were supplied to the createLogicMiddleware command as well as built-in properties.

The signature of each execution phase hook is:

validate(depObj, allow, reject)
transform(depObj, next, ?reject)
process(depObj, ?dispatch, ?done)

Supplying dependencies to createLogicMiddleware makes it easy to create testable code since you can use different injected deps in your tests than you do at runtime. It also makes it easy to inject different config or connections in development, staging, and production. Use of these is optional.

There are also built-in properties supplied to the execution hooks regardless of whether you supply any dependencies or not. These are merged in at runtime.

  • getState - the store.getState function is provided so logic can get access to the full state of the app. In the validate and transform hooks this will be the state before the reducers have updated anything for this action. For the process hook, the reducers should have been run (unless there are other middleware introducing async delays).
  • action - in validate/transform hook this is the action that triggered the logic to run. In the process hook it will be the action passed on by the transform hook, or if it was falsey then the original action will be provided.
  • ctx - initially an empty object representing a shared place that you can use to pass data between the validate/transform and process hooks if you have implemented more than one of them. For instance if you set the ctx.foo = {a: 1} in your validate hook, then the process hook can read the previous value.
  • cancelled$ - an observable that emits if the logic is cancelled. This observable will also complete when the hooks have finished, regardless of whether it was cancelled. Subscribing to the cancelled$.next allows you to respond to a cancellation performing any additional cleanup that you need to do. For instance if you had a long running web socket connection, you might close it. Normally you won't need to use this unless there is something you need to close from your end. Even without using cancelled$ future dispatching is stopped, so use of this is only necessary for cleanup or termination of resources you created.
  • action$ - the action observable stream which can be used to created advanced flows like for drag/drop, web sockets etc.

Cancellation / take latest - XHR aborting

Many libraries like fetch don't support aborting/cancelling of an in-flight request, so the best that we can do is to simply not dispatch the results for cancelled or outdated requests. Unfortunately some browsers could delay your new requests while the previous ones are still outstanding (they have limits for concurrent requests).

However if you use a HTTP library that returns an observable like RxJS DOM ajax, then you can dispatch the observable to redux-logic and XHR abort will be performed on in-flight requests if they are cancelled or in a take latest situation. Observables support cancellation and thus when redux-logic cancels the subscription it bubbles back to the source and causes an xhr abort like you would want. Thus if you need true xhr aborts, use RxJS DOM's ajax or similar API that returns an observable and dispatch that to redux-logic.

import { createLogic } from 'redux-logic';
import Rx from 'rxjs'; // or selectively import only what you need
const ajax = Rx.Observable.ajax;

const usersFetchLogic = createLogic({
  type: USERS_FETCH,
  cancelType: USERS_FETCH_CANCEL,
  latest: true, // take latest only

  // By not including done in the hook, process is in single dispatch
  // mode so it expects dispatch to be called exactly once
  process({ getState, action }, dispatch) {
    // dispatch the values from this observable
    // cancel and take latest will abort in-flight xhr requests
    dispatch(
      // the delay query param adds arbitrary delay to the response
      ajax.getJSON(`http://reqres.in/api/users?delay=${delay}`)
        .map(data => data.data) // use data property of payload
        .map(users => ({
          type: USERS_FETCH_FULFILLED,
          payload: users
        }))
        .catch(err => Observable.of({
          type: USERS_FETCH_REJECTED,
          payload: err,
          error: true
        }))
    );
  }
});

or using processOptions

import { createLogic } from 'redux-logic';
import Rx from 'rxjs'; // or selectively import only what you need
const ajax = Rx.Observable.ajax;

const usersFetchLogic = createLogic({
  type: USERS_FETCH,
  cancelType: USERS_FETCH_CANCEL,
  latest: true, // take latest only

  processOptions: {
    dispatchReturn: true,  // dispatch the values from returned obs
    successType: USERS_FETCH_FULFILLED, // apply this action type
    failType: USERS_FETCH_REJECTED // apply this action type on err
  },

  process({ getState, action }) {
    // dispatch the values from this returned observable
    // cancel and take latest will abort in-flight xhr requests
    // the delay query param adds arbitrary delay to the response
    return ajax.getJSON(`http://reqres.in/api/users?delay=${delay}`)
      .map(payload => payload.data); // use data property of payload
  }
});

allow, reject, next - optional second argument options

allow, reject, and next all support an optional second argument options which can change its operation.

By default the second argument, options, defaults to: { useDispatch: 'auto' }.

For most use cases this is the appropriate setting, it basically checks to see if the action type of the new action provided to allow, reject, or next has the same action type of the original action. If it matches the original then the allow, reject, or next call will pass the action down to the next logic or middleware.

If the action type was different then it will instead perform a dispatch. Most likely if the action type was changed then it really needs to go back to dispatch starting at the top so all middleware and any other logic have an opportunity to see it before going down the chain to the reducers. If the action type was the same then most likely we want to just let it continue down the stack (otherwise if we force it to dispatch, we better be careful not to create a loop).

If you want to force a dispatch, you may provide as your options { useDispatch: true }, for example: allow(action, { useDispatch: true }) or similarly for reject or next.

Alternatively if you just want to force the action being passed down (not dispatched regardless of type) use { useDispatch: false }, just note that in doing so previous logic and middleware won't have another opportunity to see this action.

In most situations the default options { useDefault: 'auto' } is the proper choice.

dispatch - multi-dispatching and process' variable signature

The process hook supports a variable signature that affect how dispatching works.

The official signature for process is

process({ getState, action }, ?dispatch, ?done)

which results in the following three possible variations:

// dispatch from returned object, resolved promise, or observable
// this defaults processOptions.dispatchReturn = true enabling
// dispatching of the returned/resolved values
// If you decide that you do not need to dispatch anything you may
// returning an undefined or promise resolving to undefined
process({ getState, action }) {
  return objOrPromiseOrObservable;
}
// single dispatch (deprecated) - call dispatch exactly once
// call with undefined if you do not need to dispatch anything
process({ getState, action }, dispatch) {
  dispatch(objOrPromiseOrObservable); // call exactly once
}
// multiple dispatch, call done when finished dispatching
// this defaults processOptions.dispatchMultiple = true
// which enables the multiple dispatch mode
process({ getState, action }, dispatch, done) {
  dispatch(objOrPromiseOrObservable);
  dispatch(objOrPromiseOrObservable);
  done(); // call when done dispatching
}

This should be somewhat intuitive so that you include the parameters when you need to use them. In the first case, you are returning an object, promise, or observable so the values returned or resolved will be dispatched.

In the second case you only have dispatch so like with the other hooks (validate/transform) you would be expected to call it exactly once. Due to confusion and misuse, single-dispatch is deprecated and will be removed in a future version.

And finally in the third case since done is included, it triggers multi-dispatch mode so you can freely dispatch as many items as necessary, just calling done when finished. You can also switch this mode on using processOptions.dispatchMultiple=true regardless of whether you include the done cb. In some situations like with a subscription, you might never need to end until cancelled, so you can just set the processOptions.dispatchMultiple to true and ignore the done since it isn't needed.

Note that in any case if you do not wish to dispatch something you can make an empty call like dispatch() and the call will be ignored.

Also note that in any case you can dispatch an observable which allows you to effectively dispatch any number of items. It will continue to keep dispatching alive until the observable completes.

Note: the previous v0.9 mechanism for multiple dispatches setting the second parameter of the dispatch function to {allowMore: true} will also work but is deprecated in lieu of this simpler arity approach.

An example of performing multiple dispatches without using an observable:

// including the done cb, triggers multi-dispatch mode
process({ getState, action }, dispatch, done) {
  dispatch({ type: BAR });
  dispatch({ type: CAT });
  dispatch({ type: DOG });
  done(); // indicate when done dispatching
}

For a subscription that never ends (except when cancelled) the relevant parts of our createLogic might look something like this:

cancelType: CANCEL_SUBSCRIPTION, // whatever action(s) cancel
latest: true, // use only latest

processOptions: {
  dispatchMultiple: true // turn on multiple dispatch mode
},

// we didn't include done since we never use it, dispatchMultiple
// has already turned on multi-dispatch mode for us
process({ getState, action }, dispatch) {
  apiSubscription()
    .on('updated', result => dispatch(result));
}

Alternatively you could dispatch an observable and perform any number of dispatches inside it.

process({ getState, action }, dispatch, done) {
  const ob$ = Observable.of(  // from rxjs
    { type: 'BAR' }, // first dispatch
    { type: 'CAT' }, // second dispatch
    { type: 'DOG' }  // third dispatch
  );
  dispatch(ob$);
  done(); // tell logic to complete when the ob$ completes
}

Or if dispatching things over time:

process({ getState, action }, dispatch, done) {
  const ob$ = Observable.create(obs => {
    // in parallel fire multiple requests and dispatch the results
    // by calling obs.next on the action to dispatch
    // when they arrive, then complete when done
    Promise.all([
      axios.get('http://server/users')
        .then(users => obs.next({ type: USERS, payload: users })),
      axios.get('http://server/categories')
        .then(categories => obs.next({ type: CAT, payload: categories }))
     ]).then(values => obs.complete()); // values dispatched
  });
  dispatch(ob$);
  done(); // tell logic to complete when the ob$ completes
}

Monitoring redux logic internal operations

For debugging or gaining insight into the internal operations of redux-logic, you may subscribe to the monitor$ observable. This could also be used for developing developer tools.

logicMiddleware.monitor$.subscribe(
  x => console.log(x)
);

The structure emitted by the observable is an object with properties:

  • action - the original action
  • op - what is occurring in redux-logic, one of the following values:
    • top - top of the redux-logic before any logic
    • bottom - after all redux-logic processing handed to next middleware/reducers
    • begin - enter a logic
    • next - validate or transform intercept has passed to the next logic/middleware
    • nextDisp - validate or transform intercept was successful but needs to be dispatched
    • nextError - error thrown while the middleware next (or dispatch) call was executing, possibly from downstream middleware, reducer, or render
    • filtered - validate or transform passed undefined so action is filtered and not passed to other logic/middleware/reducers
    • cancelled - intercept was cancelled either from cancelType or take latest
    • dispatch - dispAction was dispatched to store
    • dispCancelled - dispatch was cancelled either from cancelType or take latest
    • end - everything has completed for this logic including async processing
  • name - name of the logic operating on this action
  • nextAction - action being passed down to next logic/middleware/reducer
  • shouldProcess - true/false on op:next indicating whether validation was successful and process should be invoked
  • dispAction - action to dispatch to store

For example:

{ action: { type: 'FOO' }, op: 'top' }
{ action: { type: 'FOO' }, name: 'L(FOO)-0', op: 'begin' }
{ action: { type: 'FOO' }
  nextAction: { type: 'FOO', payload: 42 }
  name: 'L(FOO)-0',
  shouldProcess: true,
  op: 'next' }
{ nextAction: { type: 'FOO', payload: 42 }, op: 'bottom' }
{ action: { type: 'FOO' },
  dispAction: { type: 'BAR', payload: 42 },
  op: 'dispatch' },
{ action: { type: 'FOO' },
  name: 'L(FOO)-0',
  op: 'end' }