Skip to content

Commit

Permalink
Simple workflow engine example (#4350)
Browse files Browse the repository at this point in the history
* Add express example

* Small updates

* Make index nice

* Add tsconfig

* Add Open in StackBlitz link
  • Loading branch information
davidkpiano authored Nov 5, 2023
1 parent a79f2e4 commit bdee70b
Show file tree
Hide file tree
Showing 5 changed files with 863 additions and 0 deletions.
52 changes: 52 additions & 0 deletions examples/express-workflow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Express simple workflow engine

This is a simple workflow engine built with:

- XState v5 beta
- TypeScript
- Express

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/statelyai/xstate/tree/main/examples/express-workflow?file=index.ts)

## Usage

```bash
pnpm install
pnpm start
```

## Endpoints

### POST `/workflows`

Creates a new workflow instance.

```bash
curl -X POST http://localhost:4242/workflows
```

Example response:

```json
{
"workflowId": "7ky252"
}
```

### POST `/workflows/:id`

Sends an event to a workflow instance.

```bash
# Replace :id with the workflow ID; e.g. http://localhost:4242/workflows/7ky252
# the body should be JSON
curl -X POST http://localhost:4242/workflows/:id -d '{"type": "TIMER"}' -H "Content-Type: application/json"
```

### GET `/workflows/:id`

Gets the current state of a workflow instance.

```bash
curl -X GET http://localhost:4242/workflows/:id
```
129 changes: 129 additions & 0 deletions examples/express-workflow/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
createMachine,
createActor,
PersistedMachineState,
assign
} from 'xstate';
import bodyParser from 'body-parser';

function generateActorId() {
return Math.random().toString(36).substring(2, 8);
}

const persistedStates: Record<
string,
PersistedMachineState<any, any, any, any, any, any>
> = {};

const machine = createMachine({
id: 'counter',
initial: 'green',
context: {
cycles: 0
},
states: {
green: {
on: {
TIMER: 'yellow'
}
},
yellow: {
on: {
TIMER: 'red'
}
},
red: {
on: {
TIMER: {
target: 'green',
actions: assign({
cycles: ({ context }) => context.cycles + 1
})
}
}
}
}
});

import express from 'express';

const app = express();

app.use(bodyParser.json());

// Endpoint to start a new workflow instance
// - Generates a unique ID for the actor
// - Starts the actor
// - Persists the actor state
// - Returns the actor ID in the response
app.post('/workflows', (req, res) => {
const workflowId = generateActorId(); // generate a unique ID
const actor = createActor(machine).start();

// @ts-ignore
persistedStates[workflowId] = actor.getPersistedState();

res.send({ workflowId });
});

// Endpoint to send events to an existing workflow instance
// - Gets the actor ID from request params
// - Gets the persisted state for that actor
// - Sends the event from the request body to the actor
// - Persists the updated state
// - Returns the updated state in the response
app.post('/workflows/:workflowId', (req, res) => {
const { workflowId } = req.params;
const snapshot = persistedStates[workflowId];

if (!snapshot) {
return res.status(404).send('Actor not found');
}

const event = req.body;
const actor = createActor(machine, { state: snapshot }).start();

actor.send(event);

// @ts-ignore
persistedStates[workflowId] = actor.getPersistedState();

actor.stop();

res.sendStatus(200);
});

// Endpoint to get the current state of an existing workflow instance
// - Gets the actor ID from request params
// - Gets the persisted state for that actor
// - Returns the persisted state in the response
app.get('/workflows/:workflowId', (req, res) => {
const { workflowId } = req.params;
const persistedState = persistedStates[workflowId];

if (!persistedState) {
return res.status(404).send('Actor not found');
}

res.json(persistedState);
});

app.get('/', (_, res) => {
res.send(`
<html>
<body style="font-family: sans-serif;">
<h1>Express Workflow</h1>
<p>Start a new workflow instance:</p>
<pre>curl -X POST http://localhost:4242/workflows</pre>
<p>Send an event to a workflow instance:</p>
<pre>curl -X POST http://localhost:4242/workflows/:workflowId -d '{"type":"TIMER"}'</pre>
<p>Get the current state of a workflow instance:</p>
<pre>curl -X GET http://localhost:4242/workflows/:workflowId</pre>
</body>
</html>
`);
});

app.listen(4242, () => {
console.log('Server listening on port 4242');
});
24 changes: 24 additions & 0 deletions examples/express-workflow/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "express-workflow",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "ts-node index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/body-parser": "^1.19.3",
"body-parser": "^1.20.2",
"express": "^4.18.2",
"ts-node": "^10.9.1",
"xstate": "5.0.0-beta.33"
},
"devDependencies": {
"@types/express": "^4.17.18",
"@types/node": "^20.8.3",
"typescript": "^5.2.2"
}
}
Loading

0 comments on commit bdee70b

Please sign in to comment.