tursodatabase/turso · error

transactionAsync is not supported with…

Error message

transactionAsync is not supported with remoteWritesExperimental yet; use the deprecated transaction() for now

What it means

transactionAsync hands the callback a Transaction handle bound to a local connection that is owned for the whole BEGIN..COMMIT window. When the database was constructed with remoteWritesExperimental, transactions execute on the remote server and there is no local connection to hand out, so the override throws rather than silently misbehave. The message points you to the deprecated transaction(), whose remote path wraps the entire callback in a remote BEGIN..COMMIT.

Solutions

  1. Replace transactionAsync(fn) with transaction(fn); the remote writer wraps the whole callback remotely
  2. Inside the callback, use the database handle (db.exec) instead of the per-connection txn handle
  3. If you need the local Transaction handle, disable remoteWritesExperimental in DatabaseOpts
  4. Centralize transaction creation in one adapter so the mode switch is a single code path

Example fix

// before
const run = db.transactionAsync(async (txn) => {
  await txn.exec("INSERT INTO orders ...");
});
await run();

// after (remoteWritesExperimental enabled)
const run = db.transaction(async () => {
  await db.exec("INSERT INTO orders ...");
});
await run();
Defensive patterns

Strategy: validation

Validate before calling

// Route at construction time: remoteWritesExperimental forbids transactionAsync
const remoteWrites = Boolean(opts.remoteWritesExperimental && opts.url);
const run = remoteWrites
  ? db.transaction(fn)      // remote BEGIN..COMMIT wrapper
  : db.transactionAsync(fn); // local connection handle
await run();

Try / catch

try {
  const run = db.transactionAsync(fn);
  await run();
} catch (err) {
  if (err instanceof Error && err.message.includes("remoteWritesExperimental")) {
    // fall back to the supported API
    await db.transaction(fn)();
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: new Database({ url, authToken, remoteWritesExperimental: true }) followed by db.transactionAsync((txn) => ...) — the remoteWriter being present is exactly what triggers the throw.

Common situations: Copying transactionAsync-based helpers from a local-connection codebase into a remoteWrites-enabled app; enabling the experimental flag during a migration and reusing existing transaction utilities; version upgrades where remoteWritesExperimental was newly introduced.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/309f6a1a6653576d. Report an issue: GitHub.

Appendix: source

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

        Object.defineProperties(properties.immediate.value, properties);
        Object.defineProperties(properties.exclusive.value, properties);
        return properties.default.value as TransactionFunction<F>;
    }

    /**
     * Returns a function that executes the given function in a transaction
     * on a connection owned for the whole BEGIN..COMMIT window; the callback
     * receives a {@link Transaction} handle as its first argument.
     *
     * Not supported together with {@link DatabaseOpts.remoteWritesExperimental}
     * yet: remote-writes transactions run on the remote server and have no
     * local connection to hand out.
     */
    override transactionAsync<F extends (txn: Transaction, ...args: any[]) => Promise<any>>(
        fn: F,
    ): AsyncTransactionFunction<F> {
        if (this.#remoteWriter) {
            throw new Error(
                "transactionAsync is not supported with remoteWritesExperimental yet; use the deprecated transaction() for now",
            );
        }
        return super.transactionAsync(fn);
    }

    /**
     * close the database and relevant files
     */
    async close() {
        if (this.#remoteWriter) {
            await this.#remoteWriter.close();
        }
        if (this.#engine != null) {
            if (this.name != null && this.#worker != null) {
                const worker = this.#worker;
                await Promise.all(this.#engine.filePaths().map((path: string) => unregisterFileAtWorker(worker, path)));
            }

View on GitHub (pinned to 6c72522679)