{"record":{"id":"04a3843e395549a0","repo":"tursodatabase/turso","slug":"expected-first-argument-to-be-a-function-04a384","errorCode":null,"errorMessage":"Expected first argument to be a function","messagePattern":"Expected first argument to be a function","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"bindings/javascript/sync/packages/wasm/promise-bundle.ts","lineNumber":282,"sourceCode":"        const isReadonly = category === \"read\";\n        return new RemoteWriteStatement(\n            localStmt,\n            sql,\n            isReadonly,\n            this.#remoteWriter,\n            () => this.pull(),\n        ) as any;\n    }\n\n    /**\n     * Returns a function that executes the given function in a transaction.\n     * When remoteWrites is enabled, the entire transaction goes to remote.\n     */\n    override transaction<F extends (...args: any[]) => Promise<any>>(\n        fn: F,\n    ): TransactionFunction<F> {\n        if (typeof fn !== \"function\")\n            throw new TypeError(\"Expected first argument to be a function\");\n\n        if (!this.#remoteWriter) {\n            return super.transaction(fn);\n        }\n\n        const db = this;\n        const remoteWriter = this.#remoteWriter;\n        const wrapTxn = (mode: string) => {\n            return async (...bindParameters: any[]) => {\n                await remoteWriter.beginTransaction(mode);\n                try {\n                    const result = await fn(...bindParameters);\n                    await remoteWriter.commitTransaction();\n                    await db.pull();\n                    return result;\n                } catch (err) {\n                    await remoteWriter.rollbackTransaction();\n                    throw err;","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/javascript/sync/packages/wasm/promise-bundle.ts#L264-L300","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst save = db.transaction(\"INSERT INTO users (name) VALUES (?)\"); // TypeError\nawait save(\"ada\");\n\n// after — pass a closure; it receives the transaction's bind parameters\nconst save = db.transaction((name: string) => {\n    db.exec({ sql: \"INSERT INTO users (name) VALUES (?)\", args: [name] });\n});\nawait save(\"ada\");","handlingStrategy":"type-guard","validationCode":"if (typeof fn !== \"function\") {\n    throw new TypeError(`transaction() needs a callback, got ${typeof fn}`);\n}\nconst tx = db.transaction(fn);","typeGuard":"type TxCallback<A extends unknown[]> = (...args: A) => Promise<unknown>;\n\nfunction isTxCallback<A extends unknown[]>(fn: unknown): fn is TxCallback<A> {\n    return typeof fn === \"function\";\n}\n\nconst tx = isTxCallback(handler) ? db.transaction(handler) : undefined;","tryCatchPattern":"try {\n    const tx = db.transaction(handler);\n} catch (e) {\n    if (e instanceof TypeError && e.message.includes(\"first argument\")) {\n        // handler was undefined or not a function — fix the import or pass a closure\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["typescript","api-misuse","transaction","wasm"],"backgroundTag":"invalid-argument-type","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}