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-turbopack-hack.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
- Pass a function: const insert = db.transaction((items) => { for (const it of items) insertStmt.execute(it); }); then invoke insert(...).
- If you meant raw transaction control, use db.exec("BEGIN") / db.exec("COMMIT") or the statement API instead of transaction().
- 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
- transaction() takes a closure, never SQL text — keep BEGIN/COMMIT strings away from it.
- Type your call sites (fn: (...args: A) => Promise<T>) so TypeScript rejects non-functions at compile time.
- When importing the callback from another module, assert the import resolved; refactors and tree-shaking can silently leave it undefined.
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
- Expected first argument to be a function
- Expected first argument to be a function
- transactionAsync() callbacks receive a Transaction handle as
- sync is disabled as database was opened without sync support
- transactionAsync is not supported with remoteWritesExperimen
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/7c9612f1fa817cb9.
Report an issue: GitHub.