tursodatabase/turso · error · TypeError

Expected first argument to be a function

Error message

Expected first argument to be a function

What it means

transaction() builds a wrapper that runs your callback between BEGIN and COMMIT, so the first argument must be a function; anything else throws this TypeError immediately. Note the source marks this wrapper deprecated precisely because it does not own the connection — concurrent statements can interleave into the transaction window — but the argument check applies regardless. The thrown error names the exact expectation.

Source

Thrown at bindings/javascript/sync/packages/native/promise.ts:267

            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.
     *
     * @deprecated Use {@link transactionAsync} instead: this wrapper does
     * not own the connection, so concurrent statements can interleave into
     * the transaction's window.
     */
    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: db.transaction(async () => { ... }) and call the returned wrapper.
  2. Check typeof fn === 'function' at the call boundary in JavaScript code.
  3. Rely on TypeScript's generic constraint F extends (...args: any[]) => Promise<any> to catch this at compile time.

Example fix

// before
const runTxn = db.transaction(userCallbackOrSql); // may be a string/undefined

// after
if (typeof userCallbackOrSql !== 'function') {
  throw new TypeError('transaction() requires a callback function');
}
const runTxn = db.transaction(userCallbackOrSql);
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof fn !== 'function') {
  throw new TypeError('transaction() requires a callback function');
}
const runTxn = db.transaction(fn);

Type guard

const isTxnCallback = (f: unknown): f is (...args: any[]) => Promise<any> =>
  typeof f === 'function';

Prevention

When it happens

Trigger: db.transaction('SELECT ...') passing SQL text instead of a callback (pattern from other drivers); db.transaction() with undefined because the callback variable was misspelled or hoisted incorrectly; passing an options object or an array of statements.

Common situations: Migrating from sqlite3/better-sqlite3 APIs or raw SQL-string transaction helpers; passing a method reference that lost its binding and became undefined; refactors renaming the callback parameter.

Related errors


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