Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed Interpreter['status'] from publicly available properties #4423

Merged
merged 2 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading