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

  1. 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).
  2. Ensure the worker file is emitted as a real module worker (type: 'module') and not inlined/dropped by your bundler.
  3. From the main thread, use only the async promise API; never call sync file/database operations there.
  4. 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

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


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