CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ky

Tiny and elegant HTTP client based on the Fetch API

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

retry.mddocs/

Retry System

Configurable retry logic with exponential backoff, status code filtering, and Retry-After header support. Ky automatically retries failed requests based on configurable conditions.

Capabilities

Retry Configuration

Configure retry behavior with comprehensive options for different failure scenarios.

interface RetryOptions {
  /** Maximum number of retry attempts */
  limit?: number;
  /** HTTP methods allowed to retry */
  methods?: string[];
  /** HTTP status codes that trigger retry */
  statusCodes?: number[];
  /** Status codes that respect Retry-After header */
  afterStatusCodes?: number[];
  /** Maximum wait time for Retry-After header */
  maxRetryAfter?: number;
  /** Upper limit for retry delay */
  backoffLimit?: number;
  /** Custom delay calculation function */
  delay?: (attemptCount: number) => number;
}

Default Values:

const defaultRetryOptions = {
  limit: 2,
  methods: ['get', 'put', 'head', 'delete', 'options', 'trace'],
  statusCodes: [408, 413, 429, 500, 502, 503, 504],
  afterStatusCodes: [413, 429, 503],
  maxRetryAfter: undefined, // Uses timeout value
  backoffLimit: Infinity,
  delay: (attemptCount) => 0.3 * (2 ** (attemptCount - 1)) * 1000
};

Usage Examples:

import ky from "ky";

// Simple retry limit
const simpleRetry = await ky.get("https://api.example.com/unstable", {
  retry: 5 // Retry up to 5 times with default settings
}).json();

// Custom retry configuration
const customRetry = await ky.get("https://api.example.com/data", {
  retry: {
    limit: 3,
    methods: ["get", "post"],
    statusCodes: [408, 429, 500, 502, 503, 504],
    delay: (attemptCount) => Math.min(1000 * attemptCount, 5000)
  }
}).json();

// Aggressive retry for critical operations
const criticalClient = ky.create({
  retry: {
    limit: 10,
    methods: ["get", "post", "put", "patch", "delete"],
    statusCodes: [408, 413, 429, 500, 502, 503, 504],
    backoffLimit: 30000, // Max 30 second delay
    delay: (attemptCount) => {
      // Exponential backoff with jitter
      const baseDelay = 1000 * (2 ** (attemptCount - 1));
      const jitter = Math.random() * 0.1 * baseDelay;
      return baseDelay + jitter;
    }
  }
});

Retry-After Header Support

Automatically respect server-provided retry timing through HTTP headers.

Usage Examples:

import ky from "ky";

// Respect Retry-After header for rate limiting
const rateLimitedClient = ky.create({
  retry: {
    limit: 5,
    afterStatusCodes: [413, 429, 503], // Honor Retry-After for these statuses
    maxRetryAfter: 60000 // Wait maximum 60 seconds
  }
});

// The client will automatically wait for the time specified in:
// - Retry-After header (seconds or HTTP date)
// - RateLimit-Reset header (fallback)
// - X-RateLimit-Reset header (GitHub-style)
// - X-Rate-Limit-Reset header (Twitter-style)

const data = await rateLimitedClient.get("https://api.example.com/limited").json();

// Custom max retry delay
const customDelayClient = ky.create({
  retry: {
    afterStatusCodes: [429],
    maxRetryAfter: 10000, // Never wait more than 10 seconds
  }
});

Method-Specific Retry Behavior

Configure retry behavior per HTTP method for different operation types.

Usage Examples:

import ky from "ky";

// Safe methods only (idempotent operations)
const safeRetryClient = ky.create({
  retry: {
    limit: 5,
    methods: ["get", "head", "options"], // Only safe methods
    statusCodes: [408, 429, 500, 502, 503, 504]
  }
});

// Include PUT and DELETE (idempotent)
const idempotentClient = ky.create({
  retry: {
    limit: 3,
    methods: ["get", "put", "delete", "head"],
    statusCodes: [408, 500, 502, 503, 504]
  }
});

// Retry POST for specific use cases (be careful!)
const postRetryClient = ky.create({
  retry: {
    limit: 2,
    methods: ["get", "post"], // Only if POST is idempotent
    statusCodes: [408, 500, 502, 503, 504]
  }
});

// Different retry strategies per method
const hybridClient = ky.create({
  prefixUrl: "https://api.example.com"
});

// GET requests with aggressive retry
const getData = (endpoint: string) => hybridClient.get(endpoint, {
  retry: { limit: 5, methods: ["get"] }
});

