From 4b6864767efe8cefbdc1e42cec83abf8c70f369c Mon Sep 17 00:00:00 2001 From: "JH.Lee" Date: Mon, 10 Mar 2025 19:03:37 +0900 Subject: [PATCH 1/6] PoC --- .../findTargetActivityIndices.ts | 24 +++++ .../src/activity-utils/makeActivityReducer.ts | 23 +++- core/src/activity-utils/makeStackReducer.ts | 30 +++--- core/src/aggregate.spec.ts | 100 +++++++++++++++++- .../react/src/__internal__/suspensePlugin.tsx | 30 ++++++ .../react/src/future/loader/loaderPlugin.tsx | 41 +------ integrations/react/src/future/stackflow.tsx | 6 +- 7 files changed, 195 insertions(+), 59 deletions(-) create mode 100644 integrations/react/src/__internal__/suspensePlugin.tsx diff --git a/core/src/activity-utils/findTargetActivityIndices.ts b/core/src/activity-utils/findTargetActivityIndices.ts index 52e277693..a4ac16d48 100644 --- a/core/src/activity-utils/findTargetActivityIndices.ts +++ b/core/src/activity-utils/findTargetActivityIndices.ts @@ -105,6 +105,30 @@ export function findTargetActivityIndices( break; } + case "Paused": { + const affectedActivities = activities.filter( + (activity) => + (activity.transitionState === "enter-active" || + activity.transitionState === "enter-done") && + event.eventDate - activity.enteredBy.eventDate <= + context.transitionDuration, + ); + + targetActivities.push( + ...affectedActivities.map((activity) => activities.indexOf(activity)), + ); + break; + } + case "Resumed": { + const affectedActivities = activities.filter( + (activity) => activity.transitionState === "enter-active", + ); + + targetActivities.push( + ...affectedActivities.map((activity) => activities.indexOf(activity)), + ); + break; + } default: break; } diff --git a/core/src/activity-utils/makeActivityReducer.ts b/core/src/activity-utils/makeActivityReducer.ts index 11a96a3d0..0c225fb10 100644 --- a/core/src/activity-utils/makeActivityReducer.ts +++ b/core/src/activity-utils/makeActivityReducer.ts @@ -1,8 +1,10 @@ import type { Activity, ActivityTransitionState } from "../Stack"; import type { DomainEvent, + PausedEvent, PoppedEvent, ReplacedEvent, + ResumedEvent, StepPoppedEvent, StepPushedEvent, StepReplacedEvent, @@ -124,7 +126,24 @@ export function makeActivityReducer(context: { Initialized: noop, ActivityRegistered: noop, Pushed: noop, - Paused: noop, - Resumed: noop, + Paused: (activity: Activity, event: PausedEvent): Activity => { + return { + ...activity, + transitionState: "enter-active", + }; + }, + Resumed: (activity: Activity, event: ResumedEvent): Activity => { + const isTransitionDone = + context.now - event.eventDate >= context.transitionDuration; + + const transitionState: ActivityTransitionState = isTransitionDone + ? "enter-done" + : "enter-active"; + + return { + ...activity, + transitionState, + }; + }, } as const); } diff --git a/core/src/activity-utils/makeStackReducer.ts b/core/src/activity-utils/makeStackReducer.ts index dd957e78c..5b32f0aa0 100644 --- a/core/src/activity-utils/makeStackReducer.ts +++ b/core/src/activity-utils/makeStackReducer.ts @@ -35,7 +35,7 @@ function withActivitiesReducer( resumedAt?: number; }, ) { - return (stack: Stack, event: T) => { + return (stack: Stack, event: T): Stack => { const activitiesReducer = makeActivitiesReducer({ transitionDuration: stack.transitionDuration, now: context.now, @@ -63,20 +63,21 @@ function withActivitiesReducer( ); } - const isLoading = activities.find( - (activity) => - activity.transitionState === "enter-active" || - activity.transitionState === "exit-active", - ); - - const globalTransitionState = - stack.globalTransitionState === "paused" - ? "paused" - : isLoading - ? "loading" - : "idle"; + const finalizedStack = reducer({ ...stack, activities }, event); - return reducer({ ...stack, activities, globalTransitionState }, event); + return { + ...finalizedStack, + globalTransitionState: + finalizedStack.globalTransitionState === "paused" + ? "paused" + : finalizedStack.activities.find( + (activity) => + activity.transitionState === "enter-active" || + activity.transitionState === "exit-active", + ) + ? "loading" + : "idle", + }; }; } @@ -123,6 +124,7 @@ export function makeStackReducer(context: { return { ...stack, globalTransitionState: "paused", + pausedEvents: stack.pausedEvents ?? [], }; }, context), ), diff --git a/core/src/aggregate.spec.ts b/core/src/aggregate.spec.ts index 466971d82..ac56b9a28 100644 --- a/core/src/aggregate.spec.ts +++ b/core/src/aggregate.spec.ts @@ -4092,6 +4092,8 @@ test("aggregate - Resumed 되면 해당 시간 이후로 Transition이 정상작 let pushedEvent1: PushedEvent; let pushedEvent2: PushedEvent; + const t = nowTime(); + const events = [ initializedEvent({ transitionDuration: 300, @@ -4109,20 +4111,112 @@ test("aggregate - Resumed 되면 해당 시간 이후로 Transition이 정상작 activityParams: {}, })), makeEvent("Paused", { - eventDate: enoughPastTime(), + eventDate: t - 500, }), (pushedEvent2 = makeEvent("Pushed", { activityId: "activity-2", activityName: "b", + eventDate: t - 100, + activityParams: {}, + })), + makeEvent("Resumed", { + eventDate: t, + }), + ]; + + const output = aggregate(events, t); + + expect(output).toStrictEqual({ + activities: [ + activity({ + id: "activity-1", + name: "a", + transitionState: "enter-done", + params: {}, + steps: [ + { + id: "activity-1", + params: {}, + enteredBy: expect.anything(), + zIndex: 0, + }, + ], + enteredBy: expect.anything(), + isActive: false, + isTop: false, + isRoot: true, + zIndex: 0, + }), + activity({ + id: "activity-2", + name: "b", + transitionState: "enter-active", + params: {}, + steps: [ + { + id: "activity-2", + params: {}, + enteredBy: expect.anything(), + zIndex: 1, + }, + ], + enteredBy: expect.anything(), + isActive: true, + isTop: true, + isRoot: false, + zIndex: 1, + }), + ], + registeredActivities: [ + { + name: "a", + }, + { + name: "b", + }, + ], + transitionDuration: 300, + globalTransitionState: "loading", + }); +}); + +test("aggregate - PausedEvent makes active activities paused", () => { + let pushedEvent1: PushedEvent; + let pushedEvent2: PushedEvent; + + const t = nowTime(); + + const events = [ + initializedEvent({ + transitionDuration: 300, + }), + registeredEvent({ + activityName: "a", + }), + registeredEvent({ + activityName: "b", + }), + (pushedEvent1 = makeEvent("Pushed", { + activityId: "activity-1", + activityName: "a", eventDate: enoughPastTime(), activityParams: {}, })), + (pushedEvent2 = makeEvent("Pushed", { + activityId: "activity-2", + activityName: "b", + eventDate: t - 400, + activityParams: {}, + })), + makeEvent("Paused", { + eventDate: t - 200, + }), makeEvent("Resumed", { - eventDate: nowTime() - 150, + eventDate: t, }), ]; - const output = aggregate(events, nowTime()); + const output = aggregate(events, t); expect(output).toStrictEqual({ activities: [ diff --git a/integrations/react/src/__internal__/suspensePlugin.tsx b/integrations/react/src/__internal__/suspensePlugin.tsx new file mode 100644 index 000000000..9fbf30f0e --- /dev/null +++ b/integrations/react/src/__internal__/suspensePlugin.tsx @@ -0,0 +1,30 @@ +import { Suspense, useEffect } from "react"; +import type { StackflowReactPlugin } from "./StackflowReactPlugin"; +import { useCoreActions } from "./core"; + +export function suspensePlugin(): StackflowReactPlugin { + return () => ({ + key: "plugin-suspense", + wrapActivity: ({ activity }) => { + return ( + }>{activity.render()} + ); + }, + }); +} + +export function SuspenseFallback() { + const { pause, resume } = useCoreActions(); + + useEffect(() => { + console.log("lets pause"); + pause(); + + return () => { + console.log("lets resume"); + resume(); + }; + }, []); + + return null; +} diff --git a/integrations/react/src/future/loader/loaderPlugin.tsx b/integrations/react/src/future/loader/loaderPlugin.tsx index 744768db8..703e8902a 100644 --- a/integrations/react/src/future/loader/loaderPlugin.tsx +++ b/integrations/react/src/future/loader/loaderPlugin.tsx @@ -84,10 +84,7 @@ function createBeforeRouteHandler< [activityName in RegisteredActivityName]: ActivityComponentType; }, >(input: StackflowInput): OnBeforeRoute { - return ({ - actionParams, - actions: { overrideActionParams, pause, resume }, - }) => { + return ({ actionParams, actions: { overrideActionParams } }) => { const { activityName, activityParams, activityContext } = actionParams; const matchActivity = input.config.activities.find( @@ -106,28 +103,15 @@ function createBeforeRouteHandler< const loaderDataPromise = loaderData instanceof Promise ? loaderData : undefined; - const lazyComponentPromise = - "_load" in matchActivityComponent - ? matchActivityComponent._load?.() - : undefined; - if (loaderDataPromise || lazyComponentPromise) { - pause(); - } - Promise.allSettled([loaderDataPromise, lazyComponentPromise]) - .then(([loaderDataPromiseResult, lazyComponentPromiseResult]) => { + Promise.allSettled([loaderDataPromise]).then( + ([loaderDataPromiseResult]) => { printLoaderDataPromiseError({ promiseResult: loaderDataPromiseResult, activityName: matchActivity.name, }); - printLazyComponentPromiseError({ - promiseResult: lazyComponentPromiseResult, - activityName: matchActivity.name, - }); - }) - .finally(() => { - resume(); - }); + }, + ); overrideActionParams({ ...actionParams, @@ -153,18 +137,3 @@ function printLoaderDataPromiseError({ ); } } - -function printLazyComponentPromiseError({ - promiseResult, - activityName, -}: { - promiseResult: PromiseSettledResult; - activityName: string; -}) { - if (promiseResult.status === "rejected") { - console.error(promiseResult.reason); - console.error( - `The above error occurred while loading a lazy react component of the "${activityName}" activity`, - ); - } -} diff --git a/integrations/react/src/future/stackflow.tsx b/integrations/react/src/future/stackflow.tsx index a5807619d..89759c293 100644 --- a/integrations/react/src/future/stackflow.tsx +++ b/integrations/react/src/future/stackflow.tsx @@ -16,6 +16,7 @@ import MainRenderer from "../__internal__/MainRenderer"; import { makeActivityId } from "../__internal__/activity"; import { CoreProvider } from "../__internal__/core"; import { PluginsProvider } from "../__internal__/plugins"; +import { suspensePlugin } from "../__internal__/suspensePlugin"; import { isBrowser, makeRef } from "../__internal__/utils"; import type { StackflowReactPlugin } from "../stable"; import type { Actions } from "./Actions"; @@ -57,11 +58,8 @@ export function stackflow< ...(input.plugins ?? []) .flat(Number.POSITIVE_INFINITY as 0) .map((p) => p as StackflowReactPlugin), - - /** - * `loaderPlugin()` must be placed after `historySyncPlugin()` - */ loaderPlugin(input), + suspensePlugin(), ]; const enoughPastTime = () => From 05dcf2a8a924bd9f07032fecd6bb14188a122704 Mon Sep 17 00:00:00 2001 From: "JH.Lee" Date: Tue, 11 Mar 2025 10:13:21 +0900 Subject: [PATCH 2/6] feat: revert poc --- .../findTargetActivityIndices.ts | 24 ----- .../src/activity-utils/makeActivityReducer.ts | 23 +--- core/src/activity-utils/makeStackReducer.ts | 29 +++-- core/src/aggregate.spec.ts | 100 +----------------- 4 files changed, 19 insertions(+), 157 deletions(-) diff --git a/core/src/activity-utils/findTargetActivityIndices.ts b/core/src/activity-utils/findTargetActivityIndices.ts index a4ac16d48..52e277693 100644 --- a/core/src/activity-utils/findTargetActivityIndices.ts +++ b/core/src/activity-utils/findTargetActivityIndices.ts @@ -105,30 +105,6 @@ export function findTargetActivityIndices( break; } - case "Paused": { - const affectedActivities = activities.filter( - (activity) => - (activity.transitionState === "enter-active" || - activity.transitionState === "enter-done") && - event.eventDate - activity.enteredBy.eventDate <= - context.transitionDuration, - ); - - targetActivities.push( - ...affectedActivities.map((activity) => activities.indexOf(activity)), - ); - break; - } - case "Resumed": { - const affectedActivities = activities.filter( - (activity) => activity.transitionState === "enter-active", - ); - - targetActivities.push( - ...affectedActivities.map((activity) => activities.indexOf(activity)), - ); - break; - } default: break; } diff --git a/core/src/activity-utils/makeActivityReducer.ts b/core/src/activity-utils/makeActivityReducer.ts index 0c225fb10..11a96a3d0 100644 --- a/core/src/activity-utils/makeActivityReducer.ts +++ b/core/src/activity-utils/makeActivityReducer.ts @@ -1,10 +1,8 @@ import type { Activity, ActivityTransitionState } from "../Stack"; import type { DomainEvent, - PausedEvent, PoppedEvent, ReplacedEvent, - ResumedEvent, StepPoppedEvent, StepPushedEvent, StepReplacedEvent, @@ -126,24 +124,7 @@ export function makeActivityReducer(context: { Initialized: noop, ActivityRegistered: noop, Pushed: noop, - Paused: (activity: Activity, event: PausedEvent): Activity => { - return { - ...activity, - transitionState: "enter-active", - }; - }, - Resumed: (activity: Activity, event: ResumedEvent): Activity => { - const isTransitionDone = - context.now - event.eventDate >= context.transitionDuration; - - const transitionState: ActivityTransitionState = isTransitionDone - ? "enter-done" - : "enter-active"; - - return { - ...activity, - transitionState, - }; - }, + Paused: noop, + Resumed: noop, } as const); } diff --git a/core/src/activity-utils/makeStackReducer.ts b/core/src/activity-utils/makeStackReducer.ts index 5b32f0aa0..930eb698d 100644 --- a/core/src/activity-utils/makeStackReducer.ts +++ b/core/src/activity-utils/makeStackReducer.ts @@ -35,7 +35,7 @@ function withActivitiesReducer( resumedAt?: number; }, ) { - return (stack: Stack, event: T): Stack => { + return (stack: Stack, event: T) => { const activitiesReducer = makeActivitiesReducer({ transitionDuration: stack.transitionDuration, now: context.now, @@ -63,21 +63,20 @@ function withActivitiesReducer( ); } - const finalizedStack = reducer({ ...stack, activities }, event); + const isLoading = activities.find( + (activity) => + activity.transitionState === "enter-active" || + activity.transitionState === "exit-active", + ); + + const globalTransitionState = + stack.globalTransitionState === "paused" + ? "paused" + : isLoading + ? "loading" + : "idle"; - return { - ...finalizedStack, - globalTransitionState: - finalizedStack.globalTransitionState === "paused" - ? "paused" - : finalizedStack.activities.find( - (activity) => - activity.transitionState === "enter-active" || - activity.transitionState === "exit-active", - ) - ? "loading" - : "idle", - }; + return reducer({ ...stack, activities, globalTransitionState }, event); }; } diff --git a/core/src/aggregate.spec.ts b/core/src/aggregate.spec.ts index ac56b9a28..466971d82 100644 --- a/core/src/aggregate.spec.ts +++ b/core/src/aggregate.spec.ts @@ -4092,8 +4092,6 @@ test("aggregate - Resumed 되면 해당 시간 이후로 Transition이 정상작 let pushedEvent1: PushedEvent; let pushedEvent2: PushedEvent; - const t = nowTime(); - const events = [ initializedEvent({ transitionDuration: 300, @@ -4111,112 +4109,20 @@ test("aggregate - Resumed 되면 해당 시간 이후로 Transition이 정상작 activityParams: {}, })), makeEvent("Paused", { - eventDate: t - 500, + eventDate: enoughPastTime(), }), (pushedEvent2 = makeEvent("Pushed", { activityId: "activity-2", activityName: "b", - eventDate: t - 100, - activityParams: {}, - })), - makeEvent("Resumed", { - eventDate: t, - }), - ]; - - const output = aggregate(events, t); - - expect(output).toStrictEqual({ - activities: [ - activity({ - id: "activity-1", - name: "a", - transitionState: "enter-done", - params: {}, - steps: [ - { - id: "activity-1", - params: {}, - enteredBy: expect.anything(), - zIndex: 0, - }, - ], - enteredBy: expect.anything(), - isActive: false, - isTop: false, - isRoot: true, - zIndex: 0, - }), - activity({ - id: "activity-2", - name: "b", - transitionState: "enter-active", - params: {}, - steps: [ - { - id: "activity-2", - params: {}, - enteredBy: expect.anything(), - zIndex: 1, - }, - ], - enteredBy: expect.anything(), - isActive: true, - isTop: true, - isRoot: false, - zIndex: 1, - }), - ], - registeredActivities: [ - { - name: "a", - }, - { - name: "b", - }, - ], - transitionDuration: 300, - globalTransitionState: "loading", - }); -}); - -test("aggregate - PausedEvent makes active activities paused", () => { - let pushedEvent1: PushedEvent; - let pushedEvent2: PushedEvent; - - const t = nowTime(); - - const events = [ - initializedEvent({ - transitionDuration: 300, - }), - registeredEvent({ - activityName: "a", - }), - registeredEvent({ - activityName: "b", - }), - (pushedEvent1 = makeEvent("Pushed", { - activityId: "activity-1", - activityName: "a", eventDate: enoughPastTime(), activityParams: {}, })), - (pushedEvent2 = makeEvent("Pushed", { - activityId: "activity-2", - activityName: "b", - eventDate: t - 400, - activityParams: {}, - })), - makeEvent("Paused", { - eventDate: t - 200, - }), makeEvent("Resumed", { - eventDate: t, + eventDate: nowTime() - 150, }), ]; - const output = aggregate(events, t); + const output = aggregate(events, nowTime()); expect(output).toStrictEqual({ activities: [ From f9f3f4e16357b8981a1e1de22bc8be436eb36b40 Mon Sep 17 00:00:00 2001 From: "JH.Lee" Date: Tue, 11 Mar 2025 10:23:26 +0900 Subject: [PATCH 3/6] fix: revalidate global transition state --- core/src/activity-utils/makeStackReducer.ts | 44 +++++++++++++++------ 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/core/src/activity-utils/makeStackReducer.ts b/core/src/activity-utils/makeStackReducer.ts index 930eb698d..4b51a3d2b 100644 --- a/core/src/activity-utils/makeStackReducer.ts +++ b/core/src/activity-utils/makeStackReducer.ts @@ -11,6 +11,23 @@ import { makeActivitiesReducer } from "./makeActivitiesReducer"; import { makeActivityReducer } from "./makeActivityReducer"; import { makeReducer } from "./makeReducer"; +function calculateGlobalTransitionState( + activities: Activity[], + currentState: Stack["globalTransitionState"], +): Stack["globalTransitionState"] { + if (currentState === "paused") { + return "paused"; + } + + const hasActiveTransition = activities.some( + (activity) => + activity.transitionState === "enter-active" || + activity.transitionState === "exit-active", + ); + + return hasActiveTransition ? "loading" : "idle"; +} + function withPauseReducer( reducer: (stack: Stack, event: T) => Stack, ) { @@ -63,20 +80,25 @@ function withActivitiesReducer( ); } - const isLoading = activities.find( - (activity) => - activity.transitionState === "enter-active" || - activity.transitionState === "exit-active", + const globalTransitionState = calculateGlobalTransitionState( + activities, + stack.globalTransitionState, ); - const globalTransitionState = - stack.globalTransitionState === "paused" - ? "paused" - : isLoading - ? "loading" - : "idle"; + const result = reducer( + { ...stack, activities, globalTransitionState }, + event, + ); + + const updatedGlobalTransitionState = calculateGlobalTransitionState( + result.activities, + result.globalTransitionState, + ); - return reducer({ ...stack, activities, globalTransitionState }, event); + return { + ...result, + globalTransitionState: updatedGlobalTransitionState, + }; }; } From 05a70d88db03fc38cbbe960296cadc94ed2f4ed9 Mon Sep 17 00:00:00 2001 From: "JH.Lee" Date: Tue, 11 Mar 2025 11:21:46 +0900 Subject: [PATCH 4/6] test: add new spec --- core/src/aggregate.spec.ts | 72 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/core/src/aggregate.spec.ts b/core/src/aggregate.spec.ts index 466971d82..6073e1581 100644 --- a/core/src/aggregate.spec.ts +++ b/core/src/aggregate.spec.ts @@ -4088,6 +4088,78 @@ test("aggregate - Pause되면 이벤트가 반영되지 않고, globalTransition }); }); +test("aggregate - PausedEvent inserts source activity entering event into pausedEvents", () => { + let pushedEvent1: PushedEvent; + let pushedEvent2: PushedEvent; + + const t = nowTime(); + + const events = [ + initializedEvent({ + transitionDuration: 300, + }), + registeredEvent({ + activityName: "a", + }), + registeredEvent({ + activityName: "b", + }), + (pushedEvent1 = makeEvent("Pushed", { + activityId: "activity-1", + activityName: "a", + eventDate: enoughPastTime(), + activityParams: {}, + })), + (pushedEvent2 = makeEvent("Pushed", { + activityId: "activity-2", + activityName: "b", + activityParams: {}, + eventDate: enoughPastTime(), + })), + makeEvent("Paused", { + eventDate: t - 150, + sourceActivityId: "activity-2", + }), + ]; + + const output = aggregate(events, t); + + expect(output).toStrictEqual({ + activities: [ + activity({ + id: "activity-1", + name: "a", + transitionState: "enter-done", + params: {}, + steps: [ + { + id: "activity-1", + params: {}, + enteredBy: pushedEvent1, + zIndex: 0, + }, + ], + enteredBy: pushedEvent1, + isActive: true, + isTop: true, + isRoot: true, + zIndex: 0, + }), + ], + registeredActivities: [ + { + name: "a", + }, + { + name: "b", + }, + ], + transitionDuration: 300, + globalTransitionState: "paused", + pausedEvents: [pushedEvent2], + }); +}); + test("aggregate - Resumed 되면 해당 시간 이후로 Transition이 정상작동합니다", () => { let pushedEvent1: PushedEvent; let pushedEvent2: PushedEvent; From d884ebcbb7bafa0392dbb9b2c87c6063fe2728f4 Mon Sep 17 00:00:00 2001 From: "JH.Lee" Date: Wed, 12 Mar 2025 16:59:50 +0900 Subject: [PATCH 5/6] wip --- core/src/Stack.ts | 3 ++ .../findTargetActivityIndices.ts | 11 +++++++ .../src/activity-utils/makeActivityReducer.ts | 31 +++++++++++++++++-- core/src/event-types/PausedEvent.ts | 7 ++++- core/src/event-types/ResumedEvent.ts | 7 ++++- 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/core/src/Stack.ts b/core/src/Stack.ts index 3dbb5eea1..1e80b1024 100644 --- a/core/src/Stack.ts +++ b/core/src/Stack.ts @@ -1,9 +1,11 @@ import type { BaseDomainEvent } from "event-types/_base"; import type { DomainEvent, + PausedEvent, PoppedEvent, PushedEvent, ReplacedEvent, + ResumedEvent, StepPoppedEvent, StepPushedEvent, StepReplacedEvent, @@ -35,6 +37,7 @@ export type Activity = { context?: {}; enteredBy: PushedEvent | ReplacedEvent; exitedBy?: ReplacedEvent | PoppedEvent; + pausedBy?: PausedEvent; steps: ActivityStep[]; isTop: boolean; isActive: boolean; diff --git a/core/src/activity-utils/findTargetActivityIndices.ts b/core/src/activity-utils/findTargetActivityIndices.ts index 52e277693..77ca85ef6 100644 --- a/core/src/activity-utils/findTargetActivityIndices.ts +++ b/core/src/activity-utils/findTargetActivityIndices.ts @@ -105,6 +105,17 @@ export function findTargetActivityIndices( break; } + case "Resumed": + case "Paused": { + const activity = activities.find( + (activity) => activity.id === event.activityId, + ); + + if (activity) { + targetActivities.push(activities.indexOf(activity)); + } + break; + } default: break; } diff --git a/core/src/activity-utils/makeActivityReducer.ts b/core/src/activity-utils/makeActivityReducer.ts index 11a96a3d0..b74da7392 100644 --- a/core/src/activity-utils/makeActivityReducer.ts +++ b/core/src/activity-utils/makeActivityReducer.ts @@ -1,8 +1,10 @@ import type { Activity, ActivityTransitionState } from "../Stack"; import type { DomainEvent, + PausedEvent, PoppedEvent, ReplacedEvent, + ResumedEvent, StepPoppedEvent, StepPushedEvent, StepReplacedEvent, @@ -124,7 +126,32 @@ export function makeActivityReducer(context: { Initialized: noop, ActivityRegistered: noop, Pushed: noop, - Paused: noop, - Resumed: noop, + Paused: (activity: Activity, event: PausedEvent): Activity => { + if (activity.exitedBy || activity.pausedBy) { + return activity; + } + + return { + ...activity, + pausedBy: event, + transitionState: "enter-active", + }; + }, + Resumed: (activity: Activity, event: ResumedEvent): Activity => { + if (activity.exitedBy || activity.pausedBy) { + return activity; + } + + const { pausedBy, ...rest } = activity; + + const isTransitionDone = + context.now - (context.resumedAt ?? event.eventDate) >= + context.transitionDuration; + + return { + ...rest, + transitionState: isTransitionDone ? "enter-done" : "enter-active", + }; + }, } as const); } diff --git a/core/src/event-types/PausedEvent.ts b/core/src/event-types/PausedEvent.ts index af6c1e9ca..cd8761f35 100644 --- a/core/src/event-types/PausedEvent.ts +++ b/core/src/event-types/PausedEvent.ts @@ -1,3 +1,8 @@ import type { BaseDomainEvent } from "./_base"; -export type PausedEvent = BaseDomainEvent<"Paused", {}>; +export type PausedEvent = BaseDomainEvent< + "Paused", + { + activityId: string; + } +>; diff --git a/core/src/event-types/ResumedEvent.ts b/core/src/event-types/ResumedEvent.ts index a19839e92..566337cf4 100644 --- a/core/src/event-types/ResumedEvent.ts +++ b/core/src/event-types/ResumedEvent.ts @@ -1,3 +1,8 @@ import type { BaseDomainEvent } from "./_base"; -export type ResumedEvent = BaseDomainEvent<"Resumed", {}>; +export type ResumedEvent = BaseDomainEvent< + "Resumed", + { + activityId: string; + } +>; From 2239f4de1ed2f2aca3540507d450c0bf914cc185 Mon Sep 17 00:00:00 2001 From: "JH.Lee" Date: Fri, 14 Mar 2025 10:11:20 +0900 Subject: [PATCH 6/6] wip --- core/src/Stack.ts | 2 +- core/src/activity-utils/makeActivityReducer.ts | 10 ++++------ core/src/aggregate.spec.ts | 4 +++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/Stack.ts b/core/src/Stack.ts index 1e80b1024..972f1f0de 100644 --- a/core/src/Stack.ts +++ b/core/src/Stack.ts @@ -37,7 +37,7 @@ export type Activity = { context?: {}; enteredBy: PushedEvent | ReplacedEvent; exitedBy?: ReplacedEvent | PoppedEvent; - pausedBy?: PausedEvent; + resumedBy?: ResumedEvent; steps: ActivityStep[]; isTop: boolean; isActive: boolean; diff --git a/core/src/activity-utils/makeActivityReducer.ts b/core/src/activity-utils/makeActivityReducer.ts index b74da7392..a6524137b 100644 --- a/core/src/activity-utils/makeActivityReducer.ts +++ b/core/src/activity-utils/makeActivityReducer.ts @@ -127,30 +127,28 @@ export function makeActivityReducer(context: { ActivityRegistered: noop, Pushed: noop, Paused: (activity: Activity, event: PausedEvent): Activity => { - if (activity.exitedBy || activity.pausedBy) { + if (activity.exitedBy || activity.resumedBy) { return activity; } return { ...activity, - pausedBy: event, transitionState: "enter-active", }; }, Resumed: (activity: Activity, event: ResumedEvent): Activity => { - if (activity.exitedBy || activity.pausedBy) { + if (activity.exitedBy || activity.resumedBy) { return activity; } - const { pausedBy, ...rest } = activity; - const isTransitionDone = context.now - (context.resumedAt ?? event.eventDate) >= context.transitionDuration; return { - ...rest, + ...activity, transitionState: isTransitionDone ? "enter-done" : "enter-active", + resumedBy: event, }; }, } as const); diff --git a/core/src/aggregate.spec.ts b/core/src/aggregate.spec.ts index 6073e1581..3b16493ed 100644 --- a/core/src/aggregate.spec.ts +++ b/core/src/aggregate.spec.ts @@ -4118,7 +4118,7 @@ test("aggregate - PausedEvent inserts source activity entering event into paused })), makeEvent("Paused", { eventDate: t - 150, - sourceActivityId: "activity-2", + activityId: "activity-2", }), ]; @@ -4181,6 +4181,7 @@ test("aggregate - Resumed 되면 해당 시간 이후로 Transition이 정상작 activityParams: {}, })), makeEvent("Paused", { + activityId: "activity-1", eventDate: enoughPastTime(), }), (pushedEvent2 = makeEvent("Pushed", { @@ -4190,6 +4191,7 @@ test("aggregate - Resumed 되면 해당 시간 이후로 Transition이 정상작 activityParams: {}, })), makeEvent("Resumed", { + activityId: "activity-1", eventDate: nowTime() - 150, }), ];