Core Concepts
Workflows
Workflows allow you to automate business processes by chaining together actions, conditions, and integrations. You can trigger workflows via API, webhooks, or programmatically from your application code.
Triggering Workflows Programmatically
Use the triggerWorkflow function to start a workflow execution from anywhere in your server-side code, such as DataSource hooks, server actions, or background jobs.
Import
import { triggerWorkflow } from '@wayvo-ai/core/server';
Basic Usage
const result = await triggerWorkflow({
workflowId: 'your-workflow-id',
userName: 'user@example.com',
input: { key: 'value' },
});
console.log('Execution started:', result.executionId);
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workflowId | string | Yes | The UUID of the workflow to trigger |
userName | string | Yes | The username to run the workflow as (used for integration validation) |
input | Record<string, unknown> | No | Input payload passed to the workflow's trigger node |
version | number | No | Reserved for future use |
client | PgPoolClient | No | Database client to reuse an existing transaction |
Return Value
interface TriggerWorkflowResult {
executionId: string; // UUID for tracking the execution
status: 'running'; // Initial status
}
Using in DataSource Hooks
A common use case is triggering workflows when data changes. Use the afterInsert, afterUpdate, or afterDelete hooks to start a workflow in response to database operations.
Example: Trigger Workflow After Insert
import { triggerWorkflow } from '@wayvo-ai/core/server';
import type { DataSource } from '@wayvo-ai/core/common';
export const OrderDS: DataSource<Order> = {
// ... other configuration ...
afterInsert: async ({ rows, session, client }) => {
for (const row of rows) {
// Trigger the order processing workflow
const result = await triggerWorkflow({
workflowId: 'order-processing-workflow-id',
userName: session.user.userName,
input: {
orderId: row.id,
customerId: row.customerId,
totalAmount: row.totalAmount,
},
client, // Reuse the existing transaction
});
console.log(`Started workflow for order ${row.id}:`, result.executionId);
}
return rows;
},
};
Example: Trigger Workflow on Status Change
afterUpdate: async ({ rows, previousRows, session, client }) => {
if (!previousRows) return rows;
for (let i = 0; i < rows.length; i++) {
const prev = previousRows[i];
const row = rows[i];
// Only trigger when status changes to 'approved'
if (prev?.status !== row.status && row.status === 'approved') {
await triggerWorkflow({
workflowId: 'approval-notification-workflow-id',
userName: session.user.userName,
input: {
recordId: row.id,
approvedBy: session.user.userName,
approvedAt: new Date().toISOString(),
},
client,
});
}
}
return rows;
},
Using in Server Actions
You can also trigger workflows from custom server actions:
'use server';
import { triggerWorkflow } from '@wayvo-ai/core/server';
import { auth } from '@/auth';
export async function startOnboardingWorkflow(userId: string) {
const session = await auth();
if (!session?.user) {
throw new Error('Unauthorized');
}
const result = await triggerWorkflow({
workflowId: 'user-onboarding-workflow-id',
userName: session.user.userName,
input: {
userId,
startedAt: new Date().toISOString(),
},
});
return { executionId: result.executionId };
}
Error Handling
The triggerWorkflow function will throw an error if:
- The workflow is not found
- The workflow contains integration references that don't belong to the specified user
try {
const result = await triggerWorkflow({
workflowId: 'workflow-id',
userName: session.user.userName,
input: {},
});
} catch (error) {
if (error.message.includes('Workflow not found')) {
// Handle missing workflow
} else if (error.message.includes('invalid integration references')) {
// Handle permission issue
}
}
Tracking Execution Status
The returned executionId can be used to track the workflow execution status:
import { queryDataSource } from '@wayvo-ai/core/server';
import { getDataSource } from '@/lib/server/ds/defs';
import type { WorkflowExecutions } from '@/lib/common/ds/types/core/WorkflowExecutions';
// Query execution status
const executionsDS = getDataSource<WorkflowExecutions>('WorkflowExecutions');
const result = await queryDataSource(client, session, executionsDS, {
filter: [{ id: { is: executionId } }],
});
const execution = result.rows[0];
console.log('Status:', execution?.status); // 'running' | 'success' | 'error' | 'paused'
Execution Lifecycle
- running - Workflow is actively executing
- paused - Workflow is waiting (e.g., sleep, approval, or wait for event node)
- success - Workflow completed successfully
- error - Workflow failed after retries
Workflow Control Nodes
Wayvo provides built-in nodes for pausing workflows and waiting for external input before continuing.
Request Approval Node
The Request Approval node pauses a workflow until a human approver submits a decision (approve or reject). This is useful for processes that require human oversight.
Configuration
| Field | Type | Required | Description |
|---|---|---|---|
title | template-input | Yes | Title shown to approvers |
description | template-textarea | No | Detailed description of what needs approval |
approvalType | select | No | single (first decision wins), any (first approval wins), all (unanimous required) |
approvers | template-input | No | Comma-separated usernames or JSON array: ["user1", "user2"] |
approverRoles | template-input | No | Comma-separated roles or JSON array: ["admin", "manager"] |
requiredApprovals | number | No | Number of approvals needed (for all type) |
expiresIn | template-input | No | Expiration duration: 7d, 24h, 30m |
onRejection | select | No | error (stop workflow) or continue (proceed anyway) |
onExpiration | select | No | error (stop workflow) or continue (proceed anyway) |
contextData | template-textarea | No | JSON object with additional context for approvers |
sendNotification | select | No | Send email notification to approvers |
emailSubject | template-input | No | Custom email subject (supports {{title}} placeholder) |
approvalUrl | template-input | No | Custom approval URL with placeholders |
Output Fields
| Field | Description |
|---|---|
decision | The final decision: approved or rejected |
decisions | Array of all decisions made by approvers |
approvalRequestId | UUID of the approval request |
Example Workflow
[Trigger] → [Process Order] → [Request Approval] → [Send Confirmation]
↓
Approver reviews at
/workflows/approvals/{id}
Approval Types
- Single: First decision wins (approve or reject)
- Any: First approval wins; all must reject to reject the request
- All: Any rejection fails; all required approvals must approve
Viewing Pending Approvals
Approvers can view and respond to pending approvals at /workflows/approvals. The UI shows:
- Approval title and description
- Context data
- Previous decisions (for multi-approver scenarios)
- Approve/Reject buttons with optional comments
Wait for Event Node
The Wait for Event node pauses a workflow until an external system signals that an event has occurred. This enables integration with 3rd party systems for async operations like payment confirmations, webhook callbacks, or background job completions.
Configuration
| Field | Type | Required | Description |
|---|---|---|---|
eventNamespace | template-input | Yes | Namespace for scoping events (e.g., orders, payments) |
eventKey | template-input | Yes | Unique key within namespace (e.g., order-{{Trigger.orderId}}-paid) |
title | template-input | No | Human-readable description |
description | template-textarea | No | Detailed description of expected event |
expiresIn | template-input | No | Expiration duration: 7d, 24h, 30m |
onExpiration | select | No | error (stop workflow) or continue (proceed anyway) |
contextData | template-textarea | No | JSON object with additional context |
Output Fields
| Field | Description |
|---|---|
status | Event status: received or expired |
payload | Data payload from the external signal |
eventNamespace | The event namespace |
eventKey | The event key that was matched |
waitEventId | UUID of the wait event record |
receivedAt | ISO timestamp when the event was received |
receivedFrom | Source identifier of the signal (API key name) |
Signal API
External systems resume the workflow by calling the signal API:
POST /api/workflow/events/signal
Authorization: Bearer <workflow-api-key>
Content-Type: application/json
{
"eventNamespace": "orders",
"eventKey": "order-12345-paid",
"payload": {
"transactionId": "txn_abc123",
"amount": 99.99,
"paidAt": "2024-01-15T10:30:00Z"
}
}
Response:
{
"success": true,
"waitEventId": "uuid-of-wait-event",
"executionId": "uuid-of-workflow-execution",
"workflowResumed": true
}
Integration Pattern
Since the eventNamespace and eventKey are configured by the user, the workflow should notify the 3rd party system before the Wait for Event node using existing nodes (Call Action, HTTP request, DataSource insert, etc.):
[Trigger] → [Call Action: Create Payment Intent] → [Wait for Event] → [Fulfill Order]
↓ ↑
Notify Stripe with Stripe webhook calls
namespace: "payments" POST /api/workflow/events/signal
key: "payment-{{orderId}}" with matching namespace/key
Example: Payment Processing
1. Workflow Design:
[Order Created Trigger]
↓
[Create Stripe Payment Intent] ← Sends order ID and callback info to Stripe
↓
[Wait for Event] ← namespace: "payments", key: "order-{{Trigger.orderId}}"
↓
[Process Payment Result] ← Access payload via {{Wait for Event.payload.amount}}
↓
[Send Receipt Email]
2. Stripe Webhook Handler:
// Your webhook endpoint that Stripe calls
export async function POST(req: Request) {
const event = await req.json();
if (event.type === 'payment_intent.succeeded') {
// Signal the waiting workflow
await fetch(`${process.env.APP_URL}/api/workflow/events/signal`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.WORKFLOW_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
eventNamespace: 'payments',
eventKey: `order-${event.data.object.metadata.orderId}`,
payload: {
transactionId: event.data.object.id,
amount: event.data.object.amount / 100,
currency: event.data.object.currency,
},
}),
});
}
return Response.json({ received: true });
}
Creating Workflow API Keys
To call the signal API, external systems need a workflow API key. Create keys via the Wayvo UI or programmatically:
// POST /api/api-keys/create
{
"name": "Stripe Webhook Key"
}
// Response includes the key (shown only once):
{
"id": "uuid",
"name": "Stripe Webhook Key",
"key": "wfb_xxxxx-xxxxx-xxxxx",
"keyPrefix": "wfb_xxxxx"
}
Event Key Uniqueness
Event keys are unique within a namespace. Only one workflow can wait for a specific namespace + key combination at a time. If you need multiple workflows to respond to the same event, use different keys or consider using the Trigger Workflow node to fan out.
Best Practices
Pass the Database Client
When calling from DataSource hooks, always pass the client parameter to reuse the existing transaction:
await triggerWorkflow({
workflowId: 'workflow-id',
userName: session.user.userName,
input: { /* ... */ },
client, // Important: reuses the hook's transaction
});
Use Meaningful Input Data
Pass relevant data in the input object that your workflow nodes will need:
input: {
// Include IDs for lookups
recordId: row.id,
userId: session.user.userName,
// Include data to avoid extra queries in the workflow
customerEmail: row.email,
orderTotal: row.totalAmount,
// Include context
triggeredAt: new Date().toISOString(),
triggeredBy: session.user.displayName,
}
Handle Errors Gracefully
Workflow execution happens asynchronously. The triggerWorkflow function returns immediately after creating the execution record. Handle setup errors but don't expect workflow completion errors:
try {
const result = await triggerWorkflow({ /* ... */ });
// Execution started - workflow runs in background
return { success: true, executionId: result.executionId };
} catch (error) {
// Setup failed (workflow not found, permission error, etc.)
console.error('Failed to start workflow:', error);
return { success: false, error: error.message };
}
Custom Workflow Nodes
You can register custom workflow actions that appear in the workflow builder and can be executed by the workflow engine. Custom plugins are registered via configuration - no side-effect imports needed.
Plugin Structure
Custom workflow plugins have two parts:
- Server Plugin (
WorkflowServerPlugin) - Step functions and labels for execution - Client Plugin (
WorkflowClientPlugin) - Integration definition for the workflow builder UI
Step 1: Create the Step Functions
Create step functions that implement your custom logic:
// src/lib/workflow/plugins/my-integration/steps.ts
import 'server-only';
import type { StepFunction } from '@wayvo-ai/core/server';
export const myCustomStep: StepFunction = async (input) => {
const { message, userId } = input;
// Your custom logic here
const result = await doSomething(message, userId);
return {
success: true,
data: result,
};
};
Step 2: Create the Server Plugin
Define the server plugin with step importers and action labels:
// src/lib/workflow/plugins/my-integration/server.ts
import 'server-only';
import type { WorkflowServerPlugin } from '@wayvo-ai/core/server';
import { myCustomStep } from './steps';
export const myServerPlugin: WorkflowServerPlugin = {
stepImporters: [
{ actionId: 'my-integration/my-action', importer: { stepFunction: myCustomStep } },
],
actionLabels: [
{ actionId: 'my-integration/my-action', label: 'My Custom Action' },
],
};
Step 3: Create the Client Plugin
Define the client plugin with the integration for the workflow builder UI:
// src/lib/workflow/plugins/my-integration/client.tsx
import type { WorkflowClientPlugin } from '@wayvo-ai/core/ui';
import { MyIcon } from './icon';
export const myClientPlugin: WorkflowClientPlugin = {
integration: {
type: 'my-integration',
label: 'My Integration',
description: 'Custom integration for my app',
icon: MyIcon,
formFields: [], // No credentials needed for this example
actions: [
{
slug: 'my-action',
label: 'My Custom Action',
description: 'Does something custom',
category: 'My Integration', // Must match the integration label
stepFunction: 'myCustomStep',
stepImportPath: 'my-integration',
configFields: [
{
key: 'message',
label: 'Message',
type: 'template-input',
placeholder: 'Enter a message',
required: true,
},
],
outputFields: [
{ field: 'success', description: 'Whether the action succeeded' },
{ field: 'data', description: 'The result data' },
],
},
],
},
};
Important: The
categoryfield in each action must match the integration'slabelfor the category dropdown to work correctly.
Step 4: Register via Configuration
Add the plugins to your ServerConfig and AppProvider:
// src/lib/server/init/server-config.ts
import type { ServerConfig } from '@wayvo-ai/core/types';
import { myServerPlugin } from '@/lib/workflow/plugins/my-integration/server';
const serverConfig: ServerConfig = {
// ... other config
workflowPlugins: [myServerPlugin],
};
export default serverConfig;
// src/app/(secure)/layout.tsx
import { AppProvider } from '@wayvo-ai/core/ui';
import { myClientPlugin } from '@/lib/workflow/plugins/my-integration/client';
export default function Layout({ children }) {
return (
<AppProvider
// ... other props
workflowPlugins={[myClientPlugin]}
>
{children}
</AppProvider>
);
}
Complete Example
Here's a complete example of a custom "SMS" integration:
Step Functions
// src/lib/workflow/plugins/sms/steps.ts
import 'server-only';
import type { StepFunction } from '@wayvo-ai/core/server';
export const sendSmsStep: StepFunction = async (input) => {
const { phoneNumber, message } = input;
const response = await fetch('https://api.smsprovider.com/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ to: phoneNumber, body: message }),
});
if (!response.ok) {
return { success: false, error: 'Failed to send SMS' };
}
const data = await response.json();
return { success: true, messageId: data.id };
};
Server Plugin
// src/lib/workflow/plugins/sms/server.ts
import 'server-only';
import type { WorkflowServerPlugin } from '@wayvo-ai/core/server';
import { sendSmsStep } from './steps';
export const smsServerPlugin: WorkflowServerPlugin = {
stepImporters: [
{ actionId: 'sms/send', importer: { stepFunction: sendSmsStep } },
],
actionLabels: [
{ actionId: 'sms/send', label: 'Send SMS' },
],
};
Client Plugin
// src/lib/workflow/plugins/sms/client.tsx
import type { WorkflowClientPlugin } from '@wayvo-ai/core/ui';
import { MessageSquare } from 'lucide-react';
export const smsClientPlugin: WorkflowClientPlugin = {
integration: {
type: 'sms',
label: 'SMS',
description: 'Send SMS messages',
icon: MessageSquare,
formFields: [
{
id: 'apiKey',
label: 'API Key',
type: 'password',
placeholder: 'Your SMS provider API key',
configKey: 'apiKey',
},
],
actions: [
{
slug: 'send',
label: 'Send SMS',
description: 'Send an SMS message to a phone number',
category: 'SMS', // Matches integration label
stepFunction: 'sendSmsStep',
stepImportPath: 'sms',
configFields: [
{
key: 'phoneNumber',
label: 'Phone Number',
type: 'template-input',
placeholder: '+1234567890',
required: true,
},
{
key: 'message',
label: 'Message',
type: 'template-textarea',
placeholder: 'Your message here',
rows: 3,
required: true,
},
],
outputFields: [
{ field: 'success', description: 'Whether the SMS was sent' },
{ field: 'messageId', description: 'The SMS message ID' },
],
},
],
},
};
Dynamic Import for Code Splitting
For larger step implementations, use dynamic imports to reduce bundle size:
export const myServerPlugin: WorkflowServerPlugin = {
stepImporters: [
{
actionId: 'my-integration/heavy-action',
importer: {
importer: () => import('./steps/heavy-action'),
stepFunction: 'heavyActionStep', // Name of the exported function
},
},
],
};
Config Field Types
Available field types for configFields:
| Type | Description |
|---|---|
template-input | Single-line input with {{variable}} template support |
template-textarea | Multi-line textarea with template support |
text | Plain text input |
number | Numeric input (min optional) |
select | Dropdown with predefined options |
combobox | Searchable dropdown; options from options, optionsSource, or getOptions(config) |
schema-builder | JSON schema builder for structured output |
single-row-update | Single-row form: primary keys + dynamic attribute-value pairs. Use singleRowMode for update/insert/delete. |
match-builder | Dynamic attribute + value pairs, serializes to a JSON object (e.g. for DataSource query match) |
multi-select | Multi-select storing a JSON array of selected values; options from options or getOptions(config) |
Combobox
Searchable dropdown. Options can be static, from a built-in source, or from a callback:
// Static options
{ key: 'priority', label: 'Priority', type: 'combobox', options: [{ value: 'high', label: 'High' }, ...] }
// Built-in source (e.g. datasources)
{ key: 'datasourceId', label: 'DataSource', type: 'combobox', optionsSource: 'datasources' }
// Dynamic options from config (e.g. depends on another field)
{
key: 'attribute',
label: 'Attribute',
type: 'combobox',
getOptions: async (config) => {
const id = (config?.datasourceId as string) || '';
if (!id) return [];
const attrs = await getDataSourceAttributes(id);
return attrs.map((a) => ({ value: a.code, label: a.name }));
},
}
Optional: placeholder, searchPlaceholder.
Single-row-update
Single-row form for DataSource update, insert, or delete. Renders primary key fields first (from the selected DataSource), then optional “Fields to update” (attribute + template value pairs). Use singleRowMode to control behavior:
singleRowMode | Config key | Output | UI |
|---|---|---|---|
'update' (default) | row | One JSON object with _status: 'U' | Primary keys + Fields to update |
'insert' | rows | JSON array of one row (no _status; step adds 'I') | Primary keys + Fields to update |
'delete' | rows | JSON array of one row (step adds _status: 'D') | Primary keys only (“Fields to update” hidden) |
Requires a DataSource to be selected (e.g. via a datasourceId combobox) so attributes can be loaded.
// Update: one row object
{ key: 'row', label: 'Record to update', type: 'single-row-update', required: true }
// Insert: one row serialized as [row]
{ key: 'rows', label: 'Record to insert', type: 'single-row-update', singleRowMode: 'insert', required: true }
// Delete: identify record by primary key(s), serialized as [row]
{ key: 'rows', label: 'Record to delete', type: 'single-row-update', singleRowMode: 'delete', required: true }
Match-builder
Dynamic list of attribute + value pairs, stored as a single JSON object (e.g. for DataSource query match). Options for the attribute dropdown can come from getOptions(config) (e.g. DataSource attributes). Values support template syntax.
Multi-select
Multi-select that stores the selection as a JSON array string. Options from options or getOptions(config). Use optionsDependencyKeys so options are refetched only when those config values change (e.g. avoid refetch when the field’s own value changes):
{
key: 'select',
label: 'Select fields',
type: 'multi-select',
placeholder: 'Select fields to return (empty = all)',
searchPlaceholder: 'Search attributes...',
optionsDependencyKeys: ['datasourceId'],
getOptions: async (config) => {
const id = (config?.datasourceId as string) || '';
if (!id) return [];
const attrs = await getDataSourceAttributes(id);
return attrs.map((a) => ({ value: a.code, label: a.name }));
},
}
Optional: placeholder, searchPlaceholder.
Conditional Fields
Show fields conditionally based on other field values:
configFields: [
{
key: 'notificationType',
label: 'Type',
type: 'select',
options: [
{ value: 'email', label: 'Email' },
{ value: 'sms', label: 'SMS' },
],
},
{
key: 'emailAddress',
label: 'Email Address',
type: 'template-input',
showWhen: { field: 'notificationType', equals: 'email' },
},
{
key: 'phoneNumber',
label: 'Phone Number',
type: 'template-input',
showWhen: { field: 'notificationType', equals: 'sms' },
},
],
Displaying Workflow Execution Status
Wayvo Core provides React components to display workflow execution status in your application UI. These components show a visual canvas of the workflow with real-time status updates for each node.
Components
| Component | Description |
|---|---|
WorkflowExecutionViewer | Displays the workflow canvas with node statuses, timeline, and details panel |
WorkflowExecutionViewerDialog | A dialog wrapper that shows the viewer in a popup |
ReactFlowProvider | Required wrapper from @xyflow/react for the canvas to work |
Import
import {
WorkflowExecutionViewer,
WorkflowExecutionViewerDialog,
ReactFlowProvider,
} from '@wayvo-ai/core/ui';
WorkflowExecutionViewer
The WorkflowExecutionViewer component displays the workflow execution with:
- Visual canvas showing workflow nodes with their execution status
- Real-time updates via SSE for running executions
- Clickable nodes to view execution details and logs
- Timeline panel showing execution progress
Important: This component must be wrapped in a ReactFlowProvider.
Props
| Prop | Type | Required | Description |
|---|---|---|---|
executionId | string | Yes | The UUID of the workflow execution to display |
workflowId | string | Yes | The UUID of the workflow |
className | string | No | Additional CSS classes |
height | string | No | Container height (default: '100%') |
Basic Usage
'use client';
import { ReactFlowProvider, WorkflowExecutionViewer } from '@wayvo-ai/core/ui';
export function ExecutionStatus({ executionId, workflowId }: {
executionId: string;
workflowId: string;
}) {
return (
<ReactFlowProvider>
<WorkflowExecutionViewer
executionId={executionId}
workflowId={workflowId}
height="500px"
/>
</ReactFlowProvider>
);
}
Embedding in a Page
'use client';
import { ReactFlowProvider, WorkflowExecutionViewer } from '@wayvo-ai/core/ui';
export function OrderExecutionPage({ orderId, executionId, workflowId }: {
orderId: string;
executionId: string;
workflowId: string;
}) {
return (
<div className="flex h-screen flex-col">
<header className="border-b p-4">
<h1>Order #{orderId} - Workflow Execution</h1>
</header>
<div className="flex-1">
<ReactFlowProvider>
<WorkflowExecutionViewer
executionId={executionId}
workflowId={workflowId}
className="h-full w-full"
/>
</ReactFlowProvider>
</div>
</div>
);
}
WorkflowExecutionViewerDialog
The WorkflowExecutionViewerDialog component shows the execution viewer in a popup dialog. It handles the ReactFlowProvider internally.
Props
| Prop | Type | Required | Description |
|---|---|---|---|
executionId | string | Yes | The UUID of the workflow execution to display |
workflowId | string | Yes | The UUID of the workflow |
onClose | () => void | Yes | Callback when the dialog is closed |
Usage
'use client';
import { useState } from 'react';
import { WorkflowExecutionViewerDialog } from '@wayvo-ai/core/ui';
import { Button } from '@/components/ui/button';
export function ViewExecutionButton({ executionId, workflowId }: {
executionId: string;
workflowId: string;
}) {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button onClick={() => setIsOpen(true)}>
View Execution
</Button>
{isOpen && (
<WorkflowExecutionViewerDialog
executionId={executionId}
workflowId={workflowId}
onClose={() => setIsOpen(false)}
/>
)}
</>
);
}
Complete Example: Triggering and Viewing a Workflow
This example shows how to trigger a workflow and display its execution status:
'use client';
import { useState, useTransition } from 'react';
import { ReactFlowProvider, WorkflowExecutionViewer } from '@wayvo-ai/core/ui';
import { Button } from '@/components/ui/button';
import { startOrderWorkflow } from './actions';
export function OrderWorkflowPanel({ orderId }: { orderId: string }) {
const [executionId, setExecutionId] = useState<string | null>(null);
const [isPending, startTransition] = useTransition();
const handleTrigger = () => {
startTransition(async () => {
const result = await startOrderWorkflow(orderId);
if (result.executionId) {
setExecutionId(result.executionId);
}
});
};
if (!executionId) {
return (
<Button onClick={handleTrigger} disabled={isPending}>
{isPending ? 'Starting...' : 'Start Order Processing'}
</Button>
);
}
return (
<div className="h-[600px]">
<ReactFlowProvider>
<WorkflowExecutionViewer
executionId={executionId}
workflowId="your-order-workflow-id"
className="h-full w-full"
/>
</ReactFlowProvider>
</div>
);
}
Server action:
// actions.ts
'use server';
import { triggerWorkflow } from '@wayvo-ai/core/server';
import { auth } from '@/auth';
export async function startOrderWorkflow(orderId: string) {
const session = await auth();
if (!session?.user) throw new Error('Unauthorized');
const result = await triggerWorkflow({
workflowId: 'your-order-workflow-id',
userName: session.user.userName,
input: { orderId },
});
return { executionId: result.executionId };
}
Node Status Colors
The viewer displays nodes with different colors based on their execution status:
| Status | Description |
|---|---|
idle | Node hasn't been reached yet |
running | Node is currently executing |
paused | Node is waiting (e.g., sleep node) |
success | Node completed successfully |
error | Node failed |
Real-Time Updates
When viewing a running or paused execution, the components automatically subscribe to SSE updates. Node statuses, logs, and the timeline update in real-time as the workflow progresses.
Next Steps
- DataSources - Learn about lifecycle hooks
- Server Actions - Create custom server-side logic