Giter Site home page Giter Site logo

huan / mailbox Goto Github PK

View Code? Open in Web Editor NEW
48.0 2.0 6.0 796 KB

Mailbox is the predictable states & transitions container for actors.

Home Page: https://paka.dev/npm/mailbox/

License: Apache License 2.0

JavaScript 48.71% Shell 0.84% TypeScript 50.45%
actor xstate fsm state-machine event-driven

mailbox's Introduction

Mailbox (turns XState Machine into an Actor that can deal with concurrency requests)

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
  • Address has been abstracted by a Address class for actors
  • Unit tests covered

Voice of Developers

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)
@DavidKPiano, 🚀 Creator of XState

... statecharts process events immediately while actors (by means of message queues) give you granular control of when to process the next event. (tweet)
@chrisshank23, core member of StateML

TODO

  • Support distribution actor by adding remote actor abstraction to the Address class

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'),
      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 = Mailbox.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') 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')` 
       */
      entry: Mailbox.actions.idle('child-machine-name'),
      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 Issue #1

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->>Mailbox: {type: EVENT1}
  Note right of Consumer: Inbound Message Request 1
  Consumer-->>Mailbox: {type: EVENT2}
  Note right of Consumer: Inbound Message Request 2
  Note right of Mailbox: Processing EVENT1<br>Queue EVENT2
  Mailbox->>Machine: {type: EVENT1}
  Machine->>Service: {type: LOG_COMMAND}
  Machine->>Service: {type: DB_QUERY}
  Note right of Machine: Multiple Outbound Message Requests
  Service->>Machine: {type: LOG_COMMAND_RESPONSE}
  Service->>Machine: {type: DB_QUERY_RESPONSE}
  Note right of Machine: Multiple Outbound Message Responses
  Machine->>Mailbox: {type: EVENT1_RESPONSE}
  Mailbox->>Consumer: {type: EVENT1_RESPONSE}
  Note right of Consumer: Inbound Message Response 1
  Note right of Mailbox: Dequeue EVENT2<br>Processing EVENT2
  Mailbox-->>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') 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.10 (May 3rd, 2022)

  1. Mailbox Interface is an Observable now.
  2. Fix the race condition bug by simplifing the queue state management to be atomic. (Issue #5)
  3. Fix wrapped machine ID conflic bug when there have nested Mailbox wrapped machines (PR #8)
  4. Add Duckula Interface for modulize Mailbox Actor. (Issue #1)
  5. Renaming CHILD to ACTOR:
    1. CHILD_REPLY -> ACTOR_REPLY
    2. CHILD_IDLE -> ACTOR_IDLE
  6. Duck types/states clean

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]>

mailbox's People

Contributors

huan avatar realetive avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

mailbox's Issues

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:

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)

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 (李卓桓)

Dead Letter error, mailbox.send('TASK') doesn't work properly

# testing the mailbox-ed(actor) machine ... (an actor will be able to response two events one by one)
> sending event #0
{ type: 'TASK' }
{
  type: 'mailbox/DEAD_LETTER',
  payload: {
    message: { type: 'TASK' },
    data: 'message has no origin (un-repliable)'
  }
}
> sending event #1
{ type: 'TASK' }
{
  type: 'mailbox/DEAD_LETTER',
  payload: {
    message: { type: 'TASK' },
    data: 'message has no origin (un-repliable)'
  }
}
# testing mailbox-ed machine ... done

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.