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