{"record":{"id":"208fbdc70619c38b","repo":"tursodatabase/turso","slug":"transactionasync-callbacks-receive-a-transaction-208fbd","errorCode":null,"errorMessage":"transactionAsync() callbacks receive a Transaction handle as their first argument and must declare it: db.transactionAsync(async (tx, ...args) => { await tx.run(...) }).","messagePattern":"transactionAsync\\(\\) callbacks receive a Transaction handle as their first argument and must declare it: db\\.transactionAsync\\(async \\(tx, \\.\\.\\.args\\) => (.+?)\\)\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"serverless/javascript/src/connection.ts","lineNumber":474,"sourceCode":"   *\n   * @example\n   * ```typescript\n   * const insertMany = client.transactionAsync(async (tx, users) => {\n   *   const insert = await tx.prepare(\"INSERT INTO users (name) VALUES (?)\");\n   *   for (const user of users) {\n   *     await insert.run([user]);\n   *   }\n   * });\n   *\n   * await insertMany(['Alice', 'Bob', 'Charlie']);\n   * ```\n   */\n  transactionAsync(fn: (tx: Transaction, ...args: any[]) => any): any {\n    if (typeof fn !== \"function\") {\n      throw new TypeError(\"Expected first argument to be a function\");\n    }\n    if (fn.length === 0) {\n      throw new TypeError(\n        \"transactionAsync() callbacks receive a Transaction handle as their first argument \" +\n        \"and must declare it: db.transactionAsync(async (tx, ...args) => { await tx.run(...) }).\",\n      );\n    }\n\n    const db = this;\n    const wrapTxn = (mode: string) => {\n      return async (...bindParameters: any[]) => {\n        if (!db.isOpen) {\n          throw new TypeError(\"The database connection is not open\");\n        }\n        // The transaction owns a dedicated session (server stream), so the\n        // connection is not locked for its duration: concurrent statements\n        // on the connection run on its own stream, outside the transaction.\n        const session = new Session(db.config);\n        const txn = new Transaction(session, db.defaultSafeIntegerMode);\n        try {\n          await txn.exec(\"BEGIN \" + mode);","sourceCodeStart":456,"sourceCodeEnd":492,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/serverless/javascript/src/connection.ts#L456-L492","documentation":"Thrown by Connection.transactionAsync() when the callback declares zero parameters (fn.length === 0). transactionAsync() runs the callback on a dedicated server stream and passes a Transaction handle as the first argument; all transaction SQL must go through that handle, so a callback that ignores it would silently run SQL on the connection's own stream, outside the transaction. The library rejects such callbacks up front to make that bug impossible.","triggerScenarios":"Calling db.transactionAsync(async () => {...}) or db.transactionAsync(function() {...}) — any callback with an empty parameter list. Note that rest-only signatures like (...args) => {...} also have fn.length === 0 and are rejected, while (tx, ...args) => {...} passes.","commonSituations":"Code migrated from the deprecated transaction(), whose callback receives no tx argument; IDEs or linters removing an 'unused' parameter; passing a zero-arg wrapper like () => runWork(db) that uses the outer Connection instead of the transaction handle.","solutions":["Declare the transaction handle as the first callback parameter: db.transactionAsync(async (tx, ...args) => { await tx.run(...) })","Run all transaction SQL through the tx handle (tx.run/tx.get/tx.all/tx.prepare/tx.batch), never through db — Connection calls execute outside the transaction and cannot see its uncommitted writes","If you did not want a transaction-scoped callback, call db.run()/db.all() directly instead of transactionAsync()"],"exampleFix":"// before\nconst txn = db.transactionAsync(async () => {\n  await db.run(\"INSERT INTO users(name) VALUES ('Alice')\"); // runs OUTSIDE the transaction\n});\n\n// after\nconst txn = db.transactionAsync(async (tx) => {\n  await tx.run(\"INSERT INTO users(name) VALUES ('Alice')\"); // inside the transaction\n});\nawait txn.immediate();","handlingStrategy":"validation","validationCode":"const declaresFirstParam = (fn: unknown): boolean =>\n  typeof fn === \"function\" && fn.length > 0;\n\nif (!declaresFirstParam(callback)) {\n  throw new Error(\"transactionAsync callback must declare (tx, ...args)\");\n}\nconst txn = db.transactionAsync(callback);","typeGuard":"function isTxCallback(\n  fn: unknown,\n): fn is (tx: any, ...args: any[]) => any {\n  // fn.length counts parameters before the first default/rest,\n  // so (tx, ...args) => {} passes and (...args) => {} or () => {} fails.\n  return typeof fn === \"function\" && fn.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Type the callback explicitly as (tx: Transaction, ...args: any[]) => any so TypeScript rejects zero-parameter functions at compile time","Never write a rest-only parameter list for transaction callbacks — fn.length is 0 for (...args)","Run all transaction SQL through the tx handle; doing so makes forgetting the parameter impossible"],"tags":["typescript","javascript","transaction","api-misuse"],"backgroundTag":"callback-signature-mismatch","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}