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
- Pass a larger per-call timeout: execute(sql, args, false, { queryTimeout: 60000 })
- Raise or remove the session-level default: new Session({ url, authToken, defaultQueryTimeout: 0 }) (0 or omitted disables the deadline)
- Speed the query up: add an index, add LIMIT, or split one huge batch into smaller ones
- 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
- Measure real query latency and set defaultQueryTimeout with headroom (p99 x 2-3), not a guess
- Pass per-call queryTimeout for known-slow maintenance queries instead of raising the global default
- Keep queries idempotent (or mark them) so timeout retries are safe
- Remember the deadline also covers row streaming — large result sets need proportionally larger timeouts or iterate()
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- HTTP error! status
- missing batch result in pipeline response
- Batch execution failed
- batch mode must be one of
- batch response does not have one result and one error per…
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)