diff --git a/.changeset/eighty-jars-watch.md b/.changeset/eighty-jars-watch.md new file mode 100644 index 0000000000..c6aee3ade4 --- /dev/null +++ b/.changeset/eighty-jars-watch.md @@ -0,0 +1,5 @@ +--- +'xstate': patch +--- + +Fixed an issue with `spawn` within `assign` not returning a narrowed down `ActorRef` type on TypeScrip 5.0 diff --git a/packages/core/src/spawn.ts b/packages/core/src/spawn.ts index 194a142d91..ced5947ce9 100644 --- a/packages/core/src/spawn.ts +++ b/packages/core/src/spawn.ts @@ -37,13 +37,21 @@ type SpawnOptions< > : never; +// it's likely-ish that `(TActor & { src: TSrc })['logic']` would be faster +// but it's only possible to do it since https://github.com/microsoft/TypeScript/pull/53098 (TS 5.1) +// and we strive to support TS 5.0 whenever possible +type GetConcreteLogic< + TActor extends ProvidedActor, + TSrc extends TActor['src'] +> = Extract['logic']; + export type Spawner = IsLiteralString< TActor['src'] > extends true ? ( logic: TSrc, ...[options = {} as any]: SpawnOptions - ) => ActorRefFrom<(TActor & { src: TSrc })['logic']> + ) => ActorRefFrom> : // TODO: do not accept machines without all implementations ( src: TLogic, diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index e5b1447edf..8a3c78b993 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -1469,6 +1469,53 @@ describe('spawner in assign', () => { }) }); }); + + it(`should return a concrete actor ref type based on the used string reference`, () => { + const child = createMachine({ + types: {} as { + context: { + counter: number; + }; + }, + context: { + counter: 100 + } + }); + + const otherChild = createMachine({ + types: {} as { + context: { + title: string; + }; + }, + context: { + title: 'The Answer' + } + }); + + createMachine({ + types: {} as { + context: { + myChild?: ActorRefFrom; + }; + actors: + | { + src: 'child'; + logic: typeof child; + } + | { + src: 'other'; + logic: typeof otherChild; + }; + }, + context: {}, + entry: assign({ + myChild: ({ spawn }) => { + return spawn('child'); + } + }) + }); + }); }); describe('invoke', () => {