tursodatabase/turso · error · TypeError

Expected first argument to be a function

Error message

Expected first argument to be a function

What it means

transaction(fn) builds a reusable transaction function from a callback and throws a TypeError when its first argument is not a function. The check exists because the API takes a closure that is invoked with the transaction's bind parameters — not SQL text, not a statement list. It mirrors the better-sqlite3-style functional transaction API also used by the non-sync turso bindings.

Source

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

        const isReadonly = category === "read";
        return new RemoteWriteStatement(
            localStmt,
            sql,
            isReadonly,
            this.#remoteWriter,
            () => this.pull(),
        ) as any;
    }

    /**
     * Returns a function that executes the given function in a transaction.
     * When remoteWrites is enabled, the entire transaction goes to remote.
     */
    override transaction<F extends (...args: any[]) => Promise<any>>(
        fn: F,
    ): TransactionFunction<F> {
        if (typeof fn !== "function")
            throw new TypeError("Expected first argument to be a function");

        if (!this.#remoteWriter) {
            return super.transaction(fn);
        }

        const db = this;
        const remoteWriter = this.#remoteWriter;
        const wrapTxn = (mode: string) => {
            return async (...bindParameters: any[]) => {
                await remoteWriter.beginTransaction(mode);
                try {
                    const result = await fn(...bindParameters);
                    await remoteWriter.commitTransaction();
                    await db.pull();
                    return result;
                } catch (err) {
                    await remoteWriter.rollbackTransaction();
                    throw err;

View on GitHub (pinned to bad083fafb)

Solutions

  1. Pass a function: const insert = db.transaction((items) => { for (const it of items) insertStmt.execute(it); }); then invoke insert(...).
  2. If you meant raw transaction control, use db.exec("BEGIN") / db.exec("COMMIT") or the statement API instead of transaction().
  3. If fn is unexpectedly undefined, fix the import/destructuring that produced it before the call — log the value right before passing it.

Example fix

// before
const save = db.transaction("INSERT INTO users (name) VALUES (?)"); // TypeError
await save("ada");

// after — pass a closure; it receives the transaction's bind parameters
const save = db.transaction((name: string) => {
    db.exec({ sql: "INSERT INTO users (name) VALUES (?)", args: [name] });
});
await save("ada");
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof fn !== "function") {
    throw new TypeError(`transaction() needs a callback, got ${typeof fn}`);
}
const tx = db.transaction(fn);

Type guard

type TxCallback<A extends unknown[]> = (...args: A) => Promise<unknown>;

function isTxCallback<A extends unknown[]>(fn: unknown): fn is TxCallback<A> {
    return typeof fn === "function";
}

const tx = isTxCallback(handler) ? db.transaction(handler) : undefined;

Try / catch

try {
    const tx = db.transaction(handler);
} catch (e) {
    if (e instanceof TypeError && e.message.includes("first argument")) {
        // handler was undefined or not a function — fix the import or pass a closure
    }
    throw e;
}

Prevention

When it happens

Trigger: db.transaction("BEGIN IMMEDIATE") or any string; db.transaction() with fn undefined (broken import, wrong destructuring, a mock that does not supply the function); passing an array of statements. The guard is the first statement in the override, checked before the remoteWriter/super dispatch.

Common situations: Porting raw-SQL transaction control (BEGIN/COMMIT strings) to the functional API; SSR or test setups where the callback comes from a module that resolved to undefined; any-typed call sites where TypeScript cannot catch the mistake at compile time.

Related errors


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