fix(drizzle-adapter): count affected rows on D1 and postgres-js (#10257)

This commit is contained in:
Taesu
2026-06-26 16:48:10 -07:00
committed by GitHub
parent 39c2d59b34
commit 930b260cfd
3 changed files with 56 additions and 20 deletions

View File

@@ -0,0 +1,5 @@
---
"@better-auth/drizzle-adapter": patch
---
Fix `updateMany` and `deleteMany` reporting 0 affected rows on Cloudflare D1 and on the postgres-js / bun-sql drivers.

View File

@@ -250,6 +250,20 @@ describe("drizzle-adapter", () => {
result: [{ affectedRows: 2 }],
expected: 2,
},
// postgres-js / bun-sql return an Array subclass carrying `count`;
// a non-RETURNING write has length 0, so the count must be read off
// the array itself, not from `result.length`.
{
provider: "pg" as const,
result: Object.assign([], { count: 2 }),
expected: 2,
},
// Cloudflare D1 nests the affected-row count under `meta.changes`.
{
provider: "sqlite" as const,
result: { meta: { changes: 2 } },
expected: 2,
},
])("returns the affected-row count for $provider ($expected)", async ({
provider,
result,

View File

@@ -44,11 +44,14 @@ export interface DB {
/**
* Derive the number of affected rows from a Drizzle write result.
*
* Drizzle's drivers report affected rows under different shapes: postgres-js
* exposes `rowCount`, mysql2 reports `affectedRows`/`rowsAffected` (sometimes as
* the first element of a result-header array), and better-sqlite3 uses
* `changes`. This normalizes those so write methods that depend on affected
* rows honor the adapter contract instead of leaking the raw driver result.
* Drizzle returns the raw per-driver result for a non-returning write, so the
* count lives under a different field per driver: node-postgres / neon expose
* `rowCount`, postgres-js / bun-sql carry `count` on an Array subclass, mysql2
* reports `affectedRows` (in a result-header array), planetscale and other
* serverless drivers use `rowsAffected`, better-sqlite3 uses `changes`, and
* Cloudflare D1 nests the count under `meta.changes`. This normalizes them so
* write methods that depend on affected rows honor the adapter contract instead
* of leaking the raw driver result.
*/
function getAffectedRowCount(
result: unknown,
@@ -57,8 +60,19 @@ function getAffectedRowCount(
): number {
let count: unknown = 0;
if (result && typeof result === "object" && "rowCount" in result) {
// node-postgres / neon expose `rowCount`.
count = (result as { rowCount: unknown }).rowCount;
} else if (
result &&
typeof result === "object" &&
typeof (result as { count?: unknown }).count === "number"
) {
// postgres-js / bun-sql return an Array subclass carrying `count`.
// A non-returning write has length 0, so read this before the Array
// branch falls back to `result.length`.
count = (result as { count: number }).count;
} else if (Array.isArray(result)) {
// mysql2 returns a `[ResultSetHeader]` tuple.
count =
result.length > 0 && hasDriverRowCount(result[0])
? readDriverRowCount(result[0])
@@ -76,23 +90,26 @@ function getAffectedRowCount(
return count;
}
function hasDriverRowCount(result: unknown): boolean {
return (
!!result &&
typeof result === "object" &&
("affectedRows" in result ||
"rowsAffected" in result ||
"changes" in result)
);
function readDriverRowCount(result: unknown): unknown {
if (!result || typeof result !== "object") return undefined;
if ("affectedRows" in result) return result.affectedRows;
if ("rowsAffected" in result) return result.rowsAffected;
if ("changes" in result) return result.changes;
// Cloudflare D1 nests the affected-row count under `meta.changes`.
// @see https://developers.cloudflare.com/d1/worker-api/return-object/
if ("meta" in result) {
const meta = result.meta;
if (meta && typeof meta === "object" && "changes" in meta) {
return meta.changes;
}
}
return undefined;
}
function readDriverRowCount(result: unknown): unknown {
const r = result as {
affectedRows?: unknown;
rowsAffected?: unknown;
changes?: unknown;
};
return r.affectedRows ?? r.rowsAffected ?? r.changes;
function hasDriverRowCount(result: unknown): boolean {
return readDriverRowCount(result) !== undefined;
}
export interface DrizzleAdapterConfig {