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: `AssignAction` type error when using `exactOptionalPropertyTypes: true` #4613

Open SandroMaglione opened 11 months ago

SandroMaglione commented 11 months ago

XState version

XState version 5

Description

An assign action reports a type error when using exactOptionalPropertyTypes: true in tsconfig.json.

Expected result

It should be possible to use exactOptionalPropertyTypes: true with xstate.

Actual result

Reproduction

https://stackblitz.com/edit/vitejs-vite-lbw4wa?file=src%2Fmain.ts

Additional context

exactOptionalPropertyTypes allows for more type-safety. It is also recommended when using @effect/schema.

SandroMaglione commented 7 months ago

@Andarist is there any workaround for this? Do you know if it's possible to disable exactOptionalPropertyTypes for a specific file?

davidkpiano commented 7 months ago

@Andarist is there any workaround for this? Do you know if it's possible to disable exactOptionalPropertyTypes for a specific file?

I updated XState and TypeScript in this codebase and it seems to work fine: https://stackblitz.com/edit/vitejs-vite-lbw4wa?file=package.json,src%2Fmain.ts

Can you double-check that everything's working on your end?

SandroMaglione commented 7 months ago

@davidkpiano it seems to be working now when only actions are defined.

When instead I add also actors (as I need in my codebase) the issue comes back: https://stackblitz.com/edit/vitejs-vite-aesqqz?file=package.json,src%2Fmain.ts

Andarist commented 7 months ago

@Andarist is there any workaround for this?

I wasn't yet able to figure out a workaround. It's definitely possible to fix this on our side but it requires prioritization and time.

Do you know if it's possible to disable exactOptionalPropertyTypes for a specific file?

No, it's a global setting.

bhvngt commented 4 months ago

Facing similar issue. Will really appreciate if any workaround is available. Thanks for your time and effort

davidkpiano commented 4 months ago

Facing similar issue. Will really appreciate if any workaround is available. Thanks for your time and effort

Can you share the code that's causing the issue for you?

bhvngt commented 4 months ago

sure..

import { assign, createActor, fromPromise, setup } from "xstate";

const machine = setup({
  types: { context: {} as { value: number } },
  actors: {
    child: fromPromise(() => Promise.resolve(10))
  }
}).createMachine({
  id: "test",
  initial: "inactive",
  context: () => ({ value: 1 }),
  invoke: {
    src: "child",
    onDone: {
      actions: assign(({ event }) => ({ value: event.output }))
    }
  },
  states: {
    active: { on: { toggle: "inactive" } },
    inactive: { on: { toggle: "active" } }
  }
});

const actor = createActor(machine);
actor.subscribe(console.log);
actor.start();

Getting following error

error TS2322: Type '{ actions: ActionFunction<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, ProvidedActor, never, never, never, never>; }' is not assignable to type 'SingleOrArray<TransitionConfigOrTarget<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, { src: "child"; logic: PromiseActorLogic<number, NonReducibleUnknown, EventObject>; id: string | undefined; }, ... 4 more ..., MetaObject>>'.
  Types of property 'actions' are incompatible.
    Type 'ActionFunction<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, ProvidedActor, never, never, never, never>' is not assignable to type 'Actions<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, { src: "child"; logic: PromiseActorLogic<number, NonReducibleUnknown, EventObject>; id: string | undefined; }, never, never, never, EventObject> | undefined'.
      Type 'ActionFunction<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, ProvidedActor, never, never, never, never>' is not assignable to type 'ActionFunction<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, { src: "child"; logic: PromiseActorLogic<number, NonReducibleUnknown, EventObject>; id: string | undefined; }, never, never, never, EventObject>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
        Types of property '_out_TActor' are incompatible.
          Type 'ProvidedActor' is not assignable to type '{ src: "child"; logic: PromiseActorLogic<number, NonReducibleUnknown, EventObject>; id: string | undefined; }'.

