tursodatabase/turso · error · TimeoutError

Query timed out

Error message

Query timed out

What it means

Thrown when a query exceeds its deadline. Session.createAbortSignal builds an AbortSignal.timeout(ms) from queryOptions.queryTimeout or config.defaultQueryTimeout, and wrapAbortError converts the resulting fetch AbortError/TimeoutError into the library's TimeoutError (a DatabaseError subclass with code === 'TIMEOUT'). It wraps both the initial fetch and every reader.read() on the response stream, so a query can also time out mid-stream while rows are still arriving.

Solutions

  1. Pass a larger per-call timeout: execute(sql, args, false, { queryTimeout: 60000 })
  2. Raise or remove the session-level default: new Session({ url, authToken, defaultQueryTimeout: 0 }) (0 or omitted disables the deadline)
  3. Speed the query up: add an index, add LIMIT, or split one huge batch into smaller ones
  4. Catch TimeoutError specifically (code === 'TIMEOUT') and retry idempotent read-only queries

Example fix

// before
const session = new Session({ url, authToken, defaultQueryTimeout: 1000 });
await session.execute('SELECT count(*) FROM huge_table'); // TimeoutError after 1s

// after
const session = new Session({ url, authToken, defaultQueryTimeout: 30000 });
// or per call:
await session.execute('SELECT count(*) FROM huge_table', [], false, { queryTimeout: 30000 });
Defensive patterns

Strategy: retry

Validate before calling

// sanity-check timeout config before starting work
const timeout = queryOptions?.queryTimeout ?? config.defaultQueryTimeout;
if (timeout != null && timeout !== 0 && timeout < estimatedWorstCaseMs) {
  throw new Error(`queryTimeout ${timeout}ms is below the estimated worst case`);
}

Type guard

import { DatabaseError } from '@tursodatabase/serverless';

function isQueryTimeout(e: unknown): e is DatabaseError {
  return e instanceof DatabaseError && e.code === 'TIMEOUT';
}

Try / catch

try {
  return await session.execute(sql, args);
} catch (e) {
  if (isQueryTimeout(e) && attempt < MAX_RETRIES && isIdempotent(sql)) {
    await backoff(attempt++);
    return retry();
  }
  throw e;
}

Prevention

When it happens

Trigger: Any session.execute/executeRaw/batch/describe/sequence call (or Statement.run/get/all/iterate built on them) that runs longer than defaultQueryTimeout, or than the per-call queryTimeout passed in queryOptions. Also fires when the timeout aborts during streaming of result rows, not just at request start.

Common situations: defaultQueryTimeout copied from sample code and set too low for a large batch insert; slow unindexed SELECT over a big table; serverless cold-start latency eating the budget; slow network between the edge function and the database.

Understand the failure class

Related errors


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

Appendix: source

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

  };
}

/** 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 c1e5928725)