#
tokens: 47328/50000 10/1115 files (page 25/71)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 25 of 71. Use http://codebase.md/better-auth/better-auth?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitattributes
├── .github
│   ├── CODEOWNERS
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── renovate.json5
│   └── workflows
│       ├── ci.yml
│       ├── e2e.yml
│       ├── preview.yml
│       └── release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .vscode
│   └── settings.json
├── banner-dark.png
├── banner.png
├── biome.json
├── bump.config.ts
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── demo
│   ├── expo-example
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── app.config.ts
│   │   ├── assets
│   │   │   ├── bg-image.jpeg
│   │   │   ├── fonts
│   │   │   │   └── SpaceMono-Regular.ttf
│   │   │   ├── icon.png
│   │   │   └── images
│   │   │       ├── adaptive-icon.png
│   │   │       ├── favicon.png
│   │   │       ├── logo.png
│   │   │       ├── partial-react-logo.png
│   │   │       ├── react-logo.png
│   │   │       ├── [email protected]
│   │   │       ├── [email protected]
│   │   │       └── splash.png
│   │   ├── babel.config.js
│   │   ├── components.json
│   │   ├── expo-env.d.ts
│   │   ├── index.ts
│   │   ├── metro.config.js
│   │   ├── nativewind-env.d.ts
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── app
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── api
│   │   │   │   │   └── auth
│   │   │   │   │       └── [...route]+api.ts
│   │   │   │   ├── dashboard.tsx
│   │   │   │   ├── forget-password.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   └── sign-up.tsx
│   │   │   ├── components
│   │   │   │   ├── icons
│   │   │   │   │   └── google.tsx
│   │   │   │   └── ui
│   │   │   │       ├── avatar.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       └── text.tsx
│   │   │   ├── global.css
│   │   │   └── lib
│   │   │       ├── auth-client.ts
│   │   │       ├── auth.ts
│   │   │       ├── icons
│   │   │       │   ├── iconWithClassName.ts
│   │   │       │   └── X.tsx
│   │   │       └── utils.ts
│   │   ├── tailwind.config.js
│   │   └── tsconfig.json
│   ├── nextjs
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── app
│   │   │   ├── (auth)
│   │   │   │   ├── forget-password
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── reset-password
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── sign-in
│   │   │   │   │   ├── loading.tsx
│   │   │   │   │   └── page.tsx
│   │   │   │   └── two-factor
│   │   │   │       ├── otp
│   │   │   │       │   └── page.tsx
│   │   │   │       └── page.tsx
│   │   │   ├── accept-invitation
│   │   │   │   └── [id]
│   │   │   │       ├── invitation-error.tsx
│   │   │   │       └── page.tsx
│   │   │   ├── admin
│   │   │   │   └── page.tsx
│   │   │   ├── api
│   │   │   │   └── auth
│   │   │   │       └── [...all]
│   │   │   │           └── route.ts
│   │   │   ├── apps
│   │   │   │   └── register
│   │   │   │       └── page.tsx
│   │   │   ├── client-test
│   │   │   │   └── page.tsx
│   │   │   ├── dashboard
│   │   │   │   ├── change-plan.tsx
│   │   │   │   ├── client.tsx
│   │   │   │   ├── organization-card.tsx
│   │   │   │   ├── page.tsx
│   │   │   │   ├── upgrade-button.tsx
│   │   │   │   └── user-card.tsx
│   │   │   ├── device
│   │   │   │   ├── approve
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── denied
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── layout.tsx
│   │   │   │   ├── page.tsx
│   │   │   │   └── success
│   │   │   │       └── page.tsx
│   │   │   ├── favicon.ico
│   │   │   ├── features.tsx
│   │   │   ├── fonts
│   │   │   │   ├── GeistMonoVF.woff
│   │   │   │   └── GeistVF.woff
│   │   │   ├── globals.css
│   │   │   ├── layout.tsx
│   │   │   ├── oauth
│   │   │   │   └── authorize
│   │   │   │       ├── concet-buttons.tsx
│   │   │   │       └── page.tsx
│   │   │   ├── page.tsx
│   │   │   └── pricing
│   │   │       └── page.tsx
│   │   ├── components
│   │   │   ├── account-switch.tsx
│   │   │   ├── blocks
│   │   │   │   └── pricing.tsx
│   │   │   ├── logo.tsx
│   │   │   ├── one-tap.tsx
│   │   │   ├── sign-in-btn.tsx
│   │   │   ├── sign-in.tsx
│   │   │   ├── sign-up.tsx
│   │   │   ├── theme-provider.tsx
│   │   │   ├── theme-toggle.tsx
│   │   │   ├── tier-labels.tsx
│   │   │   ├── ui
│   │   │   │   ├── accordion.tsx
│   │   │   │   ├── alert-dialog.tsx
│   │   │   │   ├── alert.tsx
│   │   │   │   ├── aspect-ratio.tsx
│   │   │   │   ├── avatar.tsx
│   │   │   │   ├── badge.tsx
│   │   │   │   ├── breadcrumb.tsx
│   │   │   │   ├── button.tsx
│   │   │   │   ├── calendar.tsx
│   │   │   │   ├── card.tsx
│   │   │   │   ├── carousel.tsx
│   │   │   │   ├── chart.tsx
│   │   │   │   ├── checkbox.tsx
│   │   │   │   ├── collapsible.tsx
│   │   │   │   ├── command.tsx
│   │   │   │   ├── context-menu.tsx
│   │   │   │   ├── copy-button.tsx
│   │   │   │   ├── dialog.tsx
│   │   │   │   ├── drawer.tsx
│   │   │   │   ├── dropdown-menu.tsx
│   │   │   │   ├── form.tsx
│   │   │   │   ├── hover-card.tsx
│   │   │   │   ├── input-otp.tsx
│   │   │   │   ├── input.tsx
│   │   │   │   ├── label.tsx
│   │   │   │   ├── menubar.tsx
│   │   │   │   ├── navigation-menu.tsx
│   │   │   │   ├── pagination.tsx
│   │   │   │   ├── password-input.tsx
│   │   │   │   ├── popover.tsx
│   │   │   │   ├── progress.tsx
│   │   │   │   ├── radio-group.tsx
│   │   │   │   ├── resizable.tsx
│   │   │   │   ├── scroll-area.tsx
│   │   │   │   ├── select.tsx
│   │   │   │   ├── separator.tsx
│   │   │   │   ├── sheet.tsx
│   │   │   │   ├── skeleton.tsx
│   │   │   │   ├── slider.tsx
│   │   │   │   ├── sonner.tsx
│   │   │   │   ├── switch.tsx
│   │   │   │   ├── table.tsx
│   │   │   │   ├── tabs.tsx
│   │   │   │   ├── tabs2.tsx
│   │   │   │   ├── textarea.tsx
│   │   │   │   ├── toast.tsx
│   │   │   │   ├── toaster.tsx
│   │   │   │   ├── toggle-group.tsx
│   │   │   │   ├── toggle.tsx
│   │   │   │   └── tooltip.tsx
│   │   │   └── wrapper.tsx
│   │   ├── components.json
│   │   ├── hooks
│   │   │   └── use-toast.ts
│   │   ├── lib
│   │   │   ├── auth-client.ts
│   │   │   ├── auth-types.ts
│   │   │   ├── auth.ts
│   │   │   ├── email
│   │   │   │   ├── invitation.tsx
│   │   │   │   ├── resend.ts
│   │   │   │   └── reset-password.tsx
│   │   │   ├── metadata.ts
│   │   │   ├── shared.ts
│   │   │   └── utils.ts
│   │   ├── next.config.ts
│   │   ├── package.json
│   │   ├── postcss.config.mjs
│   │   ├── proxy.ts
│   │   ├── public
│   │   │   ├── __og.png
│   │   │   ├── _og.png
│   │   │   ├── favicon
│   │   │   │   ├── android-chrome-192x192.png
│   │   │   │   ├── android-chrome-512x512.png
│   │   │   │   ├── apple-touch-icon.png
│   │   │   │   ├── favicon-16x16.png
│   │   │   │   ├── favicon-32x32.png
│   │   │   │   ├── favicon.ico
│   │   │   │   ├── light
│   │   │   │   │   ├── android-chrome-192x192.png
│   │   │   │   │   ├── android-chrome-512x512.png
│   │   │   │   │   ├── apple-touch-icon.png
│   │   │   │   │   ├── favicon-16x16.png
│   │   │   │   │   ├── favicon-32x32.png
│   │   │   │   │   ├── favicon.ico
│   │   │   │   │   └── site.webmanifest
│   │   │   │   └── site.webmanifest
│   │   │   ├── logo.svg
│   │   │   └── og.png
│   │   ├── README.md
│   │   ├── tailwind.config.ts
│   │   ├── tsconfig.json
│   │   └── turbo.json
│   └── stateless
│       ├── .env.example
│       ├── .gitignore
│       ├── next.config.ts
│       ├── package.json
│       ├── postcss.config.mjs
│       ├── src
│       │   ├── app
│       │   │   ├── api
│       │   │   │   ├── auth
│       │   │   │   │   └── [...all]
│       │   │   │   │       └── route.ts
│       │   │   │   └── user
│       │   │   │       └── route.ts
│       │   │   ├── dashboard
│       │   │   │   └── page.tsx
│       │   │   ├── globals.css
│       │   │   ├── layout.tsx
│       │   │   └── page.tsx
│       │   └── lib
│       │       ├── auth-client.ts
│       │       └── auth.ts
│       ├── tailwind.config.ts
│       └── tsconfig.json
├── docker-compose.yml
├── docs
│   ├── .env.example
│   ├── .gitignore
│   ├── app
│   │   ├── api
│   │   │   ├── ai-chat
│   │   │   │   └── route.ts
│   │   │   ├── analytics
│   │   │   │   ├── conversation
│   │   │   │   │   └── route.ts
│   │   │   │   ├── event
│   │   │   │   │   └── route.ts
│   │   │   │   └── feedback
│   │   │   │       └── route.ts
│   │   │   ├── chat
│   │   │   │   └── route.ts
│   │   │   ├── og
│   │   │   │   └── route.tsx
│   │   │   ├── og-release
│   │   │   │   └── route.tsx
│   │   │   ├── search
│   │   │   │   └── route.ts
│   │   │   └── support
│   │   │       └── route.ts
│   │   ├── blog
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── blog-list.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   ├── stat-field.tsx
│   │   │   │   └── support.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── changelogs
│   │   │   ├── _components
│   │   │   │   ├── _layout.tsx
│   │   │   │   ├── changelog-layout.tsx
│   │   │   │   ├── default-changelog.tsx
│   │   │   │   ├── fmt-dates.tsx
│   │   │   │   ├── grid-pattern.tsx
│   │   │   │   ├── icons.tsx
│   │   │   │   └── stat-field.tsx
│   │   │   ├── [[...slug]]
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx
│   │   ├── community
│   │   │   ├── _components
│   │   │   │   ├── header.tsx
│   │   │   │   └── stats.tsx
│   │   │   └── page.tsx
│   │   ├── docs
│   │   │   ├── [[...slug]]
│   │   │   │   ├── page.client.tsx
│   │   │   │   └── page.tsx
│   │   │   ├── layout.tsx
│   │   │   └── lib
│   │   │       └── get-llm-text.ts
│   │   ├── global.css
│   │   ├── layout.config.tsx
│   │   ├── layout.tsx
│   │   ├── llms.txt
│   │   │   ├── [...slug]
│   │   │   │   └── route.ts
│   │   │   └── route.ts
│   │   ├── not-found.tsx
│   │   ├── page.tsx
│   │   ├── reference
│   │   │   └── route.ts
│   │   ├── sitemap.xml
│   │   ├── static.json
│   │   │   └── route.ts
│   │   └── v1
│   │       ├── _components
│   │       │   └── v1-text.tsx
│   │       ├── bg-line.tsx
│   │       └── page.tsx
│   ├── assets
│   │   ├── Geist.ttf
│   │   └── GeistMono.ttf
│   ├── components
│   │   ├── ai-chat-modal.tsx
│   │   ├── anchor-scroll-fix.tsx
│   │   ├── api-method-tabs.tsx
│   │   ├── api-method.tsx
│   │   ├── banner.tsx
│   │   ├── blocks
│   │   │   └── features.tsx
│   │   ├── builder
│   │   │   ├── beam.tsx
│   │   │   ├── code-tabs
│   │   │   │   ├── code-editor.tsx
│   │   │   │   ├── code-tabs.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── tab-bar.tsx
│   │   │   │   └── theme.ts
│   │   │   ├── index.tsx
│   │   │   ├── sign-in.tsx
│   │   │   ├── sign-up.tsx
│   │   │   ├── social-provider.tsx
│   │   │   ├── store.ts
│   │   │   └── tabs.tsx
│   │   ├── display-techstack.tsx
│   │   ├── divider-text.tsx
│   │   ├── docs
│   │   │   ├── docs.client.tsx
│   │   │   ├── docs.tsx
│   │   │   ├── layout
│   │   │   │   ├── nav.tsx
│   │   │   │   ├── theme-toggle.tsx
│   │   │   │   ├── toc-thumb.tsx
│   │   │   │   └── toc.tsx
│   │   │   ├── page.client.tsx
│   │   │   ├── page.tsx
│   │   │   ├── shared.tsx
│   │   │   └── ui
│   │   │       ├── button.tsx
│   │   │       ├── collapsible.tsx
│   │   │       ├── popover.tsx
│   │   │       └── scroll-area.tsx
│   │   ├── endpoint.tsx
│   │   ├── features.tsx
│   │   ├── floating-ai-search.tsx
│   │   ├── fork-button.tsx
│   │   ├── generate-apple-jwt.tsx
│   │   ├── generate-secret.tsx
│   │   ├── github-stat.tsx
│   │   ├── icons.tsx
│   │   ├── landing
│   │   │   ├── gradient-bg.tsx
│   │   │   ├── grid-pattern.tsx
│   │   │   ├── hero.tsx
│   │   │   ├── section-svg.tsx
│   │   │   ├── section.tsx
│   │   │   ├── spotlight.tsx
│   │   │   └── testimonials.tsx
│   │   ├── logo-context-menu.tsx
│   │   ├── logo.tsx
│   │   ├── markdown-renderer.tsx
│   │   ├── markdown.tsx
│   │   ├── mdx
│   │   │   ├── add-to-cursor.tsx
│   │   │   └── database-tables.tsx
│   │   ├── message-feedback.tsx
│   │   ├── mobile-search-icon.tsx
│   │   ├── nav-bar.tsx
│   │   ├── nav-link.tsx
│   │   ├── nav-mobile.tsx
│   │   ├── promo-card.tsx
│   │   ├── resource-card.tsx
│   │   ├── resource-grid.tsx
│   │   ├── resource-section.tsx
│   │   ├── ripple.tsx
│   │   ├── search-dialog.tsx
│   │   ├── side-bar.tsx
│   │   ├── sidebar-content.tsx
│   │   ├── techstack-icons.tsx
│   │   ├── theme-provider.tsx
│   │   ├── theme-toggler.tsx
│   │   └── ui
│   │       ├── accordion.tsx
│   │       ├── alert-dialog.tsx
│   │       ├── alert.tsx
│   │       ├── aside-link.tsx
│   │       ├── aspect-ratio.tsx
│   │       ├── avatar.tsx
│   │       ├── background-beams.tsx
│   │       ├── background-boxes.tsx
│   │       ├── badge.tsx
│   │       ├── breadcrumb.tsx
│   │       ├── button.tsx
│   │       ├── calendar.tsx
│   │       ├── callout.tsx
│   │       ├── card.tsx
│   │       ├── carousel.tsx
│   │       ├── chart.tsx
│   │       ├── checkbox.tsx
│   │       ├── code-block.tsx
│   │       ├── collapsible.tsx
│   │       ├── command.tsx
│   │       ├── context-menu.tsx
│   │       ├── dialog.tsx
│   │       ├── drawer.tsx
│   │       ├── dropdown-menu.tsx
│   │       ├── dynamic-code-block.tsx
│   │       ├── fade-in.tsx
│   │       ├── form.tsx
│   │       ├── hover-card.tsx
│   │       ├── input-otp.tsx
│   │       ├── input.tsx
│   │       ├── label.tsx
│   │       ├── menubar.tsx
│   │       ├── navigation-menu.tsx
│   │       ├── pagination.tsx
│   │       ├── popover.tsx
│   │       ├── progress.tsx
│   │       ├── radio-group.tsx
│   │       ├── resizable.tsx
│   │       ├── scroll-area.tsx
│   │       ├── select.tsx
│   │       ├── separator.tsx
│   │       ├── sheet.tsx
│   │       ├── sidebar.tsx
│   │       ├── skeleton.tsx
│   │       ├── slider.tsx
│   │       ├── sonner.tsx
│   │       ├── sparkles.tsx
│   │       ├── switch.tsx
│   │       ├── table.tsx
│   │       ├── tabs.tsx
│   │       ├── textarea.tsx
│   │       ├── toggle-group.tsx
│   │       ├── toggle.tsx
│   │       ├── tooltip-docs.tsx
│   │       ├── tooltip.tsx
│   │       └── use-copy-button.tsx
│   ├── components.json
│   ├── content
│   │   ├── blogs
│   │   │   ├── 0-supabase-auth-to-planetscale-migration.mdx
│   │   │   ├── 1-3.mdx
│   │   │   ├── authjs-joins-better-auth.mdx
│   │   │   └── seed-round.mdx
│   │   ├── changelogs
│   │   │   ├── 1-2.mdx
│   │   │   └── 1.0.mdx
│   │   └── docs
│   │       ├── adapters
│   │       │   ├── community-adapters.mdx
│   │       │   ├── drizzle.mdx
│   │       │   ├── mongo.mdx
│   │       │   ├── mssql.mdx
│   │       │   ├── mysql.mdx
│   │       │   ├── other-relational-databases.mdx
│   │       │   ├── postgresql.mdx
│   │       │   ├── prisma.mdx
│   │       │   └── sqlite.mdx
│   │       ├── authentication
│   │       │   ├── apple.mdx
│   │       │   ├── atlassian.mdx
│   │       │   ├── cognito.mdx
│   │       │   ├── discord.mdx
│   │       │   ├── dropbox.mdx
│   │       │   ├── email-password.mdx
│   │       │   ├── facebook.mdx
│   │       │   ├── figma.mdx
│   │       │   ├── github.mdx
│   │       │   ├── gitlab.mdx
│   │       │   ├── google.mdx
│   │       │   ├── huggingface.mdx
│   │       │   ├── kakao.mdx
│   │       │   ├── kick.mdx
│   │       │   ├── line.mdx
│   │       │   ├── linear.mdx
│   │       │   ├── linkedin.mdx
│   │       │   ├── microsoft.mdx
│   │       │   ├── naver.mdx
│   │       │   ├── notion.mdx
│   │       │   ├── other-social-providers.mdx
│   │       │   ├── paypal.mdx
│   │       │   ├── reddit.mdx
│   │       │   ├── roblox.mdx
│   │       │   ├── salesforce.mdx
│   │       │   ├── slack.mdx
│   │       │   ├── spotify.mdx
│   │       │   ├── tiktok.mdx
│   │       │   ├── twitch.mdx
│   │       │   ├── twitter.mdx
│   │       │   ├── vk.mdx
│   │       │   └── zoom.mdx
│   │       ├── basic-usage.mdx
│   │       ├── comparison.mdx
│   │       ├── concepts
│   │       │   ├── api.mdx
│   │       │   ├── cli.mdx
│   │       │   ├── client.mdx
│   │       │   ├── cookies.mdx
│   │       │   ├── database.mdx
│   │       │   ├── email.mdx
│   │       │   ├── hooks.mdx
│   │       │   ├── oauth.mdx
│   │       │   ├── plugins.mdx
│   │       │   ├── rate-limit.mdx
│   │       │   ├── session-management.mdx
│   │       │   ├── typescript.mdx
│   │       │   └── users-accounts.mdx
│   │       ├── examples
│   │       │   ├── astro.mdx
│   │       │   ├── next-js.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   └── svelte-kit.mdx
│   │       ├── guides
│   │       │   ├── auth0-migration-guide.mdx
│   │       │   ├── browser-extension-guide.mdx
│   │       │   ├── clerk-migration-guide.mdx
│   │       │   ├── create-a-db-adapter.mdx
│   │       │   ├── next-auth-migration-guide.mdx
│   │       │   ├── optimizing-for-performance.mdx
│   │       │   ├── saml-sso-with-okta.mdx
│   │       │   ├── supabase-migration-guide.mdx
│   │       │   └── your-first-plugin.mdx
│   │       ├── installation.mdx
│   │       ├── integrations
│   │       │   ├── astro.mdx
│   │       │   ├── convex.mdx
│   │       │   ├── elysia.mdx
│   │       │   ├── expo.mdx
│   │       │   ├── express.mdx
│   │       │   ├── fastify.mdx
│   │       │   ├── hono.mdx
│   │       │   ├── lynx.mdx
│   │       │   ├── nestjs.mdx
│   │       │   ├── next.mdx
│   │       │   ├── nitro.mdx
│   │       │   ├── nuxt.mdx
│   │       │   ├── remix.mdx
│   │       │   ├── solid-start.mdx
│   │       │   ├── svelte-kit.mdx
│   │       │   ├── tanstack.mdx
│   │       │   └── waku.mdx
│   │       ├── introduction.mdx
│   │       ├── meta.json
│   │       ├── plugins
│   │       │   ├── 2fa.mdx
│   │       │   ├── admin.mdx
│   │       │   ├── anonymous.mdx
│   │       │   ├── api-key.mdx
│   │       │   ├── autumn.mdx
│   │       │   ├── bearer.mdx
│   │       │   ├── captcha.mdx
│   │       │   ├── community-plugins.mdx
│   │       │   ├── device-authorization.mdx
│   │       │   ├── dodopayments.mdx
│   │       │   ├── dub.mdx
│   │       │   ├── email-otp.mdx
│   │       │   ├── generic-oauth.mdx
│   │       │   ├── have-i-been-pwned.mdx
│   │       │   ├── jwt.mdx
│   │       │   ├── last-login-method.mdx
│   │       │   ├── magic-link.mdx
│   │       │   ├── mcp.mdx
│   │       │   ├── multi-session.mdx
│   │       │   ├── oauth-proxy.mdx
│   │       │   ├── oidc-provider.mdx
│   │       │   ├── one-tap.mdx
│   │       │   ├── one-time-token.mdx
│   │       │   ├── open-api.mdx
│   │       │   ├── organization.mdx
│   │       │   ├── passkey.mdx
│   │       │   ├── phone-number.mdx
│   │       │   ├── polar.mdx
│   │       │   ├── siwe.mdx
│   │       │   ├── sso.mdx
│   │       │   ├── stripe.mdx
│   │       │   └── username.mdx
│   │       └── reference
│   │           ├── contributing.mdx
│   │           ├── faq.mdx
│   │           ├── options.mdx
│   │           ├── resources.mdx
│   │           ├── security.mdx
│   │           └── telemetry.mdx
│   ├── hooks
│   │   └── use-mobile.ts
│   ├── ignore-build.sh
│   ├── lib
│   │   ├── blog.ts
│   │   ├── chat
│   │   │   └── inkeep-qa-schema.ts
│   │   ├── constants.ts
│   │   ├── export-search-indexes.ts
│   │   ├── inkeep-analytics.ts
│   │   ├── is-active.ts
│   │   ├── metadata.ts
│   │   ├── source.ts
│   │   └── utils.ts
│   ├── next.config.js
│   ├── package.json
│   ├── postcss.config.js
│   ├── proxy.ts
│   ├── public
│   │   ├── avatars
│   │   │   └── beka.jpg
│   │   ├── blogs
│   │   │   ├── authjs-joins.png
│   │   │   ├── seed-round.png
│   │   │   └── supabase-ps.png
│   │   ├── branding
│   │   │   ├── better-auth-brand-assets.zip
│   │   │   ├── better-auth-logo-dark.png
│   │   │   ├── better-auth-logo-dark.svg
│   │   │   ├── better-auth-logo-light.png
│   │   │   ├── better-auth-logo-light.svg
│   │   │   ├── better-auth-logo-wordmark-dark.png
│   │   │   ├── better-auth-logo-wordmark-dark.svg
│   │   │   ├── better-auth-logo-wordmark-light.png
│   │   │   └── better-auth-logo-wordmark-light.svg
│   │   ├── extension-id.png
│   │   ├── favicon
│   │   │   ├── android-chrome-192x192.png
│   │   │   ├── android-chrome-512x512.png
│   │   │   ├── apple-touch-icon.png
│   │   │   ├── favicon-16x16.png
│   │   │   ├── favicon-32x32.png
│   │   │   ├── favicon.ico
│   │   │   ├── light
│   │   │   │   ├── android-chrome-192x192.png
│   │   │   │   ├── android-chrome-512x512.png
│   │   │   │   ├── apple-touch-icon.png
│   │   │   │   ├── favicon-16x16.png
│   │   │   │   ├── favicon-32x32.png
│   │   │   │   ├── favicon.ico
│   │   │   │   └── site.webmanifest
│   │   │   └── site.webmanifest
│   │   ├── images
│   │   │   └── blogs
│   │   │       └── better auth (1).png
│   │   ├── logo.png
│   │   ├── logo.svg
│   │   ├── LogoDark.webp
│   │   ├── LogoLight.webp
│   │   ├── og.png
│   │   ├── open-api-reference.png
│   │   ├── people-say
│   │   │   ├── code-with-antonio.jpg
│   │   │   ├── dagmawi-babi.png
│   │   │   ├── dax.png
│   │   │   ├── dev-ed.png
│   │   │   ├── egoist.png
│   │   │   ├── guillermo-rauch.png
│   │   │   ├── jonathan-wilke.png
│   │   │   ├── josh-tried-coding.jpg
│   │   │   ├── kitze.jpg
│   │   │   ├── lazar-nikolov.png
│   │   │   ├── nizzy.png
│   │   │   ├── omar-mcadam.png
│   │   │   ├── ryan-vogel.jpg
│   │   │   ├── saltyatom.jpg
│   │   │   ├── sebastien-chopin.png
│   │   │   ├── shreyas-mididoddi.png
│   │   │   ├── tech-nerd.png
│   │   │   ├── theo.png
│   │   │   ├── vybhav-bhargav.png
│   │   │   └── xavier-pladevall.jpg
│   │   ├── plus.svg
│   │   ├── release-og
│   │   │   ├── 1-2.png
│   │   │   ├── 1-3.png
│   │   │   └── changelog-og.png
│   │   └── v1-og.png
│   ├── README.md
│   ├── scripts
│   │   ├── endpoint-to-doc
│   │   │   ├── index.ts
│   │   │   ├── input.ts
│   │   │   ├── output.mdx
│   │   │   └── readme.md
│   │   └── sync-orama.ts
│   ├── source.config.ts
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   └── turbo.json
├── e2e
│   ├── integration
│   │   ├── package.json
│   │   ├── playwright.config.ts
│   │   ├── solid-vinxi
│   │   │   ├── .gitignore
│   │   │   ├── app.config.ts
│   │   │   ├── e2e
│   │   │   │   ├── test.spec.ts
│   │   │   │   └── utils.ts
│   │   │   ├── package.json
│   │   │   ├── public
│   │   │   │   └── favicon.ico
│   │   │   ├── src
│   │   │   │   ├── app.tsx
│   │   │   │   ├── entry-client.tsx
│   │   │   │   ├── entry-server.tsx
│   │   │   │   ├── global.d.ts
│   │   │   │   ├── lib
│   │   │   │   │   ├── auth-client.ts
│   │   │   │   │   └── auth.ts
│   │   │   │   └── routes
│   │   │   │       ├── [...404].tsx
│   │   │   │       ├── api
│   │   │   │       │   └── auth
│   │   │   │       │       └── [...all].ts
│   │   │   │       └── index.tsx
│   │   │   └── tsconfig.json
│   │   ├── test-utils
│   │   │   ├── package.json
│   │   │   └── src
│   │   │       └── playwright.ts
│   │   └── vanilla-node
│   │       ├── e2e
│   │       │   ├── app.ts
│   │       │   ├── domain.spec.ts
│   │       │   ├── postgres-js.spec.ts
│   │       │   ├── test.spec.ts
│   │       │   └── utils.ts
│   │       ├── index.html
│   │       ├── package.json
│   │       ├── src
│   │       │   ├── main.ts
│   │       │   └── vite-env.d.ts
│   │       ├── tsconfig.json
│   │       └── vite.config.ts
│   └── smoke
│       ├── package.json
│       ├── test
│       │   ├── bun.spec.ts
│       │   ├── cloudflare.spec.ts
│       │   ├── deno.spec.ts
│       │   ├── fixtures
│       │   │   ├── bun-simple.ts
│       │   │   ├── cloudflare
│       │   │   │   ├── .gitignore
│       │   │   │   ├── drizzle
│       │   │   │   │   ├── 0000_clean_vector.sql
│       │   │   │   │   └── meta
│       │   │   │   │       ├── _journal.json
│       │   │   │   │       └── 0000_snapshot.json
│       │   │   │   ├── drizzle.config.ts
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── auth-schema.ts
│       │   │   │   │   ├── db.ts
│       │   │   │   │   └── index.ts
│       │   │   │   ├── test
│       │   │   │   │   ├── apply-migrations.ts
│       │   │   │   │   ├── env.d.ts
│       │   │   │   │   └── index.test.ts
│       │   │   │   ├── tsconfig.json
│       │   │   │   ├── vitest.config.ts
│       │   │   │   ├── worker-configuration.d.ts
│       │   │   │   └── wrangler.json
│       │   │   ├── deno-simple.ts
│       │   │   ├── tsconfig-declaration
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── demo.ts
│       │   │   │   │   ├── index.ts
│       │   │   │   │   └── username.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-exact-optional-property-types
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── index.ts
│       │   │   │   │   ├── organization.ts
│       │   │   │   │   ├── user-additional-fields.ts
│       │   │   │   │   └── username.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-isolated-module-bundler
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-verbatim-module-syntax-node10
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   └── vite
│       │   │       ├── package.json
│       │   │       ├── src
│       │   │       │   ├── client.ts
│       │   │       │   └── server.ts
│       │   │       ├── tsconfig.json
│       │   │       └── vite.config.ts
│       │   ├── ssr.ts
│       │   ├── typecheck.spec.ts
│       │   └── vite.spec.ts
│       └── tsconfig.json
├── LICENSE.md
├── package.json
├── packages
│   ├── better-auth
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── __snapshots__
│   │   │   │   └── init.test.ts.snap
│   │   │   ├── adapters
│   │   │   │   ├── adapter-factory
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── test
│   │   │   │   │   │   ├── __snapshots__
│   │   │   │   │   │   │   └── adapter-factory.test.ts.snap
│   │   │   │   │   │   └── adapter-factory.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── create-test-suite.ts
│   │   │   │   ├── drizzle-adapter
│   │   │   │   │   ├── drizzle-adapter.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── test
│   │   │   │   │       ├── .gitignore
│   │   │   │   │       ├── adapter.drizzle.mysql.test.ts
│   │   │   │   │       ├── adapter.drizzle.pg.test.ts
│   │   │   │   │       ├── adapter.drizzle.sqlite.test.ts
│   │   │   │   │       └── generate-schema.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kysely-adapter
│   │   │   │   │   ├── bun-sqlite-dialect.ts
│   │   │   │   │   ├── dialect.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── kysely-adapter.ts
│   │   │   │   │   ├── node-sqlite-dialect.ts
│   │   │   │   │   ├── test
│   │   │   │   │   │   ├── adapter.kysely.mssql.test.ts
│   │   │   │   │   │   ├── adapter.kysely.mysql.test.ts
│   │   │   │   │   │   ├── adapter.kysely.pg.test.ts
│   │   │   │   │   │   ├── adapter.kysely.sqlite.test.ts
│   │   │   │   │   │   └── node-sqlite-dialect.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── memory-adapter
│   │   │   │   │   ├── adapter.memory.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── memory-adapter.ts
│   │   │   │   ├── mongodb-adapter
│   │   │   │   │   ├── adapter.mongo-db.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── mongodb-adapter.ts
│   │   │   │   ├── prisma-adapter
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── prisma-adapter.ts
│   │   │   │   │   └── test
│   │   │   │   │       ├── .gitignore
│   │   │   │   │       ├── base.prisma
│   │   │   │   │       ├── generate-auth-config.ts
│   │   │   │   │       ├── generate-prisma-schema.ts
│   │   │   │   │       ├── get-prisma-client.ts
│   │   │   │   │       ├── prisma.mysql.test.ts
│   │   │   │   │       ├── prisma.pg.test.ts
│   │   │   │   │       ├── prisma.sqlite.test.ts
│   │   │   │   │       └── push-prisma-schema.ts
│   │   │   │   ├── test-adapter.ts
│   │   │   │   ├── test.ts
│   │   │   │   ├── tests
│   │   │   │   │   ├── auth-flow.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── normal.ts
│   │   │   │   │   ├── number-id.ts
│   │   │   │   │   ├── performance.ts
│   │   │   │   │   └── transactions.ts
│   │   │   │   └── utils.ts
│   │   │   ├── api
│   │   │   │   ├── check-endpoint-conflicts.test.ts
│   │   │   │   ├── index.test.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── middlewares
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── origin-check.test.ts
│   │   │   │   │   └── origin-check.ts
│   │   │   │   ├── rate-limiter
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── rate-limiter.test.ts
│   │   │   │   ├── routes
│   │   │   │   │   ├── account.test.ts
│   │   │   │   │   ├── account.ts
│   │   │   │   │   ├── callback.ts
│   │   │   │   │   ├── email-verification.test.ts
│   │   │   │   │   ├── email-verification.ts
│   │   │   │   │   ├── error.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── ok.ts
│   │   │   │   │   ├── reset-password.test.ts
│   │   │   │   │   ├── reset-password.ts
│   │   │   │   │   ├── session-api.test.ts
│   │   │   │   │   ├── session.ts
│   │   │   │   │   ├── sign-in.test.ts
│   │   │   │   │   ├── sign-in.ts
│   │   │   │   │   ├── sign-out.test.ts
│   │   │   │   │   ├── sign-out.ts
│   │   │   │   │   ├── sign-up.test.ts
│   │   │   │   │   ├── sign-up.ts
│   │   │   │   │   ├── update-user.test.ts
│   │   │   │   │   └── update-user.ts
│   │   │   │   ├── to-auth-endpoints.test.ts
│   │   │   │   └── to-auth-endpoints.ts
│   │   │   ├── auth.test.ts
│   │   │   ├── auth.ts
│   │   │   ├── call.test.ts
│   │   │   ├── client
│   │   │   │   ├── client-ssr.test.ts
│   │   │   │   ├── client.test.ts
│   │   │   │   ├── config.ts
│   │   │   │   ├── fetch-plugins.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── lynx
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── lynx-store.ts
│   │   │   │   ├── parser.ts
│   │   │   │   ├── path-to-object.ts
│   │   │   │   ├── plugins
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── infer-plugin.ts
│   │   │   │   ├── proxy.ts
│   │   │   │   ├── query.ts
│   │   │   │   ├── react
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── react-store.ts
│   │   │   │   ├── session-atom.ts
│   │   │   │   ├── solid
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── solid-store.ts
│   │   │   │   ├── svelte
│   │   │   │   │   └── index.ts
│   │   │   │   ├── test-plugin.ts
│   │   │   │   ├── types.ts
│   │   │   │   ├── url.test.ts
│   │   │   │   ├── vanilla.ts
│   │   │   │   └── vue
│   │   │   │       ├── index.ts
│   │   │   │       └── vue-store.ts
│   │   │   ├── cookies
│   │   │   │   ├── check-cookies.ts
│   │   │   │   ├── cookie-utils.ts
│   │   │   │   ├── cookies.test.ts
│   │   │   │   └── index.ts
│   │   │   ├── crypto
│   │   │   │   ├── buffer.ts
│   │   │   │   ├── hash.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── jwt.ts
│   │   │   │   ├── password.test.ts
│   │   │   │   ├── password.ts
│   │   │   │   └── random.ts
│   │   │   ├── db
│   │   │   │   ├── db.test.ts
│   │   │   │   ├── field.ts
│   │   │   │   ├── get-migration.ts
│   │   │   │   ├── get-schema.ts
│   │   │   │   ├── get-tables.test.ts
│   │   │   │   ├── get-tables.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── internal-adapter.test.ts
│   │   │   │   ├── internal-adapter.ts
│   │   │   │   ├── schema.ts
│   │   │   │   ├── secondary-storage.test.ts
│   │   │   │   ├── to-zod.ts
│   │   │   │   ├── utils.ts
│   │   │   │   └── with-hooks.ts
│   │   │   ├── index.ts
│   │   │   ├── init.test.ts
│   │   │   ├── init.ts
│   │   │   ├── integrations
│   │   │   │   ├── next-js.ts
│   │   │   │   ├── node.ts
│   │   │   │   ├── react-start.ts
│   │   │   │   ├── solid-start.ts
│   │   │   │   └── svelte-kit.ts
│   │   │   ├── oauth2
│   │   │   │   ├── index.ts
│   │   │   │   ├── link-account.test.ts
│   │   │   │   ├── link-account.ts
│   │   │   │   ├── state.ts
│   │   │   │   └── utils.ts
│   │   │   ├── plugins
│   │   │   │   ├── access
│   │   │   │   │   ├── access.test.ts
│   │   │   │   │   ├── access.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── additional-fields
│   │   │   │   │   ├── additional-fields.test.ts
│   │   │   │   │   └── client.ts
│   │   │   │   ├── admin
│   │   │   │   │   ├── access
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── statement.ts
│   │   │   │   │   ├── admin.test.ts
│   │   │   │   │   ├── admin.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── has-permission.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── anonymous
│   │   │   │   │   ├── anon.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── api-key
│   │   │   │   │   ├── api-key.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── rate-limit.ts
│   │   │   │   │   ├── routes
│   │   │   │   │   │   ├── create-api-key.ts
│   │   │   │   │   │   ├── delete-all-expired-api-keys.ts
│   │   │   │   │   │   ├── delete-api-key.ts
│   │   │   │   │   │   ├── get-api-key.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── list-api-keys.ts
│   │   │   │   │   │   ├── update-api-key.ts
│   │   │   │   │   │   └── verify-api-key.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── bearer
│   │   │   │   │   ├── bearer.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── captcha
│   │   │   │   │   ├── captcha.test.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── verify-handlers
│   │   │   │   │       ├── captchafox.ts
│   │   │   │   │       ├── cloudflare-turnstile.ts
│   │   │   │   │       ├── google-recaptcha.ts
│   │   │   │   │       ├── h-captcha.ts
│   │   │   │   │       └── index.ts
│   │   │   │   ├── custom-session
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── custom-session.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── device-authorization
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── device-authorization.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── schema.ts
│   │   │   │   ├── email-otp
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── email-otp.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── generic-oauth
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── generic-oauth.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── haveibeenpwned
│   │   │   │   │   ├── haveibeenpwned.test.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── jwt
│   │   │   │   │   ├── adapter.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── jwt.test.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── sign.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── last-login-method
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── custom-prefix.test.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── last-login-method.test.ts
│   │   │   │   ├── magic-link
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── magic-link.test.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── mcp
│   │   │   │   │   ├── authorize.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── mcp.test.ts
│   │   │   │   ├── multi-session
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── multi-session.test.ts
│   │   │   │   ├── oauth-proxy
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── oauth-proxy.test.ts
│   │   │   │   ├── oidc-provider
│   │   │   │   │   ├── authorize.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── oidc.test.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── ui.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── one-tap
│   │   │   │   │   ├── client.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── one-time-token
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── one-time-token.test.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── open-api
│   │   │   │   │   ├── generator.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── logo.ts
│   │   │   │   │   └── open-api.test.ts
│   │   │   │   ├── organization
│   │   │   │   │   ├── access
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── statement.ts
│   │   │   │   │   ├── adapter.ts
│   │   │   │   │   ├── call.ts
│   │   │   │   │   ├── client.test.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── error-codes.ts
│   │   │   │   │   ├── has-permission.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── organization-hook.test.ts
│   │   │   │   │   ├── organization.test.ts
│   │   │   │   │   ├── organization.ts
│   │   │   │   │   ├── permission.ts
│   │   │   │   │   ├── routes
│   │   │   │   │   │   ├── crud-access-control.test.ts
│   │   │   │   │   │   ├── crud-access-control.ts
│   │   │   │   │   │   ├── crud-invites.ts
│   │   │   │   │   │   ├── crud-members.test.ts
│   │   │   │   │   │   ├── crud-members.ts
│   │   │   │   │   │   ├── crud-org.test.ts
│   │   │   │   │   │   ├── crud-org.ts
│   │   │   │   │   │   └── crud-team.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── team.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── passkey
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── passkey.test.ts
│   │   │   │   ├── phone-number
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── phone-number-error.ts
│   │   │   │   │   └── phone-number.test.ts
│   │   │   │   ├── siwe
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── siwe.test.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── two-factor
│   │   │   │   │   ├── backup-codes
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── client.ts
│   │   │   │   │   ├── constant.ts
│   │   │   │   │   ├── error-code.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── otp
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── schema.ts
│   │   │   │   │   ├── totp
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── two-factor.test.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   ├── utils.ts
│   │   │   │   │   └── verify-two-factor.ts
│   │   │   │   └── username
│   │   │   │       ├── client.ts
│   │   │   │       ├── error-codes.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── schema.ts
│   │   │   │       └── username.test.ts
│   │   │   ├── social-providers
│   │   │   │   └── index.ts
│   │   │   ├── social.test.ts
│   │   │   ├── test-utils
│   │   │   │   ├── headers.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── state.ts
│   │   │   │   └── test-instance.ts
│   │   │   ├── types
│   │   │   │   ├── adapter.ts
│   │   │   │   ├── api.ts
│   │   │   │   ├── helper.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── models.ts
│   │   │   │   ├── plugins.ts
│   │   │   │   └── types.test.ts
│   │   │   └── utils
│   │   │       ├── await-object.ts
│   │   │       ├── boolean.ts
│   │   │       ├── clone.ts
│   │   │       ├── constants.ts
│   │   │       ├── date.ts
│   │   │       ├── ensure-utc.ts
│   │   │       ├── get-request-ip.ts
│   │   │       ├── hashing.ts
│   │   │       ├── hide-metadata.ts
│   │   │       ├── id.ts
│   │   │       ├── import-util.ts
│   │   │       ├── index.ts
│   │   │       ├── is-atom.ts
│   │   │       ├── is-promise.ts
│   │   │       ├── json.ts
│   │   │       ├── merger.ts
│   │   │       ├── middleware-response.ts
│   │   │       ├── misc.ts
│   │   │       ├── password.ts
│   │   │       ├── plugin-helper.ts
│   │   │       ├── shim.ts
│   │   │       ├── time.ts
│   │   │       ├── url.ts
│   │   │       └── wildcard.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   ├── vitest.config.ts
│   │   └── vitest.setup.ts
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── commands
│   │   │   │   ├── generate.ts
│   │   │   │   ├── info.ts
│   │   │   │   ├── init.ts
│   │   │   │   ├── login.ts
│   │   │   │   ├── mcp.ts
│   │   │   │   ├── migrate.ts
│   │   │   │   └── secret.ts
│   │   │   ├── generators
│   │   │   │   ├── auth-config.ts
│   │   │   │   ├── drizzle.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kysely.ts
│   │   │   │   ├── prisma.ts
│   │   │   │   └── types.ts
│   │   │   ├── index.ts
│   │   │   └── utils
│   │   │       ├── add-svelte-kit-env-modules.ts
│   │   │       ├── check-package-managers.ts
│   │   │       ├── format-ms.ts
│   │   │       ├── get-config.ts
│   │   │       ├── get-package-info.ts
│   │   │       ├── get-tsconfig-info.ts
│   │   │       └── install-dependencies.ts
│   │   ├── test
│   │   │   ├── __snapshots__
│   │   │   │   ├── auth-schema-mysql-enum.txt
│   │   │   │   ├── auth-schema-mysql-number-id.txt
│   │   │   │   ├── auth-schema-mysql-passkey-number-id.txt
│   │   │   │   ├── auth-schema-mysql-passkey.txt
│   │   │   │   ├── auth-schema-mysql.txt
│   │   │   │   ├── auth-schema-number-id.txt
│   │   │   │   ├── auth-schema-pg-enum.txt
│   │   │   │   ├── auth-schema-pg-passkey.txt
│   │   │   │   ├── auth-schema-sqlite-enum.txt
│   │   │   │   ├── auth-schema-sqlite-number-id.txt
│   │   │   │   ├── auth-schema-sqlite-passkey-number-id.txt
│   │   │   │   ├── auth-schema-sqlite-passkey.txt
│   │   │   │   ├── auth-schema-sqlite.txt
│   │   │   │   ├── auth-schema.txt
│   │   │   │   ├── migrations.sql
│   │   │   │   ├── schema-mongodb.prisma
│   │   │   │   ├── schema-mysql-custom.prisma
│   │   │   │   ├── schema-mysql.prisma
│   │   │   │   ├── schema-numberid.prisma
│   │   │   │   └── schema.prisma
│   │   │   ├── generate-all-db.test.ts
│   │   │   ├── generate.test.ts
│   │   │   ├── get-config.test.ts
│   │   │   ├── info.test.ts
│   │   │   └── migrate.test.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── core
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── api
│   │   │   │   └── index.ts
│   │   │   ├── async_hooks
│   │   │   │   └── index.ts
│   │   │   ├── context
│   │   │   │   ├── endpoint-context.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── transaction.ts
│   │   │   ├── db
│   │   │   │   ├── adapter
│   │   │   │   │   └── index.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── plugin.ts
│   │   │   │   ├── schema
│   │   │   │   │   ├── account.ts
│   │   │   │   │   ├── rate-limit.ts
│   │   │   │   │   ├── session.ts
│   │   │   │   │   ├── shared.ts
│   │   │   │   │   ├── user.ts
│   │   │   │   │   └── verification.ts
│   │   │   │   └── type.ts
│   │   │   ├── env
│   │   │   │   ├── color-depth.ts
│   │   │   │   ├── env-impl.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── logger.test.ts
│   │   │   │   └── logger.ts
│   │   │   ├── error
│   │   │   │   ├── codes.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── oauth2
│   │   │   │   ├── client-credentials-token.ts
│   │   │   │   ├── create-authorization-url.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── oauth-provider.ts
│   │   │   │   ├── refresh-access-token.ts
│   │   │   │   ├── utils.ts
│   │   │   │   └── validate-authorization-code.ts
│   │   │   ├── social-providers
│   │   │   │   ├── apple.ts
│   │   │   │   ├── atlassian.ts
│   │   │   │   ├── cognito.ts
│   │   │   │   ├── discord.ts
│   │   │   │   ├── dropbox.ts
│   │   │   │   ├── facebook.ts
│   │   │   │   ├── figma.ts
│   │   │   │   ├── github.ts
│   │   │   │   ├── gitlab.ts
│   │   │   │   ├── google.ts
│   │   │   │   ├── huggingface.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── kakao.ts
│   │   │   │   ├── kick.ts
│   │   │   │   ├── line.ts
│   │   │   │   ├── linear.ts
│   │   │   │   ├── linkedin.ts
│   │   │   │   ├── microsoft-entra-id.ts
│   │   │   │   ├── naver.ts
│   │   │   │   ├── notion.ts
│   │   │   │   ├── paypal.ts
│   │   │   │   ├── reddit.ts
│   │   │   │   ├── roblox.ts
│   │   │   │   ├── salesforce.ts
│   │   │   │   ├── slack.ts
│   │   │   │   ├── spotify.ts
│   │   │   │   ├── tiktok.ts
│   │   │   │   ├── twitch.ts
│   │   │   │   ├── twitter.ts
│   │   │   │   ├── vk.ts
│   │   │   │   └── zoom.ts
│   │   │   ├── types
│   │   │   │   ├── context.ts
│   │   │   │   ├── cookie.ts
│   │   │   │   ├── helper.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── init-options.ts
│   │   │   │   ├── plugin-client.ts
│   │   │   │   └── plugin.ts
│   │   │   └── utils
│   │   │       ├── error-codes.ts
│   │   │       └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── expo
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── expo.test.ts
│   │   │   └── index.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── sso
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── index.ts
│   │   │   ├── oidc.test.ts
│   │   │   └── saml.test.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── stripe
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── client.ts
│   │   │   ├── hooks.ts
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   ├── stripe.test.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   └── vitest.config.ts
│   └── telemetry
│       ├── package.json
│       ├── src
│       │   ├── detectors
│       │   │   ├── detect-auth-config.ts
│       │   │   ├── detect-database.ts
│       │   │   ├── detect-framework.ts
│       │   │   ├── detect-project-info.ts
│       │   │   ├── detect-runtime.ts
│       │   │   └── detect-system-info.ts
│       │   ├── index.ts
│       │   ├── project-id.ts
│       │   ├── telemetry.test.ts
│       │   ├── types.ts
│       │   └── utils
│       │       ├── hash.ts
│       │       ├── id.ts
│       │       ├── import-util.ts
│       │       └── package-json.ts
│       ├── tsconfig.json
│       └── tsdown.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── SECURITY.md
├── tsconfig.base.json
├── tsconfig.json
└── turbo.json
```

# Files

--------------------------------------------------------------------------------
/docs/content/blogs/1-3.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "Better Auth 1.3"
  3 | description: "SSO with SAML, Multi Team Support, Additional Fields for Organization, Performance and more."
  4 | date: 2025-07-19
  5 | author:
  6 |   name: "Bereket Engida"
  7 |   avatar: "/avatars/beka.jpg"
  8 |   twitter: "iambereket"
  9 | image: "/release-og/1-3.png"
 10 | tags: ["1.3", "authentication", "oidc", "mcp", "sso", "organization"]
 11 | ---
 12 | 
 13 | ## Better Auth 1.3 Release
 14 | 
 15 | We're excited to announce the release of Better Auth 1.3. This release includes a lot of new features and improvements.
 16 | 
 17 | To upgrade, run:
 18 | 
 19 | ```package-install
 20 | npm install [email protected]
 21 | ```
 22 | 
 23 | ---
 24 | 
 25 | ## 🚀 Highlights
 26 | 
 27 | ### **SSO Plugin**
 28 | 
 29 | The SSO plugin has been moved to its own package and now supports both **OIDC** and **SAML 2.0**.
 30 | 
 31 | 👉 [Read the SSO docs](/docs/plugins/sso)
 32 | 
 33 | ```ts title="auth.ts"
 34 | import { betterAuth } from "better-auth";
 35 | import { sso } from "@better-auth/sso";
 36 | 
 37 | export const auth = betterAuth({
 38 |   plugins: [
 39 |     sso({
 40 |       oidc: {
 41 |         clientId: process.env.OIDC_CLIENT_ID!,
 42 |         clientSecret: process.env.OIDC_CLIENT_SECRET!,
 43 |       },
 44 |       saml: {
 45 |         entryPoint: "https://example.com/saml",
 46 |         issuer: "better-auth-example",
 47 |         certificate: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
 48 |       },
 49 |       providersLimit: async (user) => {
 50 |         const plan = await getUserPlan(user);
 51 |         return plan.name === "pro" ? 10 : 1;
 52 |       },
 53 |     }),
 54 |   ],
 55 | });
 56 | ```
 57 | 
 58 | ---
 59 | 
 60 | ### **OIDC & MCP Plugins – Now Stable**
 61 | 
 62 | Both OIDC and MCP plugins are production‑ready.
 63 | 
 64 | ✅ Features:
 65 | 
 66 | * Refresh token support in discovery & token endpoints
 67 | * JWKs and PKCE for public clients
 68 | * Trusted clients
 69 | * Encrypted & hashed client secrets
 70 | 
 71 | 👉 [Read OIDC docs](/docs/plugins/oidc-provider)
 72 | 👉 [Read MCP docs](/docs/plugins/mcp)
 73 | 
 74 | ```ts title="auth.ts"
 75 | import { mcp } from "better-auth/plugins";
 76 | 
 77 | export const auth = betterAuth({
 78 |   plugins: [
 79 |     mcp({
 80 |       loginPage: "/login",
 81 |     }),
 82 |   ],
 83 | });
 84 | ```
 85 | 
 86 | ---
 87 | 
 88 | ### **Stripe Plugin is now production ready**
 89 | 
 90 | The Stripe plugin is now stable and usage based pricing is coming very soon.
 91 | 
 92 | 👉 [Read Stripe docs](/docs/plugins/stripe)
 93 | 
 94 | 
 95 | ```ts title="auth.ts"
 96 | import { betterAuth } from "better-auth";
 97 | import { stripe } from "@better-auth/stripe";
 98 | 
 99 | export const auth = betterAuth({
100 |   plugins: [
101 |     stripe({
102 |       // ...
103 |     }),
104 |   ],
105 | });
106 | ```
107 | 
108 | ### **SIWE Plugin**
109 | 
110 | Native support for **Sign‑In with Ethereum**.
111 | 
112 | 👉 [Read SIWE docs](/docs/plugins/siwe)
113 | 
114 | ```ts title="auth.ts"
115 | import { siwe } from "better-auth/plugins";
116 | 
117 | export const auth = betterAuth({
118 |   plugins: [
119 |     siwe(),
120 |   ],
121 | });
122 | ```
123 | 
124 | ---
125 | 
126 | ### **New Social Providers**
127 | 
128 | We’ve added providers for **Notion, Slack, Linear, and Faceit**.
129 | 
130 | ```ts title="auth.ts"
131 | import { betterAuth } from "better-auth";
132 | 
133 | export const auth = betterAuth({
134 |   socialProviders: {
135 |     notion: { /* ... */ },
136 |     slack: { /* ... */ },
137 |     linear: { /* ... */ },
138 |     faceit: { /* ... */ },
139 |   },
140 | });
141 | ```
142 | 
143 | ---
144 | 
145 | ### **SvelteKit Cookie Helper Plugin**
146 | 
147 | Utilities for handling cookies in SvelteKit server actions.
148 | 
149 | <Callout type="warn">
150 |   Breaking change: `building` and `getRequestEvent` must now be passed in as props.
151 | </Callout>
152 | 
153 | ```ts title="auth.ts"
154 | import { betterAuth } from "better-auth";
155 | import { sveltekitCookies } from "better-auth/svelte-kit";
156 | import { getRequestEvent } from "$app/server";
157 | 
158 | export const auth = betterAuth({
159 |   plugins: [sveltekitCookies(getRequestEvent)],
160 | });
161 | ```
162 | 
163 | ---
164 | 
165 | ### **Email Verification on Sign‑In**
166 | 
167 | ```ts title="auth.ts"
168 | export const auth = betterAuth({
169 |   emailVerification: {
170 |     sendOnSignIn: true, // sends a verification email on sign‑in if the user isn’t verified
171 |   },
172 | });
173 | ```
174 | 
175 | ---
176 | 
177 | ### **Multi‑Team Support**
178 | 
179 | The organization plugin now supports members belonging to multiple teams.
180 | 
181 | **Breaking change:**
182 | `teamId` has been removed from the `member` table. A new `teamMembers` table is required.
183 | 
184 | ```ts title="auth.ts"
185 | export const auth = betterAuth({
186 |   plugins: [
187 |     organization({
188 |       // ...
189 |     }),
190 |   ],
191 | });
192 | ```
193 | 
194 | ```ts title="auth-client.ts"
195 | import { createAuthClient } from "better-auth/client";
196 | import { organizationClient } from "better-auth/client/plugins";
197 | import { auth } from "./auth";
198 | 
199 | export const authClient = createAuthClient({
200 |   // pass your auth instance to infer additional fields
201 |   plugins: [organizationClient({ $inferAuth: {} as typeof auth })], // [!code highlight]
202 | });
203 | ```
204 | 
205 | ---
206 | 
207 | ### **Additional Organization Fields**
208 | 
209 | Add custom fields to `organization`, `member`, and `invitation` models.
210 | 
211 | ```ts title="auth.ts"
212 | export const auth = betterAuth({
213 |   plugins: [
214 |     organization({
215 |       schema: {
216 |         organization: { additionalFields: { /* ... */ } },
217 |         member: { additionalFields: { /* ... */ } },
218 |         invitation: { additionalFields: { /* ... */ } },
219 |       },
220 |     }),
221 |   ],
222 | });
223 | ```
224 | 
225 | Other new options:
226 | 
227 | * `maximumMembersPerTeam` – set team member limits
228 | * `listUserInvitations` – list all invitations for a user
229 | 
230 | ---
231 | 
232 | ### **Generic OAuth Improvements**
233 | 
234 | * Added support for extra token URL params
235 | * OAuth token encryption options
236 | 
237 | ```ts title="auth.ts"
238 | export const auth = betterAuth({
239 |   plugins: [
240 |     genericOAuth({
241 |       // ...
242 |     }),
243 |   ],
244 | });
245 | ```
246 | 
247 | ---
248 | 
249 | ### **API Keys**
250 | 
251 | * `requireName` option for key creation
252 | * `verifyKey` now supports async functions
253 | 
254 | ---
255 | 
256 | ### **Username**
257 | 
258 | * Availability checks
259 | * Custom normalization
260 | 
261 | ---
262 | 
263 | ### ✨ More Features
264 | 
265 | * Migrated to **Zod 4** for better type safety and performance
266 | * CLI supports custom adapter `createSchema`
267 | * `inferAuth` utility to infer types from the client
268 | * Improved docs with `auth` and `authClient` examples
269 | * `rememberMe` support in `signUp`
270 | * `afterEmailVerification` hook
271 | * `freshAge` and custom `errorURL` respected properly
272 | * OAuth2 tokens now include `refresh_token_expires_in`
273 | 
274 | ---
275 | 
276 | ### 🐛 Bug Fixes & Improvements
277 | 
278 | #### Plugins
279 | 
280 | * Expo: Fixed type path import
281 | * SSO: Fixed SAML redirection & type checks
282 | * Dropbox: Token access type support
283 | * Stripe:
284 | 
285 |   * Prevent duplicate customers
286 |   * Allow upgrading incomplete subscriptions
287 | * Admin:
288 | 
289 |   * Fixed missing `ctx` in hooks
290 |   * Proper error when removing invalid user IDs
291 | 
292 | #### OAuth & Providers
293 | 
294 | * Fixed duplicate OAuth registration
295 | * Improved Google/Microsoft scope handling
296 | * Fixed malformed error URLs in generic OAuth
297 | * Facebook: Better detection for limited token JWT
298 | * Twitter: Improved email verification logic
299 | 
300 | #### Core Authentication
301 | 
302 | * Exclude current user from username uniqueness check
303 | * Support `callbackURL` in `signInUsername`
304 | * Allow account linking without email
305 | * Fixed missing `null` type in `/get-session` response
306 | * Global `onSuccess` hook now works
307 | * JWT: Alternate algorithms supported in JWKS
308 | * `origin-check`: Wildcard trusted origins supported
309 | 
310 | #### CLI, DB, and Adapters
311 | 
312 | * CLI: Improved Drizzle schema formatting
313 | * MongoAdapter: Works with `create-adapter`
314 | * Schema generation respects `useNumberId`
315 | * Postgres: Better varchar normalization and type comparison
316 | * Drizzle CLI: Uses `serial` as PK if `useNumberId` is enabled
317 | 
318 | #### Email & OTP
319 | 
320 | * OTPs now encrypted
321 | * Fixed `onEmailVerification` not firing
322 | * Proper error when sign‑up is disabled
323 | * Phone number: Reset clears verification values
324 | 
325 | #### Two-Factor Auth
326 | 
327 | * Default OTP period fix
328 | * URI generation doesn’t require enabling 2FA
329 | * Fixed OTP URI separator mismatch
330 | 
331 | #### Miscellaneous
332 | 
333 | * Delete organization if member not found
334 | * Correct error codes for API key rate limits
335 | * Additional fields now show in OpenAPI
336 | * Fixed FK constraint generation for MySQL
337 | * Various improvements to account linking
338 | * OIDC `offline_access` no longer requires `prompt=consent`
339 | * Fixed malformed base64 encoding for token validation
340 | 
341 | ---
342 | 
343 | A lot of refinements to make everything smoother, faster, and more reliable.
344 | 👉 [Check the full changelog](https://github.com/better-auth/better-auth/releases/tag/v1.3.0)
345 | 
346 | ---
347 | 
```

