Mailbox is the predictable states & transitions container for actors.

Overview

Mailbox (turns XState Machine into a REAL Actor)

NPM Version NPM TypeScript ES Modules Ducksify Extension Mailbox.Duckula Specification

Mailbox 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.

Actor Model: Mailbox

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.

  1. An actor is an object that carries out its actions in response to communications it receives.
  2. A mailbox is nothing more than the data structure that holds messages.

https://www.baeldung.com/scala/typed-mailboxes

if you send 3 messages to the same actor, it will just execute one at a time.
The actor model in 10 minutes - Actors have mailboxes

The design of Mailbox is very like the the Akka Actor Model:

Features

  • Build on top of the powerful Finite State Machine (FSM) with XState library.
  • Implemented Mailbox pattern for Actor Model: process one message at a time, with a message queue
  • Typed Inject native support: dispose() method to dispose the mailbox
  • Address has been abstracted by a Address class for actors
  • Unit tests covered

Voice of Developers

This project raises an interesting tension between statecharts and actors. By default, statecharts process events immediately while actors (by means of message queues) give you granular control of when to process the next event. (link)
@chrisshank23, core member of StateML

TODO

  • Address supports remote actor

Motivation

I'm building assistant chatbot for Wechaty community, and I want to use actor model based on XState to implement it.

My actor will receive message from Wechaty, and send message to Wechaty.

However, ... (describe the async & multi-user scanerio for the conversation turns)

It turns out ... (describe that we need to make sure the incoming messages are queued when we not finished processing the last one)

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.

The Problem

A 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.

FSM v.s. Actor

Assume we are a coffee maker, and we need 4 three steps to make a coffee:

  1. received a MAKE_ME_COFFEE event from a customer (sync)
  2. get a cup (async: cost some time)
  3. fill coffee into the cup (async: cost some time)
  4. send a COFFEE event to the customer (sync)

The 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.

The state machine of the coffee maker is:

coffee maker machine

Here's the source code of coffee maker:

const machine = createMachine({
  context: {
    customer: null,
  },
  initial: states.idle,
  states: {
    [states.idle]: {
      entry: Mailbox.actions.idle('coffee-maker')('idle'),
      on: {
        [types.MAKE_ME_COFFEE]: {
          target: states.making,
          actions: actions.assign((_, e) => ({ customer: e.customer })),
        },
        '*': states.idle,
      },
    },
    [states.making]: {
      after: {
        10: states.delivering,
      },
    },
    [states.delivering]: {
      entry: Mailbox.actions.reply(ctx => Events.COFFEE(ctx.customer || 'NO CUSTOMER')),
      after: {
        10: states.idle,
      },
      exit: actions.assign({ customer: _ => null }),
    },
  },
})

If 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.

However, 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).

The Solution

An 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.

Mailbox for rescue.

Mailbox is a NPM module written in TypeScript based on the XState finite state machine to strict follow the actor model's principle:

const mailbox = Mailboxe.from(machine)

Then use mailbox instead.

Mailbox Actor Architecture Diagram

XState Mailbox Actor Architecture Diagram

Image credit: Goeth Rubber Duck, @Lanco, https://ducksinthewindow.com/goeth-rubber-duck/
SVG image generated by https://www.visioncortex.org/

Learn more about similiar (i.e. Akka) Actor & Mailbox diagram with discussion from this Issue: statelyai/xstate#2870

Quick Start

  1. import * as Mailbox from 'mailbox'
  2. Add Mailbox.actions.idle('child-id')('data') to the entry of state of your machine which it accepting new messages, to let the Mailbox continue sending new messages from other actors.
  3. Use Mailbox.actions.reply('YOUR_EVENT') to reply event messages to other actors.
  4. 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.
import * as Mailbox       from 'mailbox'
import { createMachine }  from 'xstate'

