Skip to content

# 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:

1. Single Responsibility: Each task should do one thing well
2. Pure Functions: Tasks should have minimal side effects
3. Clear Interface: Well-defined inputs and outputs
4. 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:

1. Accept Simple Parameters:

   - Take in only what the task needs (strings, numbers, objects)
   - Don’t expect specific step names or flow structures

2. 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

3. Clear Documentation:

   - Document input parameters and their types
   - Document the shape of returned data
   - Include examples when helpful

4. 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:

- Directory
  supabase
  - Directory
    functions
    - 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”
supabase/functions/_tasks/scrapeWebsite.ts

    /** * 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:

supabase/functions/_flows/analyze_website.ts

    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:

1. Modularity: Build your flows from composable building blocks
2. Testability: Test tasks in isolation without running entire flows
3. Maintainability: Update task logic in one place, affecting all flows that use it
4. Collaboration: Different team members can focus on specific tasks
5. Reusability: Use the same task across multiple flows

Note

Pro Tip: Consider creating a library of common tasks for your organization that handle frequently needed operations like database access, API interactions, or data transformations.