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

toPromise(actorRef) #4198

Merged
merged 22 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
6 changes: 5 additions & 1 deletion packages/core/src/StateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,11 @@ export class StateMachine<
> {
return getPersistedState(state);
}

public getOutput(
state: State<TContext, TEvent, TActor, TOutput, TResolvedTypesMeta>
) {
return state.output;
}
public createState(
stateConfig:
| State<TContext, TEvent, TActor, TOutput, TResolvedTypesMeta>
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/actors/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export function fromCallback<TEvent extends EventObject, TInput>(
};
},
getSnapshot: () => undefined,
getPersistedState: ({ input, canceled }) => ({ input, canceled })
getPersistedState: ({ input, canceled }) => ({ input, canceled }),
getOutput: () => undefined
};
}
1 change: 1 addition & 0 deletions packages/core/src/actors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export function toActorRef<
},
status: ActorStatus.Running,
stop: () => void 0,
getOutput: () => undefined,
...actorRefLike
};
}
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/actors/observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export function fromObservable<T, TInput>(
input
}),
getStatus: (state) => state,
getOutput: (state) => (state.status === 'done' ? state.data : undefined),
restoreState: (state) => ({
...state,
subscription: undefined
Expand Down Expand Up @@ -234,6 +235,7 @@ export function fromEventObservable<T extends EventObject, TInput>(
restoreState: (state) => ({
...state,
subscription: undefined
})
}),
getOutput: () => undefined
};
}
1 change: 1 addition & 0 deletions packages/core/src/actors/promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export function fromPromise<T, TInput>(
getSnapshot: (state) => state.data,
getStatus: (state) => state,
getPersistedState: (state) => state,
getOutput: (state) => state.data,
restoreState: (state) => state
};

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/actors/transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function fromTransition<
},
getSnapshot: (state) => state,
getPersistedState: (state) => state,
restoreState: (state) => state
restoreState: (state) => state,
getOutput: () => undefined
};
}
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export {
fromTransition
} from './actors/index.ts';

export { toPromise } from './toPromise.ts';

export { stateIn, not, and, or } from './guards.ts';

