agentsclimarketplace

Miro performance tuning

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/miro-pack/skills/miro-performance-tuning

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill miro-performance-tuning

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Optimize Miro REST API v2 performance with caching, cursor pagination,

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

8.4 KB, as published. Nobody here has run it

Miro Performance Tuning

Overview

Optimize Miro REST API v2 throughput and latency. Key levers: minimize API calls with cursor pagination, cache board/item data, batch writes with controlled concurrency, and use connection pooling.

Latency Benchmarks

Typical latencies for api.miro.com (US region):

OperationEndpointP50P95Credits
Get boardGET /v2/boards/{id}80ms200msLevel 1
List items (50)GET /v2/boards/{id}/items?limit=50120ms350msLevel 1
Create sticky notePOST /v2/boards/{id}/sticky_notes150ms400msLevel 2
Create connectorPOST /v2/boards/{id}/connectors160ms420msLevel 2
Update itemPATCH /v2/boards/{id}/items/{id}130ms350msLevel 2
Delete itemDELETE /v2/boards/{id}/items/{id}100ms280msLevel 2

Cursor Pagination (Eliminate Over-Fetching)

Miro v2 uses cursor-based pagination. Fetch only what you need.

// Efficient paginated iterator
async function* paginateItems(
  boardId: string,
  options: { type?: string; limit?: number } = {}
): AsyncGenerator<MiroBoardItem> {
  const limit = options.limit ?? 50;  // Max 50 per page
  let cursor: string | undefined;

  do {
    const params = new URLSearchParams({ limit: String(limit) });
    if (options.type) params.set('type', options.type);
    if (cursor) params.set('cursor', cursor);

    const response = await fetch(
      `https://api.miro.com/v2/boards/${boardId}/items?${params}`,
      { headers: { 'Authorization': `Bearer ${token}` } }
    );

    const page = await response.json();
    for (const item of page.data) {
      yield item;
    }

    cursor = page.cursor;  // undefined when no more pages
  } while (cursor);
}

// Usage: process items without loading entire board into memory
for await (const item of paginateItems(boardId, { type: 'sticky_note' })) {
  await processItem(item);
}

Caching Strategy

In-Memory Cache (Single Instance)

import { LRUCache } from 'lru-cache';

const boardCache = new LRUCache<string, unknown>({
  max: 500,                    // Max 500 cached entries
  ttl: 60_000,                 // 1 minute TTL
  updateAgeOnGet: true,        // Extend TTL on access
  updateAgeOnHas: false,
});

async function getCachedBoard(boardId: string): Promise<MiroBoard> {
  const cacheKey = `board:${boardId}`;
  const cached = boardCache.get(cacheKey);
  if (cached) return cached as MiroBoard;

  const board = await miroFetch(`/v2/boards/${boardId}`);
  boardCache.set(cacheKey, board);
  return board;
}

// Invalidate on webhook events
function onBoardEvent(event: MiroBoardEvent) {
  boardCache.delete(`board:${event.boardId}`);
  boardCache.delete(`items:${event.boardId}`);
}

Redis Cache (Distributed)

import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

async function getCachedItems(boardId: string): Promise<MiroBoardItem[]> {
  const key = `miro:items:${boardId}`;
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  // Fetch all items
  const items: MiroBoardItem[] = [];
  for await (const item of paginateItems(boardId)) {
    items.push(item);
  }

  // Cache for 2 minutes (boards with active editing should use shorter TTL)
  await redis.setex(key, 120, JSON.stringify(items));
  return items;
}

// Webhook-driven cache invalidation
async function invalidateOnEvent(event: MiroBoardEvent) {
  const keys = [
    `miro:items:${event.boardId}`,
    `miro:board:${event.boardId}`,
  ];
  await redis.del(...keys);
}

Controlled Concurrency for Bulk Operations

import PQueue from 'p-queue';

// Create items in parallel with controlled concurrency
async function bulkCreateStickyNotes(
  boardId: string,
  notes: Array<{ content: string; color: string; x: number; y: number }>
): Promise<string[]> {
  const queue = new PQueue({
    concurrency: 5,       // Max 5 parallel requests
    interval: 1000,       // Per-second window
    intervalCap: 10,      // Max 10 requests per second
  });

  const ids: string[] = [];

  for (const note of notes) {
    queue.add(async () => {
      const result = await miroFetch(`/v2/boards/${boardId}/sticky_notes`, 'POST', {
        data: { content: note.content, shape: 'square' },
        style: { fillColor: note.color },
        position: { x: note.x, y: note.y },
      });
      ids.push(result.id);
    });
  }

  await queue.onIdle();  // Wait for all to complete
  return ids;
}

// Example: Create a grid of 50 sticky notes efficiently
const notes = Array.from({ length: 50 }, (_, i) => ({
  content: `Note ${i + 1}`,
  color: 'light_yellow',
  x: (i % 10) * 250,
  y: Math.floor(i / 10) * 250,
}));

const createdIds = await bulkCreateStickyNotes(boardId, notes);
console.log(`Created ${createdIds.length} sticky notes`);

Connection Pooling

import { Agent } from 'https';

// Reuse TCP connections across requests
const httpsAgent = new Agent({
  keepAlive: true,
  maxSockets: 10,        // Max 10 concurrent connections to api.miro.com
  maxFreeSockets: 5,     // Keep 5 idle connections in pool
  timeout: 30000,        // Socket timeout
});

async function miroFetchPooled(path: string, options: RequestInit = {}) {
  // Note: Node.js native fetch doesn't support custom agents directly.
  // Use undici or node-fetch for connection pooling.
  const { default: fetch } = await import('node-fetch');

  return fetch(`https://api.miro.com${path}`, {
    ...options,
    agent: httpsAgent,
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });
}

Performance Monitoring

async function instrumentedMiroFetch<T>(
  operation: string,
  path: string,
  method = 'GET',
  body?: unknown,
): Promise<{ data: T; metrics: RequestMetrics }> {
  const start = performance.now();
  const response = await fetch(`https://api.miro.com${path}`, {
    method,
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    ...(body ? { body: JSON.stringify(body) } : {}),
  });

  const duration = performance.now() - start;
  const metrics: RequestMetrics = {
    operation,
    path,
    method,
    status: response.status,
    durationMs: Math.round(duration),
    rateLimitRemaining: parseInt(response.headers.get('X-RateLimit-Remaining') ?? '0', 10),
    rateLimitReset: response.headers.get('X-RateLimit-Reset') ?? '',
  };

  // Log for dashboarding
  console.log('[MIRO_PERF]', JSON.stringify(metrics));

  const data = response.status !== 204 ? await response.json() : null;
  return { data: data as T, metrics };
}

Optimization Checklist

TechniqueImpactEffortWhen to Use
Type-filtered queriesHighLowAlways — ?type=sticky_note reduces payload
LRU cacheHighLowRead-heavy workloads
Redis cache + webhook invalidationVery HighMediumMulti-instance deployments
Controlled concurrencyHighLowBulk create/update operations
Connection poolingMediumLowHigh request volume
Cursor paginationHighLowAlways — avoid loading full board

Error Handling

IssueCauseSolution
Cache stale dataLong TTL + frequent editsShorten TTL or use webhook invalidation
Concurrency errorsToo many parallel requestsReduce concurrency in PQueue
Connection pool exhaustedMax sockets too lowIncrease maxSockets
Pagination cursor expiredLong gap between pagesRe-start pagination from beginning

Resources

Next Steps

For cost optimization, see miro-cost-tuning.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.