fix(core): coerce string where values to match field types in adapter (#7860)

This commit is contained in:
Taesu
2026-02-12 00:11:20 +00:00
committed by GitHub
parent 150d2a3454
commit a30fc6d6bf
2 changed files with 86 additions and 2 deletions
+64
View File
@@ -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();
+22 -2
View File
@@ -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 (