tursodatabase/turso · error · Error

panic: MainWorker is not initialized

Error message

panic: MainWorker is not initialized

What it means

The turbopack-hack variant (promise-turbopack-hack.ts:8) exists specifically to work around Turbopack's handling of the wasm/worker assets; init() awaits initThreadPool() and then asserts MainWorker (live export of index-turbopack-hack.js) is non-null. A null MainWorker means the setupMainThread callback in that module never created the worker - typically because Turbopack duplicated or mis-resolved the module, or the hack entry is being used under a bundler it was not meant for (or vice versa).

Source

Thrown at bindings/javascript/packages/wasm/promise-turbopack-hack.ts:8

import { DatabasePromise, DatabaseOpts, SqliteError, Transaction } from "@tursodatabase/database-common"
import { registerFileAtWorker, unregisterFileAtWorker, ioNotifier } from "@tursodatabase/database-wasm-common";
import { initThreadPool, MainWorker, Database as NativeDatabase } from "./index-turbopack-hack.js";

async function init(): Promise<Worker> {
    await initThreadPool();
    if (MainWorker == null) {
        throw new Error("panic: MainWorker is not initialized");
    }
    return MainWorker;
}

class Database extends DatabasePromise {
    #worker: Worker | null;
    constructor(path: string, opts: DatabaseOpts = {}) {
        super(
            new NativeDatabase(path, opts) as unknown as any,
            () => ioNotifier.waitForCompletion(),
        )
    }
    /**
     * connect database and pre-open necessary files in the OPFS
     */
    override async connect() {
        if (!this.memory) {
            const worker = await init();

View on GitHub (pinned to 244cde92a7)

Solutions

  1. Under Turbopack use the turbopack-hack entry consistently (and only it); under other bundlers use the matching variant.
  2. Await init() once at app startup before creating databases.
  3. Dedupe the package and confirm a single module instance serves all imports.
  4. If dev works but build fails (or vice versa), align the entry variant between dev and prod configs.

Example fix

// before
import { Database } from "@tursodatabase/database-wasm/promise-default"; // under Turbopack

// after
import { init, Database } from "@tursodatabase/database-wasm/promise-turbopack-hack";
await init();
Defensive patterns

Strategy: validation

Validate before calling

import { init, Database } from "@tursodatabase/database-wasm/promise-turbopack-hack";
await init();
const db = new Database("app.db");

Try / catch

try {
  await init();
} catch (e) {
  if (e instanceof Error && /MainWorker is not initialized/.test(e.message)) {
    // wrong entry variant for this bundler, or duplicate module: align + dedupe
  } else throw e;
}

Prevention

When it happens

Trigger: Using the turbopack-hack entry under webpack/Vite (or the default entry under Turbopack), producing a module graph where the instance you import never ran setupMainThread's worker callback; Next.js dev (Turbopack) vs production (webpack) resolving different copies; worker.js/wasm assets missing from the Turbopack build output.

Common situations: Next.js apps that work in `next dev` (Turbopack) but throw at build time, or the reverse; entry-point churn during upgrades leaving imports split across variants; monorepo setups pulling two copies of the wasm package.

Related errors


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