effect-patterns-scheduling-periodic-tasks

0
0
Source

Effect-TS patterns for Scheduling Periodic Tasks. Use when working with scheduling periodic tasks in Effect-TS applications.

Install

mkdir -p .claude/skills/effect-patterns-scheduling-periodic-tasks && curl -L -o skill.zip "https://mcp.directory/api/skills/download/7486" && unzip -o skill.zip -d .claude/skills/effect-patterns-scheduling-periodic-tasks && rm skill.zip

Installs to .claude/skills/effect-patterns-scheduling-periodic-tasks

About this skill

Effect-TS Patterns: Scheduling Periodic Tasks

This skill provides 3 curated Effect-TS patterns for scheduling periodic tasks. Use this skill when working on tasks related to:

  • scheduling periodic tasks
  • Best practices in Effect-TS applications
  • Real-world patterns and solutions

🟡 Intermediate Patterns

Scheduling Pattern 4: Debounce and Throttle Execution

Rule: Use debounce to wait for silence before executing, and throttle to limit execution frequency, both critical for handling rapid events.

Good Example:

This example demonstrates debouncing and throttling for common scenarios.

import { Effect, Schedule, Ref } from "effect";

interface SearchQuery {
  readonly query: string;
  readonly timestamp: Date;
}

// Simulate API search
const performSearch = (query: string): Effect.Effect<string[]> =>
  Effect.gen(function* () {
    yield* Effect.log(`[API] Searching for: "${query}"`);

    yield* Effect.sleep("100 millis"); // Simulate API delay

    return [
      `Result 1 for ${query}`,
      `Result 2 for ${query}`,
      `Result 3 for ${query}`,
    ];
  });

// Main: demonstrate debounce and throttle
const program = Effect.gen(function* () {
  console.log(`\n[DEBOUNCE/THROTTLE] Handling rapid events\n`);

  // Example 1: Debounce search input
  console.log(`[1] Debounced search (wait for silence):\n`);

  const searchQueries = ["h", "he", "hel", "hell", "hello"];

  const debouncedSearches = yield* Ref.make<Effect.Effect<string[]>[]>([]);

  for (const query of searchQueries) {
    yield* Effect.log(`[INPUT] User typed: "${query}"`);

    // In real app, this would be debounced
    yield* Effect.sleep("150 millis"); // User typing
  }

  // After user stops, execute search
  yield* Effect.log(`[DEBOUNCE] User silent for 200ms, executing search`);

  const searchResults = yield* performSearch("hello");

  yield* Effect.log(`[RESULTS] ${searchResults.length} results found\n`);

  // Example 2: Throttle scroll events
  console.log(`[2] Throttled scroll handler (max 10/sec):\n`);

  const scrollEventCount = yield* Ref.make(0);
  const updateCount = yield* Ref.make(0);

  // Simulate 100 rapid scroll events
  for (let i = 0; i < 100; i++) {
    yield* Ref.update(scrollEventCount, (c) => c + 1);

    // In real app, scroll handler would be throttled
    if (i % 10 === 0) {
      // Simulate throttled update (max 10 per second)
      yield* Ref.update(updateCount, (c) => c + 1);
    }
  }

  const events = yield* Ref.get(scrollEventCount);
  const updates = yield* Ref.get(updateCount);

  yield* Effect.log(
    `[THROTTLE] ${events} scroll events → ${updates} updates (${(updates / events * 100).toFixed(1)}% update rate)\n`
  );

  // Example 3: Deduplication
  console.log(`[3] Deduplicating rapid events:\n`);

  const userClicks = ["click", "click", "click", "dblclick", "click"];

  const lastClick = yield* Ref.make<string | null>(null);
  const clickCount = yield* Ref.make(0);

  for (const click of userClicks) {
    const prev = yield* Ref.get(lastClick);

    if (click !== prev) {
      yield* Effect.log(`[CLICK] Processing: ${click}`);
      yield* Ref.update(clickCount, (c) => c + 1);
      yield* Ref.set(lastClick, click);
    } else {
      yield* Effect.log(`[CLICK] Duplicate: ${click} (skipped)`);
    }
  }

  const processed = yield* Ref.get(clickCount);

  yield* Effect.log(
    `\n[DEDUPE] ${userClicks.length} clicks → ${processed} processed\n`
  );

  // Example 4: Exponential backoff on repeated errors
  console.log(`[4] Throttled retry on errors:\n`);

  let retryCount = 0;

  const operation = Effect.gen(function* () {
    retryCount++;

    if (retryCount < 3) {
      yield* Effect.fail(new Error("Still failing"));
    }

    yield* Effect.log(`[SUCCESS] Succeeded on attempt ${retryCount}`);

    return "done";
  }).pipe(
    Effect.retry(
      Schedule.exponential("100 millis").pipe(
        Schedule.upTo("1 second"),
        Schedule.recurs(5)
      )
    )
  );

  yield* operation;
});

Effect.runPromise(program);

Rationale:

Debounce and throttle manage rapid events:

  • Debounce: Wait for silence (delay after last event), then execute once
  • Throttle: Execute at most once per interval
  • Deduplication: Skip duplicate events
  • Rate limiting: Limit events per second

Pattern: Schedule.debounce(duration) or Schedule.throttle(maxEvents, duration)


Rapid events without debounce/throttle cause problems:

Debounce example: Search input

  • User types "hello" character by character
  • Without debounce: 5 API calls (one per character)
  • With debounce: 1 API call after user stops typing

Throttle example: Scroll events

  • Scroll fires 100+ times per second
  • Without throttle: Updates lag, GC pressure
  • With throttle: Update max 60 times per second

Real-world issues:

  • API overload: Search queries hammer backend
  • Rendering lag: Too many DOM updates
  • Resource exhaustion: Event handlers never catch up

