This is page 61 of 69. 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 │ │ │ │ │ ├── demo.ts │ │ │ │ │ └── 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-isolated-module-bundler │ │ │ │ ├── package.json │ │ │ │ ├── src │ │ │ │ │ └── index.ts │ │ │ │ └── tsconfig.json │ │ │ ├── tsconfig-verbatim-module-syntax-node10 │ │ │ │ ├── package.json │ │ │ │ ├── src │ │ │ │ │ └── index.ts │ │ │ │ └── tsconfig.json │ │ │ └── vite │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── client.ts │ │ │ │ └── server.ts │ │ │ ├── tsconfig.json │ │ │ └── vite.config.ts │ │ ├── ssr.ts │ │ ├── typecheck.spec.ts │ │ └── vite.spec.ts │ └── tsconfig.json ├── LICENSE.md ├── package.json ├── packages │ ├── better-auth │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── __snapshots__ │ │ │ │ └── init.test.ts.snap │ │ │ ├── adapters │ │ │ │ ├── adapter-factory │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── test │ │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ │ └── adapter-factory.test.ts.snap │ │ │ │ │ │ └── adapter-factory.test.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── create-test-suite.ts │ │ │ │ ├── drizzle-adapter │ │ │ │ │ ├── drizzle-adapter.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── test │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── adapter.drizzle.mysql.test.ts │ │ │ │ │ ├── adapter.drizzle.pg.test.ts │ │ │ │ │ ├── adapter.drizzle.sqlite.test.ts │ │ │ │ │ └── generate-schema.ts │ │ │ │ ├── index.ts │ │ │ │ ├── kysely-adapter │ │ │ │ │ ├── bun-sqlite-dialect.ts │ │ │ │ │ ├── dialect.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── kysely-adapter.ts │ │ │ │ │ ├── node-sqlite-dialect.ts │ │ │ │ │ ├── test │ │ │ │ │ │ ├── adapter.kysely.mssql.test.ts │ │ │ │ │ │ ├── adapter.kysely.mysql.test.ts │ │ │ │ │ │ ├── adapter.kysely.pg.test.ts │ │ │ │ │ │ ├── adapter.kysely.sqlite.test.ts │ │ │ │ │ │ └── node-sqlite-dialect.test.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── memory-adapter │ │ │ │ │ ├── adapter.memory.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── memory-adapter.ts │ │ │ │ ├── mongodb-adapter │ │ │ │ │ ├── adapter.mongo-db.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── mongodb-adapter.ts │ │ │ │ ├── prisma-adapter │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── prisma-adapter.ts │ │ │ │ │ └── test │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── base.prisma │ │ │ │ │ ├── generate-auth-config.ts │ │ │ │ │ ├── generate-prisma-schema.ts │ │ │ │ │ ├── get-prisma-client.ts │ │ │ │ │ ├── prisma.mysql.test.ts │ │ │ │ │ ├── prisma.pg.test.ts │ │ │ │ │ ├── prisma.sqlite.test.ts │ │ │ │ │ └── push-prisma-schema.ts │ │ │ │ ├── test-adapter.ts │ │ │ │ ├── test.ts │ │ │ │ ├── tests │ │ │ │ │ ├── auth-flow.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── normal.ts │ │ │ │ │ ├── number-id.ts │ │ │ │ │ ├── performance.ts │ │ │ │ │ └── transactions.ts │ │ │ │ └── utils.ts │ │ │ ├── api │ │ │ │ ├── check-endpoint-conflicts.test.ts │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── middlewares │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── origin-check.test.ts │ │ │ │ │ └── origin-check.ts │ │ │ │ ├── rate-limiter │ │ │ │ │ ├── index.ts │ │ │ │ │ └── rate-limiter.test.ts │ │ │ │ ├── routes │ │ │ │ │ ├── account.test.ts │ │ │ │ │ ├── account.ts │ │ │ │ │ ├── callback.ts │ │ │ │ │ ├── email-verification.test.ts │ │ │ │ │ ├── email-verification.ts │ │ │ │ │ ├── error.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── ok.ts │ │ │ │ │ ├── reset-password.test.ts │ │ │ │ │ ├── reset-password.ts │ │ │ │ │ ├── session-api.test.ts │ │ │ │ │ ├── session.ts │ │ │ │ │ ├── sign-in.test.ts │ │ │ │ │ ├── sign-in.ts │ │ │ │ │ ├── sign-out.test.ts │ │ │ │ │ ├── sign-out.ts │ │ │ │ │ ├── sign-up.test.ts │ │ │ │ │ ├── sign-up.ts │ │ │ │ │ ├── update-user.test.ts │ │ │ │ │ └── update-user.ts │ │ │ │ ├── to-auth-endpoints.test.ts │ │ │ │ └── to-auth-endpoints.ts │ │ │ ├── auth.test.ts │ │ │ ├── auth.ts │ │ │ ├── call.test.ts │ │ │ ├── client │ │ │ │ ├── client-ssr.test.ts │ │ │ │ ├── client.test.ts │ │ │ │ ├── config.ts │ │ │ │ ├── fetch-plugins.ts │ │ │ │ ├── index.ts │ │ │ │ ├── lynx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── lynx-store.ts │ │ │ │ ├── parser.ts │ │ │ │ ├── path-to-object.ts │ │ │ │ ├── plugins │ │ │ │ │ ├── index.ts │ │ │ │ │ └── infer-plugin.ts │ │ │ │ ├── proxy.ts │ │ │ │ ├── query.ts │ │ │ │ ├── react │ │ │ │ │ ├── index.ts │ │ │ │ │ └── react-store.ts │ │ │ │ ├── session-atom.ts │ │ │ │ ├── solid │ │ │ │ │ ├── index.ts │ │ │ │ │ └── solid-store.ts │ │ │ │ ├── svelte │ │ │ │ │ └── index.ts │ │ │ │ ├── test-plugin.ts │ │ │ │ ├── types.ts │ │ │ │ ├── url.test.ts │ │ │ │ ├── vanilla.ts │ │ │ │ └── vue │ │ │ │ ├── index.ts │ │ │ │ └── vue-store.ts │ │ │ ├── cookies │ │ │ │ ├── check-cookies.ts │ │ │ │ ├── cookie-utils.ts │ │ │ │ ├── cookies.test.ts │ │ │ │ └── index.ts │ │ │ ├── crypto │ │ │ │ ├── buffer.ts │ │ │ │ ├── hash.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.ts │ │ │ │ ├── password.test.ts │ │ │ │ ├── password.ts │ │ │ │ └── random.ts │ │ │ ├── db │ │ │ │ ├── db.test.ts │ │ │ │ ├── field.ts │ │ │ │ ├── get-migration.ts │ │ │ │ ├── get-schema.ts │ │ │ │ ├── get-tables.test.ts │ │ │ │ ├── get-tables.ts │ │ │ │ ├── index.ts │ │ │ │ ├── internal-adapter.test.ts │ │ │ │ ├── internal-adapter.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── secondary-storage.test.ts │ │ │ │ ├── to-zod.ts │ │ │ │ ├── utils.ts │ │ │ │ └── with-hooks.ts │ │ │ ├── index.ts │ │ │ ├── init.test.ts │ │ │ ├── init.ts │ │ │ ├── integrations │ │ │ │ ├── next-js.ts │ │ │ │ ├── node.ts │ │ │ │ ├── react-start.ts │ │ │ │ ├── solid-start.ts │ │ │ │ └── svelte-kit.ts │ │ │ ├── oauth2 │ │ │ │ ├── index.ts │ │ │ │ ├── link-account.test.ts │ │ │ │ ├── link-account.ts │ │ │ │ ├── state.ts │ │ │ │ └── utils.ts │ │ │ ├── plugins │ │ │ │ ├── access │ │ │ │ │ ├── access.test.ts │ │ │ │ │ ├── access.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── additional-fields │ │ │ │ │ ├── additional-fields.test.ts │ │ │ │ │ └── client.ts │ │ │ │ ├── admin │ │ │ │ │ ├── access │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── statement.ts │ │ │ │ │ ├── admin.test.ts │ │ │ │ │ ├── admin.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── error-codes.ts │ │ │ │ │ ├── has-permission.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── anonymous │ │ │ │ │ ├── anon.test.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── api-key │ │ │ │ │ ├── api-key.test.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── rate-limit.ts │ │ │ │ │ ├── routes │ │ │ │ │ │ ├── create-api-key.ts │ │ │ │ │ │ ├── delete-all-expired-api-keys.ts │ │ │ │ │ │ ├── delete-api-key.ts │ │ │ │ │ │ ├── get-api-key.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── list-api-keys.ts │ │ │ │ │ │ ├── update-api-key.ts │ │ │ │ │ │ └── verify-api-key.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── bearer │ │ │ │ │ ├── bearer.test.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── captcha │ │ │ │ │ ├── captcha.test.ts │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── error-codes.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── verify-handlers │ │ │ │ │ ├── captchafox.ts │ │ │ │ │ ├── cloudflare-turnstile.ts │ │ │ │ │ ├── google-recaptcha.ts │ │ │ │ │ ├── h-captcha.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── custom-session │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── custom-session.test.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── device-authorization │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── device-authorization.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── schema.ts │ │ │ │ ├── email-otp │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── email-otp.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── generic-oauth │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── generic-oauth.test.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── haveibeenpwned │ │ │ │ │ ├── haveibeenpwned.test.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jwt │ │ │ │ │ ├── adapter.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── jwt.test.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── sign.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── last-login-method │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── custom-prefix.test.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── last-login-method.test.ts │ │ │ │ ├── magic-link │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── magic-link.test.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── mcp │ │ │ │ │ ├── authorize.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── mcp.test.ts │ │ │ │ ├── multi-session │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── multi-session.test.ts │ │ │ │ ├── oauth-proxy │ │ │ │ │ ├── index.ts │ │ │ │ │ └── oauth-proxy.test.ts │ │ │ │ ├── oidc-provider │ │ │ │ │ ├── authorize.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── oidc.test.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── ui.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── one-tap │ │ │ │ │ ├── client.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── one-time-token │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── one-time-token.test.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── open-api │ │ │ │ │ ├── generator.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── logo.ts │ │ │ │ │ └── open-api.test.ts │ │ │ │ ├── organization │ │ │ │ │ ├── access │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── statement.ts │ │ │ │ │ ├── adapter.ts │ │ │ │ │ ├── call.ts │ │ │ │ │ ├── client.test.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── error-codes.ts │ │ │ │ │ ├── has-permission.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── organization-hook.test.ts │ │ │ │ │ ├── organization.test.ts │ │ │ │ │ ├── organization.ts │ │ │ │ │ ├── permission.ts │ │ │ │ │ ├── routes │ │ │ │ │ │ ├── crud-access-control.test.ts │ │ │ │ │ │ ├── crud-access-control.ts │ │ │ │ │ │ ├── crud-invites.ts │ │ │ │ │ │ ├── crud-members.test.ts │ │ │ │ │ │ ├── crud-members.ts │ │ │ │ │ │ ├── crud-org.test.ts │ │ │ │ │ │ ├── crud-org.ts │ │ │ │ │ │ └── crud-team.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── team.test.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── passkey │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── passkey.test.ts │ │ │ │ ├── phone-number │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── phone-number-error.ts │ │ │ │ │ └── phone-number.test.ts │ │ │ │ ├── siwe │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── siwe.test.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── two-factor │ │ │ │ │ ├── backup-codes │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── client.ts │ │ │ │ │ ├── constant.ts │ │ │ │ │ ├── error-code.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── otp │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── totp │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── two-factor.test.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── verify-two-factor.ts │ │ │ │ └── username │ │ │ │ ├── client.ts │ │ │ │ ├── error-codes.ts │ │ │ │ ├── index.ts │ │ │ │ ├── schema.ts │ │ │ │ └── username.test.ts │ │ │ ├── social-providers │ │ │ │ └── index.ts │ │ │ ├── social.test.ts │ │ │ ├── test-utils │ │ │ │ ├── headers.ts │ │ │ │ ├── index.ts │ │ │ │ ├── state.ts │ │ │ │ └── test-instance.ts │ │ │ ├── types │ │ │ │ ├── adapter.ts │ │ │ │ ├── api.ts │ │ │ │ ├── helper.ts │ │ │ │ ├── index.ts │ │ │ │ ├── models.ts │ │ │ │ ├── plugins.ts │ │ │ │ └── types.test.ts │ │ │ └── utils │ │ │ ├── await-object.ts │ │ │ ├── boolean.ts │ │ │ ├── clone.ts │ │ │ ├── constants.ts │ │ │ ├── date.ts │ │ │ ├── ensure-utc.ts │ │ │ ├── get-request-ip.ts │ │ │ ├── hashing.ts │ │ │ ├── hide-metadata.ts │ │ │ ├── id.ts │ │ │ ├── import-util.ts │ │ │ ├── index.ts │ │ │ ├── is-atom.ts │ │ │ ├── is-promise.ts │ │ │ ├── json.ts │ │ │ ├── merger.ts │ │ │ ├── middleware-response.ts │ │ │ ├── misc.ts │ │ │ ├── password.ts │ │ │ ├── plugin-helper.ts │ │ │ ├── shim.ts │ │ │ ├── time.ts │ │ │ ├── url.ts │ │ │ └── wildcard.ts │ │ ├── tsconfig.json │ │ ├── tsdown.config.ts │ │ ├── vitest.config.ts │ │ └── vitest.setup.ts │ ├── cli │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── commands │ │ │ │ ├── generate.ts │ │ │ │ ├── info.ts │ │ │ │ ├── init.ts │ │ │ │ ├── login.ts │ │ │ │ ├── mcp.ts │ │ │ │ ├── migrate.ts │ │ │ │ └── secret.ts │ │ │ ├── generators │ │ │ │ ├── auth-config.ts │ │ │ │ ├── drizzle.ts │ │ │ │ ├── index.ts │ │ │ │ ├── kysely.ts │ │ │ │ ├── prisma.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ └── utils │ │ │ ├── add-svelte-kit-env-modules.ts │ │ │ ├── check-package-managers.ts │ │ │ ├── format-ms.ts │ │ │ ├── get-config.ts │ │ │ ├── get-package-info.ts │ │ │ ├── get-tsconfig-info.ts │ │ │ └── install-dependencies.ts │ │ ├── test │ │ │ ├── __snapshots__ │ │ │ │ ├── auth-schema-mysql-enum.txt │ │ │ │ ├── auth-schema-mysql-number-id.txt │ │ │ │ ├── auth-schema-mysql-passkey-number-id.txt │ │ │ │ ├── auth-schema-mysql-passkey.txt │ │ │ │ ├── auth-schema-mysql.txt │ │ │ │ ├── auth-schema-number-id.txt │ │ │ │ ├── auth-schema-pg-enum.txt │ │ │ │ ├── auth-schema-pg-passkey.txt │ │ │ │ ├── auth-schema-sqlite-enum.txt │ │ │ │ ├── auth-schema-sqlite-number-id.txt │ │ │ │ ├── auth-schema-sqlite-passkey-number-id.txt │ │ │ │ ├── auth-schema-sqlite-passkey.txt │ │ │ │ ├── auth-schema-sqlite.txt │ │ │ │ ├── auth-schema.txt │ │ │ │ ├── migrations.sql │ │ │ │ ├── schema-mongodb.prisma │ │ │ │ ├── schema-mysql-custom.prisma │ │ │ │ ├── schema-mysql.prisma │ │ │ │ ├── schema-numberid.prisma │ │ │ │ └── schema.prisma │ │ │ ├── generate-all-db.test.ts │ │ │ ├── generate.test.ts │ │ │ ├── get-config.test.ts │ │ │ ├── info.test.ts │ │ │ └── migrate.test.ts │ │ ├── tsconfig.json │ │ ├── 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 -------------------------------------------------------------------------------- /packages/better-auth/src/adapters/adapter-factory/test/adapter-factory.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, test, expect } from "vitest"; 2 | import { createAdapterFactory } from ".."; 3 | import type { 4 | AdapterFactoryConfig, 5 | AdapterFactoryCustomizeAdapterCreator, 6 | } from "../types"; 7 | import type { CleanedWhere, Where } from "@better-auth/core/db/adapter"; 8 | import type { User } from "../../../types"; 9 | import type { BetterAuthOptions } from "@better-auth/core"; 10 | import { betterAuth } from "../../../auth"; 11 | 12 | /* 13 | 14 | Note that there are basically 2 types of tests here: 15 | 16 | 1. Making sure that the data within each adapter call is correct. (Transformed to suit the DB, accurate according to the schema, etc.) 17 | 2. Making sure the output of each adapter call is correct. (The data is transformed back to the correct format, etc.) 18 | 19 | The rest are just edge cases. 20 | 21 | */ 22 | 23 | async function createTestAdapter( 24 | props: { 25 | config?: Partial<AdapterFactoryConfig>; 26 | options?: BetterAuthOptions; 27 | adapter?: ( 28 | ...args: Parameters<AdapterFactoryCustomizeAdapterCreator> 29 | ) => Partial<ReturnType<AdapterFactoryCustomizeAdapterCreator>>; 30 | } = { 31 | config: { 32 | adapterId: "test-id", 33 | adapterName: "Test Adapter", 34 | usePlural: false, 35 | debugLogs: false, 36 | supportsJSON: true, 37 | supportsDates: true, 38 | supportsBooleans: true, 39 | }, 40 | options: {}, 41 | adapter: () => ({}), 42 | }, 43 | ) { 44 | const { 45 | config = { 46 | adapterId: "test-id", 47 | adapterName: "Test Adapter", 48 | usePlural: false, 49 | debugLogs: false, 50 | supportsJSON: true, 51 | supportsDates: true, 52 | supportsBooleans: true, 53 | }, 54 | options = {}, 55 | adapter = () => ({}), 56 | } = props; 57 | const testAdapter = createAdapterFactory({ 58 | config: Object.assign( 59 | { 60 | adapterId: "test-id", 61 | adapterName: "Test Adapter", 62 | usePlural: false, 63 | debugLogs: false, 64 | supportsJSON: true, 65 | supportsDates: true, 66 | supportsBooleans: true, 67 | }, 68 | config, 69 | ), 70 | adapter: (...args) => { 71 | const x = adapter(...args) as Partial< 72 | ReturnType<AdapterFactoryCustomizeAdapterCreator> 73 | >; 74 | return { 75 | async create(data) { 76 | if (x.create) { 77 | return await x.create(data); 78 | } 79 | return data.data; 80 | }, 81 | async update(data) { 82 | if (x.update) { 83 | return await x.update(data); 84 | } 85 | return data.update; 86 | }, 87 | async updateMany(data) { 88 | if (x.updateMany) { 89 | return await x.updateMany(data); 90 | } 91 | return 0; 92 | }, 93 | async count(data) { 94 | if (x.count) { 95 | return await x.count(data); 96 | } 97 | return 0; 98 | }, 99 | async delete(data) { 100 | if (x.delete) { 101 | return await x.delete(data); 102 | } 103 | return; 104 | }, 105 | async deleteMany(data) { 106 | if (x.deleteMany) { 107 | return await x.deleteMany(data); 108 | } 109 | return 0; 110 | }, 111 | async findMany(data) { 112 | if (x.findMany) { 113 | return await x.findMany(data); 114 | } 115 | return []; 116 | }, 117 | async findOne(data) { 118 | if (x.findOne) { 119 | return await x.findOne(data); 120 | } 121 | return null; 122 | }, 123 | options: x.options ?? {}, 124 | }; 125 | }, 126 | }); 127 | const auth = betterAuth({ 128 | ...options, 129 | database: testAdapter, 130 | }); 131 | 132 | return (await auth.$context).adapter; 133 | } 134 | 135 | describe("Create Adapter Helper", async () => { 136 | const adapterId = "test-adapter-id"; 137 | const adapter = await createTestAdapter({ 138 | config: { 139 | adapterId, 140 | }, 141 | }); 142 | 143 | test("Should have the correct adapter id", () => { 144 | expect(adapter.id).toBe(adapterId); 145 | }); 146 | 147 | test("Should use the id generator if passed into the betterAuth config", async () => { 148 | const adapter = await createTestAdapter({ 149 | config: { 150 | debugLogs: {}, 151 | }, 152 | options: { 153 | advanced: { 154 | database: { 155 | generateId(options) { 156 | return "HARD-CODED-ID"; 157 | }, 158 | }, 159 | }, 160 | }, 161 | }); 162 | const res = await adapter.create({ 163 | model: "user", 164 | data: { name: "test-name" }, 165 | }); 166 | expect(res).toHaveProperty("id"); 167 | expect(res.id).toBe("HARD-CODED-ID"); 168 | }); 169 | 170 | test("Should not generate an id if `advanced.database.generateId` is not defined or false", async () => { 171 | const adapter = await createTestAdapter({ 172 | config: {}, 173 | options: { 174 | advanced: { 175 | database: { generateId: false, useNumberId: false }, 176 | }, 177 | }, 178 | adapter(args_0) { 179 | return { 180 | async create(data) { 181 | expect(data.data.id).not.toBeDefined(); 182 | return data.data; 183 | }, 184 | }; 185 | }, 186 | }); 187 | 188 | const testResult = await adapter.create({ 189 | model: "user", 190 | data: { name: "test-name" }, 191 | }); 192 | expect(testResult.id).not.toBeDefined(); 193 | }); 194 | 195 | test("Should throw an error if the database doesn't support numeric ids and the user has enabled `useNumberId`", async () => { 196 | let error: any | null = null; 197 | try { 198 | await createTestAdapter({ 199 | config: { 200 | supportsNumericIds: false, 201 | }, 202 | options: { 203 | advanced: { 204 | database: { 205 | useNumberId: true, 206 | }, 207 | }, 208 | }, 209 | }); 210 | } catch (err) { 211 | error = err; 212 | } 213 | expect(error).not.toBeNull(); 214 | }); 215 | 216 | describe("Checking for the results of an adapter call, as well as the parameters passed into the adapter call", () => { 217 | describe("create", () => { 218 | test("Should fill in the missing fields in the result", async () => { 219 | const res = await adapter.create({ 220 | model: "user", 221 | data: { name: "test-name" }, 222 | }); 223 | expect(res).toHaveProperty("id"); 224 | expect(res).toHaveProperty("name"); 225 | expect(res).toHaveProperty("email"); 226 | expect(res).toHaveProperty("emailVerified"); 227 | expect(res).toHaveProperty("image"); 228 | expect(res).toHaveProperty("createdAt"); 229 | expect(res).toHaveProperty("updatedAt"); 230 | expect(res?.emailVerified).toEqual(false); 231 | expect(res?.name).toEqual("test-name"); 232 | expect(res?.email).toEqual(undefined); 233 | expect(res?.image).toEqual(undefined); 234 | expect(res?.createdAt).toBeInstanceOf(Date); 235 | expect(res?.updatedAt).toBeInstanceOf(Date); 236 | }); 237 | 238 | test("should not return string for nullable foreign keys", async () => { 239 | const adapter = await createTestAdapter({ 240 | config: { 241 | debugLogs: {}, 242 | }, 243 | options: { 244 | plugins: [ 245 | { 246 | id: "test", 247 | schema: { 248 | testModel: { 249 | fields: { 250 | nullableReference: { 251 | type: "string", 252 | references: { field: "id", model: "user" }, 253 | required: false, 254 | }, 255 | }, 256 | }, 257 | }, 258 | }, 259 | ], 260 | }, 261 | }); 262 | const res = await adapter.create({ 263 | model: "testModel", 264 | data: { nullableReference: null }, 265 | }); 266 | expect(res).toHaveProperty("nullableReference"); 267 | expect(res.nullableReference).toBeNull(); 268 | 269 | const adapter2 = await createTestAdapter({ 270 | config: { 271 | debugLogs: {}, 272 | }, 273 | options: { 274 | plugins: [ 275 | { 276 | id: "test", 277 | schema: { 278 | testModel: { 279 | fields: { 280 | nullableReference: { 281 | type: "string", 282 | references: { field: "id", model: "user" }, 283 | required: false, 284 | }, 285 | }, 286 | }, 287 | }, 288 | }, 289 | ], 290 | advanced: { 291 | database: { 292 | useNumberId: true, 293 | }, 294 | }, 295 | }, 296 | }); 297 | const res2 = await adapter2.create({ 298 | model: "testModel", 299 | data: { nullableReference: null }, 300 | }); 301 | expect(res2).toHaveProperty("nullableReference"); 302 | expect(res2.nullableReference).toBeNull(); 303 | }); 304 | 305 | test('Should include an "id" in the result in all cases, unless "select" is used to exclude it', async () => { 306 | const res = await adapter.create({ 307 | model: "user", 308 | data: { name: "test-name" }, 309 | }); 310 | expect(res).toHaveProperty("id"); 311 | expect(typeof res?.id).toEqual("string"); 312 | 313 | const adapterWithoutIdGeneration = await createTestAdapter({ 314 | config: { 315 | disableIdGeneration: true, 316 | debugLogs: {}, 317 | }, 318 | }); 319 | const res2 = await adapterWithoutIdGeneration.create({ 320 | model: "user", 321 | data: { name: "test-name" }, 322 | }); 323 | // Id will still be present, due to the transformOutput function. However it will be undefined, vvvvv 324 | expect(res2).toHaveProperty("id"); 325 | expect(typeof res2?.id).toEqual("undefined"); 326 | // In a real case, the `id` should always be present 327 | 328 | const res3 = await adapter.create({ 329 | model: "user", 330 | data: { name: "test-name" }, 331 | select: ["name"], 332 | }); 333 | expect(res3).toHaveProperty("name"); 334 | expect(res3).not.toHaveProperty("id"); 335 | }); 336 | 337 | test('Should receive a generated id during the call, unless "disableIdGeneration" is set to true', async () => { 338 | const createWithId: { id: unknown } = await new Promise(async (r) => { 339 | const adapter = await createTestAdapter({ 340 | adapter(args_0) { 341 | return { 342 | async create({ data, model, select }) { 343 | r(data as any); 344 | return data; 345 | }, 346 | }; 347 | }, 348 | }); 349 | adapter.create({ 350 | model: "user", 351 | data: { name: "test-name" }, 352 | }); 353 | }); 354 | 355 | expect(createWithId).toBeDefined(); 356 | expect(createWithId.id).toBeDefined(); 357 | expect(typeof createWithId.id).toBe("string"); 358 | 359 | const createWithoutId: { id: unknown } = await new Promise( 360 | async (r) => { 361 | const adapter = await createTestAdapter({ 362 | config: { 363 | disableIdGeneration: true, 364 | debugLogs: {}, 365 | }, 366 | adapter(args_0) { 367 | return { 368 | async create({ data, model, select }) { 369 | r(data as any); 370 | return data; 371 | }, 372 | }; 373 | }, 374 | }); 375 | adapter.create({ 376 | model: "user", 377 | data: { name: "test-name" }, 378 | }); 379 | }, 380 | ); 381 | 382 | expect(createWithoutId).toBeDefined(); 383 | expect(createWithoutId.id).toBeUndefined(); 384 | }); 385 | 386 | test("Should not modify result null to string for id or fields referencing id", async () => { 387 | const result: { id: string; testPluginField: string | null } = 388 | await new Promise(async (r) => { 389 | const adapter = await createTestAdapter({ 390 | adapter(args_0) { 391 | return { 392 | async create({ data, model, select }) { 393 | return data; 394 | }, 395 | }; 396 | }, 397 | options: { 398 | plugins: [ 399 | { 400 | id: "test-plugin-id", 401 | schema: { 402 | testPluginTable: { 403 | fields: { 404 | testPluginField: { 405 | type: "string", 406 | required: false, 407 | references: { 408 | model: "user", 409 | field: "id", 410 | }, 411 | }, 412 | }, 413 | }, 414 | }, 415 | }, 416 | ], 417 | }, 418 | }); 419 | r( 420 | await adapter.create({ 421 | model: "testPluginTable", 422 | data: { 423 | testPluginField: null, 424 | }, 425 | }), 426 | ); 427 | }); 428 | 429 | expect(result.id).toBeTypeOf("string"); 430 | expect(result.testPluginField).toBeNull(); 431 | }); 432 | 433 | test("Should modify boolean type to 1 or 0 if the DB doesn't support it. And expect the result to be transformed back to boolean", async () => { 434 | // Testing true 435 | const createTRUEParameters: { data: { emailVerified: number } } = 436 | await new Promise(async (r) => { 437 | const adapter = await createTestAdapter({ 438 | config: { 439 | supportsBooleans: false, 440 | }, 441 | adapter(args_0) { 442 | return { 443 | async create(data) { 444 | r(data as any); 445 | return data.data; 446 | }, 447 | }; 448 | }, 449 | }); 450 | const res = await adapter.create({ 451 | model: "user", 452 | data: { emailVerified: true }, 453 | }); 454 | expect(res).toHaveProperty("emailVerified"); 455 | expect(res.emailVerified).toBe(true); 456 | }); 457 | expect(createTRUEParameters.data).toHaveProperty("emailVerified"); 458 | expect(createTRUEParameters.data.emailVerified).toBe(1); 459 | 460 | // Testing false 461 | const createFALSEParameters: { data: { emailVerified: number } } = 462 | await new Promise(async (r) => { 463 | const adapter = await createTestAdapter({ 464 | config: { 465 | supportsBooleans: false, 466 | }, 467 | adapter(args_0) { 468 | return { 469 | async create(data) { 470 | r(data as any); 471 | return data.data; 472 | }, 473 | }; 474 | }, 475 | }); 476 | const res = await adapter.create({ 477 | model: "user", 478 | data: { emailVerified: false }, 479 | }); 480 | expect(res).toHaveProperty("emailVerified"); 481 | expect(res.emailVerified).toBe(false); 482 | }); 483 | expect(createFALSEParameters.data).toHaveProperty("emailVerified"); 484 | expect(createFALSEParameters.data.emailVerified).toBe(0); 485 | }); 486 | 487 | test("Should modify JSON type to TEXT if the DB doesn't support it. And expect the result to be transformed back to JSON", async () => { 488 | const createJSONParameters: { data: { preferences: string } } = 489 | await new Promise(async (r) => { 490 | const adapter = await createTestAdapter({ 491 | config: { 492 | supportsJSON: false, 493 | }, 494 | options: { 495 | user: { 496 | additionalFields: { 497 | preferences: { 498 | type: "json", 499 | }, 500 | }, 501 | }, 502 | }, 503 | adapter(args_0) { 504 | return { 505 | async create(data) { 506 | r(data as any); 507 | return data.data; 508 | }, 509 | }; 510 | }, 511 | }); 512 | const obj = { preferences: { color: "blue", size: "large" } }; 513 | const res = await adapter.create({ 514 | model: "user", 515 | data: obj, 516 | }); 517 | expect(res).toHaveProperty("preferences"); 518 | expect(res.preferences).toEqual(obj.preferences); 519 | }); 520 | expect(createJSONParameters.data).toHaveProperty("preferences"); 521 | expect(createJSONParameters.data.preferences).toEqual( 522 | '{"color":"blue","size":"large"}', 523 | ); 524 | }); 525 | 526 | test("Should modify date type to TEXT if the DB doesn't support it. And expect the result to be transformed back to date", async () => { 527 | const testDate = new Date(); 528 | const createDateParameters: { data: { createdAt: string } } = 529 | await new Promise(async (r) => { 530 | const adapter = await createTestAdapter({ 531 | config: { 532 | supportsDates: false, 533 | }, 534 | adapter(args_0) { 535 | return { 536 | async create(data) { 537 | r(data as any); 538 | return data.data; 539 | }, 540 | }; 541 | }, 542 | }); 543 | const res = await adapter.create({ 544 | model: "user", 545 | data: { createdAt: testDate }, 546 | }); 547 | expect(res).toHaveProperty("createdAt"); 548 | expect(res.createdAt).toBeInstanceOf(Date); 549 | }); 550 | expect(createDateParameters.data).toHaveProperty("createdAt"); 551 | expect(createDateParameters.data.createdAt).toEqual( 552 | testDate.toISOString(), 553 | ); 554 | }); 555 | 556 | test("Should allow custom transform input", async () => { 557 | const createCustomTransformInputParameters: { data: { name: string } } = 558 | await new Promise(async (r) => { 559 | const adapter = await createTestAdapter({ 560 | config: { 561 | debugLogs: {}, 562 | customTransformInput({ field, data }) { 563 | if (field === "name") { 564 | return data.toUpperCase(); 565 | } 566 | return data; 567 | }, 568 | }, 569 | adapter(args_0) { 570 | return { 571 | async create(data) { 572 | r(data as any); 573 | return data.data; 574 | }, 575 | }; 576 | }, 577 | }); 578 | const res = await adapter.create({ 579 | model: "user", 580 | data: { name: "test-name" }, 581 | }); 582 | expect(res).toHaveProperty("name"); 583 | expect(res.name).toEqual("TEST-NAME"); 584 | }); 585 | expect(createCustomTransformInputParameters.data).toHaveProperty( 586 | "name", 587 | ); 588 | expect(createCustomTransformInputParameters.data.name).toEqual( 589 | "TEST-NAME", 590 | ); 591 | }); 592 | 593 | test("Should allow custom transform output", async () => { 594 | const createCustomTransformOutputParameters: { 595 | data: { name: string }; 596 | } = await new Promise(async (r) => { 597 | const adapter = await createTestAdapter({ 598 | config: { 599 | debugLogs: {}, 600 | customTransformOutput({ field, data }) { 601 | if (field === "name") { 602 | return data.toLowerCase(); 603 | } 604 | return data; 605 | }, 606 | }, 607 | adapter(args_0) { 608 | return { 609 | async create(data) { 610 | r(data as any); 611 | return data.data; 612 | }, 613 | }; 614 | }, 615 | }); 616 | const res = await adapter.create({ 617 | model: "user", 618 | data: { name: "TEST-NAME" }, 619 | }); 620 | expect(res).toHaveProperty("name"); 621 | expect(res.name).toEqual("test-name"); 622 | }); 623 | expect(createCustomTransformOutputParameters.data).toHaveProperty( 624 | "name", 625 | ); 626 | expect(createCustomTransformOutputParameters.data.name).toEqual( 627 | "TEST-NAME", // Remains the same as the input because we're only transforming the output 628 | ); 629 | }); 630 | 631 | test("Should allow custom transform input and output", async () => { 632 | const createCustomTransformInputAndOutputParameters: { 633 | data: { name: string }; 634 | } = await new Promise(async (r) => { 635 | const adapter = await createTestAdapter({ 636 | config: { 637 | debugLogs: {}, 638 | customTransformInput({ field, data }) { 639 | if (field === "name") { 640 | return data.toUpperCase(); 641 | } 642 | return data; 643 | }, 644 | customTransformOutput({ field, data }) { 645 | if (field === "name") { 646 | return data.toLowerCase(); 647 | } 648 | return data; 649 | }, 650 | }, 651 | adapter(args_0) { 652 | return { 653 | async create(data) { 654 | r(data as any); 655 | return data.data; 656 | }, 657 | }; 658 | }, 659 | }); 660 | const res = await adapter.create({ 661 | model: "user", 662 | data: { name: "TEST-NAME" }, 663 | }); 664 | expect(res).toHaveProperty("name"); 665 | expect(res.name).toEqual("test-name"); 666 | }); 667 | expect( 668 | createCustomTransformInputAndOutputParameters.data, 669 | ).toHaveProperty("name"); 670 | expect(createCustomTransformInputAndOutputParameters.data.name).toEqual( 671 | "TEST-NAME", 672 | ); 673 | }); 674 | 675 | test("Should allow custom map input key transformation", async () => { 676 | const parameters: { 677 | data: { email_address: string }; 678 | } = await new Promise(async (r) => { 679 | const adapter = await createTestAdapter({ 680 | config: { 681 | debugLogs: {}, 682 | mapKeysTransformInput: { 683 | email: "email_address", 684 | }, 685 | }, 686 | adapter(args_0) { 687 | return { 688 | async create(data) { 689 | r(data as any); 690 | return data.data; 691 | }, 692 | }; 693 | }, 694 | }); 695 | 696 | const res = (await adapter.create({ 697 | model: "user", 698 | data: { email: "[email protected]" }, 699 | })) as { email: string }; 700 | 701 | expect(res).toHaveProperty("email"); 702 | expect(res).not.toHaveProperty("email_address"); 703 | expect(res.email).toEqual(undefined); // The reason it's undefined is because we did transform `email` to `email_address`, however we never transformed `email_address` back to `email`. 704 | }); 705 | expect(parameters.data).toHaveProperty("email_address"); 706 | expect(parameters.data.email_address).toEqual("[email protected]"); 707 | }); 708 | 709 | test("Should allow custom transform input to transform the where clause", async () => { 710 | const parameters: CleanedWhere[] = await new Promise(async (r) => { 711 | const adapter = await createTestAdapter({ 712 | config: { 713 | debugLogs: {}, 714 | mapKeysTransformInput: { 715 | id: "_id", 716 | }, 717 | }, 718 | adapter(args_0) { 719 | return { 720 | async findOne({ model, where, select }) { 721 | r(where); 722 | return {} as any; 723 | }, 724 | }; 725 | }, 726 | }); 727 | adapter.findOne({ 728 | model: "user", 729 | where: [{ field: "id", value: "123" }], 730 | }); 731 | }); 732 | 733 | expect(parameters[0]!.field).toEqual("_id"); 734 | }); 735 | 736 | test("Should allow custom map output key transformation", async () => { 737 | const parameters: { 738 | data: { email: string }; 739 | } = await new Promise(async (r) => { 740 | const adapter = await createTestAdapter({ 741 | config: { 742 | debugLogs: {}, 743 | mapKeysTransformOutput: { 744 | email: "wrong_email_key", 745 | }, 746 | }, 747 | 748 | adapter(args_0) { 749 | return { 750 | async create(data) { 751 | r(data as any); 752 | return data.data; 753 | }, 754 | }; 755 | }, 756 | }); 757 | const res = (await adapter.create({ 758 | model: "user", 759 | data: { email: "[email protected]" }, 760 | })) as { wrong_email_key: string }; 761 | // Even though we're using the output key transformation, we still don't actually get the key transformation we want. 762 | // This is because the output is also parsed against the schema, and the `wrong_email_key` key is not in the schema. 763 | expect(res).toHaveProperty("wrong_email_key"); 764 | expect(res).not.toHaveProperty("email"); 765 | expect(res.wrong_email_key).toEqual("[email protected]"); 766 | }); 767 | 768 | expect(parameters.data).toHaveProperty("email"); 769 | expect(parameters.data.email).toEqual("[email protected]"); 770 | }); 771 | 772 | test("Should allow custom map input and output key transformation", async () => { 773 | const parameters: { 774 | data: { email_address: string }; 775 | } = await new Promise(async (r) => { 776 | const adapter = await createTestAdapter({ 777 | config: { 778 | debugLogs: {}, 779 | mapKeysTransformInput: { 780 | email: "email_address", 781 | }, 782 | mapKeysTransformOutput: { 783 | email_address: "email", 784 | }, 785 | }, 786 | adapter(args_0) { 787 | return { 788 | async create(data) { 789 | r(data as any); 790 | return data.data; 791 | }, 792 | }; 793 | }, 794 | }); 795 | const res = await adapter.create({ 796 | model: "user", 797 | data: { email: "[email protected]" }, 798 | }); 799 | expect(res).toHaveProperty("email"); 800 | expect(res).not.toHaveProperty("email_address"); 801 | expect(res.email).toEqual("[email protected]"); 802 | }); 803 | expect(parameters.data).toHaveProperty("email_address"); 804 | expect(parameters.data).not.toHaveProperty("email"); 805 | expect(parameters.data.email_address).toEqual("[email protected]"); 806 | }); 807 | 808 | test("Should expect the fields to be transformed into the correct field names if customized", async () => { 809 | const parameters: { data: any; select?: string[]; model: string } = 810 | await new Promise(async (r) => { 811 | const adapter = await createTestAdapter({ 812 | config: { 813 | debugLogs: {}, 814 | }, 815 | options: { 816 | user: { 817 | fields: { 818 | email: "email_address", 819 | }, 820 | }, 821 | }, 822 | adapter(args_0) { 823 | return { 824 | async create(data) { 825 | r(data as any); 826 | return data.data; 827 | }, 828 | }; 829 | }, 830 | }); 831 | const res = await adapter.create({ 832 | model: "user", 833 | data: { email: "[email protected]" }, 834 | }); 835 | expect(res).toHaveProperty("email"); 836 | expect(res).not.toHaveProperty("email_address"); 837 | expect(res.email).toEqual("[email protected]"); 838 | }); 839 | expect(parameters).toHaveProperty("data"); 840 | expect(parameters.data).toHaveProperty("email_address"); 841 | expect(parameters.data).not.toHaveProperty("email"); 842 | expect(parameters.data.email_address).toEqual("[email protected]"); 843 | }); 844 | 845 | test("Should expect the model to be transformed into the correct model name if customized", async () => { 846 | const parameters: { data: any; select?: string[]; model: string } = 847 | await new Promise(async (r) => { 848 | const adapter = await createTestAdapter({ 849 | config: { 850 | debugLogs: {}, 851 | }, 852 | options: { 853 | user: { 854 | modelName: "user_table", 855 | }, 856 | }, 857 | adapter(args_0) { 858 | return { 859 | async create(data) { 860 | r(data as any); 861 | return data.data; 862 | }, 863 | }; 864 | }, 865 | }); 866 | const res = await adapter.create({ 867 | model: "user", 868 | data: { email: "[email protected]" }, 869 | }); 870 | expect(res).toHaveProperty("id"); 871 | expect(res).toHaveProperty("email"); 872 | }); 873 | expect(parameters).toHaveProperty("model"); 874 | expect(parameters.model).toEqual("user_table"); 875 | }); 876 | 877 | test("Should expect the result to follow the schema", async () => { 878 | const parameters: { data: any; select?: string[]; model: string } = 879 | await new Promise(async (r) => { 880 | const adapter = await createTestAdapter({ 881 | config: { 882 | debugLogs: {}, 883 | }, 884 | options: { 885 | user: { 886 | fields: { 887 | email: "email_address", 888 | }, 889 | }, 890 | }, 891 | adapter(args_0) { 892 | return { 893 | async create(data) { 894 | r(data as any); 895 | return data.data; 896 | }, 897 | }; 898 | }, 899 | }); 900 | const res = await adapter.create({ 901 | model: "user", 902 | data: { email: "[email protected]" }, 903 | }); 904 | expect(res).toHaveProperty("email"); 905 | expect(res).toHaveProperty("id"); 906 | expect(res).toHaveProperty("createdAt"); 907 | expect(res).toHaveProperty("updatedAt"); 908 | expect(res).toHaveProperty("name"); 909 | expect(res).toHaveProperty("emailVerified"); 910 | expect(res).toHaveProperty("image"); 911 | expect(res).not.toHaveProperty("email_address"); 912 | }); 913 | expect(parameters).toHaveProperty("data"); 914 | expect(parameters.data).toHaveProperty("email_address"); 915 | expect(parameters.data).not.toHaveProperty("email"); 916 | expect(parameters.data.email_address).toEqual("[email protected]"); 917 | }); 918 | 919 | test("Should expect the result to respect the select fields", async () => { 920 | const adapter = await createTestAdapter({ 921 | config: { 922 | debugLogs: {}, 923 | }, 924 | options: { 925 | user: { 926 | fields: { 927 | email: "email_address", 928 | }, 929 | }, 930 | }, 931 | }); 932 | const res = await adapter.create({ 933 | model: "user", 934 | data: { 935 | email: "[email protected]", 936 | name: "test-name", 937 | emailVerified: false, 938 | image: "test-image", 939 | }, 940 | select: ["email"], 941 | }); 942 | expect(res).toHaveProperty("email"); 943 | expect(res).not.toHaveProperty("name"); 944 | expect(res).not.toHaveProperty("emailVerified"); 945 | expect(res).not.toHaveProperty("image"); 946 | expect(res).toMatchSnapshot(); 947 | }); 948 | }); 949 | 950 | describe("update", () => { 951 | test("Should fill in the missing fields in the result", async () => { 952 | const user: { id: string; name: string } = await adapter.create({ 953 | model: "user", 954 | data: { name: "test-name" }, 955 | }); 956 | const res = await adapter.update({ 957 | model: "user", 958 | where: [{ field: "id", value: user.id }], 959 | update: { name: "test-name-2" }, 960 | }); 961 | expect(res).toHaveProperty("id"); 962 | expect(res).toHaveProperty("name"); 963 | expect(res).toHaveProperty("email"); 964 | expect(res).toHaveProperty("emailVerified"); 965 | expect(res).toHaveProperty("image"); 966 | expect(res).toHaveProperty("createdAt"); 967 | expect(res).toHaveProperty("updatedAt"); 968 | }); 969 | 970 | test(`Should include an "id" in the result in all cases`, async () => { 971 | const user: { id: string; name: string } = await adapter.create({ 972 | model: "user", 973 | data: { name: "test-name" }, 974 | }); 975 | const res: { id: string } | null = await adapter.update({ 976 | model: "user", 977 | where: [{ field: "id", value: user.id }], 978 | update: { name: "test-name-2" }, 979 | }); 980 | expect(res).toHaveProperty("id"); 981 | }); 982 | 983 | test("Should modify boolean type to 1 or 0 if the DB doesn't support it. And expect the result to be transformed back to boolean", async () => { 984 | // Testing true 985 | const updateTRUEParameters: { update: { emailVerified: number } } = 986 | await new Promise(async (r) => { 987 | const adapter = await createTestAdapter({ 988 | config: { 989 | supportsBooleans: false, 990 | }, 991 | adapter(args_0) { 992 | return { 993 | async update(data) { 994 | r(data as any); 995 | return data.update; 996 | }, 997 | }; 998 | }, 999 | }); 1000 | const user: { emailVerified: boolean; id: string } = 1001 | await adapter.create({ 1002 | model: "user", 1003 | data: { emailVerified: false }, 1004 | }); 1005 | const res = await adapter.update({ 1006 | model: "user", 1007 | where: [{ field: "id", value: user.id }], 1008 | update: { emailVerified: true }, 1009 | }); 1010 | expect(res).toHaveProperty("emailVerified"); 1011 | //@ts-expect-error 1012 | expect(res.emailVerified).toBe(true); 1013 | }); 1014 | expect(updateTRUEParameters.update).toHaveProperty("emailVerified"); 1015 | expect(updateTRUEParameters.update.emailVerified).toBe(1); 1016 | 1017 | // Testing false 1018 | const createFALSEParameters: { update: { emailVerified: number } } = 1019 | await new Promise(async (r) => { 1020 | const adapter = await createTestAdapter({ 1021 | config: { 1022 | supportsBooleans: false, 1023 | }, 1024 | adapter(args_0) { 1025 | return { 1026 | async update(data) { 1027 | r(data as any); 1028 | return data.update; 1029 | }, 1030 | }; 1031 | }, 1032 | }); 1033 | const user: { emailVerified: boolean; id: string } = 1034 | await adapter.create({ 1035 | model: "user", 1036 | data: { emailVerified: true }, 1037 | }); 1038 | const res = await adapter.update({ 1039 | model: "user", 1040 | where: [{ field: "id", value: user.id }], 1041 | update: { emailVerified: false }, 1042 | }); 1043 | expect(res).toHaveProperty("emailVerified"); 1044 | //@ts-expect-error 1045 | expect(res.emailVerified).toBe(false); 1046 | }); 1047 | expect(createFALSEParameters.update).toHaveProperty("emailVerified"); 1048 | expect(createFALSEParameters.update.emailVerified).toBe(0); 1049 | }); 1050 | 1051 | test("Should modify JSON type to TEXT if the DB doesn't support it. And expect the result to be transformed back to JSON", async () => { 1052 | const createJSONParameters: { update: { preferences: string } } = 1053 | await new Promise(async (r) => { 1054 | const adapter = await createTestAdapter({ 1055 | config: { 1056 | supportsJSON: false, 1057 | }, 1058 | options: { 1059 | user: { 1060 | additionalFields: { 1061 | preferences: { 1062 | type: "json", 1063 | }, 1064 | }, 1065 | }, 1066 | }, 1067 | adapter(args_0) { 1068 | return { 1069 | async update(data) { 1070 | r(data as any); 1071 | return data.update; 1072 | }, 1073 | }; 1074 | }, 1075 | }); 1076 | const obj = { preferences: { color: "blue", size: "large" } }; 1077 | const user: { email: string; id: string } = await adapter.create({ 1078 | model: "user", 1079 | data: { email: "[email protected]" }, 1080 | }); 1081 | const res: typeof obj | null = await adapter.update({ 1082 | model: "user", 1083 | where: [{ field: "id", value: user.id }], 1084 | update: { preferences: obj.preferences }, 1085 | }); 1086 | expect(res).toHaveProperty("preferences"); 1087 | expect(res?.preferences).toEqual(obj.preferences); 1088 | }); 1089 | expect(createJSONParameters.update).toHaveProperty("preferences"); 1090 | expect(createJSONParameters.update.preferences).toEqual( 1091 | '{"color":"blue","size":"large"}', 1092 | ); 1093 | }); 1094 | 1095 | test("Should modify date type to TEXT if the DB doesn't support it. And expect the result to be transformed back to date", async () => { 1096 | const testDate = new Date(); 1097 | const createDateParameters: { update: { createdAt: string } } = 1098 | await new Promise(async (r) => { 1099 | const adapter = await createTestAdapter({ 1100 | config: { 1101 | supportsDates: false, 1102 | }, 1103 | adapter(args_0) { 1104 | return { 1105 | async update(data) { 1106 | r(data as any); 1107 | return data.update; 1108 | }, 1109 | }; 1110 | }, 1111 | }); 1112 | const user: { email: string; id: string } = await adapter.create({ 1113 | model: "user", 1114 | data: { email: "[email protected]" }, 1115 | }); 1116 | const res: { createdAt: Date } | null = await adapter.update({ 1117 | model: "user", 1118 | where: [{ field: "id", value: user.id }], 1119 | update: { createdAt: testDate }, 1120 | }); 1121 | expect(res).toHaveProperty("createdAt"); 1122 | expect(res?.createdAt).toBeInstanceOf(Date); 1123 | }); 1124 | expect(createDateParameters.update).toHaveProperty("createdAt"); 1125 | expect(createDateParameters.update.createdAt).toEqual( 1126 | testDate.toISOString(), 1127 | ); 1128 | }); 1129 | 1130 | test("Should allow custom transform input", async () => { 1131 | const createCustomTransformInputParameters: { 1132 | update: { name: string }; 1133 | } = await new Promise(async (r) => { 1134 | const adapter = await createTestAdapter({ 1135 | config: { 1136 | customTransformInput({ field, data }) { 1137 | if (field === "name") { 1138 | return data.toUpperCase(); 1139 | } 1140 | return data; 1141 | }, 1142 | }, 1143 | adapter(args_0) { 1144 | return { 1145 | async update(data) { 1146 | r(data as any); 1147 | return data.update; 1148 | }, 1149 | }; 1150 | }, 1151 | }); 1152 | const user: { id: string; name: string } = await adapter.create({ 1153 | model: "user", 1154 | data: { name: "test-name" }, 1155 | }); 1156 | const res: { name: string } | null = await adapter.update({ 1157 | model: "user", 1158 | where: [{ field: "id", value: user.id }], 1159 | update: { name: "test-name-2" }, 1160 | }); 1161 | expect(res).toHaveProperty("name"); 1162 | expect(res?.name).toEqual("TEST-NAME-2"); 1163 | }); 1164 | expect(createCustomTransformInputParameters.update).toHaveProperty( 1165 | "name", 1166 | ); 1167 | expect(createCustomTransformInputParameters.update.name).toEqual( 1168 | "TEST-NAME-2", 1169 | ); 1170 | }); 1171 | 1172 | test("Should allow custom transform output", async () => { 1173 | const createCustomTransformOutputParameters: { 1174 | update: { name: string }; 1175 | } = await new Promise(async (r) => { 1176 | const adapter = await createTestAdapter({ 1177 | config: { 1178 | customTransformOutput({ field, data }) { 1179 | if (field === "name") { 1180 | return data.toLowerCase(); 1181 | } 1182 | return data; 1183 | }, 1184 | }, 1185 | adapter(args_0) { 1186 | return { 1187 | async update(data) { 1188 | r(data as any); 1189 | return data.update; 1190 | }, 1191 | }; 1192 | }, 1193 | }); 1194 | const user: { id: string; name: string } = await adapter.create({ 1195 | model: "user", 1196 | data: { name: "TEST-NAME" }, 1197 | }); 1198 | const res: { name: string } | null = await adapter.update({ 1199 | model: "user", 1200 | where: [{ field: "id", value: user.id }], 1201 | update: { name: "test-name-2" }, 1202 | }); 1203 | expect(res).toHaveProperty("name"); 1204 | expect(res?.name).toEqual("test-name-2"); 1205 | }); 1206 | expect(createCustomTransformOutputParameters.update).toHaveProperty( 1207 | "name", 1208 | ); 1209 | expect(createCustomTransformOutputParameters.update.name).toEqual( 1210 | "test-name-2", 1211 | ); 1212 | }); 1213 | 1214 | test("Should allow custom transform input and output", async () => { 1215 | const createCustomTransformInputAndOutputParameters: { 1216 | update: { name: string }; 1217 | } = await new Promise(async (r) => { 1218 | const adapter = await createTestAdapter({ 1219 | config: { 1220 | customTransformInput({ field, data }) { 1221 | if (field === "name") { 1222 | return data.toUpperCase(); 1223 | } 1224 | return data; 1225 | }, 1226 | customTransformOutput({ field, data }) { 1227 | if (field === "name") { 1228 | return data.toLowerCase(); 1229 | } 1230 | return data; 1231 | }, 1232 | }, 1233 | adapter(args_0) { 1234 | return { 1235 | async update(data) { 1236 | r(data as any); 1237 | return data.update; 1238 | }, 1239 | }; 1240 | }, 1241 | }); 1242 | const user: { id: string; name: string } = await adapter.create({ 1243 | model: "user", 1244 | data: { name: "test-name" }, 1245 | }); 1246 | const res: { name: string } | null = await adapter.update({ 1247 | model: "user", 1248 | where: [{ field: "id", value: user.id }], 1249 | update: { name: "test-name-2" }, 1250 | }); 1251 | expect(res).toHaveProperty("name"); 1252 | expect(res?.name).toEqual("test-name-2"); 1253 | }); 1254 | expect( 1255 | createCustomTransformInputAndOutputParameters.update, 1256 | ).toHaveProperty("name"); 1257 | expect( 1258 | createCustomTransformInputAndOutputParameters.update.name, 1259 | ).toEqual("test-name-2".toUpperCase()); 1260 | }); 1261 | 1262 | test("Should allow custom map input key transformation", async () => { 1263 | const parameters: { 1264 | update: { email_address: string }; 1265 | } = await new Promise(async (r) => { 1266 | const adapter = await createTestAdapter({ 1267 | config: { 1268 | debugLogs: {}, 1269 | mapKeysTransformInput: { 1270 | email: "email_address", 1271 | }, 1272 | }, 1273 | adapter(args_0) { 1274 | return { 1275 | async update(data) { 1276 | r(data as any); 1277 | return data.update; 1278 | }, 1279 | }; 1280 | }, 1281 | }); 1282 | const user = (await adapter.create({ 1283 | model: "user", 1284 | data: { email: "[email protected]" }, 1285 | })) as { email: string; id: string }; 1286 | 1287 | const res: { email: string } | null = await adapter.update({ 1288 | model: "user", 1289 | update: { email: "[email protected]" }, 1290 | where: [{ field: "id", value: user.id }], 1291 | }); 1292 | 1293 | expect(res).toHaveProperty("email"); 1294 | expect(res).not.toHaveProperty("email_address"); 1295 | expect(res?.email).toEqual(undefined); // The reason it's undefined is because we did transform `email` to `email_address`, however we never transformed `email_address` back to `email`. 1296 | }); 1297 | expect(parameters.update).toHaveProperty("email_address"); 1298 | expect(parameters.update.email_address).toEqual("[email protected]"); 1299 | }); 1300 | 1301 | test("Should allow custom map output key transformation", async () => { 1302 | const parameters: { 1303 | update: { email: string }; 1304 | } = await new Promise(async (r) => { 1305 | const adapter = await createTestAdapter({ 1306 | config: { 1307 | debugLogs: {}, 1308 | mapKeysTransformOutput: { 1309 | email: "email_address", 1310 | }, 1311 | }, 1312 | adapter(args_0) { 1313 | return { 1314 | async update(data) { 1315 | r(data as any); 1316 | return data.update; 1317 | }, 1318 | }; 1319 | }, 1320 | }); 1321 | const user = (await adapter.create({ 1322 | model: "user", 1323 | data: { email: "[email protected]" }, 1324 | })) as { email: string; id: string }; 1325 | 1326 | const res: { email_address: string } | null = await adapter.update({ 1327 | model: "user", 1328 | update: { email: "[email protected]" }, 1329 | where: [{ field: "id", value: user.id }], 1330 | }); 1331 | 1332 | expect(res).toHaveProperty("email_address"); 1333 | expect(res).not.toHaveProperty("email"); 1334 | expect(res?.email_address).toEqual("[email protected]"); 1335 | }); 1336 | expect(parameters.update).toHaveProperty("email"); 1337 | expect(parameters.update).not.toHaveProperty("email_address"); 1338 | expect(parameters.update.email).toEqual("[email protected]"); 1339 | }); 1340 | 1341 | test("Should allow custom map input and output key transformation", async () => { 1342 | const parameters: { 1343 | update: { email_address: string }; 1344 | } = await new Promise(async (r) => { 1345 | const adapter = await createTestAdapter({ 1346 | config: { 1347 | debugLogs: {}, 1348 | mapKeysTransformInput: { 1349 | email: "email_address", 1350 | }, 1351 | mapKeysTransformOutput: { 1352 | email_address: "email", 1353 | }, 1354 | }, 1355 | adapter(args_0) { 1356 | return { 1357 | async update(data) { 1358 | r(data as any); 1359 | return data.update; 1360 | }, 1361 | }; 1362 | }, 1363 | }); 1364 | const user = (await adapter.create({ 1365 | model: "user", 1366 | data: { email: "[email protected]" }, 1367 | })) as { email: string; id: string }; 1368 | 1369 | const res: { email: string } | null = await adapter.update({ 1370 | model: "user", 1371 | update: { email: "[email protected]" }, 1372 | where: [{ field: "id", value: user.id }], 1373 | }); 1374 | 1375 | expect(res).toHaveProperty("email"); 1376 | expect(res).not.toHaveProperty("email_address"); 1377 | expect(res?.email).toEqual("[email protected]"); 1378 | }); 1379 | expect(parameters.update).toHaveProperty("email_address"); 1380 | expect(parameters.update).not.toHaveProperty("email"); 1381 | expect(parameters.update.email_address).toEqual("[email protected]"); 1382 | }); 1383 | 1384 | test("Should expect the fields to be transformed into the correct field names if customized", async () => { 1385 | const parameters: { 1386 | update: { email_address: string }; 1387 | } = await new Promise(async (r) => { 1388 | const adapter = await createTestAdapter({ 1389 | config: { 1390 | debugLogs: {}, 1391 | }, 1392 | options: { 1393 | user: { 1394 | fields: { 1395 | email: "email_address", 1396 | }, 1397 | }, 1398 | }, 1399 | adapter(args_0) { 1400 | return { 1401 | async update(data) { 1402 | r(data as any); 1403 | return data.update; 1404 | }, 1405 | }; 1406 | }, 1407 | }); 1408 | const user: { id: string; email: string } = await adapter.create({ 1409 | model: "user", 1410 | data: { email: "[email protected]" }, 1411 | }); 1412 | const res: { email: string } | null = await adapter.update({ 1413 | model: "user", 1414 | update: { email: "[email protected]" }, 1415 | where: [{ field: "id", value: user.id }], 1416 | }); 1417 | expect(res).toHaveProperty("email"); 1418 | expect(res).not.toHaveProperty("email_address"); 1419 | expect(res?.email).toEqual("[email protected]"); 1420 | }); 1421 | expect(parameters.update).toHaveProperty("email_address"); 1422 | expect(parameters.update).not.toHaveProperty("email"); 1423 | expect(parameters.update.email_address).toEqual("[email protected]"); 1424 | }); 1425 | 1426 | test("Should expect not to receive an id even if disableIdGeneration is false in an update call", async () => { 1427 | const parameters: { 1428 | update: { id: string }; 1429 | } = await new Promise(async (r) => { 1430 | const adapter = await createTestAdapter({ 1431 | config: { 1432 | disableIdGeneration: true, 1433 | }, 1434 | adapter(args_0) { 1435 | return { 1436 | async update(data) { 1437 | r(data as any); 1438 | return data.update; 1439 | }, 1440 | }; 1441 | }, 1442 | }); 1443 | const user: { email: string; id: string } = await adapter.create({ 1444 | model: "user", 1445 | data: { email: "[email protected]" }, 1446 | }); 1447 | await adapter.update({ 1448 | model: "user", 1449 | update: { email: "[email protected]" }, 1450 | where: [{ field: "id", value: user.id }], 1451 | }); 1452 | }); 1453 | expect(parameters.update).not.toHaveProperty("id"); 1454 | }); 1455 | }); 1456 | 1457 | describe("find", () => { 1458 | test("findOne: Should transform the where clause according to the schema", async () => { 1459 | const parameters: { where: Where[]; model: string; select?: string[] } = 1460 | await new Promise(async (r) => { 1461 | const adapter = await createTestAdapter({ 1462 | options: { 1463 | user: { 1464 | fields: { 1465 | email: "email_address", 1466 | }, 1467 | }, 1468 | }, 1469 | adapter(args_0) { 1470 | return { 1471 | async findOne({ model, where, select }) { 1472 | const fakeResult: Omit<User, "email"> & { 1473 | email_address: string; 1474 | } = { 1475 | id: "random-id-oudwduwbdouwbdu123b", 1476 | email_address: "[email protected]", 1477 | emailVerified: false, 1478 | createdAt: new Date(), 1479 | updatedAt: new Date(), 1480 | name: "test-name", 1481 | }; 1482 | r({ model, where, select }); 1483 | return fakeResult as any; 1484 | }, 1485 | }; 1486 | }, 1487 | }); 1488 | const res = await adapter.findOne<User>({ 1489 | model: "user", 1490 | where: [{ field: "email", value: "[email protected]" }], 1491 | }); 1492 | expect(res).not.toHaveProperty("email_address"); 1493 | expect(res).toHaveProperty("email"); 1494 | expect(res?.email).toEqual("[email protected]"); 1495 | }); 1496 | expect(parameters.where[0]!.field).toEqual("email_address"); 1497 | }); 1498 | test("findMany: Should transform the where clause according to the schema", async () => { 1499 | const parameters: { where: Where[] | undefined; model: string } = 1500 | await new Promise(async (r) => { 1501 | const adapter = await createTestAdapter({ 1502 | options: { 1503 | user: { 1504 | fields: { 1505 | email: "email_address", 1506 | }, 1507 | }, 1508 | }, 1509 | adapter(args_0) { 1510 | return { 1511 | async findMany({ model, where }) { 1512 | const fakeResult: (Omit<User, "email"> & { 1513 | email_address: string; 1514 | })[] = [ 1515 | { 1516 | id: "random-id-eio1d1u12h33123ed", 1517 | email_address: "[email protected]", 1518 | emailVerified: false, 1519 | createdAt: new Date(), 1520 | updatedAt: new Date(), 1521 | name: "test-name", 1522 | }, 1523 | ]; 1524 | r({ model, where }); 1525 | return fakeResult as any; 1526 | }, 1527 | }; 1528 | }, 1529 | }); 1530 | const res = await adapter.findMany<User>({ 1531 | model: "user", 1532 | where: [{ field: "email", value: "[email protected]" }], 1533 | }); 1534 | expect(res[0]).not.toHaveProperty("email_address"); 1535 | expect(res[0]).toHaveProperty("email"); 1536 | expect(res[0]?.email).toEqual("[email protected]"); 1537 | }); 1538 | expect(parameters.where?.[0]!.field).toEqual("email_address"); 1539 | }); 1540 | 1541 | test("findOne: Should receive an integer id in where clause if the user has enabled `useNumberId`", async () => { 1542 | const parameters: { where: Where[]; model: string; select?: string[] } = 1543 | await new Promise(async (r) => { 1544 | const adapter = await createTestAdapter({ 1545 | options: { 1546 | advanced: { 1547 | database: { 1548 | useNumberId: true, 1549 | }, 1550 | }, 1551 | }, 1552 | adapter(args_0) { 1553 | return { 1554 | async findOne({ model, where, select }) { 1555 | const fakeResult: Omit<User, "id"> & { id: number } = { 1556 | id: 1, 1557 | email: "[email protected]", 1558 | emailVerified: false, 1559 | createdAt: new Date(), 1560 | updatedAt: new Date(), 1561 | name: "test-name", 1562 | }; 1563 | r({ model, where, select }); 1564 | return fakeResult as any; 1565 | }, 1566 | }; 1567 | }, 1568 | }); 1569 | const res = await adapter.findOne<User>({ 1570 | model: "user", 1571 | where: [{ field: "id", value: "1" }], 1572 | }); 1573 | 1574 | expect(res).toHaveProperty("id"); 1575 | expect(res?.id).toEqual("1"); 1576 | }); 1577 | // The where clause should convert the string id value of `"1"` to an int since `useNumberId` is true 1578 | expect(parameters.where[0]!.value).toEqual(1); 1579 | }); 1580 | test("findMany: Should receive an integer id in where clause if the user has enabled `useNumberId`", async () => { 1581 | const parameters: { where: Where[] | undefined; model: string } = 1582 | await new Promise(async (r) => { 1583 | const adapter = await createTestAdapter({ 1584 | options: { 1585 | advanced: { 1586 | database: { 1587 | useNumberId: true, 1588 | }, 1589 | }, 1590 | }, 1591 | adapter(args_0) { 1592 | return { 1593 | async findMany({ model, where }) { 1594 | const fakeResult: (Omit<User, "id"> & { id: number })[] = [ 1595 | { 1596 | id: 1, 1597 | email: "[email protected]", 1598 | emailVerified: false, 1599 | createdAt: new Date(), 1600 | updatedAt: new Date(), 1601 | name: "test-name", 1602 | }, 1603 | ]; 1604 | r({ model, where }); 1605 | return fakeResult as any; 1606 | }, 1607 | }; 1608 | }, 1609 | }); 1610 | const res = await adapter.findMany<User>({ 1611 | model: "user", 1612 | where: [{ field: "id", value: "1" }], 1613 | }); 1614 | 1615 | expect(res[0]).toHaveProperty("id"); 1616 | expect(res[0]!.id).toEqual("1"); 1617 | }); 1618 | // The where clause should convert the string id value of `"1"` to an int since `useNumberId` is true 1619 | expect(parameters.where?.[0]!.value).toEqual(1); 1620 | }); 1621 | }); 1622 | }); 1623 | }); 1624 | ```