statelyai / xstate

Actor-based state management & orchestration for complex app logic.
https://stately.ai/docs
MIT License
26.98k stars 1.24k forks source link

Bug: system.get(id) not working after restoring a snapshot #4873

Open al66 opened 5 months ago

al66 commented 5 months ago

XState version

XState version 5

Description

After restoring the snapshot an error appears, when using actor.send with the reference determined by system.get(id).

TypeError: this.logic.transition is not a function

This error appaers only after serializing the snapshot!

The code for the test program is:

"use strict";

const { createMachine, createActor } = require('xstate');
const { v4: uuid } = require("uuid");

const { ParentMachine } = require("./parent");

const parentMachine = createMachine(ParentMachine);
const parent = createActor(parentMachine, { id: "parent", systemId: "parent" });
const childId = uuid();

console.log("Child Id", childId);
parent.start();
parent.send({ type: "start", childId });
parent.send({ type: "tick", id: childId });
const snapshot = parent.getPersistedSnapshot();
parent.stop();

console.log("Snapshot", snapshot);
const restored = JSON.parse(JSON.stringify(snapshot));
console.log("Snapshot", restored);

const parentRestored = createActor(parentMachine, { id: "parent", systemId: "parent", snapshot: restored });
parentRestored.start();
parentRestored.send({ type: "tick", id: childId });
parentRestored.send({ type: "stop" });

The machine defintion of the parent machine:

"use strict";

const { createMachine, assign } = require('xstate');

const { ChildMachine } = require("./child");

const ParentMachine = {
    id: "parent",
    initial: "idle",
    context: {
        count: 0,
        child: null
    },
    entry: [
        ({ context }) => console.log("ParentMachine created", context)
    ],
    states: {
        idle: {
            on: {
                start: {
                    target: "running",
                    actions: [
                        assign(({ spawn, event }) => { 
                            const machine = createMachine(ChildMachine);
                            const id = event.childId;
                            const child = spawn(machine, { id, systemId: id });                            
                            return { child };
                        })
                    ]
                }
            }
        },
        running: {
            on: {
                tick: {
                    actions: [
                        assign({ count: ctx => ctx.count + 1 }),
                        ({ system, event }) => {
                            console.log("ParentMachine tick", { event });
                            const child = system.get(event.id);
                            if (child) child.send(event);
                       }
                    ]
                },
                stop: {
                    target: "final"
                }
            }
        },
        final: {
            type: "final"
        }
    }
};

module.exports = {
    ParentMachine
};

The machine definition of the child machine:

"use strict";

const { assign } = require('xstate');

const ChildMachine = {
    initial: "running",
    context: {
        count: 0
    },
    entry: [
        ({ context }) => console.log("ChildMachine created", context)
    ],
    states: {
        running: {
            on: {
                tick: {
                    actions: [
                        assign({ count: ({ context }) => (context.count + 1) }),
                        ({ event }) => console.log("ChildMachine tick", event)
                    ]
                },
                stop: {
                    target: "final"
                }
            }
        },
        final: {
            type: "final"
        }
    }
};

module.exports = {
    ChildMachine
};

Expected result

The spawned actor should be also reachable by systemId after restoring a serialized snapshot.

Actual result

After restoring the snapshot an error appears, when using actor.send with the reference determined by system.get(id).

TypeError: this.logic.transition is not a function
    at Actor._process (C:\Daten\node\modules\imicros-core\node_modules\xstate\dist\raise-40b1a1f5.cjs.js:814:30)
    at Mailbox.flush (C:\Daten\node\modules\imicros-core\node_modules\xstate\dist\raise-40b1a1f5.cjs.js:45:12)
    at Mailbox.enqueue (C:\Daten\node\modules\imicros-core\node_modules\xstate\dist\raise-40b1a1f5.cjs.js:37:12)
    at Actor._send (C:\Daten\node\modules\imicros-core\node_modules\xstate\dist\raise-40b1a1f5.cjs.js:935:18)
    at Object._relay (C:\Daten\node\modules\imicros-core\node_modules\xstate\dist\raise-40b1a1f5.cjs.js:229:14)
    at Actor.send (C:\Daten\node\modules\imicros-core\node_modules\xstate\dist\raise-40b1a1f5.cjs.js:944:17)
    at ParentMachine.states.running.on.tick.actions (C:\Daten\node\modules\imicros-core\dev\flow\parent.js:42:46)
    at executeAction (C:\Daten\node\modules\imicros-core\node_modules\xstate\dist\raise-40b1a1f5.cjs.js:2233:7)
    at resolveAndExecuteActionsWithContext (C:\Daten\node\modules\imicros-core\node_modules\xstate\dist\raise-40b1a1f5.cjs.js:2237:9)
    at resolveActionsAndContext (C:\Daten\node\modules\imicros-core\node_modules\xstate\dist\raise-40b1a1f5.cjs.js:2268:21)

Reproduction

see provided code above...

Additional context

No response

davidkpiano commented 5 months ago

Can you please put the code in a repo or on StackBlitz? Would make it easier to debug.

al66 commented 5 months ago

Hope, this is what you want: https://stackblitz.com/edit/stackblitz-starters-otrd6j?file=index.js