statelyai / xstate

Actor-based state management & orchestration for complex app logic.
https://stately.ai/docs
MIT License
26.87k stars 1.23k forks source link

[Feature] Add 'to' (target) to send actions #105

Closed mpolichette closed 5 years ago

mpolichette commented 6 years ago

Bug or feature request?

Feature Request

Description:

SCXML specifies an attribute target on send events.

The unique identifier of the message target that the platform should send the event to. See 6.2.4 The Target of Send for details.

I find this especially useful for reusable state charts or parallel machines (or even specifying specific targets for reusable send actions).

Potential implementation:

This is part of the existing SCXML specification Changes are very minimal:

  1. add an additional argument to SendAction and SendActionOptions and
  2. Make sure to pass it through in the send action definition.
    import { actions } from 'xstate';
    // ...
    actions.send('someEvent', {  target: 'someTarget' })
    //
    // {
    //   type: 'xstate.send',
    //   event: { type: 'someEvent' },
    //   target: 'someTarget'
    // }
davidkpiano commented 6 years ago

I think this is a good idea and xstate can handle whether the event should be handled or not, based on the target.

One thing I'd change is that the target should always be an ID, since it's not relative to anything, so { target: '#someTarget' }.

Also for SCXML's targetexpr we can support:

target: (fullState, event) => {
  // return an ID
}
mpolichette commented 6 years ago

Hmm... I'm not sure I follow.

My understanding is that an action.target is not necessarily a StateNode... even in the SCXML docs it seems like targets could be anything

In most cases, the format of the target depends on the type of the target ...

In my case, I want to use action targets to identify an external service.

Here is a bit of a partially baked example which fits my intentions:

const createConnectionChart = name => {
  return {
    key: name,
    initial: 'init',
    states: {
      init: {
        onEntry: [actions.send('connect', { target: name })],
      },
      // ... other states
    },
  }
}

const connectionsChart = {
  key: 'connections',
  parallel: true,
  states: {
    service1: createConnectionChart('service1'),
    service2: createConnectionChart('service2'),
  },
}

// Not positive on the following api, just some thoughts:
const machine = new Interpreter(connectionsChart)
machine.on('connect', ({ target }) => { /*  ...connect target...  */ })

I would expect my interpreter to use target to make sure the appropriate service got the action called against it.

davidkpiano commented 6 years ago

Ah okay, that makes sense. So it's just metadata then?

EDIT: In that case, you can add any metadata to your event:

actions.send({
  type: 'myEvent',
  target: 'somewhere',
  foo: 'bar'
});

Which will appear in the event prop of the generated event.

mpolichette commented 6 years ago

Hah, yes, for my need I think that would work fine... are those semantics right?

Just checking my understanding... would something like what I described be a event.target or an action.target in state chart land?

In my case, the metadata approach would work fine, I'd feel free to close the issue w/ that resolution.

davidkpiano commented 6 years ago

The event can have any metadata, so that's up to you. Same with actions. Both .targets are what you define it as.

But in SCXML, an event target would be an event intended for a particular statechart/~state~, to my understanding.

davidkpiano commented 5 years ago

This is now in master and will be released in 4.0. It is to be used in conjunction with the invoke: ... syntax.

xlozinguez commented 5 years ago

@davidkpiano it seems the typing doesn't allow for this second options argument in the send method... I'll look into it and eventually publish a PR once I confirm Also it seems to have been renamed to to: but the doc isn't reflecting this... https://github.com/davidkpiano/xstate/blob/master/src/actions.ts#L180 Can you confirm which one is right?

davidkpiano commented 5 years ago

This only refers to the send(...) action creator (pure function), not the interpreter.send(...) dispatcher (impure method).

xlozinguez commented 5 years ago

oh that makes more sense. Thanks for clarifying! It was quite late when I was attempting this 😁