Skip to content

Instantly share code, notes, and snippets.

@kirillrogovoy
Created December 13, 2022 10:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kirillrogovoy/2d3a9542d84bacee1db86e0054e35432 to your computer and use it in GitHub Desktop.
Save kirillrogovoy/2d3a9542d84bacee1db86e0054e35432 to your computer and use it in GitHub Desktop.
Using XState on the back end

State machine code with no implementation:

const trainingMachine = createMachine<TrainingContext>({
  predictableActionArguments: true,
  id: 'training',
  initial: 'initial',
  context: {},
  states: {
    initial: {
      on: {
        REQUESTED: {
          target: 'starting',
          actions: assign({
            trainingRequest: (_context, event) => event.trainingRequest,
            user: (_context, event) => event.user,
          }),
        },
      },
    },
    starting: {
      invoke: {
        src: 'startTraining',
        onDone: {
          target: 'waitingForWebhook',
          actions: [
            assign({
              replicateState: (_context, event) => event.data.replicateResponse,
            }),
            'sendEmailTrainingStarted',
          ],
        },
        onError: {
          target: 'failure',
          actions: assign({
            replicateState: (_context, event) => event.data.replicateResponse,
          }),
        },
      },
    },
    waitingForWebhook: {
      on: {
        WEBHOOK_ARRIVED: [
          {
            target: 'processingWebhook',
            actions: assign({
              replicateState: (_context, event) => event.webhookData,
            }),
          },
        ],
      },
    },
    processingWebhook: {
      invoke: {
        src: 'processWebhook',
        onDone: [
          {
            target: 'waitingForWebhook',
            cond: 'webhookStatusInvalid',
          },
          {
            target: 'success',
            actions: ['sendEmailTrainingSuccessful'],
          },
        ],
        onError: {
          target: 'failure',
          actions: ['sendEmailTrainingFailed'],
        },
      },
    },
    success: {
      type: 'final',
    },
    failure: {
      type: 'final',
    },
  },
})

All services I call via src are functions returning promises. There are no sub-machines in this case.

How I create and operate the machine:

  1. Create a 'complete' machine with trainingMachine.withConfig(implementation)
  2. Look if I have a saved state for this machine in the DB. If so, get and JSON.parse() it
  3. Create a service via interpret(machine)
  4. Add an onTransition callback that would rewrite the saved state in the DB if newState.changed is true
  5. Call service.start(existingState ? State.create(existingState) : undefined)
  6. Check that the current status is what I expect it to be, otherwise throw
  7. Feed it the event, e.g. trainingService.send('WEBHOOK_ARRIVED', { webhookData })
  8. await waitFor(trainingService, (state) => ...) specifying the status(es) I expect to arrive at
  9. service.stop() (I don't know if it's important to call it)
  10. Exist the process, e.g. return the HTTP response.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment