CloudflareCloudflare Workflows

Cloudflare Workflows

This document describes the Cloudflare Workflows implementation in the bloqr-backend, providing durable execution for compilation, batch processing, cache...

Cloudflare Workflows

This document describes the Cloudflare Workflows implementation in the bloqr-backend, providing durable execution for compilation, batch processing, cache warming, and health monitoring.

Table of Contents


Overview

Cloudflare Workflows provide durable execution for long-running operations. Unlike traditional queue-based processing, workflows offer:

  • Automatic state persistence between steps
  • Crash recovery - resumes from the last successful step
  • Built-in retry with configurable policies
  • Observable step-by-step progress
  • Reliable scheduled execution with cron triggers

Benefits over Queue-Based Processing

FeatureQueue-BasedWorkflows
State PersistenceManual (KV)Automatic
Crash RecoveryRe-process entire messageResume from checkpoint
Step VisibilityLimitedFull step-by-step
Retry LogicCustom implementationBuilt-in with backoff
Long-running Tasks30s limitUp to 15 minutes per step
Scheduled ExecutionExternal schedulerNative cron triggers

Available Workflows

CompilationWorkflow

Handles single async compilation requests with durable state between steps.

Steps:
  1. validate - Validate configuration
  2. compile-sources - Fetch and compile all sources
  3. cache-result - Compress and store in KV
  4. update-metrics - Update workflow metrics
Parameters:
interface CompilationParams {
  requestId: string;           // Unique tracking ID
  configuration: IConfiguration; // Filter list config
  preFetchedContent?: Record<string, string>; // Optional pre-fetched content
  benchmark?: boolean;         // Include benchmark metrics
  priority?: 'standard' | 'high';
  queuedAt: number;           // Timestamp
}

API Endpoint: POST /workflow/compile

curl -X POST http://localhost:8787/workflow/compile \
  -H "Content-Type: application/json" \
  -d '{
    "configuration": {
      "name": "My Filter List",
      "sources": [
        {"source": "https://easylist.to/easylist/easylist.txt", "name": "EasyList"}
      ],
      "transformations": ["Deduplicate", "RemoveEmptyLines"]
    },
    "priority": "high"
  }'
Response:
{
  "success": true,
  "message": "Compilation workflow started",
  "workflowId": "wf-compile-abc123",
  "workflowType": "compilation",
  "requestId": "wf-compile-abc123",
  "configName": "My Filter List"
}

BatchCompilationWorkflow

Processes multiple compilations with per-chunk durability and crash recovery.

Steps:
  1. validate-batch - Validate all configurations
  2. compile-chunk-N - Process chunks of 3 compilations in parallel
  3. update-batch-metrics - Update aggregate metrics
Parameters:
interface BatchCompilationParams {
  batchId: string;
  requests: Array<{
    id: string;
    configuration: IConfiguration;
    preFetchedContent?: Record<string, string>;
    benchmark?: boolean;
  }>;
  priority?: 'standard' | 'high';
  queuedAt: number;
}

API Endpoint: POST /workflow/batch

curl -X POST http://localhost:8787/workflow/batch \
  -H "Content-Type: application/json" \
  -d '{
    "requests": [
      {
        "id": "request-1",
        "configuration": {
          "name": "EasyList",
          "sources": [{"source": "https://easylist.to/easylist/easylist.txt"}]
        }
      },
      {
        "id": "request-2",
        "configuration": {
          "name": "EasyPrivacy",
          "sources": [{"source": "https://easylist.to/easylist/easyprivacy.txt"}]
        }
      }
    ],
    "priority": "standard"
  }'

CacheWarmingWorkflow

Pre-populates the cache with popular filter lists. Runs on schedule or manual trigger.

Steps:
  1. check-cache-status - Identify configurations needing refresh
  2. warm-chunk-N - Compile and cache configurations in chunks
  3. update-warming-metrics - Track warming statistics
Default Popular Configurations:
  • EasyList
  • EasyPrivacy
  • AdGuard Base
Parameters:
interface CacheWarmingParams {
  runId: string;
  configurations: IConfiguration[]; // Empty = use defaults
  scheduled: boolean;
}

API Endpoint: POST /workflow/cache-warm

# Trigger with default configurations
curl -X POST http://localhost:8787/workflow/cache-warm \
  -H "Content-Type: application/json" \
  -d '{}'

