{"id":13518990,"url":"https://github.com/vigetlabs/microcosm","last_synced_at":"2025-10-26T00:36:39.207Z","repository":{"id":44715715,"uuid":"32197485","full_name":"vigetlabs/microcosm","owner":"vigetlabs","description":"Flux with actions at center stage. Write optimistic updates, cancel requests, and track changes with ease. ","archived":false,"fork":false,"pushed_at":"2024-11-21T16:25:48.000Z","size":8219,"stargazers_count":487,"open_issues_count":14,"forks_count":22,"subscribers_count":47,"default_branch":"master","last_synced_at":"2025-04-10T10:03:57.231Z","etag":null,"topics":["flux","react"],"latest_commit_sha":null,"homepage":"http://code.viget.com/microcosm/","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/vigetlabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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":"2015-03-14T05:33:11.000Z","updated_at":"2025-03-27T12:37:52.000Z","dependencies_parsed_at":"2025-01-14T23:49:34.769Z","dependency_job_id":null,"html_url":"https://github.com/vigetlabs/microcosm","commit_stats":{"total_commits":1921,"total_committers":16,"mean_commits":120.0625,"dds":"0.057782404997397174","last_synced_commit":"c5a352cc432fece5c526e25c604363570b2418ee"},"previous_names":[],"tags_count":208,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vigetlabs%2Fmicrocosm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vigetlabs%2Fmicrocosm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vigetlabs%2Fmicrocosm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vigetlabs%2Fmicrocosm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vigetlabs","download_url":"https://codeload.github.com/vigetlabs/microcosm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253426209,"owners_count":21906502,"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":["flux","react"],"created_at":"2024-08-01T05:01:51.768Z","updated_at":"2025-10-26T00:36:34.177Z","avatar_url":"https://github.com/vigetlabs.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# [![Microcosm](http://code.viget.com/microcosm/assets/microcosm.svg)](http://code.viget.com/microcosm/)\n\n[![CircleCI](https://img.shields.io/circleci/project/vigetlabs/microcosm.svg?maxAge=2592000)](https://circleci.com/gh/vigetlabs/microcosm)\n[![npm](https://img.shields.io/npm/v/microcosm.svg?maxAge=2592000)](https://www.npmjs.com/package/microcosm)\n[![npm](https://img.shields.io/npm/dm/microcosm.svg?maxAge=2592000)](https://www.npmjs.com/package/microcosm)\n\nMicrocosm is a state management tool for [React](https://github.com/facebook/react) (and similar libraries). Keep track of user actions, cancel requests, and perform optimistic updates with ease.\n\n## Heads up\n\n**As of July 2019, Microcosm is no longer in active development.** We will continue to maintain it for existing clients, and would be happy to accept contributions to continue support in the future.\n\nIt's been a great journey, thanks for taking it with us.\n\n## What you get\n\n- A central place to track all application data\n- [Schedule work with actions](./docs/api/actions.md)\n- Actions understand Promises out of the box and move through predefined states.\n- Keep loading states out of the data layer. Track action progress using [status callbacks](./docs/api/actions.md#ondonecallback-scope).\n- [Split up application state in large apps](./docs/api/microcosm.md#fork) while still sharing common data\n- [Painless optimistic updates](./docs/recipes/ajax.md)\n- Track changes and handle business logic with [Presenter components](./docs/api/presenter.md)\n- 5.5kb gzipped (~18kb minified)\n\n## At a glance\n\n```javascript\nimport Microcosm, { get, set } from 'microcosm'\nimport axios from 'axios'\n\nlet repo = new Microcosm()\n\nfunction getUser (id) {\n  // This will return a promise. Microcosm automatically handles promises.\n  // See http://code.viget.com/microcosm/api/actions.html\n  return axios(`/users/${id}`)\n}\n\n// Domains define how a Microcosm should turn actions into new state\nrepo.addDomain('users', {\n  getInitialState () {\n    return {}\n  },\n  addUser (users, record) {\n    // The set helper non-destructively assigns keys to an object\n    return set(users, record.id, record)\n  },\n  register () {\n    return {\n      [getUser]: {\n        done: this.addUser\n      }\n    }\n  }\n})\n\n// Push an action, a request to perform some kind of work\nlet action = repo.push(getUser, 2)\n\naction.onDone(function () {\n  let user = get(repo.state, ['users', '2'])\n\n  console.log(user) // { id: 2, name: \"Bob\" }\n})\n\n// You could also handle errors in a domain's register method\n// by hooking into `getUser.error`\naction.onError(function () {\n  alert(\"Something went terribly wrong!\")\n})\n```\n\n## Why?\n\nOther [Flux](https://facebook.github.io/flux/) implementations treat actions as static events; the result of calling a dispatch method or resolving some sort of data structure like a Promise.\n\nBut what if a user gets tired of waiting for a file to upload, or switches pages before a GET request finishes? What if they dip into a subway tunnel and lose connectivity? They might want to retry a request, cancel it, or just see what’s happening.\n\nThe burden of this state often falls on data stores (Domains, in Microcosm) or a home-grown solution for tracking outstanding requests and binding them to related action data. Presentation layer requirements leak into the data layer, making it harder to write tests, reuse code, and accommodate unexpected changes.\n\n### How Microcosm is different\n\nMicrocosm actions are first-class citizens. An action can move from an `open` to `error` state if a request fails. Requests that are aborted may move into a `cancelled` state. As they change, actions resolve within a greater history of every other action.\n\nThis means that applications can make a lot of assumptions about user actions:\n\n- Actions resolve in a consistent, predictable order\n- Action types are automatically generated\n- Actions maintain the same public API, no matter what asynchronous pattern is utilized (or not)\n\nThis reduces a lot of boilerplate, however it also makes it easier for the presentation layer to handle use-case specific display requirements, like displaying an error, performing an optimistic update, or tracking file upload progress.\n\n## Get started\n\n```\nnpm install --save microcosm\n```\n\nCheck out our [quickstart guide](http://code.viget.com/microcosm/guides/quickstart.html).\n\n## Documentation\n\nComprehensive documentation can be found in the [docs section of this repo](docs).\n\n## Overview\n\nMicrocosm is an evolution of [Flux](https://facebook.github.io/flux/)\nthat makes it easy to manage complicated async workflows and unique\ndata modeling requirements of complicated UIs.\n\n### Actions take center stage\n\nMicrocosm organizes itself around a history of user actions. As those actions move through a set lifecycle,\nMicrocosm reconciles them in the order they were created.\n\nInvoking `push()` appends to that history, and returns an `Action` object to represent it:\n\n```javascript\n// axios is an AJAX library\n// https://github.com/mzabriskie/axios\nimport axios from 'axios'\n\nfunction getPlanet (id) {\n  // axios returns a Promise, handled out of the box\n  return axios(`/planets/${id}`)\n}\n\nlet action = repo.push(getPlanet, 'venus')\n\naction.onDone(function (planet) {\n  console.log(planet.id) // venus\n})\n```\n\n### Domains: Stateless Stores\n\nA Domain is a collection of side-effect free operations for manipulating data. As actions update, Microcosm\nuses domains to determine how state should change. Old state comes in, new state comes out:\n\n```javascript\nconst PlanetsDomain = {\n  getInitialState () {\n    return []\n  },\n\n  addPlanet (planets, record) {\n    return planets.concat(record)\n  },\n\n  register() {\n    return {\n      [getPlanet]: this.addPlanet\n    }\n  }\n}\n\nrepo.addDomain('planets', PlanetsDomain)\n```\n\nBy implementing a register method, domains can subscribe to actions. Each action\nis assigned a unique string identifier. **Action type constants are generated automatically**.\n\n### Pending, failed, and cancelled requests\n\nMicrocosm makes it easy to handle pending, loading, cancelled,\ncompleted, and failed requests:\n\n```javascript\nconst PlanetsDomain = {\n  // ...handlers\n\n  register() {\n    return {\n      [getPlanet] : {\n        open   : this.setPending,\n        update : this.setProgress,\n        done   : this.addPlanet,\n        error  : this.setError,\n        cancel : this.setCancelled\n      }\n    }\n  }\n}\n```\n\n`open`, `loading`, `done`, `error` and `cancelled` are action\nstates. In our action creator, we can unlock a deeper level of control\nby returning a function:\n\n```javascript\nimport request from 'superagent'\n\nfunction getPlanet (id) {\n\n  return function (action) {\n    action.open(id)\n\n    let request = request('/planets/' + id)\n\n    request.end(function (error, response) {\n      if (error) {\n        action.reject(error)\n      } else {\n        action.resolve(response.body)\n      }\n    })\n\n    // Cancellation!\n    action.onCancel(request.abort)\n  }\n}\n```\n\nFirst, the action becomes `open`. This state is useful when waiting\nfor something to happen, such as loading. When the request finishes,\nif it fails, we reject the action, otherwise we resolve it.\n\n**Microcosm actions are cancellable**. Invoking `action.cancel()` triggers a\ncancellation event:\n\n```javascript\nlet action = repo.push(getPlanet, 'Pluto')\n\n// Wait, Pluto isn't a planet!\naction.cancel()\n```\n\nWhen `action.cancel()` is called, the action will move into a\n`cancelled` state. If a domain doesn't handle a given state no data\noperation will occur.\n\nVisit [the API documentation for actions](./docs/api/actions.md) to\nread more.\n\n### A historical account of everything that has happened\n\nWhenever an action creator is pushed into a Microcosm, it creates an\naction to represent it. This gets placed into a tree of all actions\nthat have occurred.\n\nFor performance, completed actions are archived and purged from\nmemory, however passing the `maxHistory` option into Microcosm allows\nfor a compelling debugging story, For example, [the time-travelling\nMicrocosm debugger](https://github.com/vigetlabs/microcosm-debugger):\n\n```javascript\nlet forever = new Microcosm({ maxHistory: Infinity })\n```\n\n\u003ca href=\"https://github.com/vigetlabs/microcosm-debugger\" style=\"display: block\"\u003e\n  \u003cimg style=\"display: block; margin: 0 auto;\" src=\"https://github.com/vigetlabs/microcosm-debugger/raw/master/docs/chat-debugger.gif\" alt=\"Microcosm Debugger\" width=\"600\" /\u003e\n\u003c/a\u003e\n\nTaken from [the Chatbot example](https://github.com/vigetlabs/microcosm/tree/master/examples/chatbot).\n\n#### Optimistic updates\n\n**Microcosm will never clean up an action that precedes incomplete\nwork** When an action moves from `open` to `done`, or `cancelled`, the\nhistorical account of actions rolls back to the last state, rolling\nforward with the new action states. This makes optimistic updates simpler\nbecause action states are self cleaning:\n\n```javascript\nimport { send } from 'actions/chat'\n\nconst Messages = {\n  getInitialState () {\n    return []\n  },\n\n  setPending(messages, item) {\n    return messages.concat({ ...item, pending: true })\n  },\n\n  setError(messages, item) {\n    return messages.concat({ ...item, error: true })\n  },\n\n  addMessage(messages, item) {\n    return messages.concat(item)\n  }\n\n  register () {\n    return {\n      [send]: {\n        open: this.setPending,\n        error: this.setError,\n        done: this.addMessage\n      }\n    }\n  }\n}\n```\n\nIn this example, as chat messages are sent, we optimistically update\nstate with the pending message. At this point, the action is in an\n`open` state. The request has not finished.\n\nOn completion, when the action moves into `error` or `done`, Microcosm\nrecalculates state starting from the point _prior_ to the `open` state\nupdate. The message stops being in a loading state because, as far as\nMicrocosm is now concerned, _it never occured_.\n\n### Forks: Global state, local concerns\n\nGlobal state management reduces the complexity of change propagation\ntremendously. However it can make application features such as\npagination, sorting, and filtering cumbersome.\n\nHow do we maintain the current page we are on while keeping in sync\nwith the total pool of known records?\n\nTo accommodate this use case, there is `Microcosm::fork`:\n\n```javascript\nconst UsersDomain = {\n  getInitialState() {\n    return []\n  },\n  addUsers(users, next) {\n    return users.concat(next)\n  },\n  register() {\n    return {\n      [getUsers]: this.addUsers\n    }\n  }\n})\n\nconst PaginatedUsersDomain {\n  getInitialState() {\n    return []\n  },\n  addUsers(users, next) {\n    let page = next.map(user =\u003e user.id)\n\n    // Reduce the user list down to only what was included\n    // in the current request\n    return users.filter(user =\u003e page.contains(user.id))\n  },\n  register() {\n    return {\n      [getUsers]: this.addUsers\n    }\n  }\n})\n\nlet roster = new Microcosm()\nlet pagination = parent.fork()\n\nroster.addDomain('users', UsersDomain)\npagination.addDomain('users', PaginatedUsersDomain)\n\n// Forks share the same history, so you could also do\n// `pagination.push(getUsers, ...)`\nroster.push(getUsers, { page: 1 }) // 10 users\nroster.push(getUsers, { page: 2 }) // 10 users\n\n// when it finishes...\nconsole.log(roster.state.users.length) // 20\nconsole.log(pagination.state.users.length) // 10\n```\n\n`fork` returns a new Microcosm, however it shares the same action\nhistory. Additionally, it inherits state updates from its\nparent. In this example, we've added special version of the `roster`\nrepo that only keeps track of the current page.\n\nAs `getUsers()` is called, the `roster` will add the new users to the\ntotal pool of records. Forks dispatch sequentially, so the child\n`pagination` repo is able to filter the data set down to only what it\nneeds.\n\n### Networks of Microcosms with Presenters\n\nFork is an important component of\nthe [`Presenter` addon](./docs/api/presenter.md). Presenter is a\nspecial React component that can build a view model around a given\nMicrocosm state, sending it to child \"passive view\" components.\n\nAll Microcosms sent into a Presenter are forked, granting them a sandbox\nfor data operations specific to a particular part of an application:\n\n```javascript\nclass PaginatedUsers extends Presenter {\n  setup (repo, { page }) {\n    repo.add('users', PaginatedUsersDomain)\n\n    repo.push(getUsers, page)\n  }\n\n  getModel () {\n    return {\n      page: state =\u003e state.users\n    }\n  }\n\n  render () {\n    const { page } = this.model\n\n    return \u003cUsersTable users={page} /\u003e\n  }\n}\n\nconst repo = new Microcosm()\nrepo.addDomain('users', UsersDomain)\n\nReactDOM.render(\u003cPaginatedUsers repo={repo} page=\"1\" /\u003e, el)\n```\n\n## Inspiration\n\n- [Worlds](http://www.vpri.org/pdf/rn2008001_worlds.pdf)\n- [Om](https://github.com/omcljs/om)\n- [Elm Language](https://elm-lang.org)\n- [Flummox](https://github.com/acdlite/flummox)\n- [But the world is mutable](http://www.lispcast.com/the-world-is-mutable)\n- [Event Sourcing Pattern](http://martinfowler.com/eaaDev/EventSourcing.html)\n- [Apache Kafka](http://kafka.apache.org/)\n- [LMAX Architecture](http://martinfowler.com/articles/lmax.html)\n- [Redux](https://github.com/reactjs/redux) (Provider/Connect and thunks)\n- [Retroactive Data Structures](https://en.wikipedia.org/wiki/Retroactive_data_structures)\n\n---\n\n\u003ca href=\"http://code.viget.com\"\u003e\n  \u003cimg src=\"http://code.viget.com/github-banner.png\" alt=\"Code At Viget\"\u003e\n\u003c/a\u003e\n\nVisit [code.viget.com](http://code.viget.com) to see more projects from [Viget.](https://viget.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvigetlabs%2Fmicrocosm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvigetlabs%2Fmicrocosm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvigetlabs%2Fmicrocosm/lists"}