#
tokens: 44986/50000 4/1094 files (page 40/67)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 40 of 67. 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
├── 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-decelration
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   └── index.ts
│       │   │   │   └── tsconfig.json
│       │   │   ├── tsconfig-exact-optional-property-types
│       │   │   │   ├── package.json
│       │   │   │   ├── src
│       │   │   │   │   ├── index.ts
│       │   │   │   │   ├── organization.ts
│       │   │   │   │   ├── user-additional-fields.ts
│       │   │   │   │   └── username.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
│   ├── 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
│   │   ├── tsconfig.test.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.json
└── turbo.json
```

# Files

--------------------------------------------------------------------------------
/docs/app/v1/bg-line.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | "use client";
  2 | import { cn } from "@/lib/utils";
  3 | import { motion } from "framer-motion";
  4 | import React from "react";
  5 | 
  6 | export const BackgroundLines = ({
  7 | 	children,
  8 | 	className,
  9 | 	svgOptions,
 10 | }: {
 11 | 	children: React.ReactNode;
 12 | 	className?: string;
 13 | 	svgOptions?: {
 14 | 		duration?: number;
 15 | 	};
 16 | }) => {
 17 | 	return (
 18 | 		<div
 19 | 			className={cn(
 20 | 				"h-[20rem] md:h-screen w-full bg-white dark:bg-black",
 21 | 				className,
 22 | 			)}
 23 | 		>
 24 | 			<SVG svgOptions={svgOptions} />
 25 | 			{children}
 26 | 		</div>
 27 | 	);
 28 | };
 29 | 
 30 | const pathVariants = {
 31 | 	initial: { strokeDashoffset: 800, strokeDasharray: "50 800" },
 32 | 	animate: {
 33 | 		strokeDashoffset: 0,
 34 | 		strokeDasharray: "20 800",
 35 | 		opacity: [0, 1, 1, 0],
 36 | 	},
 37 | };
 38 | 
 39 | const SVG = ({
 40 | 	svgOptions,
 41 | }: {
 42 | 	svgOptions?: {
 43 | 		duration?: number;
 44 | 	};
 45 | }) => {
 46 | 	const paths = [
 47 | 		"M720 450C720 450 742.459 440.315 755.249 425.626C768.039 410.937 778.88 418.741 789.478 401.499C800.076 384.258 817.06 389.269 826.741 380.436C836.423 371.603 851.957 364.826 863.182 356.242C874.408 347.657 877.993 342.678 898.867 333.214C919.741 323.75 923.618 319.88 934.875 310.177C946.133 300.474 960.784 300.837 970.584 287.701C980.384 274.564 993.538 273.334 1004.85 263.087C1016.15 252.84 1026.42 250.801 1038.22 242.1C1050.02 233.399 1065.19 230.418 1074.63 215.721C1084.07 201.024 1085.49 209.128 1112.65 194.884C1139.8 180.64 1132.49 178.205 1146.43 170.636C1160.37 163.066 1168.97 158.613 1181.46 147.982C1193.95 137.35 1191.16 131.382 1217.55 125.645C1243.93 119.907 1234.19 118.899 1254.53 100.846C1274.86 82.7922 1275.12 92.8914 1290.37 76.09C1305.62 59.2886 1313.91 62.1868 1323.19 56.7536C1332.48 51.3204 1347.93 42.8082 1361.95 32.1468C1375.96 21.4855 1374.06 25.168 1397.08 10.1863C1420.09 -4.79534 1421.41 -3.16992 1431.52 -15.0078",
 48 | 		"M720 450C720 450 741.044 435.759 753.062 410.636C765.079 385.514 770.541 386.148 782.73 370.489C794.918 354.83 799.378 353.188 811.338 332.597C823.298 312.005 825.578 306.419 843.707 295.493C861.837 284.568 856.194 273.248 877.376 256.48C898.558 239.713 887.536 227.843 909.648 214.958C931.759 202.073 925.133 188.092 941.063 177.621C956.994 167.151 952.171 154.663 971.197 135.041C990.222 115.418 990.785 109.375 999.488 96.1291C1008.19 82.8827 1011.4 82.2181 1032.65 61.8861C1053.9 41.5541 1045.74 48.0281 1064.01 19.5798C1082.29 -8.86844 1077.21 -3.89415 1093.7 -19.66C1110.18 -35.4258 1105.91 -46.1146 1127.68 -60.2834C1149.46 -74.4523 1144.37 -72.1024 1154.18 -97.6802C1163.99 -123.258 1165.6 -111.332 1186.21 -135.809C1206.81 -160.285 1203.29 -160.861 1220.31 -177.633C1237.33 -194.406 1236.97 -204.408 1250.42 -214.196",
 49 | 		"M720 450C720 450 712.336 437.768 690.248 407.156C668.161 376.544 672.543 394.253 665.951 365.784C659.358 337.316 647.903 347.461 636.929 323.197C625.956 298.933 626.831 303.639 609.939 281.01C593.048 258.381 598.7 255.282 582.342 242.504C565.985 229.726 566.053 217.66 559.169 197.116C552.284 176.572 549.348 171.846 529.347 156.529C509.345 141.211 522.053 134.054 505.192 115.653C488.33 97.2527 482.671 82.5627 473.599 70.7833C464.527 59.0039 464.784 50.2169 447 32.0721C429.215 13.9272 436.29 0.858563 423.534 -12.6868C410.777 -26.2322 407.424 -44.0808 394.364 -56.4916C381.303 -68.9024 373.709 -72.6804 365.591 -96.1992C357.473 -119.718 358.364 -111.509 338.222 -136.495C318.08 -161.481 322.797 -149.499 315.32 -181.761C307.843 -214.023 294.563 -202.561 285.795 -223.25C277.026 -243.94 275.199 -244.055 258.602 -263.871",
 50 | 		"M720 450C720 450 738.983 448.651 790.209 446.852C841.436 445.052 816.31 441.421 861.866 437.296C907.422 433.172 886.273 437.037 930.656 436.651C975.04 436.264 951.399 432.343 1001.57 425.74C1051.73 419.138 1020.72 425.208 1072.85 424.127C1124.97 423.047 1114.39 420.097 1140.02 414.426C1165.65 408.754 1173.1 412.143 1214.55 411.063C1256.01 409.983 1242.78 406.182 1285.56 401.536C1328.35 396.889 1304.66 400.796 1354.41 399.573C1404.16 398.35 1381.34 394.315 1428.34 389.376C1475.35 384.438 1445.96 386.509 1497.93 385.313C1549.9 384.117 1534.63 382.499 1567.23 381.48",
 51 | 		"M720 450C720 450 696.366 458.841 682.407 472.967C668.448 487.093 673.23 487.471 647.919 492.882C622.608 498.293 636.85 499.899 609.016 512.944C581.182 525.989 596.778 528.494 571.937 533.778C547.095 539.062 551.762 548.656 536.862 556.816C521.962 564.975 515.626 563.279 497.589 575.159C479.552 587.04 484.343 590.435 461.111 598.728C437.879 607.021 442.512 605.226 423.603 618.397C404.694 631.569 402.411 629.541 390.805 641.555C379.2 653.568 369.754 658.175 353.238 663.929C336.722 669.683 330.161 674.689 312.831 684.116C295.5 693.543 288.711 698.815 278.229 704.041C267.747 709.267 258.395 712.506 240.378 726.65C222.361 740.795 230.097 738.379 203.447 745.613C176.797 752.847 193.747 752.523 166.401 767.148C139.056 781.774 151.342 783.641 130.156 791.074C108.97 798.507 116.461 802.688 96.0974 808.817C75.7334 814.946 83.8553 819.505 59.4513 830.576C35.0473 841.648 48.2548 847.874 21.8337 853.886C-4.58739 859.898 10.5966 869.102 -16.396 874.524",
 52 | 		"M720 450C720 450 695.644 482.465 682.699 506.197C669.755 529.929 671.059 521.996 643.673 556.974C616.286 591.951 625.698 590.8 606.938 615.255C588.178 639.71 592.715 642.351 569.76 665.92C546.805 689.49 557.014 687.498 538.136 722.318C519.258 757.137 520.671 760.818 503.256 774.428C485.841 788.038 491.288 790.063 463.484 831.358C435.681 872.653 437.554 867.001 425.147 885.248C412.74 903.495 411.451 911.175 389.505 934.331C367.559 957.486 375.779 966.276 352.213 990.918C328.647 1015.56 341.908 1008.07 316.804 1047.24C291.699 1086.42 301.938 1060.92 276.644 1100.23C251.349 1139.54 259.792 1138.78 243.151 1153.64",
 53 | 		"M719.974 450C719.974 450 765.293 459.346 789.305 476.402C813.318 493.459 825.526 487.104 865.093 495.586C904.659 504.068 908.361 510.231 943.918 523.51C979.475 536.789 963.13 535.277 1009.79 547.428C1056.45 559.579 1062.34 555.797 1089.82 568.96C1117.31 582.124 1133.96 582.816 1159.12 592.861C1184.28 602.906 1182.84 603.359 1233.48 614.514C1284.12 625.67 1254.63 632.207 1306.33 644.465C1358.04 656.723 1359.27 656.568 1378.67 670.21C1398.07 683.852 1406.16 676.466 1456.34 692.827C1506.51 709.188 1497.73 708.471 1527.54 715.212",
 54 | 		"M720 450C720 450 727.941 430.821 734.406 379.251C740.87 327.681 742.857 359.402 757.864 309.798C772.871 260.194 761.947 271.093 772.992 244.308C784.036 217.524 777.105 200.533 786.808 175.699C796.511 150.864 797.141 144.333 808.694 107.307C820.247 70.2821 812.404 88.4169 819.202 37.1016C826 -14.2137 829.525 -0.990829 839.341 -30.3874C849.157 -59.784 844.404 -61.5924 855.042 -98.7516C865.68 -135.911 862.018 -144.559 876.924 -167.488C891.83 -190.418 886.075 -213.535 892.87 -237.945C899.664 -262.355 903.01 -255.031 909.701 -305.588C916.393 -356.144 917.232 -330.612 925.531 -374.777",
 55 | 		"M720 450C720 450 722.468 499.363 726.104 520.449C729.739 541.535 730.644 550.025 738.836 589.07C747.028 628.115 743.766 639.319 746.146 659.812C748.526 680.306 754.006 693.598 757.006 732.469C760.007 771.34 760.322 765.244 763.893 805.195C767.465 845.146 769.92 822.227 773.398 868.469C776.875 914.71 776.207 901.365 778.233 940.19C780.259 979.015 782.53 990.477 787.977 1010.39C793.424 1030.3 791.788 1060.01 797.243 1082.24C802.698 1104.47 801.758 1130.29 808.181 1149.64C814.604 1168.99 813.135 1171.5 818.026 1225.28C822.918 1279.06 820.269 1267.92 822.905 1293.75",
 56 | 		"M720 450C720 450 737.033 492.46 757.251 515.772C777.468 539.084 768.146 548.687 785.517 570.846C802.887 593.005 814.782 609.698 824.589 634.112C834.395 658.525 838.791 656.702 855.55 695.611C872.31 734.519 875.197 724.854 890.204 764.253C905.21 803.653 899.844 790.872 919.927 820.763C940.01 850.654 939.071 862.583 954.382 886.946C969.693 911.309 968.683 909.254 993.997 945.221C1019.31 981.187 1006.67 964.436 1023.49 1007.61C1040.32 1050.79 1046.15 1038.25 1059.01 1073.05C1071.88 1107.86 1081.39 1096.19 1089.45 1131.96C1097.51 1167.73 1106.52 1162.12 1125.77 1196.89",
 57 | 		"M720 450C720 450 687.302 455.326 670.489 467.898C653.676 480.47 653.159 476.959 626.58 485.127C600.002 493.295 599.626 495.362 577.94 503.841C556.254 512.319 556.35 507.426 533.958 517.44C511.566 527.454 505.82 526.441 486.464 539.172C467.108 551.904 461.312 546.36 439.357 553.508C417.402 560.657 406.993 567.736 389.393 572.603C371.794 577.47 371.139 583.76 344.54 587.931C317.941 592.102 327.375 593.682 299.411 607.275C271.447 620.868 283.617 615.022 249.868 622.622C216.119 630.223 227.07 630.86 203.77 638.635C180.47 646.41 168.948 652.487 156.407 657.28C143.866 662.073 132.426 669.534 110.894 675.555C89.3615 681.575 90.3234 680.232 61.1669 689.897C32.0105 699.562 34.3696 702.021 15.9011 709.789C-2.56738 717.558 2.38861 719.841 -29.9494 729.462C-62.2873 739.083 -52.5552 738.225 -77.4307 744.286",
 58 | 		"M720 450C720 450 743.97 465.061 754.884 490.648C765.798 516.235 781.032 501.34 791.376 525.115C801.72 548.889 808.417 538.333 829.306 564.807C850.195 591.281 852.336 582.531 865.086 601.843C877.835 621.155 874.512 621.773 902.383 643.857C930.255 665.94 921.885 655.976 938.025 681.74C954.164 707.505 959.384 709.719 977.273 720.525C995.162 731.33 994.233 731.096 1015.92 757.676C1037.61 784.257 1025.74 768.848 1047.82 795.343C1069.91 821.837 1065.95 815.45 1085.93 834.73C1105.91 854.009 1110.53 848.089 1124.97 869.759C1139.4 891.428 1140.57 881.585 1158.53 911.499C1176.5 941.414 1184.96 933.829 1194.53 948.792C1204.09 963.755 1221.35 973.711 1232.08 986.224C1242.8 998.738 1257.34 1015.61 1269.99 1026.53C1282.63 1037.45 1293.81 1040.91 1307.21 1064.56",
 59 | 		"M720 450C720 450 718.24 412.717 716.359 397.31C714.478 381.902 713.988 362.237 710.785 344.829C707.582 327.42 708.407 322.274 701.686 292.106C694.965 261.937 699.926 270.857 694.84 240.765C689.753 210.674 693.055 217.076 689.674 184.902C686.293 152.728 686.041 149.091 682.676 133.657C679.311 118.223 682.23 106.005 681.826 80.8297C681.423 55.6545 677.891 60.196 675.66 30.0226C673.429 -0.150848 672.665 -7.94842 668.592 -26.771C664.52 -45.5935 664.724 -43.0755 661.034 -78.7766C657.343 -114.478 658.509 -103.181 653.867 -133.45C649.226 -163.719 650.748 -150.38 647.052 -182.682C643.357 -214.984 646.125 -214.921 645.216 -238.402C644.307 -261.883 640.872 -253.4 637.237 -291.706C633.602 -330.012 634.146 -309.868 630.717 -343.769C627.288 -377.669 628.008 -370.682 626.514 -394.844",
 60 | 		"M720 450C720 450 730.384 481.55 739.215 507.557C748.047 533.564 751.618 537.619 766.222 562.033C780.825 586.447 774.187 582.307 787.606 618.195C801.025 654.082 793.116 653.536 809.138 678.315C825.16 703.095 815.485 717.073 829.898 735.518C844.311 753.964 845.351 773.196 852.197 786.599C859.042 800.001 862.876 805.65 872.809 845.974C882.742 886.297 885.179 874.677 894.963 903.246C904.747 931.816 911.787 924.243 921.827 961.809C931.867 999.374 927.557 998.784 940.377 1013.59C953.197 1028.4 948.555 1055.77 966.147 1070.54C983.739 1085.31 975.539 1105.69 988.65 1125.69C1001.76 1145.69 1001.82 1141.59 1007.54 1184.37C1013.27 1227.15 1018.98 1198.8 1029.67 1241.58",
 61 | 		"M720 450C720 450 684.591 447.135 657.288 439.014C629.985 430.894 618.318 435.733 600.698 431.723C583.077 427.714 566.975 425.639 537.839 423.315C508.704 420.991 501.987 418.958 476.29 413.658C450.592 408.359 460.205 410.268 416.97 408.927C373.736 407.586 396.443 401.379 359.262 396.612C322.081 391.844 327.081 393.286 300.224 391.917C273.368 390.547 264.902 385.49 241.279 382.114C217.655 378.739 205.497 378.95 181.98 377.253C158.464 375.556 150.084 369.938 117.474 366.078C84.8644 362.218 81.5401 361.501 58.8734 358.545C36.2067 355.59 33.6442 351.938 -3.92281 346.728C-41.4898 341.519 -18.6466 345.082 -61.4654 341.179C-104.284 337.275 -102.32 338.048 -121.821 332.369",
 62 | 		"M720 450C720 450 714.384 428.193 708.622 410.693C702.86 393.193 705.531 397.066 703.397 372.66C701.264 348.254 697.8 345.181 691.079 330.466C684.357 315.751 686.929 312.356 683.352 292.664C679.776 272.973 679.079 273.949 674.646 255.07C670.213 236.192 670.622 244.371 665.271 214.561C659.921 184.751 659.864 200.13 653.352 172.377C646.841 144.623 647.767 151.954 644.123 136.021C640.48 120.088 638.183 107.491 636.127 96.8178C634.072 86.1443 632.548 77.5871 626.743 54.0492C620.938 30.5112 622.818 28.9757 618.613 16.577C614.407 4.17831 615.555 -13.1527 608.752 -24.5691C601.95 -35.9855 603.375 -51.0511 599.526 -60.1492C595.678 -69.2472 593.676 -79.3623 587.865 -100.431C582.053 -121.5 584.628 -117.913 578.882 -139.408C573.137 -160.903 576.516 -161.693 571.966 -182.241C567.416 -202.789 567.42 -198.681 562.834 -218.28C558.248 -237.879 555.335 -240.47 552.072 -260.968C548.808 -281.466 547.605 -280.956 541.772 -296.427C535.94 -311.898 537.352 -315.211 535.128 -336.018C532.905 -356.826 531.15 -360.702 524.129 -377.124",
 63 | 		"M720 450C720 450 711.433 430.82 707.745 409.428C704.056 388.035 704.937 381.711 697.503 370.916C690.069 360.121 691.274 359.999 685.371 334.109C679.469 308.22 677.496 323.883 671.24 294.303C664.984 264.724 667.608 284.849 662.065 258.116C656.522 231.383 656.357 229.024 647.442 216.172C638.527 203.319 640.134 192.925 635.555 178.727C630.976 164.529 630.575 150.179 624.994 139.987C619.413 129.794 615.849 112.779 612.251 103.074C608.654 93.3696 606.942 85.6729 603.041 63.0758C599.14 40.4787 595.242 36.9267 589.533 23.8967C583.823 10.8666 581.18 -2.12401 576.96 -14.8333C572.739 -27.5425 572.696 -37.7703 568.334 -51.3441C563.972 -64.9179 562.14 -67.2124 556.992 -93.299C551.844 -119.386 550.685 -109.743 544.056 -129.801C537.428 -149.859 534.97 -151.977 531.034 -170.076C527.099 -188.175 522.979 -185.119 519.996 -207.061C517.012 -229.004 511.045 -224.126 507.478 -247.077C503.912 -270.029 501.417 -271.033 495.534 -287C489.651 -302.968 491.488 -300.977 484.68 -326.317C477.872 -351.657 476.704 -348.494 472.792 -363.258",
 64 | 		"M720 450C720 450 723.524 466.673 728.513 497.319C733.503 527.964 731.894 519.823 740.001 542.706C748.108 565.589 744.225 560.598 748.996 588.365C753.766 616.131 756.585 602.096 761.881 636.194C767.178 670.293 768.155 649.089 771.853 679.845C775.551 710.6 775.965 703.738 781.753 724.555C787.54 745.372 787.248 758.418 791.422 773.79C795.596 789.162 798.173 807.631 804.056 819.914C809.938 832.197 806.864 843.07 811.518 865.275C816.171 887.48 816.551 892.1 822.737 912.643C828.922 933.185 830.255 942.089 833.153 956.603C836.052 971.117 839.475 969.242 846.83 1003.98C854.185 1038.71 850.193 1028.86 854.119 1048.67C858.045 1068.48 857.963 1074.39 863.202 1094.94C868.44 1115.49 867.891 1108.03 874.497 1138.67C881.102 1169.31 880.502 1170.72 887.307 1186.56C894.111 1202.4 890.388 1209.75 896.507 1231.25C902.627 1252.76 902.54 1245.39 906.742 1279.23",
 65 | 		"M720 450C720 450 698.654 436.893 669.785 424.902C640.916 412.91 634.741 410.601 615.568 402.586C596.396 394.571 594.829 395.346 568.66 378.206C542.492 361.067 547.454 359.714 514.087 348.978C480.721 338.242 479.79 334.731 467.646 329.846C455.502 324.96 448.63 312.156 416.039 303.755C383.448 295.354 391.682 293.73 365.021 280.975C338.36 268.219 328.715 267.114 309.809 252.575C290.903 238.036 277.185 246.984 259.529 230.958C241.873 214.931 240.502 224.403 211.912 206.241C183.323 188.078 193.288 190.89 157.03 181.714C120.772 172.538 127.621 170.109 108.253 154.714C88.8857 139.319 75.4927 138.974 56.9647 132.314C38.4366 125.654 33.8997 118.704 4.77584 106.7C-24.348 94.6959 -19.1326 90.266 -46.165 81.9082",
 66 | 		"M720 450C720 450 711.596 475.85 701.025 516.114C690.455 556.378 697.124 559.466 689.441 579.079C681.758 598.693 679.099 597.524 675.382 642.732C671.665 687.94 663.4 677.024 657.844 700.179C652.288 723.333 651.086 724.914 636.904 764.536C622.723 804.158 631.218 802.853 625.414 827.056C619.611 851.259 613.734 856.28 605.94 892.262C598.146 928.244 595.403 924.314 588.884 957.785C582.364 991.255 583.079 991.176 575.561 1022.63C568.044 1054.08 566.807 1058.45 558.142 1084.32C549.476 1110.2 553.961 1129.13 542.367 1149.25C530.772 1169.37 538.268 1180.37 530.338 1207.27C522.407 1234.17 520.826 1245.53 512.156 1274.2",
 67 | 		"M720 450C720 450 730.571 424.312 761.424 411.44C792.277 398.569 772.385 393.283 804.069 377.232C835.752 361.182 829.975 361.373 848.987 342.782C867.999 324.192 877.583 330.096 890.892 303.897C904.201 277.698 910.277 282.253 937.396 264.293C964.514 246.333 949.357 246.834 978.7 230.438C1008.04 214.042 990.424 217.952 1021.51 193.853C1052.6 169.753 1054.28 184.725 1065.97 158.075C1077.65 131.425 1087.76 139.068 1111.12 120.345C1134.49 101.622 1124.9 104.858 1151.67 86.3162C1178.43 67.7741 1167.09 66.2676 1197.53 47.2606C1227.96 28.2536 1225.78 23.2186 1239.27 12.9649C1252.76 2.7112 1269.32 -9.47929 1282.88 -28.5587C1296.44 -47.6381 1305.81 -41.3853 1323.82 -62.7027C1341.83 -84.0202 1340.32 -82.3794 1368.98 -98.9326",
 68 | 	];
 69 | 
 70 | 	const colors = [
 71 | 		"#46A5CA",
 72 | 		"#8C2F2F",
 73 | 		"#4FAE4D",
 74 | 		"#D6590C",
 75 | 		"#811010",
 76 | 		"#247AFB",
 77 | 		"#A534A0",
 78 | 		"#A8A438",
 79 | 		"#D6590C",
 80 | 		"#46A29C",
 81 | 		"#670F6D",
 82 | 		"#D7C200",
 83 | 		"#59BBEB",
 84 | 		"#504F1C",
 85 | 		"#55BC54",
 86 | 		"#4D3568",
 87 | 		"#9F39A5",
 88 | 		"#363636",
 89 | 		"#860909",
 90 | 		"#6A286F",
 91 | 		"#604483",
 92 | 	];
 93 | 	return (
 94 | 		<motion.svg
 95 | 			viewBox="0 0 1440 900"
 96 | 			fill="none"
 97 | 			xmlns="http://www.w3.org/2000/svg"
 98 | 			initial={{ opacity: 0 }}
 99 | 			animate={{ opacity: 1 }}
100 | 			transition={{ duration: 1 }}
101 | 			className="absolute inset-0 w-full h-full"
102 | 		>
103 | 			{paths.map((path, idx) => (
104 | 				<motion.path
105 | 					d={path}
106 | 					stroke={colors[idx]}
107 | 					strokeWidth="2.3"
108 | 					strokeLinecap="round"
109 | 					variants={pathVariants}
110 | 					initial="initial"
111 | 					animate="animate"
112 | 					transition={{
113 | 						duration: svgOptions?.duration || 10,
114 | 						ease: "linear",
115 | 						repeat: Infinity,
116 | 						repeatType: "loop",
117 | 						delay: Math.floor(Math.random() * 10),
118 | 						repeatDelay: Math.floor(Math.random() * 10 + 2),
119 | 					}}
120 | 					key={`path-first-${idx}`}
121 | 				/>
122 | 			))}
123 | 
124 | 			{/* duplicate for more paths */}
125 | 			{paths.map((path, idx) => (
126 | 				<motion.path
127 | 					d={path}
128 | 					stroke={colors[idx]}
129 | 					strokeWidth="2.3"
130 | 					strokeLinecap="round"
131 | 					variants={pathVariants}
132 | 					initial="initial"
133 | 					animate="animate"
134 | 					transition={{
135 | 						duration: svgOptions?.duration || 10,
136 | 						ease: "linear",
137 | 						repeat: Infinity,
138 | 						repeatType: "loop",
139 | 						delay: Math.floor(Math.random() * 10),
140 | 						repeatDelay: Math.floor(Math.random() * 10 + 2),
141 | 					}}
142 | 					key={`path-second-${idx}`}
143 | 				/>
144 | 			))}
145 | 		</motion.svg>
146 | 	);
147 | };
148 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/guides/create-a-db-adapter.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Create a Database Adapter
  3 | description: Learn how to create a custom database adapter for Better-Auth
  4 | ---
  5 | 
  6 | Learn how to create a custom database adapter for Better-Auth using `createAdapter`.
  7 | 
  8 | Our `createAdapter` function is designed to be very flexible, and we've done our best to make it easy to understand and use.
  9 | Our hope is to allow you to focus on writing database logic, and not have to worry about how the adapter is working with Better-Auth.
 10 | 
 11 | Anything from custom schema configurations, custom ID generation, safe JSON parsing, and more is handled by the `createAdapter` function.
 12 | All you need to do is provide the database logic, and the `createAdapter` function will handle the rest.
 13 | 
 14 | ## Quick Start
 15 | 
 16 | <Steps>
 17 | <Step>
 18 | ### Get things ready
 19 | 
 20 | 1. Import `createAdapter`.
 21 | 2. Create `CustomAdapterConfig` interface that represents your adapter config options.
 22 | 3. Create the adapter!
 23 | 
 24 | ```ts
 25 | import { createAdapter, type DBAdapterDebugLogOption } from "better-auth/adapters";
 26 | 
 27 | // Your custom adapter config options
 28 | interface CustomAdapterConfig {
 29 |   /**
 30 |    * Helps you debug issues with the adapter.
 31 |    */
 32 |   debugLogs?: DBAdapterDebugLogOption;
 33 |   /**
 34 |    * If the table names in the schema are plural.
 35 |    */
 36 |   usePlural?: boolean;
 37 | }
 38 | 
 39 | export const myAdapter = (config: CustomAdapterConfig = {}) =>
 40 |   createAdapter({
 41 |     // ...
 42 |   });
 43 | ```
 44 | 
 45 | </Step>
 46 | 
 47 | <Step>
 48 | ### Configure the adapter
 49 | 
 50 | The `config` object is mostly used to provide information about the adapter to Better-Auth.
 51 | We try to minimize the amount of code you need to write in your adapter functions, and these `config` options are used to help us do that.
 52 | 
 53 | ```ts
 54 | // ...
 55 | export const myAdapter = (config: CustomAdapterConfig = {}) =>
 56 |   createAdapter({
 57 |     config: {
 58 |       adapterId: "custom-adapter", // A unique identifier for the adapter.
 59 |       adapterName: "Custom Adapter", // The name of the adapter.
 60 |       usePlural: config.usePlural ?? false, // Whether the table names in the schema are plural.
 61 |       debugLogs: config.debugLogs ?? false, // Whether to enable debug logs.
 62 |       supportsJSON: false, // Whether the database supports JSON. (Default: false)
 63 |       supportsDates: true, // Whether the database supports dates. (Default: true)
 64 |       supportsBooleans: true, // Whether the database supports booleans. (Default: true)
 65 |       supportsNumericIds: true, // Whether the database supports auto-incrementing numeric IDs. (Default: true)
 66 |     },
 67 |     // ...
 68 |   });
 69 | ```
 70 | </Step>
 71 | 
 72 | <Step>
 73 | ### Create the adapter
 74 | 
 75 | The `adapter` function is where you write the code that interacts with your database.
 76 | 
 77 | ```ts
 78 | // ...
 79 | export const myAdapter = (config: CustomAdapterConfig = {}) =>
 80 |   createAdapter({
 81 |     config: {
 82 |       // ...
 83 |     },
 84 |     adapter: ({}) => {
 85 |       return {
 86 |         create: async ({ data, model, select }) => {
 87 |           // ...
 88 |         },
 89 |         update: async ({ data, model, select }) => {
 90 |           // ...
 91 |         },
 92 |         updateMany: async ({ data, model, select }) => {
 93 |           // ...
 94 |         },
 95 |         delete: async ({ data, model, select }) => {
 96 |           // ...
 97 |         },
 98 |         // ...
 99 |       };
100 |     },
101 |   });
102 | ```
103 | 
104 | <Callout>
105 | Learn more about the `adapter` here [here](/docs/concepts/database#adapters).
106 | </Callout>
107 | </Step>
108 | 
109 | </Steps>
110 | 
111 | ## Adapter
112 | 
113 | The `adapter` function is where you write the code that interacts with your database.
114 | 
115 | If you haven't already, check out the `options` object in the [config section](#config), as it can be useful for your adapter.
116 | 
117 | Before we get into the adapter function, let's go over the parameters that are available to you.
118 | 
119 | - `options`: The Better Auth options.
120 | - `schema`: The schema from the user's Better Auth instance.
121 | - `debugLog`: The debug log function.
122 | - `getField`: The get field function.
123 | - `getDefaultModelName`: The get default model name function.
124 | - `getDefaultFieldName`: The get default field name function.
125 | - `getFieldAttributes`: The get field attributes function.
126 | 
127 | ```ts title="Example"
128 | adapter: ({
129 |   options,
130 |   schema,
131 |   debugLog,
132 |   getField,
133 |   getDefaultModelName,
134 |   getDefaultFieldName,
135 | }) => {
136 |   return {
137 |     // ...
138 |   };
139 | };
140 | ```
141 | 
142 | ### Adapter Methods
143 | 
144 | - All `model` values are already transformed into the correct model name for the database based on the end-user's schema configuration.
145 |   - This also means that if you need access to the `schema` version of a given model, you can't use this exact `model` value, you'll need to use the `getDefaultModelName` function provided in the options to convert the `model` to the `schema` version.
146 | - We will automatically fill in any missing fields you return based on the user's `schema` configuration.
147 | - Any method that includes a `select` parameter, is only for the purpose of getting data from your database more efficiently. You do not need to worry about only returning what the `select` parameter states, as we will handle that for you.
148 | 
149 | ### `create` method
150 | 
151 | The `create` method is used to create a new record in the database.
152 | 
153 | <Callout>
154 | Note:
155 | If the user has enabled the `useNumberId` option, or if `generateId` is `false` in the user's Better Auth config,
156 | then it's expected that the `id` is provided in the `data` object. Otherwise, the `id` will be automatically generated.
157 | 
158 | Additionally, it's possible to pass `forceAllowId` as a parameter to the `create` method, which allows `id` to be provided in the `data` object.
159 | We handle `forceAllowId` internally, so you don't need to worry about it.
160 | </Callout>
161 | 
162 | parameters:
163 | 
164 | - `model`: The model/table name that new data will be inserted into.
165 | - `data`: The data to insert into the database.
166 | - `select`: An array of fields to return from the database.
167 | 
168 | <Callout>
169 |   Make sure to return the data that is inserted into the database.
170 | </Callout>
171 | 
172 | ```ts title="Example"
173 | create: async ({ model, data, select }) => {
174 |   // Example of inserting data into the database.
175 |   return await db.insert(model).values(data);
176 | };
177 | ```
178 | 
179 | ### `update` method
180 | 
181 | The `update` method is used to update a record in the database.
182 | 
183 | parameters:
184 | 
185 | - `model`: The model/table name that the record will be updated in.
186 | - `where`: The `where` clause to update the record by.
187 | - `update`: The data to update the record with.
188 | 
189 | <Callout>
190 |   Make sure to return the data in the row which is updated. This includes any
191 |   fields that were not updated.
192 | </Callout>
193 | 
194 | ```ts title="Example"
195 | update: async ({ model, where, update }) => {
196 |   // Example of updating data in the database.
197 |   return await db.update(model).set(update).where(where);
198 | };
199 | ```
200 | 
201 | ### `updateMany` method
202 | 
203 | The `updateMany` method is used to update multiple records in the database.
204 | 
205 | parameters:
206 | 
207 | - `model`: The model/table name that the records will be updated in.
208 | - `where`: The `where` clause to update the records by.
209 | - `update`: The data to update the records with.
210 | 
211 | <Callout>Make sure to return the number of records that were updated.</Callout>
212 | 
213 | ```ts title="Example"
214 | updateMany: async ({ model, where, update }) => {
215 |   // Example of updating multiple records in the database.
216 |   return await db.update(model).set(update).where(where);
217 | };
218 | ```
219 | 
220 | ### `delete` method
221 | 
222 | The `delete` method is used to delete a record from the database.
223 | 
224 | parameters:
225 | 
226 | - `model`: The model/table name that the record will be deleted from.
227 | - `where`: The `where` clause to delete the record by.
228 | 
229 | ```ts title="Example"
230 | delete: async ({ model, where }) => {
231 |   // Example of deleting a record from the database.
232 |   await db.delete(model).where(where);
233 | }
234 | ```
235 | 
236 | ### `deleteMany` method
237 | 
238 | The `deleteMany` method is used to delete multiple records from the database.
239 | 
240 | parameters:
241 | 
242 | - `model`: The model/table name that the records will be deleted from.
243 | - `where`: The `where` clause to delete the records by.
244 | 
245 | <Callout>Make sure to return the number of records that were deleted.</Callout>
246 | 
247 | ```ts title="Example"
248 | deleteMany: async ({ model, where }) => {
249 |   // Example of deleting multiple records from the database.
250 |   return await db.delete(model).where(where);
251 | };
252 | ```
253 | 
254 | ### `findOne` method
255 | 
256 | The `findOne` method is used to find a single record in the database.
257 | 
258 | parameters:
259 | 
260 | - `model`: The model/table name that the record will be found in.
261 | - `where`: The `where` clause to find the record by.
262 | - `select`: The `select` clause to return.
263 | 
264 | <Callout>Make sure to return the data that is found in the database.</Callout>
265 | 
266 | ```ts title="Example"
267 | findOne: async ({ model, where, select }) => {
268 |   // Example of finding a single record in the database.
269 |   return await db.select().from(model).where(where).limit(1);
270 | };
271 | ```
272 | 
273 | ### `findMany` method
274 | 
275 | The `findMany` method is used to find multiple records in the database.
276 | 
277 | parameters:
278 | 
279 | - `model`: The model/table name that the records will be found in.
280 | - `where`: The `where` clause to find the records by.
281 | - `limit`: The limit of records to return.
282 | - `sortBy`: The `sortBy` clause to sort the records by.
283 | - `offset`: The offset of records to return.
284 | 
285 | <Callout>
286 |   Make sure to return the array of data that is found in the database.
287 | </Callout>
288 | 
289 | ```ts title="Example"
290 | findMany: async ({ model, where, limit, sortBy, offset }) => {
291 |   // Example of finding multiple records in the database.
292 |   return await db
293 |     .select()
294 |     .from(model)
295 |     .where(where)
296 |     .limit(limit)
297 |     .offset(offset)
298 |     .orderBy(sortBy);
299 | };
300 | ```
301 | 
302 | ### `count` method
303 | 
304 | The `count` method is used to count the number of records in the database.
305 | 
306 | parameters:
307 | 
308 | - `model`: The model/table name that the records will be counted in.
309 | - `where`: The `where` clause to count the records by.
310 | 
311 | <Callout>Make sure to return the number of records that were counted.</Callout>
312 | 
313 | ```ts title="Example"
314 | count: async ({ model, where }) => {
315 |   // Example of counting the number of records in the database.
316 |   return await db.select().from(model).where(where).count();
317 | };
318 | ```
319 | 
320 | ### `options` (optional)
321 | 
322 | The `options` object is for any potential config that you got from your custom adapter options.
323 | 
324 | ```ts title="Example"
325 | const myAdapter = (config: CustomAdapterConfig) =>
326 |   createAdapter({
327 |     config: {
328 |       // ...
329 |     },
330 |     adapter: ({ options }) => {
331 |       return {
332 |         options: config,
333 |       };
334 |     },
335 |   });
336 | ```
337 | 
338 | ### `createSchema` (optional)
339 | 
340 | The `createSchema` method allows the [Better Auth CLI](/docs/concepts/cli) to [generate](/docs/concepts/cli/#generate) a schema for the database.
341 | 
342 | parameters:
343 | 
344 | - `tables`: The tables from the user's Better-Auth instance schema; which is expected to be generated into the schema file.
345 | - `file`: The file the user may have passed in to the `generate` command as the expected schema file output path.
346 | 
347 | ```ts title="Example"
348 | createSchema: async ({ file, tables }) => {
349 |   // ... Custom logic to create a schema for the database.
350 | };
351 | ```
352 | 
353 | ## Test your adapter
354 | 
355 | We've provided a test suite that you can use to test your adapter. It requires you to use `vitest`.
356 | 
357 | ```ts title="my-adapter.test.ts"
358 | import { expect, test, describe } from "vitest";
359 | import { runAdapterTest } from "better-auth/adapters/test";
360 | import { myAdapter } from "./my-adapter";
361 | 
362 | describe("My Adapter Tests", async () => {
363 |   afterAll(async () => {
364 |     // Run DB cleanup here...
365 |   });
366 |   const adapter = myAdapter({
367 |     debugLogs: {
368 |       // If your adapter config allows passing in debug logs, then pass this here.
369 |       isRunningAdapterTests: true, // This is our super secret flag to let us know to only log debug logs if a test fails.
370 |     },
371 |   });
372 | 
373 |   runAdapterTest({
374 |     getAdapter: async (betterAuthOptions = {}) => {
375 |       return adapter(betterAuthOptions);
376 |     },
377 |   });
378 | });
379 | ```
380 | 
381 | ### Numeric ID tests
382 | 
383 | If your database supports numeric IDs, then you should run this test as well:
384 | 
385 | ```ts title="my-adapter.number-id.test.ts"
386 | import { expect, test, describe } from "vitest";
387 | import { runNumberIdAdapterTest } from "better-auth/adapters/test";
388 | import { myAdapter } from "./my-adapter";
389 | 
390 | describe("My Adapter Numeric ID Tests", async () => {
391 |   afterAll(async () => {
392 |     // Run DB cleanup here...
393 |   });
394 |   const adapter = myAdapter({
395 |     debugLogs: {
396 |       // If your adapter config allows passing in debug logs, then pass this here.
397 |       isRunningAdapterTests: true, // This is our super secret flag to let us know to only log debug logs if a test fails.
398 |     },
399 |   });
400 | 
401 |   runNumberIdAdapterTest({
402 |     getAdapter: async (betterAuthOptions = {}) => {
403 |       return adapter(betterAuthOptions);
404 |     },
405 |   });
406 | });
407 | ```
408 | 
409 | ## Config
410 | 
411 | The `config` object is used to provide information about the adapter to Better-Auth.
412 | 
413 | We **highly recommend** going through and reading each provided option below, as it will help you understand how to properly configure your adapter.
414 | 
415 | ### Required Config
416 | 
417 | ### `adapterId`
418 | 
419 | A unique identifier for the adapter.
420 | 
421 | ### `adapterName`
422 | 
423 | The name of the adapter.
424 | 
425 | ### Optional Config
426 | 
427 | ### `supportsNumericIds`
428 | 
429 | Whether the database supports numeric IDs. If this is set to `false` and the user's config has enabled `useNumberId`, then we will throw an error.
430 | 
431 | ### `supportsJSON`
432 | 
433 | Whether the database supports JSON. If the database doesn't support JSON, we will use a `string` to save the JSON data.And when we retrieve the data, we will safely parse the `string` back into a JSON object.
434 | 
435 | ### `supportsDates`
436 | 
437 | Whether the database supports dates. If the database doesn't support dates, we will use a `string` to save the date. (ISO string) When we retrieve the data, we will safely parse the `string` back into a `Date` object.
438 | 
439 | ### `supportsBooleans`
440 | 
441 | Whether the database supports booleans. If the database doesn't support booleans, we will use a `0` or `1` to save the boolean value. When we retrieve the data, we will safely parse the `0` or `1` back into a boolean value.
442 | 
443 | ### `usePlural`
444 | 
445 | Whether the table names in the schema are plural. This is often defined by the user, and passed down through your custom adapter options. If you do not intend to allow the user to customize the table names, you can ignore this option, or set this to `false`.
446 | 
447 | ```ts title="Example"
448 | const adapter = myAdapter({
449 |   // This value then gets passed into the `usePlural`
450 |   // option in the createAdapter `config` object.
451 |   usePlural: true,
452 | });
453 | ```
454 | 
455 | ### `transaction`
456 | 
457 | Whether the adapter supports transactions. If `false`, operations run sequentially; otherwise provide a function that executes a callback with a `TransactionAdapter`.
458 | 
459 | <Callout type="warn">
460 |   If your database does not support transactions, the error handling and rollback
461 |   will not be as robust. We recommend using a database that supports transactions
462 |   for better data integrity.
463 | </Callout>
464 | 
465 | ### `debugLogs`
466 | 
467 | Used to enable debug logs for the adapter. You can pass in a boolean, or an object with the following keys: `create`, `update`, `updateMany`, `findOne`, `findMany`, `delete`, `deleteMany`, `count`.
468 | If any of the keys are `true`, the debug logs will be enabled for that method.
469 | 
470 | ```ts title="Example"
471 | // Will log debug logs for all methods.
472 | const adapter = myAdapter({
473 |   debugLogs: true,
474 | });
475 | ```
476 | 
477 | ```ts title="Example"
478 | // Will only log debug logs for the `create` and `update` methods.
479 | const adapter = myAdapter({
480 |   debugLogs: {
481 |     create: true,
482 |     update: true,
483 |   },
484 | });
485 | ```
486 | 
487 | ### `disableIdGeneration`
488 | 
489 | Whether to disable ID generation. If this is set to `true`, then the user's `generateId` option will be ignored.
490 | 
491 | ### `customIdGenerator`
492 | 
493 | If your database only supports a specific custom ID generation, then you can use this option to generate your own IDs.
494 | 
495 | ### `mapKeysTransformInput`
496 | 
497 | If your database uses a different key name for a given situation, you can use this option to map the keys. This is useful for databases that expect a different key name for a given situation.
498 | For example, MongoDB uses `_id` while in Better-Auth we use `id`.
499 | 
500 | Each key in the returned object represents the old key to replace.
501 | The value represents the new key.
502 | 
503 | This can be a partial object that only transforms some keys.
504 | 
505 | ```ts title="Example"
506 | mapKeysTransformInput: () => {
507 |   return {
508 |     id: "_id", // We want to replace `id` to `_id` to save into MongoDB
509 |   };
510 | },
511 | ```
512 | 
513 | ### `mapKeysTransformOutput`
514 | 
515 | If your database uses a different key name for a given situation, you can use this option to map the keys. This is useful for databases that use a different key name for a given situation.
516 | For example, MongoDB uses `_id` while in Better-Auth we use `id`.
517 | 
518 | Each key in the returned object represents the old key to replace.
519 | The value represents the new key.
520 | 
521 | This can be a partial object that only transforms some keys.
522 | 
523 | ```ts title="Example"
524 | mapKeysTransformOutput: () => {
525 |   return {
526 |     _id: "id", // We want to replace `_id` (from MongoDB) to `id` (for Better-Auth)
527 |   };
528 | },
529 | ```
530 | 
531 | ### `customTransformInput`
532 | 
533 | If you need to transform the input data before it is saved to the database, you can use this option to transform the data.
534 | 
535 | <Callout type="warn">
536 |   If you're using `supportsJSON`, `supportsDates`, or `supportsBooleans`, then
537 |   the transformations will be applied before your `customTransformInput`
538 |   function is called.
539 | </Callout>
540 | The `customTransformInput` function receives the following arguments:
541 | 
542 | - `data`: The data to transform.
543 | - `field`: The field that is being transformed.
544 | - `fieldAttributes`: The field attributes of the field that is being transformed.
545 | - `select`: The `select` values which the query expects to return.
546 | - `model`: The model that is being transformed.
547 | - `schema`: The schema that is being transformed.
548 | - `options`: Better Auth options.
549 | 
550 | The `customTransformInput` function runs at every key in the data object of a given action.
551 | 
552 | ```ts title="Example"
553 | customTransformInput: ({ field, data }) => {
554 |   if (field === "id") {
555 |     return "123"; // Force the ID to be "123"
556 |   }
557 | 
558 |   return data;
559 | };
560 | ```
561 | 
562 | ### `customTransformOutput`
563 | 
564 | If you need to transform the output data before it is returned to the user, you can use this option to transform the data. The `customTransformOutput` function is used to transform the output data.
565 | Similar to the `customTransformInput` function, it runs at every key in the data object of a given action, but it runs after the data is retrieved from the database.
566 | 
567 | ```ts title="Example"
568 | customTransformOutput: ({ field, data }) => {
569 |   if (field === "name") {
570 |     return "Bob"; // Force the name to be "Bob"
571 |   }
572 | 
573 |   return data;
574 | };
575 | ```
576 | 
577 | ```ts
578 | const some_data = await adapter.create({
579 |   model: "user",
580 |   data: {
581 |     name: "John",
582 |   },
583 | });
584 | 
585 | // The name will be "Bob"
586 | console.log(some_data.name);
587 | ```
588 | 
```

