Skip to content

Instantly share code, notes, and snippets.

@craig552uk
Last active July 29, 2025 13:37
Show Gist options
  • Save craig552uk/f427e3f5b06a41e374e29710abc3d75c to your computer and use it in GitHub Desktop.
Save craig552uk/f427e3f5b06a41e374e29710abc3d75c to your computer and use it in GitHub Desktop.
A really simple workflow management system using TypeScript
// 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