tursodatabase/turso · error · TypeError

Expected first argument to be a function

Error message

Expected first argument to be a function

What it means

Connection.transaction(fn) builds synchronous-style transaction wrappers (like better-sqlite3's transaction()) and requires fn to be a function; anything else throws a plain TypeError immediately. The wrapper later drives fn between BEGIN and COMMIT/ROLLBACK via db.exec, so a non-function input cannot work at all.

Source

Thrown at serverless/javascript/src/connection.ts:395

   *
   * @param fn - The function to wrap in a transaction
   * @returns A function that will execute fn within a transaction
   *
   * @example
   * ```typescript
   * const insert = await client.prepare("INSERT INTO users (name) VALUES (?)");
   * const insertMany = client.transaction((users) => {
   *   for (const user of users) {
   *     insert.run([user]);
   *   }
   * });
   *
   * await insertMany(['Alice', 'Bob', 'Charlie']);
   * ```
   */
  transaction(fn: (...args: any[]) => any): any {
    if (typeof fn !== "function") {
      throw new TypeError("Expected first argument to be a function");
    }

    const db = this;
    const wrapTxn = (mode: string) => {
      return async (...bindParameters: any[]) => {
        await db.exec("BEGIN " + mode);
        try {
          const result = await fn(...bindParameters);
          await db.exec("COMMIT");
          return result;
        } catch (err) {
          await db.exec("ROLLBACK");
          throw err;
        }
      };
    };

    const properties = {

View on GitHub (pinned to bad083fafb)

Solutions

  1. Pass a function reference: db.transaction((...args) => { ... })
  2. If fn comes from an option, default it or validate it before calling transaction()
  3. Check typeof fn === 'function' in your own wrapper before delegating
  4. Make sure you are not invoking the helper and passing its return value

Example fix

// before
const insertMany = client.transaction(); // no fn passed
// later: insertMany([...]) — or client.transaction(someObject)

// after
const insertMany = client.transaction((users) => {
  for (const user of users) insert.run([user]);
});
await insertMany(['Alice', 'Bob']);
Defensive patterns

Strategy: type-guard

Validate before calling

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

Type guard

function isCallable<T extends (...args: any[]) => any>(v: T | unknown): v is T {
  return typeof v === 'function';
}

Try / catch

try {
  const wrapped = db.transaction(fn);
} catch (e) {
  if (e instanceof TypeError && e.message.includes('Expected first argument to be a function')) {
    throw new TypeError('pass a function reference, not its result, to transaction()');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling db.transaction(db.insertMany) incorrectly bound, passing the result of a call instead of the function (db.transaction(makeFn())), passing undefined because an optional helper was not provided, or passing an object with a run method.

Common situations: Refactoring from inline arrow functions to named helpers and losing the reference; optional-callback APIs defaulting to undefined; copy-paste from code that stored the returned function but calling transaction on the wrong variable.

Related errors


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