Skip to content
Nile Js
Features

Retries and utilities

Retry external calls with jittered backoff, keep a bounded short-lived store, and wrap database failures safely

Read Markdown ↗

Use utils for retries, bounded stores and diagnostics. Use safeTry from @nilejs/nile/fp to catch failures from database or provider calls.

When to use them

Use retryFnWithJitter when you poll a provider or call a service that fails transiently and the operation is safe to repeat. Use createMemoryStore for values that expire quickly, such as short cache entries. Use safeTry from @nilejs/nile/fp around a database call that might throw.

Complete example

This module polls a delivery provider with up to eight attempts and a 60-second deadline. It also keeps order summaries for 30 seconds in a bounded store.

import { utils } from "@nilejs/nile";
import { Err, Ok, type Result } from "@nilejs/nile/fp";

type DeliveryStatus = {
  state: "queued" | "delivered" | "failed";
};

const checkDeliveryStatus = async (
  reference: string,
  signal: AbortSignal
): Promise<Result<DeliveryStatus, string>> => {
  if (signal.aborted) {
    return Err("Cancelled");
  }
  await Promise.resolve(reference);
  return Ok({ state: "queued" });
};

/** Polls a delivery provider with jittered backoff, up to eight attempts. */
export const waitForDelivery = async (
  reference: string,
  signal: AbortSignal
): Promise<Result<DeliveryStatus, string>> => {
  const delivered = await utils.retryFnWithJitter({
    fn: (attemptSignal: AbortSignal) =>
      checkDeliveryStatus(reference, attemptSignal),
    signal: signal,
    maxAttempts: 8,
    timeoutMs: 60_000,
    baseDelayMs: 1000,
    maxDelayMs: 10_000,
    jitterMs: 500,
    onResult: ({ result, retry, end, stop }) => {
      if (result.isErr) {
        return retry();
      }
      if (result.value.state === "delivered") {
        return end(result.value);
      }
      if (result.value.state === "failed") {
        return stop("Delivery failed");
      }
      return retry();
    },
  });

  return delivered;
};

type OrderSummary = {
  orderId: string;
  total: number;
};

/** Holds up to 1000 recent summaries for 30 seconds each. */
export const summaryCache = utils.createMemoryStore<OrderSummary>({
  maxKeys: 1000,
  ttlMs: 30_000,
  eviction: "lru",
});

export const rememberSummary = (summary: OrderSummary): Result<void, string> => {
  const stored = summaryCache.set({ key: summary.orderId, value: summary });
  if (stored.isErr) {
    return Err(stored.error);
  }
  return Ok(undefined);
};

Helpers

HelperUse it forNotable options
retryFnWithJitterRepeating a call until its result is finalmaxAttempts (default 10), timeoutMs (default 60000), baseDelayMs, maxDelayMs, jitterMs, onResult, signal
createMemoryStoreBounded values with optional expirymaxKeys (default 1000), ttlMs, eviction ("reject-new" default, or "lru")
safeTry from @nilejs/nile/fpWrapping a database call that may throwraw preserves the cause; use handleError for a safe caller message
describeErrorReading a thrown error in a diagnostic logmaxDepth, maxFieldLength, includeMessages
createRateLimiter, createQueue and othersControls and quotasSee queues and rate limiting
resolveClientIdentityReading the client address from a requestgetClientAddress, trustProxy

Retries

onResult decides what happens after each attempt. Return retry() to wait and try again, end(value) for success or stop(message) for a final failure. The helper returns a Result. If it runs out of attempts or time, it returns a failure with a string message.

Retry only operations that are safe to repeat. A payment or an email send needs an idempotency key before a retry is safe.

Memory store

Values are held by reference in this process. The default store rejects new keys when it is full, so an unexpected traffic spike cannot evict active values. Use LRU only when you accept evicting old entries.

set returns a Result. get returns the value or undefined. consume removes a value only when the expected value matches, which is useful for one-time tokens.

Limits

  • Retries cannot stop code that ignores its abort signal. The helper waits for the attempt to settle.
  • The store and the retry history live in memory and disappear on restart.
  • describeError can include message text when includeMessages is true. Driver messages may contain data, so enable it only when you need it.

Common mistakes

  • Retrying a write that is not idempotent.
  • Using safeTry from @nilejs/nile/fp and then returning the raw error message to the client.
  • Treating the memory store as shared between replicas.
  • Forgetting the Result from set, which reports a rejected insert when the store is full.

See also

On this page