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) wraps the given callback so that the returned function executes it inside a BEGIN..COMMIT window, and it validates up front that fn is callable. Passing anything that is not a function — the result of invoking the callback early, a Promise, or undefined — fails fast with this TypeError before any transaction is started.

Solutions

  1. Pass the function reference itself: db.transaction(fn), then invoke the returned wrapper
  2. Remove any early invocation — the () after a callback expression turns it into a value
  3. When fn comes from dynamic input, guard with typeof fn === "function" before calling transaction()

Example fix

// before
const run = db.transaction(processOrder(order)); // calls now, passes a Promise

// after
const run = db.transaction(() => processOrder(order)); // pass a function
await run();
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof fn !== "function") {
  throw new TypeError("transaction() needs the callback function itself");
}
const run = db.transaction(fn);

Type guard

function isCallable(fn: unknown): fn is (...args: unknown[]) => unknown {
  return typeof fn === "function";
}

Try / catch

try {
  await db.transaction(fn)();
} catch (err) {
  if (err instanceof TypeError && /first argument/i.test(err.message)) {
    // fn was not a function: fix the call site, do not retry
    throw new Error("transaction() requires a function argument");
  }
  throw err;
}

Prevention

When it happens

Trigger: db.transaction(runJob()) invokes runJob immediately and passes its return value; db.transaction(undefined) when the callback variable is misnamed, conditionally assigned, or lost in a refactor; passing a promise from an async IIFE instead of the function itself. Same behavior in the turbopack variant.

Common situations: Refactoring call sites around the wrapped-function API; dynamically selecting a callback that can be undefined; any-typed configuration that lets a non-function slip past TypeScript and fail at runtime.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at bindings/javascript/sync/packages/wasm/promise-turbopack-hack.ts:277

        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 6c72522679)