From 720dcfcd6d28d87eeff8a831dfb0c134cf85c0a0 Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Mon, 29 Dec 2025 15:57:03 +0900 Subject: [PATCH 1/5] docs: add payments section (#7030) --- docs/components/sidebar-content.tsx | 48 ++++++++++++++++------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/docs/components/sidebar-content.tsx b/docs/components/sidebar-content.tsx index 3353a5d796..54e73adbc2 100644 --- a/docs/components/sidebar-content.tsx +++ b/docs/components/sidebar-content.tsx @@ -1952,7 +1952,7 @@ C0.7,239.6,62.1,0.5,62.2,0.4c0,0,54,13.8,119.9,30.8S302.1,62,302.2,62c0.2,0,0.2, href: "/docs/plugins/jwt", }, { - title: "3rd party", + title: "Payments", group: true, href: "", icon: () => , @@ -2089,26 +2089,6 @@ C0.7,239.6,62.1,0.5,62.2,0.4c0,0,54,13.8,119.9,30.8S302.1,62,302.2,62c0.2,0,0.2, ), }, - { - title: "Dub", - href: "/docs/plugins/dub", - icon: () => ( - - - - ), - }, { title: "Commet", href: "/docs/plugins/commet", @@ -2131,6 +2111,32 @@ C0.7,239.6,62.1,0.5,62.2,0.4c0,0,54,13.8,119.9,30.8S302.1,62,302.2,62c0.2,0,0.2, ), }, + { + title: "Others", + group: true, + icon: () => null, + href: "", + }, + { + title: "Dub", + href: "/docs/plugins/dub", + icon: () => ( + + + + ), + }, { title: "Community Plugins", href: "/docs/plugins/community-plugins", From 69c582faeff06d96dca53429809e63951455a640 Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Mon, 29 Dec 2025 15:58:50 +0900 Subject: [PATCH 2/5] docs: improve community plugins page (#7031) --- docs/components/community-plugins-table.tsx | 18 ++++++++---------- .../content/docs/plugins/community-plugins.mdx | 6 ++++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/components/community-plugins-table.tsx b/docs/components/community-plugins-table.tsx index fd9463d5a6..74af9801b2 100644 --- a/docs/components/community-plugins-table.tsx +++ b/docs/components/community-plugins-table.tsx @@ -274,7 +274,13 @@ export function CommunityPluginsTable() { }); return ( -
+
+
+

+ Showing {table.getRowModel().rows.length} of {communityPlugins.length}{" "} + plugins +

+
- -
+
@@ -339,13 +344,6 @@ export function CommunityPluginsTable() {
- -
-
- Showing {table.getRowModel().rows.length} of {communityPlugins.length}{" "} - plugins -
-
); } diff --git a/docs/content/docs/plugins/community-plugins.mdx b/docs/content/docs/plugins/community-plugins.mdx index 4fdc05353f..69e477a43f 100644 --- a/docs/content/docs/plugins/community-plugins.mdx +++ b/docs/content/docs/plugins/community-plugins.mdx @@ -5,10 +5,12 @@ description: A list of recommended community plugins. import { CommunityPluginsTable } from "@/components/community-plugins-table"; -This page showcases a list of recommended community made plugins. +This page showcases a list of recommended community made plugins. We encourage you to create custom plugins and maybe get added to the list! -We encourage you to create custom plugins and maybe get added to the list! +## Create Your Own Plugin To create your own custom plugin, get started by reading our [plugins documentation](/docs/concepts/plugins). And if you want to share your plugin with the community, please open a pull request to add it to this list. +## Browse Community Plugins + From 22b932435b2fd408e63a8432a334f57db64d9238 Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Mon, 29 Dec 2025 17:07:18 +0800 Subject: [PATCH 3/5] fix(client): prevent duplicate signal processing in atom listeners (#7018) --- packages/better-auth/src/client/proxy.test.ts | 65 +++++++++++++++++++ packages/better-auth/src/client/proxy.ts | 7 ++ 2 files changed, 72 insertions(+) create mode 100644 packages/better-auth/src/client/proxy.test.ts diff --git a/packages/better-auth/src/client/proxy.test.ts b/packages/better-auth/src/client/proxy.test.ts new file mode 100644 index 0000000000..8ecf7cd630 --- /dev/null +++ b/packages/better-auth/src/client/proxy.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it, vi } from "vitest"; +import { createDynamicPathProxy } from "./proxy"; + +describe("createDynamicPathProxy", () => { + it("should avoid duplicate signal processing", async () => { + const client = vi.fn(async (path, options) => { + if (options?.onSuccess) { + await options.onSuccess({} as any); + } + return { + data: {}, + error: null, + }; + }); + const knownPathMethods = { + "/test": "POST", + } as const; + + const signalSet = vi.fn(); + const signalGet = vi.fn(() => false); + + const atoms = { + testSignal: { + get: signalGet, + set: signalSet, + subscribe: () => () => {}, + listen: () => () => {}, + off: () => {}, + value: false, + } as any, + }; + const atomListeners = [ + { + matcher: (path: string) => path === "/test", + signal: "testSignal", + }, + { + matcher: (path: string) => path === "/test", + signal: "testSignal", + }, + ]; + + const proxy = createDynamicPathProxy( + { + test: {}, + }, + client as any, + knownPathMethods, + atoms, + atomListeners, + ); + + vi.useFakeTimers(); + + await (proxy.test as any)(); + + expect(client).toHaveBeenCalled(); + + vi.runAllTimers(); + + expect(signalSet).toHaveBeenCalledTimes(1); + + vi.useRealTimers(); + }); +}); diff --git a/packages/better-auth/src/client/proxy.ts b/packages/better-auth/src/client/proxy.ts index af8d3759d5..46d8f8be6f 100644 --- a/packages/better-auth/src/client/proxy.ts +++ b/packages/better-auth/src/client/proxy.ts @@ -1,5 +1,6 @@ import type { BetterAuthClientPlugin, + ClientAtomListener, ClientFetchOption, } from "@better-auth/core"; import type { BetterFetch } from "@better-fetch/fetch"; @@ -100,9 +101,15 @@ export function createDynamicPathProxy>( */ const matches = atomListeners.filter((s) => s.matcher(routePath)); if (!matches.length) return; + + const visited = new Set(); for (const match of matches) { const signal = atoms[match.signal as any]; if (!signal) return; + if (visited.has(match.signal)) { + continue; + } + visited.add(match.signal); /** * To avoid race conditions we set the signal in a setTimeout */ From 1e060559558b326587c53674de3fbab7f1afafc7 Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Mon, 29 Dec 2025 21:22:23 +0900 Subject: [PATCH 4/5] fix(client): apply rate limit focus refetch regardless of session state (#7048) Co-authored-by: Alex Yang --- .../better-auth/src/client/session-refresh.test.ts | 12 ++++++------ packages/better-auth/src/client/session-refresh.ts | 10 ++-------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/better-auth/src/client/session-refresh.test.ts b/packages/better-auth/src/client/session-refresh.test.ts index 647829d999..307456a852 100644 --- a/packages/better-auth/src/client/session-refresh.test.ts +++ b/packages/better-auth/src/client/session-refresh.test.ts @@ -172,7 +172,7 @@ describe("session-refresh", () => { vi.useRealTimers(); }); - it("should allow refetch on focus when session is null even within rate limit", async () => { + it("should rate limit refetch on focus when session is null", async () => { const sessionAtom: SessionAtom = atom({ data: { user: { id: "1", email: "test@test.com" }, @@ -223,15 +223,15 @@ describe("session-refresh", () => { // Immediately trigger another focus event (within rate limit window) manager.triggerRefetch({ event: "visibilitychange" }); - // Signal should change because session is null (rate limit bypassed) - expect(signalChangeCount).toBeGreaterThan(signalCountAfterFirstFocus); + // Signal should NOT change because rate limit should apply even when session is null + expect(signalChangeCount).toBe(signalCountAfterFirstFocus); unsubscribeSignal(); manager.cleanup(); vi.useRealTimers(); }); - it("should allow refetch on focus when session is undefined even within rate limit", async () => { + it("should rate limit refetch on focus when session is undefined", async () => { const sessionAtom: SessionAtom = atom({ data: { user: { id: "1", email: "test@test.com" }, @@ -282,8 +282,8 @@ describe("session-refresh", () => { // Immediately trigger another focus event (within rate limit window) manager.triggerRefetch({ event: "visibilitychange" }); - // Signal should change because session is undefined (rate limit bypassed) - expect(signalChangeCount).toBeGreaterThan(signalCountAfterFirstFocus); + // Signal should NOT change because rate limit should apply even when session is undefined + expect(signalChangeCount).toBe(signalCountAfterFirstFocus); unsubscribeSignal(); manager.cleanup(); diff --git a/packages/better-auth/src/client/session-refresh.ts b/packages/better-auth/src/client/session-refresh.ts index f70f1dd15f..e3fa0b8513 100644 --- a/packages/better-auth/src/client/session-refresh.ts +++ b/packages/better-auth/src/client/session-refresh.ts @@ -100,13 +100,10 @@ export function createSessionRefreshManager(opts: SessionRefreshOptions) { // Rate limit: don't refetch on focus if a session request was made recently if (event?.event === "visibilitychange") { const timeSinceLastRequest = now() - state.lastSessionRequest; - if ( - timeSinceLastRequest < FOCUS_REFETCH_RATE_LIMIT_SECONDS && - currentSession?.data !== null && - currentSession?.data !== undefined - ) { + if (timeSinceLastRequest < FOCUS_REFETCH_RATE_LIMIT_SECONDS) { return; } + state.lastSessionRequest = now(); } if ( @@ -114,9 +111,6 @@ export function createSessionRefreshManager(opts: SessionRefreshOptions) { currentSession?.data === undefined || event?.event === "visibilitychange" ) { - if (event?.event === "visibilitychange") { - state.lastSessionRequest = now(); - } state.lastSync = now(); sessionSignal.set(!sessionSignal.get()); } From 006df7472c458cd2e0a08f8c19d36f64714e0cb1 Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Mon, 29 Dec 2025 20:41:57 +0800 Subject: [PATCH 5/5] chore: bump ai sdk (#7050) --- docs/package.json | 6 +-- pnpm-lock.yaml | 93 ++++++++++++++++++++++++----------------------- 2 files changed, 50 insertions(+), 49 deletions(-) diff --git a/docs/package.json b/docs/package.json index 6bffb17b0e..bb9955430e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -12,8 +12,8 @@ "scripts:sync-orama": "node ./scripts/sync-orama.ts" }, "dependencies": { - "@ai-sdk/openai-compatible": "^1.0.20", - "@ai-sdk/react": "^2.0.64", + "@ai-sdk/openai-compatible": "^2.0.1", + "@ai-sdk/react": "^3.0.3", "@better-auth/utils": "catalog:", "@better-fetch/fetch": "catalog:", "@hookform/resolvers": "^5.2.1", @@ -50,7 +50,7 @@ "@tanstack/react-table": "^8.21.3", "@vercel/analytics": "^1.5.0", "@vercel/og": "^0.8.5", - "ai": "^5.0.64", + "ai": "^6.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4eeb0af49..ccf4c5f489 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -598,11 +598,11 @@ importers: docs: dependencies: '@ai-sdk/openai-compatible': - specifier: ^1.0.20 - version: 1.0.20(zod@4.1.13) + specifier: ^2.0.1 + version: 2.0.1(zod@4.1.13) '@ai-sdk/react': - specifier: ^2.0.64 - version: 2.0.64(react@19.2.3)(zod@4.1.13) + specifier: ^3.0.3 + version: 3.0.3(react@19.2.3)(zod@4.1.13) '@better-auth/utils': specifier: 'catalog:' version: 0.3.0 @@ -712,8 +712,8 @@ importers: specifier: ^0.8.5 version: 0.8.5 ai: - specifier: ^5.0.64 - version: 5.0.64(zod@4.1.13) + specifier: ^6.0.3 + version: 6.0.3(zod@4.1.13) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -1575,37 +1575,33 @@ packages: graphql: optional: true - '@ai-sdk/gateway@1.0.35': - resolution: {integrity: sha512-cdsXbeRRMi6QxbZscin69Asx2fi0d2TmmPngcPFUMpZbchGEBiJYVNvIfiALKFKXEq0l/w0xGNV3E13vroaleA==} + '@ai-sdk/gateway@3.0.2': + resolution: {integrity: sha512-giJEg9ob45htbu3iautK+2kvplY2JnTj7ir4wZzYSQWvqGatWfBBfDuNCU5wSJt9BCGjymM5ZS9ziD42JGCZBw==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai-compatible@1.0.20': - resolution: {integrity: sha512-UIg7dj79wYsmHFyk8snF9q2qhbQZMI0XiUNhilMg/4htPqYF0z6Q5GSRMUtYEo7yN14a8g0KeVOawE6+H4VYcg==} + '@ai-sdk/openai-compatible@2.0.1': + resolution: {integrity: sha512-6tfF6YAREdECPHqQ4ydZFQyY6AiwRc+MHZPbwx2HGsLBTPQXtImLO+iew/pB9XjD2G8s4un6PSNYK//Y+jWDcQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@3.0.11': - resolution: {integrity: sha512-4hgHj89VqyOHzGaV85TkcgvO8WjecVF35TOUVg+C56vnzpWSgdIZu/ZWZNdZ6BTrv8y0N1toBWW7XcWiRRicLg==} + '@ai-sdk/provider-utils@4.0.1': + resolution: {integrity: sha512-de2v8gH9zj47tRI38oSxhQIewmNc+OZjYIOOaMoVWKL65ERSav2PYYZHPSPCrfOeLMkv+Dyh8Y0QGwkO29wMWQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider@2.0.0': - resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} + '@ai-sdk/provider@3.0.0': + resolution: {integrity: sha512-m9ka3ptkPQbaHHZHqDXDF9C9B5/Mav0KTdky1k2HZ3/nrW2t1AgObxIVPyGDWQNS9FXT/FS6PIoSjpcP/No8rQ==} engines: {node: '>=18'} - '@ai-sdk/react@2.0.64': - resolution: {integrity: sha512-o5scrAU+V3MJRD2HoX/pKUYZHITTTaNIM35fFH8n7mdCFovNUI1DiAHYtw8IMM+a5GM92jPvfejKRKaHHs/LVQ==} + '@ai-sdk/react@3.0.3': + resolution: {integrity: sha512-mLIgQuBdIX9gxCYQN3Pv/J8ARoFreIKYr/TVQtI+FwEzejuGFimTyhDln7UIBfrnm3Mpn1xENIWZfDfCRF7wkw==} engines: {node: '>=18'} peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - zod: ^3.25.76 || ^4.1.8 - peerDependenciesMeta: - zod: - optional: true + react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 '@algolia/abtesting@1.2.0': resolution: {integrity: sha512-Z6Liq7US5CpdHExZLfPMBPxQHHUObV587kGvCLniLr1UTx0fGFIeGNWd005WIqQXqEda9GyAi7T2e7DUupVv0g==} @@ -6258,6 +6254,9 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -6910,8 +6909,8 @@ packages: resolution: {integrity: sha512-fHqnxfBYcwkamlEgcIzaZqL8IHT09hR7FZL7UdMTdGJyoaBzM/dY6ulO5Swi4ig30FrBJI9I2C+GLV9sb9vexA==} engines: {node: '>=16'} - '@vercel/oidc@3.0.2': - resolution: {integrity: sha512-JekxQ0RApo4gS4un/iMGsIL1/k4KUBe3HmnGcDvzHuFBdQdudEJgTqcsJC7y6Ul4Yw5CeykgvQbX2XeEJd0+DA==} + '@vercel/oidc@3.0.5': + resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} engines: {node: '>= 20'} '@vinxi/listhen@1.5.6': @@ -7099,8 +7098,8 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ai@5.0.64: - resolution: {integrity: sha512-a7H1z2Xz6NQdgx+FIdDlkenoPYBbxbmJSbRfnOFnYS1S1XraiHT8M85hLvz8d8zlxVtSSjiP+c4EjqwtAe72cg==} + ai@6.0.3: + resolution: {integrity: sha512-OOo+/C+sEyscoLnbY3w42vjQDICioVNyS+F+ogwq6O5RJL/vgWGuiLzFwuP7oHTeni/MkmX8tIge48GTdaV7QQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -14455,39 +14454,39 @@ snapshots: optionalDependencies: graphql: 16.12.0 - '@ai-sdk/gateway@1.0.35(zod@4.1.13)': + '@ai-sdk/gateway@3.0.2(zod@4.1.13)': dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.11(zod@4.1.13) - '@vercel/oidc': 3.0.2 + '@ai-sdk/provider': 3.0.0 + '@ai-sdk/provider-utils': 4.0.1(zod@4.1.13) + '@vercel/oidc': 3.0.5 zod: 4.1.13 - '@ai-sdk/openai-compatible@1.0.20(zod@4.1.13)': + '@ai-sdk/openai-compatible@2.0.1(zod@4.1.13)': dependencies: - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.11(zod@4.1.13) + '@ai-sdk/provider': 3.0.0 + '@ai-sdk/provider-utils': 4.0.1(zod@4.1.13) zod: 4.1.13 - '@ai-sdk/provider-utils@3.0.11(zod@4.1.13)': + '@ai-sdk/provider-utils@4.0.1(zod@4.1.13)': dependencies: - '@ai-sdk/provider': 2.0.0 - '@standard-schema/spec': 1.0.0 + '@ai-sdk/provider': 3.0.0 + '@standard-schema/spec': 1.1.0 eventsource-parser: 3.0.6 zod: 4.1.13 - '@ai-sdk/provider@2.0.0': + '@ai-sdk/provider@3.0.0': dependencies: json-schema: 0.4.0 - '@ai-sdk/react@2.0.64(react@19.2.3)(zod@4.1.13)': + '@ai-sdk/react@3.0.3(react@19.2.3)(zod@4.1.13)': dependencies: - '@ai-sdk/provider-utils': 3.0.11(zod@4.1.13) - ai: 5.0.64(zod@4.1.13) + '@ai-sdk/provider-utils': 4.0.1(zod@4.1.13) + ai: 6.0.3(zod@4.1.13) react: 19.2.3 swr: 2.3.6(react@19.2.3) throttleit: 2.1.0 - optionalDependencies: - zod: 4.1.13 + transitivePeerDependencies: + - zod '@algolia/abtesting@1.2.0': dependencies: @@ -19744,6 +19743,8 @@ snapshots: '@standard-schema/spec@1.0.0': {} + '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} '@sveltejs/acorn-typescript@1.0.7(acorn@8.15.0)': @@ -20528,7 +20529,7 @@ snapshots: '@resvg/resvg-wasm': 2.4.0 satori: 0.16.0 - '@vercel/oidc@3.0.2': {} + '@vercel/oidc@3.0.5': {} '@vinxi/listhen@1.5.6': dependencies: @@ -20788,11 +20789,11 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ai@5.0.64(zod@4.1.13): + ai@6.0.3(zod@4.1.13): dependencies: - '@ai-sdk/gateway': 1.0.35(zod@4.1.13) - '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.11(zod@4.1.13) + '@ai-sdk/gateway': 3.0.2(zod@4.1.13) + '@ai-sdk/provider': 3.0.0 + '@ai-sdk/provider-utils': 4.0.1(zod@4.1.13) '@opentelemetry/api': 1.9.0 zod: 4.1.13