mirror of
https://github.com/better-auth/better-auth.git
synced 2026-08-23 17:58:47 -05:00
chore!: move joins to advanced.database.joins (#10359)
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
---
|
||||
"better-auth": minor
|
||||
"@better-auth/core": minor
|
||||
"@better-auth/drizzle-adapter": minor
|
||||
---
|
||||
|
||||
Database joins have moved out of `experimental` into a stable option at `advanced.database.joins` (default: `false`).
|
||||
|
||||
If you previously set `experimental: { joins: true }`, update your config to:
|
||||
|
||||
```ts
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Adapters that support native joins use them when enabled. If an adapter cannot return joined data for a query, Better Auth falls back to additional queries and combines the results. Drizzle and Prisma users should ensure their schema includes the required relations (`npx auth@latest generate`).
|
||||
@@ -109,27 +109,29 @@ const auth = betterAuth({
|
||||
|
||||
***
|
||||
|
||||
### Database Joins Improvements (experimental)
|
||||
### Database Joins Improvements
|
||||
|
||||
Better-Auth now supports database joins, improving over 50 endpoints by 2 to 3x in latency. This is achieved by using database joins under the hood.
|
||||
|
||||
To enable, simply set the `joins` flag to `true` under `experimental` in your auth config.
|
||||
To enable, set `advanced.database.joins` to `true` in your auth config.
|
||||
Then, re-run migrations or schema generation to get the updated schema which supports joins!
|
||||
|
||||
[👉 Read more about database joins](/docs/concepts/database#experimental-joins)
|
||||
[👉 Read more about database joins](/docs/concepts/database#joins)
|
||||
|
||||
```ts
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
experimental: {
|
||||
joins: true,
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
Although joins are experimental, they are supported by all adapters and will be enabled by default in the next release.
|
||||
Joins are supported by all adapters. When disabled (the default), Better Auth falls back to separate queries.
|
||||
</Callout>
|
||||
|
||||
***
|
||||
|
||||
@@ -59,20 +59,24 @@ To generate and apply the migration, run the following commands:
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Joins (Experimental)
|
||||
## Joins
|
||||
|
||||
Database joins is useful when Better-Auth needs to fetch related data from multiple tables in a single query.
|
||||
Endpoints like `/get-session`, `/get-full-organization` and many others benefit greatly from this feature,
|
||||
seeing upwards of 2x to 3x performance improvements depending on database latency.
|
||||
|
||||
The Drizzle adapter supports joins out of the box since version `1.4.0`.
|
||||
To enable this feature, you need to set the `experimental.joins` option to `true` in your auth configuration.
|
||||
To enable this feature, set `advanced.database.joins` to `true` in your auth configuration.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
experimental: { joins: true }
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -39,20 +39,24 @@ export const auth = betterAuth({
|
||||
|
||||
For MongoDB, we don't need to generate or migrate the schema.
|
||||
|
||||
## Joins (Experimental)
|
||||
## Joins
|
||||
|
||||
Database joins is useful when Better-Auth needs to fetch related data from multiple tables in a single query.
|
||||
Endpoints like `/get-session`, `/get-full-organization` and many others benefit greatly from this feature,
|
||||
seeing upwards of 2x to 3x performance improvements depending on database latency.
|
||||
|
||||
The MongoDB adapter supports joins out of the box since version `1.4.0`.
|
||||
To enable this feature, you need to set the `experimental.joins` option to `true` in your auth configuration.
|
||||
To enable this feature, set `advanced.database.joins` to `true` in your auth configuration.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
experimental: { joins: true }
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -103,27 +103,27 @@ your database schema based on your Better Auth configuration and plugins.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Joins (Experimental)
|
||||
## Joins
|
||||
|
||||
Database joins is useful when Better-Auth needs to fetch related data from multiple tables in a single query.
|
||||
Endpoints like `/get-session`, `/get-full-organization` and many others benefit greatly from this feature,
|
||||
seeing upwards of 2x to 3x performance improvements depending on database latency.
|
||||
|
||||
The Kysely MS SQL dialect supports joins out of the box since version `1.4.0`.
|
||||
To enable this feature, you need to set the `experimental.joins` option to `true` in your auth configuration.
|
||||
To enable this feature, set `advanced.database.joins` to `true` in your auth configuration.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
experimental: { joins: true }
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
It's possible that you may need to run migrations after enabling this feature.
|
||||
</Callout>
|
||||
|
||||
## Additional Information
|
||||
|
||||
MS SQL is supported under the hood via the [Kysely](https://kysely.dev/) adapter, any database supported by Kysely would also be supported. (<Link href="/docs/adapters/other-relational-databases">Read more here</Link>)
|
||||
|
||||
@@ -81,28 +81,27 @@ your database schema based on your Better Auth configuration and plugins.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Joins (Experimental)
|
||||
## Joins
|
||||
|
||||
Database joins is useful when Better-Auth needs to fetch related data from multiple tables in a single query.
|
||||
Endpoints like `/get-session`, `/get-full-organization` and many others benefit greatly from this feature,
|
||||
seeing upwards of 2x to 3x performance improvements depending on database latency.
|
||||
|
||||
The Kysely MySQL dialect supports joins out of the box since version `1.4.0`.
|
||||
|
||||
To enable this feature, you need to set the `experimental.joins` option to `true` in your auth configuration.
|
||||
To enable this feature, set `advanced.database.joins` to `true` in your auth configuration.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
experimental: { joins: true }
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
It's possible that you may need to run migrations after enabling this feature.
|
||||
</Callout>
|
||||
|
||||
## Additional Information
|
||||
|
||||
MySQL is supported under the hood via the [Kysely](https://kysely.dev/) adapter, any database supported by Kysely would also be supported. (<Link href="/docs/adapters/other-relational-databases">Read more here</Link>)
|
||||
|
||||
@@ -67,27 +67,27 @@ your database schema based on your Better Auth configuration and plugins.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Joins (Experimental)
|
||||
## Joins
|
||||
|
||||
Database joins is useful when Better-Auth needs to fetch related data from multiple tables in a single query.
|
||||
Endpoints like `/get-session`, `/get-full-organization` and many others benefit greatly from this feature,
|
||||
seeing upwards of 2x to 3x performance improvements depending on database latency.
|
||||
|
||||
The Kysely PostgreSQL dialect supports joins out of the box since version `1.4.0`.
|
||||
To enable this feature, you need to set the `experimental.joins` option to `true` in your auth configuration.
|
||||
To enable this feature, set `advanced.database.joins` to `true` in your auth configuration.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
experimental: { joins: true }
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
It's possible that you may need to run migrations after enabling this feature.
|
||||
</Callout>
|
||||
|
||||
## Use a non-default schema
|
||||
|
||||
In most cases, the default schema is `public`. To have Better Auth use a
|
||||
|
||||
@@ -67,20 +67,24 @@ your database schema based on your Better Auth configuration and plugins.
|
||||
npx auth@latest generate
|
||||
```
|
||||
|
||||
## Joins (Experimental)
|
||||
## Joins
|
||||
|
||||
Database joins is useful when Better-Auth needs to fetch related data from multiple tables in a single query.
|
||||
Endpoints like `/get-session`, `/get-full-organization` and many others benefit greatly from this feature,
|
||||
seeing upwards of 2x to 3x performance improvements depending on database latency.
|
||||
|
||||
The Prisma adapter supports joins out of the box since version `1.4.0`.
|
||||
To enable this feature, you need to set the `experimental.joins` option to `true` in your auth configuration.
|
||||
To enable this feature, set `advanced.database.joins` to `true` in your auth configuration.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
experimental: { joins: true }
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -108,25 +108,27 @@ your database schema based on your Better Auth configuration and plugins.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Joins (Experimental)
|
||||
## Joins
|
||||
|
||||
Database joins is useful when Better-Auth needs to fetch related data from multiple tables in a single query.
|
||||
Endpoints like `/get-session`, `/get-full-organization` and many others benefit greatly from this feature,
|
||||
seeing upwards of 2x to 3x performance improvements depending on database latency.
|
||||
|
||||
The Kysely SQLite dialect supports joins out of the box since version `1.4.0`.
|
||||
To enable this feature, you need to set the `experimental.joins` option to `true` in your auth configuration.
|
||||
To enable this feature, set `advanced.database.joins` to `true` in your auth configuration.
|
||||
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
experimental: { joins: true }
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
It's possible that you may need to run migrations after enabling this feature.
|
||||
</Callout>
|
||||
|
||||
## Additional Information
|
||||
|
||||
SQLite is supported under the hood via the [Kysely](https://kysely.dev/) adapter, any database supported by Kysely would also be supported. (<Link href="/docs/adapters/other-relational-databases">Read more here</Link>)
|
||||
|
||||
@@ -1013,14 +1013,14 @@ To add new tables and columns to your database, you have two options:
|
||||
|
||||
Both methods ensure your database schema stays up to date with your plugins' requirements.
|
||||
|
||||
## Experimental Joins
|
||||
## Joins
|
||||
|
||||
Since Better-Auth version `1.4` we've introduced experimental database joins support.
|
||||
Since Better-Auth version `1.4` we've introduced database joins support.
|
||||
This allows Better-Auth to perform multiple database queries in a single request, reducing the number of database roundtrips.
|
||||
Over 50 endpoints support joins, and we're constantly adding more.
|
||||
|
||||
Under the hood, our adapter system supports joins natively, meaning even if you don't enable experimental joins,
|
||||
it will still fallback to making multiple database queries and combining the results.
|
||||
Under the hood, our adapter system supports joins natively. When joins are disabled (the default),
|
||||
Better-Auth falls back to making multiple database queries and combining the results.
|
||||
|
||||
To enable joins, update your auth config with the following:
|
||||
|
||||
@@ -1028,19 +1028,22 @@ To enable joins, update your auth config with the following:
|
||||
import { betterAuth } from "better-auth";
|
||||
|
||||
export const auth = betterAuth({
|
||||
experimental: { joins: true }
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The Better-Auth `1.4` CLI will generate DrizzleORM and PrismaORM relationships for you so if you do not have those already
|
||||
be sure to update your schema by running our migrate or generate CLI commands to be up-to-date with the latest required schema.
|
||||
Make sure your DrizzleORM or PrismaORM schema includes the necessary relationships — run our migrate or generate CLI commands to stay up-to-date.
|
||||
|
||||
It's very important to read the documentation regarding experimental joins for your given adapter:
|
||||
Read the documentation regarding joins for your given adapter:
|
||||
|
||||
* [DrizzleORM](/docs/adapters/drizzle#joins-experimental)
|
||||
* [PrismaORM](/docs/adapters/prisma#joins-experimental)
|
||||
* [SQLite](/docs/adapters/sqlite#joins-experimental)
|
||||
* [MySQL](/docs/adapters/mysql#joins-experimental)
|
||||
* [PostgreSQL](/docs/adapters/postgresql#joins-experimental)
|
||||
* [MSSQL](/docs/adapters/mssql#joins-experimental)
|
||||
* [MongoDB](/docs/adapters/mongo#joins-experimental)
|
||||
* [DrizzleORM](/docs/adapters/drizzle#joins)
|
||||
* [PrismaORM](/docs/adapters/prisma#joins)
|
||||
* [SQLite](/docs/adapters/sqlite#joins)
|
||||
* [MySQL](/docs/adapters/mysql#joins)
|
||||
* [PostgreSQL](/docs/adapters/postgresql#joins)
|
||||
* [MSSQL](/docs/adapters/mssql#joins)
|
||||
* [MongoDB](/docs/adapters/mongo#joins)
|
||||
|
||||
@@ -648,7 +648,7 @@ export const auth = betterAuth({
|
||||
return "my-super-unique-id";
|
||||
})) | false | "serial" | "uuid",
|
||||
defaultFindManyLimit: 100,
|
||||
experimentalJoins: false,
|
||||
joins: false,
|
||||
},
|
||||
backgroundTasks: {
|
||||
handler: (promise) => { /* e.g. waitUntil(promise) */ }
|
||||
@@ -676,10 +676,11 @@ export const auth = betterAuth({
|
||||
|
||||
<h3 id="advanced-database"><code>database</code></h3>
|
||||
|
||||
Set custom strategies for ID generation and findMany queries.
|
||||
Set custom strategies for ID generation, findMany queries, and database joins.
|
||||
|
||||
* `generateId`: Controls how record IDs are generated. Accepts a custom function, `false`, `"serial"`, or `"uuid"` (default: [random base62 string](https://github.com/better-auth/better-auth/blob/main/packages/core/src/utils/id.ts)). See the [Database documentation](/docs/concepts/database#id-generation) for more info.
|
||||
* `defaultFindManyLimit`: The default maximum number of records returned by the `findMany` adapter method. (default: `100`)
|
||||
* `joins`: Enable database joins for adapters that support them. When disabled (default), related data is fetched via separate queries. See the [Database documentation](/docs/concepts/database#joins) for more info. (default: `false`)
|
||||
|
||||
```ts
|
||||
import { betterAuth } from "better-auth";
|
||||
@@ -689,6 +690,7 @@ export const auth = betterAuth({
|
||||
database: {
|
||||
generateId: "uuid",
|
||||
defaultFindManyLimit: 50,
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1754,7 +1754,7 @@ describe("Create Adapter Helper", async () => {
|
||||
});
|
||||
|
||||
describe("Fallback JoinOption System", async () => {
|
||||
describe("supportsJoin: false (Fallback mode)", () => {
|
||||
describe("fallback when adapter returns flat rows (no join keys)", () => {
|
||||
test("findOne: Should handle forward joins (joined model has FK to base model) by making separate queries", async () => {
|
||||
let adapterCalls: Array<{ method: string; model: string }> = [];
|
||||
|
||||
@@ -1763,9 +1763,10 @@ describe("Fallback JoinOption System", async () => {
|
||||
debugLogs: {},
|
||||
},
|
||||
options: {
|
||||
experimental: {
|
||||
// explicitally defining since in the future `join` will likely be default which would break this test
|
||||
joins: false,
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
adapter: () =>
|
||||
@@ -1828,9 +1829,10 @@ describe("Fallback JoinOption System", async () => {
|
||||
debugLogs: {},
|
||||
},
|
||||
options: {
|
||||
experimental: {
|
||||
// explicitally defining since in the future `join` will likely be default which would break this test
|
||||
joins: false,
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
adapter: () =>
|
||||
@@ -1900,8 +1902,10 @@ describe("Fallback JoinOption System", async () => {
|
||||
expect(item).toHaveProperty("session");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("findOne: Should not pass join to adapter when supportsJoin is false", async () => {
|
||||
describe("native join support (advanced.database.joins: true)", () => {
|
||||
test("findOne: Should pass join to adapter and use nested data when present", async () => {
|
||||
let joinPassedToAdapter = null;
|
||||
|
||||
const adapter = await createTestAdapter({
|
||||
@@ -1909,9 +1913,10 @@ describe("Fallback JoinOption System", async () => {
|
||||
debugLogs: {},
|
||||
},
|
||||
options: {
|
||||
experimental: {
|
||||
// explicitally defining since in the future `join` will likely be default which would break this test
|
||||
joins: false,
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
adapter: () =>
|
||||
@@ -1925,22 +1930,31 @@ describe("Fallback JoinOption System", async () => {
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
name: "Test User",
|
||||
session: [
|
||||
{
|
||||
id: "session-1",
|
||||
userId: "user-123",
|
||||
expiresAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
}) as any,
|
||||
});
|
||||
|
||||
await adapter.findOne({
|
||||
const res = await adapter.findOne({
|
||||
model: "user",
|
||||
where: [{ field: "id", value: "user-123" }],
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
// JoinOption should NOT be passed to adapter when supportsJoin is false
|
||||
expect(joinPassedToAdapter).toBeUndefined();
|
||||
expect(joinPassedToAdapter).not.toBeUndefined();
|
||||
expect(joinPassedToAdapter).toHaveProperty("session");
|
||||
expect(res).toHaveProperty("session");
|
||||
});
|
||||
|
||||
test("findMany: Should not pass join to adapter when supportsJoin is false", async () => {
|
||||
test("findMany: Should pass join to adapter and use nested data when present", async () => {
|
||||
let joinPassedToAdapter = null;
|
||||
|
||||
const adapter = await createTestAdapter({
|
||||
@@ -1948,9 +1962,10 @@ describe("Fallback JoinOption System", async () => {
|
||||
debugLogs: {},
|
||||
},
|
||||
options: {
|
||||
experimental: {
|
||||
// explicitally defining since in the future `join` will likely be default which would break this test
|
||||
joins: false,
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
adapter: () =>
|
||||
@@ -1965,118 +1980,41 @@ describe("Fallback JoinOption System", async () => {
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
name: "Test User",
|
||||
session: [
|
||||
{
|
||||
id: "session-1",
|
||||
userId: "user-123",
|
||||
expiresAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
}) as any,
|
||||
});
|
||||
|
||||
await adapter.findMany({
|
||||
const res = await adapter.findMany({
|
||||
model: "user",
|
||||
where: [],
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
// JoinOption should NOT be passed to adapter when supportsJoin is false
|
||||
expect(joinPassedToAdapter).toBeUndefined();
|
||||
expect(joinPassedToAdapter).not.toBeUndefined();
|
||||
expect(joinPassedToAdapter).toHaveProperty("session");
|
||||
expect(res).toBeInstanceOf(Array);
|
||||
expect(res[0]).toHaveProperty("session");
|
||||
});
|
||||
});
|
||||
|
||||
describe("supportsJoin: true (Native join support)", () => {
|
||||
test("findOne: Should pass join to adapter when supportsJoin is true", async () => {
|
||||
describe("default behavior (joins disabled)", () => {
|
||||
test("Should not pass join to adapter by default", async () => {
|
||||
let joinPassedToAdapter = null;
|
||||
|
||||
const adapter = await createTestAdapter({
|
||||
config: {
|
||||
debugLogs: {},
|
||||
},
|
||||
options: {
|
||||
experimental: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
adapter: () =>
|
||||
({
|
||||
async findOne({ model, join }: any) {
|
||||
joinPassedToAdapter = join;
|
||||
// When adapter supports joins, it returns data with joined structure
|
||||
return {
|
||||
id: "user-123",
|
||||
email: "test@test.com",
|
||||
emailVerified: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
name: "Test User",
|
||||
};
|
||||
},
|
||||
}) as any,
|
||||
});
|
||||
|
||||
await adapter.findOne({
|
||||
model: "user",
|
||||
where: [{ field: "id", value: "user-123" }],
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
// JoinOption SHOULD be passed to adapter when supportsJoin is true
|
||||
// It's then the adapter's responsibility to handle the join
|
||||
expect(joinPassedToAdapter).not.toBeUndefined();
|
||||
expect(joinPassedToAdapter).toHaveProperty("session");
|
||||
});
|
||||
|
||||
test("findMany: Should pass join to adapter when supportsJoin is true", async () => {
|
||||
let joinPassedToAdapter = null;
|
||||
|
||||
const adapter = await createTestAdapter({
|
||||
config: {
|
||||
debugLogs: {},
|
||||
},
|
||||
options: {
|
||||
experimental: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
adapter: () =>
|
||||
({
|
||||
async findMany({ model, join }: any) {
|
||||
joinPassedToAdapter = join;
|
||||
// When adapter supports joins, it returns data with joined structure
|
||||
return [
|
||||
{
|
||||
id: "user-123",
|
||||
email: "test@test.com",
|
||||
emailVerified: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
name: "Test User",
|
||||
},
|
||||
];
|
||||
},
|
||||
}) as any,
|
||||
});
|
||||
|
||||
await adapter.findMany({
|
||||
model: "user",
|
||||
where: [],
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
// JoinOption SHOULD be passed to adapter when supportsJoin is true
|
||||
// It's then the adapter's responsibility to handle the join
|
||||
expect(joinPassedToAdapter).not.toBeUndefined();
|
||||
expect(joinPassedToAdapter).toHaveProperty("session");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Default behavior (supportsJoin not specified)", () => {
|
||||
test("Should default to supportsJoin: false and use fallback join system", async () => {
|
||||
let joinPassedToAdapter = null;
|
||||
|
||||
const adapter = await createTestAdapter({
|
||||
config: {
|
||||
// supportsJoin not specified, should default to false
|
||||
debugLogs: {},
|
||||
},
|
||||
adapter: () =>
|
||||
({
|
||||
async findOne({ model, join }: any) {
|
||||
@@ -2090,6 +2028,12 @@ describe("Fallback JoinOption System", async () => {
|
||||
name: "Test User",
|
||||
};
|
||||
},
|
||||
async findMany({ model }: any) {
|
||||
if (model === "session") {
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
}) as any,
|
||||
});
|
||||
|
||||
@@ -2099,7 +2043,6 @@ describe("Fallback JoinOption System", async () => {
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
// Since supportsJoin defaults to false, join should NOT be passed
|
||||
expect(joinPassedToAdapter).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -208,7 +208,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/7271
|
||||
*
|
||||
* Same query on the experimental joins path.
|
||||
* Same query on the joins path.
|
||||
* The bug: `clause[0]` is used, dropping the OR group entirely.
|
||||
* Only the AND clause (email LIKE '%company.com%') is applied,
|
||||
* returning u1 AND u2 instead of just u1.
|
||||
@@ -219,7 +219,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
provider: "sqlite",
|
||||
});
|
||||
const adapter = adapterFactory({
|
||||
experimental: { joins: true },
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
const result = await adapter.findMany<User>({
|
||||
@@ -233,6 +233,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
connector: "OR",
|
||||
},
|
||||
],
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
@@ -263,7 +264,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
provider: "sqlite",
|
||||
});
|
||||
const adapter = adapterFactory({
|
||||
experimental: { joins: true },
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
const result = await adapter.findOne<User>({
|
||||
@@ -277,6 +278,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
connector: "OR",
|
||||
},
|
||||
],
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
@@ -301,7 +303,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
provider: "sqlite",
|
||||
});
|
||||
const adapter = adapterFactory({
|
||||
experimental: { joins: true },
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
const result = await adapter.findMany<User>({
|
||||
@@ -315,6 +317,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
connector: "OR",
|
||||
},
|
||||
],
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
@@ -337,7 +340,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
provider: "sqlite",
|
||||
});
|
||||
const adapter = adapterFactory({
|
||||
experimental: { joins: true },
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
const result = await adapter.findMany<User>({
|
||||
@@ -358,6 +361,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
connector: "OR",
|
||||
},
|
||||
],
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
@@ -383,7 +387,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
provider: "sqlite",
|
||||
});
|
||||
const adapter = adapterFactory({
|
||||
experimental: { joins: true },
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
const result = await adapter.findOne<User>({
|
||||
@@ -397,6 +401,7 @@ describe("drizzle adapter: mixed AND/OR connectors in where clauses", () => {
|
||||
connector: "OR",
|
||||
},
|
||||
],
|
||||
join: { session: true },
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
|
||||
@@ -117,7 +117,7 @@ const adapterSchema = {
|
||||
|
||||
// ── Tests ──
|
||||
|
||||
describe("drizzle adapter: singular config.schema keys with plural db.query keys + experimental.joins", () => {
|
||||
describe("drizzle adapter: singular config.schema keys with plural db.query keys + joins", () => {
|
||||
let sqliteDb: InstanceType<typeof Database>;
|
||||
let db: ReturnType<typeof drizzle>;
|
||||
|
||||
@@ -184,7 +184,7 @@ describe("drizzle adapter: singular config.schema keys with plural db.query keys
|
||||
});
|
||||
|
||||
const adapter = adapterFactory({
|
||||
experimental: { joins: true },
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
@@ -238,7 +238,7 @@ describe("drizzle adapter: singular config.schema keys with plural db.query keys
|
||||
});
|
||||
|
||||
const adapter = adapterFactory({
|
||||
experimental: { joins: true },
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
// findMany with join — exercises the findMany join path
|
||||
|
||||
+5
-3
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* The adapter skipped the relational query for case-insensitive where clauses,
|
||||
* falling back to the non-relational path. Under `experimental.joins` that
|
||||
* returned empty joins, so insensitive conditions are now routed through `RAW`.
|
||||
* falling back to the non-relational path and returning empty joins.
|
||||
* Insensitive conditions are now routed through `RAW`.
|
||||
*/
|
||||
import { drizzleAdapter } from "@better-auth/drizzle-adapter/relations-v2";
|
||||
import Database from "better-sqlite3";
|
||||
@@ -46,7 +46,9 @@ describe("drizzle relations-v2 adapter: case-insensitive where on the joins path
|
||||
const adapter = drizzleAdapter(db, {
|
||||
schema: { ...tables, relations },
|
||||
provider: "sqlite",
|
||||
})({ experimental: { joins: true } });
|
||||
})({
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sqliteDb.exec("DROP TABLE IF EXISTS session;");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Under `experimental.joins`, a `where` with `mode: "insensitive"` must fall back
|
||||
* With joins, a `where` with `mode: "insensitive"` must fall back
|
||||
* to the SQL builder instead of silently degrading to a case-sensitive match.
|
||||
*/
|
||||
import type { User } from "@better-auth/core/db";
|
||||
@@ -31,7 +31,9 @@ describe("drizzle relations-v2 adapter: joins path honors insensitive mode", ()
|
||||
const adapter = drizzleAdapter(db, {
|
||||
schema,
|
||||
provider: "sqlite",
|
||||
})({ experimental: { joins: true } });
|
||||
})({
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sqliteDb.exec("DROP TABLE IF EXISTS user;");
|
||||
|
||||
+4
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* With `experimental.joins`, `findMany` filters through Drizzle's relational
|
||||
* With joins, `findMany` filters through Drizzle's relational
|
||||
* query object, whose `like` filter cannot carry `ESCAPE`. LIKE is routed
|
||||
* through `RAW`, so this checks `%` and `_` still match literally.
|
||||
*
|
||||
@@ -33,7 +33,9 @@ describe("drizzle relations-v2 adapter: LIKE escaping on the joins path", () =>
|
||||
const adapter = drizzleAdapter(db, {
|
||||
schema: { ...tables, relations },
|
||||
provider: "sqlite",
|
||||
})({ experimental: { joins: true } });
|
||||
})({
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sqliteDb.exec("DROP TABLE IF EXISTS user;");
|
||||
|
||||
+10
-4
@@ -2,7 +2,7 @@
|
||||
* Drizzle keys `db.query` by the schema export names, commonly plural ("users"),
|
||||
* while Better Auth passes singular model names. The adapter read
|
||||
* `db.query[model]` directly, so a plural-keyed schema fell back to the
|
||||
* non-relational query, returning empty joins under `experimental.joins`.
|
||||
* non-relational query, returning empty joins.
|
||||
*/
|
||||
import { drizzleAdapter } from "@better-auth/drizzle-adapter/relations-v2";
|
||||
import Database from "better-sqlite3";
|
||||
@@ -49,7 +49,9 @@ describe("drizzle relations-v2 adapter: plural db.query keys", () => {
|
||||
const adapter = drizzleAdapter(db, {
|
||||
schema: { user: users, session: sessions, relations },
|
||||
provider: "sqlite",
|
||||
})({ experimental: { joins: true } });
|
||||
})({
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sqliteDb.exec("DROP TABLE IF EXISTS session;");
|
||||
@@ -128,7 +130,9 @@ describe("drizzle relations-v2 adapter: missing relational query namespace", ()
|
||||
const adapter = drizzleAdapter(db, {
|
||||
schema: { user: users, session: sessions, relations },
|
||||
provider: "sqlite",
|
||||
})({ experimental: { joins: true } });
|
||||
})({
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sqliteDb.exec("DROP TABLE IF EXISTS user;");
|
||||
@@ -174,7 +178,9 @@ describe("drizzle relations-v2 adapter: query key via relations internal", () =>
|
||||
const adapter = drizzleAdapter(db, {
|
||||
schema: { user: users, session: sessions, relations },
|
||||
provider: "sqlite",
|
||||
})({ experimental: { joins: true } });
|
||||
})({
|
||||
advanced: { database: { joins: true } },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sqliteDb.exec("DROP TABLE IF EXISTS session;");
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ describe("drizzle relations-v2 adapter: join model name ending in 's'", () => {
|
||||
schema: { user: users, address, relations },
|
||||
provider: "sqlite",
|
||||
})({
|
||||
experimental: { joins: true },
|
||||
advanced: { database: { joins: true } },
|
||||
plugins: [
|
||||
{
|
||||
id: "test-address",
|
||||
|
||||
@@ -44,8 +44,10 @@ export const schemaRefJoinTestSuite = createTestSuite(
|
||||
{
|
||||
defaultBetterAuthOptions: {
|
||||
...DEFAULT_BETTER_AUTH_OPTIONS,
|
||||
experimental: {
|
||||
joins: true,
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
alwaysMigrate: true,
|
||||
|
||||
@@ -425,19 +425,19 @@ export const createAdapterFactory =
|
||||
joinConfig,
|
||||
} of requiredModels) {
|
||||
let joinedData = await (async () => {
|
||||
if (options.experimental?.joins) {
|
||||
const result = data[modelName];
|
||||
return result;
|
||||
} else {
|
||||
// doesn't support joins, so fallback to handleFallbackJoin
|
||||
const result = await handleFallbackJoin({
|
||||
baseModel: unsafe_model,
|
||||
baseData: transformedData,
|
||||
joinModel: modelName,
|
||||
specificJoinConfig: joinConfig,
|
||||
});
|
||||
return result;
|
||||
if (options.advanced?.database?.joins) {
|
||||
// Use native joined data when the adapter included the key;
|
||||
// otherwise fall back to separate queries.
|
||||
if (modelName in data) {
|
||||
return data[modelName];
|
||||
}
|
||||
}
|
||||
return await handleFallbackJoin({
|
||||
baseModel: unsafe_model,
|
||||
baseData: transformedData,
|
||||
joinModel: modelName,
|
||||
specificJoinConfig: joinConfig,
|
||||
});
|
||||
})();
|
||||
|
||||
// If joinedData is undefined, initialize it based on relationship type
|
||||
@@ -1118,9 +1118,12 @@ export const createAdapterFactory =
|
||||
join = result.join;
|
||||
select = result.select;
|
||||
}
|
||||
// If adapter doesn't support joins and we have joins, don't pass them to the adapter
|
||||
const experimentalJoins = options.experimental?.joins;
|
||||
if (!experimentalJoins && join && Object.keys(join).length > 0) {
|
||||
// If joins are disabled and we have joins, don't pass them to the adapter
|
||||
if (
|
||||
!options.advanced?.database?.joins &&
|
||||
join &&
|
||||
Object.keys(join).length > 0
|
||||
) {
|
||||
passJoinToAdapter = false;
|
||||
}
|
||||
} else {
|
||||
@@ -1206,9 +1209,12 @@ export const createAdapterFactory =
|
||||
join = result.join;
|
||||
select = result.select;
|
||||
}
|
||||
// If adapter doesn't support joins and we have joins, don't pass them to the adapter
|
||||
const experimentalJoins = options.experimental?.joins;
|
||||
if (!experimentalJoins && join && Object.keys(join).length > 0) {
|
||||
// If joins are disabled and we have joins, don't pass them to the adapter
|
||||
if (
|
||||
!options.advanced?.database?.joins &&
|
||||
join &&
|
||||
Object.keys(join).length > 0
|
||||
) {
|
||||
passJoinToAdapter = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -454,6 +454,21 @@ export type BetterAuthAdvancedOptions = {
|
||||
* function.
|
||||
*/
|
||||
generateId?: GenerateIdFn | false | "serial" | "uuid";
|
||||
/**
|
||||
* Enable database joins for adapters that support them.
|
||||
*
|
||||
* When disabled (default), related data is fetched via
|
||||
* separate queries. When enabled, adapters that support
|
||||
* native joins use them; otherwise Better Auth falls back
|
||||
* to separate queries.
|
||||
*
|
||||
* Please read the adapter documentation for more
|
||||
* information regarding joins before enabling this.
|
||||
* Not all adapters support joins.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
joins?: boolean;
|
||||
}
|
||||
| undefined;
|
||||
/**
|
||||
@@ -1783,18 +1798,4 @@ export type BetterAuthOptions = {
|
||||
debug?: boolean;
|
||||
}
|
||||
| undefined;
|
||||
/**
|
||||
* Experimental features
|
||||
*/
|
||||
experimental?: {
|
||||
/**
|
||||
* Enable experimental joins for your database adapter.
|
||||
*
|
||||
* Please read the adapter documentation for more information regarding joins before enabling this.
|
||||
* Not all adapters support joins.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
joins?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -722,6 +722,7 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
* corresponds to the same table object
|
||||
*/
|
||||
function getQueryModel(model: string): string | null {
|
||||
if (!db.query) return null;
|
||||
if (db.query[model]) return model;
|
||||
|
||||
if (config.usePlural) {
|
||||
@@ -758,7 +759,7 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
const schemaModel = getSchema(model);
|
||||
const clause = convertWhereClause(where, model);
|
||||
|
||||
if (options.experimental?.joins) {
|
||||
if (join) {
|
||||
const queryModel = getQueryModel(model);
|
||||
if (!db.query || !queryModel) {
|
||||
logger.error(
|
||||
@@ -771,22 +772,20 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
| undefined;
|
||||
|
||||
const pluralJoinResults: string[] = [];
|
||||
if (join) {
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [model, joinAttr] of joinEntries) {
|
||||
const limit =
|
||||
joinAttr.limit ??
|
||||
options.advanced?.database?.defaultFindManyLimit ??
|
||||
100;
|
||||
const isUnique = joinAttr.relation === "one-to-one";
|
||||
const pluralSuffix = isUnique || config.usePlural ? "" : "s";
|
||||
includes[`${model}${pluralSuffix}`] = isUnique
|
||||
? true
|
||||
: { limit };
|
||||
if (!isUnique) {
|
||||
pluralJoinResults.push(`${model}${pluralSuffix}`);
|
||||
}
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [model, joinAttr] of joinEntries) {
|
||||
const limit =
|
||||
joinAttr.limit ??
|
||||
options.advanced?.database?.defaultFindManyLimit ??
|
||||
100;
|
||||
const isUnique = joinAttr.relation === "one-to-one";
|
||||
const pluralSuffix = isUnique || config.usePlural ? "" : "s";
|
||||
includes[`${model}${pluralSuffix}`] = isUnique
|
||||
? true
|
||||
: { limit };
|
||||
if (!isUnique) {
|
||||
pluralJoinResults.push(`${model}${pluralSuffix}`);
|
||||
}
|
||||
}
|
||||
const query = db.query[queryModel].findFirst({
|
||||
@@ -845,9 +844,9 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
const clause = where ? convertWhereClause(where, model) : [];
|
||||
const sortFn = sortBy?.direction === "desc" ? desc : asc;
|
||||
|
||||
if (options.experimental?.joins) {
|
||||
if (join) {
|
||||
const queryModel = getQueryModel(model);
|
||||
if (!queryModel) {
|
||||
if (!db.query || !queryModel) {
|
||||
logger.error(
|
||||
`[# Drizzle Adapter]: The model "${model}" was not found in the query object. Please update your Drizzle schema to include relations or re-generate using "npx auth@latest generate".`,
|
||||
);
|
||||
@@ -858,22 +857,20 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
| undefined;
|
||||
|
||||
const pluralJoinResults: string[] = [];
|
||||
if (join) {
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [model, joinAttr] of joinEntries) {
|
||||
const isUnique = joinAttr.relation === "one-to-one";
|
||||
const limit =
|
||||
joinAttr.limit ??
|
||||
options.advanced?.database?.defaultFindManyLimit ??
|
||||
100;
|
||||
const pluralSuffix = isUnique || config.usePlural ? "" : "s";
|
||||
includes[`${model}${pluralSuffix}`] = isUnique
|
||||
? true
|
||||
: { limit };
|
||||
if (!isUnique)
|
||||
pluralJoinResults.push(`${model}${pluralSuffix}`);
|
||||
}
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [model, joinAttr] of joinEntries) {
|
||||
const isUnique = joinAttr.relation === "one-to-one";
|
||||
const limit =
|
||||
joinAttr.limit ??
|
||||
options.advanced?.database?.defaultFindManyLimit ??
|
||||
100;
|
||||
const pluralSuffix = isUnique || config.usePlural ? "" : "s";
|
||||
includes[`${model}${pluralSuffix}`] = isUnique
|
||||
? true
|
||||
: { limit };
|
||||
if (!isUnique)
|
||||
pluralJoinResults.push(`${model}${pluralSuffix}`);
|
||||
}
|
||||
let orderBy: SQL<unknown>[] | undefined = undefined;
|
||||
if (sortBy?.field) {
|
||||
|
||||
@@ -693,7 +693,7 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
const schemaModel = getSchema(model);
|
||||
const clause = convertWhereClause(where, model);
|
||||
|
||||
if (options.experimental?.joins) {
|
||||
if (join) {
|
||||
const queryModel = getQueryModel(model);
|
||||
if (!db.query || !queryModel) {
|
||||
logger.error(
|
||||
@@ -706,20 +706,18 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
| undefined;
|
||||
|
||||
const pluralJoinResults: { key: string; target: string }[] = [];
|
||||
if (join) {
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [model, joinAttr] of joinEntries) {
|
||||
const limit =
|
||||
joinAttr.limit ??
|
||||
options.advanced?.database?.defaultFindManyLimit ??
|
||||
100;
|
||||
const isUnique = joinAttr.relation === "one-to-one";
|
||||
const relationKey = getJoinRelationKey(model, isUnique);
|
||||
includes[relationKey] = isUnique ? true : { limit };
|
||||
if (!isUnique) {
|
||||
pluralJoinResults.push({ key: relationKey, target: model });
|
||||
}
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [model, joinAttr] of joinEntries) {
|
||||
const limit =
|
||||
joinAttr.limit ??
|
||||
options.advanced?.database?.defaultFindManyLimit ??
|
||||
100;
|
||||
const isUnique = joinAttr.relation === "one-to-one";
|
||||
const relationKey = getJoinRelationKey(model, isUnique);
|
||||
includes[relationKey] = isUnique ? true : { limit };
|
||||
if (!isUnique) {
|
||||
pluralJoinResults.push({ key: relationKey, target: model });
|
||||
}
|
||||
}
|
||||
const clause = convertNewWhereClause(where, model);
|
||||
@@ -775,7 +773,7 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
const clause = where ? convertWhereClause(where, model) : [];
|
||||
const sortFn = sortBy?.direction === "desc" ? desc : asc;
|
||||
|
||||
if (options.experimental?.joins) {
|
||||
if (join) {
|
||||
const queryModel = getQueryModel(model);
|
||||
if (!db.query || !queryModel) {
|
||||
logger.error(
|
||||
@@ -788,20 +786,18 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => {
|
||||
| undefined;
|
||||
|
||||
const pluralJoinResults: { key: string; target: string }[] = [];
|
||||
if (join) {
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [model, joinAttr] of joinEntries) {
|
||||
const isUnique = joinAttr.relation === "one-to-one";
|
||||
const limit =
|
||||
joinAttr.limit ??
|
||||
options.advanced?.database?.defaultFindManyLimit ??
|
||||
100;
|
||||
const relationKey = getJoinRelationKey(model, isUnique);
|
||||
includes[relationKey] = isUnique ? true : { limit };
|
||||
if (!isUnique)
|
||||
pluralJoinResults.push({ key: relationKey, target: model });
|
||||
}
|
||||
includes = {};
|
||||
const joinEntries = Object.entries(join);
|
||||
for (const [model, joinAttr] of joinEntries) {
|
||||
const isUnique = joinAttr.relation === "one-to-one";
|
||||
const limit =
|
||||
joinAttr.limit ??
|
||||
options.advanced?.database?.defaultFindManyLimit ??
|
||||
100;
|
||||
const relationKey = getJoinRelationKey(model, isUnique);
|
||||
includes[relationKey] = isUnique ? true : { limit };
|
||||
if (!isUnique)
|
||||
pluralJoinResults.push({ key: relationKey, target: model });
|
||||
}
|
||||
let orderBy: Record<string, "asc" | "desc"> | undefined =
|
||||
undefined;
|
||||
|
||||
@@ -6,8 +6,10 @@ export const joinsTestSuite = createTestSuite(
|
||||
"joins",
|
||||
{
|
||||
defaultBetterAuthOptions: {
|
||||
experimental: {
|
||||
joins: true,
|
||||
advanced: {
|
||||
database: {
|
||||
joins: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
alwaysMigrate: true,
|
||||
@@ -20,7 +22,7 @@ export const joinsTestSuite = createTestSuite(
|
||||
return {
|
||||
"init - tests": async () => {
|
||||
const opts = helpers.getBetterAuthOptions();
|
||||
expect(opts.experimental?.joins).toBe(true);
|
||||
expect(opts.advanced?.database?.joins).toBe(true);
|
||||
},
|
||||
...normalTests,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user