mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-24 00:15:17 -05:00
fix(core): coerce string where values to match field types in adapter (#7860)
This commit is contained in:
@@ -97,6 +97,70 @@ describe("db", async () => {
|
||||
expect(session?.user.email).toBe("test@email.com");
|
||||
});
|
||||
|
||||
it("should coerce string where values to match field types", async () => {
|
||||
// HTTP query params arrive as strings.
|
||||
// The adapter should coerce values to match the field's schema type.
|
||||
const { auth, db } = await getTestInstance(
|
||||
{
|
||||
user: {
|
||||
additionalFields: {
|
||||
age: { type: "number", required: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Uses MongoDB because SQLite/MySQL/PostgreSQL silently cast types,
|
||||
// which would make this test pass even without the coercion code.
|
||||
testWith: "mongodb",
|
||||
},
|
||||
);
|
||||
|
||||
// boolean: "false" → false, "true" → true
|
||||
const users = await db.findMany<{ emailVerified: boolean }>({
|
||||
model: "user",
|
||||
where: [{ field: "emailVerified", operator: "eq", value: "false" }],
|
||||
});
|
||||
expect(users.length).toBeGreaterThanOrEqual(1);
|
||||
expect(users.every((u) => u.emailVerified === false)).toBe(true);
|
||||
|
||||
// number: "25" → 25
|
||||
await db.update({
|
||||
model: "user",
|
||||
where: [{ field: "emailVerified", operator: "eq", value: false }],
|
||||
update: { age: 25 },
|
||||
});
|
||||
const byAge = await db.findMany<{ age: number | null }>({
|
||||
model: "user",
|
||||
where: [{ field: "age", operator: "eq", value: "25" }],
|
||||
});
|
||||
expect(byAge.length).toBeGreaterThanOrEqual(1);
|
||||
expect(byAge.every((u) => u.age === 25)).toBe(true);
|
||||
|
||||
// number array: ["25", "30"] → [25, 30]
|
||||
await auth.api.signUpEmail({
|
||||
body: {
|
||||
email: "age30@test.com",
|
||||
password: "password",
|
||||
name: "user-30",
|
||||
age: 30,
|
||||
},
|
||||
});
|
||||
await auth.api.signUpEmail({
|
||||
body: {
|
||||
email: "age40@test.com",
|
||||
password: "password",
|
||||
name: "user-40",
|
||||
age: 40,
|
||||
},
|
||||
});
|
||||
const byAgeIn = await db.findMany<{ age: number | null }>({
|
||||
model: "user",
|
||||
where: [{ field: "age", operator: "in", value: ["25", "30"] }],
|
||||
});
|
||||
expect(byAgeIn).toHaveLength(2);
|
||||
expect(byAgeIn.map((u) => u.age).sort()).toEqual([25, 30]);
|
||||
});
|
||||
|
||||
it("delete hooks", async () => {
|
||||
const hookUserDeleteBefore = vi.fn();
|
||||
const hookUserDeleteAfter = vi.fn();
|
||||
|
||||
@@ -540,12 +540,32 @@ export const createAdapterFactory =
|
||||
newValue = value.toISOString();
|
||||
}
|
||||
|
||||
if (fieldAttr.type === "boolean" && typeof newValue === "string") {
|
||||
newValue = newValue === "true";
|
||||
}
|
||||
|
||||
if (fieldAttr.type === "number") {
|
||||
if (typeof newValue === "string" && newValue.trim() !== "") {
|
||||
const parsed = Number(newValue);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
newValue = parsed;
|
||||
}
|
||||
} else if (Array.isArray(newValue)) {
|
||||
const parsed = newValue.map((v) =>
|
||||
typeof v === "string" && v.trim() !== "" ? Number(v) : NaN,
|
||||
);
|
||||
if (parsed.every((n) => !Number.isNaN(n))) {
|
||||
newValue = parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
fieldAttr.type === "boolean" &&
|
||||
typeof value === "boolean" &&
|
||||
typeof newValue === "boolean" &&
|
||||
!config.supportsBooleans
|
||||
) {
|
||||
newValue = value ? 1 : 0;
|
||||
newValue = newValue ? 1 : 0;
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user