const machine = createMachine({
  initial: 'idle',
  states: {
    idle: {
      /**
       * RULE #1: machine must has `Mailbox.Actions.idle('child-id')` tinbouond-outbound.spec
      entry: Mailbox.actions.idle('child-machine-name')('idle'),
      on: {
        '*': {
          /**
           * RULE #2: machine must use an external transision to the `idle` state when it finished processing any messages, to trigger the `entry` action.
           */
          target: 'idle',
          actions: actions.log('make sure the idle state will be re-entry with external trainsition when receiving event'),
        },
        BUSY: 'busy',
      },
    },
    busy: {
      /**
       * RULE #3: machine use `Mailbox.Actions.reply(EVENT)` to reply EVENT to other actors.
       */
      entry: Mailbox.actions.reply('YOUR_EVENT'),
      after: {
        10: 'idle',
      },
    },
  },
})

const mailbox = Mailbox.from(yourMachine)
// just use it as a standard XState machine

You can run a full version at examples/mailbox-demo.ts and see the result:

$ ./mailbox-demo.ts 
# testing raw machine ...
sending TASK
TASK_RECEIVED
sending TASK
# testing raw machine ... done

# testing mailbox-ed machine ...
sending TASK
TASK_RECEIVED
sending TASK
TASK_RECEIVED
# testing mailbox-ed machine ... done

Duckula Specification

Mailbox.Duckula Specification

The Duckula is like Duck for Mailbox Actors.

Duckula Specification for Mailbox Actor

Image credit: Papercraft Count Duckula

The specification has rules that a Mailbox Actor module:

  1. MUST export a id of type string
  2. MUST export a machine of type XState machine
  3. MUST export a initialContext of type function, with the Context typing, which is the initial context of the machine
  4. MUST export a Event of type map of function which is event creators (must use typesafe-actions)
  5. MUST export a Type of type map of string which is event types, values in the form npm-module-or-app/EVENT_TYPE
  6. MUST export a State of type map of string which is states, values in the form npm-module-or-app/StateName
  7. MUST be UPPER_SNAKE_CASE for the keys of Event and Type
  8. MUST be UpperCamelCase for the keys of State

Similiar ideas: Duckula for Clojure

Duckula Interface

interface Duckula <...> {
  id: TID
  Type: TType
  Event: TEvent
  State: TState
  machine: TMachine
  initialContext: () => TContext
}

Read the source code at src/duckula/duckula.ts

duckularize Function

import * as Mailbox from 'mailbox'

import * as states from './states.js'
// `events.js` must use `typesafe-actions`
import * as events from './events.js'

interface Context {}

const duckula = Mailbox.duckularize({
  id: 'MyMachineActor',
  initialContext: {} as Context,
  events: [ events, [ 'EVENT1', 'EVENT2' ] ], // or: `events: events` if you do not need filtering
  states: [ states, [ 'State1', 'State2' ] ], // or: `states: states` if you do not need filtering
})
// `duckula` is a `Duckula` now.

Duckula Examples

  1. Ding Dong Machine: <tests/machine-behaviors/ding-dong-machine.ts>
  2. Coffee Maker Machine: <tests/machine-behaviors/coffee-maker-machine.ts>
  3. Baby Machine: <tests/machine-behaviors/baby-machine.ts>

Duckula Badge

Mailbox.Duckula Specification

