diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b30556e8e..a05f762db 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -46,6 +46,15 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
+ - name: Lint
+ run: pnpm lint
+
+ - name: Typecheck
+ run: pnpm typecheck
+
+ - name: Test
+ run: pnpm test
+
- name: Build
run: pnpm run build
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index edb7c9cd5..b6e163249 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -21,5 +21,23 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
+ - name: Lint
+ run: pnpm lint
+
+ - name: Typecheck
+ run: pnpm typecheck
+
- name: Test
run: pnpm test
+
+ - name: Build
+ run: pnpm build
+
+ - name: Install documentation dependencies
+ run: pnpm --dir docs install --frozen-lockfile
+
+ - name: Typecheck documentation
+ run: pnpm --dir docs check-types
+
+ - name: Build documentation
+ run: pnpm --dir docs build
diff --git a/docs/content/docs/integrations/btst-v3.mdx b/docs/content/docs/integrations/btst-v3.mdx
index 565e4a8b1..4ca4dec14 100644
--- a/docs/content/docs/integrations/btst-v3.mdx
+++ b/docs/content/docs/integrations/btst-v3.mdx
@@ -1,190 +1,238 @@
---
title: BTST v3
-description: Configure Better Auth UI as the client and server auth provider for BTST v3
+description: Use Better Auth UI routes and services with BTST v3
---
-`@btst/better-auth-ui` supplies the Better Auth client plugins, page routes, and
-the first-party auth-provider adapters used by BTST v3. Router, API,
-notification, localization, and auth services are configured once at the top
-level instead of repeated in each plugin override.
+`@btst/better-auth-ui` registers Better Auth UI routes with BTST and adapts
+BTST's router, notifications, and localization services to the Better Auth UI
+context. Better Auth UI continues to read its own session and permissions from
+your Better Auth client.
+
+BTST authorization is separate and optional. This package does not convert a
+Better Auth user into a BTST identity or export client/server authorization
+adapters.
## Install the release candidate
+The minimal integration uses the auth and account plugins:
+
```bash
-pnpm add @btst/stack@next @btst/better-auth-ui@next @btst/yar@^1.3.0
+pnpm add @btst/stack@next @btst/better-auth-ui@next @btst/yar@^1.3.2
```
+Enable organization, API-key, passkey, multi-session, and similar UI only when
+the matching Better Auth plugins are configured in your application.
+
## Register the client plugins
+Configure the API, site mount, and query client once. The Better Auth UI plugin
+definitions inherit that resolved runtime and remain safe to import while
+creating an SSR stack.
+
```tsx title="lib/stack-client.tsx"
-import { createStackClient } from "@btst/stack/client"
+import { createClientStack } from "@btst/stack/client"
import {
accountClientPlugin,
authClientPlugin,
organizationClientPlugin,
} from "@btst/better-auth-ui/client"
+import type { QueryClient } from "@tanstack/react-query"
-export function getStackClient() {
- return createStackClient({
- basePath: "/p",
+export function createAppClientStack(queryClient: QueryClient, baseURL: string) {
+ return createClientStack({
+ api: {
+ baseURL,
+ basePath: "/api/data",
+ },
+ site: {
+ baseURL,
+ basePath: "/pages",
+ },
+ queryClient,
plugins: {
- auth: authClientPlugin({
- siteBasePath: "/p",
- siteBaseURL: process.env.NEXT_PUBLIC_APP_URL!,
- }),
- account: accountClientPlugin({
- siteBasePath: "/p",
- siteBaseURL: process.env.NEXT_PUBLIC_APP_URL!,
- }),
- organization: organizationClientPlugin({
- siteBasePath: "/p",
- siteBaseURL: process.env.NEXT_PUBLIC_APP_URL!,
- }),
+ auth: authClientPlugin(),
+ account: accountClientPlugin(),
+ // Opt in only when Better Auth organization support is configured.
+ // organization: organizationClientPlugin(),
},
})
}
```
+For SSR, create a request-specific stack with filtered `api.headers`. Create a
+separate stable browser stack without request headers and pass that stack to
+`StackProvider`. Never serialize a server-created stack, Better Auth server
+instance, request headers, or secrets into the browser.
+
## Configure the client provider
-`createBetterAuthProvider` maps the Better Auth session to BTST identity. It
-leaves Better Auth UI's native permission hook in place unless an explicit
-authorization mapping is configured. Set `permissionProvider` only when the
-matching Better Auth client plugin is installed; resource/action checks are
-then sent to that plugin's `hasPermission` endpoint and anonymous checks fail
-closed.
+The resolved stack infers the available override keys and their value types.
+Do not declare a manual plugin override map or repeat API/site paths on the
+provider.
-```tsx title="app/p/layout.tsx"
+```tsx title="app/pages/client-layout.tsx"
"use client"
-import { StackProvider, type StackI18nProvider, type StackNotifyProvider } from "@btst/stack/context"
+import { StackProvider } from "@btst/stack/context"
import { nextRouter } from "@btst/stack/next"
-import { createBetterAuthProvider } from "@btst/better-auth-ui"
-import {
- type AccountPluginOverrides,
- type AuthPluginOverrides,
- type OrganizationPluginOverrides,
-} from "@btst/better-auth-ui/client"
-import { toast } from "sonner"
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
+import { useMemo, useState } from "react"
+import { useRouter } from "next/navigation"
import { authClient } from "@/lib/auth-client"
-import { translateForApp } from "@/lib/i18n"
+import { createAppClientStack } from "@/lib/stack-client"
+
+export default function PagesClientLayout({
+ baseURL,
+ children,
+}: {
+ baseURL: string
+ children: React.ReactNode
+}) {
+ const router = useRouter()
+ const [queryClient] = useState(() => new QueryClient())
+ const stack = useMemo(
+ () => createAppClientStack(queryClient, baseURL),
+ [baseURL, queryClient],
+ )
-type PluginOverrides = {
- auth: AuthPluginOverrides
- account: AccountPluginOverrides
- organization: OrganizationPluginOverrides
+ return (
+
+ router.refresh(),
+ },
+ account: {
+ account: true,
+ avatar: {
+ upload: uploadAvatar,
+ delete: deleteAvatar,
+ size: 128,
+ extension: "png",
+ },
+ },
+ }}
+ >
+ {children}
+
+
+ )
}
+```
-const stackAuth = createBetterAuthProvider(authClient, {
- loginPath: "/p/auth/sign-in",
- permissionProvider: "organization",
-})
+`authClient` is configured once under `auth`. Avatar configuration belongs
+under `account`. Route bases cannot be overridden: with the site mounted at
+`/pages`, the effective routes are `/pages/auth`, `/pages/account`, and, when
+registered, `/pages/organization`.
-const notify: StackNotifyProvider = {
- success: toast.success,
- error: toast.error,
- info: toast.info,
- warning: toast.warning,
-}
+Better Auth UI localization keys reach BTST i18n as
+`better-auth-ui.KEY`, retaining the package's English string as the default.
+Toasts use the top-level notification service. Links and programmatic
+navigation resolve each plugin's site endpoint; same-origin destinations use
+the top-level router and cross-origin destinations use full-page navigation.
-const i18n: StackI18nProvider = {
- translate: (key, defaultValue, params) =>
- translateForApp(key, { defaultValue, ...params }),
-}
+## Choose session synchronization explicitly
-export default function PagesLayout({ children }: { children: React.ReactNode }) {
- const baseURL =
- typeof window === "undefined"
- ? process.env.NEXT_PUBLIC_APP_URL!
- : window.location.origin
+`onSessionChange` runs after Better Auth UI changes the session. The bridge
+calls only your callback; it does not refetch a BTST identity or refresh a
+framework route behind the scenes.
- return (
-
- basePath="/p"
- router={nextRouter()}
- api={{ baseURL, basePath: "/api/data" }}
- auth={stackAuth}
- notify={notify}
- i18n={i18n}
- overrides={{
- auth: {
- authClient,
- basePath: "/p/auth",
- redirectTo: "/p/account/settings",
- },
- account: {
- authClient,
- basePath: "/p/account",
- account: true,
- },
- organization: {
- authClient,
- basePath: "/p/organization",
- organization: true,
- },
- }}
- >
- {children}
-
- )
-}
-```
+- Next.js: call `router.refresh()` to hydrate a new server-rendered
+ `initialIdentity`.
+- React Router: call `useRevalidator().revalidate()`.
+- TanStack Router: call `router.invalidate()`.
+- Client-only apps: call your application auth store's identity refetch
+ function when that is the desired behavior.
-Better Auth UI localization keys are passed to the top-level translator as
-`better-auth-ui.KEY`, with the package's English string as `defaultValue`.
-Toasts use the top-level notification provider, and navigation/session refresh
-use the top-level router.
+The application may refresh the route, refetch its BTST identity, do both, or
+do neither.
-## Configure the server provider
+## Optional application-owned BTST authorization
-The server entry exports the corresponding adapter for `stack({ auth })`. It
-passes the incoming headers to Better Auth and memoizes the session result for
-the request. BTST also shares that identity with all lifecycle hooks handling
-the request.
+Better Auth UI does not require BTST authorization. If other BTST business
+plugins should use the same signed-in user, define the mapping in application
+code so your identity schema remains explicit and type-safe.
-```ts title="lib/stack.ts"
-import { stack } from "@btst/stack/api"
-import { createBetterAuthProvider } from "@btst/better-auth-ui/server"
-import { auth } from "@/lib/auth"
-import { adapter } from "@/lib/btst-adapter"
-
-export const { handler } = stack({
- basePath: "/api/data",
- adapter,
- plugins: {},
- auth: createBetterAuthProvider(auth, {
- permissionProvider: "organization",
- }),
+```ts title="lib/authorization.client.ts"
+"use client"
+
+import { createClientAuth } from "@btst/stack/authorization/client"
+import { authorization } from "@/lib/authorization"
+import { authClient } from "@/lib/auth-client"
+
+export const clientAuth = createClientAuth({
+ authorization,
+ getIdentity: async () => {
+ const { data } = await authClient.getSession()
+ const user = data?.user
+
+ return user
+ ? {
+ id: user.id,
+ name: user.name,
+ email: user.email,
+ role: user.role,
+ tenantId: user.tenantId,
+ }
+ : null
+ },
+ loginPath: "/pages/auth/sign-in",
})
```
-If the Better Auth organization plugin is not configured, omit
-`permissionProvider`. To use the admin plugin instead, set it to `"admin"` on
-both the client and server adapters.
+```ts title="lib/authorization.server.ts"
+import "server-only"
-## Add the v3 entry factories
-
-```ts title="app/api/data/[[...all]]/route.ts"
-import { toNextRouteHandlers } from "@btst/stack/next"
-import { handler } from "@/lib/stack"
+import { createServerAuth } from "@btst/stack/authorization/server"
+import { authorization } from "@/lib/authorization"
+import { auth } from "@/lib/auth"
-export const { GET, POST, PUT, PATCH, DELETE } = toNextRouteHandlers(handler)
+export const serverAuth = createServerAuth({
+ authorization,
+ getIdentityFromHeaders: async ({ headers }) => {
+ const session = await auth.api.getSession({ headers })
+ const user = session?.user
+
+ return user
+ ? {
+ id: user.id,
+ name: user.name,
+ email: user.email,
+ role: user.role,
+ tenantId: user.tenantId,
+ }
+ : null
+ },
+})
```
-```tsx title="app/p/[[...all]]/page.tsx"
-import { createNextPage } from "@btst/stack/next"
-import { getOrCreateQueryClient } from "@/lib/query-client"
-import { getStackClient } from "@/lib/stack-client"
+The `role` and `tenantId` fields above are illustrative application fields, not
+fields inferred by Better Auth UI. Pass `clientAuth` and an optional
+server-rendered `initialIdentity` to `StackProvider`; pass `serverAuth` to
+`createBackendStack`.
-export const dynamic = "force-dynamic"
+Managed or custom backends can implement the same BTST authorization contracts
+without Better Auth. Keep backend authorization authoritative and never trust
+browser-provided identity or permission facts.
-const page = createNextPage({
- getStackClient,
- getQueryClient: getOrCreateQueryClient,
-})
+## Optional data adapters
+
+The base entries do not load optional data adapters. Install their peers only
+when importing the matching subpath:
+
+```bash
+# @btst/better-auth-ui/tanstack
+pnpm add @daveyplate/better-auth-tanstack @tanstack/react-query
+
+# @btst/better-auth-ui/instantdb
+pnpm add @instantdb/react
-export default page.Page
-export const generateMetadata = page.generateMetadata
+# @btst/better-auth-ui/triplit
+pnpm add @triplit/client @triplit/react
```
Finally, import `@btst/better-auth-ui/css` from the application's global
diff --git a/package.json b/package.json
index bb5612087..f91a25277 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@btst/better-auth-ui",
"homepage": "https://www.better-stack.ai/",
- "version": "2.0.0-rc.1",
+ "version": "2.0.0-rc.3",
"description": "Plug & play shadcn/ui components for better-auth",
"repository": {
"type": "git",
@@ -14,7 +14,8 @@
"lint": "biome check",
"lint:fix": "biome check --write",
"test": "vitest run",
- "test:watch": "vitest"
+ "test:watch": "vitest",
+ "typecheck": "tsc --noEmit"
},
"type": "module",
"main": "./dist/index.cjs",
@@ -96,11 +97,14 @@
"author": "daveycodez",
"license": "MIT",
"devDependencies": {
- "@better-auth/api-key": "1.6.16",
- "@better-auth/passkey": "1.6.16",
- "@better-auth/utils": "0.4.1",
- "@better-fetch/fetch": "1.2.2",
+ "@better-auth/api-key": "1.7.2",
+ "@better-auth/core": "1.7.2",
+ "@better-auth/passkey": "1.7.2",
+ "@better-auth/utils": "0.4.2",
+ "@better-fetch/fetch": "1.3.1",
"@biomejs/biome": "2.4.16",
+ "@btst/stack": "3.0.0-rc.3",
+ "@btst/yar": "1.3.2",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-context": "^1.1.3",
"@radix-ui/react-dialog": "^1.1.15",
@@ -112,16 +116,14 @@
"@radix-ui/react-use-callback-ref": "^1.1.1",
"@radix-ui/react-use-layout-effect": "^1.1.1",
"@tanstack/react-query": "^5.100.14",
- "@btst/stack": "3.0.0-rc.1",
- "@btst/yar": "1.3.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/node": "^25.5.0",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@types/react-google-recaptcha": "^2.1.9",
- "better-auth": "1.6.16",
- "better-call": "1.3.6",
+ "better-auth": "1.7.2",
+ "better-call": "1.4.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"esbuild-plugin-preserve-directives": "^0.0.11",
@@ -138,12 +140,11 @@
"zod": "^4.4.3"
},
"peerDependencies": {
- "@better-auth/api-key": "1.6.16",
- "@better-auth/passkey": "1.6.16",
- "@better-auth/utils": "0.4.1",
- "@better-fetch/fetch": "1.2.2",
- "@btst/stack": "^3.0.0-rc.1",
- "@btst/yar": "^1.3.0",
+ "@better-auth/api-key": "1.7.2",
+ "@better-auth/passkey": "1.7.2",
+ "@better-fetch/fetch": "1.3.1",
+ "@btst/stack": "^3.0.0-rc.3",
+ "@btst/yar": "^1.3.2",
"@captchafox/react": "^1.10.0",
"@daveyplate/better-auth-tanstack": "^1.3.6",
"@hookform/resolvers": ">=5.2.0",
@@ -166,8 +167,7 @@
"@tanstack/react-query": ">=5.100.14",
"@triplit/client": ">=1.0.0",
"@triplit/react": ">=1.0.0",
- "better-auth": "1.6.16",
- "better-call": "1.3.6",
+ "better-auth": "1.7.2",
"class-variance-authority": ">=0.7.0",
"clsx": ">=2.1.0",
"input-otp": ">=1.4.0",
@@ -180,6 +180,20 @@
"tailwindcss": ">=3.0.0",
"zod": ">=4.4.3"
},
+ "peerDependenciesMeta": {
+ "@daveyplate/better-auth-tanstack": {
+ "optional": true
+ },
+ "@instantdb/react": {
+ "optional": true
+ },
+ "@triplit/client": {
+ "optional": true
+ },
+ "@triplit/react": {
+ "optional": true
+ }
+ },
"dependencies": {
"@hcaptcha/react-hcaptcha": "^2.0.2",
"@noble/hashes": "^2.0.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 849059ce5..64b20b353 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,7 +13,7 @@ importers:
version: 1.10.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@daveyplate/better-auth-tanstack':
specifier: ^1.3.6
- version: 1.3.6(@tanstack/query-core@5.101.0)(@tanstack/react-query@5.101.0(react@19.1.0))(better-auth@1.6.16(c397378e994be405f0e2dd7ce3fe42f2))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ version: 1.3.6(@tanstack/query-core@5.101.0)(@tanstack/react-query@5.101.0(react@19.1.0))(better-auth@1.7.2(c397378e994be405f0e2dd7ce3fe42f2))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@hcaptcha/react-hcaptcha':
specifier: ^2.0.2
version: 2.0.2
@@ -85,26 +85,29 @@ importers:
version: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
devDependencies:
'@better-auth/api-key':
- specifier: 1.6.16
- version: 1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(better-auth@1.6.16(c397378e994be405f0e2dd7ce3fe42f2))(better-call@1.3.6(zod@4.4.3))
+ specifier: 1.7.2
+ version: 1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(better-auth@1.7.2(c397378e994be405f0e2dd7ce3fe42f2))(better-call@1.4.0(zod@4.4.3))
+ '@better-auth/core':
+ specifier: 1.7.2
+ version: 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
'@better-auth/passkey':
- specifier: 1.6.16
- version: 1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(better-auth@1.6.16(c397378e994be405f0e2dd7ce3fe42f2))(better-call@1.3.6(zod@4.4.3))(nanostores@1.2.0)
+ specifier: 1.7.2
+ version: 1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.7.2(c397378e994be405f0e2dd7ce3fe42f2))(better-call@1.4.0(zod@4.4.3))(nanostores@1.5.2)
'@better-auth/utils':
- specifier: 0.4.1
- version: 0.4.1
+ specifier: 0.4.2
+ version: 0.4.2
'@better-fetch/fetch':
- specifier: 1.2.2
- version: 1.2.2
+ specifier: 1.3.1
+ version: 1.3.1
'@biomejs/biome':
specifier: 2.4.16
version: 2.4.16
'@btst/stack':
- specifier: 3.0.0-rc.1
- version: 3.0.0-rc.1(e81d6175a2ec14a321d9bc0d949e24c7)
+ specifier: 3.0.0-rc.3
+ version: 3.0.0-rc.3(015e869596a3119c2b2094a599d03cb1)
'@btst/yar':
- specifier: 1.3.0
- version: 1.3.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react@19.1.0)
+ specifier: 1.3.2
+ version: 1.3.2(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react@19.1.0)
'@radix-ui/react-checkbox':
specifier: ^1.3.3
version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -157,11 +160,11 @@ importers:
specifier: ^2.1.9
version: 2.1.9
better-auth:
- specifier: 1.6.16
- version: 1.6.16(c397378e994be405f0e2dd7ce3fe42f2)
+ specifier: 1.7.2
+ version: 1.7.2(c397378e994be405f0e2dd7ce3fe42f2)
better-call:
- specifier: 1.3.6
- version: 1.3.6(zod@4.4.3)
+ specifier: 1.4.0
+ version: 1.4.0(zod@4.4.3)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -257,22 +260,22 @@ packages:
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
- '@better-auth/api-key@1.6.16':
- resolution: {integrity: sha512-3iEn1tcVsT9inPTrjinyciA8TT1cpls/uDd/7cQCN6hN4xy6l4VO11pnoMcetstdq6jOayWgyrDT6cPCl/hafA==}
+ '@better-auth/api-key@1.7.2':
+ resolution: {integrity: sha512-jih45wZaQ83lVYdChSnasxb8iax8p7AYetZ/XiImA9Yuag+cqmLBsaLPNvds5aZV18hLpmvzVkuzf7H/k1X88g==}
peerDependencies:
- '@better-auth/core': ^1.6.16
- '@better-auth/utils': 0.4.1
- better-auth: ^1.6.16
- better-call: 1.3.6
+ '@better-auth/core': ^1.7.2
+ '@better-auth/utils': 0.4.2
+ better-auth: ^1.7.2
+ better-call: 1.4.0
- '@better-auth/core@1.6.16':
- resolution: {integrity: sha512-a0+ZNaaYYxOdFXFXmOE36TgtYN8QDzSYDozaAH0zsiWB0oyljsENyCxHJSekysISftb0rFpVXNdw525aEAOa6w==}
+ '@better-auth/core@1.7.2':
+ resolution: {integrity: sha512-j0nM4ygsWbF/fcYRoKtDn8gn8uLXkmC+075HqSqsJEAV828cJR9bvYBCUQ1zmxNyRBk6Iz/qXsA0Zm2oksiOTg==}
peerDependencies:
- '@better-auth/utils': 0.4.1
- '@better-fetch/fetch': 1.2.2
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
'@cloudflare/workers-types': '>=4'
'@opentelemetry/api': ^1.9.0
- better-call: 1.3.6
+ better-call: 1.4.0
jose: ^6.1.0
kysely: ^0.28.5 || ^0.29.0
nanostores: ^1.0.1
@@ -282,57 +285,57 @@ packages:
'@opentelemetry/api':
optional: true
- '@better-auth/drizzle-adapter@1.6.16':
- resolution: {integrity: sha512-AZjswadpR7zlQduj3fRSsu1R5ldQRR9AeFqoxXRI4colrQhevOVY+tJr8RTJv9Nh18e9FMYDXUju2GX+QWHDzg==}
+ '@better-auth/drizzle-adapter@1.7.2':
+ resolution: {integrity: sha512-A5wE10PIv3aS5LGePecEHntQylKy6OOF17B4dqlE0DwJeqU/IOBSd7/LZhMop9cNJ3WFjKMpazVSf91yYM/NFg==}
peerDependencies:
- '@better-auth/core': ^1.6.16
- '@better-auth/utils': 0.4.1
- drizzle-orm: ^0.45.2
+ '@better-auth/core': ^1.7.2
+ '@better-auth/utils': 0.4.2
+ drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0
peerDependenciesMeta:
drizzle-orm:
optional: true
- '@better-auth/kysely-adapter@1.6.16':
- resolution: {integrity: sha512-ys/feL1p6By3/rQlMZ8QTgf9K2tZAIp1p+fGqT2krIoG5r+UsH3gMkUdbHlYxLt790Bo+Njkiqt59P0BMNsi+g==}
+ '@better-auth/kysely-adapter@1.7.2':
+ resolution: {integrity: sha512-LYdSRLOvZiF+6S0UThu+wE/Qxsq9P2jQs7ZKkY6BIBJqUjYyxVDmi8HFcantBvWWW1/BeQCSsD7YVDG4gICMIQ==}
peerDependencies:
- '@better-auth/core': ^1.6.16
- '@better-auth/utils': 0.4.1
+ '@better-auth/core': ^1.7.2
+ '@better-auth/utils': 0.4.2
kysely: ^0.28.17 || ^0.29.0
peerDependenciesMeta:
kysely:
optional: true
- '@better-auth/memory-adapter@1.6.16':
- resolution: {integrity: sha512-8mDqe+2PMF9hUxjGNP1NOcqU1AqjUgmE8YC1HTtxa+LjnO7zsAPSxGSyo1L+7buFNLtiNyGFxccHpwOkO4/Msw==}
+ '@better-auth/memory-adapter@1.7.2':
+ resolution: {integrity: sha512-0q1SXMzm5esH9L0xVuM6IxCk59E4G+3HySX4My9gvEwqtmUobykn+iuc/si3Y4xwUO7JODqQ5o+/pPcLDDMIrA==}
peerDependencies:
- '@better-auth/core': ^1.6.16
- '@better-auth/utils': 0.4.1
+ '@better-auth/core': ^1.7.2
+ '@better-auth/utils': 0.4.2
- '@better-auth/mongo-adapter@1.6.16':
- resolution: {integrity: sha512-JbUg/v3m9WUX94ivVdUOF8t/w2mWNBWvqYMqyWybfHQEPR8cvcqsqpfYvwg9HLBrYwhKXBS3KcJ1Rtk6gZ19Yw==}
+ '@better-auth/mongo-adapter@1.7.2':
+ resolution: {integrity: sha512-4879SmUWHUs0OYlvHoCFbycZ7i1bqytkcgAUdt9RLQMvZ5H3LRMTgax2YVlGZEXgwNjY/X7xAoXOecWLhlQWeA==}
peerDependencies:
- '@better-auth/core': ^1.6.16
- '@better-auth/utils': 0.4.1
+ '@better-auth/core': ^1.7.2
+ '@better-auth/utils': 0.4.2
mongodb: ^6.0.0 || ^7.0.0
peerDependenciesMeta:
mongodb:
optional: true
- '@better-auth/passkey@1.6.16':
- resolution: {integrity: sha512-UpDcQmu4fNCHtQ/maFjsdbfeLPu1rzlTSqfpAUhMYGFK/wzHdA1TqWvimmOgAYBbv+mIv5IGOQ6pTC4gzJJj2Q==}
+ '@better-auth/passkey@1.7.2':
+ resolution: {integrity: sha512-KBK852b+HsCdstVPPDHsuRa9Rc+7IEuRMPQboi/OXNOwgKL8GwHpzzWD2WhiT/FXJPrLCPh8vHJQfc1wdl5OZw==}
peerDependencies:
- '@better-auth/core': ^1.6.16
- '@better-auth/utils': 0.4.1
- '@better-fetch/fetch': 1.2.2
- better-auth: ^1.6.16
- better-call: 1.3.6
+ '@better-auth/core': ^1.7.2
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+ better-auth: ^1.7.2
+ better-call: 1.4.0
nanostores: ^1.0.1
- '@better-auth/prisma-adapter@1.6.16':
- resolution: {integrity: sha512-2bIlA7wjBx+4N2QcM32xL/YojRuJpDvskXqT/dGYKToDIEl/7yr12cLYlqeaFLL0O0s5qNZ8jbDtlCz20eogeQ==}
+ '@better-auth/prisma-adapter@1.7.2':
+ resolution: {integrity: sha512-mXTr/83WrNWLrvzIjtgDgdu9iXhOcSG1+qBQOAKlbGSFiOB+z4IMRneQ2wmMOiB8mKY9qGkClVUjKRFXqtHnFQ==}
peerDependencies:
- '@better-auth/core': ^1.6.16
- '@better-auth/utils': 0.4.1
+ '@better-auth/core': ^1.7.2
+ '@better-auth/utils': 0.4.2
'@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0
prisma: ^5.0.0 || ^6.0.0 || ^7.0.0
peerDependenciesMeta:
@@ -341,18 +344,21 @@ packages:
prisma:
optional: true
- '@better-auth/telemetry@1.6.16':
- resolution: {integrity: sha512-A782UQvlqZBddw0j2Q6tdroHulIpMlqQh/pbw2up30drLi66jz1ttgShRmryfOLAqN4DHqteuRrSsqDDrsp/pA==}
+ '@better-auth/telemetry@1.7.2':
+ resolution: {integrity: sha512-LcWu+O0zrxYDQj8E36vfkJwGPW4k9ZDA/rCo0zST6ihzL+juR7pBowoZIM9E6tK0Vit52mf6412bGT4XM4eTjQ==}
peerDependencies:
- '@better-auth/core': ^1.6.16
- '@better-auth/utils': 0.4.1
- '@better-fetch/fetch': 1.2.2
+ '@better-auth/core': ^1.7.2
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+
+ '@better-auth/utils@0.4.2':
+ resolution: {integrity: sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==}
- '@better-auth/utils@0.4.1':
- resolution: {integrity: sha512-SZBPRPF3z0nBvE5ygOkxae35wnnXPRShmqFo78S+qslLeFoPu/pMgnXAuNKFMMybac3tiLaVg1e3MQW5MC+1iA==}
+ '@better-auth/utils@0.5.0':
+ resolution: {integrity: sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA==}
- '@better-fetch/fetch@1.2.2':
- resolution: {integrity: sha512-xlgQcYROGFgKg5FY7ZLppFmG7rR5Hkmz7tgDuQeR79i5KhKRjr2QC9xsBG2qEGPJJjf9bxzg/NMW2hEUWs5OnA==}
+ '@better-fetch/fetch@1.3.1':
+ resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==}
'@biomejs/biome@2.4.16':
resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==}
@@ -407,16 +413,20 @@ packages:
cpu: [x64]
os: [win32]
- '@btst/db@2.2.2':
- resolution: {integrity: sha512-NLT9FXK4c60wP1DQ3lTRPd9Hqa+54VoqtQTT0w9xilk3Vm6DfNnWE9npwf8Nc+5noUBVtvgESUYssQHcOjNSbA==}
+ '@btst/db@2.2.3':
+ resolution: {integrity: sha512-tbx0o9mJnv3y5WVole5TZQwMMVmboamVJxsSeyMZH3O4qF9CNdEg6NfiR9zm/b81eO7ie6CftTWT9R7pJ4msSA==}
+ peerDependencies:
+ '@better-auth/core': 1.6.16
+ '@better-auth/utils': 0.4.1
+ better-auth: 1.6.16
- '@btst/stack@3.0.0-rc.1':
- resolution: {integrity: sha512-XphE3xQ0v/IGqt08PDi6N/I6ZSC3kzOwdy3znIMu1pY+2XseQpyBmVCnSvQNBWm9IMaUCAYuPhBuk1hM+BhgRw==}
+ '@btst/stack@3.0.0-rc.3':
+ resolution: {integrity: sha512-q9I7PFEzsYul/FwAHIsZQwMUjc9ShedUPSKL0/DAM8G6dSK2+YfGKlWOVM3oBJ//MQ1u8CtaRijg9huIy83GnA==}
peerDependencies:
'@ai-sdk/react': '>=2.0.0'
'@aws-sdk/client-s3': '>=3.0.0'
'@aws-sdk/s3-request-presigner': '>=3.0.0'
- '@btst/yar': '>=1.3.0'
+ '@btst/yar': '>=1.3.2'
'@hookform/resolvers': '>=5.0.0'
'@radix-ui/react-dialog': '>=1.1.0'
'@radix-ui/react-label': '>=2.1.0'
@@ -427,7 +437,7 @@ packages:
'@tanstack/react-router': '>=1.0.0'
'@vercel/blob': '>=0.14.0'
ai: '>=5.0.0'
- better-call: '>=1.3.5'
+ better-call: 1.3.6
class-variance-authority: '>=0.7.0'
clsx: '>=2.1.0'
cmdk: '>=1.1.0'
@@ -465,11 +475,11 @@ packages:
react-router:
optional: true
- '@btst/yar@1.3.0':
- resolution: {integrity: sha512-TD6/whPS6ES7uDFkL/1QIWSvrDyg7QDvtn9s7frAcGwbg4+0VNsC8DOhmJ7MlWb3qF5JuGQXM2hB62P4vSeWPQ==}
+ '@btst/yar@1.3.2':
+ resolution: {integrity: sha512-2YqkTht2PdKizlkwF5EG/fmLPzK4SG7YCkOojwDVRrlj0s3YpszhSak0uongrqslLoexozxN0RixbOeEnz9Vew==}
peerDependencies:
- '@types/react': ^19.1.16
- '@types/react-dom': ^19.1.9
+ '@types/react': ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^18.0.0 || ^19.0.0
react: ^18.0.0 || ^19.0.0
'@captchafox/react@1.10.0':
@@ -963,14 +973,18 @@ packages:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
- '@noble/ciphers@2.1.1':
- resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==}
+ '@noble/ciphers@2.4.0':
+ resolution: {integrity: sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw==}
engines: {node: '>= 20.19.0'}
'@noble/hashes@2.0.1':
resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==}
engines: {node: '>= 20.19.0'}
+ '@noble/hashes@2.4.0':
+ resolution: {integrity: sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==}
+ engines: {node: '>= 20.19.0'}
+
'@ocavue/utils@1.6.0':
resolution: {integrity: sha512-8W3q1hxx9qFdrYgPtbElllG/tqYkO/dMhlRUiqasO0SuDFTj78azSQjhIrBTFWxlBPPsSZN6zXYHmb3RwN2Jtg==}
@@ -978,8 +992,8 @@ packages:
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
- '@opentelemetry/semantic-conventions@1.40.0':
- resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==}
+ '@opentelemetry/semantic-conventions@1.43.0':
+ resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==}
engines: {node: '>=14'}
'@oxc-project/types@0.122.0':
@@ -2075,11 +2089,11 @@ packages:
'@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
- '@simplewebauthn/browser@13.2.2':
- resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
+ '@simplewebauthn/browser@13.3.0':
+ resolution: {integrity: sha512-BE/UWv6FOToAdVk0EokzkqQQDOWtNydYlY6+OrmiZ5SCNmb41VehttboTetUM3T/fr6EAFYVXjz4My2wg230rQ==}
- '@simplewebauthn/server@13.3.0':
- resolution: {integrity: sha512-MLHYFrYG8/wK2i+86XMhiecK72nMaHKKt4bo+7Q1TbuG9iGjlSdfkPWKO5ZFE/BX+ygCJ7pr8H/AJeyAj1EaTQ==}
+ '@simplewebauthn/server@13.3.3':
+ resolution: {integrity: sha512-LelX/lcy5cjc15A86i/aNxHhB5eU7dd20QsbP0VLAf9e38+SLlsnqCCyecx3xqfGofhmX05h1J9fKRYWxw+luA==}
engines: {node: '>=20.0.0'}
'@standard-schema/spec@1.1.0':
@@ -2387,8 +2401,8 @@ packages:
bail@2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
- better-auth@1.6.16:
- resolution: {integrity: sha512-YlBITnH3LIBRD+JpR1XRIToJAVVpoQvZzRc4sm5W0/bnPZKLbsmtXbVWJF3ypo9TVnF6geczJKprG/CsWT07Wg==}
+ better-auth@1.7.2:
+ resolution: {integrity: sha512-gKapKBEvYIGcMxi74RjQ7EbFLiqyQt58vdoJmL1qAlWSkY1Bc2Vqshl524/3u1NxauiOU03M/Ebh762Brmac9A==}
peerDependencies:
'@lynx-js/react': '*'
'@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0
@@ -2396,8 +2410,8 @@ packages:
'@tanstack/react-start': ^1.0.0
'@tanstack/solid-start': ^1.0.0
better-sqlite3: ^12.0.0
- drizzle-kit: '>=0.31.4'
- drizzle-orm: ^0.45.2
+ drizzle-kit: '>=0.31.4 || >=1.0.0-beta.1'
+ drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0
mongodb: ^6.0.0 || ^7.0.0
mysql2: ^3.0.0
next: ^14.0.0 || ^15.0.0 || ^16.0.0
@@ -2449,8 +2463,8 @@ packages:
vue:
optional: true
- better-call@1.3.6:
- resolution: {integrity: sha512-no1jI+h6Bkxs1NVBo4rONbVIzsPjZ8IUu7IHaJBiFwVX1XEQGN8KpHots5fSWmXe9nNyLuLIcgx6WEUcE6EDaA==}
+ better-call@1.4.0:
+ resolution: {integrity: sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA==}
peerDependencies:
zod: ^4.0.0
peerDependenciesMeta:
@@ -2999,8 +3013,8 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
- jose@6.1.3:
- resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
+ jose@6.2.10:
+ resolution: {integrity: sha512-iiW7J9qRFlGxvCOIBDBDxFePQSn7ZMAnrYGhrrOo6siO/MIqwfyilLR27pkfDgUk+raLuzADS8A3S/KLBisc0g==}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
@@ -3357,8 +3371,8 @@ packages:
engines: {node: ^18 || >=20}
hasBin: true
- nanostores@1.2.0:
- resolution: {integrity: sha512-F0wCzbsH80G7XXo0Jd9/AVQC7ouWY6idUCTnMwW5t/Rv9W8qmO6endavDwg7TNp5GbugwSukFMVZqzPSrSMndg==}
+ nanostores@1.5.2:
+ resolution: {integrity: sha512-B0UbxzK1s0CN8Xht6r+7iT5+xV8PTaRERR1nATeplRv1Rw5YLWfVAid0hkqY3EceqpG4RjTk8GAwIxQY39Rnwg==}
engines: {node: ^20.0.0 || >=22.0.0}
node-cleanup@2.1.2:
@@ -3741,6 +3755,9 @@ packages:
rou3@0.7.12:
resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==}
+ rou3@0.9.2:
+ resolution: {integrity: sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==}
+
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
@@ -3753,8 +3770,8 @@ packages:
seq-queue@0.0.5:
resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}
- set-cookie-parser@3.1.0:
- resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==}
+ set-cookie-parser@3.1.2:
+ resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
@@ -4260,85 +4277,89 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
- '@better-auth/api-key@1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(better-auth@1.6.16(c397378e994be405f0e2dd7ce3fe42f2))(better-call@1.3.6(zod@4.4.3))':
+ '@better-auth/api-key@1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(better-auth@1.7.2(c397378e994be405f0e2dd7ce3fe42f2))(better-call@1.4.0(zod@4.4.3))':
dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- '@better-auth/utils': 0.4.1
- better-auth: 1.6.16(c397378e994be405f0e2dd7ce3fe42f2)
- better-call: 1.3.6(zod@4.4.3)
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/utils': 0.4.2
+ better-auth: 1.7.2(c397378e994be405f0e2dd7ce3fe42f2)
+ better-call: 1.4.0(zod@4.4.3)
zod: 4.4.3
- '@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)':
+ '@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)':
dependencies:
- '@better-auth/utils': 0.4.1
- '@better-fetch/fetch': 1.2.2
- '@opentelemetry/semantic-conventions': 1.40.0
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+ '@opentelemetry/semantic-conventions': 1.43.0
'@standard-schema/spec': 1.1.0
- better-call: 1.3.6(zod@4.4.3)
- jose: 6.1.3
+ better-call: 1.4.0(zod@4.4.3)
+ jose: 6.2.10
kysely: 0.29.2
- nanostores: 1.2.0
+ nanostores: 1.5.2
zod: 4.4.3
optionalDependencies:
'@opentelemetry/api': 1.9.0
- '@better-auth/drizzle-adapter@1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@electric-sql/pglite@0.4.1)(@opentelemetry/api@1.9.0)(@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2))(kysely@0.29.2)(mysql2@3.15.3)(postgres@3.4.7)(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2)))':
+ '@better-auth/drizzle-adapter@1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@electric-sql/pglite@0.4.1)(@opentelemetry/api@1.9.0)(@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2))(kysely@0.29.2)(mysql2@3.15.3)(postgres@3.4.7)(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2)))':
dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- '@better-auth/utils': 0.4.1
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/utils': 0.4.2
optionalDependencies:
drizzle-orm: 0.45.2(@electric-sql/pglite@0.4.1)(@opentelemetry/api@1.9.0)(@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2))(kysely@0.29.2)(mysql2@3.15.3)(postgres@3.4.7)(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))
- '@better-auth/kysely-adapter@1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(kysely@0.29.2)':
+ '@better-auth/kysely-adapter@1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(kysely@0.29.2)':
dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- '@better-auth/utils': 0.4.1
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/utils': 0.4.2
optionalDependencies:
kysely: 0.29.2
- '@better-auth/memory-adapter@1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)':
+ '@better-auth/memory-adapter@1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)':
dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- '@better-auth/utils': 0.4.1
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/utils': 0.4.2
- '@better-auth/mongo-adapter@1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(mongodb@7.1.1)':
+ '@better-auth/mongo-adapter@1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(mongodb@7.1.1)':
dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- '@better-auth/utils': 0.4.1
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/utils': 0.4.2
optionalDependencies:
mongodb: 7.1.1
- '@better-auth/passkey@1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(better-auth@1.6.16(c397378e994be405f0e2dd7ce3fe42f2))(better-call@1.3.6(zod@4.4.3))(nanostores@1.2.0)':
+ '@better-auth/passkey@1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.7.2(c397378e994be405f0e2dd7ce3fe42f2))(better-call@1.4.0(zod@4.4.3))(nanostores@1.5.2)':
dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- '@better-auth/utils': 0.4.1
- '@better-fetch/fetch': 1.2.2
- '@simplewebauthn/browser': 13.2.2
- '@simplewebauthn/server': 13.3.0
- better-auth: 1.6.16(c397378e994be405f0e2dd7ce3fe42f2)
- better-call: 1.3.6(zod@4.4.3)
- nanostores: 1.2.0
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+ '@simplewebauthn/browser': 13.3.0
+ '@simplewebauthn/server': 13.3.3
+ better-auth: 1.7.2(c397378e994be405f0e2dd7ce3fe42f2)
+ better-call: 1.4.0(zod@4.4.3)
+ nanostores: 1.5.2
zod: 4.4.3
- '@better-auth/prisma-adapter@1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2))(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))':
+ '@better-auth/prisma-adapter@1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2))(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))':
dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- '@better-auth/utils': 0.4.1
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/utils': 0.4.2
optionalDependencies:
'@prisma/client': 7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2)
prisma: 7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2)
- '@better-auth/telemetry@1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)':
+ '@better-auth/telemetry@1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)':
dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- '@better-auth/utils': 0.4.1
- '@better-fetch/fetch': 1.2.2
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
- '@better-auth/utils@0.4.1':
+ '@better-auth/utils@0.4.2':
dependencies:
'@noble/hashes': 2.0.1
- '@better-fetch/fetch@1.2.2': {}
+ '@better-auth/utils@0.5.0':
+ dependencies:
+ '@noble/hashes': 2.0.1
+
+ '@better-fetch/fetch@1.3.1': {}
'@biomejs/biome@2.4.16':
optionalDependencies:
@@ -4375,44 +4396,17 @@ snapshots:
'@biomejs/cli-win32-x64@2.4.16':
optional: true
- '@btst/db@2.2.2(f10adda6bc9f871ce93e674a4e621e1a)':
+ '@btst/db@2.2.3(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(better-auth@1.7.2(c397378e994be405f0e2dd7ce3fe42f2))':
dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- better-auth: 1.6.16(c397378e994be405f0e2dd7ce3fe42f2)
- transitivePeerDependencies:
- - '@better-auth/utils'
- - '@better-fetch/fetch'
- - '@cloudflare/workers-types'
- - '@lynx-js/react'
- - '@opentelemetry/api'
- - '@prisma/client'
- - '@sveltejs/kit'
- - '@tanstack/react-start'
- - '@tanstack/solid-start'
- - better-call
- - better-sqlite3
- - drizzle-kit
- - drizzle-orm
- - jose
- - kysely
- - mongodb
- - mysql2
- - nanostores
- - next
- - pg
- - prisma
- - react
- - react-dom
- - solid-js
- - svelte
- - vitest
- - vue
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/utils': 0.4.2
+ better-auth: 1.7.2(c397378e994be405f0e2dd7ce3fe42f2)
- '@btst/stack@3.0.0-rc.1(e81d6175a2ec14a321d9bc0d949e24c7)':
+ '@btst/stack@3.0.0-rc.3(015e869596a3119c2b2094a599d03cb1)':
dependencies:
'@ai-sdk/react': 3.0.147(react@19.1.0)(zod@4.4.3)
- '@btst/db': 2.2.2(f10adda6bc9f871ce93e674a4e621e1a)
- '@btst/yar': 1.3.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react@19.1.0)
+ '@btst/db': 2.2.3(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(better-auth@1.7.2(c397378e994be405f0e2dd7ce3fe42f2))
+ '@btst/yar': 1.3.2(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react@19.1.0)
'@hookform/resolvers': 5.2.1(react-hook-form@7.56.4(react@19.1.0))
'@milkdown/crepe': 7.20.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.8)(typescript@6.0.2)
'@milkdown/kit': 7.20.0(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.41.0)(typescript@6.0.2)
@@ -4423,7 +4417,7 @@ snapshots:
'@tailwindcss/typography': 0.5.19(tailwindcss@4.1.7)
'@tanstack/react-query': 5.101.0(react@19.1.0)
ai: 6.0.145(zod@4.4.3)
- better-call: 1.3.6(zod@4.4.3)
+ better-call: 1.4.0(zod@4.4.3)
class-variance-authority: 0.7.1
clsx: 2.1.1
cmdk: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -4448,39 +4442,19 @@ snapshots:
tailwindcss: 4.1.7
zod: 4.4.3
transitivePeerDependencies:
+ - '@better-auth/core'
- '@better-auth/utils'
- - '@better-fetch/fetch'
- - '@cloudflare/workers-types'
- '@codemirror/language'
- '@codemirror/state'
- '@codemirror/view'
- - '@lynx-js/react'
- - '@opentelemetry/api'
- - '@prisma/client'
- - '@sveltejs/kit'
- - '@tanstack/react-start'
- - '@tanstack/solid-start'
- - better-sqlite3
- - drizzle-kit
- - drizzle-orm
- - jose
- - kysely
- - mongodb
- - mysql2
- - nanostores
- - pg
- - prisma
+ - better-auth
- prosemirror-model
- prosemirror-state
- prosemirror-view
- - solid-js
- supports-color
- - svelte
- typescript
- - vitest
- - vue
- '@btst/yar@1.3.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react@19.1.0)':
+ '@btst/yar@1.3.2(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react@19.1.0)':
dependencies:
'@types/react': 19.2.16
'@types/react-dom': 19.2.3(@types/react@19.2.16)
@@ -4763,11 +4737,11 @@ snapshots:
style-mod: 4.1.3
w3c-keyname: 2.2.8
- '@daveyplate/better-auth-tanstack@1.3.6(@tanstack/query-core@5.101.0)(@tanstack/react-query@5.101.0(react@19.1.0))(better-auth@1.6.16(c397378e994be405f0e2dd7ce3fe42f2))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ '@daveyplate/better-auth-tanstack@1.3.6(@tanstack/query-core@5.101.0)(@tanstack/react-query@5.101.0(react@19.1.0))(better-auth@1.7.2(c397378e994be405f0e2dd7ce3fe42f2))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@tanstack/query-core': 5.101.0
'@tanstack/react-query': 5.101.0(react@19.1.0)
- better-auth: 1.6.16(c397378e994be405f0e2dd7ce3fe42f2)
+ better-auth: 1.7.2(c397378e994be405f0e2dd7ce3fe42f2)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
@@ -5308,15 +5282,17 @@ snapshots:
'@tybys/wasm-util': 0.10.1
optional: true
- '@noble/ciphers@2.1.1': {}
+ '@noble/ciphers@2.4.0': {}
'@noble/hashes@2.0.1': {}
+ '@noble/hashes@2.4.0': {}
+
'@ocavue/utils@1.6.0': {}
'@opentelemetry/api@1.9.0': {}
- '@opentelemetry/semantic-conventions@1.40.0': {}
+ '@opentelemetry/semantic-conventions@1.43.0': {}
'@oxc-project/types@0.122.0': {}
@@ -6343,9 +6319,9 @@ snapshots:
domhandler: 5.0.3
selderee: 0.11.0
- '@simplewebauthn/browser@13.2.2': {}
+ '@simplewebauthn/browser@13.3.0': {}
- '@simplewebauthn/server@13.3.0':
+ '@simplewebauthn/server@13.3.3':
dependencies:
'@hexagon/base64': 1.1.28
'@levischuck/tiny-cbor': 0.2.11
@@ -6694,24 +6670,24 @@ snapshots:
bail@2.0.2: {}
- better-auth@1.6.16(c397378e994be405f0e2dd7ce3fe42f2):
- dependencies:
- '@better-auth/core': 1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0)
- '@better-auth/drizzle-adapter': 1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@electric-sql/pglite@0.4.1)(@opentelemetry/api@1.9.0)(@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2))(kysely@0.29.2)(mysql2@3.15.3)(postgres@3.4.7)(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2)))
- '@better-auth/kysely-adapter': 1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(kysely@0.29.2)
- '@better-auth/memory-adapter': 1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)
- '@better-auth/mongo-adapter': 1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(mongodb@7.1.1)
- '@better-auth/prisma-adapter': 1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2))(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))
- '@better-auth/telemetry': 1.6.16(@better-auth/core@1.6.16(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)(@opentelemetry/api@1.9.0)(better-call@1.3.6(zod@4.4.3))(jose@6.1.3)(kysely@0.29.2)(nanostores@1.2.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.2.2)
- '@better-auth/utils': 0.4.1
- '@better-fetch/fetch': 1.2.2
- '@noble/ciphers': 2.1.1
- '@noble/hashes': 2.0.1
- better-call: 1.3.6(zod@4.4.3)
+ better-auth@1.7.2(c397378e994be405f0e2dd7ce3fe42f2):
+ dependencies:
+ '@better-auth/core': 1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2)
+ '@better-auth/drizzle-adapter': 1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@electric-sql/pglite@0.4.1)(@opentelemetry/api@1.9.0)(@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2))(kysely@0.29.2)(mysql2@3.15.3)(postgres@3.4.7)(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2)))
+ '@better-auth/kysely-adapter': 1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(kysely@0.29.2)
+ '@better-auth/memory-adapter': 1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)
+ '@better-auth/mongo-adapter': 1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(mongodb@7.1.1)
+ '@better-auth/prisma-adapter': 1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(@prisma/client@7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2))(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))
+ '@better-auth/telemetry': 1.7.2(@better-auth/core@1.7.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.2)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)
+ '@better-auth/utils': 0.4.2
+ '@better-fetch/fetch': 1.3.1
+ '@noble/ciphers': 2.4.0
+ '@noble/hashes': 2.4.0
+ better-call: 1.4.0(zod@4.4.3)
defu: 6.1.4
- jose: 6.1.3
+ jose: 6.2.10
kysely: 0.29.2
- nanostores: 1.2.0
+ nanostores: 1.5.2
zod: 4.4.3
optionalDependencies:
'@prisma/client': 7.6.0(prisma@7.6.0(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@6.0.2))(typescript@6.0.2)
@@ -6727,12 +6703,12 @@ snapshots:
- '@cloudflare/workers-types'
- '@opentelemetry/api'
- better-call@1.3.6(zod@4.4.3):
+ better-call@1.4.0(zod@4.4.3):
dependencies:
- '@better-auth/utils': 0.4.1
- '@better-fetch/fetch': 1.2.2
- rou3: 0.7.12
- set-cookie-parser: 3.1.0
+ '@better-auth/utils': 0.5.0
+ '@better-fetch/fetch': 1.3.1
+ rou3: 0.9.2
+ set-cookie-parser: 3.1.2
optionalDependencies:
zod: 4.4.3
@@ -7273,7 +7249,7 @@ snapshots:
jiti@2.6.1:
optional: true
- jose@6.1.3: {}
+ jose@6.2.10: {}
joycon@3.1.1: {}
@@ -7812,7 +7788,7 @@ snapshots:
nanoid@5.1.5: {}
- nanostores@1.2.0: {}
+ nanostores@1.5.2: {}
node-cleanup@2.1.2: {}
@@ -8322,6 +8298,8 @@ snapshots:
rou3@0.7.12: {}
+ rou3@0.9.2: {}
+
safer-buffer@2.1.2:
optional: true
@@ -8334,7 +8312,7 @@ snapshots:
seq-queue@0.0.5:
optional: true
- set-cookie-parser@3.1.0: {}
+ set-cookie-parser@3.1.2: {}
shebang-command@2.0.0:
dependencies:
diff --git a/src/client.ts b/src/client.ts
index 9ba8fd30f..b763a81a2 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -1,21 +1,18 @@
// Re-export plugins and types
-
-export * from "./lib/better-auth-provider"
export type {
- AccountClientConfig,
AccountPageProps,
+ AccountPluginOptions,
AccountPluginOverrides
} from "./plugins/account-plugin"
export { accountClientPlugin } from "./plugins/account-plugin"
export type {
- AuthClientConfig,
AuthPageProps,
AuthPluginOverrides
} from "./plugins/auth-plugin"
export { authClientPlugin } from "./plugins/auth-plugin"
export type {
- OrganizationClientConfig,
OrganizationPageProps,
+ OrganizationPluginOptions,
OrganizationPluginOverrides
} from "./plugins/organization-plugin"
export { organizationClientPlugin } from "./plugins/organization-plugin"
diff --git a/src/components/auth/provider-button.tsx b/src/components/auth/provider-button.tsx
index bd22d19c9..3caaca82e 100644
--- a/src/components/auth/provider-button.tsx
+++ b/src/components/auth/provider-button.tsx
@@ -74,29 +74,23 @@ export function ProviderButton({
setIsSubmitting(true)
try {
- if (other) {
- const oauth2Params = {
- providerId: provider.provider,
- callbackURL: getCallbackURL(),
- fetchOptions: { throw: true }
- }
+ const socialParams = {
+ provider: provider.provider as SocialProvider,
+ callbackURL: getCallbackURL(),
+ fetchOptions: { throw: true }
+ }
+ if (other) {
if (genericOAuth?.signIn) {
- await genericOAuth.signIn(oauth2Params)
+ await genericOAuth.signIn(socialParams)
setTimeout(() => {
setIsSubmitting(false)
}, 10000)
} else {
- await authClient.signIn.oauth2(oauth2Params)
+ await authClient.signIn.social(socialParams)
}
} else {
- const socialParams = {
- provider: provider.provider as SocialProvider,
- callbackURL: getCallbackURL(),
- fetchOptions: { throw: true }
- }
-
if (social?.signIn) {
await social.signIn(socialParams)
diff --git a/src/components/settings/providers/provider-cell.tsx b/src/components/settings/providers/provider-cell.tsx
index 18d06cc66..c3848c7d2 100644
--- a/src/components/settings/providers/provider-cell.tsx
+++ b/src/components/settings/providers/provider-cell.tsx
@@ -33,7 +33,6 @@ export function ProviderCell({
classNames,
account,
localization,
- other,
provider,
refetch
}: ProviderCellProps) {
@@ -57,19 +56,11 @@ export function ProviderCell({
const callbackURL = `${baseURL}${basePath}/${viewPaths.CALLBACK}?redirectTo=${encodeURIComponent(window.location.pathname)}`
try {
- if (other) {
- await authClient.oauth2.link({
- providerId: provider.provider as SocialProvider,
- callbackURL,
- fetchOptions: { throw: true }
- })
- } else {
- await authClient.linkSocial({
- provider: provider.provider as SocialProvider,
- callbackURL,
- fetchOptions: { throw: true }
- })
- }
+ await authClient.linkSocial({
+ provider: provider.provider as SocialProvider,
+ callbackURL,
+ fetchOptions: { throw: true }
+ })
} catch (error) {
toast({
variant: "error",
@@ -85,12 +76,13 @@ export function ProviderCell({
}
const handleUnlink = async () => {
+ if (!account?.accountId) return
+
setIsLoading(true)
try {
await unlinkAccount({
- accountId: account?.accountId,
- providerId: provider.provider
+ accountId: account.accountId
})
await refetch?.()
diff --git a/src/components/settings/two-factor/two-factor-password-dialog.tsx b/src/components/settings/two-factor/two-factor-password-dialog.tsx
index 421338a5f..1f6d6405b 100644
--- a/src/components/settings/two-factor/two-factor-password-dialog.tsx
+++ b/src/components/settings/two-factor/two-factor-password-dialog.tsx
@@ -75,15 +75,18 @@ export function TwoFactorPasswordDialog({
})
onOpenChange?.(false)
- setBackupCodes(response.backupCodes)
- if (twoFactor?.includes("totp")) {
- setTotpURI(response.totpURI)
- }
+ if ("backupCodes" in response) {
+ setBackupCodes(response.backupCodes)
+
+ if (twoFactor?.includes("totp")) {
+ setTotpURI(response.totpURI)
+ }
- setTimeout(() => {
- setShowBackupCodesDialog(true)
- }, 250)
+ setTimeout(() => {
+ setShowBackupCodesDialog(true)
+ }, 250)
+ }
} catch (error) {
toast({
variant: "error",
diff --git a/src/index.ts b/src/index.ts
index 3341ee5fb..63193a087 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -64,7 +64,6 @@ export * from "./hooks/use-auth-data"
export * from "./hooks/use-authenticate"
export * from "./hooks/use-current-organization"
export * from "./lib/auth-ui-provider"
-export * from "./lib/better-auth-provider"
export * from "./lib/social-providers"
export { getViewByPath } from "./lib/utils"
export * from "./lib/view-paths"
diff --git a/src/lib/__tests__/better-auth-provider.test.ts b/src/lib/__tests__/better-auth-provider.test.ts
deleted file mode 100644
index be1ce29c1..000000000
--- a/src/lib/__tests__/better-auth-provider.test.ts
+++ /dev/null
@@ -1,174 +0,0 @@
-import { describe, expect, it, vi } from "vitest"
-import { createBetterAuthProvider } from "../better-auth-provider"
-import { createBetterAuthServerProvider } from "../better-auth-server-provider"
-
-describe("createBetterAuthProvider", () => {
- it("maps the Better Auth session user to a stack identity", async () => {
- const getSession = vi.fn().mockResolvedValue({
- data: {
- user: {
- id: "user-1",
- name: "Ada",
- email: "ada@example.com",
- image: null,
- role: "admin"
- }
- }
- })
-
- const provider = createBetterAuthProvider({ getSession })
-
- await expect(provider.getIdentity()).resolves.toEqual({
- id: "user-1",
- name: "Ada",
- email: "ada@example.com",
- role: "admin"
- })
- expect(provider.loginPath).toBe("/auth/sign-in")
- })
-
- it("returns null for an unauthenticated session", async () => {
- const provider = createBetterAuthProvider({
- getSession: vi.fn().mockResolvedValue({ data: null })
- })
-
- await expect(provider.getIdentity()).resolves.toBeNull()
- })
-
- it("leaves permissions to Better Auth unless a mapping is configured", () => {
- const provider = createBetterAuthProvider({
- getSession: vi.fn().mockResolvedValue({ data: null })
- })
-
- expect(provider.can).toBeUndefined()
- })
-
- it("maps checks to the Better Auth organization permission endpoint", async () => {
- const hasPermission = vi.fn().mockResolvedValue({
- data: { success: true }
- })
- const provider = createBetterAuthProvider(
- {
- getSession: vi.fn().mockResolvedValue({ data: null }),
- organization: { hasPermission }
- },
- { permissionProvider: "organization" }
- )
-
- await expect(
- provider.can?.({
- resource: "post",
- action: "update",
- params: { organizationId: "org-1" },
- identity: { id: "user-1" }
- })
- ).resolves.toBe(true)
- expect(hasPermission).toHaveBeenCalledWith({
- organizationId: "org-1",
- permissions: { post: ["update"] }
- })
- })
-
- it("maps checks to the Better Auth admin permission endpoint", async () => {
- const hasPermission = vi.fn().mockResolvedValue({ success: false })
- const provider = createBetterAuthProvider(
- {
- getSession: vi.fn().mockResolvedValue({ data: null }),
- admin: { hasPermission }
- },
- { loginPath: "/login", permissionProvider: "admin" }
- )
-
- await expect(
- provider.can?.({
- resource: "user",
- action: "ban",
- params: { organizationId: "ignored-for-admin" },
- identity: { id: "user-1" }
- })
- ).resolves.toBe(false)
- expect(hasPermission).toHaveBeenCalledWith({
- permissions: { user: ["ban"] }
- })
- expect(provider.loginPath).toBe("/login")
- })
-})
-
-describe("createBetterAuthServerProvider", () => {
- it("omits server authorization when no mapping is configured", () => {
- const provider = createBetterAuthServerProvider({
- api: { getSession: vi.fn().mockResolvedValue(null) }
- })
-
- expect(provider.can).toBeUndefined()
- })
-
- it("resolves a request identity from headers once per request", async () => {
- const getSession = vi.fn().mockResolvedValue({
- user: {
- id: "user-1",
- name: "Ada",
- image: "https://example.com/ada.png"
- }
- })
- const provider = createBetterAuthServerProvider({
- api: { getSession }
- })
- const request = new Request("https://example.com/api/data", {
- headers: { cookie: "session=token" }
- })
-
- const first = provider.getIdentity({
- headers: request.headers,
- request
- })
- const second = provider.getIdentity({
- headers: request.headers,
- request
- })
-
- await expect(first).resolves.toEqual({
- id: "user-1",
- name: "Ada",
- image: "https://example.com/ada.png"
- })
- await expect(second).resolves.toEqual({
- id: "user-1",
- name: "Ada",
- image: "https://example.com/ada.png"
- })
- expect(getSession).toHaveBeenCalledTimes(1)
- expect(getSession).toHaveBeenCalledWith({ headers: request.headers })
- })
-
- it("maps server checks to Better Auth organization permissions", async () => {
- const hasPermission = vi.fn().mockResolvedValue({ success: true })
- const provider = createBetterAuthServerProvider(
- {
- api: {
- getSession: vi.fn().mockResolvedValue(null),
- hasPermission
- }
- },
- { permissionProvider: "organization" }
- )
- const headers = new Headers({ cookie: "session=token" })
-
- await expect(
- provider.can?.({
- resource: "member",
- action: "delete",
- params: { organizationId: "org-1" },
- identity: { id: "user-1" },
- headers
- })
- ).resolves.toBe(true)
- expect(hasPermission).toHaveBeenCalledWith({
- headers,
- body: {
- organizationId: "org-1",
- permissions: { member: ["delete"] }
- }
- })
- })
-})
diff --git a/src/lib/__tests__/package-metadata.test.ts b/src/lib/__tests__/package-metadata.test.ts
index 057695871..001822a22 100644
--- a/src/lib/__tests__/package-metadata.test.ts
+++ b/src/lib/__tests__/package-metadata.test.ts
@@ -7,6 +7,7 @@ interface PackageManifest {
dependencies?: Record
devDependencies?: Record
peerDependencies?: Record
+ peerDependenciesMeta?: Record
}
async function readPackageManifest(): Promise {
@@ -14,45 +15,83 @@ async function readPackageManifest(): Promise {
}
describe("package dependency compatibility", () => {
- it("exports the client provider factory from the package root", async () => {
- const entrypoint = await readFile(resolve("src/index.ts"), "utf8")
-
- expect(entrypoint).toContain(
- 'export * from "./lib/better-auth-provider"'
+ it("keeps BTST authorization adapters out of public entries", async () => {
+ const entrypoints = await Promise.all(
+ ["src/index.ts", "src/client.ts", "src/server.ts"].map((path) =>
+ readFile(resolve(path), "utf8")
+ )
)
+
+ for (const entrypoint of entrypoints) {
+ expect(entrypoint).not.toContain("./lib/better-auth-provider")
+ expect(entrypoint).not.toContain(
+ "./lib/better-auth-server-provider"
+ )
+ }
})
/**
- * @see https://github.com/better-stack-ai/better-stack/issues/163
+ * @see https://github.com/better-stack-ai/better-auth-ui/issues/20
*/
it("publishes the BTST v3 RC with one compatible auth dependency set", async () => {
const manifest = await readPackageManifest()
- expect(manifest.version).toBe("2.0.0-rc.1")
+ expect(manifest.version).toBe("2.0.0-rc.3")
expect(manifest.peerDependencies).toMatchObject({
- "@better-auth/api-key": "1.6.16",
- "@better-auth/passkey": "1.6.16",
- "@better-auth/utils": "0.4.1",
- "@better-fetch/fetch": "1.2.2",
- "@btst/stack": "^3.0.0-rc.1",
- "@btst/yar": "^1.3.0",
+ "@better-auth/api-key": "1.7.2",
+ "@better-auth/passkey": "1.7.2",
+ "@better-fetch/fetch": "1.3.1",
+ "@btst/stack": "^3.0.0-rc.3",
+ "@btst/yar": "^1.3.2",
"@tanstack/react-query": ">=5.100.14",
- "better-auth": "1.6.16",
- "better-call": "1.3.6"
+ "better-auth": "1.7.2"
})
expect(manifest.devDependencies).toMatchObject({
- "@better-auth/api-key": "1.6.16",
- "@better-auth/passkey": "1.6.16",
- "@better-auth/utils": "0.4.1",
- "@better-fetch/fetch": "1.2.2",
- "@btst/stack": "3.0.0-rc.1",
- "@btst/yar": "1.3.0",
- "better-auth": "1.6.16",
- "better-call": "1.3.6"
+ "@better-auth/api-key": "1.7.2",
+ "@better-auth/core": "1.7.2",
+ "@better-auth/passkey": "1.7.2",
+ "@better-auth/utils": "0.4.2",
+ "@better-fetch/fetch": "1.3.1",
+ "@btst/stack": "3.0.0-rc.3",
+ "@btst/yar": "1.3.2",
+ "better-auth": "1.7.2",
+ "better-call": "1.4.0"
})
expect(manifest.dependencies).not.toHaveProperty("@better-auth/api-key")
expect(manifest.dependencies).not.toHaveProperty("@better-fetch/fetch")
expect(manifest.dependencies).not.toHaveProperty("better-call")
+ expect(manifest.peerDependencies).not.toHaveProperty(
+ "@better-auth/core"
+ )
+ expect(manifest.peerDependencies).not.toHaveProperty(
+ "@better-auth/utils"
+ )
+ expect(manifest.peerDependencies).not.toHaveProperty("better-call")
+ })
+
+ it("marks adapter-only peers as optional", async () => {
+ const manifest = await readPackageManifest()
+
+ expect(manifest.peerDependenciesMeta).toMatchObject({
+ "@daveyplate/better-auth-tanstack": { optional: true },
+ "@instantdb/react": { optional: true },
+ "@triplit/client": { optional: true },
+ "@triplit/react": { optional: true }
+ })
+ })
+
+ it("keeps optional adapters out of base source entry points", async () => {
+ const entrypoints = await Promise.all(
+ ["src/index.ts", "src/client.ts", "src/server.ts"].map((path) =>
+ readFile(resolve(path), "utf8")
+ )
+ )
+
+ for (const entrypoint of entrypoints) {
+ expect(entrypoint).not.toContain("@daveyplate/better-auth-tanstack")
+ expect(entrypoint).not.toContain("@instantdb/react")
+ expect(entrypoint).not.toContain("@triplit/")
+ }
})
})
@@ -69,6 +108,10 @@ describe("RC publishing", () => {
"{{ github.event.release.tag_name || inputs.release_tag }}"
)
expect(workflow).toContain("npm install -g npm@11.17.0")
+ expect(workflow).toContain("run: pnpm lint")
+ expect(workflow).toContain("run: pnpm typecheck")
+ expect(workflow).toContain("run: pnpm test")
+ expect(workflow).toContain("run: pnpm run build")
expect(workflow).toContain("npm publish --access public --provenance")
expect(workflow).toContain(
'npm view "@btst/better-auth-ui@$PKG_VERSION"'
diff --git a/src/lib/__tests__/plugin-context-bridge.test.tsx b/src/lib/__tests__/plugin-context-bridge.test.tsx
index 23f153e54..5e77d211a 100644
--- a/src/lib/__tests__/plugin-context-bridge.test.tsx
+++ b/src/lib/__tests__/plugin-context-bridge.test.tsx
@@ -17,10 +17,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest"
// ─── mocks ────────────────────────────────────────────────────────────────────
vi.mock("@btst/stack/context", () => ({
- useCan: vi.fn(),
- useIdentity: vi.fn(),
+ joinBasePath: (basePath: string, path: string) =>
+ `${basePath.replace(/\/+$/, "")}${path.startsWith("/") ? path : `/${path}`}`,
useNotify: vi.fn(),
usePluginOverrides: vi.fn(),
+ usePluginSiteNavigation: vi.fn(),
useStack: vi.fn(),
useTranslate: vi.fn()
}))
@@ -57,10 +58,9 @@ vi.mock("../organization-refetcher", () => ({
// ─── imports after mocks ──────────────────────────────────────────────────────
import {
- useCan,
- useIdentity,
useNotify,
usePluginOverrides,
+ usePluginSiteNavigation,
useStack,
useTranslate
} from "@btst/stack/context"
@@ -144,16 +144,35 @@ function renderBridge(
beforeEach(() => {
vi.clearAllMocks()
+ vi.mocked(usePluginSiteNavigation).mockImplementation((_pluginId) => ({
+ Link: "a",
+ navigate: vi.fn(),
+ resolve: (...segments: string[]) => {
+ const path = ["/pages", ...segments].join("/")
+ return { path, href: path, crossOrigin: false }
+ }
+ }))
vi.mocked(useStack).mockReturnValue({
- basePath: "/p",
+ basePath: "/pages",
overrides: {},
- router: {}
- })
- vi.mocked(useCan).mockReturnValue({ can: true, isPending: false })
- vi.mocked(useIdentity).mockReturnValue({
- identity: null,
- isPending: false,
- refetch: vi.fn()
+ router: {},
+ plugins: {
+ auth: {
+ id: "auth",
+ api: { baseURL: "https://example.com", basePath: "/api/data" },
+ site: { baseURL: "https://example.com", basePath: "/pages" }
+ },
+ account: {
+ id: "account",
+ api: { baseURL: "https://example.com", basePath: "/api/data" },
+ site: { baseURL: "https://example.com", basePath: "/pages" }
+ },
+ organization: {
+ id: "organization",
+ api: { baseURL: "https://example.com", basePath: "/api/data" },
+ site: { baseURL: "https://example.com", basePath: "/pages" }
+ }
+ }
})
vi.mocked(useNotify).mockReturnValue({
error: vi.fn(),
@@ -171,23 +190,18 @@ beforeEach(() => {
// ── 1. Field routing ──────────────────────────────────────────────────────────
describe("field routing — auth-specific fields come from authOverrides only", () => {
- it("uses authOverrides.basePath, not accountOverrides.basePath", () => {
+ it("derives the auth base path from the resolved plugin site", () => {
const ctx = renderBridge(
- { basePath: "/auth" },
- { basePath: "/account" },
- { basePath: "/org" }
+ { basePath: "/wrong-auth" },
+ { basePath: "/wrong-account" },
+ { basePath: "/wrong-organization" }
)
- expect(ctx.basePath).toBe("/auth")
+ expect(ctx.basePath).toBe("/pages/auth")
})
- it("strips trailing slash from basePath", () => {
- const ctx = renderBridge({ basePath: "/auth/" })
- expect(ctx.basePath).toBe("/auth")
- })
-
- it("defaults basePath to /auth when not set", () => {
- const ctx = renderBridge()
- expect(ctx.basePath).toBe("/auth")
+ it("uses the resolved site origin for Better Auth callbacks", () => {
+ const ctx = renderBridge({ baseURL: "https://wrong.example.com" })
+ expect(ctx.baseURL).toBe("https://example.com")
})
it("uses authOverrides.redirectTo, not accountOverrides.redirectTo", () => {
@@ -199,10 +213,10 @@ describe("field routing — auth-specific fields come from authOverrides only",
})
it("uses authOverrides.localization, not accountOverrides.localization", () => {
- const customLocalization = { SIGN_IN: "Log in" } as any
+ const customLocalization = { SIGN_IN: "Log in" }
const ctx = renderBridge(
{ localization: customLocalization },
- { localization: { SIGN_IN: "Account sign in" } as any }
+ { localization: { SIGN_IN: "Account sign in" } }
)
// Our localization is merged with defaults, so SIGN_IN should be our override
expect(ctx.localization.SIGN_IN).toBe("Log in")
@@ -210,7 +224,7 @@ describe("field routing — auth-specific fields come from authOverrides only",
it("uses authOverrides feature flags — not org overrides", () => {
const ctx = renderBridge(
- { magicLink: true, passkey: true, twoFactor: ["totp"] as any },
+ { magicLink: true, passkey: true, twoFactor: ["totp"] },
{},
{ magicLink: false, passkey: false } // org tries to override — must be ignored
)
@@ -233,8 +247,8 @@ describe("field routing — auth-specific fields come from authOverrides only",
const captcha = {
provider: "cloudflare-turnstile",
siteKey: "key"
- } as any
- const ctx = renderBridge({ captcha }, { captcha: null as any })
+ }
+ const ctx = renderBridge({ captcha }, { captcha: null })
expect(ctx.captcha).toEqual(captcha)
})
})
@@ -308,19 +322,19 @@ describe("avatar normalization", () => {
})
it("avatar: true → { extension: 'png', size: 128 }", () => {
- const ctx = renderBridge({ avatar: true })
+ const ctx = renderBridge({}, { avatar: true })
expect(ctx.avatar).toMatchObject({ extension: "png", size: 128 })
})
it("avatar with upload → size defaults to 256", () => {
const upload = vi.fn()
- const ctx = renderBridge({ avatar: { upload } })
+ const ctx = renderBridge({}, { avatar: { upload } })
expect(ctx.avatar?.size).toBe(256)
expect(ctx.avatar?.upload).toBe(upload)
})
it("avatar without upload → size defaults to 128", () => {
- const ctx = renderBridge({ avatar: {} })
+ const ctx = renderBridge({}, { avatar: {} })
expect(ctx.avatar?.size).toBe(128)
})
})
@@ -357,12 +371,12 @@ describe("account memo (from accountOverrides)", () => {
expect(ctx.account).toBeUndefined()
})
- it("account: true uses accountOverrides.basePath", () => {
- const ctx = renderBridge({}, { basePath: "/p/account", account: true })
- expect(ctx.account?.basePath).toBe("/p/account")
+ it("account: true derives its base path from the resolved site", () => {
+ const ctx = renderBridge({}, { account: true })
+ expect(ctx.account?.basePath).toBe("/pages/account")
})
- it("account object: accountProp.basePath takes priority over root basePath", () => {
+ it("ignores obsolete root and nested account base paths", () => {
const ctx = renderBridge(
{},
{
@@ -370,11 +384,11 @@ describe("account memo (from accountOverrides)", () => {
account: { basePath: "/specific-account" }
}
)
- expect(ctx.account?.basePath).toBe("/specific-account")
+ expect(ctx.account?.basePath).toBe("/pages/account")
})
it("account: true defaults fields to ['image', 'name']", () => {
- const ctx = renderBridge({}, { account: true, basePath: "/account" })
+ const ctx = renderBridge({}, { account: true })
expect(ctx.account?.fields).toEqual(["image", "name"])
})
@@ -400,16 +414,12 @@ describe("organization memo (from organizationOverrides)", () => {
expect(ctx.organization).toBeUndefined()
})
- it("organization: true uses organizationOverrides.basePath", () => {
- const ctx = renderBridge(
- {},
- {},
- { basePath: "/p/org", organization: true }
- )
- expect(ctx.organization?.basePath).toBe("/p/org")
+ it("organization: true derives its base path from the resolved site", () => {
+ const ctx = renderBridge({}, {}, { organization: true })
+ expect(ctx.organization?.basePath).toBe("/pages/organization")
})
- it("organization.basePath takes priority over root basePath", () => {
+ it("ignores obsolete root and nested organization base paths", () => {
const ctx = renderBridge(
{},
{},
@@ -418,7 +428,7 @@ describe("organization memo (from organizationOverrides)", () => {
organization: { basePath: "/specific-org", customRoles: [] }
}
)
- expect(ctx.organization?.basePath).toBe("/specific-org")
+ expect(ctx.organization?.basePath).toBe("/pages/organization")
})
it("logo: true defaults to { extension: 'png', size: 128 }", () => {
@@ -595,40 +605,31 @@ describe("hooks — useListTeamMembers uses POST (not GET)", () => {
})
})
-describe("BTST v3 top-level providers", () => {
- it("routes organization permission hooks through stack can", () => {
- vi.mocked(useStack).mockReturnValue({
- auth: { can: vi.fn(), getIdentity: vi.fn() },
- basePath: "/p",
- overrides: {},
- router: {}
- })
-
- const context = renderBridge()
- const permission = context.hooks.useHasPermission({
- organizationId: "org-1",
- permissions: { member: ["update"] }
- })
-
- expect(permission.data?.success).toBe(true)
- expect(useCan).toHaveBeenCalledWith({
- resource: "member",
- action: "update",
- params: { organizationId: "org-1" }
- })
- })
-
- it("preserves Better Auth permissions without an explicit stack can mapping", () => {
+describe("BTST v3 UI-service bridge", () => {
+ it("always preserves Better Auth native permission hooks", () => {
const useHasPermission = vi.fn(() => ({
data: { error: null, success: false },
isPending: false,
isRefetching: false
}))
vi.mocked(useStack).mockReturnValue({
- auth: { getIdentity: vi.fn() },
- basePath: "/p",
+ auth: {} as never,
+ basePath: "/pages",
overrides: {},
- router: {}
+ router: {},
+ plugins: {
+ auth: {
+ id: "auth",
+ api: {
+ baseURL: "https://example.com",
+ basePath: "/api/data"
+ },
+ site: {
+ baseURL: "https://example.com",
+ basePath: "/pages"
+ }
+ }
+ }
})
const context = renderBridge({ hooks: { useHasPermission } })
@@ -639,55 +640,110 @@ describe("BTST v3 top-level providers", () => {
expect(permission.data?.success).toBe(false)
expect(useHasPermission).toHaveBeenCalledOnce()
- expect(useCan).not.toHaveBeenCalled()
})
- it("fails closed for permission batches that stack cannot represent", () => {
- vi.mocked(useStack).mockReturnValue({
- auth: { can: vi.fn(), getIdentity: vi.fn() },
- basePath: "/p",
- overrides: {},
- router: {}
- })
-
+ it("uses the default Better Auth permission endpoint when not overridden", () => {
const context = renderBridge()
- const permission = context.hooks.useHasPermission({
+ context.hooks.useHasPermission({
organizationId: "org-1",
- permissions: { member: ["update", "delete"] }
+ permissions: { member: ["update"] }
})
- expect(permission.data?.success).toBe(false)
- expect(permission.isPending).toBe(false)
+ capturedQueryFns[
+ 'hasPermission:{"organizationId":"org-1","permissions":{"member":["update"]}}'
+ ]?.()
+ expect(mockFetch).toHaveBeenCalledWith(
+ "/organization/has-permission",
+ expect.objectContaining({
+ method: "POST",
+ body: {
+ organizationId: "org-1",
+ permissions: { member: ["update"] }
+ }
+ })
+ )
})
- it("sources navigation and refreshes identity from top-level providers", async () => {
+ it("uses the top-level router for same-origin navigation", () => {
const navigate = vi.fn()
- const refresh = vi.fn()
- const refetch = vi.fn()
- const StackLink = () => null
- vi.mocked(useIdentity).mockReturnValue({
- identity: null,
- isPending: false,
- refetch
- })
+ const StackLink = vi.fn((_props: React.ComponentProps<"a">) => null)
vi.mocked(useStack).mockReturnValue({
- basePath: "/p",
+ basePath: "/pages",
overrides: {},
- router: { Link: StackLink, navigate, refresh }
+ router: { Link: StackLink, navigate },
+ plugins: {
+ auth: {
+ id: "auth",
+ api: {
+ baseURL: "https://example.com",
+ basePath: "/api/data"
+ },
+ site: {
+ baseURL: "https://example.com",
+ basePath: "/pages"
+ }
+ }
+ }
})
- const context = renderBridge({
- Link: () => null,
- navigate: vi.fn(),
- onSessionChange: vi.fn()
- })
+ const context = renderBridge()
context.navigate("/account")
- await context.onSessionChange?.()
- expect(context.Link).toBe(StackLink)
expect(navigate).toHaveBeenCalledWith("/account")
- expect(refetch).toHaveBeenCalledOnce()
- expect(refresh).toHaveBeenCalledOnce()
+
+ render(Account)
+ expect(StackLink.mock.calls[0]?.[0]).toEqual(
+ expect.objectContaining({ href: "/account" })
+ )
+ })
+
+ it("uses absolute plugin site locations for cross-origin links", () => {
+ vi.mocked(usePluginSiteNavigation).mockImplementation((pluginId) => ({
+ Link: "a",
+ navigate: vi.fn(),
+ resolve: (...segments: string[]) => {
+ const path = ["/pages", ...segments].join("/")
+ const crossOrigin = pluginId === "account"
+ return {
+ path,
+ href: crossOrigin
+ ? `https://accounts.example.com${path}`
+ : path,
+ crossOrigin
+ }
+ }
+ }))
+
+ const context = renderBridge({}, { account: true })
+
+ expect(context.account?.basePath).toBe(
+ "https://accounts.example.com/pages/account"
+ )
+
+ const { getByRole } = render(
+
+ Account settings
+
+ )
+ expect(getByRole("link")).toHaveAttribute(
+ "href",
+ "https://accounts.example.com/pages/account/settings"
+ )
+ })
+
+ it("passes through the explicit session-change callback exactly once", async () => {
+ const onSessionChange = vi.fn()
+ const context = renderBridge({ onSessionChange })
+
+ await context.onSessionChange?.()
+
+ expect(onSessionChange).toHaveBeenCalledOnce()
+ })
+
+ it("has no hidden session synchronization without a callback", () => {
+ const context = renderBridge()
+
+ expect(context.onSessionChange).toBeUndefined()
})
it("routes toast rendering through the top-level notify provider", () => {
diff --git a/src/lib/better-auth-provider-shared.ts b/src/lib/better-auth-provider-shared.ts
deleted file mode 100644
index b90364a04..000000000
--- a/src/lib/better-auth-provider-shared.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import type { CanParams, StackIdentity } from "@btst/stack/context"
-
-export type BetterAuthPermissionProvider = "admin" | "organization"
-
-export interface BetterAuthUser {
- id: string
- name?: string | null
- email?: string | null
- image?: string | null
- [key: string]: unknown
-}
-
-export type BetterAuthPermissionResult =
- | boolean
- | {
- data?: { success?: boolean } | null
- success?: boolean
- }
-
-export function toStackIdentity(
- user: BetterAuthUser | null | undefined
-): StackIdentity | null {
- if (!user) return null
-
- const { id, name, email, image, ...fields } = user
-
- return {
- ...fields,
- id,
- ...(typeof name === "string" ? { name } : {}),
- ...(typeof email === "string" ? { email } : {}),
- ...(typeof image === "string" ? { image } : {})
- }
-}
-
-export function getPermissionBody(
- { resource, action, params }: CanParams,
- includeOrganizationId = true
-): {
- organizationId?: string
- permissions: Record
-} {
- const organizationId = params?.organizationId
-
- return {
- ...(includeOrganizationId && typeof organizationId === "string"
- ? { organizationId }
- : {}),
- permissions: { [resource]: [action] }
- }
-}
-
-export function getPermissionDecision(
- result: BetterAuthPermissionResult
-): boolean {
- if (typeof result === "boolean") return result
- return result.data?.success ?? result.success ?? false
-}
diff --git a/src/lib/better-auth-provider.ts b/src/lib/better-auth-provider.ts
deleted file mode 100644
index 57532c27a..000000000
--- a/src/lib/better-auth-provider.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { StackAuthProvider } from "@btst/stack/context"
-import {
- type BetterAuthPermissionProvider,
- type BetterAuthPermissionResult,
- type BetterAuthUser,
- getPermissionBody,
- getPermissionDecision,
- toStackIdentity
-} from "./better-auth-provider-shared"
-
-type MaybePromise = Promise | T
-
-interface BetterAuthClientSessionResult {
- data?: { user: BetterAuthUser } | null
-}
-
-interface BetterAuthClientPermissionApi {
- hasPermission: (
- input: ReturnType
- ) => MaybePromise
-}
-
-export interface BetterAuthStackClient {
- getSession: () => MaybePromise
- admin?: BetterAuthClientPermissionApi
- organization?: BetterAuthClientPermissionApi
-}
-
-export interface BetterAuthProviderOptions {
- /** Path used by BTST when an unauthenticated user reaches a gated route. */
- loginPath?: string
- /**
- * Better Auth permission plugin to use for BTST resource/action checks.
- * Leave unset when the client has no permission plugin configured.
- */
- permissionProvider?: BetterAuthPermissionProvider
- /** Override the default Better Auth permission mapping. */
- can?: NonNullable
-}
-
-/**
- * Adapt a Better Auth client to the auth contract consumed by StackProvider.
- */
-export function createBetterAuthProvider(
- authClient: BetterAuthStackClient,
- options: BetterAuthProviderOptions = {}
-): StackAuthProvider {
- const permissionProvider = options.permissionProvider
- const can =
- options.can ??
- (permissionProvider
- ? async (
- params: Parameters>[0]
- ) => {
- if (!params.identity) return false
-
- const permissionApi = authClient[permissionProvider]
- if (!permissionApi?.hasPermission) return false
-
- const result = await permissionApi.hasPermission(
- getPermissionBody(
- params,
- permissionProvider === "organization"
- )
- )
- return getPermissionDecision(result)
- }
- : undefined)
-
- return {
- getIdentity: async () => {
- const session = await authClient.getSession()
- return toStackIdentity(session.data?.user)
- },
- ...(can ? { can } : {}),
- loginPath: options.loginPath ?? "/auth/sign-in"
- }
-}
diff --git a/src/lib/better-auth-server-provider.ts b/src/lib/better-auth-server-provider.ts
deleted file mode 100644
index 9ce327a59..000000000
--- a/src/lib/better-auth-server-provider.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import type { StackServerAuthProvider } from "@btst/stack/api"
-import {
- type BetterAuthPermissionProvider,
- type BetterAuthPermissionResult,
- type BetterAuthUser,
- getPermissionBody,
- getPermissionDecision,
- toStackIdentity
-} from "./better-auth-provider-shared"
-
-type MaybePromise = Promise | T
-
-type BetterAuthServerSession =
- | { data?: { user: BetterAuthUser } | null }
- | { user: BetterAuthUser }
- | null
-
-interface BetterAuthServerApi {
- getSession: (input: {
- headers: Headers
- }) => MaybePromise
- hasPermission?: (input: {
- headers: Headers
- body: ReturnType
- }) => MaybePromise
- userHasPermission?: (input: {
- headers: Headers
- body: ReturnType
- }) => MaybePromise
-}
-
-export interface BetterAuthStackServer {
- api: BetterAuthServerApi
-}
-
-export interface BetterAuthServerProviderOptions {
- /** Better Auth permission plugin used by server-side BTST checks. */
- permissionProvider?: BetterAuthPermissionProvider
- /** Override the default Better Auth permission mapping. */
- can?: NonNullable
-}
-
-function getSessionUser(
- session: BetterAuthServerSession
-): BetterAuthUser | null {
- if (!session) return null
- if ("user" in session) return session.user
- return session.data?.user ?? null
-}
-
-/**
- * Adapt a Better Auth server instance to the auth contract consumed by stack().
- */
-export function createBetterAuthServerProvider(
- auth: BetterAuthStackServer,
- options: BetterAuthServerProviderOptions = {}
-): StackServerAuthProvider {
- const identities = new WeakMap<
- Request,
- Promise>
- >()
- const permissionProvider = options.permissionProvider
- const can =
- options.can ??
- (permissionProvider
- ? async (
- params: Parameters<
- NonNullable
- >[0]
- ) => {
- if (!params.identity) return false
-
- const permissionMethod =
- permissionProvider === "organization"
- ? auth.api.hasPermission
- : auth.api.userHasPermission
- if (!permissionMethod) return false
-
- const result = await permissionMethod({
- headers: params.headers,
- body: getPermissionBody(
- params,
- permissionProvider === "organization"
- )
- })
- return getPermissionDecision(result)
- }
- : undefined)
-
- return {
- getIdentity: ({ headers, request }) => {
- let identity = identities.get(request)
-
- if (!identity) {
- identity = Promise.resolve(
- auth.api.getSession({ headers })
- ).then((session) => toStackIdentity(getSessionUser(session)))
- identities.set(request, identity)
- }
- return identity
- },
- ...(can ? { can } : {})
- }
-}
diff --git a/src/lib/instantdb/use-list-accounts.ts b/src/lib/instantdb/use-list-accounts.ts
index 66a50a134..7c1cb0ff2 100644
--- a/src/lib/instantdb/use-list-accounts.ts
+++ b/src/lib/instantdb/use-list-accounts.ts
@@ -33,6 +33,7 @@ export function useListAccounts({
return {
data: accounts,
isPending: !accounts && (isPending || authLoading || isLoading),
+ isRefetching: false,
error: (error as BetterFetchError) || null
}
}
diff --git a/src/lib/instantdb/use-list-sessions.ts b/src/lib/instantdb/use-list-sessions.ts
index 10da47500..0de3251c0 100644
--- a/src/lib/instantdb/use-list-sessions.ts
+++ b/src/lib/instantdb/use-list-sessions.ts
@@ -48,6 +48,7 @@ export function useListSessions({
return {
data: sessions,
- isPending: !sessions && (isPending || authLoading || isLoading)
+ isPending: !sessions && (isPending || authLoading || isLoading),
+ isRefetching: false
}
}
diff --git a/src/lib/instantdb/use-session.ts b/src/lib/instantdb/use-session.ts
index 89ec9a202..359a53e61 100644
--- a/src/lib/instantdb/use-session.ts
+++ b/src/lib/instantdb/use-session.ts
@@ -49,7 +49,10 @@ export function useSession({
}
: null,
isPending,
- refetch: refetch || (() => {}),
+ isRefetching: false,
+ refetch: async () => {
+ await refetch?.()
+ },
error: (error as BetterFetchError) || null
}
}
diff --git a/src/lib/plugin-context-bridge.tsx b/src/lib/plugin-context-bridge.tsx
index c2891890a..b7dbe6910 100644
--- a/src/lib/plugin-context-bridge.tsx
+++ b/src/lib/plugin-context-bridge.tsx
@@ -1,20 +1,16 @@
"use client"
import {
- useCan,
- useIdentity,
useNotify,
usePluginOverrides,
+ usePluginSiteNavigation,
useStack,
useTranslate
} from "@btst/stack/context"
import { type ReactNode, useMemo } from "react"
import { RecaptchaV3 } from "../components/captcha/recaptcha-v3"
import { useAuthData } from "../hooks/use-auth-data"
-import {
- type AuthLocalization,
- authLocalization
-} from "../localization/auth-localization"
+import { authLocalization } from "../localization/auth-localization"
import type { AccountPluginOverrides } from "../plugins/account-plugin"
import type { AuthPluginOverrides } from "../plugins/auth-plugin"
import type { OrganizationPluginOverrides } from "../plugins/organization-plugin"
@@ -54,35 +50,6 @@ const defaultReplace = (href: string) => {
window.location.replace(href)
}
-function useStackHasPermission(
- params: Parameters[0]
-): ReturnType {
- const permissions = (
- "permissions" in params ? params.permissions : params.permission
- ) as Record
- const permissionEntries = Object.entries(permissions)
- const [resource, actions] = permissionEntries[0] ?? []
- const action = actions?.[0]
- const isSinglePermission =
- permissionEntries.length === 1 && actions?.length === 1
- const organizationId =
- "organizationId" in params ? params.organizationId : undefined
- const { can, isPending } = useCan({
- resource: resource ?? "",
- action: action ?? "",
- params: organizationId ? { organizationId } : undefined
- })
-
- return {
- data: {
- error: null,
- success: Boolean(isSinglePermission && resource && action && can)
- },
- isPending: isSinglePermission && isPending,
- isRefetching: isSinglePermission && isPending
- }
-}
-
/**
* Bridge component that converts btst plugin overrides to AuthUIContext
* This allows existing components to continue using AuthUIContext
@@ -93,10 +60,12 @@ export function BetterAuthPluginProvider({
}: {
children: ReactNode
}) {
- const { auth, router } = useStack()
- const { refetch: refetchIdentity } = useIdentity()
+ const { plugins, router } = useStack()
const notify = useNotify()
const translate = useTranslate()
+ const authNavigation = usePluginSiteNavigation("auth")
+ const accountNavigation = usePluginSiteNavigation("account")
+ const organizationNavigation = usePluginSiteNavigation("organization")
// Read auth plugin overrides
const authOverrides = usePluginOverrides<
@@ -104,7 +73,6 @@ export function BetterAuthPluginProvider({
Partial
>("auth", {
localization: authLocalization,
- basePath: "/auth",
redirectTo: "/",
freshAge: 60 * 60 * 24,
changeEmail: true,
@@ -124,11 +92,18 @@ export function BetterAuthPluginProvider({
>("organization", {})
const authClient = authOverrides.authClient as AuthClient
+ const authSite = plugins?.auth?.site
+ const authLocation = authNavigation.resolve("auth")
+ const accountLocation = accountNavigation.resolve("account")
+ const organizationLocation = organizationNavigation.resolve("organization")
+ const authBasePath = authLocation.href
+ const accountBasePath = accountLocation.href
+ const organizationBasePath = organizationLocation.href
const avatar = useMemo(() => {
- if (!authOverrides.avatar) return
+ if (!accountOverrides.avatar) return
- if (authOverrides.avatar === true) {
+ if (accountOverrides.avatar === true) {
return {
extension: "png",
size: 128
@@ -136,50 +111,34 @@ export function BetterAuthPluginProvider({
}
return {
- upload: authOverrides.avatar.upload,
- delete: authOverrides.avatar.delete,
- extension: authOverrides.avatar.extension || "png",
+ upload: accountOverrides.avatar.upload,
+ delete: accountOverrides.avatar.delete,
+ extension: accountOverrides.avatar.extension || "png",
size:
- authOverrides.avatar.size ||
- (authOverrides.avatar.upload ? 256 : 128),
- Image: authOverrides.avatar.Image
+ accountOverrides.avatar.size ||
+ (accountOverrides.avatar.upload ? 256 : 128),
+ Image: accountOverrides.avatar.Image
}
- }, [authOverrides.avatar])
+ }, [accountOverrides.avatar])
const account = useMemo(() => {
const accountProp = accountOverrides?.account
if (!accountProp) return undefined
if (accountProp === true) {
- // Use basePath from accountOverrides root, or default to "/account"
- const basePathRaw = accountOverrides?.basePath ?? "/account"
- const basePath = basePathRaw.endsWith("/")
- ? basePathRaw.slice(0, -1)
- : basePathRaw
-
return {
- basePath,
+ basePath: accountBasePath,
fields: ["image", "name"],
viewPaths: accountViewPaths
}
}
- // basePath can come from either:
- // 1. accountProp.basePath (inside the account config object)
- // 2. accountOverrides.basePath (at the root, from Partial)
- // Priority: accountProp.basePath > accountOverrides.basePath > "/account"
- const basePathRaw =
- accountProp.basePath ?? accountOverrides?.basePath ?? "/account"
- const basePath = basePathRaw.endsWith("/")
- ? basePathRaw.slice(0, -1)
- : basePathRaw
-
return {
- basePath,
+ basePath: accountBasePath,
fields: accountProp.fields || ["image", "name"],
viewPaths: { ...accountViewPaths, ...accountProp.viewPaths }
}
- }, [accountOverrides?.account, accountOverrides?.basePath])
+ }, [accountBasePath, accountOverrides.account])
const deleteUser = useMemo(() => {
if (!accountOverrides?.deleteUser) return
@@ -239,15 +198,8 @@ export function BetterAuthPluginProvider({
if (!organizationProp) return undefined
if (organizationProp === true) {
- // Use basePath from organizationOverrides root, or default to "/organization"
- const basePathRaw =
- organizationOverrides?.basePath ?? "/organization"
- const basePath = basePathRaw.endsWith("/")
- ? basePathRaw.slice(0, -1)
- : basePathRaw
-
return {
- basePath,
+ basePath: organizationBasePath,
viewPaths: organizationViewPaths,
customRoles: []
}
@@ -271,29 +223,17 @@ export function BetterAuthPluginProvider({
}
}
- // basePath can come from either:
- // 1. organizationProp.basePath (inside the organization config object)
- // 2. organizationOverrides.basePath (at the root, from Partial)
- // Priority: organizationProp.basePath > organizationOverrides.basePath > "/organization"
- const basePathRaw =
- organizationProp.basePath ??
- organizationOverrides?.basePath ??
- "/organization"
- const basePath = basePathRaw.endsWith("/")
- ? basePathRaw.slice(0, -1)
- : basePathRaw
-
return {
...organizationProp,
logo,
- basePath,
+ basePath: organizationBasePath,
customRoles: organizationProp.customRoles || [],
viewPaths: {
...organizationViewPaths,
...organizationProp.viewPaths
}
}
- }, [organizationOverrides?.organization, organizationOverrides?.basePath])
+ }, [organizationBasePath, organizationOverrides.organization])
const teams = useMemo(() => {
const teamsProp =
@@ -482,7 +422,7 @@ export function BetterAuthPluginProvider({
key,
translate(`better-auth-ui.${key}`, defaultValue)
])
- ) as AuthLocalization
+ ) as typeof authLocalization
}, [authOverrides.localization, translate])
const renderToast = useMemo(() => createRenderToast(notify), [notify])
@@ -490,39 +430,40 @@ export function BetterAuthPluginProvider({
const hooks = useMemo(() => {
return {
...defaultHooks,
- ...authOverrides.hooks,
- ...(auth?.can ? { useHasPermission: useStackHasPermission } : {})
+ ...authOverrides.hooks
}
- }, [auth, defaultHooks, authOverrides.hooks])
-
- const onSessionChange = useMemo(
- () => async () => {
- await refetchIdentity()
- await router?.refresh?.()
- },
- [refetchIdentity, router?.refresh]
- )
+ }, [defaultHooks, authOverrides.hooks])
const mutators = useMemo(() => {
return { ...defaultMutators, ...authOverrides.mutators }
}, [defaultMutators, authOverrides.mutators])
- // Remove trailing slash from baseURL — use auth-specific value only
- const baseURL = authOverrides.baseURL
- ? authOverrides.baseURL.endsWith("/")
- ? authOverrides.baseURL.slice(0, -1)
- : authOverrides.baseURL
- : ""
-
- // Remove trailing slash from basePath — use auth-specific value only.
- // accountOverrides/organizationOverrides inherit basePath from AuthPluginOverrides
- // but their basePath refers to their own route prefix (e.g. "/account"), not the
- // auth prefix. Spreading them would corrupt auth navigation links.
- const basePath = authOverrides.basePath
- ? authOverrides.basePath.endsWith("/")
- ? authOverrides.basePath.slice(0, -1)
- : authOverrides.basePath
- : "/auth"
+ const baseURL = authLocation.crossOrigin
+ ? ""
+ : (authSite?.baseURL.replace(/\/+$/, "") ?? "")
+
+ const navigate = (href: string) => {
+ if (/^https?:\/\//.test(href)) return defaultNavigate(href)
+ if (router?.navigate) return router.navigate(href)
+ return defaultNavigate(href)
+ }
+
+ const replace = (href: string) => {
+ if (/^https?:\/\//.test(href)) return defaultReplace(href)
+ if (router?.navigate) return router.navigate(href)
+ return defaultReplace(href)
+ }
+
+ const LinkComponent: Link = (props) => {
+ if (/^https?:\/\//.test(props.href)) return
+
+ const RouterLink = router?.Link as typeof DefaultLink | undefined
+ return RouterLink ? (
+
+ ) : (
+
+ )
+ }
const emailVerification = useMemo(() => {
const ev = authOverrides.emailVerification
@@ -536,7 +477,7 @@ export function BetterAuthPluginProvider({
const contextValue: AuthUIContextType = {
authClient,
avatar,
- basePath: basePath === "/" ? "" : basePath,
+ basePath: authBasePath,
baseURL,
// Auth-specific feature flags — always read from authOverrides, never from the
// merged blob, so account/org overrides can't silently overwrite them.
@@ -557,10 +498,10 @@ export function BetterAuthPluginProvider({
signUp,
social,
toast: renderToast,
- navigate: router?.navigate || defaultNavigate,
- replace: router?.navigate || defaultReplace,
+ navigate,
+ replace,
viewPaths,
- Link: (router?.Link as typeof DefaultLink | undefined) || DefaultLink,
+ Link: LinkComponent,
apiKey: authOverrides.apiKey,
gravatar: authOverrides.gravatar,
additionalFields: authOverrides.additionalFields,
@@ -574,7 +515,7 @@ export function BetterAuthPluginProvider({
localizeErrors: authOverrides.localizeErrors ?? true,
persistClient: authOverrides.persistClient,
optimistic: authOverrides.optimistic,
- onSessionChange
+ onSessionChange: authOverrides.onSessionChange
}
return (
diff --git a/src/lib/tanstack/use-tanstack-options.ts b/src/lib/tanstack/use-tanstack-options.ts
index b5e7f3627..2d3011cd8 100644
--- a/src/lib/tanstack/use-tanstack-options.ts
+++ b/src/lib/tanstack/use-tanstack-options.ts
@@ -33,7 +33,7 @@ export function useTanstackOptions({
const hooks = useMemo(
() => ({
- ...(createAuthHooks(authClient) as Partial),
+ ...(createAuthHooks(authClient) as unknown as Partial),
useIsRestoring
}),
[authClient]
diff --git a/src/lib/triplit/use-list-accounts.ts b/src/lib/triplit/use-list-accounts.ts
index 7369494fa..98f9a5153 100644
--- a/src/lib/triplit/use-list-accounts.ts
+++ b/src/lib/triplit/use-list-accounts.ts
@@ -26,6 +26,7 @@ export function useListAccounts({
return {
data: results,
isPending: isPending || fetching,
+ isRefetching: false,
error
}
}
diff --git a/src/lib/triplit/use-list-sessions.ts b/src/lib/triplit/use-list-sessions.ts
index 962e2d8e3..61e5231ba 100644
--- a/src/lib/triplit/use-list-sessions.ts
+++ b/src/lib/triplit/use-list-sessions.ts
@@ -28,6 +28,7 @@ export function useListSessions({
return {
data: sessions as Session[] | undefined,
isPending: isPending || fetching,
+ isRefetching: false,
error
}
}
diff --git a/src/lib/triplit/use-session.ts b/src/lib/triplit/use-session.ts
index 2a5ed493d..36d1cae4c 100644
--- a/src/lib/triplit/use-session.ts
+++ b/src/lib/triplit/use-session.ts
@@ -37,6 +37,9 @@ export function useSession({
: null,
error,
isPending: isPending,
- refetch: refetch || (() => {})
+ isRefetching: false,
+ refetch: async () => {
+ await refetch?.()
+ }
}
}
diff --git a/src/plugins/__tests__/plugin-overrides.typecheck.tsx b/src/plugins/__tests__/plugin-overrides.typecheck.tsx
new file mode 100644
index 000000000..6e8e2104d
--- /dev/null
+++ b/src/plugins/__tests__/plugin-overrides.typecheck.tsx
@@ -0,0 +1,158 @@
+import { createClientStack } from "@btst/stack/client"
+import { StackProvider } from "@btst/stack/context"
+import { QueryClient } from "@tanstack/react-query"
+import type { AnyAuthClient } from "../../types/any-auth-client"
+import {
+ type AccountPluginOverrides,
+ accountClientPlugin
+} from "../account-plugin"
+import { type AuthPluginOverrides, authClientPlugin } from "../auth-plugin"
+import {
+ type OrganizationPluginOverrides,
+ organizationClientPlugin
+} from "../organization-plugin"
+
+const authClient = null as unknown as AnyAuthClient
+
+const stack = createClientStack({
+ api: {
+ baseURL: "https://example.com",
+ basePath: "/api/data",
+ headers: { authorization: "Bearer server-only" }
+ },
+ site: {
+ baseURL: "https://example.com",
+ basePath: "/pages"
+ },
+ queryClient: new QueryClient() as never,
+ plugins: {
+ auth: authClientPlugin(),
+ account: accountClientPlugin(),
+ organization: organizationClientPlugin()
+ }
+})
+
+// @ts-expect-error Request headers are not exposed through the browser provider runtime.
+stack.provider.api.headers
+
+; undefined,
+ pageProps: {
+ signIn: { redirectTo: "/pages/account/settings" }
+ }
+ },
+ account: {
+ account: true,
+ avatar: {
+ extension: "png",
+ size: 128
+ },
+ pageProps: {
+ accountSettings: { className: "account-settings" }
+ }
+ },
+ organization: {
+ organization: true,
+ pageProps: {
+ organizationMembers: { className: "organization-members" }
+ }
+ }
+ }}
+/>
+
+;
+
+const invalidAuth: AuthPluginOverrides = {
+ authClient,
+ // @ts-expect-error Avatar configuration belongs to account overrides.
+ avatar: true
+}
+
+const invalidAccountAuthClient: AccountPluginOverrides = {
+ // @ts-expect-error The Better Auth client is configured once under auth.
+ authClient
+}
+
+const invalidAccountCredentials: AccountPluginOverrides = {
+ // @ts-expect-error Auth-only credentials are rejected under account.
+ credentials: true
+}
+
+const invalidAccountBasePath: AccountPluginOverrides = {
+ // @ts-expect-error Route base paths come from the resolved client stack.
+ basePath: "/account"
+}
+
+const invalidNestedAccountBasePath: AccountPluginOverrides = {
+ account: {
+ // @ts-expect-error Nested route base paths are also stack-owned.
+ basePath: "/account"
+ }
+}
+
+const invalidAccountPage: AccountPluginOverrides = {
+ pageProps: {
+ // @ts-expect-error Auth page props are rejected under account.
+ signIn: {}
+ }
+}
+
+const invalidOrganizationAuthClient: OrganizationPluginOverrides = {
+ // @ts-expect-error The Better Auth client is configured once under auth.
+ authClient
+}
+
+const invalidOrganizationHooks: OrganizationPluginOverrides = {
+ // @ts-expect-error Auth hooks are rejected under organization.
+ hooks: {}
+}
+
+const invalidOrganizationBasePath: OrganizationPluginOverrides = {
+ // @ts-expect-error Route base paths come from the resolved client stack.
+ basePath: "/organization"
+}
+
+const invalidNestedOrganizationBasePath: OrganizationPluginOverrides = {
+ organization: {
+ // @ts-expect-error Nested route base paths are also stack-owned.
+ basePath: "/organization"
+ }
+}
+
+const invalidOrganizationPage: OrganizationPluginOverrides = {
+ pageProps: {
+ // @ts-expect-error Account page props are rejected under organization.
+ accountSettings: {}
+ }
+}
+
+// @ts-expect-error Shared site runtime belongs to createClientStack.
+authClientPlugin({
+ siteBaseURL: "https://example.com",
+ siteBasePath: "/pages"
+})
+
+void invalidAuth
+void invalidAccountAuthClient
+void invalidAccountCredentials
+void invalidAccountBasePath
+void invalidNestedAccountBasePath
+void invalidAccountPage
+void invalidOrganizationAuthClient
+void invalidOrganizationHooks
+void invalidOrganizationBasePath
+void invalidNestedOrganizationBasePath
+void invalidOrganizationPage
diff --git a/src/plugins/__tests__/plugin-routes.test.ts b/src/plugins/__tests__/plugin-routes.test.ts
index b3b13196a..209090c53 100644
--- a/src/plugins/__tests__/plugin-routes.test.ts
+++ b/src/plugins/__tests__/plugin-routes.test.ts
@@ -1,3 +1,5 @@
+import { createClientStack } from "@btst/stack/client"
+import { QueryClient } from "@tanstack/react-query"
import { describe, expect, it } from "vitest"
import { accountClientPlugin } from "../account-plugin"
import { authClientPlugin } from "../auth-plugin"
@@ -12,24 +14,59 @@ interface DeclarativeRoute {
}
}
-const config = {
- siteBaseURL: "https://example.com",
- siteBasePath: "/app"
-}
+const createStack = (includeOrganization = true) =>
+ createClientStack({
+ api: {
+ baseURL: "https://example.com",
+ basePath: "/api/data"
+ },
+ site: {
+ baseURL: "https://example.com",
+ basePath: "/pages"
+ },
+ queryClient: new QueryClient() as never,
+ plugins: {
+ auth: authClientPlugin(),
+ account: accountClientPlugin(),
+ ...(includeOrganization
+ ? { organization: organizationClientPlugin() }
+ : {})
+ }
+ })
describe("BTST v3 client plugin routes", () => {
- it.each([
- ["auth", authClientPlugin(config)],
- ["account", accountClientPlugin(config)],
- ["organization", organizationClientPlugin(config)]
- ])("registers every %s page as a declarative route", (_, plugin) => {
- const routes = Object.values(plugin.routes()) as DeclarativeRoute[]
-
- expect(routes.length).toBeGreaterThan(0)
-
- for (const route of routes) {
- expect(route.def?.page).toBeDefined()
- expect(route().PageComponent).toBeDefined()
+ it("resolves every page definition against the shared stack runtime", () => {
+ const stack = createStack()
+
+ for (const plugin of Object.values(stack.context.plugins)) {
+ const routes = Object.values(
+ plugin.routes(stack.context)
+ ) as DeclarativeRoute[]
+
+ expect(routes.length).toBeGreaterThan(0)
+
+ for (const route of routes) {
+ expect(route.def?.page).toBeDefined()
+ expect(route().PageComponent).toBeDefined()
+ }
}
})
+
+ it("supports auth and account without the organization plugin", () => {
+ const stack = createStack(false)
+
+ expect(Object.keys(stack.context.plugins)).toEqual(["auth", "account"])
+ })
+
+ it("uses the resolved site runtime for sitemap URLs", async () => {
+ const stack = createStack(false)
+ const sitemap = await stack.generateSitemap()
+
+ expect(sitemap).not.toHaveLength(0)
+ expect(
+ sitemap.every(({ url }) =>
+ url.startsWith("https://example.com/pages/auth/")
+ )
+ ).toBe(true)
+ })
})
diff --git a/src/plugins/account-plugin.ts b/src/plugins/account-plugin.ts
index 45db0a75c..68f643c25 100644
--- a/src/plugins/account-plugin.ts
+++ b/src/plugins/account-plugin.ts
@@ -1,7 +1,6 @@
import {
- type ClientPlugin,
defineClientPlugin,
- type Route
+ type ResolvedClientPluginRuntime
} from "@btst/stack/plugins/client"
import { defineRoute, defineRoutes } from "@btst/yar"
import { lazy } from "react"
@@ -9,9 +8,9 @@ import type { AccountViewProps } from "../components/account/account-view"
import { accountViewPaths } from "../lib/view-paths"
import type { AuthLocalization } from "../localization/auth-localization"
import type { AccountOptions } from "../types/account-options"
+import type { AvatarOptions } from "../types/avatar-options"
import type { DeleteUserOptions } from "../types/delete-user-options"
import type { TeamOptions } from "../types/team-options"
-import type { AuthPluginOverrides } from "./auth-plugin"
/**
* Per-page customization props for account views.
@@ -24,27 +23,22 @@ export type AccountPageProps = Omit<
localization?: Partial
}
-/**
- * Configuration for account client plugin
- */
-export interface AccountClientConfig {
- siteBaseURL: string
- siteBasePath: string
-
- // Optional context to pass to loaders (for SSR)
- context?: Record
-}
+export type AccountPluginOptions = Omit, "basePath">
/**
* Plugin override interface for account plugin
- * Extends AuthPluginOverrides with account-specific options
+ * Contains only account-specific options consumed by the bridge.
*/
-export interface AccountPluginOverrides extends Partial {
+export interface AccountPluginOverrides {
/**
* Enable account view & account configuration
* @default { fields: ["image", "name"] }
*/
- account?: boolean | Partial
+ account?: boolean | AccountPluginOptions
+ /**
+ * Avatar configuration
+ */
+ avatar?: boolean | AvatarOptions
/**
* User Account deletion configuration
* @default undefined
@@ -67,7 +61,7 @@ export interface AccountPluginOverrides extends Partial {
* Per-page props (className, classNames, localization, etc.)
* passed directly to each account view component.
*/
- pageProps?: NonNullable & {
+ pageProps?: {
accountSettings?: AccountPageProps
accountSecurity?: AccountPageProps
accountApiKeys?: AccountPageProps
@@ -76,24 +70,15 @@ export interface AccountPluginOverrides extends Partial {
}
}
-// Curried helper: pins TOverrides=AccountPluginOverrides while letting TRoutes be inferred.
-// See auth-plugin.ts for explanation of why this pattern is needed.
-function definePlugin>(
- plugin: ClientPlugin
-): ClientPlugin {
- return defineClientPlugin(plugin)
-}
-
// Meta generator factory for account pages
function createAuthMeta(
- config: AccountClientConfig,
+ config: ResolvedClientPluginRuntime<"account">,
path: string,
title: string,
description: string
) {
return () => {
- const { siteBaseURL, siteBasePath } = config
- const fullUrl = `${siteBaseURL}${siteBasePath}${path}`
+ const fullUrl = `${config.site.baseURL}${config.site.basePath}${path}`
return [
{ name: "title", content: title },
@@ -113,11 +98,12 @@ function createAuthMeta(
* Account client plugin
* Provides routes, components, and meta for account management flows
*
- * @param config - Configuration including queryClient and URLs
+ * Shared site runtime is inherited from createClientStack.
*/
-export const accountClientPlugin = (config: AccountClientConfig) =>
- definePlugin({
- name: "account",
+function createResolvedAccountPlugin(
+ config: ResolvedClientPluginRuntime<"account">
+) {
+ return {
routes: () =>
defineRoutes({
// Account views
@@ -215,4 +201,11 @@ export const accountClientPlugin = (config: AccountClientConfig) =>
sitemap: async () => {
return []
}
+ }
+}
+
+export const accountClientPlugin = () =>
+ defineClientPlugin()({
+ id: "account",
+ resolve: createResolvedAccountPlugin
})
diff --git a/src/plugins/auth-plugin.ts b/src/plugins/auth-plugin.ts
index e3ad0cb04..ea5e48584 100644
--- a/src/plugins/auth-plugin.ts
+++ b/src/plugins/auth-plugin.ts
@@ -1,7 +1,6 @@
import {
- type ClientPlugin,
defineClientPlugin,
- type Route
+ type ResolvedClientPluginRuntime
} from "@btst/stack/plugins/client"
import { defineRoute, defineRoutes } from "@btst/yar"
import { lazy } from "react"
@@ -13,7 +12,6 @@ import type { AdditionalFields } from "../types/additional-fields"
import type { AnyAuthClient } from "../types/any-auth-client"
import type { AuthHooks } from "../types/auth-hooks"
import type { AuthMutators } from "../types/auth-mutators"
-import type { AvatarOptions } from "../types/avatar-options"
import type { CaptchaOptions } from "../types/captcha-options"
import type { CredentialsOptions } from "../types/credentials-options"
import type { GenericOAuthOptions } from "../types/generic-oauth-options"
@@ -32,20 +30,9 @@ export type AuthPageProps = Omit<
localization?: Partial
}
-/**
- * Configuration for auth client plugin
- */
-export interface AuthClientConfig {
- siteBaseURL: string
- siteBasePath: string
-
- // Optional context to pass to loaders (for SSR)
- context?: Record
-}
-
/**
* Plugin override interface for auth plugin
- * Defines all configurable options that can be overridden via BetterStackProvider
+ * Defines all configurable options that can be overridden via StackProvider
*/
export interface AuthPluginOverrides {
/**
@@ -58,15 +45,6 @@ export interface AuthPluginOverrides {
* Customize the Localization strings
*/
localization?: AuthLocalization
- /**
- * Base path for the auth views
- * @default "/auth"
- */
- basePath?: string
- /**
- * Front end base URL for auth API callbacks
- */
- baseURL?: string
/**
* Default redirect URL after authenticating
* @default "/"
@@ -159,10 +137,6 @@ export interface AuthPluginOverrides {
* Gravatar configuration
*/
gravatar?: boolean | GravatarOptions
- /**
- * Avatar configuration
- */
- avatar?: boolean | AvatarOptions
/**
* Additional fields for users
*/
@@ -202,6 +176,10 @@ export interface AuthPluginOverrides {
error: Error,
context: { path: string; isSSR: boolean }
) => void
+ /**
+ * Called whenever Better Auth changes the current session.
+ */
+ onSessionChange?: () => void | Promise
/**
* Per-page props (className, classNames, localization, etc.)
* passed directly to each auth view component.
@@ -222,26 +200,15 @@ export interface AuthPluginOverrides {
}
}
-// Curried helper: pins TOverrides=AuthPluginOverrides while letting TRoutes be inferred
-// from the routes object. Calling defineClientPlugin({...}) would
-// prevent TypeScript from inferring TRoutes (it falls back to Record),
-// poisoning MergeAllPluginRoutes with a string index signature downstream.
-function definePlugin>(
- plugin: ClientPlugin
-): ClientPlugin {
- return defineClientPlugin(plugin)
-}
-
// Meta generator factory for auth pages
function createAuthMeta(
- config: AuthClientConfig,
+ config: ResolvedClientPluginRuntime<"auth">,
path: string,
title: string,
description: string
) {
return () => {
- const { siteBaseURL, siteBasePath } = config
- const fullUrl = `${siteBaseURL}${siteBasePath}${path}`
+ const fullUrl = `${config.site.baseURL}${config.site.basePath}${path}`
return [
{ name: "title", content: title },
@@ -261,11 +228,10 @@ function createAuthMeta(
* Auth client plugin
* Provides routes, components, and meta for authentication flows
*
- * @param config - Configuration including queryClient and URLs
+ * Shared site runtime is inherited from createClientStack.
*/
-export const authClientPlugin = (config: AuthClientConfig) =>
- definePlugin({
- name: "auth",
+function createResolvedAuthPlugin(config: ResolvedClientPluginRuntime<"auth">) {
+ return {
routes: () =>
defineRoutes({
signIn: defineRoute(`/auth/${authViewPaths.SIGN_IN}`, {
@@ -450,20 +416,27 @@ export const authClientPlugin = (config: AuthClientConfig) =>
// Only include public-facing auth pages in sitemap
return [
{
- url: `${config.siteBaseURL}${config.siteBasePath}/auth/${authViewPaths.SIGN_IN}`,
+ url: `${config.site.baseURL}${config.site.basePath}/auth/${authViewPaths.SIGN_IN}`,
lastModified: new Date(),
priority: 0.8
},
{
- url: `${config.siteBaseURL}${config.siteBasePath}/auth/${authViewPaths.SIGN_UP}`,
+ url: `${config.site.baseURL}${config.site.basePath}/auth/${authViewPaths.SIGN_UP}`,
lastModified: new Date(),
priority: 0.8
},
{
- url: `${config.siteBaseURL}${config.siteBasePath}/auth/${authViewPaths.FORGOT_PASSWORD}`,
+ url: `${config.site.baseURL}${config.site.basePath}/auth/${authViewPaths.FORGOT_PASSWORD}`,
lastModified: new Date(),
priority: 0.5
}
]
}
+ }
+}
+
+export const authClientPlugin = () =>
+ defineClientPlugin()({
+ id: "auth",
+ resolve: createResolvedAuthPlugin
})
diff --git a/src/plugins/organization-plugin.ts b/src/plugins/organization-plugin.ts
index 060ae89b4..2076f8077 100644
--- a/src/plugins/organization-plugin.ts
+++ b/src/plugins/organization-plugin.ts
@@ -1,7 +1,6 @@
import {
- type ClientPlugin,
defineClientPlugin,
- type Route
+ type ResolvedClientPluginRuntime
} from "@btst/stack/plugins/client"
import { defineRoute, defineRoutes } from "@btst/yar"
import { lazy } from "react"
@@ -10,7 +9,6 @@ import { organizationViewPaths } from "../lib/view-paths"
import type { AuthLocalization } from "../localization/auth-localization"
import type { OrganizationOptions } from "../types/organization-options"
import type { TeamOptions } from "../types/team-options"
-import type { AuthPluginOverrides } from "./auth-plugin"
/**
* Per-page customization props for organization views.
@@ -23,28 +21,18 @@ export type OrganizationPageProps = Omit<
localization?: Partial
}
-/**
- * Configuration for organization client plugin
- */
-export interface OrganizationClientConfig {
- siteBaseURL: string
- siteBasePath: string
-
- // Optional context to pass to loaders (for SSR)
- context?: Record
-}
+export type OrganizationPluginOptions = Omit
/**
* Plugin override interface for organization plugin
- * Extends AuthPluginOverrides with organization-specific options
+ * Contains only organization-specific options consumed by the bridge.
*/
-export interface OrganizationPluginOverrides
- extends Partial {
+export interface OrganizationPluginOverrides {
/**
* Organization plugin configuration
* @default undefined
*/
- organization?: OrganizationOptions | boolean
+ organization?: OrganizationPluginOptions | boolean
/**
* Enable teams feature within organizations
* @default undefined
@@ -62,7 +50,7 @@ export interface OrganizationPluginOverrides
* Per-page props (className, classNames, localization, etc.)
* passed directly to each organization view component.
*/
- pageProps?: NonNullable & {
+ pageProps?: {
organizationSettings?: OrganizationPageProps
organizationMembers?: OrganizationPageProps
organizationApiKeys?: OrganizationPageProps
@@ -70,24 +58,15 @@ export interface OrganizationPluginOverrides
}
}
-// Curried helper: pins TOverrides=OrganizationPluginOverrides while letting TRoutes be inferred.
-// See auth-plugin.ts for explanation of why this pattern is needed.
-function definePlugin>(
- plugin: ClientPlugin
-): ClientPlugin {
- return defineClientPlugin(plugin)
-}
-
// Meta generator factory for organization pages
function createAuthMeta(
- config: OrganizationClientConfig,
+ config: ResolvedClientPluginRuntime<"organization">,
path: string,
title: string,
description: string
) {
return () => {
- const { siteBaseURL, siteBasePath } = config
- const fullUrl = `${siteBaseURL}${siteBasePath}${path}`
+ const fullUrl = `${config.site.baseURL}${config.site.basePath}${path}`
return [
{ name: "title", content: title },
@@ -107,11 +86,12 @@ function createAuthMeta(
* Organization client plugin
* Provides routes, components, and meta for organization management flows
*
- * @param config - Configuration including queryClient and URLs
+ * Shared site runtime is inherited from createClientStack.
*/
-export const organizationClientPlugin = (config: OrganizationClientConfig) =>
- definePlugin({
- name: "organization",
+function createResolvedOrganizationPlugin(
+ config: ResolvedClientPluginRuntime<"organization">
+) {
+ return {
routes: () =>
defineRoutes({
organizationSettings: defineRoute(
@@ -190,4 +170,11 @@ export const organizationClientPlugin = (config: OrganizationClientConfig) =>
sitemap: async () => {
return []
}
+ }
+}
+
+export const organizationClientPlugin = () =>
+ defineClientPlugin()({
+ id: "organization",
+ resolve: createResolvedOrganizationPlugin
})
diff --git a/src/server.ts b/src/server.ts
index 454be45fb..0279cfc79 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -1,12 +1,4 @@
export * from "./components/email/email-template"
-export type {
- BetterAuthServerProviderOptions,
- BetterAuthStackServer
-} from "./lib/better-auth-server-provider"
-export {
- createBetterAuthServerProvider,
- createBetterAuthServerProvider as createBetterAuthProvider
-} from "./lib/better-auth-server-provider"
export { getViewByPath } from "./lib/utils"
export * from "./lib/view-paths"
export * from "./localization/auth-localization"
diff --git a/src/types/auth-client.ts b/src/types/auth-client.ts
index ac6a2fd8b..5e53a9fed 100644
--- a/src/types/auth-client.ts
+++ b/src/types/auth-client.ts
@@ -3,7 +3,6 @@ import { passkeyClient } from "@better-auth/passkey/client"
import {
anonymousClient,
emailOTPClient,
- genericOAuthClient,
magicLinkClient,
multiSessionClient,
oneTapClient,
@@ -27,7 +26,6 @@ export const authClient = createAuthClient({
oneTapClient({
clientId: ""
}),
- genericOAuthClient(),
anonymousClient(),
usernameClient(),
magicLinkClient(),
diff --git a/src/types/auth-mutators.ts b/src/types/auth-mutators.ts
index 7ca1e185e..e8f648f72 100644
--- a/src/types/auth-mutators.ts
+++ b/src/types/auth-mutators.ts
@@ -17,5 +17,5 @@ export interface AuthMutators {
data: Record
}>
updateUser: MutateFn
- unlinkAccount: MutateFn<{ providerId: string; accountId?: string }>
+ unlinkAccount: MutateFn<{ accountId: string }>
}
diff --git a/src/types/generic-oauth-options.ts b/src/types/generic-oauth-options.ts
index 0bda3aa8d..d3d9bc98e 100644
--- a/src/types/generic-oauth-options.ts
+++ b/src/types/generic-oauth-options.ts
@@ -11,6 +11,6 @@ export type GenericOAuthOptions = {
* Custom generic OAuth sign in function
*/
signIn?: (
- params: Parameters[0]
+ params: Parameters[0]
) => Promise
}
diff --git a/tsconfig.json b/tsconfig.json
index 812483d7d..382a95acc 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -16,7 +16,10 @@
"esnext"
],
"module": "esnext",
- "moduleResolution": "bundler"
+ "moduleResolution": "bundler",
+ "types": [
+ "node"
+ ]
},
"include": [
"src"