From e3688ec193721ceca33e71088228a4847c89a39c Mon Sep 17 00:00:00 2001 From: olliethedev <5933733+olliethedev@users.noreply.github.com> Date: Sat, 29 Aug 2026 16:07:43 -0400 Subject: [PATCH] feat: migrate BTST plugins to RC3 Co-authored-by: Cursor --- .github/workflows/release.yml | 9 + .github/workflows/test.yml | 18 + docs/content/docs/integrations/btst-v3.mdx | 316 +++++++------ package.json | 50 +- pnpm-lock.yaml | 428 +++++++++--------- src/client.ts | 7 +- src/components/auth/provider-button.tsx | 22 +- .../settings/providers/provider-cell.tsx | 24 +- .../two-factor/two-factor-password-dialog.tsx | 17 +- src/index.ts | 1 - .../__tests__/better-auth-provider.test.ts | 174 ------- src/lib/__tests__/package-metadata.test.ts | 89 +++- .../__tests__/plugin-context-bridge.test.tsx | 272 ++++++----- src/lib/better-auth-provider-shared.ts | 58 --- src/lib/better-auth-provider.ts | 78 ---- src/lib/better-auth-server-provider.ts | 104 ----- src/lib/instantdb/use-list-accounts.ts | 1 + src/lib/instantdb/use-list-sessions.ts | 3 +- src/lib/instantdb/use-session.ts | 5 +- src/lib/plugin-context-bridge.tsx | 183 +++----- src/lib/tanstack/use-tanstack-options.ts | 2 +- src/lib/triplit/use-list-accounts.ts | 1 + src/lib/triplit/use-list-sessions.ts | 1 + src/lib/triplit/use-session.ts | 5 +- .../__tests__/plugin-overrides.typecheck.tsx | 158 +++++++ src/plugins/__tests__/plugin-routes.test.ts | 69 ++- src/plugins/account-plugin.ts | 57 +-- src/plugins/auth-plugin.ts | 69 +-- src/plugins/organization-plugin.ts | 53 +-- src/server.ts | 8 - src/types/auth-client.ts | 2 - src/types/auth-mutators.ts | 2 +- src/types/generic-oauth-options.ts | 2 +- tsconfig.json | 5 +- 34 files changed, 1061 insertions(+), 1232 deletions(-) delete mode 100644 src/lib/__tests__/better-auth-provider.test.ts delete mode 100644 src/lib/better-auth-provider-shared.ts delete mode 100644 src/lib/better-auth-provider.ts delete mode 100644 src/lib/better-auth-server-provider.ts create mode 100644 src/plugins/__tests__/plugin-overrides.typecheck.tsx 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"