{"id":15509566,"url":"https://github.com/huan/mailbox","last_synced_at":"2025-04-14T19:44:15.272Z","repository":{"id":40559628,"uuid":"475469025","full_name":"huan/mailbox","owner":"huan","description":"Mailbox is the predictable states \u0026 transitions container for actors.","archived":false,"fork":false,"pushed_at":"2023-05-27T09:36:29.000Z","size":796,"stargazers_count":50,"open_issues_count":3,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-12T05:03:03.459Z","etag":null,"topics":["actor","event-driven","fsm","state-machine","xstate"],"latest_commit_sha":null,"homepage":"https://paka.dev/npm/mailbox/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/huan.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-29T14:01:32.000Z","updated_at":"2025-01-17T12:27:47.000Z","dependencies_parsed_at":"2024-06-19T13:43:16.067Z","dependency_job_id":null,"html_url":"https://github.com/huan/mailbox","commit_stats":{"total_commits":203,"total_committers":1,"mean_commits":203.0,"dds":0.0,"last_synced_commit":"964e6084e9bdb7823d70424ae3baec3550964d04"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huan%2Fmailbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huan%2Fmailbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huan%2Fmailbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huan%2Fmailbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/huan","download_url":"https://codeload.github.com/huan/mailbox/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248879380,"owners_count":21176489,"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":["actor","event-driven","fsm","state-machine","xstate"],"created_at":"2024-10-02T09:43:16.164Z","updated_at":"2025-04-14T19:44:15.250Z","avatar_url":"https://github.com/huan.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mailbox (turns XState Machine into an Actor that can deal with concurrency requests)\n\n[![NPM Version](https://badge.fury.io/js/mailbox.svg)](https://www.npmjs.com/package/mailbox)\n[![NPM](https://github.com/huan/mailbox/workflows/NPM/badge.svg)](https://github.com/huan/mailbox/actions?query=workflow%3ANPM)\n[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-blue.svg)](https://www.typescriptlang.org/)\n[![ES Modules](https://img.shields.io/badge/ES-Modules-brightgreen)](https://github.com/Chatie/tsconfig/issues/16)\n[![Ducksify Extension](https://img.shields.io/badge/Redux-Ducksify-yellowgreen)](https://github.com/huan/ducks#3-ducksify-extension-currying--ducksify-interface)\n[![Mailbox.Duckula Specification](https://img.shields.io/badge/Specification-Mailbox.Duckula-blueviolet)](https://github.com/huan/mailbox#duckula-specification)\n\nMailbox is an NPM module built on top of the XState machine, by adding a message queue to the XState machine and letting the machine decide when to process the next message.\n\n![Actor Model: Mailbox](docs/images/mailbox.png)\n\n\u003e Mailboxes are one of the fundamental parts of the actor model. Through the mailbox mechanism, actors can decouple the reception of a message from its elaboration.\n\u003e \n\u003e 1. An actor is an object that carries out its actions in response to communications it receives.\n\u003e 1. A mailbox is nothing more than the data structure that holds messages.\n\u003e \n\u003e \u0026mdash; \u003chttps://www.baeldung.com/scala/typed-mailboxes\u003e\n\n\u003e if you send 3 messages to the same actor, it will just execute one at a time.  \n\u003e \u0026mdash; [The actor model in 10 minutes - Actors have mailboxes](https://www.brianstorti.com/the-actor-model/)\n\nThe design of Mailbox is very like the the Akka Actor Model:\n\n- [Introduction to Akka Actor Model](https://tech401.com/2018/05/31/2018/2018-05-31-introduction_to_akka_model/)\n\n## Features\n\n- Build on top of the powerful Finite State Machine (FSM) with [XState](https://xstate.js.org/docs/guides/introduction-to-state-machines-and-statecharts/) library.\n- Implemented Mailbox pattern for [Actor Model](https://en.wikipedia.org/wiki/Actor_model): process one message at a time, with a message queue\n- Address has been abstracted by a `Address` class for actors\n- Unit tests covered\n\n## Voice of Developers\n\n\u003e The mailbox is separate (which itself can be an actor) and the timing of events forwarded to the statechart can be n-time whereas transitions within the s/c are zero-time. ([tweet](https://twitter.com/DavidKPiano/status/1510793483359240194))  \n\u003e \u0026mdash; [@DavidKPiano](https://twitter.com/DavidKPiano), 🚀 Creator of XState\n\n\u003e ... statecharts process events immediately while actors (by means of message queues) give you granular control of when to process the next event. ([tweet](https://twitter.com/chrisshank23/status/1510786425276416004))  \n\u003e \u0026mdash; [@chrisshank23](https://twitter.com/chrisshank23), core member of StateML\n\n## TODO\n\n- [ ] Support distribution actor by adding remote actor abstraction to the `Address` class\n\n## Motivation\n\nI'm building assistant chatbot for Wechaty community, and I want to use actor model based on XState to implement it.\n\nMy actor will receive message from Wechaty, and send message to Wechaty.\n\nHowever, ... (describe the async \u0026 multi-user scanerio for the conversation turns)\n\nIt turns out ... (describe that we need to make sure the incoming messages are queued when we not finished processing the last one)\n\n\u003e Thread-safe code only manipulates shared data structures in a manner that ensures that all threads behave properly and fulfill their design specifications without unintended interaction.\n\n- [Wikipedia: Thread Safety](https://en.wikipedia.org/wiki/Thread_safety)\n\n## The Problem\n\nA naive state machine is a mailbox actor with `capacity=0`, which means it will face the `Dead Letter` problem when new messages come but it has not finished processing the last one.\n\n![FSM v.s. Actor](docs/images/fsm-vs-actor.png)\n\nAssume we are a coffee maker, and we need 4 three steps to make a coffee:\n\n1. received a `MAKE_ME_COFFEE` event from a customer (sync)\n1. get a cup (async: cost some time)\n1. fill coffee into the cup (async: cost some time)\n1. send a `COFFEE` event to the customer (sync)\n\nThe coffee maker can only make one cup of coffee at a time, which means that we can not process the `MAKE_ME_COFFEE` event until we have finished the last one.\n\nThe state machine of the coffee maker is:\n\n[![coffee maker machine](https://stately.ai/registry/machines/5bd10d92-3d39-49b5-ad0d-13a8a0a43891.png)](https://stately.ai/viz/5bd10d92-3d39-49b5-ad0d-13a8a0a43891)\n\nHere's the source code of coffee maker:\n\n```ts\nconst machine = createMachine({\n  context: {\n    customer: null,\n  },\n  initial: states.idle,\n  states: {\n    [states.idle]: {\n      entry: Mailbox.actions.idle('coffee-maker'),\n      on: {\n        [types.MAKE_ME_COFFEE]: {\n          target: states.making,\n          actions: actions.assign((_, e) =\u003e ({ customer: e.customer })),\n        },\n        '*': states.idle,\n      },\n    },\n    [states.making]: {\n      after: {\n        10: states.delivering,\n      },\n    },\n    [states.delivering]: {\n      entry: Mailbox.actions.reply(ctx =\u003e Events.COFFEE(ctx.customer || 'NO CUSTOMER')),\n      after: {\n        10: states.idle,\n      },\n      exit: actions.assign({ customer: _ =\u003e null }),\n    },\n  },\n})\n```\n\nIf there's a new customer come in, and he/she want coffee, we can get a cup then fill coffee to the cup then deliver a cup of coffee to our customer. Everything is fine so far so good.\n\nHowever, when there are two customer coming in together, and they talk to us at the same time and each customer want a cup of coffee. After we received the first request(event/message), we are starting to get cup and can not listen to another request anymore, which will result an event (the second one) lost (a Dead Letter).\n\n## The Solution\n\nAn actor should read the messages to process from its mailbox. A mailbox is an event proxy that holds messages and deals with the backpressure. When the actor have finished processing the current event, it will receive(pull) the next event from the mailbox.\n\n[Mailbox](https://www.npmjs.com/package/mailbox) for rescue.\n\nMailbox is a NPM module written in TypeScript based on the XState finite state machine to strict follow the actor model's principle:\n\n```ts\nconst mailbox = Mailbox.from(machine)\n```\n\nThen use `mailbox` instead.\n\n## Mailbox Actor Architecture Diagram\n\n![XState Mailbox Actor Architecture Diagram](docs/images/actor-mailbox-architecture-diagram.svg)\n\n\u003e Image credit: Goeth Rubber Duck, @Lanco, \u003chttps://ducksinthewindow.com/goeth-rubber-duck/\u003e  \n\u003e SVG image generated by \u003chttps://www.visioncortex.org/\u003e\n\nLearn more about similiar (i.e. Akka) Actor \u0026 Mailbox diagram with discussion from this Issue: [statelyai/xstate#2870](https://github.com/statelyai/xstate/issues/2870#issuecomment-1002346225)\n\n## Quick Start\n\n1. `import * as Mailbox from 'mailbox'`\n1. Add `Mailbox.actions.idle('child-id')` to the `entry` of state of your machine which it accepting new messages, to let the Mailbox continue sending new messages from other actors.\n1. Use `Mailbox.actions.reply('YOUR_EVENT')` to reply event messages to other actors.\n1. Use `const mailbox = Mailbox.from(yourMachine)` to wrap your actor with mailbox address. The mailbox address is a parent XState machine which will invok your machine as child and add message queue to the child machine.\n\n```ts\nimport * as Mailbox       from 'mailbox'\nimport { createMachine }  from 'xstate'\n\nconst machine = createMachine({\n  initial: 'idle',\n  states: {\n    idle: {\n      /**\n       * RULE #1: machine must has `Mailbox.Actions.idle('child-id')` \n       */\n      entry: Mailbox.actions.idle('child-machine-name'),\n      on: {\n        '*': {\n          /**\n           * RULE #2: machine must use an external transision to the `idle` state when it finished processing any messages, to trigger the `entry` action.\n           */\n          target: 'idle',\n          actions: actions.log('make sure the idle state will be re-entry with external trainsition when receiving event'),\n        },\n        BUSY: 'busy',\n      },\n    },\n    busy: {\n      /**\n       * RULE #3: machine use `Mailbox.Actions.reply(EVENT)` to reply EVENT to other actors.\n       */\n      entry: Mailbox.actions.reply('YOUR_EVENT'),\n      after: {\n        10: 'idle',\n      },\n    },\n  },\n})\n\nconst mailbox = Mailbox.from(yourMachine)\n// just use it as a standard XState machine\n```\n\nYou can run a full version at [examples/mailbox-demo.ts](examples/mailbox-demo.ts) and see the result:\n\n```sh\n$ ./mailbox-demo.ts \n# testing raw machine ...\nsending TASK\nTASK_RECEIVED\nsending TASK\n# testing raw machine ... done\n\n# testing mailbox-ed machine ...\nsending TASK\nTASK_RECEIVED\nsending TASK\nTASK_RECEIVED\n# testing mailbox-ed machine ... done\n```\n\n## Duckula Specification\n\n[![Mailbox.Duckula Specification](https://img.shields.io/badge/Specification-Mailbox.Duckula-blueviolet)](https://github.com/huan/mailbox#duckula-specification)\n[![Issue #1](https://img.shields.io/badge/Issue-%231-brightgreen)](https://github.com/huan/mailbox/issues/1)\n\nThe `Duckula` is like [Duck](https://github.com/huan/ducks#3-ducksify-extension-currying--ducksify-interface) for Mailbox Actors.\n\n![Duckula Specification for Mailbox Actor](docs/images/duckula.png)\n\n\u003e Image credit: [Papercraft Count Duckula](https://www.deviantart.com/cyberdrone/art/Cubeecraft-Count-Duckula-409375145)\n\nThe specification has rules that a Mailbox Actor module:\n\n1. MUST export a `id` of type `string`\n1. MUST export a `machine` of type XState machine\n1. MUST export a `initialContext` of type `function`, with the `Context` typing, which is the initial context of the machine\n1. MUST export a `Event` of type map of `function` which is event creators (must use [typesafe-actions](https://github.com/piotrwitek/typesafe-actions))\n1. MUST export a `Type` of type map of `string` which is event types, values in the form `npm-module-or-app/EVENT_TYPE`\n1. MUST export a `State` of type map of `string` which is states, values in the form `npm-module-or-app/StateName`\n1. MUST be `UPPER_SNAKE_CASE` for the keys of `Event` and `Type`\n1. MUST be `UpperCamelCase` for the keys of `State`\n\n\u003e Similiar ideas: [Duckula for Clojure](https://github.com/nomnom-insights/nomnom.duckula)\n\n### `Duckula` Interface\n\n```ts\ninterface Duckula \u003c...\u003e {\n  id: TID\n  Type: TType\n  Event: TEvent\n  State: TState\n  machine: TMachine\n  initialContext: () =\u003e TContext\n}\n```\n\nRead the source code at [src/duckula/duckula.ts](src/duckula/duckula.ts)\n\n### `duckularize` Function\n\n```ts\nimport * as Mailbox from 'mailbox'\n\nimport * as states from './states.js'\n// `events.js` must use `typesafe-actions`\nimport * as events from './events.js'\n\ninterface Context {}\n\nconst duckula = Mailbox.duckularize({\n  id: 'MyMachineActor',\n  initialContext: {} as Context,\n  events: [ events, [ 'EVENT1', 'EVENT2' ] ], // or: `events: events` if you do not need filtering\n  states: [ states, [ 'State1', 'State2' ] ], // or: `states: states` if you do not need filtering\n})\n// `duckula` is a `Duckula` now.\n```\n\n### Duckula Examples\n\n1. Ding Dong Machine: \u003ctests/machine-behaviors/ding-dong-machine.ts\u003e\n1. Coffee Maker Machine: \u003ctests/machine-behaviors/coffee-maker-machine.ts\u003e\n1. Baby Machine: \u003ctests/machine-behaviors/baby-machine.ts\u003e\n\n### Duckula Badge\n\n[![Mailbox.Duckula Specification](https://img.shields.io/badge/Specification-Mailbox.Duckula-blueviolet)](https://github.com/huan/mailbox#duckula-specification)\n\n```md\n[![Mailbox.Duckula Specification](https://img.shields.io/badge/Specification-Mailbox.Duckula-blueviolet)](https://github.com/huan/mailbox#duckula-specification)\n```\n\n## API References\n\nRead detail (auto-generated) docs at \u003chttps://paka.dev/npm/mailbox\u003e\n\n```ts\nimport * as Mailbox from 'mailbox'\n```\n\n### 1. `Mailbox`\n\n#### 1.1 `Mailbox.from()`\n\n```ts\nconst mailbox = Mailbox.from(machine, options)\n```\n\nOptions:\n\n```ts\ninterface Options {\n  id?       : string\n  capacity? : number\n  logger?   : InterpreterOptions['logger'],\n  devTools? : InterpreterOptions['devTools'],\n}\n```\n\n#### 1.2 `mailbox.address()`\n\n#### 1.3 `mailbox.send()`\n\n#### 1.4 `mailbox.on()`\n\n#### 1.5 `mailbox.open()`\n\n#### 1.6 `mailbox.close()`\n\n### 2. `Mailbox.Address`\n\n#### 2.1. `address.send()`\n\n#### 2.2. `address.condNotOrigin()`\n\n### 3. `Mailbox.actions.*`\n\n#### 3.1. `Mailbox.actions.idle()`\n\n#### 3.2. `Mailbox.actions.reply()`\n\n#### 3.3. `Mailbox.actions.proxyToChild()`\n\n### 4. `Mailbox.nil.*`\n\n#### 4.1 `Mailbox.nil.mailbox`\n\n#### 4.2 `Mailbox.nil.address`\n\n#### 4.3 `Mailbox.nil.logger`\n\n#### 4.4 `Mailbox.nil.machine`\n\n### 5. `Mailbox.helper.*`\n\n#### 5.1 `Mailbox.helper.validate()`\n\n#### 5.2 `Mailbox.helper.wrap()`\n\n## Actor Inbound \u0026 Outbound Communication\n\nThe mailbox actor will queue the second inbound messages to the child machine, and will not pass it to the child machine until the first inbound message is processed.\n\nHowever, this are cases that the child machine that needs to communicate with other actors, and receives response messages from other actors.\n\nFor outbound messages, the machine internally can send messages to other actors, and receives outbound response messages from other actors without the inbound limitation (the response of the outbound message will be passed by the mailbox queue directly).\n\nThe machine internal address will be used to send messages to other actors, and receive messages with this address will by pass the Mailbox queue, for supporting multiple outbound message communication.\n\n```mermaid\nsequenceDiagram\n  participant Consumer\n  participant Mailbox\n  participant Machine\n  participant Service\n\n  Consumer-\u003e\u003eMailbox: {type: EVENT1}\n  Note right of Consumer: Inbound Message Request 1\n  Consumer--\u003e\u003eMailbox: {type: EVENT2}\n  Note right of Consumer: Inbound Message Request 2\n  Note right of Mailbox: Processing EVENT1\u003cbr\u003eQueue EVENT2\n  Mailbox-\u003e\u003eMachine: {type: EVENT1}\n  Machine-\u003e\u003eService: {type: LOG_COMMAND}\n  Machine-\u003e\u003eService: {type: DB_QUERY}\n  Note right of Machine: Multiple Outbound Message Requests\n  Service-\u003e\u003eMachine: {type: LOG_COMMAND_RESPONSE}\n  Service-\u003e\u003eMachine: {type: DB_QUERY_RESPONSE}\n  Note right of Machine: Multiple Outbound Message Responses\n  Machine-\u003e\u003eMailbox: {type: EVENT1_RESPONSE}\n  Mailbox-\u003e\u003eConsumer: {type: EVENT1_RESPONSE}\n  Note right of Consumer: Inbound Message Response 1\n  Note right of Mailbox: Dequeue EVENT2\u003cbr\u003eProcessing EVENT2\n  Mailbox--\u003e\u003eMachine: {type: EVENT2}\n```\n\n\u003e Caution: be aware of the dead lock if your have two actors call each other in the same machine.\n\n## Actor Mailbox Concept\n\nActors have mailboxes.\n\n\u003e In the actor model, we must follow that: \"if you send 3 messages to the same actor, it will just execute one at a time.\"\n\u003e\n\u003e It’s important to understand that, although multiple actors can run at the same time, an actor will process a given message sequentially. This means that if you send 3 messages to the same actor, it will just execute one at a time. To have these 3 messages being executed concurrently, you need to create 3 actors and send one message each.\n\u003e\n\u003e Messages are sent asynchronously to an actor, that needs to store them somewhere while it’s processing another message. The mailbox is the place where these messages are stored.\n\u003e\n\u003e \u0026mdash; [The actor model in 10 minutes](https://www.brianstorti.com/the-actor-model/)\n\n\u003e an actor is started it will keep running, processing messages from its inbox and won’t stop unless you stop it. It will maintain its state throughout and only the actor has access to this state. This is unlike your traditional asynchronous programming, such as using Future in Scala or promises in javascript where once the call has finished its state between calls is not maintained and the call has finished.\n\u003e\n\u003e Once an actor receives a message, it adds the message to its mailbox. You can think of the mailbox as queue where the actor picks up the next message it needs to process. Actors process one message at a time. The actor patterns specification does say that the order of messages received is not guaranteed, but different implementations of the actor pattern do offer choices on mailbox type which do offer options on how messages received are prioritized for processing.\n\u003e\n\u003e \u0026mdash; [Introduction to the Actor Model](https://www.softinio.com/post/introduction-to-the-actor-model/)\n\n## Usage\n\n### XState Machine\n\n1. The `Mailbox.actions.idle('machine-name')('reason')` action must be put inside the `entry` action of when it's ready to receive message (in `states.idle` for example)\n1. All events that received in `states.idle` must make a `external` trancition by adding a `target` entry, so that the `states.idle` state will be entered again, which will emit the `Mailbox.actions.idle('machine-name')` to parent (Mailbox) to let the Mailbox know the machine is ready to receive the next message.\n\nLearn more from [validate.ts source code](validate.ts)\n\n### Dead Letter\n\nWhenever a message fails to be written into an actor mailbox, the actor system redirects it to a synthetic actor called /deadLetters. The delivery guarantees of dead letter messages are the same as any other message in the system. So, it’s better not to trust so much in such messages. The main purpose of dead letters is debugging.\n\n```ts\nmailbox.onEvent(event =\u003e {\n  if (event.type === Mailbox.types.DEAD_LETTER) {\n    console.error('DEAD_LETTER:', event.payload)\n  }\n})  \n```\n\nRelated reading:\n\n- [Proto.Actor - Dead Letters](https://proto.actor/docs/deadletter/)\n\n### Bounded vs. Unbounded\n\nThe mailbox is unbounded by default, which means it doesn’t reject any delivered message. Besides, there’s no back pressure mechanism implemented in the actor system. Hence, if the number of incoming messages is far bigger than the actor’s execution pace, the system will quickly run out of memory.\n\nAs we said, unbounded mailboxes grow indefinitely, consuming all the available memory if the messages’ producers are far quicker than the consumers. Hence, we use this kind of mailbox only for trivial use cases.\n\nOn the other side, bounded mailboxes retain only a fixed number of messages. The actor system will discard all of the messages arriving at the actor when the mailbox is full. This way, we can avoid running out of memory.\n\nAs we did a moment ago, we can configure the mailbox’s size directly using the Mailbox.bounded factory method. Or, better, we can specify it through the configuration properties file:\n\n```ts\nconst mailbox = Mailboxe.from(machine, { \n  capacity: 100,\n})\n```\n\nThe example above is a clear example where bounded mailboxes shine. We are not afraid of losing some messages if the counterpart maintains the system up and running.\n\nA new question should arise: Where do the discarded messages go? Are they just thrown away? Fortunately, the actor system lets us retrieve information about discarded messages through the mechanism of dead letters — we’ll soon learn more about how this works.\n\n\u003e Credit: \u003chttps://www.baeldung.com/scala/typed-mailboxes#1-bounded-vs-unbounded\u003e\n\n## Actor Patterns\n\n### The Tell Pattern\n\n\u003e Tell, Don’t Ask!\n\nIt’s often said that we must “tell an actor and not ask something“. The reason for this is that the tell pattern represents the fully asynchronous way for two actors to communicate.\n\nThe tell pattern is entirely asynchronous. After the message is sent, it’s impossible to know if the message was received or if the process succeeded or failed.\n\nTo use the Tell Pattern, an actor must retrieve an actor reference to the actor it wants to send the message to.\n\n\u003e See also: [Akka Interaction Patterns: The Tell Pattern](https://www.baeldung.com/scala/akka-tell-pattern)\n\n### The Ask Pattern\n\n\u003e Request-Response\n\nThe Ask Pattern allows us to implement the interactions that need to associate a request to precisely one response. So, it’s different from the more straightforward adapted response pattern because we can now associate a response with its request.\n\nThe Mailbox implementes the Ask Pattern by default. It will response to the original actor sender when there's any response events from the child actor.\n\n### The `Event` v.s. `Message`\n\n- \"The difference being that messages are directed, events are not — a message has a clear addressable recipient while an event just happen for others (0-N) to observe it.\" ([link](https://stackoverflow.com/a/31706206/1123955))\n- \"The difference lies in that with MessageQueues it's typical that the sender requires a response. With an EventQueue this is not necessary.\" ([link](https://stackoverflow.com/a/65209807/1123955))\n- \"A Message is some data sent to a specific address; An Event is some data emitted from a component for anyone listening to consume.\" ([link](https://developer.lightbend.com/docs/akka-platform-guide/concepts/message-driven-event-driven.html#message_vs_event))\n\n![Event v.s. Message (tell/ask)](docs/images/event-message-tell-ask-pattern.svg)\n\n\u003e Image source: [Wechaty CQRS](https://github.com/wechaty/cqrs#architecture-diagrams)\n\n## Known Issues\n\n### Never send batch events\n\nNever use `interpreter.send([...eventList])` to send multiple events. It will cause the mailbox to behavior not right (only the first event will be delivered).\n\nUse `eventList.forEach(e =\u003e interpreter.send(e))` to send event list.\n\nThe reason is that internally `Mailbox` have three parallel states and they will run into race condition in batch-event mode.\n\nSee: [XState Docs - Batched Events](https://xstate.js.org/docs/guides/interpretation.html#batched-events)\n\n## Resources\n\n- [Universal Server - My favorite Erlang Program, @Joe Arm Strong, Nov 21, 2013](https://joearms.github.io/published/2013-11-21-My-favorite-erlang-program.html)\n- [The Reactive Manifesto](https://www.reactivemanifesto.org)\n- [Apache Commons - Five Minutes SCXML](https://commons.apache.org/proper/commons-scxml/guide/scxml-documents.html)\n- [SCXML State Chart XML, Kai Blankenhorn, April 2003](https://www.i3s.unice.fr/~deantoni/teaching_resources/SI4/FSM/cours/4_SCXML_parallel.pdf)\n- [State machine replication](https://en.wikipedia.org/wiki/State_machine_replication)\n\n## Quota\n\n\u003e Redux is predictable states container, XState is predictable transitions container.\n\u003e \u0026mdash; A Youtuber comment\n\n\u003e Mailbox is predictable states \u0026 transitions container for actors.\n\u003e \u0026mdash; Huan, creator of Wechaty, Jan. 2022\n\n## History\n\n### main\n\n### v0.10 (May 3rd, 2022)\n\n1. Mailbox Interface is an Observable now.\n1. Fix the race condition bug by simplifing the queue state management to be atomic. ([Issue #5](https://github.com/huan/mailbox/issues/5))\n1. Fix wrapped machine ID conflic bug when there have nested Mailbox wrapped machines (PR [#8](https://github.com/huan/mailbox/pull/8))\n1. Add `Duckula` Interface for modulize Mailbox Actor. ([Issue #1](https://github.com/huan/mailbox/issues/1))\n1. Renaming `CHILD` to `ACTOR`:\n    1. `CHILD_REPLY` -\u003e `ACTOR_REPLY`\n    1. `CHILD_IDLE` -\u003e `ACTOR_IDLE`\n1. Duck types/states clean\n\n### v0.6 (Apr 10, 2022)\n\nRefactoring APIs\n\n1. Supports `Mailbox.actions.proxy(name)(target)` to proxy all events to a target (`string` id, `Address`, or `Mailbox`)\n1. Supports `Mailbox.actions.send(target)(event, options)` where the target can be a `string` id, `Address`, or `Mailbox` for convenience.\n\n### v0.4 (Apr 1, 2022)\n\nPublish [mailbox](https://github.com/huan/mailbox) NPM module with DevOps.\n\n### v0.2 (Dec 31, 2021)\n\n1. Implement the Actor State/Context Persisting Mechanism.\n1. Add Dead Letter Queue (DLQ) `capacity` options for dealing with the Back Pressure.\n\n- [XStates Docs - Persisting state](https://xstate.js.org/docs/guides/states.html#persisting-state)\n- [nact(node.js + actors) Docs - Persist](https://nact.xyz/en_uk/lesson/javascript/persist)\n- [XState Persistence with actors and side effects](https://codesandbox.io/s/persistence-with-actors-and-side-effects-forked-kndx7?file=/src/bootMachine.js:1892-2086)\n\n### v0.1 (Dec 24, 2021)\n\nImproving the Actor Mailbox model.\n\nRelated links:\n\n- [XState Actors](https://xstate.js.org/docs/guides/actors.html#actor-api)\n- [Typed Mailboxes in Scala, @Riccardo Cardin, Feb 23, 2021](https://www.baeldung.com/scala/typed-mailboxes)\n- [Proto.Actor - Mailboxes](https://proto.actor/docs/mailboxes/)\n- [[v5] Optimize mailbox statelyai/xstate#2870](https://github.com/statelyai/xstate/issues/2870)\n\n### v0.0.1 (Dec 18, 2021)\n\nInitial version.\n\nRelated issue discussions:\n\n- [Track the Modeling Events of XState wechaty/bot5-assistant#2](https://github.com/wechaty/bot5-assistant/issues/2)\n- [Actor model needs a Mailbox wrapper/concurrency-manager wechaty/bot5-assistant#4](https://github.com/wechaty/bot5-assistant/issues/4)\n- [Discord Actor Model discussion](https://discord.com/channels/795785288994652170/800812250306183178/917329930294009877)\n- [Kotlin Concurrency with Actors, Jag Saund, Jun 14, 2018](https://medium.com/@jagsaund/kotlin-concurrency-with-actors-34bd12531182)\n- [xstate-onentry-timing-bug.js](https://github.com/statelyai/xstate/issues/370#issuecomment-465954271)\n- [Automating Spaceships with XState, @stafford Williams, Mar 22, 2021](https://staffordwilliams.com/blog/2021/03/22/automating-spaceships-with-xstate/), \n\n## Special Thanks\n\nGreat thanks to [@alxhotel](https://github.com/alxhotel) who owned the great NPM module name [mailbox](https://www.npmjs.com/package/mailbox) and kindly transfered it to me for this new project, thank you very much [Alex](https://twitter.com/alxhotel)!\n\n\u003e Hi Huan, nice to meet you :) Glad to see a serial entrepreneur contributing to OSS Red heart I've added you as a maintainer! Feel free to remove me once you make sure you have control of the package. Looking forward to see what you are building :)\n\u003e\n\u003e Cheers,  \n\u003e Alex (Dec 20 Mon 11:47 PM)\n\n## Author\n\n[Huan LI](http://linkedin.com/in/zixia) is a serial entrepreneur, active angel investor with strong technology background.\nHuan is a widely recognized technical leader on conversational AI and open source cloud architectures.\nHe co-authored guide books \"Chatbot 0 to 1\" and \"Concise Handbook of TensorFlow 2\"\nand has been recognized both by Microsoft and Google as MVP/GDE.\nHuan is a Chatbot Architect and speaks regularly at technical conferences around the world.\nFind out more about his work at \u003chttps://github.com/huan/\u003e\n\n## Copyright \u0026 License\n\n- Docs released under Creative Commons\n- Code released under the Apache-2.0 License\n- Code \u0026 Docs © 2021 Huan LI \\\u003czixia@zixia.net\\\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhuan%2Fmailbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhuan%2Fmailbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhuan%2Fmailbox/lists"}