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
- Pass the function reference, not its result: db.transaction(fn), not db.transaction(fn())
- Verify the callback is defined before calling transaction() (guard against undefined imports)
- If you intended to pass data, restructure: transaction(fn)(data) - the wrapper takes bind parameters at call time
- 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
- Pass the reference, never the call result: transaction(fn), not transaction(fn())
- Reject async functions too - they pass the type check but break COMMIT/ROLLBACK ordering
- Lint for immediately-invoked expressions inside call arguments
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
- The supplied SQL string contains no statements
- Expected first argument to be a string
- Expected second argument to be an options object
- Expected first argument to be an array of statements
- The database connection is not open
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/af633394214b8db2.
Report an issue: GitHub.