statelyai / xstate

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

Bug: Actor<T> cannot be assigned to ActorRef<T>. The options also seem wrongly typed. #4997

Closed phcoliveira closed 3 months ago

phcoliveira commented 4 months ago

XState version

XState version 5

Description

I am using the latest xstate package: 5.15.0.

There are two issues.

This is the main issue. From the docs, I expected to assign the value returned from createActor<T> to a variable declared as ActorRefFrom<T>. But it is not possible.

This is the second, less important issue. I used to pass an inspect function to createActor(logic, {inspect}). Now I can't do it and I can't make sense of the options' type.

Expected result

I expected to assign createActor<T> to ActorRefFrom<T>.

I also expected to pass an inspect function.

Actual result

I can't do them. There are typing issues.

Reproduction

https://codesandbox.io/p/sandbox/actor-t-can-not-be-assigned-to-actorref-t-nkwptq?file=%2Fsrc%2Findex.ts%3A22%2C50

Additional context

https://discord.com/channels/795785288994652170/1265313261805502585/1266061251658649762

https://discord.com/channels/795785288994652170/1265729221305696307/1265729221305696307

phcoliveira commented 4 months ago

@Andarist, in the code sandbox above, It seems possible to pass the inspect function, which is the second, and not the most important, problem.

I am trying to figure out what I can't do it on my project.

In any case, the first problem is reproducible in the code sandbox.

phcoliveira commented 4 months ago

image

davidkpiano commented 4 months ago

This is likely because of the ConditionalRequired<…> complex type that we have, where we require the input if the logic requires the input, and there's no way of being able to "predict" that. The input and logic being in two separate places makes this harder.

cc. @Andarist


Future note: if createMachine(…) returned a function that must always be called to create actor logic, we can avoid this problem pretty nicely:

const machineFn = createMachine(...);
const actor = createActor(machineFn(someInput));

// @ts-expect-error if input is required
const actor = createActor(machineFn());

// same with other types of logic
const promiseFn = fromPromise(...);

const actor = createActor(promiseFn(someInput));
davidkpiano commented 3 months ago

As a workaround, I recommend using ActorRefFromLogic, which is introduced in #5011 (not released yet) but is just this:

export type ActorRefFromLogic<T extends AnyActorLogic> = ActorRef<
  SnapshotFrom<T>,
  EventFromLogic<T>,
  EmittedFrom<T>
>;

Then, this will work as expected:

  const logic = createMachine({});

  class ActorThing<T extends AnyActorLogic> {
    actorRef: ActorRefFromLogic<T>;
    constructor(actorLogic: T) {
      const actor = createActor(actorLogic);

      actor satisfies ActorRefFromLogic<typeof actorLogic>;
      this.actorRef = actor;
    }
  }

  new ActorThing(logic);
phcoliveira commented 3 months ago

Thank you, @davidkpiano. I will check it out.