tursodatabase/turso · error · Error

sync is disabled as database was opened without sync support

Error message

sync is disabled as database was opened without sync support

What it means

pull() fetches new changes from the remote database, which requires a SyncEngine. When the Database is constructed without a url option the constructor takes the local-only branch (this.#engine = null), so pull() — like push(), checkpoint() and stats() — throws instead of silently doing nothing.

Source

Thrown at bindings/javascript/sync/packages/wasm/promise-default.ts:194

                    registerFileAtWorker(this.#worker, this.name),
                    registerFileAtWorker(this.#worker, `${this.name}-wal`),
                    registerFileAtWorker(this.#worker, `${this.name}-wal-revert`),
                    registerFileAtWorker(this.#worker, `${this.name}-info`),
                    registerFileAtWorker(this.#worker, `${this.name}-changes`),
                ]);
            }
            await run(this.#runner, this.#engine.connect(), this.execLock);
        }
        this.connected = true;
    }
    /**
     * pull new changes from the remote database
     * if {@link DatabaseOpts.longPollTimeoutMs} is set - then server will hold the connection open until either new changes will appear in the database or timeout occurs.
     * @returns true if new changes were pulled from the remote
     */
    async pull() {
        if (this.#engine == null) {
            throw new Error("sync is disabled as database was opened without sync support")
        }
        const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait(), this.execLock));
        if (changes.empty()) {
            return false;
        }
        await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes), this.execLock));
        return true;
    }
    /**
     * push new local changes to the remote database
     * if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote
     */
    async push() {
        if (this.#engine == null) {
            throw new Error("sync is disabled as database was opened without sync support")
        }
        await this.#guards.push(async () => await run(this.#runner, this.#engine.push(), this.execLock));
    }

View on GitHub (pinned to bad083fafb)

Solutions

  1. Pass a url (and usually authToken) in DatabaseOpts: new Database({ path, url: process.env.TURSO_DATABASE_URL!, authToken }) — this is what creates the SyncEngine that pull/push/checkpoint/stats require.
  2. If the database is intentionally local-only, remove the sync call or gate it behind a 'sync enabled' flag in your own code.
  3. Verify the environment actually provides the URL in the failing context: log process.env.TURSO_DATABASE_URL at the exact construction site (CI/preview often differ from your machine).
  4. Use two explicit factory functions — local Database for dev/tests, synced Database for prod — instead of conditionally omitting url on one shared path.

Example fix

// before
const db = new Database({ path: "app.db" }); // no url -> constructor stores #engine = null
await db.pull();                               // throws "sync is disabled..."

// after — opt into sync at construction time
const db = new Database({
    path: "app.db",
    url: process.env.TURSO_DATABASE_URL!, // required for pull/push/checkpoint/stats
    authToken: process.env.TURSO_AUTH_TOKEN,
});
await db.connect();
await db.pull();
Defensive patterns

Strategy: validation

Validate before calling

// Decide once, where you build your options, whether sync is on.
const syncOpts = process.env.TURSO_DATABASE_URL
    ? { url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN }
    : {};
const db = new Database({ path: "app.db", ...syncOpts });
const syncEnabled = Boolean(syncOpts.url);

// Guard every sync call site:
if (syncEnabled) {
    await db.connect();
    await db.pull(); // likewise push(), checkpoint(), stats()
}

Type guard

// #engine is private — there is no runtime probe on the Database instance.
// Narrow your options instead, before construction:
const isSynced = (
    opts: DatabaseOpts
): opts is DatabaseOpts & { url: string | (() => string | null) } => opts.url != null;

Try / catch

try {
    await db.pull();
} catch (e) {
    if (e instanceof Error && e.message.startsWith("sync is disabled")) {
        // Local-only database: this is a configuration state, not a transient error.
        // Skip the sync loop (or alert that this deployment was meant to sync).
        return;
    }
    throw e;
}

Prevention

When it happens

Trigger: new Database({ path: 'local.db' }) — DatabaseOpts.url omitted, or explicitly evaluated to undefined (e.g. url: process.env.TURSO_DATABASE_URL ?? undefined with the variable unset) — followed by await db.pull(). The guard is the first statement of pull(), so it fires before any engine work.

Common situations: Sharing one code path between a local-only dev/test database and a synced production database; CI or preview environments missing TURSO_DATABASE_URL so url evaluates to undefined; a refactor that accidentally drops the url field from DatabaseOpts; local (non-sync) WASM demo code later extended with a sync loop.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/3279e088bf995343. Report an issue: GitHub.