declare global {
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import type {
EventFromLogic,
PersistedStateFrom,
SnapshotFrom,
AnyActorRef
AnyActorRef,
OutputFrom
} from './types.ts';
import {
ActorRef,
Expand Down Expand Up @@ -81,7 +82,7 @@ type InternalStateFrom<TLogic extends ActorLogic<any, any, any>> =
export class Actor<
TLogic extends AnyActorLogic,
TEvent extends EventObject = EventFromLogic<TLogic>
> implements ActorRef<TEvent, SnapshotFrom<TLogic>>
> implements ActorRef<TEvent, SnapshotFrom<TLogic>, OutputFrom<TLogic>>
{
/**
* The current internal state of the actor.
Expand Down Expand Up @@ -320,6 +321,10 @@ export class Actor<
return this;
}

public getOutput() {
return this.logic.getOutput?.(this._state);
}
Andarist marked this conversation as resolved.
Show resolved Hide resolved

private _process(event: TEvent) {
// TODO: reexamine what happens when an action (or a guard or smth) throws
let nextState;
Expand Down
20 changes: 20 additions & 0 deletions packages/core/src/toPromise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Actor, ActorRef, AnyActor, AnyActorRef, OutputFrom } from '.';

export function toPromise<T extends AnyActorRef>(
actor: T
): Promise<T extends Actor<infer TLogic> ? OutputFrom<TLogic> : unknown> {
return new Promise((resolve, reject) => {
actor.subscribe({
complete: () => {
resolve(
actor.getOutput()! as T extends Actor<infer TLogic>
? OutputFrom<TLogic>
: unknown
);
},
error: (err) => {
reject(err);
}
});
});
}
9 changes: 7 additions & 2 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1452,8 +1452,11 @@ export interface ActorLike<TCurrent, TEvent extends EventObject>
send: (event: TEvent) => void;
}

export interface ActorRef<TEvent extends EventObject, TSnapshot = any>
extends Subscribable<TSnapshot>,
export interface ActorRef<
TEvent extends EventObject,
TSnapshot = any,
TOutput = unknown
> extends Subscribable<TSnapshot>,
InteropObservable<TSnapshot> {
/**
* The unique identifier for this actor relative to its parent.
Expand All @@ -1466,6 +1469,7 @@ export interface ActorRef<TEvent extends EventObject, TSnapshot = any>
getSnapshot: () => TSnapshot;
// TODO: this should return some sort of TPersistedState, not any
getPersistedState?: () => any;
getOutput: () => TOutput | undefined;
stop: () => void;
toJSON?: () => any;
// TODO: figure out how to hide this externally as `sendTo(ctx => ctx.actorRef._parent._parent._parent._parent)` shouldn't be allowed
Expand Down Expand Up @@ -1632,6 +1636,7 @@ export interface ActorLogic<
) => TInternalState;
getSnapshot?: (state: TInternalState) => TSnapshot;
getStatus?: (state: TInternalState) => { status: string; data?: any };
getOutput: (state: TInternalState) => TOutput | undefined; // undefined if no output yet
start?: (
state: TInternalState,
actorCtx: ActorContext<TEvent, TSnapshot>
Expand Down
3 changes: 2 additions & 1 deletion packages/core/test/actor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,8 @@ describe('actors', () => {

return undefined;
},
getInitialState: () => undefined
getInitialState: () => undefined,
getOutput: () => undefined
};

const pingMachine = createMachine<{
Expand Down
6 changes: 4 additions & 2 deletions packages/core/test/invoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2257,7 +2257,8 @@ describe('invoke', () => {
}
return count;
},
getInitialState: () => 0
getInitialState: () => 0,
getOutput: () => undefined
};

const countMachine = createMachine({
Expand Down Expand Up @@ -2293,7 +2294,8 @@ describe('invoke', () => {

return undefined;
},
getInitialState: () => undefined
getInitialState: () => undefined,
getOutput: () => undefined
};

const pingMachine = createMachine({
Expand Down
76 changes: 76 additions & 0 deletions packages/core/test/toPromise.test.ts
davidkpiano marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
ActorRef,
createActor,
createMachine,
fromPromise,
toPromise
} from '../src';

describe('toPromise', () => {
it('should be awaitable', async () => {
const promiseActor = createActor(
fromPromise(() => Promise.resolve(42))
).start();

const result = await toPromise(promiseActor);

((_accept: number) => {})(result);
davidkpiano marked this conversation as resolved.
Show resolved Hide resolved

expect(result).toEqual(42);
});

it('should await actors', async () => {
const machine = createMachine({
types: {} as {
output: { count: 42 };
},
initial: 'pending',
states: {
pending: {
on: {
RESOLVE: 'done'
}
},
done: {
type: 'final',
output: { count: 42 }
}
}
});

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

setTimeout(() => {
actor.send({ type: 'RESOLVE' });
}, 1);

const data = await toPromise(actor);

((_accept: { count: number }) => {})(data);

expect(data).toEqual({ count: 42 });
});

it('should await already done actors', async () => {
const machine = createMachine({
types: {} as {
output: { count: 42 };
},
initial: 'done',
states: {
done: {
type: 'final',
output: { count: 42 }
}
}
});

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

const data = await toPromise(actor);

((_accept: { count: number }) => {})(data);

expect(data).toEqual({ count: 42 });
});
});
3 changes: 2 additions & 1 deletion packages/core/test/typeHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,8 @@ describe('ActorRefFrom', () => {
it('should return `ActorRef` based on actor logic', () => {
const logic: ActorLogic<{ type: 'TEST' }> = {
transition: () => {},
getInitialState: () => undefined
getInitialState: () => undefined,
getOutput: () => undefined
};

function acceptActorRef(actorRef: ActorRefFrom<typeof logic>) {
Expand Down
6 changes: 4 additions & 2 deletions packages/xstate-graph/test/graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,8 @@ it('simple paths for transition functions', () => {
}
return s;
},
getInitialState: () => 0
getInitialState: () => 0,
getOutput: () => undefined
},
{
events: [{ type: 'a' }, { type: 'b' }, { type: 'reset' }],
Expand All @@ -557,7 +558,8 @@ it('shortest paths for transition functions', () => {
}
return s;
},
getInitialState: () => 0
getInitialState: () => 0,
getOutput: () => undefined
},
{
events: [{ type: 'a' }, { type: 'b' }, { type: 'reset' }],
Expand Down
12 changes: 8 additions & 4 deletions packages/xstate-react/test/useSelector.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,8 @@ describeEachReactMode('useSelector (%s)', ({ suiteKey, render }) => {
return { unsubscribe: () => {} };
},
getSnapshot: () => latestValue,
getInitialState: () => latestValue
getInitialState: () => latestValue,
getOutput: () => undefined
});

const parentMachine = createMachine({
Expand Down Expand Up @@ -476,7 +477,8 @@ describeEachReactMode('useSelector (%s)', ({ suiteKey, render }) => {
return { unsubscribe: () => {} };
},
getSnapshot: () => latestValue,
getInitialState: () => latestValue
getInitialState: () => latestValue,
getOutput: () => undefined
});

const parentMachine = createMachine({
Expand Down Expand Up @@ -513,7 +515,8 @@ describeEachReactMode('useSelector (%s)', ({ suiteKey, render }) => {
return { unsubscribe: () => {} };
},
getSnapshot: () => latestValue,
getInitialState: () => latestValue
getInitialState: () => latestValue,
getOutput: () => undefined
});

const actor1 = createCustomActor('foo');
Expand Down Expand Up @@ -544,7 +547,8 @@ describeEachReactMode('useSelector (%s)', ({ suiteKey, render }) => {
return { unsubscribe: () => {} };
},
getSnapshot: () => undefined,
getInitialState: () => undefined
getInitialState: () => undefined,
getOutput: () => undefined
});

const App = ({ selector }: { selector: any }) => {
Expand Down
6 changes: 4 additions & 2 deletions packages/xstate-solid/test/useActor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const createSimpleActor = <T extends unknown>(value: T) =>
createActor({
transition: (s) => s,
getSnapshot: () => value,
getInitialState: () => value
getInitialState: () => value,
getOutput: () => undefined
});

describe('useActor', () => {
Expand Down Expand Up @@ -641,7 +642,8 @@ describe('useActor', () => {
const simpleActor = createActor({
transition: (s) => s,
getSnapshot: () => 42,
getInitialState: () => 42
getInitialState: () => 42,
getOutput: () => undefined
});

const Test = () => {
Expand Down
6 changes: 4 additions & 2 deletions packages/xstate-test/test/testModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ describe('custom test models', () => {
} else {
return value * 3 + 1;
}
}
},
getOutput: () => undefined
},
{
events: (state) => {
Expand Down Expand Up @@ -41,7 +42,8 @@ describe('custom test models', () => {
} else {
return value * 3 + 1;
}
}
},
getOutput: () => undefined
},
{
events: (state) => {
Expand Down