Silgi

Client Plugins

Retry, circuit breaker, timeout, batching, deduplication, and CSRF protection for the Silgi client.

Client plugins wrap the transport link to add resilience, performance, and security features. They are composable — stack them in any order.

import {  } from 'silgi/client'
import {  } from 'silgi/client/fetch'
import { , ,  } from 'silgi/client/plugins'

const  = ({ : 'https://api.example.com' })

const  = (((, { : 5000 }), { : 5 }), {
  : 3,
})

const  = <>()

Retry

Automatically retry failed requests with exponential backoff and jitter.

import {  } from 'silgi/client/plugins'

const  = (baseLink, {
  : 3, // default: 3
  : 1000, // 1s → 2s → 4s (exponential)
  : true, // adds 0-25% random jitter (default: true)
  : [500, 502, 503, 504], // status codes to retry on
  : (, ) =>  < 5,
  : ({ , , ,  }) => {
    .(`Retry #${} for ${.('/')} in ${}ms`)
  },
})
OptionTypeDefaultDescription
maxRetriesnumber3Maximum retry attempts
baseDelaynumber | (attempt) => number1000Base delay in ms. Actual: baseDelay * 2^attempt + jitter
jitterbooleantrueAdd 0-25% random delay to prevent thundering herd
retryOnnumber[][408, 429, 500, 502, 503, 504]HTTP status codes that trigger retry
shouldRetry(error, attempt) => booleanCustom predicate to stop retrying
onRetry(info) => voidCalled before each retry with attempt, delay, error, path

Network errors (no status code) are always retried unless shouldRetry returns false.

The retry plugin respects AbortSignal — if the signal is aborted during a delay wait, the retry is cancelled immediately.

Circuit Breaker

Prevents cascading failures by blocking requests when a service is down.

import {  } from 'silgi/client/plugins'

const  = (baseLink, {
  : 5, // open after 5 consecutive failures
  : 30000, // try again after 30s
  : (, {  }) => {
    .(`Circuit: ${} (${} failures)`)
  },
})

// Check state programmatically
.() // 'closed' | 'open' | 'half-open'
.() // manually close the circuit
OptionTypeDefaultDescription
failureThresholdnumber5Consecutive failures before opening
resetTimeoutnumber30000Ms to wait before half-open test
onStateChange(state, info) => voidCalled on state transitions

State machine

CLOSED → (failures >= threshold) → OPEN
OPEN → (resetTimeout elapsed) → HALF-OPEN
HALF-OPEN → (success) → CLOSED
HALF-OPEN → (failure) → OPEN

When open, requests throw CircuitBreakerOpenError immediately — no network call is made.

Timeout

Set a per-link timeout for all requests.

import {  } from 'silgi/client/plugins'

const  = (baseLink, { : 5000 })
OptionTypeDefaultDescription
timeoutnumber30000Timeout in ms

The timeout signal is combined with any existing signal using AbortSignal.any() — whichever fires first wins.

Composition

Plugins are pure link wrappers — compose them in any order. The outermost plugin runs first:

// Request flow: timeout check → circuit breaker check → retry loop → actual fetch
const  = withRetry(withCircuitBreaker(withTimeout(baseLink, { : 5000 })))

Recommended order for production:

  1. withRetry (outermost) — retries the entire inner chain
  2. withCircuitBreaker — blocks when service is down
  3. withTimeout (innermost) — aborts slow individual requests

Other plugins

Batch

Combine multiple RPC calls into a single HTTP request:

import {  } from 'silgi/client/plugins'

const  = new ({ : '/batch', : 10 })

Dedupe

Deduplicate identical in-flight requests:

import {  } from 'silgi/client/plugins'

const  = (baseLink)

CSRF

Add CSRF token to requests:

import {  } from 'silgi/client/plugins'

const  = (baseLink, { : 'x-csrf-token' })

What's next?

  • Client — typed RPC client setup
  • Testing — test procedures with createCaller()
  • Serverserve() and handler()

On this page