From a275d274deb07ed17bb582e63d9428c5616067db Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Fri, 9 Aug 2024 01:32:30 -0400 Subject: [PATCH] [core] Fix `Actor` not assignable to `ActorRef` problem (#5011) * Add failing test * Add stricter `ActorRefFromLogic`, improve `ActorRefFrom` * Changeset * Remove test --- .changeset/tasty-kangaroos-lay.md | 5 +++ packages/core/src/actors/callback.ts | 4 +- packages/core/src/actors/observable.ts | 4 +- packages/core/src/actors/promise.ts | 4 +- packages/core/src/actors/transition.ts | 4 +- packages/core/src/spawn.ts | 8 ++-- packages/core/src/types.ts | 59 +++++++------------------- packages/core/test/actions.test.ts | 21 +++++---- packages/core/test/types.test.ts | 24 ++++++++++- 9 files changed, 67 insertions(+), 66 deletions(-) create mode 100644 .changeset/tasty-kangaroos-lay.md diff --git a/.changeset/tasty-kangaroos-lay.md b/.changeset/tasty-kangaroos-lay.md new file mode 100644 index 0000000000..376d10c3cd --- /dev/null +++ b/.changeset/tasty-kangaroos-lay.md @@ -0,0 +1,5 @@ +--- +'xstate': patch +--- + +There is a new type helper: `ActorRefFromLogic`. This type is a stricter form of `ActorRefFrom` that only accepts actor logic types. See https://github.com/statelyai/xstate/issues/4997 for more details. diff --git a/packages/core/src/actors/callback.ts b/packages/core/src/actors/callback.ts index ac620d52d4..1dcc84a371 100644 --- a/packages/core/src/actors/callback.ts +++ b/packages/core/src/actors/callback.ts @@ -2,7 +2,7 @@ import { XSTATE_STOP } from '../constants.ts'; import { AnyActorSystem } from '../system.ts'; import { ActorLogic, - ActorRefFrom, + ActorRefFromLogic, AnyActorRef, AnyEventObject, EventObject, @@ -73,7 +73,7 @@ export type CallbackActorLogic< export type CallbackActorRef< TEvent extends EventObject, TInput = NonReducibleUnknown -> = ActorRefFrom>; +> = ActorRefFromLogic>; type Receiver = ( listener: { diff --git a/packages/core/src/actors/observable.ts b/packages/core/src/actors/observable.ts index bfa0c0cc3c..0dafa0813c 100644 --- a/packages/core/src/actors/observable.ts +++ b/packages/core/src/actors/observable.ts @@ -2,7 +2,7 @@ import { XSTATE_STOP } from '../constants'; import { AnyActorSystem } from '../system.ts'; import { ActorLogic, - ActorRefFrom, + ActorRefFromLogic, EventObject, NonReducibleUnknown, Snapshot, @@ -67,7 +67,7 @@ export type ObservableActorLogic< * @see {@link fromObservable} * @see {@link fromEventObservable} */ -export type ObservableActorRef = ActorRefFrom< +export type ObservableActorRef = ActorRefFromLogic< ObservableActorLogic >; diff --git a/packages/core/src/actors/promise.ts b/packages/core/src/actors/promise.ts index dbebb291d0..ccc920bc1d 100644 --- a/packages/core/src/actors/promise.ts +++ b/packages/core/src/actors/promise.ts @@ -2,7 +2,7 @@ import { XSTATE_STOP } from '../constants.ts'; import { AnyActorSystem } from '../system.ts'; import { ActorLogic, - ActorRefFrom, + ActorRefFromLogic, AnyActorRef, EventObject, NonReducibleUnknown, @@ -61,7 +61,7 @@ export type PromiseActorLogic< * * @see {@link fromPromise} */ -export type PromiseActorRef = ActorRefFrom< +export type PromiseActorRef = ActorRefFromLogic< PromiseActorLogic >; diff --git a/packages/core/src/actors/transition.ts b/packages/core/src/actors/transition.ts index 96460a242d..4c92b2c168 100644 --- a/packages/core/src/actors/transition.ts +++ b/packages/core/src/actors/transition.ts @@ -1,7 +1,7 @@ import { AnyActorSystem } from '../system.ts'; import { ActorLogic, - ActorRefFrom, + ActorRefFromLogic, ActorScope, EventObject, NonReducibleUnknown, @@ -86,7 +86,7 @@ export type TransitionActorLogic< export type TransitionActorRef< TContext, TEvent extends EventObject -> = ActorRefFrom< +> = ActorRefFromLogic< TransitionActorLogic, TEvent, unknown> >; diff --git a/packages/core/src/spawn.ts b/packages/core/src/spawn.ts index 749bb9faa4..8ad64f6556 100644 --- a/packages/core/src/spawn.ts +++ b/packages/core/src/spawn.ts @@ -1,6 +1,6 @@ import { ProcessingStatus, createActor } from './createActor.ts'; import { - ActorRefFrom, + ActorRefFromLogic, AnyActorLogic, AnyActorRef, AnyActorScope, @@ -43,7 +43,7 @@ export type Spawner = IsLiteralString< ( logic: TSrc, ...[options]: SpawnOptions - ): ActorRefFrom['logic']>; + ): ActorRefFromLogic['logic']>; ( src: TLogic, options?: { @@ -52,7 +52,7 @@ export type Spawner = IsLiteralString< input?: InputFrom; syncSnapshot?: boolean; } - ): ActorRefFrom; + ): ActorRefFromLogic; } : ( src: TLogic, @@ -62,7 +62,7 @@ export type Spawner = IsLiteralString< input?: TLogic extends string ? unknown : InputFrom; syncSnapshot?: boolean; } - ) => TLogic extends string ? AnyActorRef : ActorRefFrom; + ) => TLogic extends AnyActorLogic ? ActorRefFromLogic : AnyActorRef; export function createSpawner( actorScope: AnyActorScope, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f15330dc8e..b78f5f9253 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -2006,50 +2006,19 @@ export type ActorLogicFrom = ReturnTypeOrValue extends infer R : never : never; +// TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic export type ActorRefFrom = ReturnTypeOrValue extends infer R - ? R extends StateMachine< - infer TContext, - infer TEvent, - infer TChildren, - infer _TActor, - infer _TAction, - infer _TGuard, - infer _TDelay, - infer TStateValue, - infer TTag, - infer _TInput, - infer TOutput, - infer TEmitted, - infer TMeta, - infer TStateSchema - > - ? ActorRef< - MachineSnapshot< - TContext, - TEvent, - TChildren, - TStateValue, - TTag, - TOutput, - TMeta, - TStateSchema - >, - TEvent, - TEmitted - > - : R extends Promise - ? ActorRefFrom> - : R extends ActorLogic< - infer TSnapshot, - infer TEvent, - infer _TInput, - infer _TSystem, - infer TEmitted - > - ? ActorRef - : never + ? R extends AnyActorLogic + ? ActorRefFromLogic + : never : never; +export type ActorRefFromLogic = ActorRef< + SnapshotFrom, + EventFromLogic, + EmittedFrom +>; + export type DevToolsAdapter = (service: AnyActor) => void; /** @deprecated Use `Actor` instead. */ @@ -2259,7 +2228,7 @@ export type AnyActorLogic = ActorLogic< export type UnknownActorLogic = ActorLogic< any, // snapshot any, // event - never, // input + any, // input AnyActorSystem, any // emitted >; @@ -2429,7 +2398,9 @@ type ExtractLiteralString = T extends string : never; type ToConcreteChildren = { - [A in TActor as ExtractLiteralString]?: ActorRefFrom; + [A in TActor as ExtractLiteralString]?: ActorRefFromLogic< + A['logic'] + >; }; export type ToChildren = @@ -2444,7 +2415,7 @@ export type ToChildren = { include: { [id: string]: TActor extends any - ? ActorRefFrom | undefined + ? ActorRefFromLogic | undefined : never; }; exclude: {}; diff --git a/packages/core/test/actions.test.ts b/packages/core/test/actions.test.ts index af26f1f944..389404bfeb 100644 --- a/packages/core/test/actions.test.ts +++ b/packages/core/test/actions.test.ts @@ -7,13 +7,12 @@ import { raise, sendParent, sendTo, - spawnChild, stopChild } from '../src/actions.ts'; import { CallbackActorRef, fromCallback } from '../src/actors/callback.ts'; import { ActorRef, - ActorRefFrom, + ActorRefFromLogic, AnyActorRef, EventObject, Snapshot, @@ -1551,7 +1550,7 @@ describe('entry/exit actions', () => { const parent = createMachine({ types: {} as { context: { - child: ActorRefFrom; + child: ActorRefFromLogic; }; }, id: 'parent', @@ -1596,7 +1595,7 @@ describe('entry/exit actions', () => { const parent = createMachine({ types: {} as { context: { - child: ActorRefFrom; + child: ActorRefFromLogic; }; }, id: 'parent', @@ -2976,7 +2975,7 @@ describe('sendTo', () => { const parentMachine = createMachine({ types: {} as { context: { - child: ActorRefFrom; + child: ActorRefFromLogic; }; }, context: ({ spawn }) => ({ @@ -3008,7 +3007,7 @@ describe('sendTo', () => { const parentMachine = createMachine({ types: {} as { context: { - child: ActorRefFrom; + child: ActorRefFromLogic; count: number; }; }, @@ -3045,7 +3044,7 @@ describe('sendTo', () => { createMachine({ types: {} as { context: { - child: ActorRefFrom; + child: ActorRefFromLogic; }; }, context: ({ spawn }) => ({ @@ -3076,7 +3075,9 @@ describe('sendTo', () => { }); const parentMachine = createMachine({ - types: {} as { context: { child: ActorRefFrom } }, + types: {} as { + context: { child: ActorRefFromLogic }; + }, context: ({ spawn }) => ({ child: spawn(childMachine, { id: 'child' }) }), @@ -3105,7 +3106,9 @@ describe('sendTo', () => { }); const parentMachine = createMachine({ - types: {} as { context: { child: ActorRefFrom } }, + types: {} as { + context: { child: ActorRefFromLogic }; + }, context: ({ spawn }) => ({ child: spawn(childMachine) }), diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index 1aab05f61d..1889153423 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -25,7 +25,13 @@ import { stateIn, setup, toPromise, - UnknownActorRef + UnknownActorRef, + AnyActorLogic, + ActorRef, + SnapshotFrom, + EmittedFrom, + EventFrom, + ActorRefFromLogic } from '../src/index'; function noop(_x: unknown) { @@ -4582,3 +4588,19 @@ it('UnknownActorRef should return a Snapshot-typed value from getSnapshot()', () // @ts-expect-error actor.getSnapshot().status === 'FOO'; }); + +it('Actor should be assignable to ActorRefFromLogic', () => { + const logic = createMachine({}); + + class ActorThing { + actorRef: ActorRefFromLogic; + constructor(actorLogic: T) { + const actor = createActor(actorLogic); + + actor satisfies ActorRefFromLogic; + this.actorRef = actor; + } + } + + new ActorThing(logic); +});