Skip to content

Commit

Permalink
InterpreterFrom and ActorRefFrom types used on machines with type…
Browse files Browse the repository at this point in the history
…gen data should now correctly return types with final/resolved typegen data (#3139)
  • Loading branch information
Andarist committed Mar 10, 2022
1 parent 11d815e commit 7b45fda
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 16 deletions.
17 changes: 17 additions & 0 deletions .changeset/tasty-bananas-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'xstate': patch
---

`InterpreterFrom` and `ActorRefFrom` types used on machines with typegen data should now correctly return types with final/resolved typegen data. The "final" type here means a type that already encodes the information that all required implementations have been provided. Before this change this wouldn't typecheck correctly:

```ts
const machine = createMachine({
// this encodes that we still expect `myAction` to be provided
tsTypes: {} as Typegen0
});
const service: InterpreterFrom<typeof machine> = machine.withConfig({
actions: {
myAction: () => {}
}
});
```
22 changes: 8 additions & 14 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1690,7 +1690,7 @@ export type ActorRefFrom<T> = ReturnTypeOrValue<T> extends infer R
TContext,
TEvent,
TTypestate,
TResolvedTypesMeta
MarkAllImplementationsAsProvided<TResolvedTypesMeta>
>
: R extends Promise<infer U>
? ActorRef<never, U>
Expand All @@ -1703,7 +1703,7 @@ export type AnyInterpreter = Interpreter<any, any, any, any, any>;

export type InterpreterFrom<
T extends AnyStateMachine | ((...args: any[]) => AnyStateMachine)
> = T extends StateMachine<
> = ReturnTypeOrValue<T> extends StateMachine<
infer TContext,
infer TStateSchema,
infer TEvent,
Expand All @@ -1712,19 +1712,13 @@ export type InterpreterFrom<
any,
infer TResolvedTypesMeta
>
? Interpreter<TContext, TStateSchema, TEvent, TTypestate, TResolvedTypesMeta>
: T extends (
...args: any[]
) => StateMachine<
infer TContext,
infer TStateSchema,
infer TEvent,
infer TTypestate,
any,
any,
infer TResolvedTypesMeta
? Interpreter<
TContext,
TStateSchema,
TEvent,
TTypestate,
MarkAllImplementationsAsProvided<TResolvedTypesMeta>
>
? Interpreter<TContext, TStateSchema, TEvent, TTypestate, TResolvedTypesMeta>
: never;

export type MachineOptionsFrom<
Expand Down
74 changes: 73 additions & 1 deletion packages/xstate-react/test/typegenTypes.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { render } from '@testing-library/react';
import * as React from 'react';
import { assign, createMachine, TypegenMeta } from 'xstate';
import {
ActorRefFrom,
assign,
createMachine,
InterpreterFrom,
TypegenMeta
} from 'xstate';
import { useInterpret, useMachine } from '../src';

describe('useMachine', () => {
Expand Down Expand Up @@ -467,4 +473,70 @@ describe('useInterpret', () => {
}
};
});

it('returned service created based on a lazy machine that supplies missing implementations using `withConfig` should be assignable to the ActorRefFrom<...> type', () => {
interface TypesMeta extends TypegenMeta {
missingImplementations: {
actions: 'someAction';
delays: never;
guards: never;
services: never;
};
}

const machine = createMachine({
tsTypes: {} as TypesMeta
});

function ChildComponent({}: { actorRef: ActorRefFrom<typeof machine> }) {
return null;
}

function App() {
const service = useInterpret(() =>
machine.withConfig({
actions: {
someAction: () => {}
}
})
);

return <ChildComponent actorRef={service} />;
}

render(<App />);
});

it('returned service created based on a lazy machine that supplies missing implementations using `withConfig` should be assignable to the InterpreterFrom<...> type', () => {
interface TypesMeta extends TypegenMeta {
missingImplementations: {
actions: 'someAction';
delays: never;
guards: never;
services: never;
};
}

const machine = createMachine({
tsTypes: {} as TypesMeta
});

function ChildComponent({}: { actorRef: InterpreterFrom<typeof machine> }) {
return null;
}

function App() {
const service = useInterpret(() =>
machine.withConfig({
actions: {
someAction: () => {}
}
})
);

return <ChildComponent actorRef={service} />;
}

render(<App />);
});
});
70 changes: 69 additions & 1 deletion packages/xstate-vue/test/typegenTypes.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { defineComponent } from 'vue';
import { assign, createMachine, TypegenMeta } from 'xstate';
import {
ActorRefFrom,
assign,
createMachine,
InterpreterFrom,
TypegenMeta
} from 'xstate';
import { useInterpret, useMachine } from '../src';

describe('useMachine', () => {
Expand Down Expand Up @@ -459,4 +465,66 @@ describe('useInterpret', () => {
}
});
});

it('returned service created based on a machine that supplies missing implementations using `withConfig` should be assignable to the ActorRefFrom<...> type', () => {
interface TypesMeta extends TypegenMeta {
missingImplementations: {
actions: 'someAction';
delays: never;
guards: never;
services: never;
};
}

const machine = createMachine({
tsTypes: {} as TypesMeta
});

function useMyActor(_actor: ActorRefFrom<typeof machine>) {}

defineComponent({
setup() {
const service = useInterpret(
machine.withConfig({
actions: {
someAction: () => {}
}
})
);
useMyActor(service);
return {};
}
});
});

it('returned service created based on a machine that supplies missing implementations using `withConfig` should be assignable to the InterpreterFrom<...> type', () => {
interface TypesMeta extends TypegenMeta {
missingImplementations: {
actions: 'someAction';
delays: never;
guards: never;
services: never;
};
}

const machine = createMachine({
tsTypes: {} as TypesMeta
});

function useMyActor(_actor: InterpreterFrom<typeof machine>) {}

defineComponent({
setup() {
const service = useInterpret(
machine.withConfig({
actions: {
someAction: () => {}
}
})
);
useMyActor(service);
return {};
}
});
});
});

0 comments on commit 7b45fda

Please sign in to comment.