Tasks are the fundamental unit of work in Hyrex. They are TypeScript functions that can be executed asynchronously by workers, with automatic retry handling, monitoring, and durability.

Basic Task Definition

Define a task using the hy.task() method:
import { HyrexRegistry } from '@hyrex/hyrex';

const hy = new HyrexRegistry();

interface ProcessInput {
    file_path: string;
    options: Record<string, any>;
}

const processFile = hy.task({
    name: 'processFile',
    func: async (input: ProcessInput) => {
        // Task logic
        console.log(`Processing ${input.file_path}`);
        return { status: 'completed', file: input.file_path };
    }
});
Tasks can accept either zero arguments or one JSON-serializable argument. The return value must also be JSON-serializable or void.

Task Configuration

Configure task behavior with the config object:
const configuredTask = hy.task({
    name: 'configuredTask',
    config: {
        queue: 'processing',           // Target queue
        maxRetries: 3,                // Retry attempts (0-10, default: 3)
        timeoutSeconds: 300,          // Timeout in seconds
        priority: 5,                  // 1-10 (higher = more important, default: 3)
        cron: '0 * * * *',           // Optional cron schedule
        idempotencyKey: 'unique-key'  // Prevent duplicate executions
    },
    func: async (data: any) => {
        // Process with specific configuration
        return { processed: true };
    }
});

Configuration Options

  • queue: Route tasks to specific worker pools (default: “default”)
  • maxRetries: Number of retry attempts on failure (0-10, default: 3)
  • timeoutSeconds: Maximum execution time before timeout
  • priority: Task priority from 1-10 (default: 3)
  • cron: Cron expression for scheduled tasks
  • idempotencyKey: Unique key to prevent duplicate task executions

Sending Tasks

Queue tasks for asynchronous execution:
// Send with default configuration
const taskId = await processFile.send({
    file_path: '/data/input.csv',
    options: { format: 'csv' }
});

// Override configuration at runtime
const urgentTaskId = await processFile.withConfig({
    queue: 'high-priority',
    maxRetries: 5
}).send(input);

console.log(`Task ID: ${taskId}`);

Task Context

Access metadata about the current task execution:
import { getHyrexContext } from '@hyrex/hyrex';

const taskWithContext = hy.task({
    name: 'taskWithContext',
    func: async (data: any) => {
        // Get current task metadata
        const ctx = getHyrexContext();
        
        console.log(`Task ID: ${ctx.taskId}`);
        console.log(`Attempt: ${ctx.attemptNumber + 1}/${ctx.maxRetries + 1}`);
        console.log(`Queue: ${ctx.queue}`);
        console.log(`Parent ID: ${ctx.parentId}`);
        
        // Your task logic here
        return { processedBy: ctx.taskId };
    }
});

Context Properties

  • taskId: Unique identifier for the task
  • durableId: Persistent identifier for durable task storage
  • rootId: ID of the root task in the task tree
  • parentId: ID of the parent task (null if root)
  • workflowRunId: ID of the workflow run (if part of workflow)
  • taskName: Name of the task being executed
  • queue: Queue name where task is processed
  • priority: Task priority level
  • attemptNumber: Current attempt number (0-based)
  • maxRetries: Maximum retry attempts configured
  • timeoutSeconds: Timeout configuration
  • executorId: ID of the executor processing this task

Error Handling

Tasks can throw errors to trigger retries:
const taskWithRetries = hy.task({
    name: 'taskWithRetries',
    config: {
        maxRetries: 3
    },
    func: async (data: any) => {
        try {
            // Attempt operation
            const result = await externalApiCall(data);
            return result;
        } catch (error) {
            if (error instanceof TemporaryError) {
                // This will trigger a retry
                throw error;
            } else if (error instanceof PermanentError) {
                // This will not retry
                return { error: error.message, status: 'failed' };
            }
            throw error;
        }
    }
});

Retry Behavior

  • Throwing any exception triggers a retry (if retries remain)
  • Return a value to complete the task (even with errors)
  • Retries use exponential backoff by default

Input Validation

Use Zod schemas for type-safe input validation:
import { z } from 'zod';

const EmailSchema = z.object({
    to: z.string().email(),
    subject: z.string(),
    body: z.string()
});

const sendEmail = hy.task({
    name: 'sendEmail',
    argSchema: EmailSchema,
    func: async (input) => {
        // Input is automatically validated against schema
        // TypeScript knows input matches EmailSchema
        console.log(`Sending email to ${input.to}`);
        return { sent: true };
    }
});

// This will validate at runtime
await sendEmail.send({
    to: 'user@example.com',
    subject: 'Hello',
    body: 'Welcome!'
});

Task Results

The send() method returns a task ID immediately:
// Send a task
const taskId = await processFile.send(context);
console.log(`Task queued with ID: ${taskId}`);

// Note: The TypeScript SDK returns only the task ID
// Task status and results must be monitored through Hyrex Studio or logs

Best Practices

  1. Use Type-Safe Interfaces
    interface OrderContext {
        orderId: number;
        customerEmail: string;
        items: Array<{ sku: string; quantity: number }>;
    }
    
    const processOrder = hy.task({
        name: 'processOrder',
        func: async (context: OrderContext) => {
            // TypeScript ensures type safety
            return { orderId: context.orderId };
        }
    });
    
  2. Set Appropriate Timeouts
    // Fast operations
    config: { timeoutSeconds: 30 }
    
    // Long-running jobs
    config: { timeoutSeconds: 3600 }
    
  3. Design for Idempotency
    const processPayment = hy.task({
        name: 'processPayment',
        config: {
            idempotencyKey: 'payment-123'  // Prevents duplicate charges
        },
        func: async (context: PaymentContext) => {
            // Check if already processed
            const existing = await checkPaymentStatus(context.paymentId);
            if (existing) {
                return existing;
            }
            
            // Process payment
            const result = await chargeCard(context);
            await savePaymentResult(context.paymentId, result);
            return result;
        }
    });
    
  4. Handle Errors Gracefully
    const fetchData = hy.task({
        name: 'fetchData',
        config: { maxRetries: 3 },
        func: async (context: FetchContext) => {
            try {
                return await fetchFromApi(context.url);
            } catch (error: any) {
                if (error.code === 'RATE_LIMIT') {
                    // Retry with backoff
                    throw error;
                } else if (error.code === 'INVALID_DATA') {
                    // Don't retry, return error
                    return { error: error.message, status: 'invalid_data' };
                }
                throw error;
            }
        }
    });
    

Next Steps