xstate-codegen icon indicating copy to clipboard operation
xstate-codegen copied to clipboard

service invocation question

Open mwarger opened this issue 5 years ago • 11 comments

Let's say I have a service definition like this:

initializing: {
            invoke: {
              src: 'getToken',
              onDone: {
                actions: 'updateToken',
                target: 'ready',
              },
              onError: {
                target: 'failure',
              },
            },
          },

I want to update the token in context when I am done. Currently, this requires me to have an event declared like this:

{
      type: 'done.invoke.getToken';
      data: string;
    }

(if I change the ID, the event would change, afaik)

This is used in the event of the action:

actions: {
      updateToken: assign((context, event) => { // this event is typed correctly, but only if I create the event explicitly in the Event type for my machine
        console.log('event', event.data);
        return {
          authorization: event.data,
        };
      }),
    },

First, I'm curious if this is the correct usage? Second, I'm curious if this could be inferred somehow by properly crafting the type string for invoked services (as they follow a convention)? This could potentially allow me to not need to create the event explicitly.

mwarger avatar Sep 16 '20 16:09 mwarger

@mwarger You're absolutely right to follow this pattern. It will give you 100% type safety, but I'm a bit reticent to call it a good pattern. It requires that you know about XState's internals, which ain't right.

You can also do the same for error.platform.service, and various others.

I have had similar doubts about this, but haven't thought of an API which would make sense.

We'll likely need some kind of generic passed into the Machine. Perhaps:


type Options = {
  services: {
    getToken: {
      data: string;
    };
  };
};

const machine = Machine<Context, Event, Options, 'id'>({});

This is tricky, though - we are committed to supporting Typestates (in #27) which already makes use of this extra generic.

The floor is open - I'm open to ideas here.

mattpocock avatar Sep 16 '20 16:09 mattpocock

I don't know what I'm talking about, but this approach might be useful here if we can leverage the convention and extract keys from the config object: https://twitter.com/danvdk/status/1301707026507198464

mwarger avatar Sep 16 '20 17:09 mwarger

Yesss, this should definitely be explored when TS 4.1 is out.

mattpocock avatar Sep 17 '20 12:09 mattpocock

I was thinking more about this as I was falling asleep. It's that followup by Jamie that I think might be close.

ts playground here

Could we create one big type that models a state machine? Then extract all the types from the parsed one? I'm not sure if it works that way... Would love to talk this through with people that are more familiar with it.

(and then I go back to the twitter thread and scroll down and see that you've already commented in there with David - I'm glad it's on your radar!)

mwarger avatar Sep 17 '20 13:09 mwarger

This would be particularly interesting because we could re-parse the done.invoke.serviceName into something more readable. For instance, serviceName.onDone or serviceName.onError could be passed to your Event object. This would be a fairly small change, though would mean we'd need to require TS 4.1+ for the CLI to be used.

mattpocock avatar Sep 17 '20 13:09 mattpocock

@mwarger We could actually do the above without the TS string manipulation. When we encounter something that triggers from the done.invoke.serviceName event, we could do the string manipulation ourselves to allow for either that, or serviceName.onDone to be passed in the Events object.

mattpocock avatar Sep 19 '20 14:09 mattpocock

Just bumped into this as well. I wonder, technically when the service is declared inline or within machine options, it should be possible to infer return value and have the onDone typed correctly, shouldn't it? The problem is of course for services configured externally and that probably needs something of what you are suggesting above.

I also tried to specify ev: DoneInvokeEvent<TData> explicitly in action, but it's not compatible and complains about it for whatever reason. Otherwise, it would be a semi-nice solution.

danielkcz avatar Oct 07 '20 18:10 danielkcz

@FredyC What do you think of the above fix, to declare events like this to get onDone typed correctly?

{
  type: 'done.invoke.getToken';
  data: string;
}

mattpocock avatar Oct 07 '20 18:10 mattpocock

Well, for some reason that didn't work for me, I am getting any, but my data is an object, not a scalar value if that matters. I think it's ugly anyway :) It would be much better if at least partial inference would be available for internal services as I suggested. And figure out how can I use DoneInvokeEvent explicitly without clashing with something I don't understand.

danielkcz avatar Oct 07 '20 18:10 danielkcz

@FredyC Could you post a repro? It shouldn't matter whether it's scalar or not.

It is certainly not pretty. Inference of service payloads inline is a long way off, so it might have to do for now.

mattpocock avatar Oct 07 '20 19:10 mattpocock

I'm going to keep this open but mark it as documentation needed - we should document this approach so it's more discoverable.

mattpocock avatar Dec 19 '20 20:12 mattpocock