feat: infered types should be avaialble from both client and server

This commit is contained in:
Bereket Engida
2024-09-12 10:15:26 +03:00
parent a1fab3a54c
commit 6916d33243
12 changed files with 124 additions and 70 deletions

View File

@@ -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>
);
}

View File

@@ -19,7 +19,7 @@ export const metadata = createMetadata({
metadataBase: baseUrl,
});
const hideNavbar = true
export default function Layout({ children }: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
@@ -28,19 +28,10 @@ export default function Layout({ children }: { children: ReactNode }) {
</head>
<body className={`${GeistSans.variable} ${GeistMono.variable} font-sans`}>
<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 ">
{/* Radial gradient for the container to give a faded look */}
<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>
<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>
<NavbarProvider>
<Navbar />
{children}
</NavbarProvider>
</RootProvider>
</body>
</html>

View File

@@ -1,21 +1,20 @@
import Section from "@/components/landing/section";
import Hero from "@/components/landing/hero";
import { Button } from "@/components/ui/button";
import { BookOpen } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
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"
export default function Home() {
return (
<div className="min-h-[90vh] flex items-center justify-center">
<main className="flex flex-col gap-8 row-start-2 items-center justify-center">
<div className="flex flex-col gap-1">
<Hero />
</div>
</main>
</div>
);
>
<Hero />
<Features />
</Section>
</main>
);
}

View File

@@ -5,6 +5,9 @@ import { NavbarMobileBtn } from "./nav-mobile";
import { NavLink } from "./nav-link";
import { Logo } from "./logo";
const hideNavbar = process.env.NODE_ENV === "production"
export const Navbar = () => {
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">
@@ -19,11 +22,13 @@ export const Navbar = () => {
</Link>
<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">
{navMenu.map((menu, i) => (
<NavLink key={menu.name} href={menu.path}>
{menu.name}
</NavLink>
))}
{
hideNavbar ? null : navMenu.map((menu, i) => (
<NavLink key={menu.name} href={menu.path}>
{menu.name}
</NavLink>
))
}
</ul>
<ThemeToggle />
<NavbarMobileBtn />

View File

@@ -14,6 +14,7 @@ export const getCSRFToken = createAuthEndpoint(
ctx.context.authCookies.csrfToken.name,
ctx.context.secret,
);
if (csrfToken) {
return {
csrfToken,

View File

@@ -1,7 +1,8 @@
import type { Endpoint } from "better-call";
import type { Endpoint, Prettify } from "better-call";
import { getEndpoints, router } from "./api";
import { init } from "./init";
import type { BetterAuthOptions } from "./types/options";
import type { InferSession, InferUser } from "./types";
type InferAPI<API> = Omit<
API,
@@ -38,6 +39,10 @@ export const betterAuth = <O extends BetterAuthOptions>(options: O) => {
},
api: api as InferAPI<typeof api>,
options: authContext.options as O,
$infer: {} as {
session: Prettify<InferSession<O>>;
user: Prettify<InferUser<O>>;
},
};
};

View File

@@ -165,4 +165,32 @@ describe("type", () => {
expectTypeOf(client.setTestAtom).toEqualTypeOf<(value: boolean) => 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;
}>();
});
});

View File

@@ -4,6 +4,7 @@ import { type Atom } from "nanostores";
import type { AtomListener, ClientOptions } from "./types";
import { addCurrentURL, csrfPlugin, redirectPlugin } from "./fetch-plugins";
import type { InferSession } from "../types";
export const getClientConfig = <O extends ClientOptions>(options?: O) => {
const $fetch = createFetch({

View File

@@ -1,17 +1,28 @@
import type { BetterFetch } from "@better-fetch/fetch";
import { atom } from "nanostores";
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 { AuthClientPlugin, ClientOptions } from "./types";
import { useAuthQuery } from "./query";
import type { BetterAuthPlugin } from "../plugins";
export function getSessionAtom<Option extends ClientOptions>(
client: BetterFetch,
) {
type Plugins = Option["plugins"] extends Array<AuthClientPlugin>
? Array<Option["plugins"][number]["$InferServerPlugin"]>
: undefined;
? Array<
UnionToIntersection<
Option["plugins"] extends Array<infer Pl>
? Pl extends AuthClientPlugin
? Pl["$InferServerPlugin"] extends BetterAuthPlugin
? Pl["$InferServerPlugin"]
: never
: never
: never
>
>
: never;
type Auth = {
handler: any;
@@ -22,10 +33,8 @@ export function getSessionAtom<Option extends ClientOptions>(
};
};
type UserWithAdditionalFields = InferUser<
Auth extends BetterAuth ? Auth : never
>;
//@ts-expect-error
type UserWithAdditionalFields = InferUser<Auth["options"]>;
//@ts-expect-error
type SessionWithAdditionalFields = InferSession<Auth["options"]>;
const $signal = atom<boolean>(false);
@@ -35,5 +44,13 @@ export function getSessionAtom<Option extends ClientOptions>(
}>($signal, "/session", client, {
method: "GET",
});
return { $session: session, _sessionSignal: $signal };
return {
$session: session,
_sessionSignal: $signal,
$infer: {} as {
session: Prettify<SessionWithAdditionalFields>;
user: Prettify<UserWithAdditionalFields>;
s: Plugins;
},
};
}

View File

@@ -49,7 +49,7 @@ export function createAuthClient<Option extends ClientOptions>(
for (const [key, value] of Object.entries(pluginsAtoms)) {
resolvedHooks[getAtomKey(key)] = () => useStore(value);
}
const { $session, _sessionSignal } = getSessionAtom<Option>($fetch);
const { $session, _sessionSignal, $infer } = getSessionAtom<Option>($fetch);
function useSession() {
return useStore($session);
@@ -73,5 +73,7 @@ export function createAuthClient<Option extends ClientOptions>(
InferClientAPI<Option> &
InferActions<Option> & {
useSession: typeof useSession;
} & {
$infer: typeof $infer;
};
}

View File

@@ -8,6 +8,7 @@ import type { Atom } from "nanostores";
import type { LiteralString, UnionToIntersection } from "../types/helper";
import type { Auth } from "../auth";
import type { InferRoutes } from "./path-to-object";
import type { InferSession, InferUser } from "../types";
export type AtomListener = {
matcher: (path: string) => boolean;
@@ -86,3 +87,25 @@ export type InferActions<O extends ClientOptions> = O["plugins"] extends Array<
* convention they start with "_"
*/
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
>;

View File

@@ -1,16 +1,18 @@
## TODO
[ ] handle migration when the config removes existing schema
[x] handle migration when the config removes existing schema
[x] refresh oauth tokens
[x] remember me functionality
[x] add all oauth providers
[x] providers should only be oauth
[x] add tests
[ ] add callback url on otp and backup code verification
[x] implement the ac check on the client to for organization
[x] add delete organization endpoint
[ ] add callback url on otp and backup code verification
[ ] fix bun problem
[ ] allow enabling two factor automatically for users
[ ] change the pg driver to https://www.npmjs.com/package/postgres (maybe)
## 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