{"record":{"id":"ec07facdf97725d2","repo":"tursodatabase/turso","slug":"expected-first-argument-to-be-a-function-ec07fa","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-vite-dev-hack.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-vite-dev-hack.ts#L264-L300","documentation":"The vite promise wrapper's transaction() validates that its first argument is callable before dispatching — either to the remote-writer path (beginTransaction/commit around the callback) or to the base promise implementation. Passing anything that is not a function throws an immediate TypeError. This mirrors the standard better-sqlite3/libsql ergonomic where transaction() wraps a user callback.","triggerScenarios":"`db.transaction()` with no arguments; `db.transaction(myFn())` which calls the function and passes its return value; passing an arrow stored in a possibly-undefined variable (`db.transaction(this.handlers.save)` where the property does not exist); passing an object or string instead of a callback.","commonSituations":"Refactoring that renames or removes the callback but leaves the call site; dynamically selecting a callback from a map where the key is missing; copy-pasting between APIs where a SQL string is passed instead of a function; optional-chaining slips that yield undefined.","solutions":["Pass the function itself, not its result: `db.transaction(fn)` not `db.transaction(fn())`","If the callback may be missing, default it or check `typeof fn === \"function\"` before calling transaction()","Let TypeScript catch it: type the parameter as `(...args: any[]) => Promise<any>` and fix the compile error at the call site"],"exampleFix":"// before\nconst tx = db.transaction(saveRecord()); // invokes saveRecord, passes its result\ntx(params);\n\n// after\nconst tx = db.transaction(saveRecord); // passes the function itself\ntx(params);","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isAsyncFn<F>(fn: unknown): fn is (...args: any[]) => Promise<any> {\n  return typeof fn === 'function' && fn.constructor?.name !== 'AsyncFunction' || typeof fn === 'function';\n}\n// simpler and sufficient:\nconst isTxnFn = (f: unknown): f is (...args: any[]) => Promise<any> => typeof f === 'function';\n\nif (!isTxnFn(fn)) throw new TypeError(`transaction() needs a function, got ${typeof fn}`);\nconst tx = db.transaction(fn);","tryCatchPattern":"try { tx = db.transaction(fn); } catch (e) { if (e instanceof TypeError && /first argument/i.test(e.message)) { /* fix the call site — wrong argument */ } throw e; }","preventionTips":["Always pass the function reference, never its invocation result","Type transaction helpers generically: `<F extends (...args: any[]) => Promise<any>>` so TS rejects non-functions","When picking callbacks from maps, guard missing entries before calling transaction()"],"tags":["transaction","type-error","javascript","vite"],"backgroundTag":"invalid-argument-type","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}