Skip to content

Commit

Permalink
Removed Interpreter['status'] from publicly available properties
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Nov 2, 2023
1 parent a91fdea commit 9195ccf
Show file tree
Hide file tree
Showing 14 changed files with 80 additions and 67 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-jokes-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': major
---

Removed `Interpreter['status']` from publicly available properties.
10 changes: 6 additions & 4 deletions packages/core/src/StateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,11 +493,13 @@ export class StateMachine<
TResolvedTypesMeta
>
): void {
Object.values(state.children).forEach((child: any) => {
if (child.getSnapshot().status === 'active') {
child.start();
Object.values(state.children as Record<string, AnyActorRef>).forEach(
(child: any) => {
if (child.getSnapshot().status === 'active') {
child.start();
}
}
});
);
}

public getStateNodeById(stateId: string): StateNode<TContext, TEvent> {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/actions/spawn.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import isDevelopment from '#is-development';
import { cloneState } from '../State.ts';
import { createErrorActorEvent } from '../eventUtils.ts';
import { ActorStatus, createActor } from '../interpreter.ts';
import { ProcessingStatus, createActor } from '../interpreter.ts';
import {
ActionArgs,
AnyActorScope,
Expand Down Expand Up @@ -115,7 +115,7 @@ function executeSpawn(
}

actorScope.defer(() => {
if (actorRef.status === ActorStatus.Stopped) {
if (actorRef._processingStatus === ProcessingStatus.Stopped) {
return;
}
try {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/actions/stop.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import isDevelopment from '#is-development';
import { cloneState } from '../State.ts';
import { ActorStatus } from '../interpreter.ts';
import { ProcessingStatus } from '../interpreter.ts';
import {
ActionArgs,
ActorRef,
Expand Down Expand Up @@ -65,7 +65,7 @@ function executeStop(

// this allows us to prevent an actor from being started if it gets stopped within the same macrostep
// this can happen, for example, when the invoking state is being exited immediately by an always transition
if (actorRef.status !== ActorStatus.Running) {
if (actorRef._processingStatus !== ProcessingStatus.Running) {
actorScope.stopChild(actorRef);
return;
}
Expand Down
11 changes: 1 addition & 10 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,7 @@ export { getStateNodes } from './stateUtils.ts';
export * from './typegenTypes.ts';
export * from './types.ts';
export { waitFor } from './waitFor.ts';
import {
Actor,
ActorStatus,
createActor,
interpret,
Interpreter,
InterpreterStatus
} from './interpreter.ts';
import { Actor, createActor, interpret, Interpreter } from './interpreter.ts';
import { createMachine } from './Machine.ts';
import { mapState } from './mapState.ts';
import { State } from './State.ts';
Expand All @@ -23,11 +16,9 @@ import { StateNode } from './StateNode.ts';
export { matchesState, pathToStateValue, toObserver } from './utils.ts';
export {
Actor,
ActorStatus,
createActor,
createMachine,
interpret,
InterpreterStatus,
mapState,
State,
StateNode,
Expand Down
39 changes: 17 additions & 22 deletions packages/core/src/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,13 @@ export interface Clock {
clearTimeout(id: any): void;
}

export enum ActorStatus {
NotStarted,
Running,
Stopped
// those values are currently used by @xstate/react directly so it's important to keep the assigned values in sync
export enum ProcessingStatus {
NotStarted = 0,
Running = 1,
Stopped = 2
}

/**
* @deprecated Use `ActorStatus` instead.
*/
export const InterpreterStatus = ActorStatus;

const defaultOptions = {
clock: {
setTimeout: (fn, ms) => {
Expand Down Expand Up @@ -103,10 +99,9 @@ export class Actor<TLogic extends AnyActorLogic>

private observers: Set<Observer<SnapshotFrom<TLogic>>> = new Set();
private logger: (...args: any[]) => void;
/**
* Whether the service is started.
*/
public status: ActorStatus = ActorStatus.NotStarted;

/** @internal */
public _processingStatus: ProcessingStatus = ProcessingStatus.NotStarted;

// Actor Ref
public _parent?: ActorRef<any, any>;
Expand Down Expand Up @@ -324,7 +319,7 @@ export class Actor<TLogic extends AnyActorLogic>
completeListener
);

if (this.status !== ActorStatus.Stopped) {
if (this._processingStatus !== ProcessingStatus.Stopped) {
this.observers.add(observer);
} else {
try {
Expand All @@ -345,7 +340,7 @@ export class Actor<TLogic extends AnyActorLogic>
* Starts the Actor from the initial state
*/
public start(): this {
if (this.status === ActorStatus.Running) {
if (this._processingStatus === ProcessingStatus.Running) {
// Do not restart the service if it is already started
return this;
}
Expand All @@ -354,7 +349,7 @@ export class Actor<TLogic extends AnyActorLogic>
if (this._systemId) {
this.system._set(this._systemId, this);
}
this.status = ActorStatus.Running;
this._processingStatus = ProcessingStatus.Running;

const initEvent = createInitEvent(this.options.input);

Expand Down Expand Up @@ -434,12 +429,12 @@ export class Actor<TLogic extends AnyActorLogic>
}

private _stop(): this {
if (this.status === ActorStatus.Stopped) {
if (this._processingStatus === ProcessingStatus.Stopped) {
return this;
}
this.mailbox.clear();
if (this.status === ActorStatus.NotStarted) {
this.status = ActorStatus.Stopped;
if (this._processingStatus === ProcessingStatus.NotStarted) {
this._processingStatus = ProcessingStatus.Stopped;
return this;
}
this.mailbox.enqueue({ type: XSTATE_STOP } as any);
Expand Down Expand Up @@ -490,7 +485,7 @@ export class Actor<TLogic extends AnyActorLogic>
}
}
private _stopProcedure(): this {
if (this.status !== ActorStatus.Running) {
if (this._processingStatus !== ProcessingStatus.Running) {
// Actor already stopped; do nothing
return this;
}
Expand All @@ -508,7 +503,7 @@ export class Actor<TLogic extends AnyActorLogic>
// so perhaps this should be unified somehow for all of them
this.mailbox = new Mailbox(this._process.bind(this));

this.status = ActorStatus.Stopped;
this._processingStatus = ProcessingStatus.Stopped;
this.system._unregister(this);

return this;
Expand All @@ -518,7 +513,7 @@ export class Actor<TLogic extends AnyActorLogic>
* @internal
*/
public _send(event: EventFromLogic<TLogic>) {
if (this.status === ActorStatus.Stopped) {
if (this._processingStatus === ProcessingStatus.Stopped) {
// do nothing
if (isDevelopment) {
const eventString = JSON.stringify(event);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/spawn.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createErrorActorEvent } from './eventUtils.ts';
import { ActorStatus, createActor } from './interpreter.ts';
import { ProcessingStatus, createActor } from './interpreter.ts';
import {
ActorRefFrom,
AnyActorScope,
Expand Down Expand Up @@ -140,7 +140,7 @@ export function createSpawner(
const actorRef = spawn(src, options) as TODO; // TODO: fix types
spawnedChildren[actorRef.id] = actorRef;
actorScope.defer(() => {
if (actorRef.status === ActorStatus.Stopped) {
if (actorRef._processingStatus === ProcessingStatus.Stopped) {
return;
}
try {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/stateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
WILDCARD
} from './constants.ts';
import { evaluateGuard } from './guards.ts';
import { ActorStatus } from './interpreter.ts';
import {
ActionArgs,
AnyEventObject,
Expand Down Expand Up @@ -50,6 +49,7 @@ import {
toStateValue,
toTransitionConfigArray
} from './utils.ts';
import { ProcessingStatus } from './interpreter.ts';

type Configuration<
TContext extends MachineContext,
Expand Down Expand Up @@ -1541,7 +1541,7 @@ function resolveActionsAndContextWorker(
: undefined;

if (!('resolve' in resolvedAction)) {
if (actorScope?.self.status === ActorStatus.Running) {
if (actorScope?.self._processingStatus === ProcessingStatus.Running) {
resolvedAction(actionArgs, actionParams);
} else {
actorScope?.defer(() => {
Expand All @@ -1568,7 +1568,7 @@ function resolveActionsAndContextWorker(
}

if ('execute' in builtinAction) {
if (actorScope?.self.status === ActorStatus.Running) {
if (actorScope?.self._processingStatus === ProcessingStatus.Running) {
builtinAction.execute(actorScope!, params);
} else {
actorScope?.defer(
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { StateNode } from './StateNode.ts';
import type { State } from './State.ts';
import type { ActorStatus, Clock, Actor } from './interpreter.ts';
import type { Clock, Actor, ProcessingStatus } from './interpreter.ts';
import type { MachineSnapshot, StateMachine } from './StateMachine.ts';
import {
TypegenDisabled,
Expand Down Expand Up @@ -1792,7 +1792,8 @@ export interface ActorRef<
// TODO: figure out how to hide this externally as `sendTo(ctx => ctx.actorRef._parent._parent._parent._parent)` shouldn't be allowed
_parent?: ActorRef<any, any>;
system?: ActorSystem<any>;
status: ActorStatus;
/** @internal */
_processingStatus: ProcessingStatus;
src?: string;
}

Expand Down
44 changes: 33 additions & 11 deletions packages/core/test/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createMachine, fromCallback, fromPromise, createActor } from '../src';
import { ActorStatus } from '../src/interpreter';

const cleanups: (() => void)[] = [];
function installGlobalOnErrorHandler(handler: (ev: ErrorEvent) => void) {
Expand Down Expand Up @@ -50,6 +49,8 @@ describe('error handling', () => {
});

it(`doesn't crash the actor when an error is thrown in subscribe`, (done) => {
const spy = jest.fn();

const machine = createMachine({
id: 'machine',
initial: 'initial',
Expand All @@ -60,27 +61,37 @@ describe('error handling', () => {
initial: {
on: { activate: 'active' }
},
active: {}
active: {
on: {
do: {
actions: spy
}
}
}
}
});

const spy = jest.fn().mockImplementation(() => {
const subscriber = jest.fn().mockImplementation(() => {
throw new Error('doesnt_crash_actor_when_error_is_thrown_in_subscribe');
});

const actor = createActor(machine).start();

actor.subscribe(spy);
actor.subscribe(subscriber);
actor.send({ type: 'activate' });

expect(spy).toHaveBeenCalledTimes(1);
expect(actor.status).toEqual(ActorStatus.Running);
expect(subscriber).toHaveBeenCalledTimes(1);
expect(actor.getSnapshot().status).toEqual('active');

installGlobalOnErrorHandler((ev) => {
ev.preventDefault();
expect(ev.error.message).toEqual(
'doesnt_crash_actor_when_error_is_thrown_in_subscribe'
);

actor.send({ type: 'do' });
expect(spy).toHaveBeenCalledTimes(1);

done();
});
});
Expand Down Expand Up @@ -424,6 +435,8 @@ describe('error handling', () => {
});

it(`handled sync errors thrown when starting an actor shouldn't crash the parent`, () => {
const spy = jest.fn();

const machine = createMachine({
initial: 'pending',
states: {
Expand All @@ -435,14 +448,23 @@ describe('error handling', () => {
onError: 'failed'
}
},
failed: {}
failed: {
on: {
do: {
actions: spy
}
}
}
}
});

const actorRef = createActor(machine);
actorRef.start();

expect(actorRef.status).toBe(ActorStatus.Running);
expect(actorRef.getSnapshot().status).toBe('active');

actorRef.send({ type: 'do' });
expect(spy).toHaveBeenCalledTimes(1);
});

it(`unhandled sync errors thrown when starting an actor should crash the parent`, (done) => {
Expand All @@ -452,7 +474,7 @@ describe('error handling', () => {
pending: {
invoke: {
src: fromCallback(() => {
throw new Error('handled_sync_error_in_actor_start');
throw new Error('unhandled_sync_error_in_actor_start');
})
}
}
Expand All @@ -462,11 +484,11 @@ describe('error handling', () => {
const actorRef = createActor(machine);
actorRef.start();

expect(actorRef.status).not.toBe(ActorStatus.Running);
expect(actorRef.getSnapshot().status).toBe('error');

installGlobalOnErrorHandler((ev) => {
ev.preventDefault();
expect(ev.error.message).toEqual('handled_sync_error_in_actor_start');
expect(ev.error.message).toEqual('unhandled_sync_error_in_actor_start');
done();
});
});
Expand Down
4 changes: 2 additions & 2 deletions packages/core/test/interpreter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
sendParent,
StateValue,
createMachine,
ActorStatus,
ActorRefFrom,
ActorRef,
cancel,
Expand Down Expand Up @@ -1793,7 +1792,8 @@ describe('interpreter', () => {

const service = createActor(machine).start();

expect(service.status).toBe(ActorStatus.Stopped);
expect(service.getSnapshot().status).toBe('done');

service.subscribe({
complete: () => {
done();
Expand Down
Loading

0 comments on commit 9195ccf

Please sign in to comment.