Event Storming Diagram Renderer for GitHub and VS Code
A browser extension (Manifest V3) and VS Code Markdown preview extension that render event storming diagrams from fenced code blocks.
Features
- 🎨 Full event storming visual language: events, commands, queries, aggregates, actors, policies, views, read models, external systems, errors, temp objects, notes
- 📦 Container-based layout: JSON DSL containers render as visual boxes for aggregates, external systems, read models, and processes
- 🗂️ Group subgraphs: each child group renders inside its own nested container with the group name in the top-left corner
- ⬅️→ Left-to-right process flows: actor → command/query/policy → event inside containers
- ↘️ Directional arrows with shared-target fan-in layouts for commands and views
- 🔍 Interactive zoom & pan
- 💡 Tooltips on hover, including attached notes
- 📝 Note badge on nodes that have notes
- 🎯 Collapsible diagrams with toggle button
- 🌈 Color-coded rectangles (black border) per event storming standard:
- Orange (
#FFA500) → Domain Events
- Light Green (
#91D49C) → Commands
- Dark Green (
#5BAA62) → Queries / Read Models
- Yellow (
#FEE254) → Aggregates
- Yellow (
#FEE254) → Views
- Gray (
#D4D3D3) → Actors
- Blue (
#859EBF) → Policies
- Pink (
#FB8597) → External Systems
- Cyan (
#8DCFF9) → Errors
- Light Yellow (
#FFF1AA) → Temp Objects / Notes
Getting Started
1. Install Dependencies
npm install
2. Build
npm run build
This bundles TypeScript (via esbuild IIFE) and copies assets into dist/.
3. Run Tests
npm test
4. Load in Edge (or Chrome)
- Open
edge://extensions/ (or chrome://extensions/)
- Enable Developer mode (toggle in top-right)
- Click Load unpacked
- Select the
dist/ folder
5. Package for VS Code
npm run package:vscode
This creates a .vsix file that can be shared or installed using Extensions: Install from VSIX...
Usage on GitHub
In any Markdown file (README, issue, PR comment, wiki), you can use:
eventstorming for the text DSL
json for the JSON DSL, which gives editors JSON syntax help
[
{
"type": "Aggregate",
"name": "User",
"notes": ["This aggregate represents a user in the system, including their registration and profile management processes."],
"children": [
{
"name": "User Registration",
"nodes": [
{ "type": "Actor", "name": "Customer", "next": "Register" },
{ "type": "Command", "name": "Register", "next": "Is Email Valid?" },
{ "type": "Policy", "name": "Is Email Valid?", "next": "UserRegistered", "negativeNext": "Invalid Email" },
{ "type": "Error", "name": "Invalid Email", "notes": ["The email address provided is not valid. Please enter a valid email address and try again."] },
{ "type": "Event", "name": "UserRegistered", "next":"Some Note" },
{ "type": "Note", "name": "Some Note", "notes": ["This is a note attached to the UserRegistered event."] }
],
"notes": ["This process handles user registration, including validating the email address and creating a new user account if the email is valid."]
}
]
},
{
"type": "Aggregate",
"name": "Morning Routine",
"children": [
{
"name": "Wake Up",
"nodes": [
{ "type": "Actor", "name": "Me", "next": "Wake Up" },
{ "type": "Command", "name": "Wake Up", "next": "Is Alarm Ringing?" },
{ "type": "Policy", "name": "Is Alarm Ringing?", "next": "Got Out of Bed", "negativeNext": "Sleep In" },
{ "type": "Error", "name": "Sleep In" },
{ "type": "Event", "name": "Got Out of Bed" }
]
},
{
"name": "Shower",
"nodes": [
{ "type": "Command", "name": "Have Shower", "next": "Is the shower running?" },
{ "type": "Policy", "name": "Is the shower running?", "next": "Have shower gel?", "negativeNext": "Switch on shower" },
{ "type": "ExternalSystem", "name": "Switch on shower", "next": "Have shower gel?" },
{ "type": "Policy", "name": "Have shower gel?", "next": "Had Shower", "negativeNext": "Go Buy Shower Gel" },
{ "type": "Error", "name": "Go Buy Shower Gel" },
{ "type": "Event", "name": "Had Shower" }
]
}
]
},
{
"type": "Aggregate",
"name": "User Profile",
"children": [
{
"name": "Update Profile",
"nodes": [
{ "type": "Actor", "name": "Customer", "next": "UpdateProfile" },
{ "type": "Command", "name": "UpdateProfile", "next": "Is User Authenticated?" },
{ "type": "Policy", "name": "Is User Authenticated?", "next": "ProfileUpdated", "negativeNext": "Authentication Required" },
{ "type": "Error", "name": "Authentication Required", "notes": ["You must be logged in to update your profile. Please log in and try again."] },
{ "type": "Event", "name": "ProfileUpdated" }
],
"notes": ["This process allows users to update their profile information, but only if they are authenticated. If the user is not authenticated, an error is returned."]
}
]
},
{
"type": "Aggregate",
"name": "Order",
"children": [
{
"name": "Place Order",
"nodes": [
{ "type": "Command", "name": "PlaceOrder", "next": "InventoryService" },
{ "type": "ExternalSystem", "name": "InventoryService", "next": "Do We Have Stock?" },
{ "type": "Policy", "name": "Do We Have Stock?", "next": "Is Order Detail Valid?", "negativeNext": "Out Of Stock" },
{ "type": "Policy", "name": "Is Order Detail Valid?", "next": "PaymentGateway", "negativeNext": "Invalid Order Detail" },
{ "type": "Error", "name": "Invalid Order Detail", "notes": ["Order details are invalid, please review your order and try again."] },
{ "type": "ExternalSystem", "name": "PaymentGateway", "next": "Is Payment Successful?" },
{ "type": "Policy", "name": "Is Payment Successful?", "next": "OrderPlaced", "negativeNext": "PaymentFailed" },
{ "type": "Error", "name": "PaymentFailed", "notes": ["Payment failed, please try again or use a different payment method.", "Client should handle this error."] },
{ "type": "Event", "name": "OrderPlaced" }
]
},
{
"name": "Cancel Order",
"nodes": [
{ "type": "Actor", "name": "Customer", "next": "CancelOrder" },
{ "type": "Actor", "name": "Staff", "next": "CancelOrder" },
{ "type": "Event", "name": "PaymentFailed", "next": "CancelOrder" },
{ "type": "Command", "name": "CancelOrder", "next": "Is Cancellation Allowed?" },
{ "type": "Policy", "name": "Is Cancellation Allowed?", "next": "OrderCancelled", "negativeNext": "CancellationDenied" },
{ "type": "Event", "name": "OrderCancelled", "notes": ["Order has been cancelled successfully.", "another note"] }
]
}
]
},
{
"type": "ExternalSystem",
"name": "Inventory Service",
"children": [
{ "name": "Inventory Check", "nodes": [
{"type": "Command", "name": "Check Inventory", "next": "Get Inventory"},
{"type": "Query", "name": "Get Inventory", "next": "Has Stock?"},
{"type": "Policy", "name": "Has Stock?", "next": "InventoryCheckPassed", "negativeNext": "Out of Stock"},
{"type": "Event", "name": "Inventory Check Passed"}
]}
]
},
{
"type": "ReadModel",
"name": "OrderDetail",
"children": [
{
"name": "Order Detail Projection",
"nodes": [
{ "type": "Event", "name": "OrderPlaced", "next": "Order Detail View" },
{ "type": "Event", "name": "OrderCancelled", "next": "Order Detail View" },
{ "type": "Event", "name": "OrderUpdated", "next": "Order Detail View" },
{ "type": "Event", "name": "OrderShipped", "next": "Order Detail View" },
{ "type": "View", "name": "Order Detail View", "notes": ["This view is used to display the details of an order, including its status, items, and other relevant information."] }
]
}
]
},
{
"type": "Process",
"name": "Customer Order View",
"children": [
{
"name": "View Order Details",
"nodes": [
{ "type": "Actor", "name": "Customer", "next": "GetOrderDetails" },
{ "type": "Query", "name": "GetOrderDetails", "next": "Order Detail Projection" },
{ "type": "View", "name": "Order Detail Projection" }
]
}
]
}
]
The diagram will render inline, replacing the code block automatically.
For the JSON DSL shown above, supported container types are Aggregate, ExternalSystem, ReadModel, and Process.
Usage in VS Code
Open a Markdown file containing an eventstorming fenced block or a matching json fenced block, then run Markdown: Open Preview to the Side.
The built-in Markdown preview keeps normal Markdown rendering and replaces matching blocks with the diagram UI.
Note: The current VS Code integration targets the desktop Markdown preview.
DSL Reference
The current DSL is JSON. Its shape matches the implementation in src/dsl-type.ts:
type ContainerType = "Aggregate" | "ExternalSystem" | "ReadModel" | "Process";
type NodeType =
| "Aggregate"
| "Actor"
| "Command"
| "Event"
| "Query"
| "Policy"
| "Error"
| "ExternalSystem"
| "Note"
| "View";
interface Container {
type: ContainerType;
name: string;
notes?: string[];
children: Group[];
}
interface Group {
name: string;
nodes: Node[];
notes?: string[];
}
interface Node {
type: NodeType;
name: string;
next?: string;
negativeNext?: string;
notes?: string[];
}
Schema Notes
children defines independent process groups inside a container.
next links to another node in the same group and renders to the right.
negativeNext is used for policy failure paths and renders below the policy.
- If a policy omits a matching negative-path node, the renderer creates a default error node.
notes can be added to containers, groups, or nodes. Nodes with notes show a small badge, and hover tooltips include the note text.
Project Structure
├── src/ # TypeScript source
│ ├── content.ts # GitHub content script
│ ├── dsl.ts # DSL parser + types
│ ├── dsl-type.ts # Container/Process type definitions
│ ├── dsl.test.ts # DSL parser tests
│ ├── renderer.ts # D3.js diagram renderer
│ ├── renderer.test.ts # Renderer tests
│ └── types.d.ts # D3 type declarations
├── dist/ # Built output (load this as extension)
│ ├── manifest.json
│ ├── content.js # Single bundled file (IIFE)
│ └── style.css
├── package.json
├── tsconfig.json
├── build.mjs # esbuild bundler
└── vitest.config.ts # Test configuration
Testing
73 tests (51 DSL + 22 renderer) using Vitest with jsdom:
npm test # Run once
npm run test:watch # Watch mode
License
MIT