tursodatabase/turso · error · napi::Error
GenericFailure
GenericFailure
Error message
sync engine operation failed: {err} What it means
The JS sync binding drives DatabaseSyncEngine as a coroutine. When the generator completes with Err, resume converts the turso_sync_engine error to a napi GenericFailure: "sync engine operation failed: {err}". This is the terminal failure of one sync session, not a single retryable IO step - the completed coroutine cannot be resumed.
Source
Thrown at bindings/javascript/sync/src/generator.rs:29
fn resume(&mut self, result: Option<String>) -> napi::Result<GeneratorResponse>;
}
impl<F: Future<Output = turso_sync_engine::Result<()>>> Generator
for genawaiter::sync::Gen<SyncEngineIoResult, turso_sync_engine::Result<()>, F>
{
fn resume(&mut self, error: Option<String>) -> napi::Result<GeneratorResponse> {
let result = match error {
Some(err) => Err(turso_sync_engine::errors::Error::DatabaseSyncEngineError(
format!("JsSyncEngineIo error: {err}"),
)),
None => Ok(()),
};
match self.resume_with(result) {
genawaiter::GeneratorState::Yielded(SyncEngineIoResult::IO) => {
Ok(GeneratorResponse::IO)
}
genawaiter::GeneratorState::Complete(Ok(())) => Ok(GeneratorResponse::Done),
genawaiter::GeneratorState::Complete(Err(err)) => Err(napi::Error::new(
napi::Status::GenericFailure,
format!("sync engine operation failed: {err}"),
)),
}
}
}
#[napi]
pub struct SyncEngineChanges {
pub(crate) status: Box<Option<DbChangesStatus>>,
}
#[napi(discriminant = "type", object_from_js = false)]
pub enum GeneratorResponse {
IO,
Done,
SyncEngineStats {
cdc_operations: i64,View on GitHub (pinned to 244cde92a7)
Solutions
- Read the wrapped engine error after the prefix - it distinguishes auth, protocol, and engine failures
- Refresh the auth token and verify the sync endpoint URL and schema
- If the local database diverged, re-bootstrap from the server into a fresh database
- Construct a new SyncEngine afterwards - this coroutine is finished
Example fix
// before
await engine.ioLoopAsync(); // rejects with 'sync engine operation failed: ...'
// after - classify and restart
try {
await engine.ioLoopAsync();
} catch (e) {
if (String(e.message).startsWith('sync engine operation failed:')) {
engine.close();
engine = new SyncEngine(opts); // fresh session
}
} Defensive patterns
Strategy: retry
Validate before calling
const res = await fetch(syncUrl, { method: 'HEAD' }).catch(() => null);
if (!res) throw new Error('sync endpoint unreachable before sync start'); Type guard
function isSyncEngineFailure(e: unknown): boolean {
return e instanceof Error && e.message.startsWith('sync engine operation failed:');
} Try / catch
try { await runSync(engine); } catch (e) { if (isSyncEngineFailure(e)) { engine.close(); engine = new SyncEngine(opts); } else throw e; } Prevention
- Rotate auth tokens before expiry
- Verify the sync URL and schema match the server
- Treat this error as session-terminal: always recreate the engine
When it happens
Trigger: The sync coroutine ends in an error state: authentication rejected by the sync server, an unrecoverable protocol or session error, local database state incompatible with the server, or a JS-side IO error fed back in via resume(Some(err)).
Common situations: Expired or wrong auth token, sync URL pointing at a non-sync endpoint, server schema changed after an upgrade, or the local replica diverged (e.g. restored from an old backup).
Related errors
- Unexpected status from operation.resume(): ${status}
- HTTP request failed: ${e instanceof Error ? e.message : Stri
- GenericFailure
- {message}: {e}
- HTTP error! status: ${response.status}
AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20).
Data as JSON: /api/errors/921deae5e52a3950.
Report an issue: GitHub.