Resilience

Retry and repeat operations with sophisticated scheduling policies

Overview

The resilience module provides powerful tools for retrying failed operations and repeating successful ones with configurable scheduling policies. Inspired by Arrow-kt's resilience patterns, it enables you to build robust applications that gracefully handle transient failures.

Key features include exponential backoff, fibonacci delays, and composable scheduling policies.

Basic Retry

Retry failed operations with exponential backoff

import { Schedule, retry } from 'kotlinify-ts'

// Retry up to 5 times with exponential backoff
const schedule = Schedule.exponential(100, 2).and(Schedule.recurs(5))

const data = await retry(schedule, async () => {
  const response = await fetch('/api/data')
  if (!response.ok) throw new Error('Failed to fetch')
  return response.json()
})

Schedule Policies

Different scheduling strategies

import { Schedule } from 'kotlinify-ts'

// Exponential backoff: 100ms, 200ms, 400ms, 800ms...
const exponential = Schedule.exponential(100, 2)

// Fibonacci delays: 100ms, 100ms, 200ms, 300ms, 500ms...
const fibonacci = Schedule.fibonacci(100)

// Fixed spacing: 1000ms between each attempt
const spaced = Schedule.spaced(1000)

// Linear increase: 100ms, 200ms, 300ms, 400ms...
const linear = Schedule.linear(100)

// Limit attempts: maximum 10 retries
const limited = Schedule.recurs(10)

Combining Schedules

Compose policies with combinators

import { Schedule } from 'kotlinify-ts'

// Exponential backoff AND max 5 retries (stops when either condition met)
const policy1 = Schedule.exponential(100).and(Schedule.recurs(5))

// Exponential backoff OR max duration (stops when either condition met)
const policy2 = Schedule.exponential(100).or(Schedule.spaced(10000))

// Try exponential first, THEN switch to fixed spacing
const policy3 = Schedule.exponential(100, 2)
  .and(Schedule.recurs(3))
  .andThen(Schedule.spaced(5000))

// Add random jitter to prevent thundering herd
const withJitter = Schedule.exponential(100).jittered(0.2)

// Keep left schedule's output, ignore right's output
const keepLeft = Schedule.exponential(100)
  .zipLeft(Schedule.recurs(5))

// Keep right schedule's output, ignore left's output
const keepRight = Schedule.recurs(5)
  .zipRight(Schedule.spaced(1000))

Repeat Operations

Repeat successful operations

import { Schedule, repeat } from 'kotlinify-ts'

// Poll an API every 5 seconds until a condition is met
await Schedule.doUntil<Status>(
  (status) => status === 'completed'
).repeat(async () => {
  const response = await fetch('/api/status')
  return response.json()
})

// Repeat 10 times with delays
let count = 0
await Schedule.spaced(1000)
  .and(Schedule.recurs(10))
  .repeat(async () => {
    console.log(`Iteration ${++count}`)
  })

Conditional Policies

Continue while or until conditions are met

import { Schedule } from 'kotlinify-ts'

// Continue while result meets condition
Schedule.doWhile<string>(
  (result) => result.length <= 100
)

// Continue until result meets condition
Schedule.doUntil<number>(
  (result) => result >= 1000
)

Real-World Example

Network request with retry logic

import { Schedule, retry } from 'kotlinify-ts'

async function fetchWithRetry<T>(url: string): Promise<T> {
  const schedule = Schedule.exponential(1000, 2)
    .and(Schedule.recurs(3))
    .jittered(0.1)

  return retry(schedule, async () => {
    const response = await fetch(url)

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }

    return response.json()
  })
}

// Usage
try {
  const data = await fetchWithRetry('/api/users')
  console.log('Success:', data)
} catch (error) {
  console.error('Failed after retries:', error)
}

Advanced Schedule Types

Special-purpose schedules for complex scenarios

import { Schedule } from 'kotlinify-ts'

// identity - passes input through as state
const passThrough = Schedule.identity<string>()
  .and(Schedule.recurs(3))
  .retry(() => fetchData())

// collect - accumulates all inputs into an array
const collector = Schedule.collect<Error>()
  .zipLeft(Schedule.recurs(5))
  .retry(() => riskyOperation())
// Collects all errors encountered during retries

// forever - runs indefinitely
const poll = Schedule.forever()
  .and(Schedule.spaced(5000))
  .repeat(() => checkStatus())
// Polls every 5 seconds forever

// map - transform the schedule's output
const mapped = Schedule.recurs(5)
  .map(count => count * 100)
  .repeat(() => processItem())

Synchronous Operations

Retry sync functions without async overhead

import { Schedule, retrySync, repeatSync } from 'kotlinify-ts'

// Retry synchronous operations
const result = retrySync(
  Schedule.recurs(3),
  () => {
    if (Math.random() < 0.5) throw new Error('Random failure')
    return 'success'
  }
)

// Repeat synchronous operations
let counter = 0
repeatSync(Schedule.recurs(5), () => {
  counter++
  return counter
})

Next Steps