Create Reusable Tasks
This guide explains best practices for creating reusable task functions that can be composed into pgflow workflows.
Task Design Philosophy
Section titled “Task Design Philosophy”Tasks in pgflow should follow functional programming principles:
- Single Responsibility: Each task should do one thing well
- Pure Functions: Tasks should have minimal side effects
- Clear Interface: Well-defined inputs and outputs
- Reusability: Tasks should be designed for use across multiple flows
Task Implementation Best Practices
Section titled “Task Implementation Best Practices”A well-designed task function should:
- Accept specific parameters rather than the entire flow input object
- Return a well-defined output structure
- Be testable in isolation
- Have no knowledge about other tasks or the overall flow
Example: Good Task Design
Section titled “Example: Good Task Design”// Good: Task accepts direct content parameterasync function summarizeContent(content: string) { // Process the content directly return { summary: "Processed summary..." };}
// In your flow:.step( { slug: 'summary', dependsOn: ['website'] }, async (input) => await summarizeContent(input.website.content))
Example: Poor Task Design
Section titled “Example: Poor Task Design”// Bad: Task expects specific step structureasync function summarizeWebsite(input: { website: { content: string } }) { // Tightly coupled to previous step name and structure return { summary: "Processed summary..." };}
// In your flow:.step( { slug: 'summary', dependsOn: ['website'] }, async (input) => await summarizeWebsite(input) // Passing entire input object)
Task Guidelines
Section titled “Task Guidelines”When creating reusable tasks:
-
Accept Simple Parameters:
- Take in only what the task needs (strings, numbers, objects)
- Don’t expect specific step names or flow structures
-
Return JSON-Compatible Data:
- All returned data must be serializable to JSON
- Use primitive types: strings, numbers, booleans, null, plain objects, arrays
- Convert dates to ISO strings:
new Date().toISOString()
- Avoid class instances, functions, symbols, undefined, and circular references
-
Clear Documentation:
- Document input parameters and their types
- Document the shape of returned data
- Include examples when helpful
-
Error Handling:
- Gracefully handle expected errors
- Provide meaningful error messages
- Consider retry strategies for transient issues
Organizing Task Code
Section titled “Organizing Task Code”A common pattern is to organize tasks in a dedicated directory:
Directorysupabase
Directoryfunctions
Directory_tasks
- scrapeWebsite.ts
- summarizeWithAI.ts
- extractTags.ts
- saveWebsite.ts
Directory_flows
- analyze_website.ts
This makes tasks easy to discover, import, and reuse across different flows.
Example: Reusable Task for Website Scraping
Section titled “Example: Reusable Task for Website Scraping”/** * Fetches website content from a URL */export default async function scrapeWebsite(url: string) { console.log(`Fetching content from: ${url}`);
// Implementation details... const response = await fetch(url); const html = await response.text();
return { content: html, metadata: { url, fetched_at: new Date().toISOString() } };}
Using Tasks in Flows
Section titled “Using Tasks in Flows”Import and use tasks in flow definitions:
import { Flow } from 'npm:@pgflow/dsl';import scrapeWebsite from '../_tasks/scrapeWebsite.ts';import summarizeWithAI from '../_tasks/summarizeWithAI.ts';
type Input = { url: string;};
export default new Flow<Input>({ slug: 'analyze_website',}) .step( { slug: 'website' }, async (input) => await scrapeWebsite(input.run.url), ) .step( { slug: 'summary', dependsOn: ['website'] }, async (input) => await summarizeWithAI(input.website.content), );
Benefits of Reusable Tasks
Section titled “Benefits of Reusable Tasks”Creating reusable tasks provides several advantages:
- Modularity: Build your flows from composable building blocks
- Testability: Test tasks in isolation without running entire flows
- Maintainability: Update task logic in one place, affecting all flows that use it
- Collaboration: Different team members can focus on specific tasks
- Reusability: Use the same task across multiple flows