Claude Code Plugins

Community-maintained marketplace

Feedback

cloudflare-api-orchestration

@jukasdrj/books-v3
1
0

|

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name cloudflare-api-orchestration
description Activates automatically when designing or implementing Cloudflare Workers APIs, orchestrating multi-provider API calls, implementing D1 database operations, or managing KV-based caching strategies. Ensures proper error handling, provider tagging, fallback chains, and orchestration patterns for BooksTrack backend infrastructure. Auto-activates on: - File patterns: *.worker.js, *-api.ts, *-service.ts in backend code - Keywords: "api orchestration", "multi-provider", "fallback chain", "D1 query" - Context: Cloudflare Workers development, API design discussions
allowed-tools Read, Grep, Edit, Write, WebFetch

Cloudflare API Orchestration Skill

This skill enforces BooksTrack-specific API orchestration patterns for Cloudflare Workers.

Core Principles

1. Provider Orchestration (MANDATORY)

NEVER make direct API calls without orchestration layer.

Correct pattern:

// orchestrator.js
export class APIOrchestrator {
  async searchBooks(query, options = {}) {
    const providers = [
      { name: 'google', fn: () => this.googleBooks.search(query) },
      { name: 'openlibrary', fn: () => this.openLibrary.search(query) }
    ];

    for (const provider of providers) {
      try {
        const result = await provider.fn();
        return {
          ...result,
          provider: `orchestrated:${providers.map(p => p.name).join('+')}`,
          cached: false
        };
      } catch (error) {
        console.warn(`Provider ${provider.name} failed:`, error);
        continue;
      }
    }

    // Final fallback to cache
    const cached = await this.kv.get(`book:${query}`);
    if (cached) {
      return {
        ...JSON.parse(cached),
        provider: 'cache:kv',
        cached: true
      };
    }

    throw new Error('All providers failed');
  }
}

Wrong pattern (FORBIDDEN):

// ❌ Direct API call without orchestration
async function searchBooks(query) {
  const response = await fetch(`https://www.googleapis.com/books/v1/volumes?q=${query}`);
  return response.json(); // Missing provider tag, no fallback, no caching
}

2. Provider Tagging (REQUIRED)

All responses MUST include provider metadata:

{
  data: { /* actual response */ },
  provider: "orchestrated:google+openlibrary", // Multi-provider
  cached: false,
  timestamp: Date.now()
}

Tag formats:

  • "orchestrated:google+openlibrary" - Successful multi-provider aggregation
  • "google" - Single provider (fallback failed or only one available)
  • "cache:kv" - Retrieved from KV cache
  • "cache:d1" - Retrieved from D1 database

3. Error Handling Patterns

Implement graceful degradation:

try {
  // Primary provider
  const result = await primaryProvider.fetch();
  return { data: result, provider: 'primary', cached: false };
} catch (primaryError) {
  console.warn('Primary provider failed:', primaryError);

  try {
    // Secondary provider
    const result = await secondaryProvider.fetch();
    return { data: result, provider: 'secondary', cached: false };
  } catch (secondaryError) {
    console.warn('Secondary provider failed:', secondaryError);

    // Cache fallback
    const cached = await cache.get(key);
    if (cached) {
      return { data: cached, provider: 'cache:kv', cached: true };
    }

    // Final error with context
    throw new APIError('All providers exhausted', {
      primaryError,
      secondaryError,
      key
    });
  }
}

4. D1 Query Patterns

Always use prepared statements:

// ✅ Correct - prepared statement
const result = await env.DB.prepare(
  'SELECT * FROM books WHERE isbn = ?'
).bind(isbn).first();

// ❌ Wrong - SQL injection vulnerability
const result = await env.DB.prepare(
  `SELECT * FROM books WHERE isbn = '${isbn}'`
).first();

Batch operations for efficiency:

// ✅ Correct - batch insert
const stmt = env.DB.prepare('INSERT INTO books (isbn, title) VALUES (?, ?)');
const batch = books.map(book => stmt.bind(book.isbn, book.title));
await env.DB.batch(batch);

// ❌ Wrong - individual inserts (slow)
for (const book of books) {
  await env.DB.prepare('INSERT INTO books (isbn, title) VALUES (?, ?)')
    .bind(book.isbn, book.title)
    .run();
}

5. KV Caching Strategy

Key naming convention:

namespace:entity:id

