Workflows
Workflows allow you to execute complex, multi-step processes with built-in durability, retries, and state management. Execute backend workflows directly from your frontend application.
Executing Workflows
Basic Execution
const execution = await ductape.workflows.execute({
workflow: 'process-order',
input: {
orderId: 'order-123',
userId: 'user-456'
}
});
console.log('Execution ID:', execution.executionId);
console.log('Status:', execution.status);
Execute with Options
const execution = await ductape.workflows.execute({
workflow: 'send-notification',
input: {
userId: 'user-123',
message: 'Your order has shipped!'
},
options: {
timeout: 60000, // 60 seconds
retries: 3,
metadata: {
source: 'frontend',
triggeredBy: 'user-action'
}
}
});
Checking Workflow Status
Get Current Status
const status = await ductape.workflows.getStatus({
executionId: 'exec-123'
});
console.log('Status:', status.status); // 'running', 'completed', 'failed', 'cancelled'
console.log('Result:', status.result);
console.log('Error:', status.error);
Poll for Completion
async function waitForCompletion(executionId: string): Promise<any> {
while (true) {
const status = await ductape.workflows.getStatus({ executionId });
if (status.status === 'completed') {
return status.result;
}
if (status.status === 'failed') {
throw new Error(status.error);
}
if (status.status === 'cancelled') {
throw new Error('Workflow was cancelled');
}
// Wait 1 second before checking again
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// Usage
const execution = await ductape.workflows.execute({
workflow: 'data-processing',
input: { dataId: '123' }
});
try {
const result = await waitForCompletion(execution.executionId);
console.log('Workflow completed:', result);
} catch (error) {
console.error('Workflow failed:', error);
}
Workflow History
Get the execution history to see all steps:
const history = await ductape.workflows.getHistory({
executionId: 'exec-123'
});
history.events.forEach(event => {
console.log(`[${event.timestamp}] ${event.type}:`, event.data);
});
Sending Signals
Send signals to running workflows:
// Start a long-running workflow
const execution = await ductape.workflows.execute({
workflow: 'approval-process',
input: { requestId: 'req-123' }
});
// Later: send an approval signal
await ductape.workflows.signal({
executionId: execution.executionId,
signal: 'approve',
data: {
approvedBy: 'user-456',
comments: 'Looks good'
}
});
Cancelling Workflows
await ductape.workflows.cancel({
executionId: 'exec-123',
reason: 'User cancelled the operation'
});
Subscribing to Workflow Updates
Get real-time updates on workflow execution:
// Connect to WebSocket first
await ductape.connect();
const subscription = ductape.workflows.subscribe({
executionId: 'exec-123',
onChange: (event) => {
console.log('Workflow event:', event);
if (event.type === 'status_changed') {
console.log('New status:', event.data.status);
}
if (event.type === 'step_completed') {
console.log('Step completed:', event.data.step);
}
if (event.type === 'completed') {
console.log('Workflow completed:', event.data.result);
}
if (event.type === 'failed') {
console.error('Workflow failed:', event.data.error);
}
}
});
// Later: unsubscribe
subscription.unsubscribe();
Listing Workflows
Get a list of workflow executions:
const result = await ductape.workflows.list({
workflow: 'process-order',
status: 'running',
limit: 20,
offset: 0
});
console.log(`Found ${result.count} running executions`);
result.executions.forEach(exec => {
console.log(`- ${exec.executionId}: ${exec.status}`);
});
Complete Order Processing Example
class OrderProcessor {
private ductape: Ductape;
constructor(ductape: Ductape) {
this.ductape = ductape;
}
async processOrder(orderId: string) {
// Show loading state
this.showLoading(true);
try {
// Execute workflow
const execution = await this.ductape.workflows.execute({
workflow: 'process-order',
input: {
orderId: orderId,
timestamp: new Date().toISOString()
}
});
// Subscribe to updates
const subscription = this.ductape.workflows.subscribe({
executionId: execution.executionId,
onChange: (event) => {
this.handleWorkflowEvent(event);
}
});
// Wait for completion
const result = await this.waitForCompletion(execution.executionId);
// Cleanup
subscription.unsubscribe();
this.showLoading(false);
// Show success
this.showSuccess('Order processed successfully!');
return result;
} catch (error) {
this.showLoading(false);
this.showError('Failed to process order: ' + error.message);
throw error;
}
}
private async waitForCompletion(executionId: string): Promise<any> {
while (true) {
const status = await this.ductape.workflows.getStatus({ executionId });
if (status.status === 'completed') {
return status.result;
}
if (status.status === 'failed') {
throw new Error(status.error);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
private handleWorkflowEvent(event: any) {
switch (event.type) {
case 'status_changed':
this.updateStatus(event.data.status);
break;
case 'step_completed':
this.updateProgress(event.data.step);
break;
case 'completed':
console.log('Workflow completed:', event.data.result);
break;
case 'failed':
console.error('Workflow failed:', event.data.error);
break;
}
}
private showLoading(show: boolean) {
document.getElementById('loading').style.display = show ? 'block' : 'none';
}
private updateStatus(status: string) {
document.getElementById('status').textContent = `Status: ${status}`;
}
private updateProgress(step: string) {
const progress = document.getElementById('progress');
const item = document.createElement('li');
item.textContent = `✓ ${step}`;
progress.appendChild(item);
}
private showSuccess(message: string) {
alert(message);
}
private showError(message: string) {
alert(message);
}
}
// Usage
const processor = new OrderProcessor(ductape);
await processor.processOrder('order-123');
Approval Workflow Example
class ApprovalWorkflow {
private ductape: Ductape;
private executionId: string | null = null;
constructor(ductape: Ductape) {
this.ductape = ductape;
}
async submitForApproval(data: any) {
const execution = await this.ductape.workflows.execute({
workflow: 'approval-process',
input: {
data: data,
submittedBy: 'current-user-id',
submittedAt: new Date().toISOString()
}
});
this.executionId = execution.executionId;
// Subscribe to updates
this.ductape.workflows.subscribe({
executionId: this.executionId,
onChange: (event) => {
if (event.type === 'completed') {
this.onApproved(event.data.result);
} else if (event.type === 'failed') {
this.onRejected(event.data.error);
}
}
});
return execution;
}
async approve(comments: string) {
if (!this.executionId) {
throw new Error('No active workflow');
}
await this.ductape.workflows.signal({
executionId: this.executionId,
signal: 'approve',
data: {
approvedBy: 'current-user-id',
comments: comments,
approvedAt: new Date().toISOString()
}
});
}
async reject(reason: string) {
if (!this.executionId) {
throw new Error('No active workflow');
}
await this.ductape.workflows.signal({
executionId: this.executionId,
signal: 'reject',
data: {
rejectedBy: 'current-user-id',
reason: reason,
rejectedAt: new Date().toISOString()
}
});
}
async cancel() {
if (!this.executionId) {
throw new Error('No active workflow');
}
await this.ductape.workflows.cancel({
executionId: this.executionId,
reason: 'Cancelled by user'
});
}
private onApproved(result: any) {
console.log('Approved:', result);
alert('Request approved!');
}
private onRejected(reason: string) {
console.log('Rejected:', reason);
alert('Request rejected: ' + reason);
}
}
// Usage
const approval = new ApprovalWorkflow(ductape);
// Submit for approval
await approval.submitForApproval({
type: 'expense',
amount: 1000,
description: 'New laptop'
});
// Later: approve
await approval.approve('Approved for purchase');
// Or: reject
// await approval.reject('Budget exceeded');
Multi-Step Workflow with Progress
class MultiStepWorkflow {
private ductape: Ductape;
private steps: string[] = [
'Validating input',
'Processing data',
'Generating report',
'Sending notifications',
'Cleanup'
];
private currentStep: number = 0;
constructor(ductape: Ductape) {
this.ductape = ductape;
}
async execute(input: any) {
const execution = await this.ductape.workflows.execute({
workflow: 'multi-step-process',
input: input
});
// Subscribe to step updates
const subscription = this.ductape.workflows.subscribe({
executionId: execution.executionId,
onChange: (event) => {
if (event.type === 'step_completed') {
this.currentStep++;
this.updateProgress();
}
}
});
try {
const result = await this.waitForCompletion(execution.executionId);
subscription.unsubscribe();
return result;
} catch (error) {
subscription.unsubscribe();
throw error;
}
}
private async waitForCompletion(executionId: string): Promise<any> {
while (true) {
const status = await this.ductape.workflows.getStatus({ executionId });
if (status.status === 'completed') {
return status.result;
}
if (status.status === 'failed') {
throw new Error(status.error);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
private updateProgress() {
const percentage = (this.currentStep / this.steps.length) * 100;
const currentStepName = this.steps[this.currentStep - 1] || 'Starting...';
document.getElementById('progress-bar').style.width = `${percentage}%`;
document.getElementById('progress-text').textContent =
`${currentStepName} (${this.currentStep}/${this.steps.length})`;
}
}
// Usage
const workflow = new MultiStepWorkflow(ductape);
const result = await workflow.execute({ dataId: '123' });
Workflow Dashboard
class WorkflowDashboard {
private ductape: Ductape;
constructor(ductape: Ductape) {
this.ductape = ductape;
}
async loadWorkflows() {
const [running, completed, failed] = await Promise.all([
this.ductape.workflows.list({ status: 'running', limit: 10 }),
this.ductape.workflows.list({ status: 'completed', limit: 10 }),
this.ductape.workflows.list({ status: 'failed', limit: 10 })
]);
this.renderSection('running', running.executions);
this.renderSection('completed', completed.executions);
this.renderSection('failed', failed.executions);
}
private renderSection(status: string, executions: any[]) {
const container = document.getElementById(`${status}-workflows`);
container.innerHTML = '';
executions.forEach(exec => {
const item = document.createElement('div');
item.className = 'workflow-item';
item.innerHTML = `
<div class="workflow-info">
<strong>${exec.workflow}</strong>
<span>${exec.executionId}</span>
<span>${new Date(exec.startedAt).toLocaleString()}</span>
</div>
<button onclick="dashboard.viewDetails('${exec.executionId}')">
View Details
</button>
`;
container.appendChild(item);
});
}
async viewDetails(executionId: string) {
const [status, history] = await Promise.all([
this.ductape.workflows.getStatus({ executionId }),
this.ductape.workflows.getHistory({ executionId })
]);
console.log('Status:', status);
console.log('History:', history);
// Show in modal or details panel
this.showDetailsModal(status, history);
}
private showDetailsModal(status: any, history: any) {
// Implementation for showing details
console.log('Showing details:', status, history);
}
}
// Usage
const dashboard = new WorkflowDashboard(ductape);
await dashboard.loadWorkflows();
Best Practices
- Handle errors gracefully: Always wrap workflow execution in try-catch
- Use subscriptions: Subscribe to workflow updates for real-time feedback
- Implement timeouts: Set reasonable timeouts for workflow execution
- Store execution IDs: Save execution IDs in your database for tracking
- Show progress: Provide visual feedback during long-running workflows
- Handle cancellation: Allow users to cancel long-running operations
- Retry logic: Implement retry logic for transient failures
- Log events: Log workflow events for debugging and monitoring