[![Mailbox.Duckula Specification](https://img.shields.io/badge/Specification-Mailbox.Duckula-blueviolet)](https://github.com/huan/mailbox#duckula-specification)

API References

Read detail (auto-generated) docs at https://paka.dev/npm/mailbox

import * as Mailbox from 'mailbox'

1. Mailbox

1.1 Mailbox.from()

const mailbox = Mailbox.from(machine, options)

Options:

interface Options {
  id?       : string
  capacity? : number
  logger?   : InterpreterOptions['logger'],
  devTools? : InterpreterOptions['devTools'],
}

1.2 mailbox.address()

1.3 mailbox.send()

1.4 mailbox.on()

1.5 mailbox.open()

1.6 mailbox.close()

2. Mailbox.Address

2.1. address.send()

2.2. address.condNotOrigin()

3. Mailbox.actions.*

3.1. Mailbox.actions.idle()

3.2. Mailbox.actions.reply()

3.3. Mailbox.actions.proxyToChild()

4. Mailbox.nil.*

4.1 Mailbox.nil.mailbox

4.2 Mailbox.nil.address

4.3 Mailbox.nil.logger

4.4 Mailbox.nil.machine

5. Mailbox.helper.*

5.1 Mailbox.helper.validate()

5.2 Mailbox.helper.wrap()

Actor Inbound & Outbound Communication

The 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.

However, this are cases that the child machine that needs to communicate with other actors, and receives response messages from other actors.

For 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).

The 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.

sequenceDiagram
  participant Consumer
  participant Mailbox
  participant Machine
  participant Service

  Consumer-&gt;&gt;Mailbox: {type: EVENT1}
  Note right of Consumer: Inbound Message Request 1
  Consumer--&gt;&gt;Mailbox: {type: EVENT2}
  Note right of Consumer: Inbound Message Request 2
  Note right of Mailbox: Processing EVENT1&lt;br&gt;Queue EVENT2
  Mailbox-&gt;&gt;Machine: {type: EVENT1}
  Machine-&gt;&gt;Service: {type: LOG_COMMAND}
  Machine-&gt;&gt;Service: {type: DB_QUERY}
  Note right of Machine: Multiple Outbound Message Requests
  Service-&gt;&gt;Machine: {type: LOG_COMMAND_RESPONSE}
  Service-&gt;&gt;Machine: {type: DB_QUERY_RESPONSE}
  Note right of Machine: Multiple Outbound Message Responses
  Machine-&gt;&gt;Mailbox: {type: EVENT1_RESPONSE}
  Mailbox-&gt;&gt;Consumer: {type: EVENT1_RESPONSE}
  Note right of Consumer: Inbound Message Response 1
  Note right of Mailbox: Dequeue EVENT2&lt;br&gt;Processing EVENT2
  Mailbox--&gt;&gt;Machine: {type: EVENT2}

Caution: be aware of the dead lock if your have two actors call each other in the same machine.

Actor Mailbox Concept

Actors have mailboxes.

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."

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.

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.

The actor model in 10 minutes

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.

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.

Introduction to the Actor Model

Usage

XState Machine

  1. 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)
  2. 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')('reason') to parent (Mailbox) to let the Mailbox know the machine is ready to receive the next message.

Learn more from validate.ts source code

Dead Letter

Whenever 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.

mailbox.onEvent(event => {
  if (event.type === Mailbox.types.DEAD_LETTER) {
    console.error('DEAD_LETTER:', event.payload)
  }
})  

Related reading:

Bounded vs. Unbounded

The 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.

As 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.

On 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.

As 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:

const mailbox = Mailboxe.from(machine, { 
  capacity: 100,
})

The 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.

A 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.

Credit: https://www.baeldung.com/scala/typed-mailboxes#1-bounded-vs-unbounded

Actor Patterns

The Tell Pattern

Tell, Don’t Ask!

It’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.

The 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.

To use the Tell Pattern, an actor must retrieve an actor reference to the actor it wants to send the message to.

See also: Akka Interaction Patterns: The Tell Pattern

The Ask Pattern

Request-Response

The 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.

The 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.

The Event v.s. Message

  • "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)
  • "The difference lies in that with MessageQueues it's typical that the sender requires a response. With an EventQueue this is not necessary." (link)
  • "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)

Event v.s. Message (tell/ask)

Image source: Wechaty CQRS

Known Issues

Never send batch events

Never use interpreter.send([...eventList]) to send multiple events. It will cause the mailbox to behavior not right (only the first event will be delivered).

Use eventList.forEach(e => interpreter.send(e)) to send event list.

The reason is that internally Mailbox have three parallel states and they will run into race condition in batch-event mode.

See: XState Docs - Batched Events

Resources

Quota

Redux is predictable states container, XState is predictable transitions container. — A Youtuber comment

