-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Simple workflow engine example (#4350)
* Add express example * Small updates * Make index nice * Add tsconfig * Add Open in StackBlitz link
- Loading branch information
1 parent
a79f2e4
commit bdee70b
Showing
5 changed files
with
863 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
Oops, something went wrong.