Task
Task
Overview
Tasks are scheduled event processors that handle recurring operations triggered by cron expressions. Each task responds to specific timing patterns and executes background work without direct HTTP requests.
Tasks excel at periodic maintenance, scheduled data processing, and time-based automation where consistent execution timing is required. They operate independently of user requests and provide reliable scheduling for batch operations.
Key benefits include cron-based scheduling, background processing capabilities, automatic retry handling, and isolation from request-response cycles.
Prerequisites
- Active Raindrop project with manifest configuration
- Understanding of cron expression syntax and scheduling concepts
- Familiarity with TypeScript classes and async patterns
- Knowledge of background processing and scheduled job patterns
Creating/Getting Started
Define tasks in your manifest file to enable scheduled event processing:
application "scheduler-app" { task "daily-cleanup" { cron = "0 2 * * *" # Daily at 2 AM UTC }
task "hourly-sync" { cron = "0 * * * *" # Every hour at minute 0 }}
Generate the task implementation:
raindrop build generate
This creates the base task class structure:
export default class extends Task<Env> { async handle(event: Event): Promise<void> { console.log('Task executed at:', new Date(event.scheduledTime)); }}
Accessing/Basic Usage
Implement scheduled processing logic in the handle
method with access to event metadata:
export default class extends Task<Env> { async handle(event: Event): Promise<void> { console.log(`Cron task triggered: ${event.cron}`); console.log(`Scheduled for: ${new Date(event.scheduledTime)}`); console.log(`Event type: ${event.type}`);
// Perform scheduled work await this.performCleanup(); await this.sendSummaryEmail(); }
private async performCleanup(): Promise<void> { // Clean up expired data await this.env.DATABASE.prepare( 'DELETE FROM sessions WHERE expires_at < ?' ).bind(Date.now()).run(); }
private async sendSummaryEmail(): Promise<void> { // Send daily summary const stats = await this.gatherStats(); await this.env.EMAIL_SERVICE.sendSummary(stats); }}
Access environment resources through bindings for database operations and external service calls:
export default class extends Task<Env> { async handle(event: Event): Promise<void> { // Access database const users = await this.env.USER_DB.prepare( 'SELECT * FROM users WHERE last_activity < ?' ).bind(Date.now() - (30 * 24 * 60 * 60 * 1000)).all();
// Call other services for (const user of users.results) { await this.env.NOTIFICATION_SERVICE.sendReengagementEmail(user); } }}
Core Concepts
Tasks follow an event-driven execution model where cron triggers create scheduled events processed by the task handler.
Event Structure: Each task execution receives an Event
object containing the triggering cron expression, scheduled time, and event type information.
Execution Context: Tasks receive an ExecutionContext
providing access to runtime capabilities and the waitUntil
method for extended background operations.
Environment Bindings: Tasks access resources through the env
property containing typed bindings to databases, services, and other components.
Scheduling Reliability: The platform ensures task execution reliability with automatic retries and failure handling for scheduled operations.
Task Base Class
The Task<Env>
abstract class provides the foundation for all scheduled event processing:
abstract class Task<Env> { ctx: ExecutionContext; env: Env;
constructor(ctx: ExecutionContext, env: Env) { this.ctx = ctx; this.env = env; }
abstract handle(event: Event): Promise<void>;}
Properties:
ctx
:ExecutionContext
- Runtime execution contextenv
:Env
- Typed environment bindings for resources
Event Interface
Scheduled events provide timing and trigger information for task execution:
interface Event { cron: string; // The cron expression that triggered this event type: 'scheduled'; // Event type (always 'scheduled' for cron triggers) scheduledTime: number; // Unix timestamp when event was scheduled to run}
Properties:
cron
:string
- The cron expression pattern that triggered the eventtype
:'scheduled'
- Event type identifier (always ‘scheduled’)scheduledTime
:number
- Scheduled execution time as Unix timestamp
Event Processing
Handle scheduled events with access to timing information and execution context for background operations.
handle(event)
Process scheduled events with full access to trigger metadata:
async handle(event: Event): Promise<void> { const scheduledDate = new Date(event.scheduledTime); const currentDate = new Date(); const delay = currentDate.getTime() - event.scheduledTime;
console.log(`Task: ${event.cron}`); console.log(`Scheduled: ${scheduledDate.toISOString()}`); console.log(`Executed: ${currentDate.toISOString()}`); console.log(`Delay: ${delay}ms`);
// Route to specific processing based on cron pattern switch (event.cron) { case '0 2 * * *': // Daily at 2 AM await this.dailyMaintenance(); break;
case '0 * * * *': // Hourly await this.hourlySync(); break;
case '*/5 * * * *': // Every 5 minutes await this.healthCheck(); break;
default: console.log(`Unhandled cron pattern: ${event.cron}`); }}
Parameters:
event
:Event
- Scheduled event containing trigger and timing information
Returns: Promise<void>
- Async completion of scheduled work
Error Handling Patterns
Implement robust error handling for scheduled operations:
async handle(event: Event): Promise<void> { try { await this.performScheduledWork(); } catch (error) { console.error('Task failed:', error);
// Log error for monitoring await this.logTaskError(event, error);
// Optionally send alert if (this.isCriticalTask(event.cron)) { await this.sendAlert(event, error); }
// Re-throw to trigger platform retry logic throw error; }}
private async logTaskError(event: Event, error: Error): Promise<void> { await this.env.ERROR_LOG.prepare( 'INSERT INTO task_errors (cron, scheduled_time, error_message, stack_trace) VALUES (?, ?, ?, ?)' ).bind( event.cron, event.scheduledTime, error.message, error.stack ).run();}
private isCriticalTask(cron: string): boolean { const criticalPatterns = ['0 2 * * *', '0 0 * * 0']; // Daily, weekly return criticalPatterns.includes(cron);}
Background Work Extension
Use the execution context to extend processing beyond normal task limits.
ctx.waitUntil(promise)
Schedule extended background work that continues beyond normal task execution time:
async handle(event: Event): Promise<void> { // Perform immediate work await this.quickTasks();
// Schedule extended background processing this.ctx.waitUntil( this.longRunningOperation() );
console.log('Task completed, background work continues');}
private async longRunningOperation(): Promise<void> { // This continues running beyond normal task limits await this.processLargeDataset(); await this.generateReports(); await this.cleanupTemporaryFiles();}
Parameters:
promise
:Promise<any>
- Extended asynchronous work to complete
Code Examples
Complete task implementations demonstrating common scheduling patterns and use cases.
Database Maintenance Task
// manifest.hclapplication "data-management" { task "daily-maintenance" { cron = "0 2 * * *" # Daily at 2 AM UTC }
sqlDatabase "app_db" { url = "postgresql://user:pass@localhost/app" }
service "metrics" {}}
export default class extends Task<Env> { async handle(event: Event): Promise<void> { console.log(`Starting daily maintenance: ${new Date().toISOString()}`);
const maintenanceTasks = [ this.cleanupExpiredSessions(), this.archiveOldLogs(), this.updateUserStats(), this.optimizeDatabase(), this.generateDailyReport() ];
// Execute all maintenance tasks await Promise.all(maintenanceTasks);
// Schedule extended cleanup in background this.ctx.waitUntil( this.deepCleanup() );
console.log('Daily maintenance completed'); }
private async cleanupExpiredSessions(): Promise<void> { const expiredThreshold = Date.now() - (24 * 60 * 60 * 1000); // 24 hours ago
const result = await this.env.APP_DB.prepare( 'DELETE FROM user_sessions WHERE last_activity < ? RETURNING COUNT(*) as deleted' ).bind(expiredThreshold).first();
console.log(`Cleaned up ${result.deleted} expired sessions`); }
private async archiveOldLogs(): Promise<void> { const archiveThreshold = Date.now() - (30 * 24 * 60 * 60 * 1000); // 30 days ago
// Move old logs to archive table await this.env.APP_DB.prepare(` INSERT INTO log_archive (level, message, timestamp, metadata) SELECT level, message, timestamp, metadata FROM application_logs WHERE timestamp < ? `).bind(archiveThreshold).run();
// Delete archived logs from main table const result = await this.env.APP_DB.prepare( 'DELETE FROM application_logs WHERE timestamp < ? RETURNING COUNT(*) as archived' ).bind(archiveThreshold).first();
console.log(`Archived ${result.archived} log entries`); }
private async updateUserStats(): Promise<void> { await this.env.APP_DB.prepare(` UPDATE user_profiles SET daily_activity_count = ( SELECT COUNT(*) FROM user_activities WHERE user_id = user_profiles.id AND created_at >= ? ), last_stats_update = ? `).bind( Date.now() - (24 * 60 * 60 * 1000), // Last 24 hours Date.now() ).run();
console.log('Updated user activity statistics'); }
private async optimizeDatabase(): Promise<void> { // Run database optimization commands await this.env.APP_DB.prepare('VACUUM ANALYZE').run(); await this.env.APP_DB.prepare('REINDEX DATABASE app').run();
console.log('Database optimization completed'); }
private async generateDailyReport(): Promise<void> { const stats = await this.gatherDailyStats();
// Send stats to metrics service await this.env.METRICS.recordDailyStats(stats);
console.log('Daily report generated and sent'); }
private async gatherDailyStats(): Promise<any> { const yesterday = Date.now() - (24 * 60 * 60 * 1000);
const [userStats, activityStats, sessionStats] = await Promise.all([ this.env.APP_DB.prepare(` SELECT COUNT(*) as new_users, COUNT(CASE WHEN last_login >= ? THEN 1 END) as active_users FROM user_profiles `).bind(yesterday).first(),
this.env.APP_DB.prepare(` SELECT COUNT(*) as total_activities, COUNT(DISTINCT user_id) as active_users FROM user_activities WHERE created_at >= ? `).bind(yesterday).first(),
this.env.APP_DB.prepare(` SELECT COUNT(*) as total_sessions, AVG(duration) as avg_session_duration FROM user_sessions WHERE created_at >= ? `).bind(yesterday).first() ]);
return { date: new Date().toISOString(), users: userStats, activities: activityStats, sessions: sessionStats }; }
private async deepCleanup(): Promise<void> { // Extended cleanup operations that can take longer await this.cleanupOrphanedFiles(); await this.compressOldBackups(); await this.updateSearchIndexes();
console.log('Deep cleanup completed in background'); }
private async cleanupOrphanedFiles(): Promise<void> { // Find and remove files not referenced in database const fileRefs = await this.env.APP_DB.prepare( 'SELECT file_path FROM file_uploads WHERE created_at >= ?' ).bind(Date.now() - (90 * 24 * 60 * 60 * 1000)).all();
// Implementation would check storage and remove orphaned files console.log(`Checked ${fileRefs.results.length} file references`); }
private async compressOldBackups(): Promise<void> { // Compress backups older than 7 days console.log('Compressed old backup files'); }
private async updateSearchIndexes(): Promise<void> { // Rebuild search indexes for optimal performance console.log('Updated search indexes'); }}
Multi-Schedule Processing Task
export default class extends Task<Env> { async handle(event: Event): Promise<void> { const handler = this.getHandlerForSchedule(event.cron);
if (handler) { await handler(event); } else { console.log(`No handler found for cron: ${event.cron}`); } }
private getHandlerForSchedule(cron: string): ((event: Event) => Promise<void>) | null { const handlers: Record<string, (event: Event) => Promise<void>> = { '*/5 * * * *': this.handleHealthCheck.bind(this), // Every 5 minutes '0 * * * *': this.handleHourlySync.bind(this), // Every hour '0 6 * * *': this.handleDailyReport.bind(this), // Daily at 6 AM '0 0 * * 0': this.handleWeeklyMaintenance.bind(this), // Weekly on Sunday '0 0 1 * *': this.handleMonthlyCleanup.bind(this) // Monthly on 1st };
return handlers[cron] || null; }
private async handleHealthCheck(event: Event): Promise<void> { console.log('Running health check...');
const checks = await Promise.all([ this.checkDatabaseHealth(), this.checkExternalServices(), this.checkSystemResources() ]);
const allHealthy = checks.every(check => check.healthy);
if (!allHealthy) { await this.env.ALERT_SERVICE.sendHealthAlert(checks); }
await this.env.METRICS.recordHealthCheck(checks); }
private async handleHourlySync(event: Event): Promise<void> { console.log('Running hourly data sync...');
await Promise.all([ this.syncUserData(), this.syncInventoryData(), this.syncAnalyticsData() ]);
console.log('Hourly sync completed'); }
private async handleDailyReport(event: Event): Promise<void> { console.log('Generating daily report...');
const report = await this.generateReport('daily'); await this.env.EMAIL_SERVICE.sendDailyReport(report);
console.log('Daily report sent'); }
private async handleWeeklyMaintenance(event: Event): Promise<void> { console.log('Running weekly maintenance...');
this.ctx.waitUntil( this.performWeeklyTasks() );
console.log('Weekly maintenance initiated'); }
private async handleMonthlyCleanup(event: Event): Promise<void> { console.log('Running monthly cleanup...');
await Promise.all([ this.archiveOldData(), this.cleanupTempFiles(), this.generateMonthlyReport() ]);
console.log('Monthly cleanup completed'); }
private async checkDatabaseHealth(): Promise<{service: string, healthy: boolean, responseTime: number}> { const startTime = Date.now();
try { await this.env.APP_DB.prepare('SELECT 1').first(); return { service: 'database', healthy: true, responseTime: Date.now() - startTime }; } catch (error) { return { service: 'database', healthy: false, responseTime: Date.now() - startTime }; } }
private async checkExternalServices(): Promise<{service: string, healthy: boolean, responseTime: number}> { // Check external API health return { service: 'external_api', healthy: true, responseTime: 150 }; }
private async checkSystemResources(): Promise<{service: string, healthy: boolean, responseTime: number}> { // Check system resource usage return { service: 'system_resources', healthy: true, responseTime: 50 }; }
private async performWeeklyTasks(): Promise<void> { await this.deepDatabaseCleanup(); await this.generateWeeklyAnalytics(); await this.updateUserEngagementScores(); }}
## raindrop.manifest
Configure scheduled tasks in your manifest for automated background processing:
```hclapplication "scheduler-app" { task "data-cleanup" { # Periodic data maintenance task }
task "report-generation" { # Scheduled report generation }
task "user-notifications" { # Periodic user notification processing }
service "scheduler" { # Service can trigger tasks or handle task results }
actor "task-manager" { # Actors can also execute scheduled tasks }}