diff --git a/.changeset/fix-jwt-client-plugin-inference.md b/.changeset/fix-jwt-client-plugin-inference.md new file mode 100644 index 0000000000..ae73afe0f7 --- /dev/null +++ b/.changeset/fix-jwt-client-plugin-inference.md @@ -0,0 +1,5 @@ +--- +"better-auth": patch +--- + +Fix `jwtClient()` collapsing `createAuthClient` type inference when combined with other client plugins such as `inferAdditionalFields`. Additional user fields (for example on `updateUser`) are preserved again. diff --git a/packages/better-auth/src/plugins/jwt/client.ts b/packages/better-auth/src/plugins/jwt/client.ts index fa13da9b47..251d84810b 100644 --- a/packages/better-auth/src/plugins/jwt/client.ts +++ b/packages/better-auth/src/plugins/jwt/client.ts @@ -1,4 +1,10 @@ -import type { BetterAuthClientPlugin } from "@better-auth/core"; +import type { + BetterAuthClientOptions, + BetterAuthClientPlugin, + ClientFetchOption, + ClientStore, +} from "@better-auth/core"; +import type { BetterFetch } from "@better-fetch/fetch"; import type { JSONWebKeySet } from "jose"; import { PACKAGE_VERSION } from "../../version"; import type { jwt } from "./index"; @@ -25,8 +31,12 @@ export const jwtClient = (options?: JwtClientOptions) => { pathMethods: { [jwksPath]: "GET", }, - getActions: ($fetch) => ({ - jwks: async (fetchOptions?: any) => { + getActions: ( + $fetch: BetterFetch, + _$store: ClientStore, + _options: BetterAuthClientOptions | undefined, + ) => ({ + jwks: async (fetchOptions?: ClientFetchOption) => { return await $fetch(jwksPath, { method: "GET", ...fetchOptions, diff --git a/packages/better-auth/src/plugins/jwt/jwt.test.ts b/packages/better-auth/src/plugins/jwt/jwt.test.ts index 993fe876d1..b592eed29c 100644 --- a/packages/better-auth/src/plugins/jwt/jwt.test.ts +++ b/packages/better-auth/src/plugins/jwt/jwt.test.ts @@ -1,8 +1,10 @@ +import type { BetterAuthClientPlugin } from "@better-auth/core"; import type { JSONWebKeySet } from "jose"; import { createLocalJWKSet, jwtVerify } from "jose"; -import { describe, expect, it } from "vitest"; +import { describe, expect, expectTypeOf, it } from "vitest"; import { createAuthClient } from "../../client"; import { getTestInstance } from "../../test-utils/test-instance"; +import { inferAdditionalFields } from "../additional-fields/client"; import { jwt } from "."; import { jwtClient } from "./client"; import type { JWKOptions, Jwk, JwtOptions } from "./types"; @@ -833,3 +835,46 @@ describe("toExpJWT", () => { }); }); }); + +/** + * Declaration emit previously narrowed `$fetch` from `$fetch(...)`, + * making `jwtClient()` unassignable to `BetterAuthClientPlugin` and collapsing + * `createAuthClient` option inference when combined with other plugins. + * + * @see https://github.com/masumi-network/sokosumi/pull/3403 + */ +describe("jwtClient types", () => { + it("should be assignable to BetterAuthClientPlugin", () => { + const plugin: BetterAuthClientPlugin = jwtClient(); + const plugins: BetterAuthClientPlugin[] = [jwtClient()]; + expect(plugin.id).toBe("better-auth-client"); + expect(plugins).toHaveLength(1); + }); + + it("should preserve additional user fields when combined with inferAdditionalFields", () => { + const client = createAuthClient({ + plugins: [ + inferAdditionalFields({ + user: { + logo: { + type: "string", + required: false, + }, + }, + }), + jwtClient(), + ], + }); + + type UpdateUserBody = NonNullable[0]>; + + const body = { + logo: "https://example.com/logo.png", + } satisfies UpdateUserBody; + expect(body.logo).toBe("https://example.com/logo.png"); + expectTypeOf().toMatchTypeOf<{ + logo?: string | null | undefined; + }>(); + expectTypeOf(client.jwks).toBeFunction(); + }); +});