// POST requests with conservative retry
const postData = (endpoint: string, data: any) => hybridClient.post(endpoint, {
  json: data,
  retry: { limit: 1, methods: ["post"], statusCodes: [408, 500] }
});

Status Code Filtering

Configure which HTTP status codes should trigger retry attempts.

Usage Examples:

import ky from "ky";

// Default retriable status codes
const defaultClient = ky.create({
  retry: {
    statusCodes: [
      408, // Request Timeout
      413, // Payload Too Large (with Retry-After)
      429, // Too Many Requests (with Retry-After)
      500, // Internal Server Error
      502, // Bad Gateway
      503, // Service Unavailable (with Retry-After)
      504  // Gateway Timeout
    ]
  }
});

// Custom status codes for specific APIs
const customStatusClient = ky.create({
  retry: {
    statusCodes: [
      408, 429, 500, 502, 503, 504, // Standard codes
      520, 521, 522, 523, 524       // Cloudflare-specific codes
    ]
  }
});

// Minimal retry for fast-fail scenarios
const fastFailClient = ky.create({
  retry: {
    limit: 1,
    statusCodes: [500, 502, 503], // Only server errors
    delay: () => 100 // Very short delay
  }
});

// Never retry client errors (4xx)
const noClientErrorRetry = ky.create({
  retry: {
    statusCodes: [408, 500, 502, 503, 504], // Excludes 413, 429
    afterStatusCodes: [] // No Retry-After respect
  }
});

Custom Delay Strategies

Implement custom delay calculations for different backoff strategies.

Usage Examples:

import ky from "ky";

// Linear backoff
const linearClient = ky.create({
  retry: {
    delay: (attemptCount) => attemptCount * 1000 // 1s, 2s, 3s, 4s...
  }
});

// Exponential backoff with jitter
const jitterClient = ky.create({
  retry: {
    delay: (attemptCount) => {
      const baseDelay = 1000 * (2 ** (attemptCount - 1));
      const jitter = Math.random() * 0.1 * baseDelay;
      return baseDelay + jitter;
    },
    backoffLimit: 10000 // Cap at 10 seconds
  }
});

// Fibonacci backoff
const fibonacciClient = ky.create({
  retry: {
    delay: (attemptCount) => {
      const fibonacci = (n: number): number => {
        if (n <= 1) return n;
        return fibonacci(n - 1) + fibonacci(n - 2);
      };
      return fibonacci(attemptCount) * 1000;
    }
  }
});

// Adaptive delay based on response time
let lastResponseTime = 1000;

const adaptiveClient = ky.create({
  retry: {
    delay: (attemptCount) => {
      // Increase delay based on server response time
      const baseDelay = Math.max(lastResponseTime * 2, 1000);
      return baseDelay * attemptCount;
    }
  },
  hooks: {
    afterResponse: [
      (request, options, response) => {
        // Track response time (simplified)
        lastResponseTime = Date.now() - (request as any).startTime || 1000;
      }
    ]
  }
});

Retry with Timeout Interaction

Understanding how retry interacts with timeout settings.

Usage Examples:

import ky from "ky";

// Total timeout includes all retry attempts
const timeoutClient = ky.create({
  timeout: 30000, // 30 seconds total (including retries)
  retry: {
    limit: 3,
    delay: (attemptCount) => 2000 * attemptCount // 2s, 4s, 6s
  }
});

// Per-attempt timeout
const perAttemptClient = ky.create({
  timeout: 5000, // 5 seconds per attempt
  retry: {
    limit: 5,
    delay: (attemptCount) => 1000 * attemptCount
  }
});

// Fast timeout, many retries
const fastRetryClient = ky.create({
  timeout: 3000, // Fail fast per attempt
  retry: {
    limit: 10, // But try many times
    delay: (attemptCount) => Math.min(500 * attemptCount, 5000)
  }
});

// No retry on timeout
const noTimeoutRetryClient = ky.create({
  timeout: 10000,
  retry: {
    limit: 3,
    // Timeouts are never retried automatically
    statusCodes: [500, 502, 503, 504] // Excludes timeout scenarios
  }
});

Types

interface RetryOptions {
  limit?: number;
  methods?: string[];
  statusCodes?: number[];
  afterStatusCodes?: number[];
  maxRetryAfter?: number;
  backoffLimit?: number;
  delay?: (attemptCount: number) => number;
}

// Retry can be configured as number (limit only) or full options
type RetryConfiguration = RetryOptions | number;

docs

configuration.md

errors.md

hooks.md

http-methods.md

index.md

instances.md

responses.md

retry.md

tile.json