Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/cron-triggers/patterns.md
1# Cron Triggers Patterns23## API Data Sync45```typescript6export default {7async scheduled(controller, env, ctx) {8const response = await fetch("https://api.example.com/data", {headers: { "Authorization": `Bearer ${env.API_KEY}` }});9if (!response.ok) throw new Error(`API error: ${response.status}`);10ctx.waitUntil(env.MY_KV.put("cached_data", JSON.stringify(await response.json()), {expirationTtl: 3600}));11},12};13```1415## Database Cleanup1617```typescript18export default {19async scheduled(controller, env, ctx) {20const result = await env.DB.prepare(`DELETE FROM sessions WHERE expires_at < datetime('now')`).run();21console.log(`Deleted ${result.meta.changes} expired sessions`);22ctx.waitUntil(env.DB.prepare("VACUUM").run());23},24};25```2627## Report Generation2829```typescript30export default {31async scheduled(controller, env, ctx) {32const startOfWeek = new Date(); startOfWeek.setDate(startOfWeek.getDate() - 7);33const { results } = await env.DB.prepare(`SELECT date, revenue, orders FROM daily_stats WHERE date >= ? ORDER BY date`).bind(startOfWeek.toISOString()).all();34const report = {period: "weekly", totalRevenue: results.reduce((sum, d) => sum + d.revenue, 0), totalOrders: results.reduce((sum, d) => sum + d.orders, 0), dailyBreakdown: results};35const reportKey = `reports/weekly-${Date.now()}.json`;36await env.REPORTS_BUCKET.put(reportKey, JSON.stringify(report));37ctx.waitUntil(env.SEND_EMAIL.fetch("https://example.com/send", {method: "POST", body: JSON.stringify({to: "[email protected]", subject: "Weekly Report", reportUrl: `https://reports.example.com/${reportKey}`})}));38},39};40```4142## Health Checks4344```typescript45export default {46async scheduled(controller, env, ctx) {47const services = [{name: "API", url: "https://api.example.com/health"}, {name: "CDN", url: "https://cdn.example.com/health"}];48const checks = await Promise.all(services.map(async (service) => {49const start = Date.now();50try {51const response = await fetch(service.url, { signal: AbortSignal.timeout(5000) });52return {name: service.name, status: response.ok ? "up" : "down", responseTime: Date.now() - start};53} catch (error) {54return {name: service.name, status: "down", responseTime: Date.now() - start, error: error.message};55}56}));57ctx.waitUntil(env.STATUS_KV.put("health_status", JSON.stringify(checks)));58const failures = checks.filter(c => c.status === "down");59if (failures.length > 0) ctx.waitUntil(fetch(env.ALERT_WEBHOOK, {method: "POST", body: JSON.stringify({text: `${failures.length} service(s) down: ${failures.map(f => f.name).join(", ")}`})}));60},61};62```6364## Batch Processing (Rate-Limited)6566```typescript67export default {68async scheduled(controller, env, ctx) {69const queueData = await env.QUEUE_KV.get("pending_items", "json");70if (!queueData || queueData.length === 0) return;71const batch = queueData.slice(0, 100);72const results = await Promise.allSettled(batch.map(item => fetch("https://api.example.com/process", {method: "POST", headers: {"Authorization": `Bearer ${env.API_KEY}`, "Content-Type": "application/json"}, body: JSON.stringify(item)})));73console.log(`Processed ${results.filter(r => r.status === "fulfilled").length}/${batch.length} items`);74ctx.waitUntil(env.QUEUE_KV.put("pending_items", JSON.stringify(queueData.slice(100))));75},76};77```7879## Queue Integration8081```typescript82export default {83async scheduled(controller, env, ctx) {84const batch = await env.MY_QUEUE.receive({ batchSize: 100 });85const results = await Promise.allSettled(batch.messages.map(async (msg) => {86await processMessage(msg.body, env);87await msg.ack();88}));89console.log(`Processed ${results.filter(r => r.status === "fulfilled").length}/${batch.messages.length}`);90},91};92```9394## Monitoring & Observability9596```typescript97export default {98async scheduled(controller, env, ctx) {99const startTime = Date.now();100const meta = { cron: controller.cron, scheduledTime: controller.scheduledTime };101console.log("[START]", meta);102try {103const result = await performTask(env);104console.log("[SUCCESS]", { ...meta, duration: Date.now() - startTime, count: result.count });105ctx.waitUntil(env.METRICS.put(`cron:${controller.scheduledTime}`, JSON.stringify({ ...meta, status: "success" }), { expirationTtl: 2592000 }));106} catch (error) {107console.error("[ERROR]", { ...meta, duration: Date.now() - startTime, error: error.message });108ctx.waitUntil(fetch(env.ALERT_WEBHOOK, { method: "POST", body: JSON.stringify({ text: `Cron failed: ${controller.cron}`, error: error.message }) }));109throw error;110}111},112};113```114115**View logs:** `npx wrangler tail` or Dashboard → Workers & Pages → Worker → Logs116117## Durable Objects Coordination118119```typescript120export default {121async scheduled(controller, env, ctx) {122const stub = env.COORDINATOR.get(env.COORDINATOR.idFromName("cron-lock"));123const acquired = await stub.tryAcquireLock(controller.scheduledTime);124if (!acquired) {125controller.noRetry();126return;127}128try {129await performTask(env);130} finally {131await stub.releaseLock();132}133},134};135```136137## Python Handler138139```python140from workers import WorkerEntrypoint141142class Default(WorkerEntrypoint):143async def scheduled(self, controller, env, ctx):144data = await env.MY_KV.get("key")145ctx.waitUntil(env.DB.execute("DELETE FROM logs WHERE created_at < datetime('now', '-7 days')"))146```147148## Testing Patterns149150**Local testing with /__scheduled:**151```bash152# Start dev server153npx wrangler dev154155# Test specific cron156curl "http://localhost:8787/__scheduled?cron=*/5+*+*+*+*"157158# Test with specific time159curl "http://localhost:8787/__scheduled?cron=0+2+*+*+*&scheduledTime=1704067200000"160```161162**Unit tests:**163```typescript164// test/scheduled.test.ts165import { describe, it, expect, vi } from "vitest";166import { env } from "cloudflare:test";167import worker from "../src/index";168169describe("Scheduled Handler", () => {170it("executes cron", async () => {171const controller = { scheduledTime: Date.now(), cron: "*/5 * * * *", type: "scheduled" as const, noRetry: vi.fn() };172const ctx = { waitUntil: vi.fn(), passThroughOnException: vi.fn() };173await worker.scheduled(controller, env, ctx);174expect(await env.MY_KV.get("last_run")).toBeDefined();175});176177it("calls noRetry on duplicate", async () => {178const controller = { scheduledTime: 1704067200000, cron: "0 2 * * *", type: "scheduled" as const, noRetry: vi.fn() };179await env.EXECUTIONS.put("0 2 * * *-1704067200000", "1");180await worker.scheduled(controller, env, { waitUntil: vi.fn(), passThroughOnException: vi.fn() });181expect(controller.noRetry).toHaveBeenCalled();182});183});184```185186## See Also187188- [README.md](./README.md) - Overview189- [api.md](./api.md) - Handler implementation190- [gotchas.md](./gotchas.md) - Troubleshooting191