Skip to content

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:

Terminal window
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 context
  • env: 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 event
  • type: '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.hcl
application "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:
```hcl
application "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
}
}