Silgi

Client Plugins

Retry, circuit breaker, timeout, batching, deduplication, CSRF, and OpenTelemetry tracing 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
respectRetryAfterbooleantrueParse Retry-After header from 429/503 responses for delay

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

When respectRetryAfter is enabled (the default), the plugin reads the Retry-After header from 429 and 503 responses. It supports both delay-seconds (Retry-After: 2) and HTTP-date formats. When present, the header value overrides the exponential backoff delay.

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

OpenTelemetry

Wrap any link with automatic span creation for distributed tracing:

import {  } from 'silgi/client/plugins'
import {  } from '@opentelemetry/api'

const  = (baseLink, {
  : .getTracer('my-service'),
})

Each call creates a span named rpc.client/{procedure} with attributes:

AttributeValue
rpc.systemsilgi
rpc.methodProcedure path (e.g. users.list)

On error, the span records an exception event and sets status code 2. The same Tracer interface works for both server-side (otelWrap) and client-side (withOtel).

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