Debounce/throttle enable:

  • Efficiency: Fewer operations
  • Responsiveness: UI stays smooth
  • Resource safety: Prevent exhaustion
  • Sanity: Predictable execution


Scheduling Pattern 3: Schedule Tasks with Cron Expressions

Rule: Use cron expressions to schedule periodic tasks at specific calendar times, enabling flexible scheduling beyond simple fixed intervals.

Good Example:

This example demonstrates scheduling a daily report generation using cron, with timezone support.

import { Effect, Schedule, Console } from "effect";
import { DateTime } from "luxon"; // For timezone handling

interface ReportConfig {
  readonly cronExpression: string;
  readonly timezone?: string;
  readonly jobName: string;
}

interface ScheduledReport {
  readonly timestamp: Date;
  readonly jobName: string;
  readonly result: string;
}

// Simple cron parser (in production, use a library like cron-parser)
const parseCronExpression = (
  expression: string
): {
  minute: number[];
  hour: number[];
  dayOfMonth: number[];
  month: number[];
  dayOfWeek: number[];
} => {
  const parts = expression.split(" ");

  const parseField = (field: string, max: number): number[] => {
    if (field === "*") {
      return Array.from({ length: max + 1 }, (_, i) => i);
    }

    if (field.includes(",")) {
      return field.split(",").flatMap((part) => parseField(part, max));
    }

    if (field.includes("-")) {
      const [start, end] = field.split("-").map(Number);
      return Array.from({ length: end - start + 1 }, (_, i) => start + i);
    }

    return [Number(field)];
  };

  return {
    minute: parseField(parts[0], 59),
    hour: parseField(parts[1], 23),
    dayOfMonth: parseField(parts[2], 31),
    month: parseField(parts[3], 12),
    dayOfWeek: parseField(parts[4], 6),
  };
};

// Check if current time matches cron expression
const shouldRunNow = (parsed: ReturnType<typeof parseCronExpression>): boolean => {
  const now = new Date();

  return (
    parsed.minute.includes(now.getUTCMinutes()) &&
    parsed.hour.includes(now.getUTCHours()) &&
    parsed.dayOfMonth.includes(now.getUTCDate()) &&
    parsed.month.includes(now.getUTCMonth() + 1) &&
    parsed.dayOfWeek.includes(now.getUTCDay())
  );
};

// Generate a report
const generateReport = (jobName: string): Effect.Effect<ScheduledReport> =>
  Effect.gen(function* () {
    yield* Console.log(`[REPORT] Generating ${jobName}...`);

    // Simulate report generation
    yield* Effect.sleep("100 millis");

    return {
      timestamp: new Date(),
      jobName,
      result: `Report generated at ${new Date().toISOString()}`,
    };
  });

// Schedule with cron expression
const scheduleWithCron = (config: ReportConfig) =>
  Effect.gen(function* () {
    const parsed = parseCronExpression(config.cronExpression);

    yield* Console.log(
      `[SCHEDULER] Scheduling job: ${config.jobName}`
    );
    yield* Console.log(`[SCHEDULER] Cron: ${config.cronExpression}`);
    yield* Console.log(`[SCHEDULER] Timezone: ${config.timezone || "UTC"}\n`);

    // Create schedule that checks every minute
    const schedule = Schedule.fixed("1 minute").pipe(
      Schedule.untilInputEffect((report: ScheduledReport) =>
        Effect.gen(function* () {
          const isPastTime = shouldRunNow(parsed);

          if (isPastTime) {
            yield* Console.log(
              `[SCHEDULED] ✓ Running at ${report.timestamp.toISOString()}`
            );
            return true; // Stop scheduling
          }

          return false; // Continue scheduling
        })
      )
    );

    // Generate report with cron schedule
    yield* generateReport(config.jobName).pipe(
      Effect.repeat(schedule)
    );
  });

// Demonstrate multiple cron schedules
const program = Effect.gen(function* () {
  console.log(
    `\n[START] Scheduling multiple jobs with cron expressions\n`
  );

  // Schedule examples (note: in real app, these would run at actual times)
  const jobs = [
    {
      cronExpression: "0 9 * * 1-5", // 9 AM weekdays
      jobName: "Daily Standup Report",
      timezone: "America/New_York",
    },
    {
      cronExpression: "0 0 * * *", // Midnight daily
      jobName: "Nightly Backup",
      timezone: "UTC",
    },
    {
      cronExpression: "0 0 1 * *", // Midnight on 1st of month
      jobName: "Monthly Summary",
      timezone: "Europe/London",
    },
  ];

  yield* Console.log("[JOBS] Scheduled:");
  jobs.forEach((job) => {
    console.log(
      `  - ${job.jobName}: ${job.cronExpression} (${job.timezone})`
    );
  });
});

Effect.runPromise(program);

Rationale:

Use cron expressions for scheduling that aligns with business calendars:

  • Hourly backups: 0 * * * * (at :00 every hour)
  • Daily reports: 0 9 * * 1-5 (9 AM weekdays)
  • Monthly cleanup: 0 0 1 * * (midnight on 1st of month)
  • Business hours: 0 9-17 * * 1-5 (9 AM-5 PM, Mon-Fri)

Format: minute hour day month weekday


Fixed interval


Content truncated.

You might also like

flutter-development

aj-geddes

Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.

9521,094

drawio-diagrams-enhanced

jgtolentino

Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.

846846

ui-ux-pro-max

nextlevelbuilder

"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."

571699

godot

bfollington

This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.

548492

nano-banana-pro

garg-aayush

Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.

673466

fastapi-templates

wshobson

Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.

514280

Stay ahead of the MCP ecosystem

Get weekly updates on new skills and servers.