tursodatabase/turso · error · Error
method ${name} must be invoked only from the worker thread
Error message
method ${name} must be invoked only from the worker thread What it means
panicMain() in wasm-common/index.ts builds the main-thread import object for the WASM module; every synchronous file-IO import (write, sync, truncate, size, ...) is wired to this throw. Those imports are only ever supposed to be called from inside the dedicated worker where the database engine runs, because synchronous OPFS access handles are only legal there. Seeing this error means the main-thread import shim was invoked - i.e. sync engine IO executed on the main thread.
Source
Thrown at bindings/javascript/packages/wasm-common/index.ts:38
return decoder.decode(copy);
}
interface BrowserImports {
is_web_worker(): boolean;
lookup_file(ptr: number, len: number): number;
read(handle: number, ptr: number, len: number, offset: number): number;
read_async(handle: number, ptr: number, len: number, offset: number, c: number);
write(handle: number, ptr: number, len: number, offset: number): number;
write_async(handle: number, ptr: number, len: number, offset: number, c: number);
sync(handle: number): number;
sync_async(handle: number, c: number);
truncate(handle: number, len: number): number;
truncate_async(handle: number, len: number, c: number);
size(handle: number): number;
}
function panicMain(name): never {
throw new Error(`method ${name} must be invoked only from the worker thread`);
}
function panicWorker(name): never {
throw new Error(`method ${name} must be invoked only from the main thread`);
}
function mainImports(worker: Worker, completeOpfs: (c: any, r: any) => void): BrowserImports {
return {
is_web_worker(): boolean {
return false;
},
write_async(handle, ptr, len, offset, c) {
writeFileAtWorker(worker, handle, ptr, len, offset)
.then(result => {
completeOpfs(c, result);
}, err => {
console.error('write_async', err);
completeOpfs(c, -1);View on GitHub (pinned to 244cde92a7)
Solutions
- Load the database through the package's provided worker entry points so the engine runs in the worker (use the documented init()/setup flow, not a manual module instantiation).
- Ensure the worker file is emitted as a real module worker (type: 'module') and not inlined/dropped by your bundler.
- From the main thread, use only the async promise API; never call sync file/database operations there.
- In tests, run under a real worker-capable environment instead of a stubbed Worker global.
Example fix
// before - engine instantiated on the main thread, sync IO hits panicMain
const mod = await initMainThreadImports(); // wrong side
// after - let the package spawn the worker and keep sync IO there
import { init, Database } from "@tursodatabase/database-wasm/promise-default";
await init();
const db = new Database("app.db"); Defensive patterns
Strategy: validation
Validate before calling
const inWorker =
typeof WorkerGlobalScope !== "undefined" &&
self instanceof WorkerGlobalScope;
if (!inWorker) {
// main thread: use only the async promise API, never sync engine IO
} Type guard
const isWorkerThread = (): boolean => typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
Try / catch
try {
await db.exec(sql);
} catch (e) {
if (e instanceof Error && /invoked only from the worker thread/.test(e.message)) {
// engine running on the main thread: fix worker wiring (see prevention)
} else throw e;
} Prevention
- Load the engine only through the package's worker entry so sync IO stays inside the worker.
- Emit the worker as a real module worker; disable inlining for that chunk.
- Avoid manually instantiating turso.wasm with mainImports.
- Test in real worker-capable environments, not stubbed ones.
When it happens
Trigger: The WASM database instance running on the main thread instead of inside the 'turso-database' worker, so its sync file-IO imports resolve to the main shim; a bundler inlining/merging the worker code into the main bundle so workerImports never replace mainImports; manually instantiating the wasm module on the main thread with mainImports while performing synchronous database work.
Common situations: Bundler misconfiguration (missing new Worker(..., { type: 'module' }) support, worker inlined by the bundler); custom embedding that loads turso.wasm directly on the main thread; testing in an environment (jsdom) where Worker is a stub so the engine silently runs main-thread; mixing entry files so the worker never spawns.
Related errors
- method ${name} must be invoked only from the main thread
- panic: MainWorker is not initialized
- panic: MainWorker is not initialized
- panic: MainWorker is not initialized
- panic: MainWorker is not initialized
AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20).
Data as JSON: /api/errors/8eba8e6e646670f1.
Report an issue: GitHub.