Appearance
BPMN Store Architecture
This document explains the state management architecture for BPMN panels, including data flow, change detection, and backend persistence.
Overview
The BPMN store (useBpmnPanelsStore) is a Pinia store that manages state for all BPMN workflow diagram panels. It coordinates between:
- UI Layer:
@mes/bpmnrenderer component - Backend API: Method run endpoints for graph data and persistence
- Action Modules: Specialized handlers for create/update/delete operations
Store Structure
State Schema
Each panel maintains its own state object:
typescript
type PanelState = {
loading: boolean; // API request in progress
response: GraphStateResponse; // Raw backend response (3-level nested array)
error: unknown | null; // Last error if any
lastModel?: BpmnRendererInput; // Snapshot for change detection
newNodeData?: {
// Temporary data during node creation
tempId: string;
position: { x: number; y: number };
};
lastCreatedNodeGuid?: string; // GUID of most recently created node
viewbox?: {
// Saved canvas position
x: number;
y: number;
zoom: number;
};
};Getters
bpmnJsonModel(panelId)
Returns the computed BPMN model ready for @mes/bpmn rendering.
- Delegates to
useBpmnRendererInputcomposable - Transforms raw
responseintoBpmnRendererInputformat - Computed reactively - updates when response changes
- Returns
undefinedif no response available
lastModel(panelId)
Returns the most recent snapshot of the BPMN model.
- Used for change detection by comparing with current model
- Updated after
fetchData()and after applying changes - Identifies what the user modified (positions, edges, nodes)
Actions
ensurePanelState(panelId)
Initializes panel state if not already present.
- Creates default state structure
- Safe to call multiple times (no-op if exists)
- Prevents undefined access errors
cleanupPanelTransientState(panelId)
Cleans up transient state while preserving persistent data.
- Called on: Component unmount
- Preserves:
viewbox(user's zoom/pan position) - Clears:
response, loading/error flags, creation data,lastModel - Purpose: Prevent memory leaks and state pollution between panels
setLastModel({ panelId, model })
Captures current model state as baseline for change detection.
- Called after
fetchData()completes - Called after applying changes
- Maintains sync between backend state and local model
fetchData({ panelId })
Fetches BPMN graph data from backend via method run API.
Process:
- Validates panel configuration and request settings
- Resolves object GUIDs (from config or selected object events)
- Builds
MethodRunRequestbody - Executes POST to
/api/methods/run - Stores response in state
- Initializes
lastModelfrom computed model
Object Resolution:
- Uses
panel.request.objectsif provided - Falls back to
useSelectedObjectForEvent()ifgetSelectedObjectForEventIdconfigured - Filters falsy values from objects array
applyBpmnChanges({ panelId, previous, current })
Detects and persists user modifications to the BPMN graph.
Process:
- Compares
previousvscurrentmodel to identify changes processBpmnChanges()categorizes changes into typed events- Dispatches each event to appropriate action handler
- Action handlers make backend API calls to persist changes
Supported Change Events:
update:nodesPositions- Node drag/drop position updatescreate:nodes- New workflow steps addeddelete:nodes- Workflow steps removedcreate:edges- New connections between nodesdelete:edges- Connections removedupdate:edges- Edge source/target reconnection
Event Processing Order:
- Deletions (nodes, edges)
- Position updates
- Edge updates (reconnection)
- Creations (edges, nodes)
This order ensures referential integrity (delete before create).
Error Handling:
- Logs warning to console
- Shows error toast with localized message
- Does not throw (allows other operations to continue)
requestDeleteNodes({ panelId, bpmnNodes })
Delegates node deletion to action module.
- Exposed as store action for external callers (e.g., confirmation dialogs)
- Directly calls
requestDeleteNodes()action module - Used when deletion needs to be triggered outside of
applyBpmnChanges()flow
Data Flow
Initial Load
1. Component mount
↓
2. fetchData({ panelId })
↓
3. POST /api/methods/run with parent/method/objects/params
↓
4. Backend returns GraphStateResponse (nested array structure)
↓
5. Store saves response
↓
6. useBpmnRendererInput transforms response → BpmnRendererInput
↓
7. setLastModel() captures snapshot
↓
8. Component renders @mes/bpmn with modelChange Detection & Persistence
1. User interaction (drag, connect, create, delete)
↓
2. @mes/bpmn emits bpmn:changed event
↓
3. Component calls applyBpmnChanges(previous, current)
↓
4. processBpmnChanges() diffs models → typed events
↓
5. Switch statement routes events to action modules:
- update:nodesPositions → updateNodesPositions()
- create:edges → requestCreateEdges()
- delete:edges → requestDeleteEdges()
- create:nodes → requestCreateNodes()
- delete:nodes → requestDeleteNodes()
- update:edges → requestUpdateEdges()
↓
6. Action modules make backend API calls
↓
7. Success: changes persisted
↓
8. Failure: error toast shown, state remains unchangedIntegration Points
Composables
useBpmnRendererInput(panelId)
Location: ../composables/use-bpmn-renderer-input.ts
- Transforms raw
GraphStateResponseintoBpmnRendererInput - Extracts nodes and edges from nested array structure
- Matches backend objects to BPMN node types
- Builds node cards with field data
- Returns computed model that updates reactively
Action Modules
Location: ./actions/
All action modules follow similar patterns:
- Accept typed payload
- Build backend API request
- Execute request via
$fetchApi - Handle success/error states
- Show toast notifications
updateNodesPositions(payload)
Updates x_pos, y_pos attributes for moved nodes.
requestCreateEdges(payload)
Creates new link objects between nodes.
requestDeleteEdges(edgeGuids)
Deletes link objects by GUID.
requestCreateNodes({ panelId, bpmnNodes })
Creates new node objects with position attributes.
- Matches toolbar item by
node.data._bpmnTaskId - Executes
onObjectCreateactions from toolbar item events - Supports direct request creation without modal (
editor.mode = "direct-request") - Updates node GUID after object creation
requestDeleteNodes({ panelId, bpmnNodes })
Deletes node objects by GUID.
- Confirmation dialog shown before calling
- Removes objects from backend
- Graph refetch triggered after deletion
requestUpdateEdges(payload)
Updates edge source/target when reconnected.
Utilities
processBpmnChanges(previous, current)
Location: ./utils/process-bpmn-changes.ts
- Diffs two BPMN models using
just-diff - Categorizes changes into typed events
- Returns array of
ProcessBpmnChangesResult
Detection Strategy:
- Compares node/edge ID sets to detect creates/deletes
- Inspects diff paths to detect position changes
- Validates changes before including in results
State Management Patterns
Transient vs Persistent State
Transient (cleared on unmount):
response- Large graph dataloading,error- Request status flagsnewNodeData,lastCreatedNodeGuid- Temporary creation statelastModel- Can be regenerated from response
Persistent (preserved on unmount):
viewbox- User's zoom/pan position
This prevents memory leaks while maintaining good UX (canvas position preserved).
Change Detection
Change detection uses snapshot comparison:
- After fetch, capture
lastModelsnapshot - User interacts with UI
- Component watches for model changes
- When changed, compare current vs
lastModel - Persist only the delta
- Update
lastModelto current
This approach:
- Minimizes backend calls (only changed data sent)
- Preserves undo/redo potential
- Enables optimistic updates
Error Boundaries
Errors are handled at multiple levels:
- Store actions: Catch, log, toast, don't throw
- Action modules: Validate payloads, catch API errors
- Components: Can handle specific error states
This prevents one failed operation from breaking the entire panel.
Best Practices
When to Use Store vs Composable
Use Store for:
- Persistent state across component lifecycles
- Shared state between multiple panels
- Backend API interactions
- Change tracking and history
Use Composable for:
- Computed transformations (like
useBpmnRendererInput) - Reactive derivations from store state
- Component-specific logic
- One-time calculations
Panel State Cleanup
Always call cleanupPanelTransientState() on unmount:
typescript
onBeforeUnmount(() => {
useBpmnPanelsStore().cleanupPanelTransientState(panelId);
});This prevents memory leaks from accumulated response data.
Object GUID Resolution
When panel needs object context:
typescript
// Preferred: Config-based
panel.request.objects = ['object-guid-1', 'object-guid-2'];
// Fallback: Event-based
panel.request.getSelectedObjectForEventId = 'main:object-selected';Event-based is useful for dynamic object selection (e.g., from tree navigation).
Related Documentation
- BPMN Allowed Items Processing - Toolbar configuration
- API Reference - Type definitions
- Panel Types - Configuration schema