Installable skillMIT

Data Service Pattern

Create data fetching services with circuit breaker pattern for API resilience. Services handle fetch, cache, retry, and expose typed data to panel components.

BY AG064UPDATED Oct 28, 2026
INTENDED FOR✳Claude Code>_Codex↗Cursor✦GeminiInstall in a listed agent.

instructions.md

---
name: data-service-d74586bd0c98
description: Create data fetching services with circuit breaker pattern for API resilience. Services handle fetch, cache, retry, and expose typed data to panel components.
---

# Data Service Pattern

Each panel's data comes from a dedicated **service module** in `src/services/`. Services handle API calls, caching, error handling with circuit breaker pattern.

## Circuit Breaker

The circuit breaker prevents cascading failures when an API is down. After N consecutive failures, it enters "cooldown" mode and serves cached data instead of hitting the API.

Create `src/utils/circuit-breaker.ts`:

```typescript
interface CircuitState {
  failures: number;
  cooldownUntil: number;
}

interface CacheEntry<T> {
  data: T;
  timestamp: number;
}

export interface CircuitBreakerOptions {
  name: string;
  maxFailures?: number;
  cooldownMs?: number;
  cacheTtlMs?: number;
}

export class CircuitBreaker<T> {
  private state: CircuitState = { failures: 0, cooldownUntil: 0 };
  private cache: CacheEntry<T> | null = null;
  private name: string;
  private maxFailures: number;
  private cooldownMs: number;
  private cacheTtlMs: number;

  constructor(options: CircuitBreakerOptions) {
    this.name = options.name;
    this.maxFailures = options.maxFailures ?? 2;
    this.cooldownMs = options.cooldownMs ?? 5 * 60 * 1000;  // 5 minutes
    this.cacheTtlMs = options.cacheTtlMs ?? 10 * 60 * 1000; // 10 minutes
  }

  isOnCooldown(): boolean {
    if (Date.now() < this.state.cooldownUntil) return true;
    if (this.state.cooldownUntil > 0) {
      this.state = { failures: 0, cooldownUntil: 0 };
    }
    return false;
  }

  getCached(): T | null {
    if (this.cache && Date.now() - this.cache.timestamp < this.cacheTtlMs) {
      return this.cache.data;
    }
    return null;
  }

  recordSuccess(data: T): void {
    this.state = { failures: 0, cooldownUntil: 0 };
    this.cache = { data, timestamp: Date.now() };
  }

  recordFailure(error?: string): void {
    this.state.failures++;
    if (this.state.failures >= this.maxFailures) {
      this.state.cooldownUntil = Date.now() + this.cooldownMs;
      console.warn(`[${this.name}] Cooldown for ${this.cooldownMs / 1000}s`);
    }
  }

  async execute<R extends T>(fn: () => Promise<R>, defaultValue: R): Promise<R> {
    if (this.isOnCooldown()) {
      const cached = this.getCached();
      return (cached as R) ?? defaultValue;
    }
    const cached = this.getCached();
    if (cached !== null) return cached as R;
    try {
      const result = await fn();
      this.recordSuccess(result);
      return result;
    } catch (e) {
      console.error(`[${this.name}] Failed:`, e);
      this.recordFailure(String(e));
      return defaultValue;
    }
  }
}

export function createCircuitBreaker<T>(options: CircuitBreakerOptions): CircuitBreaker<T> {
  return new CircuitBreaker<T>(options);
}
```

## Service Module Pattern

Each service is a TypeScript module that exports async data-fetching functions:

```typescript
// src/services/stock-market.ts
import { createCircuitBreaker } from '../utils/circuit-breaker';

export interface StockQuote {
  symbol: string;
  name: string;
  price: number | null;
  change: number | null;
  sparkline?: number[];
}

const breaker = createCircuitBreaker<StockQuote[]>({
  name: 'StockMarket',
  cacheTtlMs: 60_000, // 1 minute cache
});

export async function fetchStockQuotes(symbols: string[]): Promise<StockQuote[]> {
  return breaker.execute(async () => {
    const params = new URLSearchParams({ symbols: symbols.join(',') });
    const resp = await fetch(`/api/stocks?${params}`);
    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
    return resp.json();
  }, []);
}
```

## Key Patterns

1. **One circuit breaker per API endpoint** — create at module level
2. **Export typed interfaces** for data shapes
3. **Wrap fetch calls** in `breaker.execute(fn, defaultValue)`
4. **Default values** are empty arrays `[]` or empty objects `{}`
5. **Services are stateless modules** — no class instances, just exported functions
6. **API proxy**: Frontend calls `/api/...` which proxies to external APIs (hides API keys)

## API Proxy Pattern

For APIs requiring keys, create server-side proxy endpoints:

```typescript
// api/stocks.ts (Vercel serverless function)
export default async function handler(req, res) {
  const { symbols } = req.query;
  const apiKey = process.env.FINNHUB_API_KEY;
  const data = await fetch(`https://finnhub.io/api/v1/quote?symbol=${symbols}&token=${apiKey}`);
  res.json(await data.json());
}
```

This keeps API keys server-side and provides a single endpoint for the frontend.

COMPATIBILITY & REFERENCES

Platform guidance

Imported from the listed AgentSkill.sh category. Complete supplied package files and notices are retained. Required external tools and credentials are not installed. No bundled code was executed during import. Individual agent task behavior has not been tested.