Skip to content

Events, Errors, and Retries

Event System

TooLostClient extends a typed event emitter with four event channels:

  • request
  • response
  • error
  • tokenRefresh

Event Listener Methods

ts
client.on(eventName, listener);
client.once(eventName, listener);
client.off(eventName, listener);

Event Payloads

request

ts
interface RequestEvent {
  method: string;
  path: string;
  url: string;
  headers: Record<string, string>;
  body?: unknown;
  attempt: number;
}

response

ts
interface ResponseEvent {
  method: string;
  path: string;
  url: string;
  status: number;
  ok: boolean;
  durationMs: number;
}

error

ts
interface ErrorEvent {
  method: string;
  path: string;
  url: string;
  status?: number;
  message: string;
}

tokenRefresh

ts
interface TokenRefreshEvent {
  accessToken: string;
  refreshToken: string;
  expiresAt: number;
}

Important Event Semantics

  • error is emitted for API or network failures observed by the REST layer.
  • error emission is guarded in TooLostClient: if no error listeners are registered, it does not emit.
  • response is emitted for every fetch response, including non-2xx responses.
  • request.attempt increases when retry logic re-sends the request.

Error Types

TooLostAPIError

ts
class TooLostAPIError extends Error {
  readonly status: number;
  readonly body?: unknown;
}

Thrown for non-2xx API responses and specific token refresh constraints.

Message extraction strategy

For non-2xx responses, message is selected in order:

  1. body.message
  2. body.error
  3. response.statusText
  4. fallback "Request failed"

Retry Behavior

Retry is implemented in REST.request.

Default retries

  • Client default: 2
  • Per-request override: RequestOptions.retry

Retry conditions

Request retries occur when either is true:

  • response status is >= 500
  • response status is 429
  • fetch throws non-TooLostAPIError error (network/runtime fetch failure)

Backoff schedule

Backoff by attempt index (attempt is zero-based inside REST):

  • attempt 0 -> 1000 ms
  • attempt 1 -> 2000 ms
  • attempt 2+ -> capped at 3000 ms

Formula:

ts
Math.min(1000 * (attempt + 1), 3000);

Token Refresh Behavior

Pre-request refresh

Before each request, client may refresh if:

  • autoRefresh is enabled
  • skipAuth is false
  • refreshToken exists
  • expiresAt is known
  • token expires within 30 seconds

401 refresh-and-retry

After request failure, client retries once when:

  • error is TooLostAPIError
  • status is 401
  • autoRefresh enabled
  • skipAuth false
  • refresh token available

The original request is then re-sent after refresh.

Refresh deduplication

Concurrent refresh triggers share a single in-flight promise (inflightRefresh) to avoid duplicate token refresh requests.

Practical Guidance

  • Register at least one error listener for production observability.
  • Keep write operations idempotent when possible if using retry.
  • Set explicit per-request retry to 0 for non-repeatable calls when needed.

Released under the MIT License. Not affiliated with Too Lost.