Skip to content

# Create Reusable Tasks

Not production ready

pgflow is currently in Alpha stage and not yet production ready.

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.