Examples:

  • book:isbn:9780134685991
  • search:query:swift+programming
  • user:profile:user123

Caching pattern:

async function getCachedOrFetch(key, fetchFn, ttl = 3600) {
  // Try cache first
  const cached = await env.KV.get(key, { type: 'json' });
  if (cached) {
    return { ...cached, cached: true };
  }

  // Fetch from provider
  const fresh = await fetchFn();

  // Cache for TTL seconds
  await env.KV.put(key, JSON.stringify(fresh), {
    expirationTtl: ttl,
    metadata: { cached_at: Date.now() }
  });

  return { ...fresh, cached: false };
}

6. Rate Limiting

Implement per-endpoint rate limits:

import { RateLimiter } from './rate-limiter';

const limiter = new RateLimiter({
  search: { requests: 100, window: 60 }, // 100 req/min
  details: { requests: 200, window: 60 }  // 200 req/min
});

async function handleSearch(request, env) {
  const clientId = request.headers.get('CF-Connecting-IP');

  if (!await limiter.check('search', clientId)) {
    return new Response('Rate limit exceeded', {
      status: 429,
      headers: {
        'Retry-After': '60',
        'X-RateLimit-Limit': '100',
        'X-RateLimit-Remaining': '0'
      }
    });
  }

  // Process request...
}

7. Circuit Breaker Pattern

Prevent cascading failures:

class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = Date.now();
  }

  async call(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker OPEN');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failureCount++;
    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
}

BooksTrack-Specific Patterns

Book Search API

// Route: GET /api/v2/books/search?q=...
export async function handleBookSearch(request, env) {
  const url = new URL(request.url);
  const query = url.searchParams.get('q');

  if (!query) {
    return new Response(JSON.stringify({ error: 'Missing query parameter' }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' }
    });
  }

  const orchestrator = new APIOrchestrator(env);

  try {
    const result = await orchestrator.searchBooks(query);
    return new Response(JSON.stringify(result), {
      status: 200,
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'public, max-age=3600',
        'X-Provider': result.provider
      }
    });
  } catch (error) {
    console.error('Search failed:', error);
    return new Response(JSON.stringify({
      error: 'Search failed',
      message: error.message
    }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

Book Details API

// Route: GET /api/v2/books/:isbn
export async function handleBookDetails(isbn, env) {
  // Try D1 first (authoritative source)
  const cached = await env.DB.prepare(
    'SELECT * FROM books WHERE isbn = ?'
  ).bind(isbn).first();

  if (cached) {
    return {
      ...cached,
      provider: 'cache:d1',
      cached: true
    };
  }

  // Fetch from providers
  const orchestrator = new APIOrchestrator(env);
  const result = await orchestrator.getBookByISBN(isbn);

  // Store in D1 for future requests
  await env.DB.prepare(
    'INSERT OR REPLACE INTO books (isbn, title, author, data) VALUES (?, ?, ?, ?)'
  ).bind(isbn, result.title, result.author, JSON.stringify(result)).run();

  return result;
}

Anti-Patterns to Avoid

❌ Direct API Calls

// DON'T DO THIS
const response = await fetch('https://api.example.com/books');

❌ Missing Provider Tags

// DON'T DO THIS
return { data: books }; // Missing provider metadata

❌ Unhandled Errors

// DON'T DO THIS
const result = await provider.fetch(); // No try-catch

❌ SQL Injection Vulnerabilities

// DON'T DO THIS
await env.DB.prepare(`SELECT * FROM books WHERE title = '${title}'`).first();

❌ No Caching Strategy

// DON'T DO THIS - always hitting live APIs
const result = await provider.fetch(); // No cache check

Validation Checklist

When implementing or reviewing Cloudflare Workers APIs, verify:

  • All API calls go through orchestration layer
  • Responses include provider tags
  • Fallback chains implemented (primary → secondary → cache)
  • D1 queries use prepared statements
  • KV keys follow naming convention
  • Rate limiting configured for public endpoints
  • Circuit breakers protect external services
  • Error handling provides useful context
  • Caching strategy reduces API calls
  • Logging captures provider failures

Integration with Code Review

This skill complements the code-review-grok subagent. When code review identifies orchestration violations, this skill provides the correct patterns to follow.


Last Updated: November 23, 2025 Maintained by: Claude Code PM System Related Agents: cloudflare-specialist, code-review-grok