agentsclimarketplace

Flyio sdk patterns

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/flyio-pack/skills/flyio-sdk-patterns

'Apply production-ready Fly.io Machines API patterns for TypeScript with typedFrom its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill flyio-sdk-patterns

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

What its file declares

Copied from the file, not written here

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

5.3 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

Fly.io SDK Patterns

Overview

Production-ready patterns for the Fly.io Machines REST API at https://api.machines.dev. Fly.io exposes both GraphQL (organization queries) and REST (machine lifecycle) APIs. The Machines REST API is the primary integration surface for creating, starting, stopping, and destroying VMs across 30+ global regions. A structured client ensures consistent auth, typed machine states, and reliable wait-for-state polling.

Singleton Client

const FLY_API = 'https://api.machines.dev';
let _client: FlyClient | null = null;
export function getClient(appName: string): FlyClient {
  if (!_client) {
    const token = process.env.FLY_API_TOKEN;
    if (!token) throw new Error('FLY_API_TOKEN must be set');
    _client = new FlyClient(appName, token);
  }
  return _client;
}
class FlyClient {
  private h: Record<string, string>;
  constructor(private app: string, token: string) {
    this.h = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
  }
  async listMachines(): Promise<FlyMachine[]> {
    const r = await fetch(`${FLY_API}/v1/apps/${this.app}/machines`, { headers: this.h });
    if (!r.ok) throw new FlyError(r.status, await r.text()); return r.json();
  }
  async createMachine(config: MachineConfig, region: string): Promise<FlyMachine> {
    const r = await fetch(`${FLY_API}/v1/apps/${this.app}/machines`, {
      method: 'POST', headers: this.h, body: JSON.stringify({ region, config }) });
    if (!r.ok) throw new FlyError(r.status, await r.text()); return r.json();
  }
  async waitForState(id: string, state: string, timeout = 30): Promise<void> {
    const r = await fetch(`${FLY_API}/v1/apps/${this.app}/machines/${id}/wait?state=${state}&timeout=${timeout}`,
      { headers: this.h });
    if (!r.ok) throw new FlyError(r.status, `Wait for ${state} timed out`);
  }
}

Error Wrapper

export class FlyError extends Error {
  constructor(public status: number, message: string) { super(message); this.name = 'FlyError'; }
}
export async function safeCall<T>(operation: string, fn: () => Promise<T>): Promise<T> {
  try { return await fn(); }
  catch (err: any) {
    if (err instanceof FlyError && err.status === 429) { await new Promise(r => setTimeout(r, 2000)); return fn(); }
    if (err instanceof FlyError && err.status === 401) throw new FlyError(401, 'Invalid FLY_API_TOKEN');
    throw new FlyError(err.status ?? 0, `${operation} failed: ${err.message}`);
  }
}

Request Builder

class DeployBuilder {
  private regions: string[] = []; private config: Partial<MachineConfig> = {};
  toRegions(...r: string[]) { this.regions = r; return this; }
  withImage(img: string) { this.config.image = img; return this; }
  withGuest(cpus: number, mem: number) { this.config.guest = { cpu_kind: 'shared', cpus, memory_mb: mem }; return this; }
  async execute(client: FlyClient): Promise<FlyMachine[]> {
    return Promise.all(this.regions.map(async r => {
      const m = await client.createMachine(this.config as MachineConfig, r);
      await client.waitForState(m.id, 'started'); return m;
    }));
  }
}
// Usage: await new DeployBuilder().toRegions('iad','lhr','nrt').withImage('app:latest').withGuest(1,256).execute(client);

Response Types

type MachineState = 'created' | 'starting' | 'started' | 'stopping' | 'stopped' | 'destroying' | 'destroyed';
interface FlyMachine {
  id: string; name: string; state: MachineState; region: string;
  config: MachineConfig; created_at: string; updated_at: string;
}
interface MachineConfig {
  image: string; guest: { cpu_kind: string; cpus: number; memory_mb: number };
  services: Array<{ ports: Array<{ port: number; handlers: string[] }>; internal_port: number }>;
  env: Record<string, string>;
}
interface FlyVolume { id: string; name: string; region: string; size_gb: number; attached_machine_id: string | null; }

Testing Utilities

export function mockMachine(overrides: Partial<FlyMachine> = {}): FlyMachine {
  return { id: 'mach-001', name: 'test-machine', state: 'started', region: 'iad',
    config: { image: 'app:latest', guest: { cpu_kind: 'shared', cpus: 1, memory_mb: 256 }, services: [], env: {} },
    created_at: '2025-01-01T00:00:00Z', updated_at: '2025-01-01T00:00:00Z', ...overrides };
}

Error Handling

PatternWhen to UseExample
safeCall wrapperAll Machines API callsCatches network + API errors uniformly
Retry on 429Bulk machine creation2s delay before retry
waitForState timeoutAfter create/start/stopPrevents hanging deploys
Region fallbackMulti-region deploy failureSkip failed region, continue others

Resources

Next Steps

Apply patterns in flyio-core-workflow-a.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. 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.