tursodatabase/turso · error · TypeError

Expected first argument to be a function

Error message

Expected first argument to be a function

What it means

Thrown by Database.transaction() when its first argument is not a function. transaction() wraps a synchronous callback between BEGIN and COMMIT/ROLLBACK, so the callback is essential; anything else (a string, an arrow-call result, undefined) is rejected up front with a TypeError.

Source

Thrown at bindings/javascript/packages/common/compat.ts:179

    if (!sql) {
      throw new RangeError("The supplied SQL string contains no statements");
    }

    try {
      return new Statement(this.db.prepare(sql), this.db);
    } catch (err) {
      throw convertError(err);
    }
  }

  /**
   * Returns a function that executes the given function in a transaction.
   *
   * @param {function} fn - The function to wrap in a transaction.
   */
  transaction(fn) {
    if (typeof fn !== "function")
      throw new TypeError("Expected first argument to be a function");

    const db = this;
    const wrapTxn = (mode) => {
      return (...bindParameters) => {
        db.exec("BEGIN " + mode);
        try {
          const result = fn(...bindParameters);
          db.exec("COMMIT");
          return result;
        } catch (err) {
          db.exec("ROLLBACK");
          throw err;
        }
      };
    };
    const properties = {
      default: { value: wrapTxn("") },
      deferred: { value: wrapTxn("DEFERRED") },

View on GitHub (pinned to bad083fafb)

Solutions

  1. Pass the function reference, not its result: db.transaction(fn), not db.transaction(fn())
  2. Verify the callback is defined before calling transaction() (guard against undefined imports)
  3. If you intended to pass data, restructure: transaction(fn)(data) - the wrapper takes bind parameters at call time
  4. Keep the callback synchronous; async functions silently break rollback guarantees even though they pass the type check

Example fix

// before
const insertUser = db.transaction(createUser(42)); // calls fn, passes result

// after
const insertUser = db.transaction(createUser); // reference only
insertUser(42);
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof fn !== 'function') {
  throw new TypeError('transaction() requires a function reference (no call parentheses)');
}
const tx = db.transaction(fn);

Type guard

function isSyncFunction(fn: unknown): fn is (...args: unknown[]) => unknown {
  return typeof fn === 'function' && fn.constructor?.name !== 'AsyncFunction';
}

Prevention

When it happens

Trigger: Calling db.transaction(fn()) instead of db.transaction(fn) (executing the function and passing its return value); passing the name of a function as a string; passing undefined because an import failed or the callback is optional and missing.

Common situations: Refactoring from inline callbacks to named functions and accidentally leaving parentheses in; copy-pasting better-sqlite3 examples into code where fn is conditionally defined; passing an async function - note it passes this check but breaks transaction semantics because COMMIT runs before the awaited work completes.

Related errors


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