statelyai / xstate

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

Bug: When a state listens to more than one event, strict event type checking breaks #4951

Open rexsuit opened 5 months ago

rexsuit commented 5 months ago

Bug or feature request?

Bug with xstate v5.13.2

Description:

When creating a strictly typed machine with the setup function, and adding event definitions, TS informs if we try to listen to an undefined event.

image

However, this behavior breaks when we try to listen to more than one event on the same state

image

(Bug) Expected result:

I am able to listen to more than one event per state, and still have TS guarantee that I am used pre-defined events

(Bug) Actual result:

Listening to more than one event per state stops TS from informing me of my using undefined events

Link to reproduction or proof-of-concept:

Here's a codesandbox of both the machine where TS shows an where listens to an undefined event (and shows a TS error), and another machine the same sate listens to more than one event, and fails to inform the vscode user of the fact that an undefined error is being listened to.

Alternatively, here's a snipper that can be pasted into local dev. The snippet shoud cause a TS error if it listens to an event not defined in the "types" object, but does not as long as more than one "on" event is listened to.

export const testMachine = setup({
  types: {
    events: {} as { type: "start" },
  },
}).createMachine({
  initial: "Idle",
  states: {
    Idle: {
      on: {
        fakeEvent: "Pending",
        start: "Pending",
      },
    },
    Pending: {},
  },
});
davidkpiano commented 5 months ago

From my quick investigation, this has to do with EventDescriptor<TEvent> here: https://github.com/statelyai/xstate/blob/main/packages/core/src/types.ts#L577

Changing this to just TEvent['type'] solves the problem, although we still have to account for partial event descriptors.

cc. @Andarist

zollipaul commented 1 week ago

Is there a solution in sight here? Typescript safety on machines with more more than one event would be so great :) Thank you for looking into this!

Andarist commented 1 week ago

Minimal repro case: TS playground. I don't think EventDescriptor is at fault here as the same thing happens with TEvent['type']: TS playground

The issue is that with setup we infer TConfig and on is part of it. By definition inference allows extra properties to be inferred. Otherwise this wouldn't work:

declare function test<T extends { a: unknown }>(arg: T): void

test({ a: 1, b: 2 })

I try to come up with some solution for this but so far I don't see how to get excess property checks back in this situation.

Andarist commented 1 week ago

This works: TS playground. But applying this in our, way more complex, types breaks things quite a bit