From 284ce324d8e80ca94279ca3deaf8f6e61d853659 Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Mon, 2 Dec 2024 10:16:54 -0800 Subject: [PATCH 1/2] Add snapshot update feature to promises --- packages/core/src/actors/promise.ts | 73 ++++++++++++++++++++------- packages/core/test/actorLogic.test.ts | 41 ++++++++++++++- 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/packages/core/src/actors/promise.ts b/packages/core/src/actors/promise.ts index ccc920bc1d..b2dcc3551d 100644 --- a/packages/core/src/actors/promise.ts +++ b/packages/core/src/actors/promise.ts @@ -5,24 +5,42 @@ import { ActorRefFromLogic, AnyActorRef, EventObject, + MachineContext, NonReducibleUnknown, - Snapshot + Snapshot, + StateValue } from '../types.ts'; -export type PromiseSnapshot = Snapshot & { - input: TInput | undefined; -}; +export interface PromiseState { + value?: StateValue; + context?: MachineContext; +} + +export type PromiseSnapshot< + TOutput, + TInput, + TPromiseState extends PromiseState +> = Snapshot & + TPromiseState & { + input: TInput | undefined; + }; const XSTATE_PROMISE_RESOLVE = 'xstate.promise.resolve'; const XSTATE_PROMISE_REJECT = 'xstate.promise.reject'; +const XSTATE_PROMISE_UPDATE = 'xstate.promise.update'; export type PromiseActorLogic< TOutput, TInput = unknown, - TEmitted extends EventObject = EventObject + TEmitted extends EventObject = EventObject, + TPromiseState extends PromiseState = {} > = ActorLogic< - PromiseSnapshot, - { type: string; [k: string]: unknown }, + PromiseSnapshot, + // | { type: string; [k: string]: unknown } + | { type: typeof XSTATE_PROMISE_RESOLVE; data: TOutput } + | { type: typeof XSTATE_PROMISE_REJECT; data: unknown } + | { type: typeof XSTATE_STOP } + | { type: typeof XSTATE_PROMISE_UPDATE; state: TPromiseState }, TInput, // input AnyActorSystem, TEmitted // TEmitted @@ -61,9 +79,10 @@ export type PromiseActorLogic< * * @see {@link fromPromise} */ -export type PromiseActorRef = ActorRefFromLogic< - PromiseActorLogic ->; +export type PromiseActorRef< + TOutput, + TPromiseState extends PromiseState = {} +> = ActorRefFromLogic>; const controllerMap = new WeakMap(); @@ -120,26 +139,29 @@ const controllerMap = new WeakMap(); export function fromPromise< TOutput, TInput = NonReducibleUnknown, - TEmitted extends EventObject = EventObject + TEmitted extends EventObject = EventObject, + TPromiseState extends PromiseState = {} >( promiseCreator: ({ input, system, self, signal, - emit + emit, + update }: { /** Data that was provided to the promise actor */ input: TInput; /** The actor system to which the promise actor belongs */ system: AnyActorSystem; /** The parent actor of the promise actor */ - self: PromiseActorRef; + self: PromiseActorRef; signal: AbortSignal; emit: (emitted: TEmitted) => void; + update: (state: TPromiseState) => void; }) => PromiseLike -): PromiseActorLogic { - const logic: PromiseActorLogic = { +): PromiseActorLogic { + const logic: PromiseActorLogic = { config: promiseCreator, transition: (state, event, scope) => { if (state.status !== 'active') { @@ -148,7 +170,7 @@ export function fromPromise< switch (event.type) { case XSTATE_PROMISE_RESOLVE: { - const resolvedValue = (event as any).data; + const resolvedValue = event.data; return { ...state, status: 'done', @@ -160,7 +182,7 @@ export function fromPromise< return { ...state, status: 'error', - error: (event as any).data, + error: event.data, input: undefined }; case XSTATE_STOP: { @@ -171,6 +193,16 @@ export function fromPromise< input: undefined }; } + case XSTATE_PROMISE_UPDATE: { + const { + state: { context, value } + } = event; + return { + ...state, + context, + value + }; + } default: return state; } @@ -189,7 +221,12 @@ export function fromPromise< system, self, signal: controller.signal, - emit + emit, + update: (state) => + self.send({ + type: XSTATE_PROMISE_UPDATE, + state + }) }) ); diff --git a/packages/core/test/actorLogic.test.ts b/packages/core/test/actorLogic.test.ts index 7f37a7f145..8b6109c62f 100644 --- a/packages/core/test/actorLogic.test.ts +++ b/packages/core/test/actorLogic.test.ts @@ -6,7 +6,8 @@ import { createActor, AnyActorLogic, Snapshot, - ActorLogic + ActorLogic, + toPromise } from '../src/index.ts'; import { fromCallback, @@ -464,6 +465,44 @@ describe('promise logic (fromPromise)', () => { const fn2 = signalListenerList[1]; expect(fn2).toHaveBeenCalled(); }); + + it('can emit updates', async () => { + const p = fromPromise(async ({ update }) => { + update({ value: 'starting' }); + update({ value: 'loading', context: { progress: 0.6 } }); + update({ value: 'finished' }); + return 'done data'; + }); + + const stuff: Array<{}> = []; + + const actor = createActor(p); + actor.subscribe((s) => { + stuff.push(s); + }); + actor.start(); + + const res = await toPromise(actor); + + expect(res).toEqual('done data'); + + expect(stuff).toEqual( + expect.arrayContaining([ + expect.objectContaining({}), + expect.objectContaining({ value: 'starting', context: undefined }), + expect.objectContaining({ + value: 'loading', + context: { progress: 0.6 } + }), + expect.objectContaining({ value: 'finished', context: undefined }), + expect.objectContaining({ + status: 'done', + output: 'done data', + value: 'finished' + }) + ]) + ); + }); }); describe('transition function logic (fromTransition)', () => { From 6a17ed61754219f803f6448e81d28448ac862ba4 Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Thu, 5 Dec 2024 01:32:36 +1100 Subject: [PATCH 2/2] Update packages/core/src/actors/promise.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mateusz BurzyƄski --- packages/core/src/actors/promise.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/actors/promise.ts b/packages/core/src/actors/promise.ts index b2dcc3551d..0dd88efc56 100644 --- a/packages/core/src/actors/promise.ts +++ b/packages/core/src/actors/promise.ts @@ -36,7 +36,6 @@ export type PromiseActorLogic< TPromiseState extends PromiseState = {} > = ActorLogic< PromiseSnapshot, - // | { type: string; [k: string]: unknown } | { type: typeof XSTATE_PROMISE_RESOLVE; data: TOutput } | { type: typeof XSTATE_PROMISE_REJECT; data: unknown } | { type: typeof XSTATE_STOP }