--------------------------------------------------------------------------------
/docs/components/builder/code-tabs/code-tabs.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { cn } from "@/lib/utils";
 2 | interface TabProps {
 3 | 	fileName: string;
 4 | 	isActive: boolean;
 5 | 	brightnessLevel?: number; // New optional prop for brightness level
 6 | 	onClick: () => void;
 7 | 	onClose: () => void;
 8 | }
 9 | 
10 | const brightnessLevels = [
11 | 	"bg-background",
12 | 	"bg-background-200", //
13 | 	"bg-background-300",
14 | 	"bg-background-400",
15 | 	"bg-background-500",
16 | 	"bg-background-600",
17 | 	"bg-background-700",
18 | ];
19 | 
20 | export function CodeTab({
21 | 	fileName,
22 | 	isActive,
23 | 	brightnessLevel = 0,
24 | 	onClick,
25 | 	onClose,
26 | }: TabProps) {
27 | 	const activeBrightnessClass = isActive
28 | 		? brightnessLevels[brightnessLevel % brightnessLevels.length]
29 | 		: "bg-muted";
30 | 
31 | 	const textColor = isActive ? "text-foreground" : "text-muted-foreground";
32 | 	const borderColor = isActive
33 | 		? "border-t-foreground"
34 | 		: "border-t-transparent hover:bg-background/50";
35 | 
36 | 	return (
37 | 		<div
38 | 			className={cn(
39 | 				"flex items-center px-3 py-2 gap-2 text-sm font-medium border-t-2 cursor-pointer transition-colors duration-200",
40 | 				activeBrightnessClass,
41 | 				textColor,
42 | 				borderColor,
43 | 			)}
44 | 			onClick={onClick}
45 | 		>
46 | 			{fileName.endsWith(".ts") && (
47 | 				<svg
48 | 					xmlns="http://www.w3.org/2000/svg"
49 | 					width="1em"
50 | 					height="1em"
51 | 					viewBox="0 0 128 128"
52 | 				>
53 | 					<path
54 | 						className="fill-current"
55 | 						d="M2 63.91v62.5h125v-125H2zm100.73-5a15.56 15.56 0 0 1 7.82 4.5a20.6 20.6 0 0 1 3 4c0 .16-5.4 3.81-8.69 5.85c-.12.08-.6-.44-1.13-1.23a7.09 7.09 0 0 0-5.87-3.53c-3.79-.26-6.23 1.73-6.21 5a4.6 4.6 0 0 0 .54 2.34c.83 1.73 2.38 2.76 7.24 4.86c8.95 3.85 12.78 6.39 15.16 10c2.66 4 3.25 10.46 1.45 15.24c-2 5.2-6.9 8.73-13.83 9.9a38.3 38.3 0 0 1-9.52-.1A23 23 0 0 1 80 109.19c-1.15-1.27-3.39-4.58-3.25-4.82a9 9 0 0 1 1.15-.73l4.6-2.64l3.59-2.08l.75 1.11a16.8 16.8 0 0 0 4.74 4.54c4 2.1 9.46 1.81 12.16-.62a5.43 5.43 0 0 0 .69-6.92c-1-1.39-3-2.56-8.59-5c-6.45-2.78-9.23-4.5-11.77-7.24a16.5 16.5 0 0 1-3.43-6.25a25 25 0 0 1-.22-8c1.33-6.23 6-10.58 12.82-11.87a31.7 31.7 0 0 1 9.49.26zm-29.34 5.24v5.12H57.16v46.23H45.65V69.26H29.38v-5a49 49 0 0 1 .14-5.16c.06-.08 10-.12 22-.1h21.81z"
56 | 					></path>
57 | 				</svg>
58 | 			)}
59 | 			{fileName.endsWith(".tsx") && (
60 | 				<svg
61 | 					fill="currentColor"
62 | 					xmlns="http://www.w3.org/2000/svg"
63 | 					width="1.3em"
64 | 					height="1.3em"
65 | 					viewBox="0 0 30 30"
66 | 				>
67 | 					<path
68 | 						className="fill-current"
69 | 						d="M 10.679688 4.1816406 C 10.068687 4.1816406 9.502 4.3184219 9 4.6074219 C 7.4311297 5.5132122 6.8339651 7.7205462 7.1503906 10.46875 C 4.6127006 11.568833 3 13.188667 3 15 C 3 16.811333 4.6127006 18.431167 7.1503906 19.53125 C 6.8341285 22.279346 7.4311297 24.486788 9 25.392578 C 9.501 25.681578 10.067687 25.818359 10.679688 25.818359 C 11.982314 25.818359 13.48785 25.164589 15 24.042969 C 16.512282 25.164589 18.01964 25.818359 19.322266 25.818359 C 19.933266 25.818359 20.499953 25.681578 21.001953 25.392578 C 22.570814 24.486793 23.167976 22.279432 22.851562 19.53125 C 25.388297 18.431178 27 16.81094 27 15 C 27 13.188667 25.387299 11.568833 22.849609 10.46875 C 23.165872 7.7206538 22.56887 5.5132122 21 4.6074219 C 20.499 4.3174219 19.932312 4.1816406 19.320312 4.1816406 C 18.017686 4.1816406 16.51215 4.8354109 15 5.9570312 C 13.487763 4.8354109 11.981863 4.1816406 10.679688 4.1816406 z M 10.679688 5.9316406 C 11.461321 5.9316406 12.49496 6.3472486 13.617188 7.1171875 C 12.95737 7.7398717 12.311153 8.4479321 11.689453 9.2363281 C 10.681079 9.3809166 9.7303472 9.5916908 8.8496094 9.8554688 C 8.8448793 9.7943902 8.8336776 9.7303008 8.8300781 9.6699219 C 8.7230781 7.8899219 9.114 6.5630469 9.875 6.1230469 C 10.1 5.9930469 10.362688 5.9316406 10.679688 5.9316406 z M 19.320312 5.9316406 C 19.636312 5.9316406 19.9 5.9930469 20.125 6.1230469 C 20.886 6.5620469 21.276922 7.8899219 21.169922 9.6699219 C 21.166295 9.7303008 21.155145 9.7943902 21.150391 9.8554688 C 20.2691 9.5915252 19.317669 9.3809265 18.308594 9.2363281 C 17.686902 8.4480417 17.042616 7.7397993 16.382812 7.1171875 C 17.504962 6.3473772 18.539083 5.9316406 19.320312 5.9316406 z M 15 8.2285156 C 15.27108 8.4752506 15.540266 8.7360345 15.8125 9.0214844 C 15.542718 9.012422 15.274373 9 15 9 C 14.726286 9 14.458598 9.0124652 14.189453 9.0214844 C 14.461446 8.7363308 14.729174 8.4750167 15 8.2285156 z M 15 10.75 C 15.828688 10.75 16.614128 10.796321 17.359375 10.876953 C 17.813861 11.494697 18.261774 12.147811 18.681641 12.875 C 19.084074 13.572033 19.439938 14.285488 19.753906 15 C 19.439896 15.714942 19.084316 16.429502 18.681641 17.126953 C 18.263078 17.852044 17.816279 18.500949 17.363281 19.117188 C 16.591711 19.201607 15.800219 19.25 15 19.25 C 14.171312 19.25 13.385872 19.203679 12.640625 19.123047 C 12.186139 18.505303 11.738226 17.854142 11.318359 17.126953 C 10.915684 16.429502 10.560194 15.714942 10.246094 15 C 10.559972 14.285488 10.915926 13.572033 11.318359 12.875 C 11.737083 12.149909 12.183612 11.499051 12.636719 10.882812 C 13.408289 10.798393 14.199781 10.75 15 10.75 z M 19.746094 11.291016 C 20.142841 11.386804 20.524253 11.490209 20.882812 11.605469 C 20.801579 11.97252 20.702235 12.346608 20.589844 12.724609 C 20.461164 12.483141 20.336375 12.240903 20.197266 12 C 20.054139 11.752196 19.895244 11.529558 19.746094 11.291016 z M 10.251953 11.292969 C 10.103305 11.530776 9.9454023 11.752991 9.8027344 12 C 9.6636666 12.240944 9.5387971 12.483106 9.4101562 12.724609 C 9.29751 12.345829 9.1965499 11.971295 9.1152344 11.603516 C 9.4803698 11.48815 9.86083 11.385986 10.251953 11.292969 z M 7.46875 12.246094 C 7.6794464 13.135714 7.9717297 14.057918 8.3476562 14.998047 C 7.9725263 15.935943 7.6814729 16.856453 7.4707031 17.744141 C 5.7292327 16.903203 4.75 15.856373 4.75 15 C 4.75 14.121 5.701875 13.119266 7.296875 12.322266 C 7.3513169 12.295031 7.4131225 12.272692 7.46875 12.246094 z M 22.529297 12.255859 C 24.270767 13.096797 25.25 14.143627 25.25 15 C 25.25 15.879 24.298125 16.880734 22.703125 17.677734 C 22.648683 17.704969 22.586877 17.727308 22.53125 17.753906 C 22.32043 16.863764 22.030541 15.940699 21.654297 15 C 22.028977 14.062913 22.318703 13.142804 22.529297 12.255859 z M 15 13 C 13.895 13 13 13.895 13 15 C 13 16.105 13.895 17 15 17 C 16.105 17 17 16.105 17 15 C 17 13.895 16.105 13 15 13 z M 9.4101562 17.275391 C 9.5388794 17.516948 9.6655262 17.759008 9.8046875 18 C 9.9476585 18.247625 10.104915 18.470608 10.253906 18.708984 C 9.857159 18.613196 9.4757466 18.509791 9.1171875 18.394531 C 9.1984813 18.02725 9.2976676 17.653633 9.4101562 17.275391 z M 20.589844 17.277344 C 20.702364 17.655759 20.803517 18.02905 20.884766 18.396484 C 20.51963 18.51185 20.13917 18.614014 19.748047 18.707031 C 19.896695 18.469224 20.054598 18.247009 20.197266 18 C 20.336044 17.759557 20.461449 17.518344 20.589844 17.277344 z M 8.8496094 20.144531 C 9.7309004 20.408475 10.682331 20.619073 11.691406 20.763672 C 12.313288 21.552345 12.957085 22.261935 13.617188 22.884766 C 12.495042 23.654481 11.461272 24.070312 10.679688 24.070312 C 10.363687 24.070312 10.1 24.006953 9.875 23.876953 C 9.114 23.437953 8.7230781 22.112031 8.8300781 20.332031 C 8.8337424 20.271023 8.8447938 20.206253 8.8496094 20.144531 z M 21.150391 20.144531 C 21.155182 20.206253 21.166285 20.271023 21.169922 20.332031 C 21.276922 22.112031 20.886 23.436953 20.125 23.876953 C 19.9 24.006953 19.637312 24.070313 19.320312 24.070312 C 18.538728 24.070312 17.504958 23.654609 16.382812 22.884766 C 17.042964 22.261863 17.688542 21.552454 18.310547 20.763672 C 19.318921 20.619083 20.269653 20.408309 21.150391 20.144531 z M 14.1875 20.978516 C 14.457282 20.987578 14.725627 21 15 21 C 15.274373 21 15.542718 20.987578 15.8125 20.978516 C 15.540266 21.263964 15.27108 21.524765 15 21.771484 C 14.72892 21.524749 14.459734 21.263966 14.1875 20.978516 z"
70 | 					></path>
71 | 				</svg>
72 | 			)}
73 | 			<span className="truncate max-w-[100px]">{fileName}</span>
74 | 		</div>
75 | 	);
76 | }
77 | 
```

--------------------------------------------------------------------------------
/docs/components/docs/layout/toc.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | import type { TOCItemType } from "fumadocs-core/server";
  3 | import * as Primitive from "fumadocs-core/toc";
  4 | import {
  5 | 	type ComponentProps,
  6 | 	createContext,
  7 | 	type HTMLAttributes,
  8 | 	type ReactNode,
  9 | 	use,
 10 | 	useEffect,
 11 | 	useMemo,
 12 | 	useRef,
 13 | 	useState,
 14 | } from "react";
 15 | import { cn } from "@/lib/utils";
 16 | import { useI18n } from "fumadocs-ui/provider";
 17 | import { TocThumb } from "./toc-thumb";
 18 | import { ScrollArea, ScrollViewport } from "../ui/scroll-area";
 19 | import type {
 20 | 	PopoverContentProps,
 21 | 	PopoverTriggerProps,
 22 | } from "@radix-ui/react-popover";
 23 | import { ChevronRight, Text } from "lucide-react";
 24 | import { usePageStyles } from "fumadocs-ui/provider";
 25 | import {
 26 | 	Collapsible,
 27 | 	CollapsibleContent,
 28 | 	CollapsibleTrigger,
 29 | } from "../ui/collapsible";
 30 | 
 31 | export interface TOCProps {
 32 | 	/**
 33 | 	 * Custom content in TOC container, before the main TOC
 34 | 	 */
 35 | 	header?: ReactNode;
 36 | 
 37 | 	/**
 38 | 	 * Custom content in TOC container, after the main TOC
 39 | 	 */
 40 | 	footer?: ReactNode;
 41 | 
 42 | 	children: ReactNode;
 43 | }
 44 | 
 45 | export function Toc(props: HTMLAttributes<HTMLDivElement>) {
 46 | 	const { toc } = usePageStyles();
 47 | 
 48 | 	return (
 49 | 		<div
 50 | 			id="nd-toc"
 51 | 			{...props}
 52 | 			className={cn(
 53 | 				"sticky top-[calc(var(--fd-banner-height)+var(--fd-nav-height))] h-(--fd-toc-height) pb-2 pt-12",
 54 | 				toc,
 55 | 				props.className,
 56 | 			)}
 57 | 			style={
 58 | 				{
 59 | 					...props.style,
 60 | 					"--fd-toc-height":
 61 | 						"calc(100dvh - var(--fd-banner-height) - var(--fd-nav-height))",
 62 | 				} as object
 63 | 			}
 64 | 		>
 65 | 			<div className="flex h-full w-(--fd-toc-width) max-w-full flex-col gap-3 pe-4">
 66 | 				{props.children}
 67 | 			</div>
 68 | 		</div>
 69 | 	);
 70 | }
 71 | 
 72 | export function TocItemsEmpty() {
 73 | 	const { text } = useI18n();
 74 | 
 75 | 	return (
 76 | 		<div className="rounded-lg border bg-fd-card p-3 text-xs text-fd-muted-foreground">
 77 | 			{text.tocNoHeadings}
 78 | 		</div>
 79 | 	);
 80 | }
 81 | 
 82 | export function TOCScrollArea({
 83 | 	isMenu,
 84 | 	...props
 85 | }: ComponentProps<typeof ScrollArea> & { isMenu?: boolean }) {
 86 | 	const viewRef = useRef<HTMLDivElement>(null);
 87 | 
 88 | 	return (
 89 | 		<ScrollArea
 90 | 			{...props}
 91 | 			className={cn("flex flex-col ps-px", props.className)}
 92 | 		>
 93 | 			<Primitive.ScrollProvider containerRef={viewRef}>
 94 | 				<ScrollViewport
 95 | 					className={cn(
 96 | 						"relative min-h-0 text-sm",
 97 | 						isMenu && "mt-2 mb-4 mx-4 md:mx-6",
 98 | 					)}
 99 | 					ref={viewRef}
100 | 				>
101 | 					{props.children}
102 | 				</ScrollViewport>
103 | 			</Primitive.ScrollProvider>
104 | 		</ScrollArea>
105 | 	);
106 | }
107 | 
108 | export function TOCItems({ items }: { items: TOCItemType[] }) {
109 | 	const containerRef = useRef<HTMLDivElement>(null);
110 | 
111 | 	const [svg, setSvg] = useState<{
112 | 		path: string;
113 | 		width: number;
114 | 		height: number;
115 | 	}>();
116 | 
117 | 	useEffect(() => {
118 | 		if (!containerRef.current) return;
119 | 		const container = containerRef.current;
120 | 
121 | 		function onResize(): void {
122 | 			if (container.clientHeight === 0) return;
123 | 			let w = 0,
124 | 				h = 0;
125 | 			const d: string[] = [];
126 | 			for (let i = 0; i < items.length; i++) {
127 | 				const element: HTMLElement | null = container.querySelector(
128 | 					`a[href="#${items[i].url.slice(1)}"]`,
129 | 				);
130 | 				if (!element) continue;
131 | 
132 | 				const styles = getComputedStyle(element);
133 | 				const offset = getLineOffset(items[i].depth) + 1,
134 | 					top = element.offsetTop + parseFloat(styles.paddingTop),
135 | 					bottom =
136 | 						element.offsetTop +
137 | 						element.clientHeight -
138 | 						parseFloat(styles.paddingBottom);
139 | 
140 | 				w = Math.max(offset, w);
141 | 				h = Math.max(h, bottom);
142 | 
143 | 				d.push(`${i === 0 ? "M" : "L"}${offset} ${top}`);
144 | 				d.push(`L${offset} ${bottom}`);
145 | 			}
146 | 
147 | 			setSvg({
148 | 				path: d.join(" "),
149 | 				width: w + 1,
150 | 				height: h,
151 | 			});
152 | 		}
153 | 
154 | 		const observer = new ResizeObserver(onResize);
155 | 		onResize();
156 | 
157 | 		observer.observe(container);
158 | 		return () => {
159 | 			observer.disconnect();
160 | 		};
161 | 	}, [items]);
162 | 
163 | 	if (items.length === 0) return <TocItemsEmpty />;
164 | 
165 | 	return (
166 | 		<>
167 | 			{svg ? (
168 | 				<div
169 | 					className="absolute start-0 top-0 rtl:-scale-x-100"
170 | 					style={{
171 | 						width: svg.width,
172 | 						height: svg.height,
173 | 						maskImage: `url("data:image/svg+xml,${
174 | 							// Inline SVG
175 | 							encodeURIComponent(
176 | 								`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svg.width} ${svg.height}"><path d="${svg.path}" stroke="black" stroke-width="1" fill="none" /></svg>`,
177 | 							)
178 | 						}")`,
179 | 					}}
180 | 				>
181 | 					<TocThumb
182 | 						containerRef={containerRef}
183 | 						className="mt-(--fd-top) h-(--fd-height) bg-fd-primary transition-all"
184 | 					/>
185 | 				</div>
186 | 			) : null}
187 | 			<div className="flex flex-col" ref={containerRef}>
188 | 				{items.map((item, i) => (
189 | 					<TOCItem
190 | 						key={item.url}
191 | 						item={item}
192 | 						upper={items[i - 1]?.depth}
193 | 						lower={items[i + 1]?.depth}
194 | 					/>
195 | 				))}
196 | 			</div>
197 | 		</>
198 | 	);
199 | }
200 | 
201 | function getItemOffset(depth: number): number {
202 | 	if (depth <= 2) return 14;
203 | 	if (depth === 3) return 26;
204 | 	return 36;
205 | }
206 | 
207 | function getLineOffset(depth: number): number {
208 | 	return depth >= 3 ? 10 : 0;
209 | }
210 | 
211 | function TOCItem({
212 | 	item,
213 | 	upper = item.depth,
214 | 	lower = item.depth,
215 | }: {
216 | 	item: TOCItemType;
217 | 	upper?: number;
218 | 	lower?: number;
219 | }) {
220 | 	const offset = getLineOffset(item.depth),
221 | 		upperOffset = getLineOffset(upper),
222 | 		lowerOffset = getLineOffset(lower);
223 | 
224 | 	return (
225 | 		<Primitive.TOCItem
226 | 			href={item.url}
227 | 			style={{
228 | 				paddingInlineStart: getItemOffset(item.depth),
229 | 			}}
230 | 			className="prose relative py-1.5 text-sm text-fd-muted-foreground transition-colors [overflow-wrap:anywhere] first:pt-0 last:pb-0 data-[active=true]:text-fd-primary"
231 | 		>
232 | 			{offset !== upperOffset ? (
233 | 				<svg
234 | 					xmlns="http://www.w3.org/2000/svg"
235 | 					viewBox="0 0 16 16"
236 | 					className="absolute -top-1.5 start-0 size-4 rtl:-scale-x-100"
237 | 				>
238 | 					<line
239 | 						x1={upperOffset}
240 | 						y1="0"
241 | 						x2={offset}
242 | 						y2="12"
243 | 						className="stroke-fd-foreground/10"
244 | 						strokeWidth="1"
245 | 					/>
246 | 				</svg>
247 | 			) : null}
248 | 			<div
249 | 				className={cn(
250 | 					"absolute inset-y-0 w-px bg-fd-foreground/10",
251 | 					offset !== upperOffset && "top-1.5",
252 | 					offset !== lowerOffset && "bottom-1.5",
253 | 				)}
254 | 				style={{
255 | 					insetInlineStart: offset,
256 | 				}}
257 | 			/>
258 | 			{item.title}
259 | 		</Primitive.TOCItem>
260 | 	);
261 | }
262 | 
263 | type MakeRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
264 | 
265 | const Context = createContext<{
266 | 	open: boolean;
267 | 	setOpen: (open: boolean) => void;
268 | } | null>(null);
269 | 
270 | const TocProvider = Context.Provider || Context;
271 | 
272 | export function TocPopover({
273 | 	open,
274 | 	onOpenChange,
275 | 	ref: _ref,
276 | 	...props
277 | }: MakeRequired<ComponentProps<typeof Collapsible>, "open" | "onOpenChange">) {
278 | 	return (
279 | 		<Collapsible open={open} onOpenChange={onOpenChange} {...props}>
280 | 			<TocProvider
281 | 				value={useMemo(
282 | 					() => ({
283 | 						open,
284 | 						setOpen: onOpenChange,
285 | 					}),
286 | 					[onOpenChange, open],
287 | 				)}
288 | 			>
289 | 				{props.children}
290 | 			</TocProvider>
291 | 		</Collapsible>
292 | 	);
293 | }
294 | 
295 | export function TocPopoverTrigger({
296 | 	items,
297 | 	...props
298 | }: PopoverTriggerProps & { items: TOCItemType[] }) {
299 | 	const { text } = useI18n();
300 | 	const { open } = use(Context)!;
301 | 	const active = Primitive.useActiveAnchor();
302 | 	const current = useMemo(() => {
303 | 		return items.find((item) => active === item.url.slice(1))?.title;
304 | 	}, [items, active]);
305 | 
306 | 	return (
307 | 		<CollapsibleTrigger
308 | 			{...props}
309 | 			className={cn(
310 | 				"inline-flex items-center text-sm gap-2 text-nowrap px-4 py-2.5 text-start md:px-6 focus-visible:outline-none",
311 | 				props.className,
312 | 			)}
313 | 		>
314 | 			<Text className="size-4 shrink-0" />
315 | 			{text.toc}
316 | 			<ChevronRight
317 | 				className={cn(
318 | 					"size-4 shrink-0 text-fd-muted-foreground transition-all",
319 | 					!current && "opacity-0",
320 | 					open ? "rotate-90" : "-ms-1.5",
321 | 				)}
322 | 			/>
323 | 			<span
324 | 				className={cn(
325 | 					"truncate text-fd-muted-foreground transition-opacity -ms-1.5",
326 | 					(!current || open) && "opacity-0",
327 | 				)}
328 | 			>
329 | 				{current}
330 | 			</span>
331 | 		</CollapsibleTrigger>
332 | 	);
333 | }
334 | 
335 | export function TocPopoverContent(props: PopoverContentProps) {
336 | 	return (
337 | 		<CollapsibleContent
338 | 			data-toc-popover=""
339 | 			className="flex flex-col max-h-[50vh]"
340 | 			{...props}
341 | 		>
342 | 			{props.children}
343 | 		</CollapsibleContent>
344 | 	);
345 | }
346 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/test-utils/test-instance.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { AsyncLocalStorage } from "node:async_hooks";
  2 | import { afterAll } from "vitest";
  3 | import { betterAuth } from "../auth";
  4 | import { createAuthClient } from "../client/vanilla";
  5 | import type { Session, User } from "../types";
  6 | import type { BetterAuthClientOptions } from "@better-auth/core";
  7 | import { getMigrations } from "../db/get-migration";
  8 | import { parseSetCookieHeader, setCookieToHeader } from "../cookies";
  9 | import type { SuccessContext } from "@better-fetch/fetch";
 10 | import { getAdapter } from "../db/utils";
 11 | import Database from "better-sqlite3";
 12 | import { getBaseURL } from "../utils/url";
 13 | import { Kysely, MysqlDialect, PostgresDialect, sql } from "kysely";
 14 | import { Pool } from "pg";
 15 | import { MongoClient } from "mongodb";
 16 | import { mongodbAdapter } from "../adapters/mongodb-adapter";
 17 | import { createPool } from "mysql2/promise";
 18 | import { bearer } from "../plugins";
 19 | import type { BetterAuthOptions } from "@better-auth/core";
 20 | 
 21 | const cleanupSet = new Set<Function>();
 22 | 
 23 | type CurrentUserContext = {
 24 | 	headers: Headers;
 25 | };
 26 | const currentUserContextStorage = new AsyncLocalStorage<CurrentUserContext>();
 27 | 
 28 | afterAll(async () => {
 29 | 	for (const cleanup of cleanupSet) {
 30 | 		await cleanup();
 31 | 		cleanupSet.delete(cleanup);
 32 | 	}
 33 | });
 34 | 
 35 | export async function getTestInstance<
 36 | 	O extends Partial<BetterAuthOptions>,
 37 | 	C extends BetterAuthClientOptions,
 38 | >(
 39 | 	options?: O,
 40 | 	config?: {
 41 | 		clientOptions?: C;
 42 | 		port?: number;
 43 | 		disableTestUser?: boolean;
 44 | 		testUser?: Partial<User>;
 45 | 		testWith?: "sqlite" | "postgres" | "mongodb" | "mysql";
 46 | 	},
 47 | ) {
 48 | 	const testWith = config?.testWith || "sqlite";
 49 | 
 50 | 	const postgres = new Kysely({
 51 | 		dialect: new PostgresDialect({
 52 | 			pool: new Pool({
 53 | 				connectionString: "postgres://user:password@localhost:5432/better_auth",
 54 | 			}),
 55 | 		}),
 56 | 	});
 57 | 
 58 | 	const sqlite = new Database(":memory:");
 59 | 
 60 | 	const mysql = new Kysely({
 61 | 		dialect: new MysqlDialect(
 62 | 			createPool("mysql://user:password@localhost:3306/better_auth"),
 63 | 		),
 64 | 	});
 65 | 
 66 | 	async function mongodbClient() {
 67 | 		const dbClient = async (connectionString: string, dbName: string) => {
 68 | 			const client = new MongoClient(connectionString);
 69 | 			await client.connect();
 70 | 			const db = client.db(dbName);
 71 | 			return db;
 72 | 		};
 73 | 		const db = await dbClient("mongodb://127.0.0.1:27017", "better-auth");
 74 | 		return db;
 75 | 	}
 76 | 
 77 | 	const opts = {
 78 | 		socialProviders: {
 79 | 			github: {
 80 | 				clientId: "test",
 81 | 				clientSecret: "test",
 82 | 			},
 83 | 			google: {
 84 | 				clientId: "test",
 85 | 				clientSecret: "test",
 86 | 			},
 87 | 		},
 88 | 		secret: "better-auth.secret",
 89 | 		database:
 90 | 			testWith === "postgres"
 91 | 				? { db: postgres, type: "postgres" }
 92 | 				: testWith === "mongodb"
 93 | 					? mongodbAdapter(await mongodbClient())
 94 | 					: testWith === "mysql"
 95 | 						? { db: mysql, type: "mysql" }
 96 | 						: sqlite,
 97 | 		emailAndPassword: {
 98 | 			enabled: true,
 99 | 		},
100 | 		rateLimit: {
101 | 			enabled: false,
102 | 		},
103 | 		advanced: {
104 | 			cookies: {},
105 | 		},
106 | 		logger: {
107 | 			level: "debug",
108 | 		},
109 | 	} satisfies BetterAuthOptions;
110 | 
111 | 	const auth = betterAuth({
112 | 		baseURL: "http://localhost:" + (config?.port || 3000),
113 | 		...opts,
114 | 		...options,
115 | 		plugins: [bearer(), ...(options?.plugins || [])],
116 | 	} as unknown as O);
117 | 
118 | 	const testUser = {
119 | 		email: "[email protected]",
120 | 		password: "test123456",
121 | 		name: "test user",
122 | 		...config?.testUser,
123 | 	};
124 | 	async function createTestUser() {
125 | 		if (config?.disableTestUser) {
126 | 			return;
127 | 		}
128 | 		//@ts-expect-error
129 | 		await auth.api.signUpEmail({
130 | 			body: testUser,
131 | 		});
132 | 	}
133 | 
134 | 	if (testWith !== "mongodb") {
135 | 		const { runMigrations } = await getMigrations({
136 | 			...auth.options,
137 | 			database: opts.database,
138 | 		});
139 | 		await runMigrations();
140 | 	}
141 | 
142 | 	await createTestUser();
143 | 
144 | 	const cleanup = async () => {
145 | 		if (testWith === "mongodb") {
146 | 			const db = await mongodbClient();
147 | 			await db.dropDatabase();
148 | 			return;
149 | 		}
150 | 		if (testWith === "postgres") {
151 | 			await sql`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`.execute(
152 | 				postgres,
153 | 			);
154 | 			await postgres.destroy();
155 | 			return;
156 | 		}
157 | 
158 | 		if (testWith === "mysql") {
159 | 			await sql`SET FOREIGN_KEY_CHECKS = 0;`.execute(mysql);
160 | 			const tables = await mysql.introspection.getTables();
161 | 			for (const table of tables) {
162 | 				// @ts-expect-error
163 | 				await mysql.deleteFrom(table.name).execute();
164 | 			}
165 | 			await sql`SET FOREIGN_KEY_CHECKS = 1;`.execute(mysql);
166 | 			return;
167 | 		}
168 | 		if (testWith === "sqlite") {
169 | 			sqlite.close();
170 | 			return;
171 | 		}
172 | 	};
173 | 	cleanupSet.add(cleanup);
174 | 
175 | 	const customFetchImpl = async (
176 | 		url: string | URL | Request,
177 | 		init?: RequestInit,
178 | 	) => {
179 | 		const headers = init?.headers || {};
180 | 		const storageHeaders = currentUserContextStorage.getStore()?.headers;
181 | 		return auth.handler(
182 | 			new Request(
183 | 				url,
184 | 				init
185 | 					? {
186 | 							...init,
187 | 							headers: new Headers({
188 | 								...(storageHeaders
189 | 									? Object.fromEntries(storageHeaders.entries())
190 | 									: {}),
191 | 								...(headers instanceof Headers
192 | 									? Object.fromEntries(headers.entries())
193 | 									: typeof headers === "object"
194 | 										? headers
195 | 										: {}),
196 | 							}),
197 | 						}
198 | 					: {
199 | 							headers,
200 | 						},
201 | 			),
202 | 		);
203 | 	};
204 | 
205 | 	const client = createAuthClient({
206 | 		...(config?.clientOptions as C extends undefined ? {} : C),
207 | 		baseURL: getBaseURL(
208 | 			options?.baseURL || "http://localhost:" + (config?.port || 3000),
209 | 			options?.basePath || "/api/auth",
210 | 		),
211 | 		fetchOptions: {
212 | 			customFetchImpl,
213 | 		},
214 | 	});
215 | 
216 | 	async function signInWithTestUser() {
217 | 		if (config?.disableTestUser) {
218 | 			throw new Error("Test user is disabled");
219 | 		}
220 | 		let headers = new Headers();
221 | 		const setCookie = (name: string, value: string) => {
222 | 			const current = headers.get("cookie");
223 | 			headers.set("cookie", `${current || ""}; ${name}=${value}`);
224 | 		};
225 | 		//@ts-expect-error
226 | 		const { data, error } = await client.signIn.email({
227 | 			email: testUser.email,
228 | 			password: testUser.password,
229 | 			fetchOptions: {
230 | 				//@ts-expect-error
231 | 				onSuccess(context) {
232 | 					const header = context.response.headers.get("set-cookie");
233 | 					const cookies = parseSetCookieHeader(header || "");
234 | 					const signedCookie = cookies.get("better-auth.session_token")?.value;
235 | 					headers.set("cookie", `better-auth.session_token=${signedCookie}`);
236 | 				},
237 | 			},
238 | 		});
239 | 		return {
240 | 			session: data.session as Session,
241 | 			user: data.user as User,
242 | 			headers,
243 | 			setCookie,
244 | 			runWithUser: async (fn: (headers: Headers) => Promise<void>) => {
245 | 				return currentUserContextStorage.run({ headers }, async () => {
246 | 					await fn(headers);
247 | 				});
248 | 			},
249 | 		};
250 | 	}
251 | 	async function signInWithUser(email: string, password: string) {
252 | 		const headers = new Headers();
253 | 		//@ts-expect-error
254 | 		const { data } = await client.signIn.email({
255 | 			email,
256 | 			password,
257 | 			fetchOptions: {
258 | 				//@ts-expect-error
259 | 				onSuccess(context) {
260 | 					const header = context.response.headers.get("set-cookie");
261 | 					const cookies = parseSetCookieHeader(header || "");
262 | 					const signedCookie = cookies.get("better-auth.session_token")?.value;
263 | 					headers.set("cookie", `better-auth.session_token=${signedCookie}`);
264 | 				},
265 | 			},
266 | 		});
267 | 		return {
268 | 			res: data as {
269 | 				user: User;
270 | 				session: Session;
271 | 			},
272 | 			headers,
273 | 		};
274 | 	}
275 | 
276 | 	function sessionSetter(headers: Headers) {
277 | 		return (context: SuccessContext) => {
278 | 			const header = context.response.headers.get("set-cookie");
279 | 			if (header) {
280 | 				const cookies = parseSetCookieHeader(header || "");
281 | 				const signedCookie = cookies.get("better-auth.session_token")?.value;
282 | 				headers.set("cookie", `better-auth.session_token=${signedCookie}`);
283 | 			}
284 | 		};
285 | 	}
286 | 
287 | 	return {
288 | 		auth,
289 | 		client,
290 | 		testUser,
291 | 		signInWithTestUser,
292 | 		signInWithUser,
293 | 		cookieSetter: setCookieToHeader,
294 | 		customFetchImpl,
295 | 		sessionSetter,
296 | 		db: await getAdapter(auth.options),
297 | 		runWithUser: async (
298 | 			email: string,
299 | 			password: string,
300 | 			fn: (headers: Headers) => Promise<void> | void,
301 | 		) => {
302 | 			const { headers } = await signInWithUser(email, password);
303 | 			return currentUserContextStorage.run({ headers }, async () => {
304 | 				await fn(headers);
305 | 			});
306 | 		},
307 | 	};
308 | }
309 | 
```

--------------------------------------------------------------------------------
/demo/nextjs/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
  5 | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | 
  9 | function DropdownMenu({
 10 | 	...props
 11 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
 12 | 	return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
 13 | }
 14 | 
 15 | function DropdownMenuPortal({
 16 | 	...props
 17 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
 18 | 	return (
 19 | 		<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
 20 | 	);
 21 | }
 22 | 
 23 | function DropdownMenuTrigger({
 24 | 	...props
 25 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
 26 | 	return (
 27 | 		<DropdownMenuPrimitive.Trigger
 28 | 			data-slot="dropdown-menu-trigger"
 29 | 			{...props}
 30 | 		/>
 31 | 	);
 32 | }
 33 | 
 34 | function DropdownMenuContent({
 35 | 	className,
 36 | 	sideOffset = 4,
 37 | 	...props
 38 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
 39 | 	return (
 40 | 		<DropdownMenuPrimitive.Portal>
 41 | 			<DropdownMenuPrimitive.Content
 42 | 				data-slot="dropdown-menu-content"
 43 | 				sideOffset={sideOffset}
 44 | 				className={cn(
 45 | 					"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-32 overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
 46 | 					className,
 47 | 				)}
 48 | 				{...props}
 49 | 			/>
 50 | 		</DropdownMenuPrimitive.Portal>
 51 | 	);
 52 | }
 53 | 
 54 | function DropdownMenuGroup({
 55 | 	...props
 56 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
 57 | 	return (
 58 | 		<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
 59 | 	);
 60 | }
 61 | 
 62 | function DropdownMenuItem({
 63 | 	className,
 64 | 	inset,
 65 | 	variant = "default",
 66 | 	...props
 67 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
 68 | 	inset?: boolean;
 69 | 	variant?: "default" | "destructive";
 70 | }) {
 71 | 	return (
 72 | 		<DropdownMenuPrimitive.Item
 73 | 			data-slot="dropdown-menu-item"
 74 | 			data-inset={inset}
 75 | 			data-variant={variant}
 76 | 			className={cn(
 77 | 				"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:text-destructive-foreground! [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
 78 | 				className,
 79 | 			)}
 80 | 			{...props}
 81 | 		/>
 82 | 	);
 83 | }
 84 | 
 85 | function DropdownMenuCheckboxItem({
 86 | 	className,
 87 | 	children,
 88 | 	checked,
 89 | 	...props
 90 | }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
 91 | 	return (
 92 | 		<DropdownMenuPrimitive.CheckboxItem
 93 | 			data-slot="dropdown-menu-checkbox-item"
 94 | 			className={cn(
 95 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
 96 | 				className,
 97 | 			)}
 98 | 			checked={checked}
 99 | 			{...props}
100 | 		>
101 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102 | 				<DropdownMenuPrimitive.ItemIndicator>
103 | 					<CheckIcon className="size-4" />
104 | 				</DropdownMenuPrimitive.ItemIndicator>
105 | 			</span>
106 | 			{children}
107 | 		</DropdownMenuPrimitive.CheckboxItem>
108 | 	);
109 | }
110 | 
111 | function DropdownMenuRadioGroup({
112 | 	...props
113 | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114 | 	return (
115 | 		<DropdownMenuPrimitive.RadioGroup
116 | 			data-slot="dropdown-menu-radio-group"
117 | 			{...props}
118 | 		/>
119 | 	);
120 | }
121 | 
122 | function DropdownMenuRadioItem({
123 | 	className,
124 | 	children,
125 | 	...props
126 | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127 | 	return (
128 | 		<DropdownMenuPrimitive.RadioItem
129 | 			data-slot="dropdown-menu-radio-item"
130 | 			className={cn(
131 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132 | 				className,
133 | 			)}
134 | 			{...props}
135 | 		>
136 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137 | 				<DropdownMenuPrimitive.ItemIndicator>
138 | 					<CircleIcon className="size-2 fill-current" />
139 | 				</DropdownMenuPrimitive.ItemIndicator>
140 | 			</span>
141 | 			{children}
142 | 		</DropdownMenuPrimitive.RadioItem>
143 | 	);
144 | }
145 | 
146 | function DropdownMenuLabel({
147 | 	className,
148 | 	inset,
149 | 	...props
150 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151 | 	inset?: boolean;
152 | }) {
153 | 	return (
154 | 		<DropdownMenuPrimitive.Label
155 | 			data-slot="dropdown-menu-label"
156 | 			data-inset={inset}
157 | 			className={cn(
158 | 				"px-2 py-1.5 text-sm font-medium data-inset:pl-8",
159 | 				className,
160 | 			)}
161 | 			{...props}
162 | 		/>
163 | 	);
164 | }
165 | 
166 | function DropdownMenuSeparator({
167 | 	className,
168 | 	...props
169 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170 | 	return (
171 | 		<DropdownMenuPrimitive.Separator
172 | 			data-slot="dropdown-menu-separator"
173 | 			className={cn("bg-border -mx-1 my-1 h-px", className)}
174 | 			{...props}
175 | 		/>
176 | 	);
177 | }
178 | 
179 | function DropdownMenuShortcut({
180 | 	className,
181 | 	...props
182 | }: React.ComponentProps<"span">) {
183 | 	return (
184 | 		<span
185 | 			data-slot="dropdown-menu-shortcut"
186 | 			className={cn(
187 | 				"text-muted-foreground ml-auto text-xs tracking-widest",
188 | 				className,
189 | 			)}
190 | 			{...props}
191 | 		/>
192 | 	);
193 | }
194 | 
195 | function DropdownMenuSub({
196 | 	...props
197 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198 | 	return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
199 | }
200 | 
201 | function DropdownMenuSubTrigger({
202 | 	className,
203 | 	inset,
204 | 	children,
205 | 	...props
206 | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207 | 	inset?: boolean;
208 | }) {
209 | 	return (
210 | 		<DropdownMenuPrimitive.SubTrigger
211 | 			data-slot="dropdown-menu-sub-trigger"
212 | 			data-inset={inset}
213 | 			className={cn(
214 | 				"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-inset:pl-8",
215 | 				className,
216 | 			)}
217 | 			{...props}
218 | 		>
219 | 			{children}
220 | 			<ChevronRightIcon className="ml-auto size-4" />
221 | 		</DropdownMenuPrimitive.SubTrigger>
222 | 	);
223 | }
224 | 
225 | function DropdownMenuSubContent({
226 | 	className,
227 | 	...props
228 | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229 | 	return (
230 | 		<DropdownMenuPrimitive.SubContent
231 | 			data-slot="dropdown-menu-sub-content"
232 | 			className={cn(
233 | 				"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg",
234 | 				className,
235 | 			)}
236 | 			{...props}
237 | 		/>
238 | 	);
239 | }
240 | 
241 | export {
242 | 	DropdownMenu,
243 | 	DropdownMenuPortal,
244 | 	DropdownMenuTrigger,
245 | 	DropdownMenuContent,
246 | 	DropdownMenuGroup,
247 | 	DropdownMenuLabel,
248 | 	DropdownMenuItem,
249 | 	DropdownMenuCheckboxItem,
250 | 	DropdownMenuRadioGroup,
251 | 	DropdownMenuRadioItem,
252 | 	DropdownMenuSeparator,
253 | 	DropdownMenuShortcut,
254 | 	DropdownMenuSub,
255 | 	DropdownMenuSubTrigger,
256 | 	DropdownMenuSubContent,
257 | };
258 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/generators/prisma.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getAuthTables, type FieldType } from "better-auth/db";
  2 | import { produceSchema } from "@mrleebo/prisma-ast";
  3 | import { existsSync } from "fs";
  4 | import path from "path";
  5 | import fs from "fs/promises";
  6 | import { capitalizeFirstLetter } from "better-auth";
  7 | import type { SchemaGenerator } from "./types";
  8 | 
  9 | export const generatePrismaSchema: SchemaGenerator = async ({
 10 | 	adapter,
 11 | 	options,
 12 | 	file,
 13 | }) => {
 14 | 	const provider = adapter.options?.provider || "postgresql";
 15 | 	const tables = getAuthTables(options);
 16 | 	const filePath = file || "./prisma/schema.prisma";
 17 | 	const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
 18 | 
 19 | 	let schemaPrisma = "";
 20 | 	if (schemaPrismaExist) {
 21 | 		schemaPrisma = await fs.readFile(
 22 | 			path.join(process.cwd(), filePath),
 23 | 			"utf-8",
 24 | 		);
 25 | 	} else {
 26 | 		schemaPrisma = getNewPrisma(provider);
 27 | 	}
 28 | 
 29 | 	const manyToManyRelations = new Map();
 30 | 
 31 | 	for (const table in tables) {
 32 | 		const fields = tables[table]?.fields;
 33 | 		for (const field in fields) {
 34 | 			const attr = fields[field]!;
 35 | 			if (attr.references) {
 36 | 				const referencedOriginalModel = attr.references.model;
 37 | 				const referencedCustomModel =
 38 | 					tables[referencedOriginalModel]?.modelName || referencedOriginalModel;
 39 | 				const referencedModelNameCap = capitalizeFirstLetter(
 40 | 					referencedCustomModel,
 41 | 				);
 42 | 
 43 | 				if (!manyToManyRelations.has(referencedModelNameCap)) {
 44 | 					manyToManyRelations.set(referencedModelNameCap, new Set());
 45 | 				}
 46 | 
 47 | 				const currentCustomModel = tables[table]?.modelName || table;
 48 | 				const currentModelNameCap = capitalizeFirstLetter(currentCustomModel);
 49 | 
 50 | 				manyToManyRelations
 51 | 					.get(referencedModelNameCap)
 52 | 					.add(currentModelNameCap);
 53 | 			}
 54 | 		}
 55 | 	}
 56 | 
 57 | 	const schema = produceSchema(schemaPrisma, (builder) => {
 58 | 		for (const table in tables) {
 59 | 			const originalTableName = table;
 60 | 			const customModelName = tables[table]?.modelName || table;
 61 | 			const modelName = capitalizeFirstLetter(customModelName);
 62 | 			const fields = tables[table]?.fields;
 63 | 			function getType({
 64 | 				isBigint,
 65 | 				isOptional,
 66 | 				type,
 67 | 			}: {
 68 | 				type: FieldType;
 69 | 				isOptional: boolean;
 70 | 				isBigint: boolean;
 71 | 			}) {
 72 | 				if (type === "string") {
 73 | 					return isOptional ? "String?" : "String";
 74 | 				}
 75 | 				if (type === "number" && isBigint) {
 76 | 					return isOptional ? "BigInt?" : "BigInt";
 77 | 				}
 78 | 				if (type === "number") {
 79 | 					return isOptional ? "Int?" : "Int";
 80 | 				}
 81 | 				if (type === "boolean") {
 82 | 					return isOptional ? "Boolean?" : "Boolean";
 83 | 				}
 84 | 				if (type === "date") {
 85 | 					return isOptional ? "DateTime?" : "DateTime";
 86 | 				}
 87 | 				if (type === "json") {
 88 | 					return isOptional ? "Json?" : "Json";
 89 | 				}
 90 | 				if (type === "string[]") {
 91 | 					return isOptional ? "String[]" : "String[]";
 92 | 				}
 93 | 				if (type === "number[]") {
 94 | 					return isOptional ? "Int[]" : "Int[]";
 95 | 				}
 96 | 			}
 97 | 
 98 | 			const prismaModel = builder.findByType("model", {
 99 | 				name: modelName,
100 | 			});
101 | 
102 | 			if (!prismaModel) {
103 | 				if (provider === "mongodb") {
104 | 					// Mongo DB doesn't support auto increment, so just use their normal _id.
105 | 					builder
106 | 						.model(modelName)
107 | 						.field("id", "String")
108 | 						.attribute("id")
109 | 						.attribute(`map("_id")`);
110 | 				} else {
111 | 					if (options.advanced?.database?.useNumberId) {
112 | 						builder
113 | 							.model(modelName)
114 | 							.field("id", "Int")
115 | 							.attribute("id")
116 | 							.attribute("default(autoincrement())");
117 | 					} else {
118 | 						builder.model(modelName).field("id", "String").attribute("id");
119 | 					}
120 | 				}
121 | 			}
122 | 
123 | 			for (const field in fields) {
124 | 				const attr = fields[field]!;
125 | 				const fieldName = attr.fieldName || field;
126 | 
127 | 				if (prismaModel) {
128 | 					const isAlreadyExist = builder.findByType("field", {
129 | 						name: fieldName,
130 | 						within: prismaModel.properties,
131 | 					});
132 | 					if (isAlreadyExist) {
133 | 						continue;
134 | 					}
135 | 				}
136 | 
137 | 				const fieldBuilder = builder.model(modelName).field(
138 | 					fieldName,
139 | 					field === "id" && options.advanced?.database?.useNumberId
140 | 						? getType({
141 | 								isBigint: false,
142 | 								isOptional: false,
143 | 								type: "number",
144 | 							})
145 | 						: getType({
146 | 								isBigint: attr?.bigint || false,
147 | 								isOptional: !attr?.required,
148 | 								type:
149 | 									attr.references?.field === "id"
150 | 										? options.advanced?.database?.useNumberId
151 | 											? "number"
152 | 											: "string"
153 | 										: attr.type,
154 | 							}),
155 | 				);
156 | 				if (field === "id") {
157 | 					fieldBuilder.attribute("id");
158 | 					if (provider === "mongodb") {
159 | 						fieldBuilder.attribute(`map("_id")`);
160 | 					}
161 | 				}
162 | 
163 | 				if (attr.unique) {
164 | 					builder.model(modelName).blockAttribute(`unique([${fieldName}])`);
165 | 				}
166 | 
167 | 				if (attr.defaultValue !== undefined) {
168 | 					if (field === "createdAt") {
169 | 						fieldBuilder.attribute("default(now())");
170 | 					} else if (typeof attr.defaultValue === "boolean") {
171 | 						fieldBuilder.attribute(`default(${attr.defaultValue})`);
172 | 					} else if (typeof attr.defaultValue === "function") {
173 | 						// we are intentionally not adding the default value here
174 | 						// this is because if the defaultValue is a function, it could have
175 | 						// custom logic within that function that might not work in prisma's context.
176 | 					}
177 | 				}
178 | 
179 | 				// This is a special handling for updatedAt fields
180 | 				if (field === "updatedAt" && attr.onUpdate) {
181 | 					fieldBuilder.attribute("updatedAt");
182 | 				} else if (attr.onUpdate) {
183 | 					// we are intentionally not adding the onUpdate value here
184 | 					// this is because if the onUpdate is a function, it could have
185 | 					// custom logic within that function that might not work in prisma's context.
186 | 				}
187 | 
188 | 				if (attr.references) {
189 | 					const referencedOriginalModelName = attr.references.model;
190 | 					const referencedCustomModelName =
191 | 						tables[referencedOriginalModelName]?.modelName ||
192 | 						referencedOriginalModelName;
193 | 					let action = "Cascade";
194 | 					if (attr.references.onDelete === "no action") action = "NoAction";
195 | 					else if (attr.references.onDelete === "set null") action = "SetNull";
196 | 					else if (attr.references.onDelete === "set default")
197 | 						action = "SetDefault";
198 | 					else if (attr.references.onDelete === "restrict") action = "Restrict";
199 | 					builder
200 | 						.model(modelName)
201 | 						.field(
202 | 							`${referencedCustomModelName.toLowerCase()}`,
203 | 							`${capitalizeFirstLetter(referencedCustomModelName)}${
204 | 								!attr.required ? "?" : ""
205 | 							}`,
206 | 						)
207 | 						.attribute(
208 | 							`relation(fields: [${fieldName}], references: [${attr.references.field}], onDelete: ${action})`,
209 | 						);
210 | 				}
211 | 				if (
212 | 					!attr.unique &&
213 | 					!attr.references &&
214 | 					provider === "mysql" &&
215 | 					attr.type === "string"
216 | 				) {
217 | 					builder.model(modelName).field(fieldName).attribute("db.Text");
218 | 				}
219 | 			}
220 | 
221 | 			// Add many-to-many fields
222 | 			if (manyToManyRelations.has(modelName)) {
223 | 				for (const relatedModel of manyToManyRelations.get(modelName)) {
224 | 					const fieldName = `${relatedModel.toLowerCase()}s`;
225 | 					const existingField = builder.findByType("field", {
226 | 						name: fieldName,
227 | 						within: prismaModel?.properties,
228 | 					});
229 | 					if (!existingField) {
230 | 						builder.model(modelName).field(fieldName, `${relatedModel}[]`);
231 | 					}
232 | 				}
233 | 			}
234 | 
235 | 			const hasAttribute = builder.findByType("attribute", {
236 | 				name: "map",
237 | 				within: prismaModel?.properties,
238 | 			});
239 | 			const hasChanged = customModelName !== originalTableName;
240 | 			if (!hasAttribute) {
241 | 				builder
242 | 					.model(modelName)
243 | 					.blockAttribute(
244 | 						"map",
245 | 						`${hasChanged ? customModelName : originalTableName}`,
246 | 					);
247 | 			}
248 | 		}
249 | 	});
250 | 
251 | 	const schemaChanged = schema.trim() !== schemaPrisma.trim();
252 | 
253 | 	return {
254 | 		code: schemaChanged ? schema : "",
255 | 		fileName: filePath,
256 | 		overwrite: schemaPrismaExist && schemaChanged,
257 | 	};
258 | };
259 | 
260 | const getNewPrisma = (provider: string) => `generator client {
261 |     provider = "prisma-client-js"
262 |   }
263 |   
264 |   datasource db {
265 |     provider = "${provider}"
266 |     url      = ${
267 | 			provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`
268 | 		}
269 |   }`;
270 | 
```

--------------------------------------------------------------------------------
/docs/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
  5 | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | 
  9 | function DropdownMenu({
 10 | 	...props
 11 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
 12 | 	return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
 13 | }
 14 | 
 15 | function DropdownMenuPortal({
 16 | 	...props
 17 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
 18 | 	return (
 19 | 		<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
 20 | 	);
 21 | }
 22 | 
 23 | function DropdownMenuTrigger({
 24 | 	...props
 25 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
 26 | 	return (
 27 | 		<DropdownMenuPrimitive.Trigger
 28 | 			data-slot="dropdown-menu-trigger"
 29 | 			{...props}
 30 | 		/>
 31 | 	);
 32 | }
 33 | 
 34 | function DropdownMenuContent({
 35 | 	className,
 36 | 	sideOffset = 4,
 37 | 	...props
 38 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
 39 | 	return (
 40 | 		<DropdownMenuPrimitive.Portal>
 41 | 			<DropdownMenuPrimitive.Content
 42 | 				data-slot="dropdown-menu-content"
 43 | 				sideOffset={sideOffset}
 44 | 				className={cn(
 45 | 					"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
 46 | 					className,
 47 | 				)}
 48 | 				{...props}
 49 | 			/>
 50 | 		</DropdownMenuPrimitive.Portal>
 51 | 	);
 52 | }
 53 | 
 54 | function DropdownMenuGroup({
 55 | 	...props
 56 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
 57 | 	return (
 58 | 		<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
 59 | 	);
 60 | }
 61 | 
 62 | function DropdownMenuItem({
 63 | 	className,
 64 | 	inset,
 65 | 	variant = "default",
 66 | 	...props
 67 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
 68 | 	inset?: boolean;
 69 | 	variant?: "default" | "destructive";
 70 | }) {
 71 | 	return (
 72 | 		<DropdownMenuPrimitive.Item
 73 | 			data-slot="dropdown-menu-item"
 74 | 			data-inset={inset}
 75 | 			data-variant={variant}
 76 | 			className={cn(
 77 | 				"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
 78 | 				className,
 79 | 			)}
 80 | 			{...props}
 81 | 		/>
 82 | 	);
 83 | }
 84 | 
 85 | function DropdownMenuCheckboxItem({
 86 | 	className,
 87 | 	children,
 88 | 	checked,
 89 | 	...props
 90 | }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
 91 | 	return (
 92 | 		<DropdownMenuPrimitive.CheckboxItem
 93 | 			data-slot="dropdown-menu-checkbox-item"
 94 | 			className={cn(
 95 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
 96 | 				className,
 97 | 			)}
 98 | 			checked={checked}
 99 | 			{...props}
100 | 		>
101 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102 | 				<DropdownMenuPrimitive.ItemIndicator>
103 | 					<CheckIcon className="size-4" />
104 | 				</DropdownMenuPrimitive.ItemIndicator>
105 | 			</span>
106 | 			{children}
107 | 		</DropdownMenuPrimitive.CheckboxItem>
108 | 	);
109 | }
110 | 
111 | function DropdownMenuRadioGroup({
112 | 	...props
113 | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114 | 	return (
115 | 		<DropdownMenuPrimitive.RadioGroup
116 | 			data-slot="dropdown-menu-radio-group"
117 | 			{...props}
118 | 		/>
119 | 	);
120 | }
121 | 
122 | function DropdownMenuRadioItem({
123 | 	className,
124 | 	children,
125 | 	...props
126 | }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127 | 	return (
128 | 		<DropdownMenuPrimitive.RadioItem
129 | 			data-slot="dropdown-menu-radio-item"
130 | 			className={cn(
131 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132 | 				className,
133 | 			)}
134 | 			{...props}
135 | 		>
136 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137 | 				<DropdownMenuPrimitive.ItemIndicator>
138 | 					<CircleIcon className="size-2 fill-current" />
139 | 				</DropdownMenuPrimitive.ItemIndicator>
140 | 			</span>
141 | 			{children}
142 | 		</DropdownMenuPrimitive.RadioItem>
143 | 	);
144 | }
145 | 
146 | function DropdownMenuLabel({
147 | 	className,
148 | 	inset,
149 | 	...props
150 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151 | 	inset?: boolean;
152 | }) {
153 | 	return (
154 | 		<DropdownMenuPrimitive.Label
155 | 			data-slot="dropdown-menu-label"
156 | 			data-inset={inset}
157 | 			className={cn(
158 | 				"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
159 | 				className,
160 | 			)}
161 | 			{...props}
162 | 		/>
163 | 	);
164 | }
165 | 
166 | function DropdownMenuSeparator({
167 | 	className,
168 | 	...props
169 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170 | 	return (
171 | 		<DropdownMenuPrimitive.Separator
172 | 			data-slot="dropdown-menu-separator"
173 | 			className={cn("bg-border -mx-1 my-1 h-px", className)}
174 | 			{...props}
175 | 		/>
176 | 	);
177 | }
178 | 
179 | function DropdownMenuShortcut({
180 | 	className,
181 | 	...props
182 | }: React.ComponentProps<"span">) {
183 | 	return (
184 | 		<span
185 | 			data-slot="dropdown-menu-shortcut"
186 | 			className={cn(
187 | 				"text-muted-foreground ml-auto text-xs tracking-widest",
188 | 				className,
189 | 			)}
190 | 			{...props}
191 | 		/>
192 | 	);
193 | }
194 | 
195 | function DropdownMenuSub({
196 | 	...props
197 | }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198 | 	return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
199 | }
200 | 
201 | function DropdownMenuSubTrigger({
202 | 	className,
203 | 	inset,
204 | 	children,
205 | 	...props
206 | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207 | 	inset?: boolean;
208 | }) {
209 | 	return (
210 | 		<DropdownMenuPrimitive.SubTrigger
211 | 			data-slot="dropdown-menu-sub-trigger"
212 | 			data-inset={inset}
213 | 			className={cn(
214 | 				"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
215 | 				className,
216 | 			)}
217 | 			{...props}
218 | 		>
219 | 			{children}
220 | 			<ChevronRightIcon className="ml-auto size-4" />
221 | 		</DropdownMenuPrimitive.SubTrigger>
222 | 	);
223 | }
224 | 
225 | function DropdownMenuSubContent({
226 | 	className,
227 | 	...props
228 | }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229 | 	return (
230 | 		<DropdownMenuPrimitive.SubContent
231 | 			data-slot="dropdown-menu-sub-content"
232 | 			className={cn(
233 | 				"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
234 | 				className,
235 | 			)}
236 | 			{...props}
237 | 		/>
238 | 	);
239 | }
240 | 
241 | export {
242 | 	DropdownMenu,
243 | 	DropdownMenuPortal,
244 | 	DropdownMenuTrigger,
245 | 	DropdownMenuContent,
246 | 	DropdownMenuGroup,
247 | 	DropdownMenuLabel,
248 | 	DropdownMenuItem,
249 | 	DropdownMenuCheckboxItem,
250 | 	DropdownMenuRadioGroup,
251 | 	DropdownMenuRadioItem,
252 | 	DropdownMenuSeparator,
253 | 	DropdownMenuShortcut,
254 | 	DropdownMenuSub,
255 | 	DropdownMenuSubTrigger,
256 | 	DropdownMenuSubContent,
257 | };
258 | 
```

--------------------------------------------------------------------------------
/docs/components/ui/menubar.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | 
  3 | import * as React from "react";
  4 | import * as MenubarPrimitive from "@radix-ui/react-menubar";
  5 | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
  6 | 
  7 | import { cn } from "@/lib/utils";
  8 | 
  9 | function Menubar({
 10 | 	className,
 11 | 	...props
 12 | }: React.ComponentProps<typeof MenubarPrimitive.Root>) {
 13 | 	return (
 14 | 		<MenubarPrimitive.Root
 15 | 			data-slot="menubar"
 16 | 			className={cn(
 17 | 				"bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
 18 | 				className,
 19 | 			)}
 20 | 			{...props}
 21 | 		/>
 22 | 	);
 23 | }
 24 | 
 25 | function MenubarMenu({
 26 | 	...props
 27 | }: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
 28 | 	return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />;
 29 | }
 30 | 
 31 | function MenubarGroup({
 32 | 	...props
 33 | }: React.ComponentProps<typeof MenubarPrimitive.Group>) {
 34 | 	return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />;
 35 | }
 36 | 
 37 | function MenubarPortal({
 38 | 	...props
 39 | }: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
 40 | 	return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />;
 41 | }
 42 | 
 43 | function MenubarRadioGroup({
 44 | 	...props
 45 | }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
 46 | 	return (
 47 | 		<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
 48 | 	);
 49 | }
 50 | 
 51 | function MenubarTrigger({
 52 | 	className,
 53 | 	...props
 54 | }: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
 55 | 	return (
 56 | 		<MenubarPrimitive.Trigger
 57 | 			data-slot="menubar-trigger"
 58 | 			className={cn(
 59 | 				"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
 60 | 				className,
 61 | 			)}
 62 | 			{...props}
 63 | 		/>
 64 | 	);
 65 | }
 66 | 
 67 | function MenubarContent({
 68 | 	className,
 69 | 	align = "start",
 70 | 	alignOffset = -4,
 71 | 	sideOffset = 8,
 72 | 	...props
 73 | }: React.ComponentProps<typeof MenubarPrimitive.Content>) {
 74 | 	return (
 75 | 		<MenubarPortal>
 76 | 			<MenubarPrimitive.Content
 77 | 				data-slot="menubar-content"
 78 | 				align={align}
 79 | 				alignOffset={alignOffset}
 80 | 				sideOffset={sideOffset}
 81 | 				className={cn(
 82 | 					"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[12rem] overflow-hidden rounded-md border p-1 shadow-md",
 83 | 					className,
 84 | 				)}
 85 | 				{...props}
 86 | 			/>
 87 | 		</MenubarPortal>
 88 | 	);
 89 | }
 90 | 
 91 | function MenubarItem({
 92 | 	className,
 93 | 	inset,
 94 | 	variant = "default",
 95 | 	...props
 96 | }: React.ComponentProps<typeof MenubarPrimitive.Item> & {
 97 | 	inset?: boolean;
 98 | 	variant?: "default" | "destructive";
 99 | }) {
100 | 	return (
101 | 		<MenubarPrimitive.Item
102 | 			data-slot="menubar-item"
103 | 			data-inset={inset}
104 | 			data-variant={variant}
105 | 			className={cn(
106 | 				"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
107 | 				className,
108 | 			)}
109 | 			{...props}
110 | 		/>
111 | 	);
112 | }
113 | 
114 | function MenubarCheckboxItem({
115 | 	className,
116 | 	children,
117 | 	checked,
118 | 	...props
119 | }: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
120 | 	return (
121 | 		<MenubarPrimitive.CheckboxItem
122 | 			data-slot="menubar-checkbox-item"
123 | 			className={cn(
124 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
125 | 				className,
126 | 			)}
127 | 			checked={checked}
128 | 			{...props}
129 | 		>
130 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
131 | 				<MenubarPrimitive.ItemIndicator>
132 | 					<CheckIcon className="size-4" />
133 | 				</MenubarPrimitive.ItemIndicator>
134 | 			</span>
135 | 			{children}
136 | 		</MenubarPrimitive.CheckboxItem>
137 | 	);
138 | }
139 | 
140 | function MenubarRadioItem({
141 | 	className,
142 | 	children,
143 | 	...props
144 | }: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
145 | 	return (
146 | 		<MenubarPrimitive.RadioItem
147 | 			data-slot="menubar-radio-item"
148 | 			className={cn(
149 | 				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
150 | 				className,
151 | 			)}
152 | 			{...props}
153 | 		>
154 | 			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
155 | 				<MenubarPrimitive.ItemIndicator>
156 | 					<CircleIcon className="size-2 fill-current" />
157 | 				</MenubarPrimitive.ItemIndicator>
158 | 			</span>
159 | 			{children}
160 | 		</MenubarPrimitive.RadioItem>
161 | 	);
162 | }
163 | 
164 | function MenubarLabel({
165 | 	className,
166 | 	inset,
167 | 	...props
168 | }: React.ComponentProps<typeof MenubarPrimitive.Label> & {
169 | 	inset?: boolean;
170 | }) {
171 | 	return (
172 | 		<MenubarPrimitive.Label
173 | 			data-slot="menubar-label"
174 | 			data-inset={inset}
175 | 			className={cn(
176 | 				"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
177 | 				className,
178 | 			)}
179 | 			{...props}
180 | 		/>
181 | 	);
182 | }
183 | 
184 | function MenubarSeparator({
185 | 	className,
186 | 	...props
187 | }: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
188 | 	return (
189 | 		<MenubarPrimitive.Separator
190 | 			data-slot="menubar-separator"
191 | 			className={cn("bg-border -mx-1 my-1 h-px", className)}
192 | 			{...props}
193 | 		/>
194 | 	);
195 | }
196 | 
197 | function MenubarShortcut({
198 | 	className,
199 | 	...props
200 | }: React.ComponentProps<"span">) {
201 | 	return (
202 | 		<span
203 | 			data-slot="menubar-shortcut"
204 | 			className={cn(
205 | 				"text-muted-foreground ml-auto text-xs tracking-widest",
206 | 				className,
207 | 			)}
208 | 			{...props}
209 | 		/>
210 | 	);
211 | }
212 | 
213 | function MenubarSub({
214 | 	...props
215 | }: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
216 | 	return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
217 | }
218 | 
219 | function MenubarSubTrigger({
220 | 	className,
221 | 	inset,
222 | 	children,
223 | 	...props
224 | }: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
225 | 	inset?: boolean;
226 | }) {
227 | 	return (
228 | 		<MenubarPrimitive.SubTrigger
229 | 			data-slot="menubar-sub-trigger"
230 | 			data-inset={inset}
231 | 			className={cn(
232 | 				"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8",
233 | 				className,
234 | 			)}
235 | 			{...props}
236 | 		>
237 | 			{children}
238 | 			<ChevronRightIcon className="ml-auto h-4 w-4" />
239 | 		</MenubarPrimitive.SubTrigger>
240 | 	);
241 | }
242 | 
243 | function MenubarSubContent({
244 | 	className,
245 | 	...props
246 | }: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
247 | 	return (
248 | 		<MenubarPrimitive.SubContent
249 | 			data-slot="menubar-sub-content"
250 | 			className={cn(
251 | 				"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
252 | 				className,
253 | 			)}
254 | 			{...props}
255 | 		/>
256 | 	);
257 | }
258 | 
259 | export {
260 | 	Menubar,
261 | 	MenubarPortal,
262 | 	MenubarMenu,
263 | 	MenubarTrigger,
264 | 	MenubarContent,
265 | 	MenubarGroup,
266 | 	MenubarSeparator,
267 | 	MenubarLabel,
268 | 	MenubarItem,
269 | 	MenubarShortcut,
270 | 	MenubarCheckboxItem,
271 | 	MenubarRadioGroup,
272 | 	MenubarRadioItem,
273 | 	MenubarSub,
274 | 	MenubarSubTrigger,
275 | 	MenubarSubContent,
276 | };
277 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/reference/security.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Security
  3 | description: Better Auth security features.
  4 | ---
  5 | 
  6 | This page contains information about security features of Better Auth.
  7 | 
  8 | 
  9 | ## Password Hashing
 10 | 
 11 | Better Auth uses the `scrypt` algorithm to hash passwords by default. This algorithm is designed to be memory-hard and CPU-intensive, making it resistant to brute-force attacks. You can customize the password hashing function by setting the `password` option in the configuration. This option should include a `hash` function to hash passwords and a `verify` function to verify them.
 12 | 
 13 | ## Session Management
 14 | 
 15 | ### Session Expiration
 16 | 
 17 | Better Auth uses secure session management to protect user data. Sessions are stored in the database or a secondary storage, if configured, to prevent unauthorized access. By default, sessions expire after 7 days, but you can customize this value in the configuration. Additionally, each time a session is used, if it reaches the `updateAge` threshold, the expiration date is extended, which by default is set to 1 day.
 18 | 
 19 | ### Session Revocation
 20 | 
 21 | Better Auth allows you to revoke sessions to enhance security. When a session is revoked, the user is logged out and can no longer access the application. A logged in user can also revoke their own sessions to log out from different devices or browsers.
 22 | 
 23 | See the [session management](/docs/concepts/session-management) for more details.
 24 | 
 25 | ## CSRF Protection
 26 | 
 27 | Better Auth includes multiple safeguards to prevent Cross-Site Request Forgery (CSRF) attacks:
 28 | 
 29 | 1. **Non-Simple Headers**
 30 |    POST requests must either have a non-simple header or a `Content-Type` header of `application/json`. Non-simple headers are headers that are not included in the [simple headers](https://developer.mozilla.org/en-US/docs/Glossary/Simple_header) list.
 31 | 
 32 | 2. **Origin Validation**
 33 |    Each request’s `Origin` header is verified to confirm it comes from your application or another explicitly trusted source. Requests from untrusted origins are rejected. By default, Better Auth trusts the base URL of your app, but you can specify additional trusted origins via the `trustedOrigins` configuration option.
 34 | 
 35 | 3. **Secure Cookie Settings**
 36 |    Session cookies use the `SameSite=Lax` attribute by default, preventing browsers from sending cookies with most cross-site requests. You can override this behavior using the `defaultCookieAttributes` option.
 37 | 
 38 | 4. **No Mutations on GET Requests (with additional safeguards)**
 39 |   `GET` requests are assumed to be read-only and should not alter the application’s state. In cases where a `GET` request must perform a mutation—such as during OAuth callbacks - Better Auth applies extra security measures, including validating `nonce` and `state` parameters to ensure the request’s authenticity.
 40 | 
 41 | You can skip the CSRF check for all requests by setting the `disableCSRFCheck` option to `true` in the configuration.
 42 | 
 43 | ```typescript
 44 | {
 45 |   advanced: {
 46 |     disableCSRFCheck: true
 47 |   }
 48 | }
 49 | ```
 50 | 
 51 | You can skip the origin check for all requests by setting the `disableOriginCheck` option to `true` in the configuration.
 52 | 
 53 | ```typescript
 54 | {
 55 |   advanced: {
 56 |     disableOriginCheck: true
 57 |   }
 58 | }
 59 | ```
 60 | 
 61 | <Callout type="warning">
 62 |   Skipping csrf check will open your application to CSRF attacks. And skipping origin check may open up your application to other security vulnerabilities including open redirects.
 63 | </Callout>
 64 | 
 65 | ## OAuth State and PKCE
 66 | 
 67 | To secure OAuth flows, Better Auth stores the OAuth state and PKCE (Proof Key for Code Exchange) in the database. The state helps prevent CSRF attacks, while PKCE protects against code injection threats. Once the OAuth process completes, these values are removed from the database.
 68 | 
 69 | ## Cookies
 70 | 
 71 | Better Auth assigns secure cookies by default when the base URL uses `https`. These secure cookies are encrypted and only sent over secure connections, adding an extra layer of protection. They are also set with the `sameSite` attribute to `lax` by default to prevent cross-site request forgery attacks. And the `httpOnly` attribute is enabled to prevent client-side JavaScript from accessing the cookie. 
 72 | 
 73 | For Cross-Subdomain Cookies, you can set the `crossSubDomainCookies` option in the configuration. This option allows cookies to be shared across subdomains, enabling seamless authentication across multiple subdomains.
 74 | 
 75 | ### Customizing Cookies
 76 | 
 77 | You can customize cookie names to minimize the risk of fingerprinting attacks and set specific cookie options as needed for additional control. For more information, refer to the [cookie options](/docs/concepts/cookies).
 78 | 
 79 | Plugins can also set custom cookie options to align with specific security needs. If you're using Better Auth in non-browser environments, plugins offer ways to manage cookies securely in those contexts as well.
 80 | 
 81 | ## Rate Limiting
 82 | 
 83 | Better Auth includes built-in rate limiting to safeguard against brute-force attacks. Rate limits are applied across all routes by default, with specific routes subject to stricter limits based on potential risk.
 84 | 
 85 | ## IP Address Headers
 86 | 
 87 | Better Auth uses client IP addresses for rate limiting and security monitoring. By default, it reads the IP address from the standard `X-Forwarded-For` header. However, you can configure a specific trusted header to ensure accurate IP address detection and prevent IP spoofing attacks.
 88 | 
 89 | You can configure the IP address header in your Better Auth configuration:
 90 | 
 91 | ```typescript
 92 | {
 93 |   advanced: {
 94 |     ipAddress: {
 95 |       ipAddressHeaders: ['cf-connecting-ip'] // or any other custom header
 96 |     }
 97 |   }
 98 | }
 99 | ```
100 | 
101 | This ensures that Better Auth only accepts IP addresses from your trusted proxy's header, making it more difficult for attackers to bypass rate limiting or other IP-based security measures by spoofing headers.
102 | 
103 | > **Important**: When setting a custom IP address header, ensure that your proxy or load balancer is properly configured to set this header, and that it cannot be set by end users directly.
104 | 
105 | ## Trusted Origins
106 | 
107 | Trusted origins prevent CSRF attacks and block open redirects. You can set a list of trusted origins in the `trustedOrigins` configuration option. Requests from origins not on this list are automatically blocked.
108 | 
109 | ### Basic Usage
110 | 
111 | The most basic usage is to specify exact origins:
112 | 
113 | ```typescript
114 | {
115 |   trustedOrigins: [
116 |     "https://example.com",
117 |     "https://app.example.com",
118 |     "http://localhost:3000"
119 |   ]
120 | }
121 | ```
122 | 
123 | ### Wildcard Domains
124 | 
125 | Better Auth supports wildcard patterns in trusted origins, which allows you to trust multiple subdomains with a single entry:
126 | 
127 | ```typescript
128 | {
129 |   trustedOrigins: [
130 |     "*.example.com",             // Trust all subdomains of example.com (any protocol)
131 |     "https://*.example.com",     // Trust only HTTPS subdomains of example.com
132 |     "http://*.dev.example.com"   // Trust all HTTP subdomains of dev.example.com
133 |   ]
134 | }
135 | ```
136 | 
137 | #### Protocol-specific wildcards
138 | 
139 | When using a wildcard pattern with a protocol prefix (like `https://`):
140 | - The protocol must match exactly
141 | - The domain can have any subdomain in place of the `*`
142 | - Requests using a different protocol will be rejected, even if the domain matches
143 | 
144 | #### Protocol-agnostic wildcards
145 | 
146 | When using a wildcard pattern without a protocol prefix (like `*.example.com`):
147 | - Any protocol (http, https, etc.) will be accepted
148 | - The domain must match the wildcard pattern
149 | 
150 | ### Custom Schemes
151 | 
152 | Trusted origins also support custom schemes for mobile apps and browser extensions:
153 | 
154 | ```typescript
155 | {
156 |   trustedOrigins: [
157 |     "myapp://",                               // Mobile app scheme
158 |     "chrome-extension://YOUR_EXTENSION_ID",   // Browser extension
159 |     "exp://*/*",                              // Trust all Expo development URLs
160 |     "exp://10.0.0.*:*/*",                     // Trust 10.0.0.x IP range with any port
161 |   ]
162 | }
163 | ```
164 | 
165 | ## Reporting Vulnerabilities
166 | 
167 | If you discover a security vulnerability in Better Auth, please report it to us at [[email protected]](mailto:[email protected]). We address all reports promptly, and credits will be given for validated discoveries.
168 | 
```

--------------------------------------------------------------------------------
/packages/better-auth/src/init.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { defu } from "defu";
  2 | import { hashPassword, verifyPassword } from "./crypto/password";
  3 | import { createInternalAdapter, getAuthTables, getMigrations } from "./db";
  4 | import type { Entries } from "type-fest";
  5 | import { getAdapter } from "./db/utils";
  6 | import type { BetterAuthOptions, BetterAuthPlugin } from "@better-auth/core";
  7 | import { DEFAULT_SECRET } from "./utils/constants";
  8 | import { createCookieGetter, getCookies } from "./cookies";
  9 | import { createLogger, isTest } from "@better-auth/core/env";
 10 | import {
 11 | 	type SocialProviders,
 12 | 	socialProviders,
 13 | } from "@better-auth/core/social-providers";
 14 | import type { OAuthProvider } from "@better-auth/core/oauth2";
 15 | import { generateId } from "./utils";
 16 | import { env, isProduction } from "@better-auth/core/env";
 17 | import { checkPassword } from "./utils/password";
 18 | import { getBaseURL } from "./utils/url";
 19 | import { BetterAuthError } from "@better-auth/core/error";
 20 | import { createTelemetry } from "@better-auth/telemetry";
 21 | import { getKyselyDatabaseType } from "./adapters/kysely-adapter";
 22 | import { checkEndpointConflicts } from "./api";
 23 | import { isPromise } from "./utils/is-promise";
 24 | import type { AuthContext } from "@better-auth/core";
 25 | 
 26 | export const init = async (options: BetterAuthOptions) => {
 27 | 	const adapter = await getAdapter(options);
 28 | 	const plugins = options.plugins || [];
 29 | 	const internalPlugins = getInternalPlugins(options);
 30 | 	const logger = createLogger(options.logger);
 31 | 	const baseURL = getBaseURL(options.baseURL, options.basePath);
 32 | 
 33 | 	const secret =
 34 | 		options.secret ||
 35 | 		env.BETTER_AUTH_SECRET ||
 36 | 		env.AUTH_SECRET ||
 37 | 		DEFAULT_SECRET;
 38 | 
 39 | 	if (secret === DEFAULT_SECRET) {
 40 | 		if (isProduction) {
 41 | 			logger.error(
 42 | 				"You are using the default secret. Please set `BETTER_AUTH_SECRET` in your environment variables or pass `secret` in your auth config.",
 43 | 			);
 44 | 		}
 45 | 	}
 46 | 
 47 | 	options = {
 48 | 		...options,
 49 | 		secret,
 50 | 		baseURL: baseURL ? new URL(baseURL).origin : "",
 51 | 		basePath: options.basePath || "/api/auth",
 52 | 		plugins: plugins.concat(internalPlugins),
 53 | 	};
 54 | 
 55 | 	checkEndpointConflicts(options, logger);
 56 | 	const cookies = getCookies(options);
 57 | 	const tables = getAuthTables(options);
 58 | 	const providers: OAuthProvider[] = (
 59 | 		Object.entries(
 60 | 			options.socialProviders || {},
 61 | 		) as unknown as Entries<SocialProviders>
 62 | 	)
 63 | 		.map(([key, config]) => {
 64 | 			if (config == null) {
 65 | 				return null;
 66 | 			}
 67 | 			if (config.enabled === false) {
 68 | 				return null;
 69 | 			}
 70 | 			if (!config.clientId) {
 71 | 				logger.warn(
 72 | 					`Social provider ${key} is missing clientId or clientSecret`,
 73 | 				);
 74 | 			}
 75 | 			const provider = socialProviders[key](config as never);
 76 | 			(provider as OAuthProvider).disableImplicitSignUp =
 77 | 				config.disableImplicitSignUp;
 78 | 			return provider;
 79 | 		})
 80 | 		.filter((x) => x !== null);
 81 | 
 82 | 	const generateIdFunc: AuthContext["generateId"] = ({ model, size }) => {
 83 | 		if (typeof (options.advanced as any)?.generateId === "function") {
 84 | 			return (options.advanced as any).generateId({ model, size });
 85 | 		}
 86 | 		if (typeof options?.advanced?.database?.generateId === "function") {
 87 | 			return options.advanced.database.generateId({ model, size });
 88 | 		}
 89 | 		return generateId(size);
 90 | 	};
 91 | 
 92 | 	const { publish } = await createTelemetry(options, {
 93 | 		adapter: adapter.id,
 94 | 		database:
 95 | 			typeof options.database === "function"
 96 | 				? "adapter"
 97 | 				: getKyselyDatabaseType(options.database) || "unknown",
 98 | 	});
 99 | 
100 | 	let ctx: AuthContext = {
101 | 		appName: options.appName || "Better Auth",
102 | 		socialProviders: providers,
103 | 		options,
104 | 		oauthConfig: {
105 | 			storeStateStrategy:
106 | 				options.advanced?.oauthConfig?.storeStateStrategy || "database",
107 | 			skipStateCookieCheck:
108 | 				!!options.advanced?.oauthConfig?.skipStateCookieCheck,
109 | 		},
110 | 		tables,
111 | 		trustedOrigins: getTrustedOrigins(options),
112 | 		baseURL: baseURL || "",
113 | 		sessionConfig: {
114 | 			updateAge:
115 | 				options.session?.updateAge !== undefined
116 | 					? options.session.updateAge
117 | 					: 24 * 60 * 60, // 24 hours
118 | 			expiresIn: options.session?.expiresIn || 60 * 60 * 24 * 7, // 7 days
119 | 			freshAge:
120 | 				options.session?.freshAge === undefined
121 | 					? 60 * 60 * 24 // 24 hours
122 | 					: options.session.freshAge,
123 | 		},
124 | 		secret,
125 | 		rateLimit: {
126 | 			...options.rateLimit,
127 | 			enabled: options.rateLimit?.enabled ?? isProduction,
128 | 			window: options.rateLimit?.window || 10,
129 | 			max: options.rateLimit?.max || 100,
130 | 			storage:
131 | 				options.rateLimit?.storage ||
132 | 				(options.secondaryStorage ? "secondary-storage" : "memory"),
133 | 		},
134 | 		authCookies: cookies,
135 | 		logger,
136 | 		generateId: generateIdFunc,
137 | 		session: null,
138 | 		secondaryStorage: options.secondaryStorage,
139 | 		password: {
140 | 			hash: options.emailAndPassword?.password?.hash || hashPassword,
141 | 			verify: options.emailAndPassword?.password?.verify || verifyPassword,
142 | 			config: {
143 | 				minPasswordLength: options.emailAndPassword?.minPasswordLength || 8,
144 | 				maxPasswordLength: options.emailAndPassword?.maxPasswordLength || 128,
145 | 			},
146 | 			checkPassword,
147 | 		},
148 | 		setNewSession(session) {
149 | 			this.newSession = session;
150 | 		},
151 | 		newSession: null,
152 | 		adapter: adapter,
153 | 		internalAdapter: createInternalAdapter(adapter, {
154 | 			options,
155 | 			logger,
156 | 			hooks: options.databaseHooks ? [options.databaseHooks] : [],
157 | 			generateId: generateIdFunc,
158 | 		}),
159 | 		createAuthCookie: createCookieGetter(options),
160 | 		async runMigrations() {
161 | 			//only run migrations if database is provided and it's not an adapter
162 | 			if (!options.database || "updateMany" in options.database) {
163 | 				throw new BetterAuthError(
164 | 					"Database is not provided or it's an adapter. Migrations are only supported with a database instance.",
165 | 				);
166 | 			}
167 | 			const { runMigrations } = await getMigrations(options);
168 | 			await runMigrations();
169 | 		},
170 | 		publishTelemetry: publish,
171 | 		skipCSRFCheck: !!options.advanced?.disableCSRFCheck,
172 | 		skipOriginCheck:
173 | 			options.advanced?.disableOriginCheck !== undefined
174 | 				? options.advanced.disableOriginCheck
175 | 				: isTest()
176 | 					? true
177 | 					: false,
178 | 	};
179 | 	const initOrPromise = runPluginInit(ctx);
180 | 	let context: AuthContext;
181 | 	if (isPromise(initOrPromise)) {
182 | 		({ context } = await initOrPromise);
183 | 	} else {
184 | 		({ context } = initOrPromise);
185 | 	}
186 | 	return context;
187 | };
188 | 
189 | async function runPluginInit(ctx: AuthContext) {
190 | 	let options = ctx.options;
191 | 	const plugins = options.plugins || [];
192 | 	let context: AuthContext = ctx;
193 | 	const dbHooks: BetterAuthOptions["databaseHooks"][] = [];
194 | 	for (const plugin of plugins) {
195 | 		if (plugin.init) {
196 | 			let initPromise = plugin.init(context);
197 | 			let result: ReturnType<Required<BetterAuthPlugin>["init"]>;
198 | 			if (isPromise(initPromise)) {
199 | 				result = await initPromise;
200 | 			} else {
201 | 				result = initPromise;
202 | 			}
203 | 			if (typeof result === "object") {
204 | 				if (result.options) {
205 | 					const { databaseHooks, ...restOpts } = result.options;
206 | 					if (databaseHooks) {
207 | 						dbHooks.push(databaseHooks);
208 | 					}
209 | 					options = defu(options, restOpts);
210 | 				}
211 | 				if (result.context) {
212 | 					context = {
213 | 						...context,
214 | 						...(result.context as Partial<AuthContext>),
215 | 					};
216 | 				}
217 | 			}
218 | 		}
219 | 	}
220 | 	// Add the global database hooks last
221 | 	dbHooks.push(options.databaseHooks);
222 | 	context.internalAdapter = createInternalAdapter(ctx.adapter, {
223 | 		options,
224 | 		logger: ctx.logger,
225 | 		hooks: dbHooks.filter((u) => u !== undefined),
226 | 		generateId: ctx.generateId,
227 | 	});
228 | 	context.options = options;
229 | 	return { context };
230 | }
231 | 
232 | function getInternalPlugins(options: BetterAuthOptions) {
233 | 	const plugins: BetterAuthPlugin[] = [];
234 | 	if (options.advanced?.crossSubDomainCookies?.enabled) {
235 | 		//TODO: add internal plugin
236 | 	}
237 | 	return plugins;
238 | }
239 | 
240 | function getTrustedOrigins(options: BetterAuthOptions) {
241 | 	const baseURL = getBaseURL(options.baseURL, options.basePath);
242 | 	if (!baseURL) {
243 | 		return [];
244 | 	}
245 | 	const trustedOrigins = [new URL(baseURL).origin];
246 | 	if (options.trustedOrigins && Array.isArray(options.trustedOrigins)) {
247 | 		trustedOrigins.push(...options.trustedOrigins);
248 | 	}
249 | 	const envTrustedOrigins = env.BETTER_AUTH_TRUSTED_ORIGINS;
250 | 	if (envTrustedOrigins) {
251 | 		trustedOrigins.push(...envTrustedOrigins.split(","));
252 | 	}
253 | 	if (trustedOrigins.filter((x) => !x).length) {
254 | 		throw new BetterAuthError(
255 | 			"A provided trusted origin is invalid, make sure your trusted origins list is properly defined.",
256 | 		);
257 | 	}
258 | 	return trustedOrigins;
259 | }
260 | 
```
Page 25/71FirstPrevNextLast