--------------------------------------------------------------------------------
/docs/content/docs/plugins/device-authorization.mdx:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: Device Authorization
  3 | description: OAuth 2.0 Device Authorization Grant for limited-input devices
  4 | ---
  5 | 
  6 | `RFC 8628` `CLI` `Smart TV` `IoT`
  7 | 
  8 | The Device Authorization plugin implements the OAuth 2.0 Device Authorization Grant ([RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628)), enabling authentication for devices with limited input capabilities such as smart TVs, CLI applications, IoT devices, and gaming consoles.
  9 | 
 10 | ## Try It Out
 11 | 
 12 | You can test the device authorization flow right now using the Better Auth CLI:
 13 | 
 14 | ```bash
 15 | npx @better-auth/cli login
 16 | ```
 17 | 
 18 | This will demonstrate the complete device authorization flow by:
 19 | 1. Requesting a device code from the Better Auth demo server
 20 | 2. Displaying a user code for you to enter
 21 | 3. Opening your browser to the verification page
 22 | 4. Polling for authorization completion
 23 | 
 24 | <Callout type="info">
 25 |   The CLI login command is a demo feature that connects to the Better Auth demo server to showcase the device authorization flow in action.
 26 | </Callout>
 27 | 
 28 | ## Installation
 29 | 
 30 | <Steps>
 31 |     <Step>
 32 |         ### Add the plugin to your auth config
 33 |         
 34 |         Add the device authorization plugin to your server configuration.
 35 | 
 36 |         ```ts title="auth.ts"
 37 |         import { betterAuth } from "better-auth";
 38 |         import { deviceAuthorization } from "better-auth/plugins"; // [!code highlight]
 39 | 
 40 |         export const auth = betterAuth({
 41 |           // ... other config
 42 |           plugins: [ // [!code highlight]
 43 |             deviceAuthorization({ // [!code highlight]
 44 |               // Optional configuration
 45 |               expiresIn: "30m", // Device code expiration time // [!code highlight]
 46 |               interval: "5s",    // Minimum polling interval // [!code highlight]
 47 |             }), // [!code highlight]
 48 |           ], // [!code highlight]
 49 |         });
 50 |         ```
 51 |     </Step>
 52 |     
 53 |     <Step>
 54 |         ### Migrate the database
 55 |         
 56 |         Run the migration or generate the schema to add the necessary tables to the database.
 57 |         
 58 |         <Tabs items={["migrate", "generate"]}>
 59 |             <Tab value="migrate">
 60 |             ```bash
 61 |             npx @better-auth/cli migrate
 62 |             ```
 63 |             </Tab>
 64 |             <Tab value="generate">
 65 |             ```bash
 66 |             npx @better-auth/cli generate
 67 |             ```
 68 |             </Tab>
 69 |         </Tabs>
 70 |         See the [Schema](#schema) section to add the fields manually.
 71 |     </Step>
 72 | 
 73 |     <Step>
 74 |         ### Add the client plugin
 75 |         
 76 |         Add the device authorization plugin to your client.
 77 | 
 78 |         ```ts title="auth-client.ts"
 79 |         import { createAuthClient } from "better-auth/client";
 80 |         import { deviceAuthorizationClient } from "better-auth/client/plugins"; // [!code highlight]
 81 | 
 82 |         export const authClient = createAuthClient({
 83 |           plugins: [ // [!code highlight]
 84 |             deviceAuthorizationClient(), // [!code highlight]
 85 |           ], // [!code highlight]
 86 |         });
 87 |         ```
 88 |     </Step>
 89 | </Steps>
 90 | 
 91 | ## How It Works
 92 | 
 93 | The device flow follows these steps:
 94 | 
 95 | 1. **Device requests codes**: The device requests a device code and user code from the authorization server
 96 | 2. **User authorizes**: The user visits a verification URL and enters the user code
 97 | 3. **Device polls for token**: The device polls the server until the user completes authorization
 98 | 4. **Access granted**: Once authorized, the device receives an access token
 99 | 
100 | ## Basic Usage
101 | 
102 | ### Requesting Device Authorization
103 | 
104 | To initiate device authorization, call `device.code` with the client ID:
105 | 
106 | <APIMethod
107 |   path="/device/code"
108 |   method="POST"
109 | >
110 | ```ts
111 | type deviceCode = {
112 |     /**
113 |      * The OAuth client identifier
114 |      */
115 |     client_id: string;
116 |     /**
117 |      * Space-separated list of requested scopes (optional)
118 |      */
119 |     scope?: string;
120 | }
121 | ```
122 | </APIMethod>
123 | 
124 | Example usage:
125 | ```ts
126 | const { data } = await authClient.device.code({
127 |   client_id: "your-client-id",
128 |   scope: "openid profile email",
129 | });
130 | 
131 | if (data) {
132 |   console.log(`Please visit: ${data.verification_uri}`);
133 |   console.log(`And enter code: ${data.user_code}`);
134 | }
135 | ```
136 | 
137 | ### Polling for Token
138 | 
139 | After displaying the user code, poll for the access token:
140 | 
141 | <APIMethod
142 |   path="/device/token"
143 |   method="POST"
144 | >
145 | ```ts
146 | type deviceToken = {
147 |     /**
148 |      * Must be "urn:ietf:params:oauth:grant-type:device_code"
149 |      */
150 |     grant_type: string;
151 |     /**
152 |      * The device code from the initial request
153 |      */
154 |     device_code: string;
155 |     /**
156 |      * The OAuth client identifier
157 |      */
158 |     client_id: string;
159 | }
160 | ```
161 | </APIMethod>
162 | 
163 | Example polling implementation:
164 | ```ts
165 | let pollingInterval = 5; // Start with 5 seconds
166 | const pollForToken = async () => {
167 |   const { data, error } = await authClient.device.token({
168 |     grant_type: "urn:ietf:params:oauth:grant-type:device_code",
169 |     device_code,
170 |     client_id: yourClientId,
171 |     fetchOptions: {
172 |       headers: {
173 |         "user-agent": `My CLI`,
174 |       },
175 |     },
176 |   });
177 | 
178 |   if (data?.access_token) {
179 |     console.log("Authorization successful!");
180 |   } else if (error) {
181 |     switch (error.error) {
182 |       case "authorization_pending":
183 |         // Continue polling
184 |         break;
185 |       case "slow_down":
186 |         pollingInterval += 5;
187 |         break;
188 |       case "access_denied":
189 |         console.error("Access was denied by the user");
190 |         return;
191 |       case "expired_token":
192 |         console.error("The device code has expired. Please try again.");
193 |         return;
194 |       default:
195 |         console.error(`Error: ${error.error_description}`);
196 |         return;
197 |     }
198 |     setTimeout(pollForToken, pollingInterval * 1000);
199 |   }
200 | };
201 | 
202 | pollForToken();
203 | ```
204 | 
205 | ### User Authorization Flow
206 | 
207 | The user authorization flow requires two steps:
208 | 1. **Code Verification**: Check if the entered user code is valid
209 | 2. **Authorization**: User must be authenticated to approve/deny the device
210 | 
211 | <Callout type="warn">
212 |   Users must be authenticated before they can approve or deny device authorization requests. If not authenticated, redirect them to the login page with a return URL.
213 | </Callout>
214 | 
215 | Create a page where users can enter their code:
216 | 
217 | ```tsx title="app/device/page.tsx"
218 | export default function DeviceAuthorizationPage() {
219 |   const [userCode, setUserCode] = useState("");
220 |   const [error, setError] = useState(null);
221 |   
222 |   const handleSubmit = async (e) => {
223 |     e.preventDefault();
224 |     
225 |     try {
226 |       // Format the code: remove dashes and convert to uppercase
227 |       const formattedCode = userCode.trim().replace(/-/g, "").toUpperCase();
228 | 
229 |       // Check if the code is valid using GET /device endpoint
230 |       const response = await authClient.device({
231 |         query: { user_code: formattedCode },
232 |       });
233 |       
234 |       if (response.data) {
235 |         // Redirect to approval page
236 |         window.location.href = `/device/approve?user_code=${formattedCode}`;
237 |       }
238 |     } catch (err) {
239 |       setError("Invalid or expired code");
240 |     }
241 |   };
242 |   
243 |   return (
244 |     <form onSubmit={handleSubmit}>
245 |       <input
246 |         type="text"
247 |         value={userCode}
248 |         onChange={(e) => setUserCode(e.target.value)}
249 |         placeholder="Enter device code (e.g., ABCD-1234)"
250 |         maxLength={12}
251 |       />
252 |       <button type="submit">Continue</button>
253 |       {error && <p>{error}</p>}
254 |     </form>
255 |   );
256 | }
257 | ```
258 | 
259 | ### Approving or Denying Device
260 | 
261 | Users must be authenticated to approve or deny device authorization requests:
262 | 
263 | #### Approve Device
264 | 
265 | <APIMethod
266 |   path="/device/approve"
267 |   method="POST"
268 |   requireSession
269 | >
270 | ```ts
271 | type deviceApprove = {
272 |     /**
273 |      * The user code to approve
274 |      */
275 |     userCode: string;
276 | }
277 | ```
278 | </APIMethod>
279 | 
280 | #### Deny Device
281 | 
282 | <APIMethod
283 |   path="/device/deny"
284 |   method="POST"
285 |   requireSession
286 | >
287 | ```ts
288 | type deviceDeny = {
289 |     /**
290 |      * The user code to deny
291 |      */
292 |     userCode: string;
293 | }
294 | ```
295 | </APIMethod>
296 | 
297 | #### Example Approval Page
298 | 
299 | ```tsx title="app/device/approve/page.tsx"
300 | export default function DeviceApprovalPage() {
301 |   const { user } = useAuth(); // Must be authenticated
302 |   const searchParams = useSearchParams();
303 |   const userCode = searchParams.get("userCode");
304 |   const [isProcessing, setIsProcessing] = useState(false);
305 |   
306 |   const handleApprove = async () => {
307 |     setIsProcessing(true);
308 |     try {
309 |       await authClient.device.approve({
310 |         userCode: userCode,
311 |       });
312 |       // Show success message
313 |       alert("Device approved successfully!");
314 |       window.location.href = "/";
315 |     } catch (error) {
316 |       alert("Failed to approve device");
317 |     }
318 |     setIsProcessing(false);
319 |   };
320 |   
321 |   const handleDeny = async () => {
322 |     setIsProcessing(true);
323 |     try {
324 |       await authClient.device.deny({
325 |         userCode: userCode,
326 |       });
327 |       alert("Device denied");
328 |       window.location.href = "/";
329 |     } catch (error) {
330 |       alert("Failed to deny device");
331 |     }
332 |     setIsProcessing(false);
333 |   };
334 | 
335 |   if (!user) {
336 |     // Redirect to login if not authenticated
337 |     window.location.href = `/login?redirect=/device/approve?user_code=${userCode}`;
338 |     return null;
339 |   }
340 |   
341 |   return (
342 |     <div>
343 |       <h2>Device Authorization Request</h2>
344 |       <p>A device is requesting access to your account.</p>
345 |       <p>Code: {userCode}</p>
346 |       
347 |       <button onClick={handleApprove} disabled={isProcessing}>
348 |         Approve
349 |       </button>
350 |       <button onClick={handleDeny} disabled={isProcessing}>
351 |         Deny
352 |       </button>
353 |     </div>
354 |   );
355 | }
356 | ```
357 | 
358 | ## Advanced Configuration
359 | 
360 | ### Client Validation
361 | 
362 | You can validate client IDs to ensure only authorized applications can use the device flow:
363 | 
364 | ```ts
365 | deviceAuthorization({
366 |   validateClient: async (clientId) => {
367 |     // Check if client is authorized
368 |     const client = await db.oauth_clients.findOne({ id: clientId });
369 |     return client && client.allowDeviceFlow;
370 |   },
371 |   
372 |   onDeviceAuthRequest: async (clientId, scope) => {
373 |     // Log device authorization requests
374 |     await logDeviceAuthRequest(clientId, scope);
375 |   },
376 | })
377 | ```
378 | 
379 | ### Custom Code Generation
380 | 
381 | Customize how device and user codes are generated:
382 | 
383 | ```ts
384 | deviceAuthorization({
385 |   generateDeviceCode: async () => {
386 |     // Custom device code generation
387 |     return crypto.randomBytes(32).toString("hex");
388 |   },
389 |   
390 |   generateUserCode: async () => {
391 |     // Custom user code generation
392 |     // Default uses: ABCDEFGHJKLMNPQRSTUVWXYZ23456789
393 |     // (excludes 0, O, 1, I to avoid confusion)
394 |     const charset = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
395 |     let code = "";
396 |     for (let i = 0; i < 8; i++) {
397 |       code += charset[Math.floor(Math.random() * charset.length)];
398 |     }
399 |     return code;
400 |   },
401 | })
402 | ```
403 | 
404 | ## Error Handling
405 | 
406 | The device flow defines specific error codes:
407 | 
408 | | Error Code | Description |
409 | |------------|-------------|
410 | | `authorization_pending` | User hasn't approved yet (continue polling) |
411 | | `slow_down` | Polling too frequently (increase interval) |
412 | | `expired_token` | Device code has expired |
413 | | `access_denied` | User denied the authorization |
414 | | `invalid_grant` | Invalid device code or client ID |
415 | 
416 | ## Example: CLI Application
417 | 
418 | Here's a complete example for a CLI application based on the actual demo:
419 | 
420 | ```ts title="cli-auth.ts"
421 | import { createAuthClient } from "better-auth/client";
422 | import { deviceAuthorizationClient } from "better-auth/client/plugins";
423 | import open from "open";
424 | 
425 | const authClient = createAuthClient({
426 |   baseURL: "http://localhost:3000",
427 |   plugins: [deviceAuthorizationClient()],
428 | });
429 | 
430 | async function authenticateCLI() {
431 |   console.log("🔐 Better Auth Device Authorization Demo");
432 |   console.log("⏳ Requesting device authorization...");
433 |   
434 |   try {
435 |     // Request device code
436 |     const { data, error } = await authClient.device.code({
437 |       client_id: "demo-cli",
438 |       scope: "openid profile email",
439 |     });
440 |     
441 |     if (error || !data) {
442 |       console.error("❌ Error:", error?.error_description);
443 |       process.exit(1);
444 |     }
445 |     
446 |     const {
447 |       device_code,
448 |       user_code,
449 |       verification_uri,
450 |       verification_uri_complete,
451 |       interval = 5,
452 |     } = data;
453 |     
454 |     console.log("\n📱 Device Authorization in Progress");
455 |     console.log(`Please visit: ${verification_uri}`);
456 |     console.log(`Enter code: ${user_code}\n`);
457 |     
458 |     // Open browser with the complete URL
459 |     const urlToOpen = verification_uri_complete || verification_uri;
460 |     if (urlToOpen) {
461 |       console.log("🌐 Opening browser...");
462 |       await open(urlToOpen);
463 |     }
464 |     
465 |     console.log(`⏳ Waiting for authorization... (polling every ${interval}s)`);
466 |     
467 |     // Poll for token
468 |     await pollForToken(device_code, interval);
469 |   } catch (err) {
470 |     console.error("❌ Error:", err.message);
471 |     process.exit(1);
472 |   }
473 | }
474 | 
475 | async function pollForToken(deviceCode: string, interval: number) {
476 |   let pollingInterval = interval;
477 |   
478 |   return new Promise<void>((resolve) => {
479 |     const poll = async () => {
480 |       try {
481 |         const { data, error } = await authClient.device.token({
482 |           grant_type: "urn:ietf:params:oauth:grant-type:device_code",
483 |           device_code: deviceCode,
484 |           client_id: "demo-cli",
485 |         });
486 |         
487 |         if (data?.access_token) {
488 |           console.log("\nAuthorization Successful!");
489 |           console.log("Access token received!");
490 |           
491 |           // Get user session
492 |           const { data: session } = await authClient.getSession({
493 |             fetchOptions: {
494 |               headers: {
495 |                 Authorization: `Bearer ${data.access_token}`,
496 |               },
497 |             },
498 |           });
499 |           
500 |           console.log(`Hello, ${session?.user?.name || "User"}!`);
501 |           resolve();
502 |           process.exit(0);
503 |         } else if (error) {
504 |           switch (error.error) {
505 |             case "authorization_pending":
506 |               // Continue polling silently
507 |               break;
508 |             case "slow_down":
509 |               pollingInterval += 5;
510 |               console.log(`⚠️  Slowing down polling to ${pollingInterval}s`);
511 |               break;
512 |             case "access_denied":
513 |               console.error("❌ Access was denied by the user");
514 |               process.exit(1);
515 |               break;
516 |             case "expired_token":
517 |               console.error("❌ The device code has expired. Please try again.");
518 |               process.exit(1);
519 |               break;
520 |             default:
521 |               console.error("❌ Error:", error.error_description);
522 |               process.exit(1);
523 |           }
524 |         }
525 |       } catch (err) {
526 |         console.error("❌ Network error:", err.message);
527 |         process.exit(1);
528 |       }
529 |       
530 |       // Schedule next poll
531 |       setTimeout(poll, pollingInterval * 1000);
532 |     };
533 |     
534 |     // Start polling
535 |     setTimeout(poll, pollingInterval * 1000);
536 |   });
537 | }
538 | 
539 | // Run the authentication flow
540 | authenticateCLI().catch((err) => {
541 |   console.error("❌ Fatal error:", err);
542 |   process.exit(1);
543 | });
544 | ```
545 | 
546 | ## Security Considerations
547 | 
548 | 1. **Rate Limiting**: The plugin enforces polling intervals to prevent abuse
549 | 2. **Code Expiration**: Device and user codes expire after the configured time (default: 30 minutes)
550 | 3. **Client Validation**: Always validate client IDs in production to prevent unauthorized access
551 | 4. **HTTPS Only**: Always use HTTPS in production for device authorization
552 | 5. **User Code Format**: User codes use a limited character set (excluding similar-looking characters like 0/O, 1/I) to reduce typing errors
553 | 6. **Authentication Required**: Users must be authenticated before they can approve or deny device requests
554 | 
555 | ## Options
556 | 
557 | ### Server
558 | 
559 | **expiresIn**: The expiration time for device codes. Default: `"30m"` (30 minutes).
560 | 
561 | **interval**: The minimum polling interval. Default: `"5s"` (5 seconds).
562 | 
563 | **userCodeLength**: The length of the user code. Default: `8`.
564 | 
565 | **deviceCodeLength**: The length of the device code. Default: `40`.
566 | 
567 | **generateDeviceCode**: Custom function to generate device codes. Returns a string or `Promise<string>`.
568 | 
569 | **generateUserCode**: Custom function to generate user codes. Returns a string or `Promise<string>`.
570 | 
571 | **validateClient**: Function to validate client IDs. Takes a clientId and returns boolean or `Promise<boolean>`.
572 | 
573 | **onDeviceAuthRequest**: Hook called when device authorization is requested. Takes clientId and optional scope.
574 | 
575 | ### Client
576 | 
577 | No client-specific configuration options. The plugin adds the following methods:
578 | 
579 | - **device()**: Verify user code validity
580 | - **device.code()**: Request device and user codes
581 | - **device.token()**: Poll for access token  
582 | - **device.approve()**: Approve device (requires authentication)
583 | - **device.deny()**: Deny device (requires authentication)
584 | 
585 | ## Schema
586 | 
587 | The plugin requires a new table to store device authorization data.
588 | 
589 | Table Name: `deviceCode`
590 | 
591 | <DatabaseTable
592 |     fields={[
593 |         { 
594 |             name: "id", 
595 |             type: "string", 
596 |             description: "Unique identifier for the device authorization request",
597 |             isPrimaryKey: true
598 |         },
599 |         {
600 |             name: "deviceCode",
601 |             type: "string",
602 |             description: "The device verification code",
603 |         },
604 |         {
605 |             name: "userCode",
606 |             type: "string",
607 |             description: "The user-friendly code for verification",
608 |         },
609 |         { 
610 |             name: "userId", 
611 |             type: "string", 
612 |             description: "The ID of the user who approved/denied",
613 |             isOptional: true,
614 |             isForeignKey: true
615 |         },
616 |         {
617 |             name: "clientId",
618 |             type: "string",
619 |             description: "The OAuth client identifier",
620 |             isOptional: true
621 |         },
622 |         {
623 |             name: "scope",
624 |             type: "string",
625 |             description: "Requested OAuth scopes",
626 |             isOptional: true
627 |         },
628 |         {
629 |             name: "status",
630 |             type: "string",
631 |             description: "Current status: pending, approved, or denied",
632 |         },
633 |         {
634 |             name: "expiresAt",
635 |             type: "Date",
636 |             description: "When the device code expires",
637 |         },
638 |         {
639 |             name: "lastPolledAt",
640 |             type: "Date",
641 |             description: "Last time the device polled for status",
642 |             isOptional: true
643 |         },
644 |         {
645 |             name: "pollingInterval",
646 |             type: "number",
647 |             description: "Minimum seconds between polls",
648 |             isOptional: true
649 |         },
650 |         {
651 |             name: "createdAt",
652 |             type: "Date",
653 |             description: "When the request was created",
654 |         },
655 |         {
656 |             name: "updatedAt",
657 |             type: "Date",
658 |             description: "When the request was last updated",
659 |         }
660 |     ]}
661 | />
```

--------------------------------------------------------------------------------
/packages/cli/src/generators/auth-config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { type spinner as clackSpinner } from "@clack/prompts";
  2 | import { logger } from "better-auth";
  3 | import {
  4 | 	type SupportedDatabases,
  5 | 	type SupportedPlugin,
  6 | } from "../commands/init";
  7 | 
  8 | export type Import = {
  9 | 	path: string;
 10 | 	variables:
 11 | 		| { asType?: boolean; name: string; as?: string }[]
 12 | 		| { asType?: boolean; name: string; as?: string };
 13 | };
 14 | 
 15 | type Format = (code: string) => Promise<string>;
 16 | 
 17 | type CommonIndexConfig_Regex<AdditionalFields> = {
 18 | 	type: "regex";
 19 | 	regex: RegExp;
 20 | 	getIndex: (args: {
 21 | 		matchIndex: number;
 22 | 		match: RegExpMatchArray;
 23 | 		additionalFields: AdditionalFields;
 24 | 	}) => number | null;
 25 | };
 26 | type CommonIndexConfig_manual<AdditionalFields> = {
 27 | 	type: "manual";
 28 | 	getIndex: (args: {
 29 | 		content: string;
 30 | 		additionalFields: AdditionalFields;
 31 | 	}) => number | null;
 32 | };
 33 | 
 34 | export type CommonIndexConfig<AdditionalFields> =
 35 | 	| CommonIndexConfig_Regex<AdditionalFields>
 36 | 	| CommonIndexConfig_manual<AdditionalFields>;
 37 | 
 38 | export async function generateAuthConfig({
 39 | 	format,
 40 | 	current_user_config,
 41 | 	spinner,
 42 | 	plugins,
 43 | 	database,
 44 | }: {
 45 | 	format: Format;
 46 | 	current_user_config: string;
 47 | 	spinner: ReturnType<typeof clackSpinner>;
 48 | 	plugins: SupportedPlugin[];
 49 | 	database: SupportedDatabases | null;
 50 | }): Promise<{
 51 | 	generatedCode: string;
 52 | 	dependencies: string[];
 53 | 	envs: string[];
 54 | }> {
 55 | 	let _start_of_plugins_common_index = {
 56 | 		START_OF_PLUGINS: {
 57 | 			type: "regex",
 58 | 			regex: /betterAuth\([\w\W]*plugins:[\W]*\[()/m,
 59 | 			getIndex: ({ matchIndex, match }) => {
 60 | 				return matchIndex + match[0].length;
 61 | 			},
 62 | 		} satisfies CommonIndexConfig<{}>,
 63 | 	};
 64 | 	const common_indexes = {
 65 | 		START_OF_PLUGINS:
 66 | 			_start_of_plugins_common_index.START_OF_PLUGINS satisfies CommonIndexConfig<{}>,
 67 | 		END_OF_PLUGINS: {
 68 | 			type: "manual",
 69 | 			getIndex: ({ content, additionalFields }) => {
 70 | 				const closingBracketIndex = findClosingBracket(
 71 | 					content,
 72 | 					additionalFields.start_of_plugins,
 73 | 					"[",
 74 | 					"]",
 75 | 				);
 76 | 				return closingBracketIndex;
 77 | 			},
 78 | 		} satisfies CommonIndexConfig<{ start_of_plugins: number }>,
 79 | 		START_OF_BETTERAUTH: {
 80 | 			type: "regex",
 81 | 			regex: /betterAuth\({()/m,
 82 | 			getIndex: ({ matchIndex }) => {
 83 | 				return matchIndex + "betterAuth({".length;
 84 | 			},
 85 | 		} satisfies CommonIndexConfig<{}>,
 86 | 	};
 87 | 
 88 | 	const config_generation = {
 89 | 		add_plugin: async (opts: {
 90 | 			direction_in_plugins_array: "append" | "prepend";
 91 | 			pluginFunctionName: string;
 92 | 			pluginContents: string;
 93 | 			config: string;
 94 | 		}): Promise<{ code: string; dependencies: string[]; envs: string[] }> => {
 95 | 			let start_of_plugins = getGroupInfo(
 96 | 				opts.config,
 97 | 				common_indexes.START_OF_PLUGINS,
 98 | 				{},
 99 | 			);
100 | 
101 | 			// console.log(`start of plugins:`, start_of_plugins);
102 | 
103 | 			if (!start_of_plugins) {
104 | 				throw new Error(
105 | 					"Couldn't find start of your plugins array in your auth config file.",
106 | 				);
107 | 			}
108 | 			let end_of_plugins = getGroupInfo(
109 | 				opts.config,
110 | 				common_indexes.END_OF_PLUGINS,
111 | 				{ start_of_plugins: start_of_plugins.index },
112 | 			);
113 | 
114 | 			// console.log(`end of plugins:`, end_of_plugins);
115 | 
116 | 			if (!end_of_plugins) {
117 | 				throw new Error(
118 | 					"Couldn't find end of your plugins array in your auth config file.",
119 | 				);
120 | 			}
121 | 			// console.log(
122 | 			// 	"slice:\n",
123 | 			// 	opts.config.slice(start_of_plugins.index, end_of_plugins.index),
124 | 			// );
125 | 			let new_content: string;
126 | 
127 | 			if (opts.direction_in_plugins_array === "prepend") {
128 | 				new_content = insertContent({
129 | 					line: start_of_plugins.line,
130 | 					character: start_of_plugins.character,
131 | 					content: opts.config,
132 | 					insert_content: `${opts.pluginFunctionName}(${opts.pluginContents}),`,
133 | 				});
134 | 			} else {
135 | 				const pluginArrayContent = opts.config
136 | 					.slice(start_of_plugins.index, end_of_plugins.index)
137 | 					.trim();
138 | 				const isPluginArrayEmpty = pluginArrayContent === "";
139 | 				const isPluginArrayEndsWithComma = pluginArrayContent.endsWith(",");
140 | 				const needsComma = !isPluginArrayEmpty && !isPluginArrayEndsWithComma;
141 | 
142 | 				new_content = insertContent({
143 | 					line: end_of_plugins.line,
144 | 					character: end_of_plugins.character,
145 | 					content: opts.config,
146 | 					insert_content: `${needsComma ? "," : ""}${opts.pluginFunctionName}(${
147 | 						opts.pluginContents
148 | 					})`,
149 | 				});
150 | 			}
151 | 
152 | 			// console.log(`new_content`, new_content);
153 | 			try {
154 | 				new_content = await format(new_content);
155 | 			} catch (error) {
156 | 				console.error(error);
157 | 				throw new Error(
158 | 					`Failed to generate new auth config during plugin addition phase.`,
159 | 				);
160 | 			}
161 | 			return { code: new_content, dependencies: [], envs: [] };
162 | 		},
163 | 		add_import: async (opts: {
164 | 			imports: Import[];
165 | 			config: string;
166 | 		}): Promise<{ code: string; dependencies: string[]; envs: string[] }> => {
167 | 			let importString = "";
168 | 			for (const import_ of opts.imports) {
169 | 				if (Array.isArray(import_.variables)) {
170 | 					importString += `import { ${import_.variables
171 | 						.map(
172 | 							(x) =>
173 | 								`${x.asType ? "type " : ""}${x.name}${
174 | 									x.as ? ` as ${x.as}` : ""
175 | 								}`,
176 | 						)
177 | 						.join(", ")} } from "${import_.path}";\n`;
178 | 				} else {
179 | 					importString += `import ${import_.variables.asType ? "type " : ""}${
180 | 						import_.variables.name
181 | 					}${import_.variables.as ? ` as ${import_.variables.as}` : ""} from "${
182 | 						import_.path
183 | 					}";\n`;
184 | 				}
185 | 			}
186 | 			try {
187 | 				let new_content = format(importString + opts.config);
188 | 				return { code: await new_content, dependencies: [], envs: [] };
189 | 			} catch (error) {
190 | 				console.error(error);
191 | 				throw new Error(
192 | 					`Failed to generate new auth config during import addition phase.`,
193 | 				);
194 | 			}
195 | 		},
196 | 		add_database: async (opts: {
197 | 			database: SupportedDatabases;
198 | 			config: string;
199 | 		}): Promise<{ code: string; dependencies: string[]; envs: string[] }> => {
200 | 			const required_envs: string[] = [];
201 | 			const required_deps: string[] = [];
202 | 			let database_code_str: string = "";
203 | 
204 | 			async function add_db({
205 | 				db_code,
206 | 				dependencies,
207 | 				envs,
208 | 				imports,
209 | 				code_before_betterAuth,
210 | 			}: {
211 | 				imports: Import[];
212 | 				db_code: string;
213 | 				envs: string[];
214 | 				dependencies: string[];
215 | 				/**
216 | 				 * Any code you want to put before the betterAuth export
217 | 				 */
218 | 				code_before_betterAuth?: string;
219 | 			}) {
220 | 				if (code_before_betterAuth) {
221 | 					let start_of_betterauth = getGroupInfo(
222 | 						opts.config,
223 | 						common_indexes.START_OF_BETTERAUTH,
224 | 						{},
225 | 					);
226 | 					if (!start_of_betterauth) {
227 | 						throw new Error("Couldn't find start of betterAuth() function.");
228 | 					}
229 | 					opts.config = insertContent({
230 | 						line: start_of_betterauth.line - 1,
231 | 						character: 0,
232 | 						content: opts.config,
233 | 						insert_content: `\n${code_before_betterAuth}\n`,
234 | 					});
235 | 				}
236 | 
237 | 				const code_gen = await config_generation.add_import({
238 | 					config: opts.config,
239 | 					imports: imports,
240 | 				});
241 | 				opts.config = code_gen.code;
242 | 				database_code_str = db_code;
243 | 				required_envs.push(...envs, ...code_gen.envs);
244 | 				required_deps.push(...dependencies, ...code_gen.dependencies);
245 | 			}
246 | 
247 | 			if (opts.database === "sqlite") {
248 | 				await add_db({
249 | 					db_code: `new Database(process.env.DATABASE_URL || "database.sqlite")`,
250 | 					dependencies: ["better-sqlite3"],
251 | 					envs: ["DATABASE_URL"],
252 | 					imports: [
253 | 						{
254 | 							path: "better-sqlite3",
255 | 							variables: {
256 | 								asType: false,
257 | 								name: "Database",
258 | 							},
259 | 						},
260 | 					],
261 | 				});
262 | 			} else if (opts.database === "postgres") {
263 | 				await add_db({
264 | 					db_code: `new Pool({\nconnectionString: process.env.DATABASE_URL || "postgresql://postgres:password@localhost:5432/database"\n})`,
265 | 					dependencies: ["pg"],
266 | 					envs: ["DATABASE_URL"],
267 | 					imports: [
268 | 						{
269 | 							path: "pg",
270 | 							variables: [
271 | 								{
272 | 									asType: false,
273 | 									name: "Pool",
274 | 								},
275 | 							],
276 | 						},
277 | 					],
278 | 				});
279 | 			} else if (opts.database === "mysql") {
280 | 				await add_db({
281 | 					db_code: `createPool(process.env.DATABASE_URL!)`,
282 | 					dependencies: ["mysql2"],
283 | 					envs: ["DATABASE_URL"],
284 | 					imports: [
285 | 						{
286 | 							path: "mysql2/promise",
287 | 							variables: [
288 | 								{
289 | 									asType: false,
290 | 									name: "createPool",
291 | 								},
292 | 							],
293 | 						},
294 | 					],
295 | 				});
296 | 			} else if (opts.database === "mssql") {
297 | 				const dialectCode = `new MssqlDialect({
298 | 						tarn: {
299 | 							...Tarn,
300 | 							options: {
301 | 							min: 0,
302 | 							max: 10,
303 | 							},
304 | 						},
305 | 						tedious: {
306 | 							...Tedious,
307 | 							connectionFactory: () => new Tedious.Connection({
308 | 							authentication: {
309 | 								options: {
310 | 								password: 'password',
311 | 								userName: 'username',
312 | 								},
313 | 								type: 'default',
314 | 							},
315 | 							options: {
316 | 								database: 'some_db',
317 | 								port: 1433,
318 | 								trustServerCertificate: true,
319 | 							},
320 | 							server: 'localhost',
321 | 							}),
322 | 						},
323 | 					})`;
324 | 				await add_db({
325 | 					code_before_betterAuth: dialectCode,
326 | 					db_code: `dialect`,
327 | 					dependencies: ["tedious", "tarn", "kysely"],
328 | 					envs: ["DATABASE_URL"],
329 | 					imports: [
330 | 						{
331 | 							path: "tedious",
332 | 							variables: {
333 | 								name: "*",
334 | 								as: "Tedious",
335 | 							},
336 | 						},
337 | 						{
338 | 							path: "tarn",
339 | 							variables: {
340 | 								name: "*",
341 | 								as: "Tarn",
342 | 							},
343 | 						},
344 | 						{
345 | 							path: "kysely",
346 | 							variables: [
347 | 								{
348 | 									name: "MssqlDialect",
349 | 								},
350 | 							],
351 | 						},
352 | 					],
353 | 				});
354 | 			} else if (
355 | 				opts.database === "drizzle:mysql" ||
356 | 				opts.database === "drizzle:sqlite" ||
357 | 				opts.database === "drizzle:pg"
358 | 			) {
359 | 				await add_db({
360 | 					db_code: `drizzleAdapter(db, {\nprovider: "${opts.database.replace(
361 | 						"drizzle:",
362 | 						"",
363 | 					)}",\n})`,
364 | 					dependencies: [""],
365 | 					envs: [],
366 | 					imports: [
367 | 						{
368 | 							path: "better-auth/adapters/drizzle",
369 | 							variables: [
370 | 								{
371 | 									name: "drizzleAdapter",
372 | 								},
373 | 							],
374 | 						},
375 | 						{
376 | 							path: "./database.ts",
377 | 							variables: [
378 | 								{
379 | 									name: "db",
380 | 								},
381 | 							],
382 | 						},
383 | 					],
384 | 				});
385 | 			} else if (
386 | 				opts.database === "prisma:mysql" ||
387 | 				opts.database === "prisma:sqlite" ||
388 | 				opts.database === "prisma:postgresql"
389 | 			) {
390 | 				await add_db({
391 | 					db_code: `prismaAdapter(client, {\nprovider: "${opts.database.replace(
392 | 						"prisma:",
393 | 						"",
394 | 					)}",\n})`,
395 | 					dependencies: [`@prisma/client`],
396 | 					envs: [],
397 | 					code_before_betterAuth: "const client = new PrismaClient();",
398 | 					imports: [
399 | 						{
400 | 							path: "better-auth/adapters/prisma",
401 | 							variables: [
402 | 								{
403 | 									name: "prismaAdapter",
404 | 								},
405 | 							],
406 | 						},
407 | 						{
408 | 							path: "@prisma/client",
409 | 							variables: [
410 | 								{
411 | 									name: "PrismaClient",
412 | 								},
413 | 							],
414 | 						},
415 | 					],
416 | 				});
417 | 			} else if (opts.database === "mongodb") {
418 | 				await add_db({
419 | 					db_code: `mongodbAdapter(db)`,
420 | 					dependencies: ["mongodb"],
421 | 					envs: [`DATABASE_URL`],
422 | 					code_before_betterAuth: [
423 | 						`const client = new MongoClient(process.env.DATABASE_URL || "mongodb://localhost:27017/database");`,
424 | 						`const db = client.db();`,
425 | 					].join("\n"),
426 | 					imports: [
427 | 						{
428 | 							path: "better-auth/adapters/mongodb",
429 | 							variables: [
430 | 								{
431 | 									name: "mongodbAdapter",
432 | 								},
433 | 							],
434 | 						},
435 | 						{
436 | 							path: "mongodb",
437 | 							variables: [
438 | 								{
439 | 									name: "MongoClient",
440 | 								},
441 | 							],
442 | 						},
443 | 					],
444 | 				});
445 | 			}
446 | 
447 | 			let start_of_betterauth = getGroupInfo(
448 | 				opts.config,
449 | 				common_indexes.START_OF_BETTERAUTH,
450 | 				{},
451 | 			);
452 | 			if (!start_of_betterauth) {
453 | 				throw new Error("Couldn't find start of betterAuth() function.");
454 | 			}
455 | 			let new_content: string;
456 | 			new_content = insertContent({
457 | 				line: start_of_betterauth.line,
458 | 				character: start_of_betterauth.character,
459 | 				content: opts.config,
460 | 				insert_content: `database: ${database_code_str},`,
461 | 			});
462 | 
463 | 			try {
464 | 				new_content = await format(new_content);
465 | 				return {
466 | 					code: new_content,
467 | 					dependencies: required_deps,
468 | 					envs: required_envs,
469 | 				};
470 | 			} catch (error) {
471 | 				console.error(error);
472 | 				throw new Error(
473 | 					`Failed to generate new auth config during database addition phase.`,
474 | 				);
475 | 			}
476 | 		},
477 | 	};
478 | 
479 | 	let new_user_config: string = await format(current_user_config);
480 | 	let total_dependencies: string[] = [];
481 | 	let total_envs: string[] = [];
482 | 
483 | 	if (plugins.length !== 0) {
484 | 		const imports: {
485 | 			path: string;
486 | 			variables: {
487 | 				asType: boolean;
488 | 				name: string;
489 | 			}[];
490 | 		}[] = [];
491 | 		for await (const plugin of plugins) {
492 | 			const existingIndex = imports.findIndex((x) => x.path === plugin.path);
493 | 			if (existingIndex !== -1) {
494 | 				imports[existingIndex]!.variables.push({
495 | 					name: plugin.name,
496 | 					asType: false,
497 | 				});
498 | 			} else {
499 | 				imports.push({
500 | 					path: plugin.path,
501 | 					variables: [
502 | 						{
503 | 							name: plugin.name,
504 | 							asType: false,
505 | 						},
506 | 					],
507 | 				});
508 | 			}
509 | 		}
510 | 		if (imports.length !== 0) {
511 | 			const { code, envs, dependencies } = await config_generation.add_import({
512 | 				config: new_user_config,
513 | 				imports: imports,
514 | 			});
515 | 			total_dependencies.push(...dependencies);
516 | 			total_envs.push(...envs);
517 | 			new_user_config = code;
518 | 		}
519 | 	}
520 | 
521 | 	for await (const plugin of plugins) {
522 | 		try {
523 | 			// console.log(`--------- UPDATE: ${plugin} ---------`);
524 | 			let pluginContents = "";
525 | 			if (plugin.id === "magic-link") {
526 | 				pluginContents = `{\nsendMagicLink({ email, token, url }, request) {\n// Send email with magic link\n},\n}`;
527 | 			} else if (plugin.id === "email-otp") {
528 | 				pluginContents = `{\nasync sendVerificationOTP({ email, otp, type }, request) {\n// Send email with OTP\n},\n}`;
529 | 			} else if (plugin.id === "generic-oauth") {
530 | 				pluginContents = `{\nconfig: [],\n}`;
531 | 			} else if (plugin.id === "oidc") {
532 | 				pluginContents = `{\nloginPage: "/sign-in",\n}`;
533 | 			}
534 | 			const { code, dependencies, envs } = await config_generation.add_plugin({
535 | 				config: new_user_config,
536 | 				direction_in_plugins_array:
537 | 					plugin.id === "next-cookies" ? "append" : "prepend",
538 | 				pluginFunctionName: plugin.name,
539 | 				pluginContents: pluginContents,
540 | 			});
541 | 			new_user_config = code;
542 | 			total_envs.push(...envs);
543 | 			total_dependencies.push(...dependencies);
544 | 			// console.log(new_user_config);
545 | 			// console.log(`--------- UPDATE END ---------`);
546 | 		} catch (error: any) {
547 | 			spinner.stop(
548 | 				`Something went wrong while generating/updating your new auth config file.`,
549 | 				1,
550 | 			);
551 | 			logger.error(error.message);
552 | 			process.exit(1);
553 | 		}
554 | 	}
555 | 
556 | 	if (database) {
557 | 		try {
558 | 			const { code, dependencies, envs } = await config_generation.add_database(
559 | 				{
560 | 					config: new_user_config,
561 | 					database: database,
562 | 				},
563 | 			);
564 | 			new_user_config = code;
565 | 			total_dependencies.push(...dependencies);
566 | 			total_envs.push(...envs);
567 | 		} catch (error: any) {
568 | 			spinner.stop(
569 | 				`Something went wrong while generating/updating your new auth config file.`,
570 | 				1,
571 | 			);
572 | 			logger.error(error.message);
573 | 			process.exit(1);
574 | 		}
575 | 	}
576 | 
577 | 	return {
578 | 		generatedCode: new_user_config,
579 | 		dependencies: total_dependencies,
580 | 		envs: total_envs,
581 | 	};
582 | }
583 | 
584 | function findClosingBracket(
585 | 	content: string,
586 | 	startIndex: number,
587 | 	openingBracket: string,
588 | 	closingBracket: string,
589 | ): number | null {
590 | 	let stack = 0;
591 | 	let inString = false; // To track if we are inside a string
592 | 	let quoteChar: string | null = null; // To track the type of quote
593 | 
594 | 	for (let i = startIndex; i < content.length; i++) {
595 | 		const char = content[i];
596 | 
597 | 		// Check if we are entering or exiting a string
598 | 		if (char === '"' || char === "'" || char === "`") {
599 | 			if (!inString) {
600 | 				inString = true;
601 | 				quoteChar = char; // Set the quote character
602 | 			} else if (char === quoteChar) {
603 | 				inString = false; // Exiting the string
604 | 				quoteChar = null; // Reset the quote character
605 | 			}
606 | 			continue; // Skip processing for characters inside strings
607 | 		}
608 | 
609 | 		// If we are not inside a string, check for brackets
610 | 		if (!inString) {
611 | 			if (char === openingBracket) {
612 | 				// console.log(`Found opening bracket:`, stack);
613 | 				stack++;
614 | 			} else if (char === closingBracket) {
615 | 				// console.log(`Found closing bracket:`, stack, closingBracket, i);
616 | 				if (stack === 0) {
617 | 					return i; // Found the matching closing bracket
618 | 				}
619 | 				stack--;
620 | 			}
621 | 		}
622 | 	}
623 | 
624 | 	return null; // No matching closing bracket found
625 | }
626 | 
627 | /**
628 |  * Helper function to insert content at a specific line and character position in a string.
629 |  */
630 | function insertContent(params: {
631 | 	line: number;
632 | 	character: number;
633 | 	content: string;
634 | 	insert_content: string;
635 | }): string {
636 | 	const { line, character, content, insert_content } = params;
637 | 
638 | 	// Split the content into lines
639 | 	const lines = content.split("\n");
640 | 
641 | 	// Check if the specified line number is valid
642 | 	if (line < 1 || line > lines.length) {
643 | 		throw new Error("Invalid line number");
644 | 	}
645 | 
646 | 	// Adjust for zero-based index
647 | 	const targetLineIndex = line - 1;
648 | 
649 | 	// Check if the specified character index is valid
650 | 	if (character < 0 || character > lines[targetLineIndex]!.length) {
651 | 		throw new Error("Invalid character index");
652 | 	}
653 | 
654 | 	// Insert the new content at the specified position
655 | 	const targetLine = lines[targetLineIndex]!;
656 | 	const updatedLine =
657 | 		targetLine.slice(0, character) +
658 | 		insert_content +
659 | 		targetLine.slice(character);
660 | 	lines[targetLineIndex] = updatedLine;
661 | 
662 | 	// Join the lines back into a single string
663 | 	return lines.join("\n");
664 | }
665 | 
666 | /**
667 |  * Helper function to get the line and character position of a specific group in a string using a CommonIndexConfig.
668 |  */
669 | function getGroupInfo<AdditionalFields>(
670 | 	content: string,
671 | 	commonIndexConfig: CommonIndexConfig<AdditionalFields>,
672 | 	additionalFields: AdditionalFields,
673 | ): {
674 | 	line: number;
675 | 	character: number;
676 | 	index: number;
677 | } | null {
678 | 	if (commonIndexConfig.type === "regex") {
679 | 		const { regex, getIndex } = commonIndexConfig;
680 | 		const match = regex.exec(content);
681 | 		if (match) {
682 | 			const matchIndex = match.index;
683 | 			const groupIndex = getIndex({ matchIndex, match, additionalFields });
684 | 			if (groupIndex === null) return null;
685 | 			const position = getPosition(content, groupIndex);
686 | 			return {
687 | 				line: position.line,
688 | 				character: position.character,
689 | 				index: groupIndex,
690 | 			};
691 | 		}
692 | 
693 | 		return null; // Return null if no match is found
694 | 	} else {
695 | 		const { getIndex } = commonIndexConfig;
696 | 		const index = getIndex({ content, additionalFields });
697 | 		if (index === null) return null;
698 | 
699 | 		const { line, character } = getPosition(content, index);
700 | 		return {
701 | 			line: line,
702 | 			character: character,
703 | 			index,
704 | 		};
705 | 	}
706 | }
707 | 
708 | /**
709 |  * Helper function to calculate line and character position based on an index
710 |  */
711 | const getPosition = (str: string, index: number) => {
712 | 	const lines = str.slice(0, index).split("\n");
713 | 	return {
714 | 		line: lines.length,
715 | 		character: lines[lines.length - 1]!.length,
716 | 	};
717 | };
718 | 
```
Page 40/67FirstPrevNextLast