tursodatabase/turso · error · napi::Error
GenericFailure
GenericFailure
Error message
{message}: {e} What it means
Node.js (napi) binding error wrapper. When stepping a prepared statement fails inside the native engine, to_generic_error("step failed", e) re-throws the underlying turso_core error as a napi GenericFailure with message "step failed: <cause>". The prefix only localizes the failure to the VDBE step loop; the real reason is the text after the colon.
Source
Thrown at bindings/javascript/src/lib.rs:229
Ok(turso_core::StepResult::IO) => Ok((STEP_IO, 0)),
Ok(turso_core::StepResult::Yield) => Ok((STEP_SLEEP, 1)),
Ok(turso_core::StepResult::Sleep { duration }) => {
// Round sub-millisecond delays up to 1ms: a 0ms setTimeout would
// make the JS step loop spin without letting the backoff expire.
let sleep_ms = duration.as_millis().clamp(1, u32::MAX as u128) as u32;
Ok((STEP_SLEEP, sleep_ms))
}
Ok(turso_core::StepResult::Done) => Ok((STEP_DONE, 0)),
Ok(turso_core::StepResult::Interrupt) => {
Err(create_generic_error("statement was interrupted"))
}
Ok(turso_core::StepResult::Busy) => Err(create_generic_error("database is locked")),
Err(e) => Err(to_generic_error("step failed", e)),
}
}
fn to_generic_error<E: std::error::Error>(message: &str, e: E) -> napi::Error {
Error::new(Status::GenericFailure, format!("{message}: {e}"))
}
fn to_error<E: std::error::Error>(status: napi::Status, message: &str, e: E) -> napi::Error {
Error::new(status, format!("{message}: {e}"))
}
fn create_generic_error(message: &str) -> napi::Error {
Error::new(Status::GenericFailure, message)
}
fn create_error(status: napi::Status, message: &str) -> napi::Error {
Error::new(status, message)
}
fn query_timeout_duration(timeout_ms: u32) -> Option<std::time::Duration> {
if timeout_ms > 0 {
Some(std::time::Duration::from_millis(timeout_ms as u64))
} else {View on GitHub (pinned to 244cde92a7)
Solutions
- Read the text after "step failed:" - it is the verbatim native error (e.g. "Disk I/O error", "Interrupted") and names the real cause
- If I/O related: verify the database path, file permissions, and disk health
- If interrupted or locked: stop closing or interrupting the connection from other threads while a step is in flight
- Reinstall the native package so the JS wrapper and native engine versions match
Example fix
// before
while (stmt.step()) { /* ... */ }
// after - surface the native cause
try {
while (stmt.step()) { /* ... */ }
} catch (e) {
if (String(e.message).startsWith('step failed:')) {
throw new Error('engine step failure: ' + e.message.slice('step failed:'.length));
}
throw e;
} Defensive patterns
Strategy: try-catch
Type guard
function isStepFailure(e: unknown): boolean {
return e instanceof Error && e.message.startsWith('step failed:');
} Try / catch
try { stmt.step(); } catch (e) { if (isStepFailure(e)) { /* the suffix is the real native error - log and decide */ } throw e; } Prevention
- Keep the connection open and untouched while statements are stepped from JS
- Verify the database file stays readable for the process's lifetime
- Pin matching versions of the JS wrapper and native module
When it happens
Trigger: stmt.step() (or any JS API that steps a statement) returns Err from turso_core: a page read fails with an I/O error, the statement is interrupted mid-execution, the database file is corrupted, or memory is exhausted while materializing rows.
Common situations: Database file moved, deleted, or on an unplugged disk between prepare and step; another thread closes the connection or interrupts during a long query; corrupted database after a crash; or a native/JS binding version mismatch.
Related errors
- {message}: {e}
- GenericFailure
- Only finite numbers (not Infinity or NaN) can be passed as a
- BigInt value is outside SQLite's signed 64-bit integer range
- BigInt value is outside SQLite's signed 64-bit integer range
AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20).
Data as JSON: /api/errors/4057ad9709d5679a.
Report an issue: GitHub.