43         onDone: {
           ~~~~~~
michael-wisely-gravwell commented 4 months ago

I'm also seeing this sort of error when exactOptionalPropertyTypes is true. I'm using...

Example:

import { timer } from 'rxjs';
import { setup, fromObservable, createActor, assign } from 'xstate';

const machine = setup({
  types: { context: {} as { value: number }, events: {} as { type: 'start' } },
  actors: {
    child: fromObservable(() => timer(5_000)),
  },
}).createMachine({
  id: 'test',
  initial: 'notStarted',
  context: { value: 1 },
  states: {
    notStarted: {
      on: {
        // Type 'ProvidedActor' is not assignable to type '{ src: "child";...
        start: {
          target: 'running',
          actions: assign({
            value: ({ context }) => context.value + 1,
          }),
        },
      },
    },
    running: {
      invoke: {
        src: 'child',
        onDone: {
          target: 'done',
        },
      },
    },
    done: { type: 'final' },
  },
});

const actor = createActor(machine);
actor.subscribe((snapshot) => console.log(snapshot.value, snapshot));
actor.start();

document.addEventListener('click', () => actor.send({ type: 'start' }));

Produces the following error on the indicated line in the example (start:)

Type '{ target: string; actions: ActionFunction<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, ProvidedActor, never, never, never, never>; }' is not assignable to type 'TransitionConfigOrTarget<{ value: number; }, { type: "start"; }, { type: "start"; }, { src: "child"; logic: ObservableActorLogic<0, NonReducibleUnknown, EventObject>; id: string | undefined; }, ... 4 more ..., MetaObject>'.
  Types of property 'actions' are incompatible.
    Type 'ActionFunction<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, ProvidedActor, never, never, never, never>' is not assignable to type 'Actions<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, { src: "child"; logic: ObservableActorLogic<0, NonReducibleUnknown, EventObject>; id: string | undefined; }, never, never, never, EventObject> | undefined'.
      Type 'ActionFunction<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, ProvidedActor, never, never, never, never>' is not assignable to type 'ActionFunction<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, { src: "child"; logic: ObservableActorLogic<0, NonReducibleUnknown, EventObject>; id: string | undefined; }, never, never, never, EventObject>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
        Types of property '_out_TActor' are incompatible.
          Type 'ProvidedActor' is not assignable to type '{ src: "child"; logic: ObservableActorLogic<0, NonReducibleUnknown, EventObject>; id: string | undefined; }'.ts(2322)
khusmann commented 3 months ago

I'm also seeing this type error with exactOptionalPropertyTypes: true with

As @SandroMaglione noted, the issue only manifests when at least one actors is defined. But the weird part is that it is not localized to actions with actors... As soon as an actor is defined, even assign actions that are not connected to actors also get type errors.

Here's a variation of @bhvngt's example to illustrate:

import { assign, createActor, fromPromise, setup } from 'xstate';

const machine = setup({
  types: { context: {} as { value: number } },
  /////////// Comment this chunk out and the type error on "toggle" disappears
  actors: {
    child: fromPromise(() => Promise.resolve(10)),
  },
  ///////////////// end chunk
}).createMachine({
  id: 'test',
  initial: 'inactive',
  context: () => ({ value: 1 }),
  states: {
    active: {
      on: {
        // Type error on `toggle` here
        toggle: { target: 'inactive', actions: assign({ value: () => 0 }) },
      },
    },
    inactive: { on: { toggle: 'active' } },
  },
});

const actor = createActor(machine);

Link to stackblitz: https://stackblitz.com/edit/node-lsnym4?file=index.ts

spaceemotion commented 3 months ago

Seeing the same for this example:

https://www.typescriptlang.org/play/?declaration=false&target=99&jsx=1&exactOptionalPropertyTypes=true&ts=5.5.4#code/JYWwDg9gTgLgBAbzgZwKYwK5gDRwIbLLADmAdrgGZQQgAK1IwaupE8AvnFTXAEQAeyGHhipeAbjgB6KXBgBPMKmQAuOAFYAdAE5NARgBQBgMYRSQuABsIeACYBZZcjzFlcALz5k80sbgAKAEoPAD5EAzg4KHQMKFI4AG0IyLgAcmtokFTsZMjU4DBkDCycyIBdcQN2IzRMMH8EZIUlVUROAnCUuFNSUX4YNUauyJAnF2U1IShgUmIEsty4dhzF1AA3VF7VRciAH0Q5RVQ1VIBlABUAQQAlc9Slnbh9pGbjtOuAUVOPu4fhp4OrxOABkAPKXAAi2Tgo0I40mMGmsz+SxWkTwxhgwDMrSGKWitTUBCIZH8QVCnWG0UwcUp-1hzlcrXmpWG7EqXXYgTRKWsdjUDW6Zj6HFwYDwUDwIFxMLGTIRSLmZSWwXcYTxXR6Is0DPGyA8cHFkulOrlyk0yEswGMqCCHJSy2SjuSxAwEtsuMWAAsCI44fKAkgtah+iqKRr8TFacH+qb-ebLJtiDAvXAQp51PbIo7szyMTBoJ6unyHGbWtw6AwmKgADxrCDAWy4euNkL+Ag+PzktV0lJ4ADueGA8BLfsZyjtiy5TpyXM0xmiIlQ9gxXpmtrxM2HwDwlhO-dQllMo2yyRjA17uoDLJnySES6LKQPR5obwjkTMg0ekQuN3OJxLU9-hzB0eUiEsv3+GZ6wAazfb8UCgYwAJsUt42QID-jgMwITMN5UmJVAQAAI0TTDOVZXMjC6WwmDASw8HkSDhk-XsujBSFmKwuBXXdE4fWQMc9XIrDhCgVwL1SNZgFQfsRIo6isM+b5-zY4ZeKgWx+N9Mt5OGfNsXMNQkm4rpUgJdA9K6MpKLZWyqIUxZpNkri+woUQoFcro9AAfXUAAGfyTlo5B6MYvSQIc1FFNYiMOIhLyeLdTS1FYGB-FSAShKZVJuUeAycROCyYBEkDHS5cQgA

lukeramsden commented 2 months ago

Also seeing this. Seems like only option is to disabled exactOptionalPropertyTypes for now which isn't ideal but not the end of the world.