@gravito/flux 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,16 +2,31 @@
2
2
 
3
3
  > ⚡ Platform-agnostic, high-performance workflow engine for Gravito
4
4
 
5
- ## Features
5
+ ## Features
6
6
 
7
- - **Pure State Machine** - No runtime dependencies, Web Standard APIs only
8
- - **Fluent Builder API** - Type-safe, chainable workflow definitions
9
- - **Storage Adapters** - Memory, SQLite (Bun), PostgreSQL (coming soon)
10
- - **Distributed Locking** - Redis-based locking for multi-node deployments
11
- - **Retry & Timeout** - Automatic retry with exponential backoff
12
- - **Event Hooks** - Subscribe to workflow/step lifecycle events
13
- - **Dual Platform** - Works with both Bun and Node.js
14
- - **Well-Tested** - 87% function coverage, 92% line coverage, 277 passing tests
7
+ - 🪐 **Galaxy-Ready Workflow Engine** - Native integration with PlanetCore for universal business process orchestration.
8
+ - 🔄 **Distributed Saga Coordination** - Reliable transaction management across multiple isolated Satellites with automatic rollback.
9
+ - **Pure State Machine** - High-performance engine with zero runtime dependencies beyond Web Standard APIs.
10
+ - 🔐 **Distributed Locking** - Plasma-backed Redis locking to ensure process safety in multi-node clusters.
11
+ - 📡 **Signal & Suspend** - Pause workflows to wait for external stimulus (Webhooks, manual approvals) and resume instantly.
12
+ - 🛠️ **Fluent Builder API** - Fully type-safe, chainable definitions for complex logic flows.
13
+
14
+ ## 🌌 Role in Galaxy Architecture
15
+
16
+ In the **Gravito Galaxy Architecture**, Flux acts as the **Logic Orchestrator (Cerebral Cortex)**.
17
+
18
+ - **Process Master**: Manages complex, long-running business processes that span across multiple Satellites (e.g., a "Checkout" process involving `Catalog`, `Payment`, and `Notification`).
19
+ - **Reliability Engine**: Ensures that even if a Satellite fails mid-process, the state is preserved and the appropriate compensation logic (Saga) is executed.
20
+ - **State Persistence**: Works with `Atlas` or `Plasma` to store the execution state, allowing workflows to survive system restarts or move between nodes.
21
+
22
+ ```mermaid
23
+ graph TD
24
+ S1[Satellite: Order] -- "Trigger Workflow" --> Flux{Flux Engine}
25
+ Flux -- "Step 1" --> S2[Satellite: Inventory]
26
+ Flux -- "Step 2" --> S3[Satellite: Payment]
27
+ Flux -- "Fail / Rollback" --> S2
28
+ Flux -.-> State[(State: Plasma/Redis)]
29
+ ```
15
30
 
16
31
  ## Installation
17
32
 
@@ -151,6 +166,14 @@ const reportWorkflow = createWorkflow('generate-report')
151
166
  })
