chore: fix type inference when user and custom session plugins are combined (#8448)

This commit is contained in:
Jonathan Samines
2026-03-10 17:47:18 -06:00
committed by GitHub
parent a980b169a5
commit cf10f92051
2 changed files with 104 additions and 1 deletions

View File

@@ -41,6 +41,28 @@ type ReplaceAuthUserAndSession<
InferSessionFromClient<ClientOpts>
>;
type MergeCustomSessionField<
R extends object,
Field extends "user" | "session",
InferType,
> = Field extends keyof R
? {
[K in Field]: KeepNullishFromOriginal<
R[K],
NonNullable<R[K]> & InferType
>;
}
: {};
type MergeCustomSessionWithInferred<
R,
ClientOpts extends BetterAuthClientOptions,
> = R extends object
? Omit<R, "user" | "session"> &
MergeCustomSessionField<R, "user", InferUserFromClient<ClientOpts>> &
MergeCustomSessionField<R, "session", InferSessionFromClient<ClientOpts>>
: never;
type RefineAuthResponse<
Data,
ClientOpts extends BetterAuthClientOptions,
@@ -194,7 +216,10 @@ export type InferRoute<API, COpts extends BetterAuthClientOptions> =
Meta extends {
CUSTOM_SESSION: boolean;
}
? NonNullable<Awaited<R>>
? MergeCustomSessionWithInferred<
NonNullable<Awaited<R>>,
COpts
>
: T["path"] extends "/get-session"
? {
user: InferUserFromClient<COpts>;

View File

@@ -3,6 +3,7 @@ import { createAuthClient } from "../../client";
import { parseSetCookieHeader } from "../../cookies";
import { getTestInstance } from "../../test-utils/test-instance";
import type { BetterAuthOptions } from "../../types";
import { inferAdditionalFields } from "../additional-fields/client";
import { admin } from "../admin";
import { adminClient } from "../admin/client";
import { multiSession } from "../multi-session";
@@ -300,4 +301,81 @@ describe("Custom Session Plugin Tests", async () => {
};
}>();
});
it("should not add user/session to client getSession type when custom session omits them", async () => {
const { auth } = await getTestInstance({
plugins: [
customSession(async () => {
return {
custom: {
field: "field",
},
};
}),
],
});
const client = createAuthClient({
plugins: [customSessionClient<typeof auth>()],
});
type SessionData = typeof client.$Infer.Session;
// When custom session omits user/session, the client type must not claim they exist
expectTypeOf<SessionData>().toEqualTypeOf<{
custom: {
field: string;
};
}>();
// Verify user and session are not in the type
expectTypeOf<keyof SessionData>().toEqualTypeOf<"custom">();
});
it("should infer both customSessionClient and inferAdditionalFields when combined", async () => {
const { auth } = await getTestInstance({
user: {
additionalFields: {
role: {
type: "string",
required: false,
},
},
},
plugins: [
customSession(async ({ user, session }) => {
return {
user: {
firstName: user.name.split(" ")[0],
lastName: user.name.split(" ")[1],
},
session,
customData: { message: "hello" },
};
}),
],
});
const client = createAuthClient({
plugins: [
customSessionClient<typeof auth>(),
inferAdditionalFields<typeof auth>(),
],
});
type SessionData = typeof client.$Infer.Session;
type User = SessionData["user"];
expectTypeOf<User>().toMatchObjectType<{
id: string;
firstName: string | undefined;
lastName: string | undefined;
role?: string | undefined | null;
}>();
type CustomData = SessionData["customData"];
expectTypeOf<CustomData>().toMatchObjectType<{
message: string;
}>();
});
});