# Trigger with custom configurations
curl -X POST http://localhost:8787/workflow/cache-warm \
  -H "Content-Type: application/json" \
  -d '{
    "configurations": [
      {
        "name": "Custom List",
        "sources": [{"source": "https://example.com/filters.txt"}]
      }
    ]
  }'

Cron Schedule: Every 6 hours (0 */6 * * *)


HealthMonitoringWorkflow

Monitors filter source availability and alerts on failures.

Steps:
  1. load-health-history - Load recent health check history
  2. check-source-N - Check each source individually
  3. analyze-results - Detect consecutive failures for alerting
  4. send-alerts - Send alerts if threshold exceeded
  5. store-results - Persist health data
Default Sources Monitored:
  • EasyList (expected: 50,000+ rules)
  • EasyPrivacy (expected: 10,000+ rules)
  • AdGuard Base (expected: 30,000+ rules)
  • AdGuard Tracking Protection (expected: 10,000+ rules)
  • Peter Lowe's List (expected: 2,000+ rules)
Health Thresholds:
  • Max response time: 30 seconds
  • Failure threshold: 3 consecutive failures before alerting
Parameters:
interface HealthMonitoringParams {
  runId: string;
  sources: Array<{
    name: string;
    url: string;
    expectedMinRules?: number;
  }>; // Empty = use defaults
  alertOnFailure: boolean;
}

API Endpoint: POST /workflow/health-check

# Trigger with default sources
curl -X POST http://localhost:8787/workflow/health-check \
  -H "Content-Type: application/json" \
  -d '{"alertOnFailure": true}'

# Check custom sources
curl -X POST http://localhost:8787/workflow/health-check \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [
      {"name": "My Source", "url": "https://example.com/filters.txt", "expectedMinRules": 100}
    ],
    "alertOnFailure": true
  }'

Cron Schedule: Every hour (0 * * * *)


DynamicCompilationWorkflow

Durable compilation workflow where each step runs in an isolated V8 Worker loaded at runtime via @cloudflare/dynamic-workflows. Introduced 2026-05-01.

Key properties vs CompilationWorkflow:
  • Each step source is an inline JavaScript string (VALIDATE_STEP_SOURCE, TRANSFORM_STEP_SOURCE, CACHE_STEP_SOURCE)
  • Steps run in sandboxed V8 isolates — zero ambient authority (globalOutbound: null)
  • Requires DYNAMIC_WORKFLOW_LOADER binding (Cloudflare beta)
Steps:
  1. validate - Load and execute inline validator Worker; validates configuration structure
  2. transform - Load and execute inline transform Worker; processes preFetchedContent and produces compiled filter rules
  3. cache - Load and execute inline cache Worker; derives SHA-256 cache key from the compiled rules (not the configuration JSON)
ZTA properties:
  • clerkUserId sourced exclusively from verified JWT (authContext.userId), never from request body
  • globalOutbound: null enforced on all dynamic step isolates
  • Workflow instance ID equals requestId; event.instanceId used for all WorkflowEvents and analytics calls
Parameters:
interface DynamicCompilationParams {
  clerkUserId: string;                      // ZTA: from authContext only
  requestId: string;
  configuration: IConfiguration;
  preFetchedContent: Record<string, string>; // required: isolates run with globalOutbound: null
  benchmark?: boolean;
  priority: 'high' | 'standard';            // defaults to 'standard'
  queuedAt: number;                         // Unix ms — queue-latency tracking
}

API Endpoint: POST /workflow/dynamic-compile

curl -X POST http://localhost:8787/workflow/dynamic-compile \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "configuration": {
      "name": "My Filter List",
      "sources": [
        { "source": "https://easylist.to/easylist/easylist.txt", "name": "EasyList" }
      ]
    },
    "preFetchedContent": {
      "EasyList": "! EasyList
||example.com^"
    },
    "priority": "high"
  }'

Note: preFetchedContent is required for POST /workflow/dynamic-compile. Each dynamic step isolate runs with globalOutbound: null — it has no outbound network access and therefore cannot fetch filter list sources at runtime. The caller must pre-fetch and provide all source content before creating the workflow. When Cloudflare Access is configured, this endpoint returns 403 Forbidden if the CF Access JWT is absent or invalid.

Flow diagram:

API Endpoints

Workflow Management