152
167
  ```
153
168
 
169
+ ## 📚 Documentation
170
+
171
+ Detailed guides and references for the Galaxy Architecture:
172
+
173
+ - [🏗️ **Architecture Overview**](./README.md) — Under the hood of the state machine.
174
+ - [🔄 **Workflow Patterns**](./doc/WORKFLOW_PATTERNS.md) — **NEW**: Sagas, Suspension, and Distributed Locking.
175
+ - [🧪 **Testing Workflows**](./tests/README.md) — Mocking and execution testing.
176
+
154
177
  ## API
155
178
 
156
179
  ### `createWorkflow(name)`
package/dist/bun.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkYXBEYVGYcjs = require('./chunk-YXBEYVGY.cjs');
3
+ var _chunkZE2RDS47cjs = require('./chunk-ZE2RDS47.cjs');
4
4
 
5
5
 
6
- exports.BunSQLiteStorage = _chunkYXBEYVGYcjs.BunSQLiteStorage;
6
+ exports.BunSQLiteStorage = _chunkZE2RDS47cjs.BunSQLiteStorage;
7
7
  //# sourceMappingURL=bun.cjs.map
package/dist/bun.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/carl/Dev/Carl/gravito-core-ci-fix/packages/flux/dist/bun.cjs"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,8DAAC","file":"/Users/carl/Dev/Carl/gravito-core-ci-fix/packages/flux/dist/bun.cjs"}
1
+ {"version":3,"sources":["/Users/carl/Dev/Carl/gravito-core/packages/flux/dist/bun.cjs"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,8DAAC","file":"/Users/carl/Dev/Carl/gravito-core/packages/flux/dist/bun.cjs"}
package/dist/bun.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Database } from 'bun:sqlite';
2
- import { p as WorkflowStorage, a as WorkflowState, n as WorkflowFilter } from './types-CRz5XdLd.cjs';
2
+ import { p as WorkflowStorage, a as WorkflowState, n as WorkflowFilter } from './types-CGIEQPFv.cjs';
3
3
 
4
4
  /**
5
5
  * Configuration options for the Bun SQLite storage adapter.
package/dist/bun.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Database } from 'bun:sqlite';
2
- import { p as WorkflowStorage, a as WorkflowState, n as WorkflowFilter } from './types-CRz5XdLd.js';
2
+ import { p as WorkflowStorage, a as WorkflowState, n as WorkflowFilter } from './types-CGIEQPFv.js';
3
3
 
4
4
  /**
5
5
  * Configuration options for the Bun SQLite storage adapter.
package/dist/bun.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BunSQLiteStorage
3
- } from "./chunk-NAIVO7RR.js";
3
+ } from "./chunk-EZGSU6AW.js";
4
4
  export {
5
5
  BunSQLiteStorage
6
6
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/carl/Dev/Carl/gravito-core-ci-fix/packages/flux/dist/chunk-6AZNHVEO.cjs","../src/visualization/MermaidGenerator.ts"],"names":[],"mappings":"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","file":"/Users/carl/Dev/Carl/gravito-core-ci-fix/packages/flux/dist/chunk-6AZNHVEO.cjs","sourcesContent":[null,"import type { StepExecution, WorkflowDefinition, WorkflowState } from '../types'\n\n/**\n * Options for customizing Mermaid diagram generation.\n */\nexport interface MermaidOptions {\n /** Include detailed step information (retries, timeout, conditions). */\n showDetails?: boolean\n /** Include execution history with status colors. */\n showStatus?: boolean\n /** Render parallel groups visually. */\n showParallelGroups?: boolean\n /** Theme: 'default' | 'dark' | 'forest' | 'neutral'. */\n theme?: 'default' | 'dark' | 'forest' | 'neutral'\n}\n\n/**\n * Generates Mermaid flowchart diagrams from workflow definitions and execution states.\n *\n * **Features**:\n * - Workflow structure visualization (steps, parallel groups, conditions)\n * - Execution status overlay (completed, failed, compensated)\n * - Detailed metadata (retries, timeout, commit markers)\n *\n * **Use Cases**:\n * - Documentation generation\n * - Debugging workflow execution\n * - Visual workflow design validation\n */\nexport class MermaidGenerator {\n /**\n * Generates a Mermaid flowchart from a workflow definition.\n *\n * @param definition - The workflow definition to visualize\n * @param options - Customization options\n * @returns Mermaid diagram syntax as a string\n *\n * @example\n * ```typescript\n * const generator = new MermaidGenerator()\n * const diagram = generator.generateFromDefinition(workflow, {\n * showDetails: true,\n * showParallelGroups: true\n * })\n * console.log(diagram)\n * ```\n */\n generateFromDefinition<TInput, TData>(\n definition: WorkflowDefinition<TInput, TData>,\n options: MermaidOptions = {}\n ): string {\n const { showDetails = false, showParallelGroups = true, theme = 'default' } = options\n\n const lines: string[] = []\n lines.push(`%%{init: {'theme':'${theme}'}}%%`)\n lines.push('flowchart TD')\n lines.push(` Start([Start: ${definition.name}])`)\n\n let prevNode = 'Start'\n const parallelGroups = new Map<string, string[]>()\n\n // First pass: identify parallel groups\n if (showParallelGroups) {\n for (const step of definition.steps) {\n if (step.parallelGroup) {\n const group = parallelGroups.get(step.parallelGroup) || []\n group.push(step.name)\n parallelGroups.set(step.parallelGroup, group)\n }\n }\n }\n\n const processedGroups = new Set<string>()\n\n for (let i = 0; i < definition.steps.length; i++) {\n const step = definition.steps[i]!\n\n // Handle parallel groups\n if (step.parallelGroup && showParallelGroups) {\n if (processedGroups.has(step.parallelGroup)) {\n continue // Already processed this group\n }\n processedGroups.add(step.parallelGroup)\n\n const groupSteps = parallelGroups.get(step.parallelGroup)!\n const groupName = `ParallelGroup_${step.parallelGroup}`\n\n // Create subgraph for parallel execution\n lines.push(` subgraph ${groupName}[\" \"]`)\n lines.push(` direction LR`)\n\n for (const stepName of groupSteps) {\n const s = definition.steps.find((st) => st.name === stepName)!\n const nodeId = this.sanitizeNodeId(stepName)\n const label = this.buildStepLabel(s, showDetails)\n const shape = s.commit ? `[${label}]` : `(${label})`\n lines.push(` ${nodeId}${shape}`)\n\n if (s.compensate) {\n lines.push(` ${nodeId}_comp[🔄 Compensate]`)\n lines.push(` ${nodeId} -.->|on failure| ${nodeId}_comp`)\n }\n }\n\n lines.push(` end`)\n lines.push(` ${prevNode} --> ${groupName}`)\n prevNode = groupName\n } else if (!step.parallelGroup) {\n // Regular sequential step\n const nodeId = this.sanitizeNodeId(step.name)\n const label = this.buildStepLabel(step, showDetails)\n const shape = step.commit ? `[${label}]` : `(${label})`\n\n lines.push(` ${nodeId}${shape}`)\n\n // Handle conditional steps\n if (step.when) {\n const condId = `${nodeId}_cond`\n lines.push(` ${condId}{Condition?}`)\n lines.push(` ${prevNode} --> ${condId}`)\n lines.push(` ${condId} -->|yes| ${nodeId}`)\n lines.push(` ${condId} -->|no| ${this.getNextNodeId(definition.steps, i)}`)\n prevNode = nodeId\n } else {\n lines.push(` ${prevNode} --> ${nodeId}`)\n prevNode = nodeId\n }\n\n // Show compensation\n if (step.compensate) {\n lines.push(` ${nodeId}_comp[🔄 Compensate]`)\n lines.push(` ${nodeId} -.->|on failure| ${nodeId}_comp`)\n }\n }\n }\n\n lines.push(` ${prevNode} --> End([End])`)\n\n // Add styling\n lines.push('')\n lines.push(' classDef commitStep fill:#e1f5e1,stroke:#4caf50,stroke-width:2px')\n lines.push(' classDef normalStep fill:#e3f2fd,stroke:#2196f3,stroke-width:2px')\n lines.push(\n ' classDef compensateStep fill:#fff3e0,stroke:#ff9800,stroke-width:1px,stroke-dasharray: 5 5'\n )\n\n return lines.join('\\n')\n }\n\n /**\n * Generates a Mermaid flowchart from a workflow execution state.\n * Overlays execution status (completed, failed, compensated) on top of the structure.\n *\n * @param definition - The workflow definition\n * @param state - The current execution state\n * @param options - Customization options\n * @returns Mermaid diagram syntax with status overlay\n *\n * @example\n * ```typescript\n * const diagram = generator.generateFromState(workflow, executionState, {\n * showStatus: true,\n * showDetails: true\n * })\n * ```\n */\n generateFromState<TInput, TData>(\n definition: WorkflowDefinition<TInput, TData>,\n state: WorkflowState<TInput, TData>,\n options: MermaidOptions = {}\n ): string {\n const {\n showDetails = true,\n showStatus = true,\n showParallelGroups = true,\n theme = 'default',\n } = options\n\n const lines: string[] = []\n lines.push(`%%{init: {'theme':'${theme}'}}%%`)\n lines.push('flowchart TD')\n lines.push(` Start([Start: ${definition.name}])`)\n lines.push(` Start:::started`)\n\n let prevNode = 'Start'\n const parallelGroups = new Map<string, string[]>()\n const executionMap = new Map<string, StepExecution>()\n\n // Build execution map\n for (const exec of state.history) {\n executionMap.set(exec.name, exec)\n }\n\n // Identify parallel groups\n if (showParallelGroups) {\n for (const step of definition.steps) {\n if (step.parallelGroup) {\n const group = parallelGroups.get(step.parallelGroup) || []\n group.push(step.name)\n parallelGroups.set(step.parallelGroup, group)\n }\n }\n }\n\n const processedGroups = new Set<string>()\n\n for (let i = 0; i < definition.steps.length; i++) {\n const step = definition.steps[i]!\n\n // Handle parallel groups\n if (step.parallelGroup && showParallelGroups) {\n if (processedGroups.has(step.parallelGroup)) {\n continue\n }\n processedGroups.add(step.parallelGroup)\n\n const groupSteps = parallelGroups.get(step.parallelGroup)!\n const groupName = `ParallelGroup_${step.parallelGroup}`\n\n lines.push(` subgraph ${groupName}[\" \"]`)\n lines.push(` direction LR`)\n\n for (const stepName of groupSteps) {\n const s = definition.steps.find((st) => st.name === stepName)!\n const nodeId = this.sanitizeNodeId(stepName)\n const exec = executionMap.get(stepName)\n const label = this.buildStepLabelWithStatus(s, exec, showDetails, showStatus)\n const shape = s.commit ? `[${label}]` : `(${label})`\n\n lines.push(` ${nodeId}${shape}`)\n\n if (exec && showStatus) {\n const statusClass = this.getStatusClass(exec.status)\n lines.push(` ${nodeId}:::${statusClass}`)\n }\n\n if (s.compensate && exec?.status === 'compensated') {\n lines.push(` ${nodeId}_comp[🔄 Compensated]`)\n lines.push(` ${nodeId}_comp:::compensated`)\n lines.push(` ${nodeId} -.->|rolled back| ${nodeId}_comp`)\n }\n }\n\n lines.push(` end`)\n lines.push(` ${prevNode} --> ${groupName}`)\n prevNode = groupName\n } else if (!step.parallelGroup) {\n const nodeId = this.sanitizeNodeId(step.name)\n const exec = executionMap.get(step.name)\n const label = this.buildStepLabelWithStatus(step, exec, showDetails, showStatus)\n const shape = step.commit ? `[${label}]` : `(${label})`\n\n lines.push(` ${nodeId}${shape}`)\n\n if (exec && showStatus) {\n const statusClass = this.getStatusClass(exec.status)\n lines.push(` ${nodeId}:::${statusClass}`)\n }\n\n if (step.when) {\n const condId = `${nodeId}_cond`\n const skipped = exec?.status === 'skipped'\n lines.push(` ${condId}{Condition?}`)\n lines.push(` ${prevNode} --> ${condId}`)\n\n if (skipped) {\n lines.push(` ${condId}:::skipped`)\n lines.push(` ${condId} -->|no| ${nodeId}`)\n } else {\n lines.push(` ${condId} -->|yes| ${nodeId}`)\n }\n } else {\n lines.push(` ${prevNode} --> ${nodeId}`)\n }\n\n if (step.compensate && exec?.status === 'compensated') {\n lines.push(` ${nodeId}_comp[🔄 Compensated]`)\n lines.push(` ${nodeId}_comp:::compensated`)\n lines.push(` ${nodeId} -.->|rolled back| ${nodeId}_comp`)\n }\n\n prevNode = nodeId\n }\n }\n\n const finalStatus = this.getWorkflowFinalStatus(state.status)\n lines.push(` ${prevNode} --> End([End: ${finalStatus}])`)\n lines.push(` End:::${this.getStatusClass(state.status)}`)\n\n // Add status-based styling\n lines.push('')\n lines.push(' classDef started fill:#e3f2fd,stroke:#2196f3,stroke-width:2px')\n lines.push(' classDef completed fill:#e8f5e9,stroke:#4caf50,stroke-width:2px')\n lines.push(' classDef failed fill:#ffebee,stroke:#f44336,stroke-width:2px')\n lines.push(' classDef compensated fill:#fff3e0,stroke:#ff9800,stroke-width:2px')\n lines.push(\n ' classDef skipped fill:#f5f5f5,stroke:#9e9e9e,stroke-width:1px,stroke-dasharray: 5 5'\n )\n lines.push(' classDef pending fill:#fafafa,stroke:#bdbdbd,stroke-width:1px')\n lines.push(\n ' classDef suspended fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px,stroke-dasharray: 3 3'\n )\n\n return lines.join('\\n')\n }\n\n private sanitizeNodeId(name: string): string {\n return name.replace(/[^a-zA-Z0-9_]/g, '_')\n }\n\n private buildStepLabel(step: any, showDetails: boolean): string {\n let label = step.name\n\n if (showDetails) {\n const meta: string[] = []\n if (step.commit) {\n meta.push('📝')\n }\n if (step.retries && step.retries > 0) {\n meta.push(`↻${step.retries}`)\n }\n if (step.timeout) {\n meta.push(`⏱${step.timeout}ms`)\n }\n if (step.when) {\n meta.push('❓')\n }\n\n if (meta.length > 0) {\n label += `<br/><small>${meta.join(' ')}</small>`\n }\n }\n\n return label\n }\n\n private buildStepLabelWithStatus(\n step: any,\n exec: StepExecution | undefined,\n showDetails: boolean,\n showStatus: boolean\n ): string {\n let label = step.name\n\n if (showStatus && exec) {\n label += `<br/><small><b>${exec.status.toUpperCase()}</b></small>`\n\n if (exec.duration) {\n label += `<br/><small>${exec.duration}ms</small>`\n }\n\n if (exec.retries > 0) {\n label += `<br/><small>Retries: ${exec.retries}</small>`\n }\n } else if (showDetails) {\n const meta: string[] = []\n if (step.commit) {\n meta.push('📝')\n }\n if (step.retries && step.retries > 0) {\n meta.push(`↻${step.retries}`)\n }\n if (step.timeout) {\n meta.push(`⏱${step.timeout}ms`)\n }\n if (step.when) {\n meta.push('❓')\n }\n\n if (meta.length > 0) {\n label += `<br/><small>${meta.join(' ')}</small>`\n }\n }\n\n return label\n }\n\n private getStatusClass(status: string): string {\n switch (status) {\n case 'completed':\n return 'completed'\n case 'failed':\n return 'failed'\n case 'compensated':\n case 'compensating':\n return 'compensated'\n case 'skipped':\n return 'skipped'\n case 'suspended':\n return 'suspended'\n case 'running':\n return 'started'\n default:\n return 'pending'\n }\n }\n\n private getWorkflowFinalStatus(status: string): string {\n switch (status) {\n case 'completed':\n return '✅ Completed'\n case 'failed':\n return '❌ Failed'\n case 'rolled_back':\n return '🔄 Rolled Back'\n case 'suspended':\n return '⏸ Suspended'\n default:\n return status\n }\n }\n\n private getNextNodeId(steps: any[], currentIndex: number): string {\n const nextStep = steps[currentIndex + 1]\n return nextStep ? this.sanitizeNodeId(nextStep.name) : 'End'\n }\n}\n"]}
1
+ {"version":3,"sources":["/Users/carl/Dev/Carl/gravito-core/packages/flux/dist/chunk-6AZNHVEO.cjs","../src/visualization/MermaidGenerator.ts"],"names":[],"mappings":"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","file":"/Users/carl/Dev/Carl/gravito-core/packages/flux/dist/chunk-6AZNHVEO.cjs","sourcesContent":[null,"import type { StepExecution, WorkflowDefinition, WorkflowState } from '../types'\n\n/**\n * Options for customizing Mermaid diagram generation.\n */\nexport interface MermaidOptions {\n /** Include detailed step information (retries, timeout, conditions). */\n showDetails?: boolean\n /** Include execution history with status colors. */\n showStatus?: boolean\n /** Render parallel groups visually. */\n showParallelGroups?: boolean\n /** Theme: 'default' | 'dark' | 'forest' | 'neutral'. */\n theme?: 'default' | 'dark' | 'forest' | 'neutral'\n}\n\n/**\n * Generates Mermaid flowchart diagrams from workflow definitions and execution states.\n *\n * **Features**:\n * - Workflow structure visualization (steps, parallel groups, conditions)\n * - Execution status overlay (completed, failed, compensated)\n * - Detailed metadata (retries, timeout, commit markers)\n *\n * **Use Cases**:\n * - Documentation generation\n * - Debugging workflow execution\n * - Visual workflow design validation\n */\nexport class MermaidGenerator {\n /**\n * Generates a Mermaid flowchart from a workflow definition.\n *\n * @param definition - The workflow definition to visualize\n * @param options - Customization options\n * @returns Mermaid diagram syntax as a string\n *\n * @example\n * ```typescript\n * const generator = new MermaidGenerator()\n * const diagram = generator.generateFromDefinition(workflow, {\n * showDetails: true,\n * showParallelGroups: true\n * })\n * console.log(diagram)\n * ```\n */\n generateFromDefinition<TInput, TData>(\n definition: WorkflowDefinition<TInput, TData>,\n options: MermaidOptions = {}\n ): string {\n const { showDetails = false, showParallelGroups = true, theme = 'default' } = options\n\n const lines: string[] = []\n lines.push(`%%{init: {'theme':'${theme}'}}%%`)\n lines.push('flowchart TD')\n lines.push(` Start([Start: ${definition.name}])`)\n\n let prevNode = 'Start'\n const parallelGroups = new Map<string, string[]>()\n\n // First pass: identify parallel groups\n if (showParallelGroups) {\n for (const step of definition.steps) {\n if (step.parallelGroup) {\n const group = parallelGroups.get(step.parallelGroup) || []\n group.push(step.name)\n parallelGroups.set(step.parallelGroup, group)\n }\n }\n }\n\n const processedGroups = new Set<string>()\n\n for (let i = 0; i < definition.steps.length; i++) {\n const step = definition.steps[i]!\n\n // Handle parallel groups\n if (step.parallelGroup && showParallelGroups) {\n if (processedGroups.has(step.parallelGroup)) {\n continue // Already processed this group\n }\n processedGroups.add(step.parallelGroup)\n\n const groupSteps = parallelGroups.get(step.parallelGroup)!\n const groupName = `ParallelGroup_${step.parallelGroup}`\n\n // Create subgraph for parallel execution\n lines.push(` subgraph ${groupName}[\" \"]`)\n lines.push(` direction LR`)\n\n for (const stepName of groupSteps) {\n const s = definition.steps.find((st) => st.name === stepName)!\n const nodeId = this.sanitizeNodeId(stepName)\n const label = this.buildStepLabel(s, showDetails)\n const shape = s.commit ? `[${label}]` : `(${label})`\n lines.push(` ${nodeId}${shape}`)\n\n if (s.compensate) {\n lines.push(` ${nodeId}_comp[🔄 Compensate]`)\n lines.push(` ${nodeId} -.->|on failure| ${nodeId}_comp`)\n }\n }\n\n lines.push(` end`)\n lines.push(` ${prevNode} --> ${groupName}`)\n prevNode = groupName\n } else if (!step.parallelGroup) {\n // Regular sequential step\n const nodeId = this.sanitizeNodeId(step.name)\n const label = this.buildStepLabel(step, showDetails)\n const shape = step.commit ? `[${label}]` : `(${label})`\n\n lines.push(` ${nodeId}${shape}`)\n\n // Handle conditional steps\n if (step.when) {\n const condId = `${nodeId}_cond`\n lines.push(` ${condId}{Condition?}`)\n lines.push(` ${prevNode} --> ${condId}`)\n lines.push(` ${condId} -->|yes| ${nodeId}`)\n lines.push(` ${condId} -->|no| ${this.getNextNodeId(definition.steps, i)}`)\n prevNode = nodeId\n } else {\n lines.push(` ${prevNode} --> ${nodeId}`)\n prevNode = nodeId\n }\n\n // Show compensation\n if (step.compensate) {\n lines.push(` ${nodeId}_comp[🔄 Compensate]`)\n lines.push(` ${nodeId} -.->|on failure| ${nodeId}_comp`)\n }\n }\n }\n\n lines.push(` ${prevNode} --> End([End])`)\n\n // Add styling\n lines.push('')\n lines.push(' classDef commitStep fill:#e1f5e1,stroke:#4caf50,stroke-width:2px')\n lines.push(' classDef normalStep fill:#e3f2fd,stroke:#2196f3,stroke-width:2px')\n lines.push(\n ' classDef compensateStep fill:#fff3e0,stroke:#ff9800,stroke-width:1px,stroke-dasharray: 5 5'\n )\n\n return lines.join('\\n')\n }\n\n /**\n * Generates a Mermaid flowchart from a workflow execution state.\n * Overlays execution status (completed, failed, compensated) on top of the structure.\n *\n * @param definition - The workflow definition\n * @param state - The current execution state\n * @param options - Customization options\n * @returns Mermaid diagram syntax with status overlay\n *\n * @example\n * ```typescript\n * const diagram = generator.generateFromState(workflow, executionState, {\n * showStatus: true,\n * showDetails: true\n * })\n * ```\n */\n generateFromState<TInput, TData>(\n definition: WorkflowDefinition<TInput, TData>,\n state: WorkflowState<TInput, TData>,\n options: MermaidOptions = {}\n ): string {\n const {\n showDetails = true,\n showStatus = true,\n showParallelGroups = true,\n theme = 'default',\n } = options\n\n const lines: string[] = []\n lines.push(`%%{init: {'theme':'${theme}'}}%%`)\n lines.push('flowchart TD')\n lines.push(` Start([Start: ${definition.name}])`)\n lines.push(` Start:::started`)\n\n let prevNode = 'Start'\n const parallelGroups = new Map<string, string[]>()\n const executionMap = new Map<string, StepExecution>()\n\n // Build execution map\n for (const exec of state.history) {\n executionMap.set(exec.name, exec)\n }\n\n // Identify parallel groups\n if (showParallelGroups) {\n for (const step of definition.steps) {\n if (step.parallelGroup) {\n const group = parallelGroups.get(step.parallelGroup) || []\n group.push(step.name)\n parallelGroups.set(step.parallelGroup, group)\n }\n }\n }\n\n const processedGroups = new Set<string>()\n\n for (let i = 0; i < definition.steps.length; i++) {\n const step = definition.steps[i]!\n\n // Handle parallel groups\n if (step.parallelGroup && showParallelGroups) {\n if (processedGroups.has(step.parallelGroup)) {\n continue\n }\n processedGroups.add(step.parallelGroup)\n\n const groupSteps = parallelGroups.get(step.parallelGroup)!\n const groupName = `ParallelGroup_${step.parallelGroup}`\n\n lines.push(` subgraph ${groupName}[\" \"]`)\n lines.push(` direction LR`)\n\n for (const stepName of groupSteps) {\n const s = definition.steps.find((st) => st.name === stepName)!\n const nodeId = this.sanitizeNodeId(stepName)\n const exec = executionMap.get(stepName)\n const label = this.buildStepLabelWithStatus(s, exec, showDetails, showStatus)\n const shape = s.commit ? `[${label}]` : `(${label})`\n\n lines.push(` ${nodeId}${shape}`)\n\n if (exec && showStatus) {\n const statusClass = this.getStatusClass(exec.status)\n lines.push(` ${nodeId}:::${statusClass}`)\n }\n\n if (s.compensate && exec?.status === 'compensated') {\n lines.push(` ${nodeId}_comp[🔄 Compensated]`)\n lines.push(` ${nodeId}_comp:::compensated`)\n lines.push(` ${nodeId} -.->|rolled back| ${nodeId}_comp`)\n }\n }\n\n lines.push(` end`)\n lines.push(` ${prevNode} --> ${groupName}`)\n prevNode = groupName\n } else if (!step.parallelGroup) {\n const nodeId = this.sanitizeNodeId(step.name)\n const exec = executionMap.get(step.name)\n const label = this.buildStepLabelWithStatus(step, exec, showDetails, showStatus)\n const shape = step.commit ? `[${label}]` : `(${label})`\n\n lines.push(` ${nodeId}${shape}`)\n\n if (exec && showStatus) {\n const statusClass = this.getStatusClass(exec.status)\n lines.push(` ${nodeId}:::${statusClass}`)\n }\n\n if (step.when) {\n const condId = `${nodeId}_cond`\n const skipped = exec?.status === 'skipped'\n lines.push(` ${condId}{Condition?}`)\n lines.push(` ${prevNode} --> ${condId}`)\n\n if (skipped) {\n lines.push(` ${condId}:::skipped`)\n lines.push(` ${condId} -->|no| ${nodeId}`)\n } else {\n lines.push(` ${condId} -->|yes| ${nodeId}`)\n }\n } else {\n lines.push(` ${prevNode} --> ${nodeId}`)\n }\n\n if (step.compensate && exec?.status === 'compensated') {\n lines.push(` ${nodeId}_comp[🔄 Compensated]`)\n lines.push(` ${nodeId}_comp:::compensated`)\n lines.push(` ${nodeId} -.->|rolled back| ${nodeId}_comp`)\n }\n\n prevNode = nodeId\n }\n }\n\n const finalStatus = this.getWorkflowFinalStatus(state.status)\n lines.push(` ${prevNode} --> End([End: ${finalStatus}])`)\n lines.push(` End:::${this.getStatusClass(state.status)}`)\n\n // Add status-based styling\n lines.push('')\n lines.push(' classDef started fill:#e3f2fd,stroke:#2196f3,stroke-width:2px')\n lines.push(' classDef completed fill:#e8f5e9,stroke:#4caf50,stroke-width:2px')\n lines.push(' classDef failed fill:#ffebee,stroke:#f44336,stroke-width:2px')\n lines.push(' classDef compensated fill:#fff3e0,stroke:#ff9800,stroke-width:2px')\n lines.push(\n ' classDef skipped fill:#f5f5f5,stroke:#9e9e9e,stroke-width:1px,stroke-dasharray: 5 5'\n )\n lines.push(' classDef pending fill:#fafafa,stroke:#bdbdbd,stroke-width:1px')\n lines.push(\n ' classDef suspended fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px,stroke-dasharray: 3 3'\n )\n\n return lines.join('\\n')\n }\n\n private sanitizeNodeId(name: string): string {\n return name.replace(/[^a-zA-Z0-9_]/g, '_')\n }\n\n private buildStepLabel(step: any, showDetails: boolean): string {\n let label = step.name\n\n if (showDetails) {\n const meta: string[] = []\n if (step.commit) {\n meta.push('📝')\n }\n if (step.retries && step.retries > 0) {\n meta.push(`↻${step.retries}`)\n }\n if (step.timeout) {\n meta.push(`⏱${step.timeout}ms`)\n }\n if (step.when) {\n meta.push('❓')\n }\n\n if (meta.length > 0) {\n label += `<br/><small>${meta.join(' ')}</small>`\n }\n }\n\n return label\n }\n\n private buildStepLabelWithStatus(\n step: any,\n exec: StepExecution | undefined,\n showDetails: boolean,\n showStatus: boolean\n ): string {\n let label = step.name\n\n if (showStatus && exec) {\n label += `<br/><small><b>${exec.status.toUpperCase()}</b></small>`\n\n if (exec.duration) {\n label += `<br/><small>${exec.duration}ms</small>`\n }\n\n if (exec.retries > 0) {\n label += `<br/><small>Retries: ${exec.retries}</small>`\n }\n } else if (showDetails) {\n const meta: string[] = []\n if (step.commit) {\n meta.push('📝')\n }\n if (step.retries && step.retries > 0) {\n meta.push(`↻${step.retries}`)\n }\n if (step.timeout) {\n meta.push(`⏱${step.timeout}ms`)\n }\n if (step.when) {\n meta.push('❓')\n }\n\n if (meta.length > 0) {\n label += `<br/><small>${meta.join(' ')}</small>`\n }\n }\n\n return label\n }\n\n private getStatusClass(status: string): string {\n switch (status) {\n case 'completed':\n return 'completed'\n case 'failed':\n return 'failed'\n case 'compensated':\n case 'compensating':\n return 'compensated'\n case 'skipped':\n return 'skipped'\n case 'suspended':\n return 'suspended'\n case 'running':\n return 'started'\n default:\n return 'pending'\n }\n }\n\n private getWorkflowFinalStatus(status: string): string {\n switch (status) {\n case 'completed':\n return '✅ Completed'\n case 'failed':\n return '❌ Failed'\n case 'rolled_back':\n return '🔄 Rolled Back'\n case 'suspended':\n return '⏸ Suspended'\n default:\n return status\n }\n }\n\n private getNextNodeId(steps: any[], currentIndex: number): string {\n const nextStep = steps[currentIndex + 1]\n return nextStep ? this.sanitizeNodeId(nextStep.name) : 'End'\n }\n}\n"]}