Skip to content

Commit

Permalink
Utilize TActor to type spawn (#4222)
Browse files Browse the repository at this point in the history
* Utilize `TActor` to type `spawn`

* fix dts emit problem

* try this

* rename type params

* Add spawn tests

* add input-related tests

* add changeset
  • Loading branch information
Andarist committed Sep 11, 2023
1 parent dbbd3c2 commit 41822f0
Show file tree
Hide file tree
Showing 16 changed files with 875 additions and 276 deletions.
5 changes: 5 additions & 0 deletions .changeset/calm-experts-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': minor
---

`spawn` can now benefit from the actor types. Its arguments are strongly-typed based on them.
5 changes: 4 additions & 1 deletion packages/core/src/StateNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import type {
UnknownAction,
ParameterizedObject,
AnyStateMachine,
AnyStateNodeConfig
AnyStateNodeConfig,
ProvidedActor
} from './types.ts';
import {
createInvokeId,
Expand Down Expand Up @@ -289,6 +290,7 @@ export class StateNode<
InvokeDefinition<
TContext,
TEvent,
ProvidedActor,
ParameterizedObject,
ParameterizedObject,
string
Expand Down Expand Up @@ -332,6 +334,7 @@ export class StateNode<
} as InvokeDefinition<
TContext,
TEvent,
ProvidedActor,
ParameterizedObject,
ParameterizedObject,
string
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/actions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export { assign, type AssignAction } from './actions/assign.ts';
export {
assign,
type AssignAction,
type AssignArgs
} from './actions/assign.ts';
export { cancel, type CancelAction } from './actions/cancel.ts';
export { choose, type ChooseAction } from './actions/choose.ts';
export { log, type LogAction } from './actions/log.ts';
Expand Down
39 changes: 29 additions & 10 deletions packages/core/src/actions/assign.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
import isDevelopment from '#is-development';
import { cloneState } from '../State.ts';
import { createSpawner } from '../spawn.ts';
import { Spawner, createSpawner } from '../spawn.ts';
import type {
ActionArgs,
AnyActorContext,
AnyActorRef,
AnyEventObject,
AnyState,
AssignArgs,
Assigner,
EventObject,
LowInfer,
MachineContext,
ParameterizedObject,
PropertyAssigner
PropertyAssigner,
ProvidedActor
} from '../types.ts';

export interface AssignArgs<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TExpressionAction extends ParameterizedObject | undefined,
TActor extends ProvidedActor
> extends ActionArgs<TContext, TExpressionEvent, TExpressionAction> {
spawn: Spawner<TActor>;
}

function resolve(
actorContext: AnyActorContext,
state: AnyState,
actionArgs: ActionArgs<any, any, any>,
{
assignment
}: {
assignment: Assigner<any, any, any> | PropertyAssigner<any, any, any>;
assignment:
| Assigner<any, any, any, any>
| PropertyAssigner<any, any, any, any>;
}
) {
if (!state.context) {
Expand All @@ -33,7 +44,7 @@ function resolve(
}
const spawnedChildren: Record<string, AnyActorRef> = {};

const assignArgs: AssignArgs<any, any, any> = {
const assignArgs: AssignArgs<any, any, any, any> = {
context: state.context,
event: actionArgs.event,
action: actionArgs.action,
Expand Down Expand Up @@ -77,9 +88,11 @@ function resolve(
export interface AssignAction<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TExpressionAction extends ParameterizedObject | undefined
TExpressionAction extends ParameterizedObject | undefined,
TActor extends ProvidedActor
> {
(_: ActionArgs<TContext, TExpressionEvent, TExpressionAction>): void;
_out_TActor?: TActor;
}

/**
Expand All @@ -92,12 +105,18 @@ export function assign<
TExpressionEvent extends AnyEventObject = AnyEventObject, // TODO: consider using a stricter `EventObject` here
TExpressionAction extends ParameterizedObject | undefined =
| ParameterizedObject
| undefined
| undefined,
TActor extends ProvidedActor = ProvidedActor
>(
assignment:
| Assigner<LowInfer<TContext>, TExpressionEvent, TExpressionAction>
| PropertyAssigner<LowInfer<TContext>, TExpressionEvent, TExpressionAction>
): AssignAction<TContext, TExpressionEvent, TExpressionAction> {
| Assigner<LowInfer<TContext>, TExpressionEvent, TExpressionAction, TActor>
| PropertyAssigner<
LowInfer<TContext>,
TExpressionEvent,
TExpressionAction,
TActor
>
): AssignAction<TContext, TExpressionEvent, TExpressionAction, TActor> {
function assign(
_: ActionArgs<TContext, TExpressionEvent, TExpressionAction>
) {
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/actions/choose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
AnyState,
ActionArgs,
ParameterizedObject,
NoInfer
NoInfer,
ProvidedActor
} from '../types.ts';
import { evaluateGuard } from '../guards.ts';
import { toArray } from '../utils.ts';
Expand All @@ -24,6 +25,7 @@ function resolve(
MachineContext,
EventObject,
EventObject,
ProvidedActor,
ParameterizedObject,
ParameterizedObject,
string
Expand All @@ -45,11 +47,13 @@ export interface ChooseAction<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TExpressionAction extends ParameterizedObject | undefined,
TActor extends ProvidedActor,
TAction extends ParameterizedObject,
TGuard extends ParameterizedObject,
TDelay extends string
> {
(_: ActionArgs<TContext, TExpressionEvent, TExpressionAction>): void;
_out_TActor?: TActor;
_out_TAction?: TAction;
_out_TGuard?: TGuard;
_out_TDelay?: TDelay;
Expand All @@ -60,6 +64,7 @@ export function choose<
TExpressionEvent extends EventObject,
TEvent extends EventObject,
TExpressionAction extends ParameterizedObject | undefined,
TActor extends ProvidedActor,
TAction extends ParameterizedObject,
TGuard extends ParameterizedObject,
TDelay extends string
Expand All @@ -69,6 +74,7 @@ export function choose<
TContext,
TExpressionEvent,
TEvent,
TActor,
NoInfer<TAction>,
NoInfer<TGuard>,
TDelay
Expand All @@ -78,6 +84,7 @@ export function choose<
TContext,
TExpressionEvent,
TExpressionAction,
TActor,
TAction,
TGuard,
TDelay
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/actions/pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
MachineContext,
ParameterizedObject,
SingleOrArray,
NoInfer
NoInfer,
ProvidedActor
} from '../types.ts';
import { toArray } from '../utils.ts';

Expand Down Expand Up @@ -41,12 +42,14 @@ export interface PureAction<
TExpressionEvent extends EventObject,
TExpressionAction extends ParameterizedObject | undefined,
TEvent extends EventObject,
TActor extends ProvidedActor,
TAction extends ParameterizedObject,
TGuard extends ParameterizedObject,
TDelay extends string
> {
(_: ActionArgs<TContext, TExpressionEvent, TExpressionAction>): void;
_out_TEvent?: TEvent;
_out_TActor?: TActor;
_out_TAction?: TAction;
_out_TGuard?: TGuard;
_out_TDelay?: TDelay;
Expand All @@ -59,6 +62,7 @@ export function pure<
| ParameterizedObject
| undefined,
TEvent extends EventObject = TExpressionEvent,
TActor extends ProvidedActor = ProvidedActor,
TAction extends ParameterizedObject = ParameterizedObject,
TGuard extends ParameterizedObject = ParameterizedObject,
TDelay extends string = string
Expand All @@ -75,6 +79,7 @@ export function pure<
TExpressionEvent,
NoInfer<TEvent>,
undefined,
TActor,
NoInfer<TAction>,
NoInfer<TGuard>,
TDelay
Expand All @@ -85,6 +90,7 @@ export function pure<
TExpressionEvent,
TExpressionAction,
TEvent,
TActor,
TAction,
TGuard,
TDelay
Expand Down
15 changes: 8 additions & 7 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
export * from './actions.ts';
export {
fromCallback,
fromEventObservable,
fromObservable,
fromPromise,
fromTransition
} from './actors/index.ts';
export { SimulatedClock } from './SimulatedClock.ts';
export { type Spawner } from './spawn.ts';
export { StateMachine } from './StateMachine.ts';
export { getStateNodes } from './stateUtils.ts';
export * from './typegenTypes.ts';
Expand All @@ -18,13 +26,6 @@ import { mapState } from './mapState.ts';
import { State } from './State.ts';
import { StateNode } from './StateNode.ts';
// TODO: decide from where those should be exported
export {
fromCallback,
fromEventObservable,
fromObservable,
fromPromise,
fromTransition
} from './actors/index.ts';
export { matchesState, pathToStateValue, toObserver } from './utils.ts';
export {
Actor,
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/scxml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ function createGuard<

function mapAction(
element: XMLElement
): ActionFunction<any, any, any, any, any, any, any> {
): ActionFunction<any, any, any, any, any, any, any, any> {
switch (element.name) {
case 'raise': {
return raise({
Expand Down Expand Up @@ -322,8 +322,8 @@ return ${element.attributes!.expr};

function mapActions(
elements: XMLElement[]
): ActionFunction<any, any, any, any, any, any, any>[] {
const mapped: ActionFunction<any, any, any, any, any, any, any>[] = [];
): ActionFunction<any, any, any, any, any, any, any, any>[] {
const mapped: ActionFunction<any, any, any, any, any, any, any, any>[] = [];

for (const element of elements) {
if (element.type === 'comment') {
Expand Down
50 changes: 47 additions & 3 deletions packages/core/src/spawn.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,66 @@
import { createErrorPlatformEvent } from './eventUtils.ts';
import { ActorStatus, createActor } from './interpreter.ts';
import {
ActorRefFrom,
AnyActorContext,
AnyActorLogic,
AnyActorRef,
AnyEventObject,
AnyState,
Spawner,
InputFrom,
IsLiteralString,
ProvidedActor,
TODO
} from './types.ts';
import { resolveReferencedActor } from './utils.ts';

type SpawnOptions<
TActor extends ProvidedActor,
TSrc extends TActor['src']
> = TActor extends {
src: TSrc;
}
? 'id' extends keyof TActor
? [
options: {
id: TActor['id'];
systemId?: string;
input?: InputFrom<TActor['logic']>;
}
]
: [
options?: {
id?: string;
systemId?: string;
input?: InputFrom<TActor['logic']>;
}
]
: never;

export type Spawner<TActor extends ProvidedActor> = IsLiteralString<
TActor['src']
> extends true
? <TSrc extends TActor['src']>(
logic: TSrc,
...[options = {} as any]: SpawnOptions<TActor, TSrc>
) => ActorRefFrom<(TActor & { src: TSrc })['logic']>
: // TODO: do not accept machines without all implementations
(
src: AnyActorLogic | string,
options?: {
id?: string;
systemId?: string;
input?: unknown;
}
) => AnyActorRef;

export function createSpawner(
actorContext: AnyActorContext,
{ machine, context }: AnyState,
event: AnyEventObject,
spawnedChildren: Record<string, AnyActorRef>
): Spawner {
const spawn: Spawner = (src, options = {}) => {
): Spawner<any> {
const spawn: Spawner<any> = (src, options = {}) => {
const { systemId } = options;
if (typeof src === 'string') {
const referenced = resolveReferencedActor(
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/stateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import {
UnknownAction,
ParameterizedObject,
ActionFunction,
AnyTransitionConfig
AnyTransitionConfig,
ProvidedActor
} from './types.ts';
import {
isArray,
Expand Down Expand Up @@ -459,7 +460,7 @@ export function formatInitialTransition<
stateNode: AnyStateNode,
_target:
| SingleOrArray<string>
| InitialTransitionConfig<TContext, TEvent, TODO, TODO, TODO>
| InitialTransitionConfig<TContext, TEvent, TODO, TODO, TODO, TODO>
): InitialTransitionDefinition<TContext, TEvent> {
if (typeof _target === 'string' || isArray(_target)) {
const targets = toArray(_target).map((t) => {
Expand Down Expand Up @@ -1431,6 +1432,7 @@ export function resolveActionsAndContext<
EventObject,
EventObject,
ParameterizedObject | undefined,
ProvidedActor,
ParameterizedObject,
ParameterizedObject,
string
Expand Down
Loading

0 comments on commit 41822f0

Please sign in to comment.