mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-18 11:32:15 -05:00
fix(drizzle-adapter): defer relation metadata access (#11263)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@better-auth/drizzle-adapter": patch
|
||||
---
|
||||
|
||||
Defer Drizzle relation metadata access until a relational query runs, preserving lazy database initialization during application builds.
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
timestamp,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { DB } from "./drizzle-adapter";
|
||||
import { drizzleAdapter } from "./drizzle-adapter";
|
||||
import { drizzleAdapter as drizzleRelationsV2Adapter } from "./relations-v2";
|
||||
|
||||
describe("drizzle-adapter", () => {
|
||||
it("should create drizzle adapter", () => {
|
||||
@@ -23,6 +25,94 @@ describe("drizzle-adapter", () => {
|
||||
expect(adapter).toBeDefined();
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/11258
|
||||
*/
|
||||
describe("lazy database initialization", () => {
|
||||
const adapterFactories = [
|
||||
{
|
||||
relations: "Relations v1",
|
||||
createAdapter: drizzleAdapter,
|
||||
relationMetadata: { schema: {} },
|
||||
},
|
||||
{
|
||||
relations: "Relations v2",
|
||||
createAdapter: drizzleRelationsV2Adapter,
|
||||
relationMetadata: { relations: {} },
|
||||
},
|
||||
] as const;
|
||||
|
||||
it.for(
|
||||
adapterFactories,
|
||||
)("should not initialize a lazy database during $relations adapter construction", ({
|
||||
createAdapter,
|
||||
}) => {
|
||||
const db = new Proxy({} as DB, {
|
||||
get() {
|
||||
throw new Error("DATABASE_URL is not set");
|
||||
},
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
createAdapter(db, { provider: "pg" })({
|
||||
secret: "test-secret-that-is-at-least-32-chars-long!!",
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it.for(
|
||||
adapterFactories,
|
||||
)("should support a first $relations joined read inside a transaction", async ({
|
||||
createAdapter,
|
||||
relationMetadata,
|
||||
}) => {
|
||||
const findFirst = vi.fn().mockResolvedValue(null);
|
||||
const transactionDatabase = new Proxy(
|
||||
{
|
||||
query: { user: { findFirst } },
|
||||
} as DB,
|
||||
{
|
||||
get(target, property, receiver) {
|
||||
if (property === "_") {
|
||||
throw new Error("Transaction metadata should not be read");
|
||||
}
|
||||
return Reflect.get(target, property, receiver);
|
||||
},
|
||||
},
|
||||
);
|
||||
const database = {
|
||||
_: relationMetadata,
|
||||
transaction: (callback: (transactionDatabase: DB) => unknown) =>
|
||||
callback(transactionDatabase),
|
||||
} as DB;
|
||||
const user = pgTable("user", { id: text("id") });
|
||||
const adapter = createAdapter(database, {
|
||||
provider: "pg",
|
||||
schema: { user },
|
||||
transaction: true,
|
||||
})({
|
||||
advanced: {
|
||||
database: { joins: true, validateSchema: false },
|
||||
},
|
||||
});
|
||||
|
||||
if (!adapter.transaction) {
|
||||
throw new Error("Transaction support should be enabled");
|
||||
}
|
||||
|
||||
await expect(
|
||||
adapter.transaction((transactionAdapter) =>
|
||||
transactionAdapter.findOne({
|
||||
model: "user",
|
||||
where: [],
|
||||
join: { session: true },
|
||||
}),
|
||||
),
|
||||
).resolves.toBeNull();
|
||||
expect(findFirst).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
it("should use unique column fallback for MySQL creates without an id", async () => {
|
||||
const userRow = {
|
||||
id: 42,
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
or,
|
||||
sql,
|
||||
} from "drizzle-orm";
|
||||
import type { RelationKeysByModel } from "./join-relation-key";
|
||||
import {
|
||||
buildRelationKeysByModel,
|
||||
getOneToOneRelationKey,
|
||||
@@ -184,7 +185,9 @@ export interface DrizzleAdapterConfig {
|
||||
export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
let lazyOptions: BetterAuthOptions | null = null;
|
||||
let mysqlNoIdWarned = false;
|
||||
const relationKeysByModel = buildRelationKeysByModel(db._?.schema);
|
||||
let relationKeysByModel: RelationKeysByModel | undefined;
|
||||
const getRelationKeysByModel = () =>
|
||||
(relationKeysByModel ??= buildRelationKeysByModel(db._?.schema));
|
||||
const createCustomAdapter =
|
||||
(db: DB, inTransaction = false): AdapterFactoryCustomizeAdapterCreator =>
|
||||
({
|
||||
@@ -803,7 +806,7 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
| undefined;
|
||||
|
||||
const renamedJoinResults: { key: string; target: string }[] = [];
|
||||
const relationKeys = relationKeysByModel.get(queryModel);
|
||||
const relationKeys = getRelationKeysByModel().get(queryModel);
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [joinModel, joinAttr] of joinEntries) {
|
||||
@@ -890,7 +893,7 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
| undefined;
|
||||
|
||||
const renamedJoinResults: { key: string; target: string }[] = [];
|
||||
const relationKeys = relationKeysByModel.get(queryModel);
|
||||
const relationKeys = getRelationKeysByModel().get(queryModel);
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [joinModel, joinAttr] of joinEntries) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
or,
|
||||
sql,
|
||||
} from "drizzle-orm";
|
||||
import type { RelationKeysByModel } from "../join-relation-key";
|
||||
import {
|
||||
buildRelationKeysByModel,
|
||||
getOneToOneRelationKey,
|
||||
@@ -274,7 +275,9 @@ export interface DrizzleAdapterConfig {
|
||||
export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
let lazyOptions: BetterAuthOptions | null = null;
|
||||
let mysqlNoIdWarned = false;
|
||||
const relationKeysByModel = buildRelationKeysByModel(db._?.relations);
|
||||
let relationKeysByModel: RelationKeysByModel | undefined;
|
||||
const getRelationKeysByModel = () =>
|
||||
(relationKeysByModel ??= buildRelationKeysByModel(db._?.relations));
|
||||
const createCustomAdapter =
|
||||
(db: DB, inTransaction = false): AdapterFactoryCustomizeAdapterCreator =>
|
||||
({ getFieldName, getDefaultModelName, options, schema: baSchema }) => {
|
||||
@@ -724,7 +727,7 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
| undefined;
|
||||
|
||||
const renamedJoinResults: { key: string; target: string }[] = [];
|
||||
const relationKeys = relationKeysByModel.get(queryModel);
|
||||
const relationKeys = getRelationKeysByModel().get(queryModel);
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [joinModel, joinAttr] of joinEntries) {
|
||||
@@ -812,7 +815,7 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
| undefined;
|
||||
|
||||
const renamedJoinResults: { key: string; target: string }[] = [];
|
||||
const relationKeys = relationKeysByModel.get(queryModel);
|
||||
const relationKeys = getRelationKeysByModel().get(queryModel);
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [joinModel, joinAttr] of joinEntries) {
|
||||
|
||||
Reference in New Issue
Block a user