Last active
July 29, 2025 13:37
-
-
Save craig552uk/f427e3f5b06a41e374e29710abc3d75c to your computer and use it in GitHub Desktop.
A really simple workflow management system using TypeScript
This file contains hidden or 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
// A really simple workflow management system using TypeScript | |
// This code defines a Workflow class that allows you to define transitions between states, | |
// validate transitions, and convert the workflow to a Mermaid diagram format. | |
// It also includes an example of how to use the Workflow class with a simple document review process | |
export class Workflow<T> { | |
private transitions: [T, T, string?][]; | |
private startStates: T[] | null; | |
private endStates: T[] | null; | |
private title: string | null; | |
/** | |
* Creates a new Workflow instance. | |
* @param transitions - An array of transitions, each represented as a tuple of [fromState, toState, label?]. | |
* @param startState - An optional array of starting states. | |
* @param endState - An optional array of ending states. | |
*/ | |
constructor({ | |
transitions = [], | |
startStates = null, | |
endStates = null, | |
title = null, | |
}: { | |
transitions?: [T, T, string?][]; | |
startStates?: T[] | null; | |
endStates?: T[] | null; | |
title?: string | null; | |
}) { | |
this.transitions = transitions; | |
this.startStates = startStates; | |
this.endStates = endStates; | |
this.title = title; | |
} | |
/** | |
* Checks if a transition is valid between two states. | |
* @param from - The state to transition from. | |
* @param to - The state to transition to. | |
* @returns True if the transition is valid, false otherwise. | |
*/ | |
isValidTransition(from: T, to: T): boolean { | |
return this.transitions.some(transition => transition[0] === from && transition[1] === to); | |
} | |
/** | |
* Checks if a state is a valid starting state. | |
* @param state - The state to check. | |
* @returns True if the state is a valid starting state, false otherwise. | |
*/ | |
isValidStartState(state: T): boolean { | |
return this.startStates ? this.startStates.includes(state) : false; | |
} | |
/** | |
* Checks if a state is a valid ending state. | |
* @param state - The state to check. | |
* @returns True if the state is a valid ending state, false otherwise. | |
*/ | |
isValidEndState(state: T): boolean { | |
return this.endStates ? this.endStates.includes(state) : false; | |
} | |
/** | |
* Converts the workflow to a Mermaid diagram string. | |
* @returns A string formatted for Mermaid diagram representation. | |
*/ | |
toMermaid(): string { | |
let mermaidString = ''; | |
if (this.title) { | |
mermaidString += '---\n'; | |
mermaidString += `title: ${this.title}\n`; | |
mermaidString += '---\n'; | |
} | |
mermaidString += 'stateDiagram-v2\n'; | |
if (this.startStates && this.startStates.length > 0) { | |
this.startStates.forEach(state => { | |
mermaidString += ` [*] --> ${state}\n`; | |
}); | |
} | |
this.transitions.forEach(transition => { | |
// If a transition has a label, include it in the Mermaid string | |
if (transition[2]) { | |
mermaidString += ` ${transition[0]} --> ${transition[1]}: ${transition[2]}\n`; | |
} else { | |
mermaidString += ` ${transition[0]} --> ${transition[1]}\n`; | |
} | |
}); | |
if (this.endStates && this.endStates.length > 0) { | |
this.endStates.forEach(state => { | |
mermaidString += ` ${state} --> [*]\n`; | |
}); | |
} | |
return mermaidString; | |
} | |
} | |
// Example usage of the Workflow class | |
// Define the states for a simple document review process | |
// This example includes states like DRAFT, PENDING, APPROVED, and REJECTED | |
// Each transition can have an optional label to describe the action taken | |
// The workflow can be visualized using Mermaid syntax for state diagrams | |
// | |
// $ node workflow.js | |
// --- | |
// title: Document Review Workflow | |
// --- | |
// stateDiagram-v2 | |
// [*] --> DRAFT | |
// DRAFT --> PENDING: Submit for review | |
// PENDING --> DRAFT: Return to draft | |
// PENDING --> APPROVED: Approve for publication | |
// PENDING --> REJECTED | |
// REJECTED --> DRAFT: Resubmit | |
// APPROVED --> [*] | |
// isValidTransition? DRAFT -> PENDING true | |
// isValidTransition? APPROVED -> DRAFT false | |
// isValidTransition? PENDING -> APPROVED true | |
// isValidStartState? DRAFT true | |
// isValidStartState? PENDING false | |
// isValidEndState? APPROVED true | |
// isValidEndState? REJECTED false | |
enum States { | |
DRAFT = 'DRAFT', | |
PENDING = 'PENDING', | |
APPROVED = 'APPROVED', | |
REJECTED = 'REJECTED', | |
} | |
const wf = new Workflow<States>({ | |
title: 'Document Review Workflow', | |
transitions: [ | |
[States.DRAFT, States.PENDING, 'Submit for review'], | |
[States.PENDING, States.DRAFT, 'Return to draft'], | |
[States.PENDING, States.APPROVED, 'Approve for publication'], | |
[States.PENDING, States.REJECTED], | |
[States.REJECTED, States.DRAFT, 'Resubmit'], | |
], | |
startStates: [States.DRAFT], | |
endStates: [States.APPROVED], | |
}); | |
console.log(wf.toMermaid()); | |
console.log('isValidTransition? DRAFT -> PENDING', wf.isValidTransition(States.DRAFT, States.PENDING)); // true | |
console.log('isValidTransition? APPROVED -> DRAFT', wf.isValidTransition(States.APPROVED, States.DRAFT)); // false | |
console.log('isValidTransition? PENDING -> APPROVED', wf.isValidTransition(States.PENDING, States.APPROVED)); // true | |
console.log('isValidStartState? DRAFT', wf.isValidStartState(States.DRAFT)); // true | |
console.log('isValidStartState? PENDING', wf.isValidStartState(States.PENDING)); // false | |
console.log('isValidEndState? APPROVED', wf.isValidEndState(States.APPROVED)); // true | |
console.log('isValidEndState? REJECTED', wf.isValidEndState(States.REJECTED)); // false |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment