{"id":20252450,"url":"https://github.com/ivandotv/oktopod","last_synced_at":"2025-06-19T18:39:34.318Z","repository":{"id":37986187,"uuid":"433948851","full_name":"ivandotv/oktopod","owner":"ivandotv","description":"Event bus for communication between decoupled Xstate machines (services)","archived":false,"fork":false,"pushed_at":"2025-06-18T17:57:00.000Z","size":260,"stargazers_count":13,"open_issues_count":11,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-18T18:45:44.768Z","etag":null,"topics":["event-bus","eventbus","state-machine","statemachine","xstate"],"latest_commit_sha":null,"homepage":"https://codesandbox.io/s/xstate-event-bus-demo-nvrhje?file=/src/App.tsx","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/ivandotv.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,"zenodo":null}},"created_at":"2021-12-01T18:55:51.000Z","updated_at":"2025-04-14T03:40:36.000Z","dependencies_parsed_at":"2024-04-08T02:27:51.521Z","dependency_job_id":"e0fd0dc8-5dda-4437-9b35-6ddd50ce35ca","html_url":"https://github.com/ivandotv/oktopod","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":"ivandotv/microbundle-template","purl":"pkg:github/ivandotv/oktopod","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Foktopod","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Foktopod/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Foktopod/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Foktopod/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ivandotv","download_url":"https://codeload.github.com/ivandotv/oktopod/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivandotv%2Foktopod/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260806655,"owners_count":23065991,"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":["event-bus","eventbus","state-machine","statemachine","xstate"],"created_at":"2024-11-14T10:16:48.711Z","updated_at":"2025-06-19T18:39:29.305Z","avatar_url":"https://github.com/ivandotv.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Oktopod\n\nEvent Bus for Xstate Machines\n\n[![Test](https://github.com/ivandotv/oktopod/actions/workflows/CI.yml/badge.svg)](https://github.com/ivandotv/oktopod/actions/workflows/CI.yml)\n[![Codecov](https://img.shields.io/codecov/c/gh/ivandotv/oktopod)](https://app.codecov.io/gh/ivandotv/oktopod)\n[![GitHub license](https://img.shields.io/github/license/ivandotv/oktopod)](https://github.com/ivandotv/oktopod/blob/main/LICENSE)\n\nSmall ([~1KB](https://bundlephobia.com/package/oktopod)) event bus implementation that is primarily made for sending events between Xstate services that are completely decoupled, and it also supports regular listeners (functions).\n\nPlay with a simple demo on [codesandbox](https://codesandbox.io/s/xstate-event-bus-demo-nvrhje?file=/src/App.tsx)\n\n\u003c!-- toc --\u003e\n\n- [Motivation](#motivation)\n- [Installation](#installation)\n- [Communication between services](#communication-between-services)\n  - [Registering the service with the event bus](#registering-the-service-with-the-event-bus)\n  - [Event bus action helpers](#event-bus-action-helpers)\n    - [sendTo](#sendto)\n    - [forwardTo](#forwardto)\n    - [emit](#emit)\n- [Register Service for event bus events](#register-service-for-event-bus-events)\n- [Simple function listeners](#simple-function-listeners)\n  - [Unregister all listeners](#unregister-all-listeners)\n- [API docs](#api-docs)\n- [License](#license)\n\n\u003c!-- tocstop --\u003e\n\n## Motivation\n\nAfter working with Xstate on a couple of projects, one problem was always present. At some point, there was always a need to enable communication between services that are completely decoupled. There was a need for some kind of event bus. So I've made this little module that enables just that.\n\nThis module is a specialized event bus that can route events between services.\n\n## Installation\n\n```sh\nnpm install oktopod\n```\n\n## Communication between services\n\nThere are two ways for the event bus to work with Xstate services.\n\n1. By registering the service with the event bus. When the service is registered, other services can send events directly to the registered service, just by knowing the service id.\n\n2. Register the service with the event bus for specific events. This way the service can respond to any event that is emitted via the event bus.\n\n### Registering the service with the event bus\n\nLet's start with creating the machine, starting the service, and registering the service with the event bus.\n\n```ts\nimport Oktopod from 'oktopod'\nimport { interpret } from 'xstate'\nimport { createMachine } from './machines'\n// create the event bus\nconst eventBus = new Oktopod()\n\nconst service = interpret(createMachine()).start()\n\nconst unregisterFn = eventBus.register(service)\n//unreigster later\nunregisterFn()\n// or\neventBus.unreigster(service)\n```\n\nWhen the service is registered with the event bus, it can be retrieved with the service `id`\n\n```ts\neventBus.getServiceById(service.id)\n```\n\nOther services can send events directly to the registered service, all they need is access to the event bus.\n\nIn the next example, the service (`machineTwo`) has access to the event bus, and when the service receives the `SEND_HELLO` event, it will send the `HELLO` event with data `{foo:'bar'}` to the service with the `id` of `machineOne` (which is registered with the event bus)\n\n```ts\nexport function machineTwo(eventBus: Oktopod) {\n  // event bus has special actions for xstate services\n  const { sendTo } = eventBus.actions\n\n  return {\n    id: 'machineTwo',\n    initial: 'idle',\n    states: {\n      idle: {\n        on: {\n          SEND_HELLO: {\n            actions: sendTo('machineOne', {\n              type: 'HELLO',\n              data: { foo: 'bar' }\n            })\n          }\n        }\n      }\n    }\n  }\n}\n\nconst machineOne = {\n  id: 'machineOne',\n  initial: 'idle',\n  states: {\n    idle: {\n      on: {\n        HELLO: {\n          //^  triggered via event bus\n          actions: (_ctx, evt) =\u003e {\n            //evt: {type: 'HELLO', data:{foo:'bar'}}\n          }\n        }\n      }\n    }\n  }\n}\n\nconst serviceOne = interpret(machineOne).start()\n//serviceTwo needs access to the even bus\nconst serviceTwo = interpret(machineTwo(eventBus)).start()\n\n//serviceOne needs to be registered with the event bus\neventBus.register(serviceOne)\n\nserviceTwo.send({ type: 'SEND_HELLO' })\n```\n\nAnother way to send the even to the machine is by using the event bus directly.\n\n```ts\neventBus.register(serviceOne)\n\n/*\nif useStrict is true, the event bus will throw if\nservice is not present or does not accept the event\n */\nconst useStrict = true\neventBus.sendTo(\n  service.id,\n  {\n    type: 'EVENT_A',\n    data\n  },\n  useStrict\n)\n```\n\nFew very important things to keep in mind:\n\n- `serviceOne` will **only** receive the `HELLO` event if the machine is in the state where one of the next accepted events is `HELLO`. This protects from services that have `strict` mode enabled (which throws an error if the service can't receive the event that is sent to it)\n\n- `serviceOne` will **only** receive the event if it running. If the service is in a `final` state then the event will not be sent.\n\n### Event bus action helpers\n\nAs seen in the previous example event bus comes with some `xstate actions` that can be used from inside the machine.\nYou can access them via `actions` property on the `Oktopod` instance\n\n```ts\neventBusInstance.actions //property\n```\n\n`actions` property is bound to the event bus instance so it can safely be de-structured\n\n```ts\nconst { sendTo, forwardTo } = eventBusInstance.actions\n```\n\n#### sendTo\n\n`sendTo` Sends an event to Xstate service that is registered with the event bus.\n\n```ts\nsendTo('machineOne', {\n  // or array of id's ['machineOne','machineX']\n  type: 'HELLO',\n  data: { foo: 'bar' }\n})\n\n// OR\n\nsendTo(\n  (ctx, evt) =\u003e {\n    return 'moonMachine'\n    // or\n    return ['machineOne', 'machineX', 'machineY']\n  },\n  (ctx, evt) =\u003e {\n    return {\n      type: 'HELLO',\n      data: { earthTemp: '46℃' }\n    }\n  }\n)\n// OR\n// combination of the above\n\n//strict mode\nsendTo('machineOne', {}, true)\n```\n\nIn `strict` mode (third argument), the event bus will an throw error if the service that is being sent to is not registered with the event bus.\n\n#### forwardTo\n\n`forwardTo` Forwards the received event to a service that is registered with the event bus. In the next example, `machineOne` will forward `SOME_EVENT` to `machineTwo` (which is registered with the event bus).\n\n```ts\nconst { forwardTo } = eventBusInstance.actions\n\ncreateMachine({\n  id: 'machineOne',\n  initial: 'idle',\n  context: {},\n  states: {\n    idle: {\n      on: {\n        SOME_EVENT: {\n          actions: [forwardTo('machineTwo')]\n        }\n      }\n    }\n  }\n})\n```\n\nYou can use it like this:\n\n```ts\nforwardTo('machineOne')\n\n// or\nforwardTo(['machineOne', 'machineTwo', 'machineThree'])\n// or\nforwardTo((ctx, evt) =\u003e {\n  return 'machineOne'\n  // or\n  return ['machineOne', 'machineTwo', 'machineThree']\n})\n//strict mode\nforwardTo('machineOne', true)\n```\n\nIn `strict` mode, the event bus will throw an error if the service that is being forwarded to is not registered with the event bus.\n\n#### emit\n\n`emit` Emits the event on the event bus and any listeners that are registered for that specific event will be triggered.\n\n```ts\nemit('HELLO_WORLD', { foo: 'bar' })\n// OR\nemit(\n  (ctx, evt) =\u003e {\n    return 'HELLO_WORLD'\n  },\n  (ctx: any) =\u003e {\n    return { foo: 'bar' }\n  }\n)\n```\n\n## Register Service for event bus events\n\nIn addition to facilitating communication between services,\nevent bus can directly register xstate service to respond to events that are sent via event bus (you can also register for all events that are emitted from the even bus by using a `\"*\"` as event name)\n\n```ts\nconst eventBus = new Oktopod()\n\nconst unsubscribeFn = bus.on('HELLO_WORLD', service, 'EVENT_ON_SERVICE')\n//or register for all events\nconst unsubscribeFn = bus.on('*', service, 'EVENT_ON_SERVICE')\n\n//emit the event\neventBus.emit('HELLO_WORLD', { foo: 'bar' })\n\n//example machine\nconst machine = {\n  initial: 'idle',\n  context: {},\n  states: {\n    idle: {\n      on: {\n        EVENT_ON_SERVICE: {\n          actions: (ctx, evt) =\u003e {\n            // evt will be:\n            //   {\n            //   type: 'EVENT_ON_SERVICE', \u003c- xstate event\n            //   event: 'HELLO_WORLD', \u003c- bus event\n            //   data: { foo: 'bar' } \u003c- bus event data\n            // }\n          }\n        }\n      }\n    }\n  }\n}\n\n//unregister later\nunsubscribeFn()\n// or\neventBus.off('HELLO_WORLD', service)\n```\n\nIn the example above, machine service has been registered for the `HELLO_WORLD` event on the event bus, and when that event is emitted, the service will receive `EVENT_ON_SERVICE` event.\n\nLike in the _service to service_ communication previously documented, the service will not receive the event if it is not supported, or the service is not running (it's in its final state, and doesn't accept any events)\n\n## Simple function listeners\n\n\u003e Note: This part has nothing to do with xstate services\n\nEvent bus can also accept functions as listeners. This functionality enables you to hook other parts of your app to the event bus.\n\n```ts\nimport Oktopod, { EventPayload } from 'oktopod'\n\nconst eventBus = new Oktopod()\n\n//register simple function\nconst listener = (evt: EventPayload\u003c{ foo: string }\u003e) =\u003e {\n  //  evt will be:\n  //{ data: { foo: 'bar' }, event: 'hello' }\n}\n\nconst unregister = eventBus.on('hello', listener)\n//unregister later\nunregister()\n// or\neventBus.off('hello', listener)\n\n//trigger the listener\neventBus.emit('hello', { foo: 'bar' })\n```\n\n### Unregister all listeners\n\nYou can unregister all listeners for a specific event. This will clear all listeners (services and functions).\n\n```ts\neventBus.clear('event_name')\n```\n\n## API docs\n\n`Oktopod` is written in TypeScript, [auto generated API documentation](docs/api/README.md) is available.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details\n\n---\n\n`Oktopod` means `Octopus` in Serbian :)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivandotv%2Foktopod","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fivandotv%2Foktopod","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivandotv%2Foktopod/lists"}