| name | flow-convert-workflow-definition |
| description | Convert Flow SDK workflow class to Output SDK workflow() function. Use when migrating workflow.ts files from class-based to functional definitions. |
| allowed-tools | ["Bash","Read","Write","Grep","Edit"] |
Convert Flow Workflow Definition to Output SDK
Overview
This skill guides the conversion of Flow SDK workflow classes to Output SDK functional workflow definitions. Workflows orchestrate step execution and define the overall data flow.
When to Use This Skill
During Migration:
- Converting
workflow.ts from class-based to functional
- Setting up workflow input/output schemas
- Updating step invocation patterns
Key Differences
| Aspect | Flow SDK | Output SDK |
|---|
| Definition | Class with execute() method | workflow() function |
| Input/Output | TypeScript interfaces | Zod schemas |
| Activity Calls | Direct function calls | Step calls with object params |
| Error Handling | Try-catch blocks | Let errors propagate |
| Imports | @flow/sdk | @outputai/core |
Conversion Pattern
Flow SDK Workflow (Before)
import { Workflow, WorkflowScope } from '@flow/sdk';
import { fetchUser, processData, saveResults } from './activities';
interface WorkflowInput {
userId: string;
processType: string;
}
interface WorkflowOutput {
success: boolean;
resultId: string;
}
export class DataProcessingWorkflow implements Workflow<WorkflowInput, WorkflowOutput> {
async execute( input: WorkflowInput ): Promise<WorkflowOutput> {
try {
const user = await fetchUser( input.userId );
const processed = await processData( user, input.processType );
const result = await saveResults( processed );
return {
success: true,
resultId: result.id
};
} catch ( error ) {
throw new Error( `Workflow failed: ${error.message}` );
}
}
}
Output SDK Workflow (After)
import { workflow, z } from '@outputai/core';
import { fetchUser, processData, saveResults } from './steps.js';
const WorkflowInputSchema = z.object( {
userId: z.string(),
processType: z.string()
} );
const WorkflowOutputSchema = z.object( {
success: z.boolean(),
resultId: z.string()
} );
export default workflow( {
name: 'dataProcessing',
description: 'Process user data and save results',
inputSchema: WorkflowInputSchema,
outputSchema: WorkflowOutputSchema,
fn: async ( input ) => {
const user = await fetchUser( { userId: input.userId } );
const processed = await processData( { user, processType: input.processType } );
const result = await saveResults( { data: processed } );
return {
success: true,
resultId: result.id
};
}
} );
Step-by-Step Conversion Process
Step 1: Identify Workflow Class
Find the workflow class in workflow.ts:
grep -E "class.*Workflow" src/workflows/my-workflow/workflow.ts
Step 2: Extract Input/Output Types
Convert TypeScript interfaces to Zod schemas:
interface WorkflowInput {
userId: string;
count: number;
options?: ProcessOptions;
}
const WorkflowInputSchema = z.object( {
userId: z.string(),
count: z.number(),
options: ProcessOptionsSchema.optional()
} );
Step 3: Convert Class to Function
Replace class definition with workflow() call:
export class MyWorkflow implements Workflow<Input, Output> {
async execute( input: Input ): Promise<Output> {
}
}
export default workflow( {
name: 'myWorkflow',
description: 'Description of what this workflow does',
inputSchema: InputSchema,
outputSchema: OutputSchema,
fn: async ( input ) => {
}
} );
Step 4: Update Step Invocations
Change direct function calls to object parameter calls:
const result = await someActivity( param1, param2, param3 );
const result = await someStep( { param1, param2, param3 } );
Step 5: Remove Try-Catch Blocks
Remove error handling wrappers (see flow-error-try-catch-removal):
async execute( input ) {
try {
const result = await doWork( input );
return result;
} catch ( error ) {
throw new Error( error.message );
}
}
fn: async ( input ) => {
const result = await doWork( { ...input } );
return result;
}
Step 6: Update Imports
import { Workflow, WorkflowScope } from '@flow/sdk';
import { z } from 'zod';
import { workflow, z } from '@outputai/core';
Important Conventions
1. Workflow Naming
The workflow name must match the legacy name if other systems depend on it:
export default workflow( {
name: 'userOnboarding',
} );
2. Default Export
Workflows should use export default:
export default workflow( { ... } );
export const myWorkflow = workflow( { ... } );
3. File Extension in Imports
Always include .js extension for local imports:
import { fetchUser, processData } from './steps.js';
import { WorkflowInputSchema } from './types.js';
Child Workflows
Workflows can invoke other workflows directly:
import { workflow, z } from '@outputai/core';
import childWorkflow from '../child/workflow.js';
export default workflow( {
name: 'parentWorkflow',
inputSchema: z.object( { data: z.string() } ),
outputSchema: z.object( { result: z.string() } ),
fn: async ( input ) => {
const childResult = await childWorkflow( { input: input.data } );
return { result: childResult.output };
}
} );
Complete Migration Example
Before: Flow SDK
import { Workflow, WorkflowScope, FatalError } from '@flow/sdk';
import {
validateInput,
fetchUserProfile,
generateReport,
sendNotification
} from './activities';
interface ReportInput {
userId: string;
reportType: 'daily' | 'weekly' | 'monthly';
includeCharts: boolean;
}
interface ReportOutput {
reportId: string;
downloadUrl: string;
generatedAt: string;
}
export class GenerateReportWorkflow implements Workflow<ReportInput, ReportOutput> {
async execute( input: ReportInput ): Promise<ReportOutput> {
try {
const validated = await validateInput( input );
const profile = await fetchUserProfile( input.userId );
const report = await generateReport(
profile,
validated.reportType,
validated.includeCharts
);
await sendNotification( profile.email, report.id );
return {
reportId: report.id,
downloadUrl: report.url,
generatedAt: new Date().toISOString()
};
} catch ( error ) {
throw new FatalError( `Report generation failed: ${error.message}` );
}
}
}
After: Output SDK
import { workflow, z } from '@outputai/core';
import {
validateInput,
fetchUserProfile,
generateReport,
sendNotification
} from './steps.js';
const ReportInputSchema = z.object( {
userId: z.string(),
reportType: z.enum( [ 'daily', 'weekly', 'monthly' ] ),
includeCharts: z.boolean()
} );
const ReportOutputSchema = z.object( {
reportId: z.string(),
downloadUrl: z.string(),
generatedAt: z.string()
} );
export default workflow( {
name: 'generateReport',
description: 'Generate user reports with optional charts',
inputSchema: ReportInputSchema,
outputSchema: ReportOutputSchema,
fn: async ( input ) => {
const validated = await validateInput( {
userId: input.userId,
reportType: input.reportType,
includeCharts: input.includeCharts
} );
const profile = await fetchUserProfile( { userId: input.userId } );
const report = await generateReport( {
profile,
reportType: validated.reportType,
includeCharts: validated.includeCharts
} );
await sendNotification( {
email: profile.email,
reportId: report.id
} );
return {
reportId: report.id,
downloadUrl: report.url,
generatedAt: new Date().toISOString()
};
}
} );
Verification Checklist
Related Skills
flow-convert-activities-to-steps - Step conversion
flow-error-try-catch-removal - Try-catch antipattern
flow-error-zod-import - Zod import issues
flow-validation-checklist - Complete validation