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
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
-
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 };
}
});
-
Set Appropriate Timeouts
// Fast operations
config: { timeoutSeconds: 30 }
// Long-running jobs
config: { timeoutSeconds: 3600 }
-
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;
}
});
-
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