mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-10 04:19:32 +00:00
feat: infered types should be avaialble from both client and server
This commit is contained in:
@@ -1,20 +0,0 @@
|
|||||||
import Section from "@/components/landing/section";
|
|
||||||
import Hero from "@/components/landing/hero";
|
|
||||||
import Features from "@/components/features";
|
|
||||||
export default function HomePage() {
|
|
||||||
return (
|
|
||||||
<main className="h-min">
|
|
||||||
<Section
|
|
||||||
className="-z-1 mb-1"
|
|
||||||
crosses
|
|
||||||
crossesOffset="lg:translate-y-[5.25rem]"
|
|
||||||
customPaddings
|
|
||||||
id="hero"
|
|
||||||
|
|
||||||
>
|
|
||||||
<Hero />
|
|
||||||
<Features />
|
|
||||||
</Section>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ export const metadata = createMetadata({
|
|||||||
metadataBase: baseUrl,
|
metadataBase: baseUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
const hideNavbar = true
|
|
||||||
export default function Layout({ children }: { children: ReactNode }) {
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
@@ -28,19 +28,10 @@ export default function Layout({ children }: { children: ReactNode }) {
|
|||||||
</head>
|
</head>
|
||||||
<body className={`${GeistSans.variable} ${GeistMono.variable} font-sans`}>
|
<body className={`${GeistSans.variable} ${GeistMono.variable} font-sans`}>
|
||||||
<RootProvider>
|
<RootProvider>
|
||||||
<div className="min-h-screen w-full dark:bg-black bg-white dark:bg-grid-small-white/[0.2] bg-grid-small-black/[0.2] relative flex justify-center ">
|
<NavbarProvider>
|
||||||
|
<Navbar />
|
||||||
{/* Radial gradient for the container to give a faded look */}
|
{children}
|
||||||
<div className="absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-black bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)]"></div>
|
</NavbarProvider>
|
||||||
<div className=" bg-white dark:bg-black border-b py-2 flex justify-between items-center px-4 border-border absolute z-50 w-1/2">
|
|
||||||
</div>
|
|
||||||
<NavbarProvider>
|
|
||||||
{
|
|
||||||
hideNavbar ? null : <Navbar />
|
|
||||||
}
|
|
||||||
{children}
|
|
||||||
</NavbarProvider>
|
|
||||||
</div>
|
|
||||||
</RootProvider>
|
</RootProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
|
import Section from "@/components/landing/section";
|
||||||
import Hero from "@/components/landing/hero";
|
import Hero from "@/components/landing/hero";
|
||||||
import { Button } from "@/components/ui/button";
|
import Features from "@/components/features";
|
||||||
import { BookOpen } from "lucide-react";
|
export default function HomePage() {
|
||||||
import Image from "next/image";
|
return (
|
||||||
import Link from "next/link";
|
<main className="h-min">
|
||||||
|
<Section
|
||||||
|
className="-z-1 mb-1"
|
||||||
|
crosses
|
||||||
|
crossesOffset="lg:translate-y-[5.25rem]"
|
||||||
|
customPaddings
|
||||||
|
id="hero"
|
||||||
|
|
||||||
export default function Home() {
|
>
|
||||||
return (
|
<Hero />
|
||||||
<div className="min-h-[90vh] flex items-center justify-center">
|
<Features />
|
||||||
<main className="flex flex-col gap-8 row-start-2 items-center justify-center">
|
</Section>
|
||||||
<div className="flex flex-col gap-1">
|
</main>
|
||||||
|
);
|
||||||
<Hero />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import { NavbarMobileBtn } from "./nav-mobile";
|
|||||||
import { NavLink } from "./nav-link";
|
import { NavLink } from "./nav-link";
|
||||||
import { Logo } from "./logo";
|
import { Logo } from "./logo";
|
||||||
|
|
||||||
|
|
||||||
|
const hideNavbar = process.env.NODE_ENV === "production"
|
||||||
|
|
||||||
export const Navbar = () => {
|
export const Navbar = () => {
|
||||||
return (
|
return (
|
||||||
<nav className="md:grid grid-cols-12 border-b sticky top-0 flex items-center justify-end bg-background backdrop-blur-md z-50">
|
<nav className="md:grid grid-cols-12 border-b sticky top-0 flex items-center justify-end bg-background backdrop-blur-md z-50">
|
||||||
@@ -19,11 +22,13 @@ export const Navbar = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
<div className="md:col-span-9 lg:col-span-10 flex items-center justify-end ">
|
<div className="md:col-span-9 lg:col-span-10 flex items-center justify-end ">
|
||||||
<ul className="md:flex items-center divide-x w-max border-r hidden shrink-0">
|
<ul className="md:flex items-center divide-x w-max border-r hidden shrink-0">
|
||||||
{navMenu.map((menu, i) => (
|
{
|
||||||
<NavLink key={menu.name} href={menu.path}>
|
hideNavbar ? null : navMenu.map((menu, i) => (
|
||||||
{menu.name}
|
<NavLink key={menu.name} href={menu.path}>
|
||||||
</NavLink>
|
{menu.name}
|
||||||
))}
|
</NavLink>
|
||||||
|
))
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<NavbarMobileBtn />
|
<NavbarMobileBtn />
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const getCSRFToken = createAuthEndpoint(
|
|||||||
ctx.context.authCookies.csrfToken.name,
|
ctx.context.authCookies.csrfToken.name,
|
||||||
ctx.context.secret,
|
ctx.context.secret,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (csrfToken) {
|
if (csrfToken) {
|
||||||
return {
|
return {
|
||||||
csrfToken,
|
csrfToken,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { Endpoint } from "better-call";
|
import type { Endpoint, Prettify } from "better-call";
|
||||||
import { getEndpoints, router } from "./api";
|
import { getEndpoints, router } from "./api";
|
||||||
import { init } from "./init";
|
import { init } from "./init";
|
||||||
import type { BetterAuthOptions } from "./types/options";
|
import type { BetterAuthOptions } from "./types/options";
|
||||||
|
import type { InferSession, InferUser } from "./types";
|
||||||
|
|
||||||
type InferAPI<API> = Omit<
|
type InferAPI<API> = Omit<
|
||||||
API,
|
API,
|
||||||
@@ -38,6 +39,10 @@ export const betterAuth = <O extends BetterAuthOptions>(options: O) => {
|
|||||||
},
|
},
|
||||||
api: api as InferAPI<typeof api>,
|
api: api as InferAPI<typeof api>,
|
||||||
options: authContext.options as O,
|
options: authContext.options as O,
|
||||||
|
$infer: {} as {
|
||||||
|
session: Prettify<InferSession<O>>;
|
||||||
|
user: Prettify<InferUser<O>>;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -165,4 +165,32 @@ describe("type", () => {
|
|||||||
expectTypeOf(client.setTestAtom).toEqualTypeOf<(value: boolean) => void>();
|
expectTypeOf(client.setTestAtom).toEqualTypeOf<(value: boolean) => void>();
|
||||||
expectTypeOf(client.test.signOut).toEqualTypeOf<() => Promise<void>>();
|
expectTypeOf(client.test.signOut).toEqualTypeOf<() => Promise<void>>();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should infer session", () => {
|
||||||
|
const client = createSolidClient({
|
||||||
|
plugins: [testClientPlugin(), testClientPlugin2()],
|
||||||
|
baseURL: "http://localhost:3000",
|
||||||
|
});
|
||||||
|
client.$infer.s;
|
||||||
|
const $infer = client.$infer;
|
||||||
|
expectTypeOf($infer.session).toEqualTypeOf<{
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
expiresAt: Date;
|
||||||
|
ipAddress?: string | undefined;
|
||||||
|
userAgent?: string | undefined;
|
||||||
|
}>();
|
||||||
|
expectTypeOf($infer.user).toEqualTypeOf<{
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
emailVerified: boolean;
|
||||||
|
name: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
image?: string | undefined;
|
||||||
|
testField?: string | undefined;
|
||||||
|
testField2?: number | undefined;
|
||||||
|
testField4: string;
|
||||||
|
}>();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { type Atom } from "nanostores";
|
|||||||
import type { AtomListener, ClientOptions } from "./types";
|
import type { AtomListener, ClientOptions } from "./types";
|
||||||
|
|
||||||
import { addCurrentURL, csrfPlugin, redirectPlugin } from "./fetch-plugins";
|
import { addCurrentURL, csrfPlugin, redirectPlugin } from "./fetch-plugins";
|
||||||
|
import type { InferSession } from "../types";
|
||||||
|
|
||||||
export const getClientConfig = <O extends ClientOptions>(options?: O) => {
|
export const getClientConfig = <O extends ClientOptions>(options?: O) => {
|
||||||
const $fetch = createFetch({
|
const $fetch = createFetch({
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
import type { BetterFetch } from "@better-fetch/fetch";
|
import type { BetterFetch } from "@better-fetch/fetch";
|
||||||
import { atom } from "nanostores";
|
import { atom } from "nanostores";
|
||||||
import type { Auth as BetterAuth } from "../auth";
|
import type { Auth as BetterAuth } from "../auth";
|
||||||
import type { Prettify } from "../types/helper";
|
import type { Prettify, UnionToIntersection } from "../types/helper";
|
||||||
import type { InferSession, InferUser } from "../types/models";
|
import type { InferSession, InferUser } from "../types/models";
|
||||||
import type { AuthClientPlugin, ClientOptions } from "./types";
|
import type { AuthClientPlugin, ClientOptions } from "./types";
|
||||||
import { useAuthQuery } from "./query";
|
import { useAuthQuery } from "./query";
|
||||||
|
import type { BetterAuthPlugin } from "../plugins";
|
||||||
|
|
||||||
export function getSessionAtom<Option extends ClientOptions>(
|
export function getSessionAtom<Option extends ClientOptions>(
|
||||||
client: BetterFetch,
|
client: BetterFetch,
|
||||||
) {
|
) {
|
||||||
type Plugins = Option["plugins"] extends Array<AuthClientPlugin>
|
type Plugins = Option["plugins"] extends Array<AuthClientPlugin>
|
||||||
? Array<Option["plugins"][number]["$InferServerPlugin"]>
|
? Array<
|
||||||
: undefined;
|
UnionToIntersection<
|
||||||
|
Option["plugins"] extends Array<infer Pl>
|
||||||
|
? Pl extends AuthClientPlugin
|
||||||
|
? Pl["$InferServerPlugin"] extends BetterAuthPlugin
|
||||||
|
? Pl["$InferServerPlugin"]
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
>
|
||||||
|
>
|
||||||
|
: never;
|
||||||
|
|
||||||
type Auth = {
|
type Auth = {
|
||||||
handler: any;
|
handler: any;
|
||||||
@@ -22,10 +33,8 @@ export function getSessionAtom<Option extends ClientOptions>(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type UserWithAdditionalFields = InferUser<
|
//@ts-expect-error
|
||||||
Auth extends BetterAuth ? Auth : never
|
type UserWithAdditionalFields = InferUser<Auth["options"]>;
|
||||||
>;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
type SessionWithAdditionalFields = InferSession<Auth["options"]>;
|
type SessionWithAdditionalFields = InferSession<Auth["options"]>;
|
||||||
const $signal = atom<boolean>(false);
|
const $signal = atom<boolean>(false);
|
||||||
@@ -35,5 +44,13 @@ export function getSessionAtom<Option extends ClientOptions>(
|
|||||||
}>($signal, "/session", client, {
|
}>($signal, "/session", client, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
return { $session: session, _sessionSignal: $signal };
|
return {
|
||||||
|
$session: session,
|
||||||
|
_sessionSignal: $signal,
|
||||||
|
$infer: {} as {
|
||||||
|
session: Prettify<SessionWithAdditionalFields>;
|
||||||
|
user: Prettify<UserWithAdditionalFields>;
|
||||||
|
s: Plugins;
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function createAuthClient<Option extends ClientOptions>(
|
|||||||
for (const [key, value] of Object.entries(pluginsAtoms)) {
|
for (const [key, value] of Object.entries(pluginsAtoms)) {
|
||||||
resolvedHooks[getAtomKey(key)] = () => useStore(value);
|
resolvedHooks[getAtomKey(key)] = () => useStore(value);
|
||||||
}
|
}
|
||||||
const { $session, _sessionSignal } = getSessionAtom<Option>($fetch);
|
const { $session, _sessionSignal, $infer } = getSessionAtom<Option>($fetch);
|
||||||
|
|
||||||
function useSession() {
|
function useSession() {
|
||||||
return useStore($session);
|
return useStore($session);
|
||||||
@@ -73,5 +73,7 @@ export function createAuthClient<Option extends ClientOptions>(
|
|||||||
InferClientAPI<Option> &
|
InferClientAPI<Option> &
|
||||||
InferActions<Option> & {
|
InferActions<Option> & {
|
||||||
useSession: typeof useSession;
|
useSession: typeof useSession;
|
||||||
|
} & {
|
||||||
|
$infer: typeof $infer;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { Atom } from "nanostores";
|
|||||||
import type { LiteralString, UnionToIntersection } from "../types/helper";
|
import type { LiteralString, UnionToIntersection } from "../types/helper";
|
||||||
import type { Auth } from "../auth";
|
import type { Auth } from "../auth";
|
||||||
import type { InferRoutes } from "./path-to-object";
|
import type { InferRoutes } from "./path-to-object";
|
||||||
|
import type { InferSession, InferUser } from "../types";
|
||||||
|
|
||||||
export type AtomListener = {
|
export type AtomListener = {
|
||||||
matcher: (path: string) => boolean;
|
matcher: (path: string) => boolean;
|
||||||
@@ -86,3 +87,25 @@ export type InferActions<O extends ClientOptions> = O["plugins"] extends Array<
|
|||||||
* convention they start with "_"
|
* convention they start with "_"
|
||||||
*/
|
*/
|
||||||
export type IsSignal<T> = T extends `_${infer _}` ? true : false;
|
export type IsSignal<T> = T extends `_${infer _}` ? true : false;
|
||||||
|
|
||||||
|
export type InferPluginsFromClient<O extends ClientOptions> =
|
||||||
|
O["plugins"] extends Array<AuthClientPlugin>
|
||||||
|
? Array<O["plugins"][number]["$InferServerPlugin"]>
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
type InferAuthFromClient<O extends ClientOptions> = {
|
||||||
|
handler: any;
|
||||||
|
api: any;
|
||||||
|
options: {
|
||||||
|
database: any;
|
||||||
|
plugins: InferPluginsFromClient<O>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type InferSessionFromClient<O extends ClientOptions> = InferSession<
|
||||||
|
InferAuthFromClient<O> extends Auth ? InferAuthFromClient<O> : never
|
||||||
|
>;
|
||||||
|
|
||||||
|
type InferUserFromClient<O extends ClientOptions> = InferUser<
|
||||||
|
InferAuthFromClient<O> extends Auth ? InferAuthFromClient<O> : never
|
||||||
|
>;
|
||||||
|
|||||||
8
todo.md
8
todo.md
@@ -1,16 +1,18 @@
|
|||||||
## TODO
|
## TODO
|
||||||
[ ] handle migration when the config removes existing schema
|
[x] handle migration when the config removes existing schema
|
||||||
[x] refresh oauth tokens
|
[x] refresh oauth tokens
|
||||||
[x] remember me functionality
|
[x] remember me functionality
|
||||||
[x] add all oauth providers
|
[x] add all oauth providers
|
||||||
[x] providers should only be oauth
|
[x] providers should only be oauth
|
||||||
[x] add tests
|
[x] add tests
|
||||||
[ ] add callback url on otp and backup code verification
|
|
||||||
[x] implement the ac check on the client to for organization
|
[x] implement the ac check on the client to for organization
|
||||||
[x] add delete organization endpoint
|
[x] add delete organization endpoint
|
||||||
|
[ ] add callback url on otp and backup code verification
|
||||||
[ ] fix bun problem
|
[ ] fix bun problem
|
||||||
[ ] allow enabling two factor automatically for users
|
[ ] allow enabling two factor automatically for users
|
||||||
|
[ ] change the pg driver to https://www.npmjs.com/package/postgres (maybe)
|
||||||
|
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
[ ] specify everywhere `auth` should be exported
|
[x] specify everywhere `auth` should be exported
|
||||||
|
[ ] add a note about better-sqlite3 requiring to be added to webpack externals or find alternative that doesn't require it
|
||||||
|
|||||||
Reference in New Issue
Block a user