Mailbox is predictable states & transitions container for actors. — Huan, creator of Wechaty, Jan. 2022

History

main v0.7

  1. Add Duckula Interface for modulize Mailbox Actor. (Issue #1)
  2. Fix the race condition bug by simplifing the queue state management to be atomic. (Issue #5)

v0.6 (Apr 10, 2022)

Refactoring APIs

  1. Supports Mailbox.actions.proxy(name)(target) to proxy all events to a target (string id, Address, or Mailbox)
  2. Supports Mailbox.actions.send(target)(event, options) where the target can be a string id, Address, or Mailbox for convenience.

v0.4 (Apr 1, 2022)

Publish mailbox NPM module with DevOps.

v0.2 (Dec 31, 2021)

  1. Implement the Actor State/Context Persisting Mechanism.
  2. Add Dead Letter Queue (DLQ) capacity options for dealing with the Back Pressure.

v0.1 (Dec 24, 2021)

Improving the Actor Mailbox model.

Related links:

v0.0.1 (Dec 18, 2021)

Initial version.

Related issue discussions:

Special Thanks

Great thanks to @alxhotel who owned the great NPM module name mailbox and kindly transfered it to me for this new project, thank you very much Alex!

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 :)

Cheers,
Alex (Dec 20 Mon 11:47 PM)

Author

Huan LI is a serial entrepreneur, active angel investor with strong technology background. Huan is a widely recognized technical leader on conversational AI and open source cloud architectures. He co-authored guide books "Chatbot 0 to 1" and "Concise Handbook of TensorFlow 2" and has been recognized both by Microsoft and Google as MVP/GDE. Huan is a Chatbot Architect and speaks regularly at technical conferences around the world. Find out more about his work at https://github.com/huan

Copyright & License

  • Docs released under Creative Commons
  • Code released under the Apache-2.0 License
  • Code & Docs © 2021 Huan LI <[email protected]>
Comments
  • Workaround Paka.dev BUG: unpublish v0.0.x versions on NPM

    Workaround Paka.dev BUG: unpublish v0.0.x versions on NPM

    image

    We need to keep mailbox no DevOps publish and no npm install for a week so that the NPM download number can reduce to below 300 / week.

    image

    GitHub (GitHub Support)
    
    Apr 6, 2022, 6:30 PM UTC
    
    Hello Huan,
    
    Thanks for reaching out to npm Support.
    
    We see there are over 300 weekly download counts for the mailbox package. As there are over 300 weekly download counts, we are unable to unpublish the below versions at this time. We will need the weekly download counts to be below 300 before we can unpublish the packages.
    
    0.0.1
    0.0.2
    0.0.3
    0.0.4
    I will need to wait until next week to see if we can unpublish the above versions of mailbox and will provide you an update on them.
    
    Please let us know if you have questions. We'll be here to help.
    
    Thanks so much.
    
    Regards,
    YL
    GitHub Support - Supporting the npm registry
    
    	
    Huan (李卓桓)
    
    Apr 6, 2022, 1:20 AM UTC
    
    Hi there,
    
    According to the NPM Unpublish Policy: https://docs.npmjs.com/policies/unpublish , I should be able to unpublish a version of my NPM if:
    
    1. no other packages in the npm Public Registry depend on
    2. had less than 300 downloads over the last week
    3. has a single owner/maintainer
    
    I want to unpublish my package [email protected] which is 11 years old, I believe it meets the above 3 conditions, but I still can not unpublish it:
    
        $ npm unpublish [email protected]
        npm ERR! code E405
        npm ERR! 405 Method Not Allowed - PUT https://registry.npmjs.org/mailbox/-rev/49-54b6290013f4a03f4ca15de41d0178ed - Yo
        npm ERR! Failed criteria:
        npm ERR! has too many downloads
    
    I need to remove all the [email protected] versions due to the incompatible metadata with https://paka.dev/npm/mailbox (you can see those 4 very old version metadata cause an error page).
    
    Could you please help me to unpublish that 4 v0.0.x versions?
    
    Thank you very much.
    
    Huan
    

    	
    GitHub (GitHub Support)
    
    Apr 7, 2022, 11:42 AM UTC
    
    Hello Huan,
    
    Thank you for following up.
    
    We see you have published multiple versions in a short period of time which will attribute to higher weekly download counts. Higher download counts are due to:
    
    automated build servers
    mirrors downloading your package versions
    robots that download every package for analysis
    We are unable to unpublish versions 0.0.1, 0.0.2, 0.0.3, and 0.0.4 of the mailbox package until next week when the weekly download counts are below 300.
    
    Please let us know if you have questions. We'll be here to help.
    
    Thanks so much.
    
    Regards,
    YL
    GitHub Support - Supporting the npm registry
    
    	
    Huan (李卓桓)
    
    Apr 7, 2022, 7:27 AM UTC
    
    Hello YL,
    
    I think all the downloads is not related to any v0.0.x version.
    
    All downloads are begin from the last week when I have published v0.3.3 6 days ago, they should all relate to the v0.3+ versions.
    
    You can see the weekly downloads chart from the https://www.npmjs.com/package/mailbox :
    
    image.png
    So I think we can safely remove the v0.0.x version, could you please help me to confirm that whether that's satisfy the policy?
    
    Thank you very much.
    
    Best,
    Huan (李卓桓)
    
    enhancement 
    opened by huan 2
  • Define a schema standard for Mailbox Actor: Another Duck - Duckula

    Define a schema standard for Mailbox Actor: Another Duck - Duckula

    Like the

    Ducksify Extension

    The Mailbox Actor also needs a standard for a best practice suggestion.

    Draft

    const Type = {
      GERROR: 'GERROR',
    } as const
    
    type Type = typeof Type[keyof typeof Type]
    
    const Event = {
      GERROR: createAction(types.GERROR)(),
    } as const
    
    type Event = {
      [key in keyof typeof Event]: ReturnType<typeof Event[key]>
    }
    
    const State = {
      Idle: 'Idle',
    } as const
    
    type State = typeof State[keyof typeof State]
    
    interface Context {}
    
    const initialContext = (): Context => {
      const context: Context = {}
      return JSON.parse(JSON.stringify(context))
    }
    
    const NAME = 'FileBoxToTextMachine'
    
    const machine = createMachine<Context, ReturnType<Event[keyof Event]>> ({
      id: NAME,
      initialState: State.Idle,
      on: {
        [Type.GERROR]: {
          entry: actions.send(Event.GERROR(new Error('demo'))),
        },
        '*': State.Idle,
      },
    })
    
    export {
      NAME,
      Type,
      Event,
      State,
      machine,
      type Context,
      initialContext,
    }
    

    Name it

    We need a good name for this standard, maybe we can follow the Duck proposal and use XXX Duck as the name.

    For example, Actor Duck, or Mailbox Duck?

    Option 1: Duckula (see: Count Duckula)

    image

    source: Papercraft Count Duckula

    enhancement 
    opened by huan 2
  • Race condition: two NEW_MESSAGEs concurrency lost one

    Race condition: two NEW_MESSAGEs concurrency lost one

    Actor: (bot5-assistant/Idle) + [bot5-assistant/MESSAGE] = (bot5-assistant/Parsing)
    -------------------------
    WechatyActor<Mailbox> contexts.queueAcceptingMessageWithCapacity(Infinity) queue [cqrs-wechaty/SEND_MESSAGE_COMMAND]@x:5 for child(idle)
    WechatyActor<Mailbox> contexts.queueAcceptingMessageWithCapacity(Infinity) queue [wechaty-actor/BATCH]@x:5 for child(idle)
    WechatyActor<Mailbox> states.child.idle.on.NEW_MESSAGE (cqrs-wechaty/SEND_MESSAGE_COMMAND)
    WechatyActor<Mailbox> states.child.idle.on.NEW_MESSAGE (wechaty-actor/BATCH)
    WechatyActor<Mailbox> states.queue.checking.entry <- [DISPATCH(mailbox/NEW_MESSAGE)]
    WechatyActor<Mailbox> states.queue.checking.always -> dequeuing (queue size 2 > 0)
    WechatyActor<Mailbox> states.queue.dequeuing.entry [cqrs-wechaty/SEND_MESSAGE_COMMAND]@x:5
    WechatyActor<Mailbox> states.queue.listening.entry
    WechatyActor<Mailbox> states.queue.checking.entry <- [DISPATCH(mailbox/NEW_MESSAGE)]
    WechatyActor<Mailbox> states.queue.checking.always -> dequeuing (queue size 1 > 0)
    WechatyActor<Mailbox> states.queue.dequeuing.entry [wechaty-actor/BATCH]@x:5
    WechatyActor<Mailbox> states.queue.listening.entry
    WechatyActor<Mailbox> states.child.idle.on.DEQUEUE [cqrs-wechaty/SEND_MESSAGE_COMMAND]@x:5
    WechatyActor<Mailbox> states.child.busy.entry DEQUEUE [cqrs-wechaty/SEND_MESSAGE_COMMAND]
    WechatyActor State.Preparing.entry
    WechatyActor State.Preparing.entry found Command/Query [cqrs-wechaty/SEND_MESSAGE_COMMAND]
    ______________________________
    Wechaty: (wechaty-actor/idle) + [cqrs-wechaty/SEND_MESSAGE_COMMAND] = (wechaty-actor/preparing)
    -------------------------
    WechatyActor State.Executing.entry EXECUTE [cqrs-wechaty/SEND_MESSAGE_COMMAND]
    ______________________________
    Wechaty: (wechaty-actor/preparing) + [wechaty-actor/EXECUTE] = (wechaty-actor/executing)
    -------------------------
    ______________________________
    Wechaty: (wechaty-actor/executing) + [done.invoke.WechatyActor.wechaty-ac
    

    We can see there's two NEW_MESSAGEs have been accepted by the Mailbox, which should be only accepted one:

    WechatyActor<Mailbox> states.child.idle.on.NEW_MESSAGE (cqrs-wechaty/SEND_MESSAGE_COMMAND)
    WechatyActor<Mailbox> states.child.idle.on.NEW_MESSAGE (wechaty-actor/BATCH)
    
    bug 
    opened by huan 1
  • Fix nested id conflict

    Fix nested id conflict

    The previous version of Mailbox was using a hard-coded ID for the wrapped machine, which will cause a conflict when we have nested Mailbox wrapped machines.

    This PR generate distinct ID based on the wrapped machine ID.

    bug 
    opened by huan 0
  • Error: Unable to send event to child 'xxx' from service 'yyy'.

    Error: Unable to send event to child 'xxx' from service 'yyy'.

    Be careful about the transitions: an external transition will cause the actions.send(e, { to: invokedMachineId }) to throw exception if the invokedMachine is inside the external transition state.

    Learn more from:

    • https://github.com/statelyai/xstate/issues/3253
    documentation 
    opened by huan 0
  • XState `state.exit.actions` & micro transitions will be executed in the next `state`

    XState `state.exit.actions` & micro transitions will be executed in the next `state`

    If the action is sensitive to the current state, then a workaround will be needed.

    For example:

    createMachine({
      states: {
        idle: {},
        busy: {
          always: {
            10: 'idle',
          },
          exit: {
            actions: [
              Mailbox.actions.send(...), // <- this will be executed with state `idle`
            ],
          },
        },
      },
    })
    

    In the above code, the Mailbox.actions.send(...) will be executed within the state idle, instead of in the state busy.

    Related discussion:

    • https://github.com/statelyai/xstate/discussions/3231
    documentation 
    opened by huan 1
Owner
Huan (李卓桓)
Angel Investor, Serial Entrepreneur, Microsoft AI MVP, Google ML GDE, Tencent TVP of Chatbot, Conversational AI Coder with 💖
Huan (李卓桓)
Based on Google Chrome recorder, implement UI interface capture and notify the result to the target mailbox

chrome-recoder-crawler README-CN Modify the .js file exported by Google Chrome recorder. By default, the innerText property of the node operated in th

wudu 4 Oct 18, 2022
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
Load twemoji with a predictable url.

twemoji-image-functions Cloud Functions which hosts twemoji with a predictable url. Why? You can get any twemoji urls without parsing DOM or text usin

catnose 8 Mar 26, 2022
Minimalistic, opinionated, and predictable release automation tool.

Release Minimalistic, opinionated, and predictable release automation tool. General idea Think Prettier but for automated releases: minimalistic, opin

Open Source: JavaScript 173 Dec 18, 2022
Awesome TV is the First and Original streaming entertainment network for Global Africa from United States of America (USA).

LEADBOARD APP Awesome TV is the First and Original streaming entertainment network for Global Africa from United States of America (USA). Built With H

Aime Malaika 9 Apr 4, 2022
Node.js module for verifying Plumo proofs and reading states based on it

plumo-verifier Node.js module for verifying Plumo proofs and reading states based on it. Plumo is a SNARK-based light client verifier for the Celo blo

Celo 3 Dec 15, 2022
Search, fetch, and get data regarding United States presidents.

us-presidents Search, fetch, and get data regarding United States presidents. GitHub Documention Discord Examples Installation NPM npm install us-pres

Spen 3 May 7, 2022
Promise-based utility to control modal states in React

Promise-based utility to control modal states in React Zero-dependency library that easily integrates with your existing UI components and allows you

Thiago Zanivan 8 Dec 5, 2022
Minimalistic portfolio/photography site with masonry grid, page transitions and big images.

Gatsby Starter Portfolio: Emilia Minimalistic portfolio/photography site with masonry grid, page transitions and big images. Themeable with Theme UI.

Cryptob3auty 1 May 20, 2022
A container-friendly alternative to os.cpus().length. Both cgroups v1 and cgroups v2 are supported.

node-cpu-count A container-friendly alternative to os.cpus().length. Both cgroups v1 and cgroups v2 are supported. Installation $ npm install node-cpu

Jiahao Lu 2 Jan 17, 2022
The invoker based on event model provides an elegant way to call your methods in another container via promisify functions

The invoker based on event model provides an elegant way to call your methods in another container via promisify functions. (like child-processes, iframe, web worker etc).

尹挚 7 Dec 29, 2022
Container Image Signing & Verifying on Ethereum [Testnet]

cosigneth An experimental decentralized application for storing and verifying container image signatures as an NFT on Ethereum cosigneth, is a decentr

Furkan Türkal 17 Jul 4, 2022
ContainerMenu is an API for BDSX that allows you to create fake interactive container menus !

ContainerMenu - A BDSX API ContainerMenu is an API for BDSX that allows you to create fake interactive container menus ! Features Multiple containers

Se7en 8 Oct 28, 2022
Jugglr is a tool for managing test data and running tests with a dedicated database running in a Docker container.

Jugglr Jugglr is a tool for managing test data and running tests with a lightweight, dedicated database. Jugglr enables developers, testers, and CI/CD

OSLabs Beta 74 Aug 20, 2022
A docker container with a wide variety of tools for debugging and setting up micro-services

Frame One Software Placeholder There are numerous times during the dev ops deployments, that a placeholder container is needed. In the past, Frame One

Frame One Software 8 May 29, 2022
Pin any element within a container

jQuery.pin Ever wanted to pin something to the side of a text? Ever needed a subtle sticky element to quietly hang around as you scroll down? Jquery.P

Webpop 1.3k Nov 30, 2022
Public repository of assets used during editions of AWS LATAM Container Roadshow event.

LATAM Containers Roadshow This is the official repository of assets related to LATAM Containers Roadshow, an all-day customer-facing event to highligh

AWS Samples 12 Dec 6, 2022