tursodatabase/turso · error · TimeoutError

Query timed out

Error message

Query timed out

What it means

TimeoutError (a DatabaseError subclass with code 'TIMEOUT') thrown when the fetch backing a request is aborted because it exceeded its deadline. The deadline is QueryOptions.queryTimeout for a single call or SessionConfig.defaultQueryTimeout for the session, implemented with AbortSignal.timeout(); both fetch aborts and mid-stream read aborts are wrapped into this error.

Source

Thrown at serverless/javascript/src/protocol.ts:274

  };
}

/** Per-query options. Override the session-level defaults for a single call. */
export interface QueryOptions {
  /** Per-query timeout in milliseconds. Overrides defaultQueryTimeout for this call. */
  queryTimeout?: number;
  /**
   * Extra HTTP headers attached to this request only. Applied after the
   * standard headers and any session-level `requestHeaders`, so they can
   * override both. Passing the `Host` key (case-insensitive) throws —
   * fetch forbids setting it.
   */
  requestHeaders?: Record<string, string>;
}

function wrapAbortError(error: unknown): never {
  if (error instanceof Error && (error.name === 'AbortError' || error.name === 'TimeoutError')) {
    throw new TimeoutError('Query timed out');
  }
  throw error;
}

export async function executeCursor(
  ctx: HttpContext,
  request: CursorRequest,
  signal?: AbortSignal
): Promise<{ response: CursorResponse; entries: AsyncGenerator<CursorEntry> }> {
  let response: Response;
  try {
    response = await fetch(`${ctx.url}/v3/cursor`, buildFetchOptions(ctx, JSON.stringify(request), signal));
  } catch (error) {
    wrapAbortError(error);
  }

  if (!response.ok) {
    let errorMessage = `HTTP error! status: ${response.status}`;

View on GitHub (pinned to bad083fafb)

Solutions

  1. Raise or correct the timeout: pass a larger queryTimeout for known-slow calls, or a larger defaultQueryTimeout — both are milliseconds
  2. Optimize the query (add indexes, add LIMIT, use EXPLAIN QUERY PLAN) so it finishes under the deadline
  3. Catch TimeoutError (code 'TIMEOUT') and retry with backoff for transiently slow queries

Example fix

// before
const db = connect({ url, authToken, defaultQueryTimeout: 5 }); // meant 5 seconds, is 5ms

// after
const db = connect({ url, authToken, defaultQueryTimeout: 5000 }); // 5 seconds, in milliseconds
Defensive patterns

Strategy: retry

Validate before calling

const timeout = config.defaultQueryTimeout;
if (timeout != null && (timeout < 100 || !Number.isFinite(timeout))) {
  console.warn("defaultQueryTimeout is in MILLISECONDS;", timeout, "looks wrong");
}

Type guard

const isTimeoutError = (e: unknown): boolean =>
  e instanceof Error && (e as { code?: string }).code === "TIMEOUT";

Try / catch

async function withRetry<T>(fn: () => Promise<T>, tries = 3): Promise<T> {
  for (let i = 1; ; i++) {
    try {
      return await fn();
    } catch (e) {
      if (!(e instanceof Error && (e as { code?: string }).code === "TIMEOUT") || i === tries) throw e;
      await new Promise((r) => setTimeout(r, 100 * 2 ** i));
    }
  }
}
const rows = await withRetry(() => db.all("SELECT ..."));

Prevention

When it happens

Trigger: A query running longer than the configured timeout in milliseconds; a timeout value written in the wrong unit (5 intended as seconds is 5ms); slow database cold starts or network stalls under a tight defaultQueryTimeout; a queryTimeout passed to any of run/get/all/exec/batch/pragma/prepare or transaction handles.

Common situations: Setting defaultQueryTimeout: 5 expecting seconds; large table scans or unindexed joins exceeding a conservative default; edge deployments physically far from the database; retries after the abort arrive but the session baton was reset so the next query starts fresh.

Understand the failure class

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/f6d33dad7461fa10. Report an issue: GitHub.