MethodEndpointDescription
POST/workflow/compileStart compilation workflow
POST/workflow/batchStart batch compilation workflow
POST/workflow/cache-warmTrigger cache warming
POST/workflow/health-checkTrigger health monitoring
POST/workflow/dynamic-compileStart isolated dynamic compilation
GET/workflow/status/:type/:idGet workflow instance status
GET/workflow/events/:idGet real-time progress events
GET/workflow/metricsGet aggregate workflow metrics
GET/health/latestGet latest health check results

Status Endpoint

Get the status of a running or completed workflow:

curl http://localhost:8787/workflow/status/compilation/wf-compile-abc123
Response:
{
  "success": true,
  "workflowType": "compilation",
  "workflowId": "wf-compile-abc123",
  "status": "complete",
  "output": {
    "success": true,
    "requestId": "wf-compile-abc123",
    "configName": "My Filter List",
    "ruleCount": 45000,
    "totalDurationMs": 2500
  }
}
Workflow Status Values:
  • queued - Waiting to start
  • running - Currently executing
  • paused - Manually paused
  • complete - Successfully finished
  • errored - Failed with error
  • terminated - Manually stopped
  • unknown - Status unavailable

Metrics Endpoint

Get aggregate metrics for all workflows:

curl http://localhost:8787/workflow/metrics
Response:
{
  "compilation": {
    "totalRuns": 150,
    "successfulRuns": 145,
    "failedRuns": 5,
    "avgDurationMs": 3200,
    "lastRunAt": "2024-01-15T10:30:00Z"
  },
  "batch": {
    "totalRuns": 25,
    "totalCompilations": 100,
    "avgDurationMs": 15000
  },
  "cacheWarming": {
    "totalRuns": 48,
    "scheduledRuns": 46,
    "manualRuns": 2,
    "totalConfigsWarmed": 144
  },
  "health": {
    "totalChecks": 168,
    "totalSourcesChecked": 840,
    "totalHealthy": 820,
    "alertsTriggered": 3
  }
}

Latest Health Results

Get the most recent health check results:

curl http://localhost:8787/health/latest
Response:
{
  "success": true,
  "timestamp": "2024-01-15T10:00:00Z",
  "runId": "cron-health-abc123",
  "results": [
    {
      "name": "EasyList",
      "url": "https://easylist.to/easylist/easylist.txt",
      "healthy": true,
      "statusCode": 200,
      "responseTimeMs": 450,
      "ruleCount": 72500
    },
    {
      "name": "EasyPrivacy",
      "url": "https://easylist.to/easylist/easyprivacy.txt",
      "healthy": true,
      "statusCode": 200,
      "responseTimeMs": 380,
      "ruleCount": 18200
    }
  ],
  "summary": {
    "total": 5,
    "healthy": 5,
    "unhealthy": 0
  }
}

Workflow Events (Real-Time Progress)

Get real-time progress events for a running workflow:

# Get all events for a workflow
curl http://localhost:8787/workflow/events/wf-compile-abc123

# Get events since a specific timestamp (for polling)
curl "http://localhost:8787/workflow/events/wf-compile-abc123?since=2024-01-15T10:30:00.000Z"
Response:
{
  "success": true,
  "workflowId": "wf-compile-abc123",
  "workflowType": "compilation",
  "startedAt": "2024-01-15T10:30:00.000Z",
  "completedAt": "2024-01-15T10:30:05.000Z",
  "progress": 100,
  "isComplete": true,
  "events": [
    {
      "type": "workflow:started",
      "workflowId": "wf-compile-abc123",
      "workflowType": "compilation",
      "timestamp": "2024-01-15T10:30:00.000Z",
      "data": {"configName": "My Filter List", "sourceCount": 2}
    },
    {
      "type": "workflow:step:started",
      "workflowId": "wf-compile-abc123",
      "workflowType": "compilation",
      "timestamp": "2024-01-15T10:30:00.100Z",
      "step": "validate"
    },
    {
      "type": "workflow:progress",
      "workflowId": "wf-compile-abc123",
      "workflowType": "compilation",
      "timestamp": "2024-01-15T10:30:00.500Z",
      "progress": 25,
      "message": "Configuration validated"
    },
    {
      "type": "workflow:completed",
      "workflowId": "wf-compile-abc123",
      "workflowType": "compilation",
      "timestamp": "2024-01-15T10:30:05.000Z",
      "data": {"ruleCount": 45000, "totalDurationMs": 5000}
    }
  ]
}
Event Types:
TypeDescription
workflow:startedWorkflow execution began
workflow:step:startedA workflow step started
workflow:step:completedA workflow step finished successfully
workflow:step:failedA workflow step failed
workflow:progressProgress update with percentage and message
workflow:completedWorkflow finished successfully
workflow:failedWorkflow failed with error
source:fetch:startedSource fetch operation started
source:fetch:completedSource fetch completed with rule count
transformation:startedTransformation step started
transformation:completedTransformation completed
cache:storedResult cached to KV
health:check:startedHealth check started for a source
health:check:completedHealth check completed
Polling for Real-Time Updates:

To monitor workflow progress in real-time, poll the events endpoint:

async function pollWorkflowEvents(workflowId) {
    let lastTimestamp = null;

    while (true) {
        const url = `/workflow/events/${workflowId}`;
        const params = lastTimestamp ? `?since=${encodeURIComponent(lastTimestamp)}` : '';

        const response = await fetch(url + params);
        const data = await response.json();

        if (data.events?.length > 0) {
            for (const event of data.events) {
                console.log(`[${event.type}] ${event.message || event.step || ''}`);
                lastTimestamp = event.timestamp;
            }
        }

        if (data.isComplete) {
            console.log('Workflow completed!');
            break;
        }

        await new Promise(resolve => setTimeout(resolve, 2000));
    }
}

Scheduled Workflows (Cron)

Workflows can be triggered automatically via cron schedules defined in wrangler.toml:

[triggers]
crons = [
    "0 */6 * * *",   # Cache warming: every 6 hours
    "0 * * * *",     # Health monitoring: every hour
]

The scheduled() handler routes cron events to the appropriate workflow:

Cron PatternWorkflowPurpose
0 */6 * * *CacheWarmingWorkflowPre-warm popular filter list caches
0 * * * *HealthMonitoringWorkflowMonitor source availability

Configuration

wrangler.toml

# Workflow bindings
[[workflows]]
name = "compilation-workflow"
binding = "COMPILATION_WORKFLOW"
class_name = "CompilationWorkflow"

[[workflows]]
name = "batch-compilation-workflow"
binding = "BATCH_COMPILATION_WORKFLOW"
class_name = "BatchCompilationWorkflow"

[[workflows]]
name = "cache-warming-workflow"
binding = "CACHE_WARMING_WORKFLOW"
class_name = "CacheWarmingWorkflow"

[[workflows]]
name = "health-monitoring-workflow"
binding = "HEALTH_MONITORING_WORKFLOW"
class_name = "HealthMonitoringWorkflow"

# Cron triggers
[triggers]
crons = [
    "0 */6 * * *",
    "0 * * * *",
]

Step Configuration

Each workflow step can have custom retry and timeout settings:

await step.do('step-name', {
    retries: {
        limit: 3,                    // Max retries
        delay: '30 seconds',         // Initial delay
        backoff: 'exponential',      // Backoff strategy
    },
    timeout: '5 minutes',            // Step timeout
}, async () => {
    // Step logic
});

Error Handling & Recovery

Automatic Retry

Each step has configurable retry policies:

  • Compilation steps: 2 retries with 30s exponential backoff, 5 minute timeout
  • Cache steps: 2 retries with 2s delay
  • Health checks: 2 retries with 5s delay, 2 minute timeout

Crash Recovery

If a workflow crashes mid-execution:

  1. Cloudflare detects the failure
  2. Workflow resumes from the last completed step
  3. State is automatically restored
  4. Processing continues without re-running completed steps

Dead Letter Handling

Failed workflows after max retries are logged with:

  • Full error details
  • Step that failed
  • Workflow parameters
  • Timestamp

Alerts can be configured via the health monitoring workflow to notify on persistent failures.


Workflow Diagrams

Compilation Workflow

Batch Workflow with Chunking

Health Monitoring Workflow


Notes

  • Workflows are available when deployed to Cloudflare Workers
  • Local development may use stubs for workflow bindings
  • Metrics are stored in the METRICS KV namespace
  • Cached results use the COMPILATION_CACHE KV namespace
  • Health history is retained for 30 days
  • Workflow instances can be monitored in the Cloudflare dashboard