Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Type issue with not helper #5090

Open
flugg opened this issue Sep 22, 2024 · 5 comments
Open

Bug: Type issue with not helper #5090

flugg opened this issue Sep 22, 2024 · 5 comments
Assignees

Comments

@flugg
Copy link

flugg commented Sep 22, 2024

XState version

XState version 5

Description

I'm trying to create a package that creates a reusable state machine. I've simplified our machine to locate the error being the not function:

import { not, setup } from 'xstate';

export const myMachine = setup({
  guards: {
    myGuard: () => true,
  },
}).createMachine({
  states: {
    foo: {
      on: {
        someEvent: {
          target: 'bar',
          guard: not('myGuard'),
        },
      },
    },
    bar: {},
  },
});

Expected result

I expected the typings to be built without issues.

Actual result

When trying to build types for the packages, I get the following error:

src/my-machine.ts:3:14 - error TS2742: The inferred type of 'myMachine' cannot be named without a reference to '../../../node_modules/xstate/dist/declarations/src/guards'. This is likely not portable. A type annotation is necessary.

3 export const myMachine = setup({

It seems like the issue stems from the not function referencing the GuardPredicate type which is not exported by XState and TypeScript is unable to build a type for it without this reference.

Reproduction

https://codesandbox.io/p/devbox/angry-goldberg-v42j8m?workspaceId=4a3e8946-02ff-4af1-aeb6-deeb01d9e58a

Additional context

Try running pnpm run build in the reproduction and you will see the error. For some reason, the error doesn't occur when using...

"moduleResolution": "node",

...in tsconfig.json. But that doesn't seem like a viable solution as that causes other module issues and is not really recommended by TS at this point.

@flugg flugg added the bug label Sep 22, 2024
@flugg
Copy link
Author

flugg commented Sep 22, 2024

I also found this issue which seems to have the same issue, but was closed without further investigation.

And as the reproduction above seems to prove, this doesn't seem to have anything to do with the size of the machines.

@Loque-
Copy link

Loque- commented Oct 2, 2024

I think I just encountered this issue when trying the machine from here: https://github.com/Devessier/xstatebyexample/blob/main/src/examples/video-player/machine.ts with

"typescript": "5.6.2",
"xstate": "^5.18.2"

I found adding;

// @ts-ignore: 'is declared but its value is never read'
import { Guard } from 'xstate/guards';

resolved the squiggly line, which I considered after this explanation: microsoft/TypeScript#58176 (comment)

@boneskull
Copy link
Contributor

I created #5046 and I was not able to track down a minimal way to reproduce the problem.

@Loque-'s workaround works for me. You don't even need Guard though:

import 'xstate/guards';

Can anyone (@Loque-, @flugg) reproduce this problem in an XState version older than 5.17.1?

@boneskull
Copy link
Contributor

boneskull commented Jan 4, 2025

A way to eliminate this error seems to be--if using and, not, etc.--don't use them within a machine configuration directly. Define the composite guard in setup() only:

e.g.:

const machine = setup({
  guards: {
    foo: () => true,
    bar: () => false,
  }
}).createMachine({
  always: {
    guard: and(['foo', 'bar']), // <-- BAD
    actions: log('success')
  }
});

Instead:

const machine = setup({
  guards: {
    foo: () => true,
    bar: () => false,
    fooAndBar: and(['foo', 'bar']),
  }
}).createMachine({
  always: {
    guard: 'fooAndBar', // <-- OK
    actions: log('success')
  }
});

@boneskull
Copy link
Contributor

@Andarist, @davidkpiano: I don't know how "popular" this problem is, but I think the above gives a clue to where it lies. The first example results in a type explosion, but the second does not.

Probably worth looking at using the "native" NoInfer and then scrutinizing the differences in type arguments between these and built-in actions. Things like TExpressionEvent extends EventObject vs TExpressionEvent extends AnyEventObject...

Still, the compiler won't protest without a sufficiently complex machine. Since I have (several!) handy which exhibit this behavior, I'll take a closer look whenever the planets align.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants