{"id":15654277,"url":"https://github.com/runjuu/mst-effect","last_synced_at":"2025-07-07T08:08:16.398Z","repository":{"id":40795442,"uuid":"280823209","full_name":"runjuu/mst-effect","owner":"runjuu","description":"💫 Designed to be used with MobX-State-Tree to create asynchronous actions using RxJS","archived":false,"fork":false,"pushed_at":"2024-08-30T08:58:21.000Z","size":1531,"stargazers_count":28,"open_issues_count":3,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-06-19T04:55:39.693Z","etag":null,"topics":["async","asynchronous-programming","mobx","mobx-state-tree","mst","observable","promise","react","redux-observable","rxjs","state-management","stream"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/runjuu.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-19T08:24:09.000Z","updated_at":"2025-05-03T05:46:00.000Z","dependencies_parsed_at":"2024-06-19T20:01:50.799Z","dependency_job_id":"18d0684a-e858-4ac5-a738-083d12dee140","html_url":"https://github.com/runjuu/mst-effect","commit_stats":{"total_commits":90,"total_committers":4,"mean_commits":22.5,"dds":0.3222222222222222,"last_synced_commit":"0709003ca43ca389ee032645d5065f6a473b5f65"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/runjuu/mst-effect","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runjuu%2Fmst-effect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runjuu%2Fmst-effect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runjuu%2Fmst-effect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runjuu%2Fmst-effect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/runjuu","download_url":"https://codeload.github.com/runjuu/mst-effect/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runjuu%2Fmst-effect/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261416251,"owners_count":23155035,"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","mobx","mobx-state-tree","mst","observable","promise","react","redux-observable","rxjs","state-management","stream"],"created_at":"2024-10-03T12:50:21.870Z","updated_at":"2025-07-07T08:08:16.380Z","avatar_url":"https://github.com/runjuu.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/Runjuu/mst-effect\"\u003e\n    \u003cimg width=\"150px\" src=\"https://user-images.githubusercontent.com/12002941/111877518-b2c60900-89de-11eb-8f28-d5fd95897258.png\" alt=\"mst-effect\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/Runjuu/mst-effect/blob/main/LICENSE\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/l/mst-effect?colorA=373737\u0026colorB=0A70E9\u0026style=flat\" alt=\"GitHub license\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/mst-effect\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/mst-effect?colorA=373737\u0026colorB=0A70E9\u0026style=flat\" alt=\"NPM version\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/result?p=mst-effect\"\u003e\n    \u003cimg src=\"https://img.shields.io/bundlephobia/min/mst-effect?label=bundle%20size\u0026colorA=373737\u0026colorB=0A70E9\u0026style=flat\" alt=\"Bundle size\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://coveralls.io/github/Runjuu/mst-effect?branch=main\"\u003e\n    \u003cimg src=\"https://img.shields.io/coveralls/github/Runjuu/mst-effect?colorA=373737\u0026colorB=0A70E9\u0026style=flat\" alt=\"Coverage Status\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/Runjuu/mst-effect/discussions\"\u003e\n    \u003cimg src=\"https://img.shields.io/static/v1?label=discuss%20on\u0026message=github\u0026colorA=373737\u0026colorB=0A70E9\u0026style=flat\" alt=\"Github discussions\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n`mst-effect` is designed to be used with \u003ca href=\"https://github.com/mobxjs/mobx-state-tree\"\u003eMobX-State-Tree\u003c/a\u003e to create asynchronous actions using \u003ca href=\"https://github.com/ReactiveX/rxjs\"\u003eRxJS\u003c/a\u003e. In case you haven't used them before:\n\n\u003e `MobX-State-Tree` is a full-featured reactive state management library that can **structure the state model** super intuitively.\n\u003e \u003cbr /\u003e `RxJS` is a library for composing asynchronous and event-based programs that provides the best practice to **manage async codes**.\n\nIf you are still hesitant about learning `RxJS`, check the examples below and play around with them. I assure you that you'll be amazed by what it can do and how clean the code could be.\n\nAlready using `MobX-State-Tree`? Awesome! `mst-effect` is 100% compatible with your current project.\n\n## Examples\n\n- [Fetch data](https://codesandbox.io/s/fetch-data-i9hqb?file=/src/app.tsx)\n- [Fetch with token](https://codesandbox.io/s/fetch-with-token-rbveh?file=/src/app.tsx)\n- [Handle user input](https://codesandbox.io/s/handle-user-input-ef1pt?file=/src/app.tsx)\n- [Mutually exclusive actions](https://codesandbox.io/s/mutually-exclusive-actions-ylqlf?file=/src/app.tsx)\n- [Back pressure](https://codesandbox.io/s/backpressure-ulu1y?file=/src/app.tsx)\n\n## Installation\n\n`mst-effect` has peer dependencies of [mobx](https://www.npmjs.com/package/mobx), [mobx-state-tree](https://www.npmjs.com/package/mobx-state-tree) and [rxjs](https://www.npmjs.com/package/rxjs), which will have to be installed as well.\n\n##### Using [yarn](https://yarnpkg.com/en/package/mst-effect):\n\n```bash\nyarn add mst-effect\n```\n\n##### Or via [npm](https://www.npmjs.com/package/mst-effect):\n\n```bash\nnpm install mst-effect --save\n```\n\n## Basics\n\n**`effect`** is the core method of `mst-effect`. It can automatically manage subscriptions and execute the emitted actions. For example:\n\n```ts\nimport { types, effect, action } from 'mst-effect'\nimport { map, switchMap } from 'rxjs/operators'\n\nconst Model = types\n  .model({\n    value: types.string,\n  })\n  .actions((self) =\u003e ({\n    fetch: effect\u003cstring\u003e(self, (payload$) =\u003e {\n      function setValue(value: string) {\n        self.value = value\n      }\n\n      return payload$.pipe(\n        switchMap((url) =\u003e fetch$(url)),\n        map((value) =\u003e action(setValue, value)),\n      )\n    }),\n  }))\n```\n\n#### Import location\n\nAs you can see in the example above, `types` need to be imported from `mst-effect`([Why?](#why-we-need-to-import-types-from-mst-effect)).\n\n#### The definition of the `effect`\n\nThe first parameter is the model instance, as `effect` needs to unsubscribe the [`Observable`](https://rxjs-dev.firebaseapp.com/api/index/class/Observable) when the model is destroyed.\n\nThe second parameter, a factory function, can be thought of as the `Epic` of [redux-observable](https://redux-observable.js.org/docs/basics/Epics.html). The factory function is called only once at model creation. It takes a stream of payloads and returns a stream of actions. — **Payloads in, actions out.**\n\nFinally, `effect` returns a function to feed a new value to the `payload$`. In actual implementation code, it's just an alias to `subject.next`.\n\n#### What is `action`?\n\n`action` can be considered roughly as a higher-order function that takes a callback function and the arguments for the callback function. But instead of executing immediately, it returns a new function. Action will be immediately invoked when emitted.\n\n```ts\nfunction action(callback, ...params): EffectAction {\n  return () =\u003e callback(...params)\n}\n```\n\n## API Reference\n\n### [👾](https://github.com/Runjuu/mst-effect/blob/main/src/effect/effect.ts) effect\n\n`effect` is used to manage subscriptions automatically.\n\n```ts\ntype ValidEffectActions = EffectAction | EffectAction[]\n\ntype EffectDispatcher\u003cP\u003e = (payload: P) =\u003e void\n\nfunction effect\u003cP\u003e(\n  self: AnyInstance,\n  fn: (payload$: Observable\u003cP\u003e) =\u003e Observable\u003cValidEffectActions\u003e,\n): EffectDispatcher\u003cP\u003e\n```\n\n`payload$` emits data synchronously when the function returned by the effect is called. The returned `Observable\u003cValidEffectActions\u003e` will automatically subscribed by `effect`\n\n### [👾](https://github.com/Runjuu/mst-effect/blob/main/src/effect/doll-effect.ts) dollEffect\n\n```ts\ntype ValidEffectActions = EffectAction | EffectAction[]\n\ntype DollEffectDispatcher\u003cP, S\u003e = \u003cSS = S\u003e(\n  payload: P,\n  handler?: (resolve$: Observable\u003cS\u003e) =\u003e Observable\u003cSS\u003e,\n) =\u003e Promise\u003cSS\u003e\n\ntype SignalDispatcher\u003cS\u003e = (value: S) =\u003e void\n\nfunction dollEffect\u003cP, S\u003e(\n  self: AnyInstance,\n  fn: (\n    payload$: Observable\u003cP\u003e,\n    signalDispatcher: SignalDispatcher\u003cS\u003e,\n  ) =\u003e Observable\u003cValidEffectActions\u003e,\n): DollEffectDispatcher\u003cP, S\u003e\n```\n\n`dollEffect` is almost identical with `effect`. The primary difference is `DollEffectDispatcher` will return a `Promise` which is useful when you want to report some message to the caller. The `Promise` will fulfill when `SignalDispatcher` being invoked ([example](https://codesandbox.io/s/report-fetch-status-to-caller-d6gmh?file=/src/app.tsx)). Also, you can use the `handler` to control when and what the `Promise` should resolve ([example](https://codesandbox.io/s/report-fetch-status-to-caller-2-1eogq?file=/src/app.tsx)).\n\n### [👾](https://github.com/Runjuu/mst-effect/blob/main/src/signal/index.ts) signal\n\n```ts\nexport function signal\u003cP, R = P\u003e(\n  self: AnyInstance,\n  fn?: (payload$: Observable\u003cP\u003e) =\u003e Observable\u003cR\u003e,\n): [Observable\u003cR\u003e, (payload: P) =\u003e void]\n```\n\n`signal` is an encapsulation of the [`Subject`](https://rxjs-dev.firebaseapp.com/api/index/class/Subject). You can use the second parameter to do some processing of the output data.\n\n### [👾](https://github.com/Runjuu/mst-effect/blob/main/src/reaction$/index.ts) reaction$\n\n```ts\nexport function reaction$\u003cT\u003e(\n  expression: (r: IReactionPublic) =\u003e T,\n  opts?: IReactionOptions,\n): Observable\u003c{ current: T; prev: T; r: IReactionPublic }\u003e\n```\n\n`reaction$` encapsulates the [`reaction`](https://mobx.js.org/reactions.html#reaction) method from `mobx`. When the returned value changes, it will emit the corresponding data to the returned `Observable`.\n\n## Recipes\n\n#### Error Handling\n\nWhen an error occurred in `Observable`, `effect` will re-subscribe the `Observable` (will not re-run the factory function). The common practice is to use the [`catchError`](https://rxjs-dev.firebaseapp.com/api/operators/catchError) operator for error handling. Check [fetch data](https://codesandbox.io/s/fetch-data-i9hqb?file=/src/app.tsx) example for more detail.\n\n#### Cancellation\n\nYou can combine `signal` and [`takeUntil()`](https://rxjs-dev.firebaseapp.com/api/operators/takeUntil) operator to cancel an `Observable`. Check [mutually exclusive actions](https://codesandbox.io/s/mutually-exclusive-actions-ylqlf?file=/src/app.tsx) example for more detail.\n\n## FAQ\n\n#### Why we need to import `types` from `mst-effect`\n\nCurrently, `mobx-state-tree` does not support modifying the model outside of actions.\n`mst-effect` overrides `types.model` so that the model can be modified in an asynchronous process.\nBecause `mst-effect` re-export all the variables and types in `mobx-state-tree`, you can simply change the import location to `mst-effect`.\n\n```diff\n- import { types, Instance } from 'mobx-state-tree'\n+ import { types, Instance } from 'mst-effect'\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frunjuu%2Fmst-effect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frunjuu%2Fmst-effect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frunjuu%2Fmst-effect/lists"}