statelyai / xstate

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

createMachine fails to infer typestate for particular state #1138

Closed ColCh closed 1 year ago

ColCh commented 4 years ago

Description

given this code


type YesNoContext = { value?: number };

type YesNoEvent = { type: 'YES' };

type NoLiteralContext = { value: 'no'; context: { value: undefined } };
type YesLiteralContext = { value: 'yes'; context: { value: number } };

type YesNoTypestate =
  | NoLiteralContext
  | YesLiteralContext;

const expectYesLiteralContext = (ctx: YesLiteralContext) => ctx;
const expectNoLiteralContext = (ctx: NoLiteralContext) => ctx;

const yesNoMachine = createMachine<
  YesNoContext,
  YesNoEvent,
  YesNoTypestate
>({
  context: {
    value: undefined
  },
  initial: 'no',
  states: {
    no: {
      on: {
        YES: 'yes'
      },
      entry: expectNoLiteralContext,
    },
    yes: {
      type: 'final',
      entry: expectYesLiteralContext,
    }
  }
});

Expected Result

assertion is not satisfied since context within entry is inferred as YesNoContext

image

Actual Result

Within state, it should be inferred as one of literal contexts, see assertion functions

Reproduction

please take a look on this snippet in CodeSandbox: https://codesandbox.io/s/beautiful-cartwright-02oiw?file=/src/index.ts

Additional context

Latest XState version by the moment

XState version: 4.8.0

TypeScript version: 3.8.3

ColCh commented 4 years ago

May be, there is some way to get this working?

I'd like to utilize TypeSchema as well

may be, there is some generic with 4 parameters? e.g. MachineDefinition<TContext, TSchema, TEvent, TTypestate>? That would be enough for me, to use it in somewhat like following snippet:

type YesNoContext = { value?: number };

type YesNoEvent = { type: 'YES' };

type NoLiteralContext = { value: 'no'; context: { value: undefined } };
type YesLiteralContext = { value: 'yes'; context: { value: number } };

type YesNoTypestate = NoLiteralContext | YesLiteralContext;

const expectYesLiteralContext = (ctx: YesLiteralContext) => ctx;
const expectNoLiteralContext = (ctx: YesLiteralContext) => ctx;

type YesNoSchema = {
    states: {
        yes: {};
        no: {};
    };
};

const definition: MachineDefinition<YesNoContext, YesNoSchema, YesNoEvent, YesNoTypestate> = {
    context: {
        value: undefined,
    },
    initial: 'no',
    states: {
        no: {
            on: {
                YES: 'yes',
            },
            entry: expectNoLiteralContext,
        },
        yes: {
            type: 'final',
            entry: expectYesLiteralContext,
        },
    },
};

const yesNoMachine = createMachine<YesNoContext, YesNoEvent, YesNoTypestate>(definition);
ColCh commented 4 years ago

may be related but I'm not sure https://github.com/davidkpiano/xstate/issues/1120

davidkpiano commented 4 years ago

The Typestate feature is for state.matches(...), and not yet for within the state machine. That's something we're going to work on for V5.

ColCh commented 4 years ago

Thank you for your answer!

So, there is no temporary workaround for this?

ColCh commented 4 years ago

Ah yes, it seems that main point is here:

export declare type StatesConfig<TContext, TStateSchema extends StateSchema, TEvent extends EventObject> = {
    [K in keyof TStateSchema['states']]: StateNodeConfig</* --> */TContext /* <--- here! */, TStateSchema['states'][K], TEvent>;
};
davidkpiano commented 4 years ago

I would accept a PR if you really want this feature.

ColCh commented 4 years ago

Sure but please advise me on some steps to get started, because I'm not familiar with codebase. It's StatesConfig right?

huan commented 2 years ago

This use case should be supported by the Typestate because it is an intuition.

I feel very strange why the context is not following the Typestate under a specific state and trying to figure out why until I reach this thread and read the answer from @davidkpiano.

Really looking forward to upgrading with the v5 alpha/RC/beta/next because there are lots of exciting improvements.

Andarist commented 1 year ago

Typestates were removed in v5. We know that this is a highly requested feature but to introduce it back we need to do it in a type-safe way and that's very challenging