{"id":22984375,"url":"https://github.com/joshburgess/redux-most","last_synced_at":"2025-08-25T05:06:05.605Z","repository":{"id":11387736,"uuid":"69513957","full_name":"joshburgess/redux-most","owner":"joshburgess","description":"Most.js based middleware for Redux. Handle async actions with monadic streams \u0026 reactive programming.","archived":false,"fork":false,"pushed_at":"2022-02-27T14:40:54.000Z","size":567,"stargazers_count":139,"open_issues_count":12,"forks_count":14,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-30T21:08:49.513Z","etag":null,"topics":["async","asynchronous-programming","epics","functional","functional-programming","middleware","monadic-streams","most","mostjs","observable","reactive","reactive-programming","redux","redux-observable","redux-saga","redux-thunk","rxjs","sagas","streams","the-saga-pattern"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/redux-most","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joshburgess.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-09-29T00:09:32.000Z","updated_at":"2023-03-11T12:20:16.000Z","dependencies_parsed_at":"2022-08-07T06:16:16.506Z","dependency_job_id":null,"html_url":"https://github.com/joshburgess/redux-most","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshburgess%2Fredux-most","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshburgess%2Fredux-most/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshburgess%2Fredux-most/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joshburgess%2Fredux-most/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joshburgess","download_url":"https://codeload.github.com/joshburgess/redux-most/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247557767,"owners_count":20958047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","asynchronous-programming","epics","functional","functional-programming","middleware","monadic-streams","most","mostjs","observable","reactive","reactive-programming","redux","redux-observable","redux-saga","redux-thunk","rxjs","sagas","streams","the-saga-pattern"],"created_at":"2024-12-15T03:15:49.536Z","updated_at":"2025-04-06T22:07:52.558Z","avatar_url":"https://github.com/joshburgess.png","language":"JavaScript","funding_links":[],"categories":["Marks"],"sub_categories":["[React - A JavaScript library for building user interfaces](http://facebook.github.io/react)"],"readme":"redux-most\n==========\n\n[Most.js](https://github.com/cujojs/most) based middleware for [Redux](http://redux.js.org/).\n\nHandle async actions with monadic streams \u0026 reactive programming.\n\n### [Jump to API Reference](https://github.com/joshburgess/redux-most#api-reference)\n\n### Install\nWith yarn (recommended):\n```bash\nyarn add redux-most\n```\n\nor with npm:\n```bash\nnpm install --save redux-most\n```\n\nAdditionally, make sure the peer dependencies, `redux` and `most`, are also installed.\n\n\n### Background\n\n`redux-most` is based on [`redux-observable`](https://redux-observable.js.org/).\nIt uses the same pattern/concept of [\"epics\"](https://redux-observable.js.org/docs/basics/Epics.html)\nwithout requiring [`RxJS 5`](http://reactivex.io/rxjs/) as a peer dependency.\nAlthough `redux-observable` does provide capability for using other stream libraries via adapters,\n`redux-most` allows you to bypass needing to install both `RxJS 5` and `Most`. I prefer `Most` for\nworking with observables and would rather have minimal dependencies. So, I wrote\nthis middleware primarily for my own use.\n\nPlease, see `redux-observable`'s [documentation](https://redux-observable.js.org/)\nfor details on usage.\n\n### Why Most over RxJS?\n\n`RxJS 5` is great. It's quite a bit faster than `RxJS 4`, and `Rx`, in general, is a\nvery useful tool which happens to exist across many different languages.\nLearning it is definitely a good idea. However, `Most` is significantly smaller,\nless complicated, and faster than `RxJS 5`. I prefer its more minimal set of \noperators and its focus on performance. Also, like [`Ramda`](http://ramdajs.com/)\nor [`lodash/fp`](https://github.com/lodash/lodash/wiki/FP-Guide), `Most`\nsupports a functional API in which the data collection (a stream, rather than \nan array, in this case) gets passed in last. This is important, because it \nallows you to use functional programming techniques like currying \u0026 partial \napplication, which you can't do with `RxJS` without writing your own wrapper \nfunctions, because it only offers an OOP/fluent/chaining style API.\n\n### Why integrate `Most`/`RxJS` with `redux` instead of recreating it with streams?\n\nIt's true that it's quite easy to implement the core ideas of `Redux` with\nobservables using the `scan` operator. (See my [inferno-most-fp-demo](https://github.com/joshburgess/inferno-most-fp-demo)\nfor an example.) However, the [Redux DevTools](https://github.com/gaearon/redux-devtools)\nprovide what is arguably the nicest developer tooling experience currently available\nin the JavaScript ecosystem. Therefore, it is huge to be able to maintain it as an asset\nwhile still reaping the benefits of reactive programming with streams. Purists, those who \nare very experienced with working with observables, and those working on smaller apps\nmay not care as much about taking advantage of that tooling as using an elegant\nstreams-only based solution, and that's fine. The important thing is having a choice.\n\n### Why `redux-most` or `redux-observable` over [`redux-saga`](https://redux-saga.js.org/)?\n\n`redux-saga` is nice. It's a sophisticated approach to handling asynchronous\nactions with `Redux` and can handle very complicated tasks with ease. However,\ndue to generators being pull-based, it is much more imperative in nature. I\nsimply prefer the more declarative style of push-based streams \u0026 reactive\nprogramming.\n\n### Differences between `redux-most` \u0026 `redux-observable`\n\n__Summary__\n\n- There are no adapters. `redux-most` is only intended to be used with `Most`.\n- `redux-most` offers 2 separate APIs: a `redux-observable`-like API, where Epics\nget passed an action stream \u0026 a store middleware object containing `dispatch` \u0026 `getState`\nmethods, and a stricter, more declarative API, where Epics get passed an action stream \u0026 a state stream.\n- `combineEpics` takes in an array of epics instead of multiple arguments.\n- Standard `Most` streams are used instead of a custom Observable extension.\n- `select` and `selectArray` are available instead of the variadic `ofType`.\n\n\n__Further Elaboration:__\n\nAs the name implies, `redux-most` does not offer adapters for use with other reactive\nprogramming libraries that implement the Observable type. It's merely an implementation of\n`redux-observable`'s \"Epic\" pattern exclusively intended for use with `Most`. `Most` is arguably\nthe fastest, simplest, most functional, \u0026 most elegant reactive programming library in the\nJavaScript ecosystem right now, and `Most 2.0` will be even better, as it will feature an\nauto-curried API like `lodash/fp` and `ramda`, but for working with streams instead of arrays.\nFor a preview of what's to come, check out what's going on [here](https://github.com/mostjs/core).\n\nInitially, `redux-most` offered the same API as `redux-observable`, where Epics received an action\nstream \u0026 a store middleware object containing `dispatch` \u0026 `getState` methods. However, it now offers\nboth that API and another stricter, more declarative API which eliminates the use of `dispatch` \u0026\n`getState`. The reason for this is that I rarely found myself using the imperative `dispatch`\nmethod. It's not really needed, because you can use `switch`, `merge`, `mergeArray`, etc. to send\nmultiple actions through your outgoing stream. This is nice, because it allows you to stay locked into\nthe declarative programming style the entire time.\n\nHowever, using `getState` was still required in epics that needed access to the current state. I\nwanted a nice, convenient way to access the current state, just like I had for dispatching actions.\nSo, I created an alternate API where Epics receive a stream of state changes rather than the\n`{ dispatch, getState }` object. This state stream, combined with the new `withState` utility function,\nlet's you use streams for both dispatching actions \u0026 accessing the current state, allowing you to stay \nfocused \u0026 in the zone (the reactive programming mindset).\n\nMoving on, whereas `comebineEpics` is variadic in `redux-observable`, it's unary in `redux-most`. It\ntakes in only one argument, an array of epics, instead of individual epics getting passed in as separate\narguments.\n\nAs for streams, I chose not to extend the `Observable` type with a custom `ActionsObservable`\ntype. So, when working with `redux-most`, you will be working with normal `most`\nstreams without any special extension methods. However, I have offered something\nsimilar to `redux-observable`'s `ofType` operator in `redux-most` with the\n`select` and `selectArray` helper functions.\n \n\nLike `ofType`, `select` and `selectArray` are convenience utilities for filtering\nactions by a specific type or types. In `redux-observable`, `ofType` can optionally take multiple\naction types to filter on. In `redux-most`, we want to be more explicit, as it is generally a good \npractice in functional programming to prefer a known number of arguments over a variable amount\nof arguments. Therefore, `select` is used when we want to filter by a single action type, and\n`selectArray` is used when we want to filter by multiple action types (via an array) simultaneously.\n\nAdditionally, to better align with the `Most` API, and because these functions take a known number\nof arguments, `select` \u0026 `selectArray` are curried, which allows them to be used in either a\nfluent style or a more functional style which enables the use of further currying, partial\napplication, \u0026 functional composition.\n\nTo use the fluent style, just use `Most`'s `thru` operator to pass the stream\nthrough to `select`/`selectArray` as the 2nd argument.\n\n```js\n// Fluent style\nconst filteredAction$ = action$.thru(select(SOME_ACTION_TYPE))\nconst filteredActions$ = action$.thru(selectArray([SOME_ACTION_TYPE, SOME_OTHER_ACTION_TYPE]))\n```\n\nOtherwise, simply directly pass the stream as the 2nd argument.\n\n```js\n// Functional style\nconst filteredAction$ = select(SOME_ACTION_TYPE, action$)\nconst filteredActions$ = selectArray([SOME_ACTION_TYPE, SOME_OTHER_ACTION_TYPE], action$)\n```\nAlternatively, you can delay passing the 2nd argument while defining functional pipelines\nvia functional composition by using the `compose` or `pipe` functions from your favorite FP library, \nlike `ramda` or `lodash/fp`. Again, this is because `select` \u0026 `selectArray` are auto-curried. Being\nable to program in this very functional \u0026 Pointfree style is one of the main reasons why someone\nmight prefer using redux-most over redux-observable.\n\n```js\n// Functional \u0026 Pointfree style using currying \u0026 functional composition\nimport { compose, curry, pipe } from 'ramda'\nimport { debounce, filter, map } from 'most'\n\n// NOTE: Most 2.0 will feature auto-curried functions, but right now we must curry them manually.\nconst curriedDebounce = curry(debounce)\nconst curriedFilter = curry(filter)\nconst curriedMap = curry(map)\n\n// someEpic is a new function which is still awaiting one argument, the action$\nconst someEpic = compose(\n  curriedMap(someFunction),\n  curriedDebounce(800),\n  select(SOME_ACTION_TYPE)\n)\n\n// someOtherEpic is a new function which is still awaiting one argument, the action$\n// pipe is the same as compose, but read from left-to-right rather than right-to-left.\nconst someOtherEpic = pipe(\n  selectArray([SOME_ACTION_TYPE, SOME_OTHER_ACTION_TYPE]),\n  curriedFilter(somePredicate),\n  curriedMap(someFunction)\n)\n```\n\n## API Reference\n\n- [createEpicMiddleware](https://github.com/joshburgess/redux-most#createepicmiddleware-rootepic)\n- [createStateStreamEnhancer](https://github.com/joshburgess/redux-most#createstatestreamenhancer-epicmiddleware)\n- [combineEpics](https://github.com/joshburgess/redux-most#combineepics-epics)\n- [EpicMiddleware](https://github.com/joshburgess/redux-most#epicmiddleware)\n- [replaceEpic](https://github.com/joshburgess/redux-most#replaceEpic)\n- [select](https://github.com/joshburgess/redux-most#select-actiontype-stream)\n- [selectArray](https://github.com/joshburgess/redux-most#selectArray-actiontypes-stream)\n- [withState](https://github.com/joshburgess/redux-most#withstate-statestream-actionstream)\n\n---\n\n### `createEpicMiddleware (rootEpic)`\n\n`createEpicMiddleware` is used to create an instance of the actual `redux-most` middleware.\nYou provide a single root `Epic`.\n\n__Arguments__\n\n1. `rootEpic` _(`Epic`)_: The root Epic.\n\n__Returns__\n\n_(`MiddlewareAPI`)_: An instance of the `redux-most` middleware.\n\n__Example__\n```js\n// redux/configureStore.js\n\nimport { createStore, applyMiddleware, compose } from 'redux'\nimport { createEpicMiddleware } from 'redux-most'\nimport { rootEpic, rootReducer } from './modules/root'\n\nconst epicMiddleware = createEpicMiddleware(rootEpic)\n\nexport default function configureStore() {\n  const store = createStore(\n    rootReducer,\n    applyMiddleware(epicMiddleware)\n  )\n\n  return store\n}\n```\n\n---\n\n### `createStateStreamEnhancer (epicMiddleware)`\n\n`createStateStreamEnhancer` is used to access `redux-most`'s alternate API, which passes\n`Epics` a state stream (Ex: `state$`) instead of the `{ dispatch, getState }` store\n`MiddlewareAPI` object. You must provide an instance of the `EpicMiddleware`, and the\nresulting function must be applied AFTER using `redux`'s `applyMiddleware` if also using\nother middleware.\n\n__Arguments__\n\n1. `rootEpic` _(`Epic`)_: The root Epic.\n\n__Returns__\n\n_(`MiddlewareAPI`)_: An enhanced instance of the `redux-most` middleware, exposing a stream\nof state change values.\n\n__Example__\n```js\nimport { createStore, applyMiddleware } from 'redux'\nimport {\n  createEpicMiddleware,\n  createStateStreamEnhancer,\n} from 'redux-most'\nimport rootEpic from '../epics'\n\nconst epicMiddleware = createEpicMiddleware(rootEpic)\nconst middleware = [...] // other middleware here\nconst storeEnhancers = compose(\n  createStateStreamEnhancer(epicMiddleware),\n  applyMiddleware(...middleware)\n)\n\nconst store = createStore(rootReducer, storeEnhancers)\n```\n\n---\n\n### `combineEpics (epicsArray)`\n\n`combineEpics`, as the name suggests, allows you to pass in an array of epics and combine them into a single one.\n\n__Arguments__\n\n1. `epicsArray` _(`Epic[]`)_: The array of `epics` to combine into one root epic.\n\n__Returns__\n\n_(`Epic`)_: An Epic that merges the output of every Epic provided and passes along the redux store as arguments.\n\n__Example__\n```js\n// epics/index.js\n\nimport { combineEpics } from 'redux-most'\nimport searchUsersDebounced from './searchUsersDebounced'\nimport searchUsers from './searchUsers'\nimport clearSearchResults from './clearSearchResults'\nimport fetchReposByUser from './fetchReposByUser'\nimport adminAccess from './adminAccess'\n\nconst rootEpic = combineEpics([\n  searchUsersDebounced,\n  searchUsers,\n  clearSearchResults,\n  fetchReposByUser,\n  adminAccess,\n])\n\nexport default rootEpic\n\n```\n\n---\n\n### `EpicMiddleware`\n\nAn instance of the `redux-most` middleware.\n\nTo create it, pass your root Epic to [`createEpicMiddleware`](https://github.com/joshburgess/redux-most#createepicmiddleware-rootepic).\n\n__Methods__\n\n- [`replaceEpic (nextEpic)`](https://github.com/joshburgess/redux-most#replaceEpic)\n\n#### \u003ca id='replaceEpic'\u003e\u003c/a\u003e`replaceEpic (nextEpic)`\n\nReplaces the epic currently used by the middleware.\n\nIt is an advanced API. You might need this if your app implements code splitting and you\nwant to load some of the epics dynamically or you're using hot reloading.\n\n__Example__\n\n```js\n\nimport { createEpicMiddleware } from 'redux-most'\nimport rootEpic from '../epics'\n\n...\n\nconst epicMiddleware = createEpicMiddleware(rootEpic)\n\n...\n\n// hot reload epics\nconst replaceRootEpic = () =\u003e {\n  import('../epics').then(\n    ({ default: nextRootEpic }) =\u003e { epicMiddleware.replaceEpic(nextRootEpic) }\n  )\n}\n\nif (module.hot) {\n  module.hot.accept('../epics', replaceRootEpic)\n}\n```\n\n__Arguments__\n\n1. `nextEpic` _(`Epic`)_: The next epic for the middleware to use.\n\n---\n\n### `select (actionType, stream)`\n\nA helper function for filtering the stream of actions by a  single action type.\n\n__Arguments__\n\n1. `actionType` _(`string`)_: The type of action to filter by.\n2. `stream` _(`Stream`)_: The stream of actions you are filtering. Ex: `actions$`.\n\n__Returns__\n\n_(Stream)_: A new, filtered stream holding only the actions corresponding to the action\ntype passed to `select`.\n\nThe `select` operator is curried, allowing you to use a fluent or functional style.\n\n__Examples__\n```js\n// Fluent style\n\nimport { SEARCHED_USERS_DEBOUNCED } from '../constants/ActionTypes'\nimport { clearSearchResults } from '../actions'\nimport { select } from 'redux-most'\n\nconst whereEmpty = ({ payload: { query } }) =\u003e !query\n\nconst clear = action$ =\u003e\n  action$.thru(select(SEARCHED_USERS_DEBOUNCED))\n    .filter(whereEmpty)\n    .map(clearSearchResults)\n\nexport default clear\n```\n\n```js\n// Functional style\n\nimport { SEARCHED_USERS_DEBOUNCED } from '../constants/ActionTypes'\nimport { clearSearchResults } from '../actions'\nimport { select } from 'redux-most'\n\nconst whereEmpty = ({ payload: { query } }) =\u003e !query\n\nconst clear = action$ =\u003e {\n  const search$ = select(SEARCHED_USERS_DEBOUNCED, action$)\n  const emptySearch$ = filter(whereEmpty, search$)\n  return map(clearSearchResults, emptySearch$)\n}\n\nexport default clear\n```\n\n```js\n// Functional \u0026 Pointfree style using functional composition\n\nimport { SEARCHED_USERS_DEBOUNCED } from '../constants/ActionTypes'\nimport { clearSearchResults } from '../actions'\nimport { select } from 'redux-most'\nimport {\n  curriedFilter as filter,\n  curriedMap as map,\n} from '../utils'\nimport { compose } from 'ramda'\n\nconst whereEmpty = ({ payload: { query } }) =\u003e !query\n\nconst clear = compose(\n  map(clearSearchResults),\n  filter(whereEmpty),\n  select(SEARCHED_USERS_DEBOUNCED)\n)\n\nexport default clear\n```\n---\n\n### `selectArray (actionTypes, stream)`\n\nA helper function for filtering the stream of actions by an array of action types.\n\n__Arguments__\n\n1. `actionTypes` _(`string[]`)_: An array of action types to filter by.\n2. `stream` _(`Stream`)_: The stream of actions you are filtering. Ex: `actions$`.\n\n__Returns__\n\n_(Stream)_: A new, filtered stream holding only the actions corresponding to the action\ntypes passed to `selectArray`.\n\nThe `selectArray` operator is curried, allowing you to use a fluent or functional style.\n\n__Examples__\n```js\n// Fluent style\n\nimport {\n  SEARCHED_USERS,\n  SEARCHED_USERS_DEBOUNCED,\n} from '../constants/ActionTypes'\nimport { clearSearchResults } from '../actions'\nimport { selectArray } from 'redux-most'\n\nconst whereEmpty = ({ payload: { query } }) =\u003e !query\n\nconst clear = action$ =\u003e\n  action$.thru(selectArray([\n      SEARCHED_USERS,\n      SEARCHED_USERS_DEBOUNCED,\n    ]))\n    .filter(whereEmpty)\n    .map(clearSearchResults)\n\nexport default clear\n```\n\n```js\n// Functional style\n\nimport {\n  SEARCHED_USERS,\n  SEARCHED_USERS_DEBOUNCED,\n} from '../constants/ActionTypes'\nimport { clearSearchResults } from '../actions'\nimport { selectArray } from 'redux-most'\n\nconst whereEmpty = ({ payload: { query } }) =\u003e !query\n\nconst clear = action$ =\u003e {\n  const search$ = selectArray([\n    SEARCHED_USERS,\n    SEARCHED_USERS_DEBOUNCED,\n  ], action$)\n  const emptySearch$ = filter(whereEmpty, search$)\n  return map(clearSearchResults, emptySearch$)\n}\n\nexport default clear\n```\n\n```js\n// Functional \u0026 Pointfree style using functional composition\n\nimport {\n  SEARCHED_USERS,\n  SEARCHED_USERS_DEBOUNCED,\n} from '../constants/ActionTypes'\nimport { clearSearchResults } from '../actions'\nimport { selectArray } from 'redux-most'\nimport {\n  curriedFilter as filter,\n  curriedMap as map,\n} from '../utils'\nimport { compose } from 'ramda'\n\nconst whereEmpty = ({ payload: { query } }) =\u003e !query\n\nconst clear = compose(\n  map(clearSearchResults),\n  filter(whereEmpty),\n  selectArray([\n    SEARCHED_USERS,\n    SEARCHED_USERS_DEBOUNCED,\n  ])\n)\n\nexport default clear\n```\n---\n\n### `withState (stateStream, actionStream)`\n\nA utility function for use with `redux-most`'s optional state stream API. This\nprovides a convenient way to `sample` the latest state change value. Note:\naccessing the alternate API requires using `createStateStreamEnhancer`.\n\n__Arguments__\n\n1. `stateStream` _(`Stream`)_: The state stream provided by `redux-most`'s alternate API.\n2. `actionStream` _(`Stream`)_: The filtered stream of action events used to trigger\nsampling of the latest state. (Ex: `actions$`).\n\n__Returns__\n\n_(`[state, action]`)_: An Array of length 2 (or Tuple) containing the latest\nstate value at index 0 and the latest action of the filtered action stream at index 1.\n\n`withState` is curried, allowing you to pass in the state stream \u0026 action stream\ntogether, at the same time, or separately, delaying passing in the action stream.\nThis provides the user extra flexibility, allowing it to easily be used within \nfunctional composition pipelines.\n\n__Examples__\n```js\nimport { select, withState } from 'redux-most'\nimport { curriedMap as map } from '../utils'\nimport compose from 'ramda/src/compose'\n\nconst accessStateFromArray = ([state, action]) =\u003e ({\n  type: 'ACCESS_STATE',\n  payload: {\n    latestState: state,\n    accessedByAction: action,\n  },\n})\n\n// dispatch { type: 'STATE_STREAM_TEST' } in Redux DevTools to test\nconst stateStreamTest = (action$, state$) =\u003e compose(\n map(accessStateFromArray),\n withState(state$),\n select('STATE_STREAM_TEST')\n)(action$)\n\nexport default stateStreamTest\n```\n\n---","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshburgess%2Fredux-most","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoshburgess%2Fredux-most","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshburgess%2Fredux-most/lists"}