diff --git a/.env.example b/.env.example
index eba6428..0c7804a 100644
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,4 @@
DATABASE_URL=postgres://postgres:mypassword@db-host:5432/databasename
-DATABASE_URL_LOCAL=postgres://postgres:mypassword@localhost:5432/databasename
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
@@ -11,11 +10,3 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
-
-VITE_WS_URL=
-
-UID=1000
-GID=1000
-
-RESEND_API_KEY=
-EMAIL_FROM=mail@example.com
diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml
index dc34ae3..a48cee1 100644
--- a/.forgejo/workflows/deploy.yml
+++ b/.forgejo/workflows/deploy.yml
@@ -5,31 +5,8 @@ on:
branches: [main]
jobs:
- quality:
- runs-on: docker
- steps:
- - name: Install tools
- run: apt-get update && apt-get install -y nodejs
- - name: Checkout code
- uses: https://data.forgejo.org/actions/checkout@v4
- - name: Install pnpm
- run: npm install -g pnpm
- - name: Install dependencies
- run: pnpm install
- - name: Build shared packages
- run: pnpm --filter @lila/shared build && pnpm --filter @lila/db build
- - name: Format check
- run: pnpm format:check
- - name: Lint
- run: pnpm lint
- - name: Type-check
- run: pnpm typecheck
- - name: Test
- run: pnpm test:run
-
build-and-deploy:
runs-on: docker
- needs: quality
steps:
- name: Install tools
run: apt-get update && apt-get install -y docker.io openssh-client
diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100755
index cb2c84d..0000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1 +0,0 @@
-pnpm lint-staged
diff --git a/.husky/pre-push b/.husky/pre-push
deleted file mode 100755
index 6c45485..0000000
--- a/.husky/pre-push
+++ /dev/null
@@ -1 +0,0 @@
-pnpm test:run
diff --git a/.prettierignore b/.prettierignore
index 773c5d1..9699559 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -18,5 +18,3 @@ coverage/
pnpm-lock.yaml
routeTree.gen.ts
-
-.pnpm-store/
diff --git a/apps/api/package.json b/apps/api/package.json
index 1a7cdc3..870a77d 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -7,8 +7,7 @@
"dev": "pnpm --filter shared build && pnpm --filter db build && tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/src/server.js",
- "test": "vitest",
- "typecheck": "tsc --noEmit"
+ "test": "vitest"
},
"dependencies": {
"@lila/db": "workspace:*",
@@ -18,7 +17,6 @@
"express": "^5.2.1",
"express-rate-limit": "^8.4.0",
"helmet": "^8.1.0",
- "resend": "^6.12.2",
"ws": "^8.20.0"
},
"devDependencies": {
diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts
index ad9f509..47c51e6 100644
--- a/apps/api/src/app.ts
+++ b/apps/api/src/app.ts
@@ -27,7 +27,7 @@ export function createApp() {
const store = new InMemoryGameSessionStore();
app.use("/api/v1", createApiRouter(store));
-
+
app.use(errorHandler);
return app;
diff --git a/apps/api/src/controllers/gameController.test.ts b/apps/api/src/controllers/gameController.test.ts
index 0351c2a..168d2d1 100644
--- a/apps/api/src/controllers/gameController.test.ts
+++ b/apps/api/src/controllers/gameController.test.ts
@@ -197,7 +197,7 @@ describe("POST /api/v1/game/answer", () => {
expect(body.success).toBe(false);
expect(body.error).toContain("Question already answered");
});
-
+
it("returns 400 when a field has an invalid value", async () => {
const res = await request(app)
.post("/api/v1/game/start")
diff --git a/apps/api/src/errors/AppError.ts b/apps/api/src/errors/AppError.ts
index bc5a92a..7805f3e 100644
--- a/apps/api/src/errors/AppError.ts
+++ b/apps/api/src/errors/AppError.ts
@@ -30,4 +30,4 @@ export class UnprocessableEntityError extends AppError {
constructor(message: string) {
super(message, 422);
}
-}
+}
\ No newline at end of file
diff --git a/apps/api/src/lib/auth.ts b/apps/api/src/lib/auth.ts
index fe41e35..8e2b818 100644
--- a/apps/api/src/lib/auth.ts
+++ b/apps/api/src/lib/auth.ts
@@ -1,11 +1,8 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
-import { Resend } from "resend";
import { db } from "@lila/db";
import * as schema from "@lila/db/schema";
-const emailFrom = process.env["EMAIL_FROM"] ?? "noreply@lilastudy.com";
-
export const auth = betterAuth({
baseURL: process.env["BETTER_AUTH_URL"] || "http://localhost:3000",
advanced: {
@@ -19,44 +16,6 @@ export const auth = betterAuth({
},
},
database: drizzleAdapter(db, { provider: "pg", schema }),
- emailAndPassword: {
- enabled: true,
- requireEmailVerification: true,
- sendResetPassword: async ({
- user,
- url,
- }: {
- user: { email: string };
- url: string;
- }) => {
- const resend = new Resend(process.env["RESEND_API_KEY"]);
- await resend.emails.send({
- from: emailFrom,
- to: user.email,
- subject: "Reset your lila password",
- html: `
Click here to reset your password. This link expires in 1 hour.
`,
- });
- },
- },
- emailVerification: {
- sendOnSignUp: true,
- autoSignInAfterVerification: true,
- sendVerificationEmail: async ({
- user,
- url,
- }: {
- user: { email: string };
- url: string;
- }) => {
- const resend = new Resend(process.env["RESEND_API_KEY"]);
- await resend.emails.send({
- from: emailFrom,
- to: user.email,
- subject: "Verify your lila account",
- html: `Click here to verify your email address.
`,
- });
- },
- },
trustedOrigins: [process.env["CORS_ORIGIN"] || "http://localhost:5173"],
socialProviders: {
google: {
diff --git a/apps/api/src/routes/gameRouter.ts b/apps/api/src/routes/gameRouter.ts
index a40622d..9e29a5d 100644
--- a/apps/api/src/routes/gameRouter.ts
+++ b/apps/api/src/routes/gameRouter.ts
@@ -14,6 +14,6 @@ export const createGameRouter = (store: GameSessionStore): Router => {
router.post("/start", controller.createGame as express.RequestHandler);
router.post("/answer", controller.submitAnswer as express.RequestHandler);
-
+
return router;
};
diff --git a/apps/web/package.json b/apps/web/package.json
index 8068c8f..922f6f9 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -6,8 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
- "preview": "vite preview",
- "typecheck": "tsc --noEmit"
+ "preview": "vite preview"
},
"dependencies": {
"@lila/shared": "workspace:*",
@@ -17,7 +16,6 @@
"better-auth": "^1.6.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
- "sonner": "^2.0.7",
"tailwindcss": "^4.2.2"
},
"devDependencies": {
diff --git a/apps/web/src/components/auth/AuthModal.tsx b/apps/web/src/components/auth/AuthModal.tsx
deleted file mode 100644
index 01b2c20..0000000
--- a/apps/web/src/components/auth/AuthModal.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import { useState, useEffect } from "react";
-import { toast } from "sonner";
-import { authClient } from "../../lib/auth-client";
-
-type Tab = "login" | "register";
-
-type AuthModalProps = { onClose: () => void; onSuccess: () => void };
-
-type LoginFormProps = { onSuccess: () => void };
-
-const LoginForm = ({ onSuccess }: LoginFormProps) => {
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [isPending, setIsPending] = useState(false);
-
- const handleSubmit = async () => {
- setIsPending(true);
- await authClient.signIn.email(
- { email, password },
- {
- onSuccess: () => {
- toast.success("Welcome back!");
- onSuccess();
- },
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- setIsPending(false);
- },
- },
- );
- };
-
- return (
-
- );
-};
-
-type RegisterFormProps = { onSuccess: () => void };
-
-const RegisterForm = ({ onSuccess }: RegisterFormProps) => {
- const [name, setName] = useState("");
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [isPending, setIsPending] = useState(false);
-
- const handleSubmit = async () => {
- setIsPending(true);
- await authClient.signUp.email(
- { name, email, password },
- {
- onSuccess: () => {
- toast.success("Check your email to verify your account.");
- onSuccess();
- },
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- setIsPending(false);
- },
- },
- );
- };
-
- return (
-
- );
-};
-
-type SocialButtonsProps = { onSuccess: () => void };
-
-const SocialButtons = ({ onSuccess }: SocialButtonsProps) => {
- const handleSocial = (provider: "google" | "github") => {
- void authClient.signIn.social(
- { provider, callbackURL: window.location.origin },
- {
- onSuccess,
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- },
- },
- );
- };
-
- return (
-
-
-
-
- or continue with
-
-
-
-
-
-
- );
-};
-
-export const AuthModal = ({ onClose, onSuccess }: AuthModalProps) => {
- const [tab, setTab] = useState("login");
-
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (e.key === "Escape") onClose();
- };
- document.addEventListener("keydown", handleKeyDown);
- return () => document.removeEventListener("keydown", handleKeyDown);
- }, [onClose]);
-
- return (
-
-
e.stopPropagation()}
- >
- {/* Close button */}
-
-
- {/* Header */}
-
-
- lila
-
-
-
- {/* Tabs */}
-
- {(["login", "register"] as Tab[]).map((t) => (
-
- ))}
-
-
- {tab === "login" ? (
-
- ) : (
-
- )}
-
- {/* Social */}
-
-
-
- );
-};
diff --git a/apps/web/src/components/landing/Hero.tsx b/apps/web/src/components/landing/Hero.tsx
index 238d313..81f7bba 100644
--- a/apps/web/src/components/landing/Hero.tsx
+++ b/apps/web/src/components/landing/Hero.tsx
@@ -66,15 +66,13 @@ const Hero = () => {
) : (
<>
Get started
Log in
diff --git a/apps/web/src/components/navbar/NavAuth.tsx b/apps/web/src/components/navbar/NavAuth.tsx
index f65f569..22b8479 100644
--- a/apps/web/src/components/navbar/NavAuth.tsx
+++ b/apps/web/src/components/navbar/NavAuth.tsx
@@ -24,14 +24,13 @@ const NavAuth = () => {
) : (
- Login
+ Sign in
)}
diff --git a/apps/web/src/components/navbar/NavLogin.tsx b/apps/web/src/components/navbar/NavLogin.tsx
new file mode 100644
index 0000000..f28bfdd
--- /dev/null
+++ b/apps/web/src/components/navbar/NavLogin.tsx
@@ -0,0 +1,17 @@
+import { Link } from "@tanstack/react-router";
+
+const NavLogin = () => {
+ return (
+
+ Login
+
+ );
+};
+
+export default NavLogin;
diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts
index a85f2f2..96c3044 100644
--- a/apps/web/src/routeTree.gen.ts
+++ b/apps/web/src/routeTree.gen.ts
@@ -9,21 +9,15 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
-import { Route as ResetPasswordRouteImport } from './routes/reset-password'
import { Route as PlayRouteImport } from './routes/play'
import { Route as MultiplayerRouteImport } from './routes/multiplayer'
-import { Route as ForgotPasswordRouteImport } from './routes/forgot-password'
+import { Route as LoginRouteImport } from './routes/login'
import { Route as AboutRouteImport } from './routes/about'
import { Route as IndexRouteImport } from './routes/index'
import { Route as MultiplayerIndexRouteImport } from './routes/multiplayer/index'
import { Route as MultiplayerLobbyCodeRouteImport } from './routes/multiplayer/lobby.$code'
import { Route as MultiplayerGameCodeRouteImport } from './routes/multiplayer/game.$code'
-const ResetPasswordRoute = ResetPasswordRouteImport.update({
- id: '/reset-password',
- path: '/reset-password',
- getParentRoute: () => rootRouteImport,
-} as any)
const PlayRoute = PlayRouteImport.update({
id: '/play',
path: '/play',
@@ -34,9 +28,9 @@ const MultiplayerRoute = MultiplayerRouteImport.update({
path: '/multiplayer',
getParentRoute: () => rootRouteImport,
} as any)
-const ForgotPasswordRoute = ForgotPasswordRouteImport.update({
- id: '/forgot-password',
- path: '/forgot-password',
+const LoginRoute = LoginRouteImport.update({
+ id: '/login',
+ path: '/login',
getParentRoute: () => rootRouteImport,
} as any)
const AboutRoute = AboutRouteImport.update({
@@ -68,10 +62,9 @@ const MultiplayerGameCodeRoute = MultiplayerGameCodeRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/about': typeof AboutRoute
- '/forgot-password': typeof ForgotPasswordRoute
+ '/login': typeof LoginRoute
'/multiplayer': typeof MultiplayerRouteWithChildren
'/play': typeof PlayRoute
- '/reset-password': typeof ResetPasswordRoute
'/multiplayer/': typeof MultiplayerIndexRoute
'/multiplayer/game/$code': typeof MultiplayerGameCodeRoute
'/multiplayer/lobby/$code': typeof MultiplayerLobbyCodeRoute
@@ -79,9 +72,8 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/about': typeof AboutRoute
- '/forgot-password': typeof ForgotPasswordRoute
+ '/login': typeof LoginRoute
'/play': typeof PlayRoute
- '/reset-password': typeof ResetPasswordRoute
'/multiplayer': typeof MultiplayerIndexRoute
'/multiplayer/game/$code': typeof MultiplayerGameCodeRoute
'/multiplayer/lobby/$code': typeof MultiplayerLobbyCodeRoute
@@ -90,10 +82,9 @@ export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/about': typeof AboutRoute
- '/forgot-password': typeof ForgotPasswordRoute
+ '/login': typeof LoginRoute
'/multiplayer': typeof MultiplayerRouteWithChildren
'/play': typeof PlayRoute
- '/reset-password': typeof ResetPasswordRoute
'/multiplayer/': typeof MultiplayerIndexRoute
'/multiplayer/game/$code': typeof MultiplayerGameCodeRoute
'/multiplayer/lobby/$code': typeof MultiplayerLobbyCodeRoute
@@ -103,10 +94,9 @@ export interface FileRouteTypes {
fullPaths:
| '/'
| '/about'
- | '/forgot-password'
+ | '/login'
| '/multiplayer'
| '/play'
- | '/reset-password'
| '/multiplayer/'
| '/multiplayer/game/$code'
| '/multiplayer/lobby/$code'
@@ -114,9 +104,8 @@ export interface FileRouteTypes {
to:
| '/'
| '/about'
- | '/forgot-password'
+ | '/login'
| '/play'
- | '/reset-password'
| '/multiplayer'
| '/multiplayer/game/$code'
| '/multiplayer/lobby/$code'
@@ -124,10 +113,9 @@ export interface FileRouteTypes {
| '__root__'
| '/'
| '/about'
- | '/forgot-password'
+ | '/login'
| '/multiplayer'
| '/play'
- | '/reset-password'
| '/multiplayer/'
| '/multiplayer/game/$code'
| '/multiplayer/lobby/$code'
@@ -136,21 +124,13 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AboutRoute: typeof AboutRoute
- ForgotPasswordRoute: typeof ForgotPasswordRoute
+ LoginRoute: typeof LoginRoute
MultiplayerRoute: typeof MultiplayerRouteWithChildren
PlayRoute: typeof PlayRoute
- ResetPasswordRoute: typeof ResetPasswordRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
- '/reset-password': {
- id: '/reset-password'
- path: '/reset-password'
- fullPath: '/reset-password'
- preLoaderRoute: typeof ResetPasswordRouteImport
- parentRoute: typeof rootRouteImport
- }
'/play': {
id: '/play'
path: '/play'
@@ -165,11 +145,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MultiplayerRouteImport
parentRoute: typeof rootRouteImport
}
- '/forgot-password': {
- id: '/forgot-password'
- path: '/forgot-password'
- fullPath: '/forgot-password'
- preLoaderRoute: typeof ForgotPasswordRouteImport
+ '/login': {
+ id: '/login'
+ path: '/login'
+ fullPath: '/login'
+ preLoaderRoute: typeof LoginRouteImport
parentRoute: typeof rootRouteImport
}
'/about': {
@@ -229,10 +209,9 @@ const MultiplayerRouteWithChildren = MultiplayerRoute._addFileChildren(
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AboutRoute: AboutRoute,
- ForgotPasswordRoute: ForgotPasswordRoute,
+ LoginRoute: LoginRoute,
MultiplayerRoute: MultiplayerRouteWithChildren,
PlayRoute: PlayRoute,
- ResetPasswordRoute: ResetPasswordRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
index 7df9998..c672ced 100644
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -1,39 +1,16 @@
-import {
- createRootRoute,
- Outlet,
- useNavigate,
- useSearch,
-} from "@tanstack/react-router";
+import { createRootRoute, Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
-import { Toaster } from "sonner";
import Navbar from "../components/navbar/NavBar";
import NotFound from "../components/NotFound";
import RootError from "../components/RootError";
-import { AuthModal } from "../components/auth/AuthModal";
-import { AuthModalSearchSchema } from "@lila/shared";
const RootLayout = () => {
- const navigate = useNavigate();
- const { modal, redirect } = useSearch({ from: "__root__" });
-
- const handleClose = () => {
- void navigate({ to: "/", search: {} });
- };
-
- const handleSuccess = () => {
- void navigate({ to: (redirect as string) ?? "/", search: {} });
- };
-
return (
<>
- {modal === "auth" && (
-
- )}
-
>
);
@@ -43,5 +20,4 @@ export const Route = createRootRoute({
component: RootLayout,
notFoundComponent: NotFound,
errorComponent: RootError,
- validateSearch: AuthModalSearchSchema,
});
diff --git a/apps/web/src/routes/forgot-password.tsx b/apps/web/src/routes/forgot-password.tsx
deleted file mode 100644
index 9d929a8..0000000
--- a/apps/web/src/routes/forgot-password.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { useState } from "react";
-import { createFileRoute, Link } from "@tanstack/react-router";
-import { authClient } from "../lib/auth-client";
-import { toast } from "sonner";
-
-function ForgotPasswordPage() {
- const [email, setEmail] = useState("");
- const [isPending, setIsPending] = useState(false);
-
- const handleSubmit = async () => {
- setIsPending(true);
- await authClient.requestPasswordReset(
- { email, redirectTo: `${window.location.origin}/reset-password` },
- {
- onSuccess: () => {
- toast.success("Check your email for a reset link.");
- setIsPending(false);
- },
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- setIsPending(false);
- },
- },
- );
- };
-
- return (
-
-
-
- Forgot password
-
-
- Enter your email and we'll send you a reset link.
-
-
-
-
-
-
- Back to home
-
-
- );
-}
-
-export const Route = createFileRoute("/forgot-password")({
- component: ForgotPasswordPage,
-});
diff --git a/apps/web/src/routes/login.tsx b/apps/web/src/routes/login.tsx
new file mode 100644
index 0000000..8451d41
--- /dev/null
+++ b/apps/web/src/routes/login.tsx
@@ -0,0 +1,46 @@
+import { createFileRoute, useNavigate } from "@tanstack/react-router";
+import { signIn, useSession } from "../lib/auth-client";
+
+const LoginPage = () => {
+ const { data: session, isPending } = useSession();
+ const navigate = useNavigate();
+
+ if (isPending) return Loading...
;
+
+ if (session) {
+ void navigate({ to: "/" });
+ return null;
+ }
+
+ return (
+
+
sign in to lila
+
+
+
+ );
+};
+
+export const Route = createFileRoute("/login")({ component: LoginPage });
diff --git a/apps/web/src/routes/multiplayer.tsx b/apps/web/src/routes/multiplayer.tsx
index 0008b37..7adffd6 100644
--- a/apps/web/src/routes/multiplayer.tsx
+++ b/apps/web/src/routes/multiplayer.tsx
@@ -14,10 +14,7 @@ export const Route = createFileRoute("/multiplayer")({
beforeLoad: async () => {
const { data: session } = await authClient.getSession();
if (!session) {
- throw redirect({
- to: "/",
- search: { modal: "auth", redirect: "/multiplayer" },
- });
+ throw redirect({ to: "/login" });
}
return { session };
},
diff --git a/apps/web/src/routes/play.tsx b/apps/web/src/routes/play.tsx
index bc4cde3..df4959d 100644
--- a/apps/web/src/routes/play.tsx
+++ b/apps/web/src/routes/play.tsx
@@ -132,7 +132,7 @@ export const Route = createFileRoute("/play")({
beforeLoad: async () => {
const { data: session } = await authClient.getSession();
if (!session) {
- throw redirect({ to: "/", search: { modal: "auth", redirect: "/play" } });
+ throw redirect({ to: "/login" });
}
},
});
diff --git a/apps/web/src/routes/reset-password.tsx b/apps/web/src/routes/reset-password.tsx
deleted file mode 100644
index 837949b..0000000
--- a/apps/web/src/routes/reset-password.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { useState } from "react";
-import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
-import { authClient } from "../lib/auth-client";
-import { toast } from "sonner";
-import { ResetPasswordSearchSchema } from "@lila/shared";
-
-function ResetPasswordPage() {
- const { token } = Route.useSearch();
- const navigate = useNavigate();
- const [password, setPassword] = useState("");
- const [isPending, setIsPending] = useState(false);
-
- if (!token) {
- return (
-
-
- Invalid link
-
-
- This reset link is invalid or has expired.
-
-
- Request a new one
-
-
- );
- }
-
- const handleSubmit = async () => {
- setIsPending(true);
- await authClient.resetPassword(
- { newPassword: password, token },
- {
- onSuccess: () => {
- toast.success("Password updated. You can now sign in.");
- void navigate({ to: "/" });
- },
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- setIsPending(false);
- },
- },
- );
- };
-
- return (
-
-
-
- Reset password
-
-
- Enter your new password below.
-
-
-
-
-
- );
-}
-
-export const Route = createFileRoute("/reset-password")({
- component: ResetPasswordPage,
- validateSearch: ResetPasswordSearchSchema,
-});
diff --git a/documentation/backlog.md b/documentation/backlog.md
index 35a5f05..6a91e2d 100644
--- a/documentation/backlog.md
+++ b/documentation/backlog.md
@@ -90,6 +90,9 @@ Directionally right, timing is unclear. Revisit when the next/now work is done.
- **Resolve eslint peer dependency warning** `[debt]`
`eslint-plugin-react-hooks 7.0.1` expects `eslint ^3.0.0โ^9.0.0` but found `10.0.3`. Low impact but worth cleaning up when nearby.
+- **husky + lint-staged** `[debt]`
+ Set up husky and lint-staged to run linting and formatting checks before every commit. Prevents CI failures from formatting or lint issues that slipped through locally.
+
- **OpenAPI documentation for REST endpoints** `[feature]`
Document the API surface using OpenAPI/Swagger. Covers all REST endpoints with request/response shapes. Useful groundwork for the admin dashboard and any future contributors.
@@ -102,7 +105,6 @@ Directionally right, timing is unclear. Revisit when the next/now work is done.
Shipped milestones, newest first.
-- **04 - 2026 - husky + lint-staged + CI quality gate** - Pre-commit formatting, pre-push tests, and CI lint/typecheck/test gate before every deploy.
- **04 - 2026 - t00001 - Docker credential helper**
- **04 - 2026 - Pin dependencies in package.json** - Unpinned deps in a CI/CD pipeline are a real risk.
- **04 - 2026 - React error boundaries** - Catch and display runtime errors gracefully instead of crashing the entire app.
diff --git a/documentation/notes.md b/documentation/notes.md
index 45f8955..4391d87 100644
--- a/documentation/notes.md
+++ b/documentation/notes.md
@@ -12,8 +12,7 @@ task description.
4. if we go through a file, we'll do it slowly section by section, no matter how many sections
5. how to name the current feature branch? also tell me when its time to git commit and provide a commit message
6. if we have multiple options to do something, also always provide options that reflect current industry standards and best practices
-7. never assume anything! always ask for clarification!
-8. For every completed task, produce a ticket file in documentation/tickets/. Use ADR format (adr-) for decisions between options with long-term consequences. Use feat-/fix-/chore- for routine tasks. Always include a setup guide or summary of what was done. Suggest the filename.
+7. For every completed task, produce a ticket file in documentation/tickets/. Use ADR format (adr-) for decisions between options with long-term consequences. Use feat-/fix-/chore- for routine tasks. Always include a setup guide or summary of what was done. Suggest the filename.
## tasks
diff --git a/documentation/roasts/gameService.md b/documentation/roasts/gameService.md
index ac3d2ed..73283de 100644
--- a/documentation/roasts/gameService.md
+++ b/documentation/roasts/gameService.md
@@ -1,11 +1,11 @@
# ๐ฅ GameService Roast: `apps/api/src/services/gameService.ts`
-> _"It works on my machine" is not a scalability strategy._
+> *"It works on my machine" is not a scalability strategy.*
**Project:** lila โ Vocabulary Trainer
**File Roasted:** `gameService.ts`
**Date:** $(date)
-**Roaster:** Qwen3.6
+**Roaster:** Qwen3.6
---
@@ -28,8 +28,8 @@
**Location:** `gameService.ts:45-58` + `InMemoryGameSessionStore.ts:update()`
// Current flow (VULNERABLE):
-const session = await store.get(submission.sessionId); // READ
-const updatedAnswers = new Map(session.answers); // MODIFY (local copy)
+const session = await store.get(submission.sessionId); // READ
+const updatedAnswers = new Map(session.answers); // MODIFY (local copy)
updatedAnswers.delete(submission.questionId);
await store.update(submission.sessionId, { answers: updatedAnswers }); // WRITE
@@ -46,55 +46,64 @@ Fix Options:
// Option A: Add atomic operation to store interface
interface GameSessionStore {
-deleteAnswer(sessionId: string, questionId: string): Promise;
+ deleteAnswer(sessionId: string, questionId: string): Promise;
}
// Option B: Use Valkey Lua script for atomic read-modify-write
// Option C: Optimistic locking with version numbers
-Priority: ๐ด CRITICAL โ Data integrity issue 2. N+1 Query: Database Performance Bomb
+Priority: ๐ด CRITICAL โ Data integrity issue
+2. N+1 Query: Database Performance Bomb
Location: gameService.ts:24-26 + termModel.ts:getDistractors()
// For each of N terms, we call getDistractors():
const questions: GameQuestion[] = await Promise.all(
-terms.map(async (term) => {
-const distractorTexts = await getDistractors(term.termId, ...); // ๐ฉ N queries!
-})
+ terms.map(async (term) => {
+ const distractorTexts = await getDistractors(term.termId, ...); // ๐ฉ N queries!
+ })
);
Impact Analysis:
Rounds
+
DB Queries
+
At 50 concurrent users
3
+
1 + 3 = 4
+
200 queries/min
10
+
1 + 10 = 11
+
550 queries/min
20
+
1 + 20 = 21
+
1,050 queries/min
Each getDistractors() runs:
-SELECT text FROM terms
-JOIN translations ON ...
-WHERE pos = $1 AND difficulty = $2 AND term_id != $3 AND text != $4
+SELECT text FROM terms
+JOIN translations ON ...
+WHERE pos = $1 AND difficulty = $2 AND term_id != $3 AND text != $4
ORDER BY RANDOM() LIMIT 6
Fix: Batch Fetch Distractors
// Fetch all distractors in ONE query
const allDistractors = await db
-.select({ termId: terms.id, text: translations.text })
-.from(terms)
-.innerJoin(translations, /_ ... _/)
-.where(and(
-eq(terms.pos, pos),
-eq(translations.difficulty, difficulty),
-inArray(terms.id, termIds), // Batch!
-))
-.limit(DISTRACTOR_FETCH_COUNT \* termIds.length);
+ .select({ termId: terms.id, text: translations.text })
+ .from(terms)
+ .innerJoin(translations, /* ... */)
+ .where(and(
+ eq(terms.pos, pos),
+ eq(translations.difficulty, difficulty),
+ inArray(terms.id, termIds), // Batch!
+ ))
+ .limit(DISTRACTOR_FETCH_COUNT * termIds.length);
// Group by termId in JS, then slice to 3 unique distractors per term
const distractorsByTerm = groupByTermId(allDistractors);
@@ -102,10 +111,10 @@ const distractorsByTerm = groupByTermId(allDistractors);
Priority: ๐ด CRITICAL โ Performance/scalability issue
3. Error Handling Inconsistency
- Location: gameService.ts:33-36
+Location: gameService.ts:33-36
if (uniqueDistractors.length < 3) {
-throw new Error(`Not enough unique distractors for term: ${term.targetText}`); // ๐ฉ
+ throw new Error(`Not enough unique distractors for term: ${term.targetText}`); // ๐ฉ
}
Problem: Raw Error bypasses your errorHandler middleware:
@@ -118,14 +127,15 @@ Fix:
import { UnprocessableEntityError } from "../errors/AppError.js";
if (uniqueDistractors.length < 3) {
-logger.warn({ termId: term.termId, uniqueCount: uniqueDistractors.length },
-"insufficient_distractors");
-throw new UnprocessableEntityError(
-`Not enough unique distractors for term: ${term.targetText}`
-);
+ logger.warn({ termId: term.termId, uniqueCount: uniqueDistractors.length },
+ "insufficient_distractors");
+ throw new UnprocessableEntityError(
+ `Not enough unique distractors for term: ${term.targetText}`
+ );
}
Priority: ๐ก HIGH โ Observability & UX issue
-โ ๏ธ High-Severity Smells 4. Code Duplication: Singleplayer vs Multiplayer
+โ ๏ธ High-Severity Smells
+4. Code Duplication: Singleplayer vs Multiplayer
Compare: gameService.ts vs multiplayerGameService.ts
// gameService.ts
const optionTexts = [term.targetText, ...uniqueDistractors.slice(0, 3)];
@@ -147,29 +157,30 @@ Fix: Extract pure function to @lila/shared or new @lila/game-logic:
// packages/shared/src/game-logic.ts
export const buildQuestionOptions = (
-correctAnswer: string,
-distractors: string[],
-optionCount: number = 4
+ correctAnswer: string,
+ distractors: string[],
+ optionCount: number = 4
): { options: AnswerOption[]; correctOptionId: number } => {
-const uniqueDistractors = [...new Set(distractors.filter(d => d !== correctAnswer))];
-const optionTexts = [correctAnswer, ...uniqueDistractors.slice(0, optionCount - 1)];
-const shuffled = shuffleSecure(optionTexts);
-const correctOptionId = shuffled.indexOf(correctAnswer);
-
-return {
-options: shuffled.map((text, idx) => ({ optionId: idx, text })),
-correctOptionId
-};
+ const uniqueDistractors = [...new Set(distractors.filter(d => d !== correctAnswer))];
+ const optionTexts = [correctAnswer, ...uniqueDistractors.slice(0, optionCount - 1)];
+ const shuffled = shuffleSecure(optionTexts);
+ const correctOptionId = shuffled.indexOf(correctAnswer);
+
+ return {
+ options: shuffled.map((text, idx) => ({ optionId: idx, text })),
+ correctOptionId
+ };
};
-Priority: ๐ก HIGH โ Maintainability issue 5. Shuffle Bias: Math.random() Trap
+Priority: ๐ก HIGH โ Maintainability issue
+5. Shuffle Bias: Math.random() Trap
Location: utils.ts:shuffleArray() + multiplayerGameService.ts:shuffle()
export const shuffleArray = (array: T[]): T[] => {
-for (let i = result.length - 1; i > 0; i--) {
-const j = Math.floor(Math.random() \* (i + 1)); // ๐ฉ Modulo bias + non-crypto RNG
-// ...
-}
+ for (let i = result.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1)); // ๐ฉ Modulo bias + non-crypto RNG
+ // ...
+ }
};
The Math:
@@ -188,64 +199,65 @@ Fix (if needed):
import { randomBytes } from "crypto";
const shuffleSecure = (array: T[]): T[] => {
-const result = [...array];
-for (let i = result.length - 1; i > 0; i--) {
-// Use crypto.getRandomValues for better randomness
-const rand = randomBytes(4).readUInt32LE(0);
-const j = rand % (i + 1);
-[result[i], result[j]] = [result[j], result[i]];
-}
-return result;
+ const result = [...array];
+ for (let i = result.length - 1; i > 0; i--) {
+ // Use crypto.getRandomValues for better randomness
+ const rand = randomBytes(4).readUInt32LE(0);
+ const j = rand % (i + 1);
+ [result[i], result[j]] = [result[j], result[i]];
+ }
+ return result;
};
Priority: ๐ข LOW โ Document tradeoff and move on for now
-6. Test Coverage Gaps
- File: gameService.test.ts
- โ
Well Tested:
+6. Test Coverage Gaps
+File: gameService.test.ts
+โ
Well Tested:
- Happy path: session creation, answer evaluation
- Edge cases: duplicate distractors, empty results, invalid inputs
- Error propagation from DB layer
+ Happy path: session creation, answer evaluation
+ Edge cases: duplicate distractors, empty results, invalid inputs
+ Error propagation from DB layer
โ Missing Tests:
// 1. Concurrency test (race condition)
it("rejects duplicate answers for same question under concurrent load", async () => {
-const session = await createGameSession(validRequest, store, "user-1");
-const question = session.questions[0]!;
-
-// Submit two answers simultaneously
-const [result1, result2] = await Promise.allSettled([
-evaluateAnswer({ sessionId, questionId, selectedOptionId: 0 }, store, "user-1"),
-evaluateAnswer({ sessionId, questionId, selectedOptionId: 1 }, store, "user-1"),
-]);
-
-// Exactly one should succeed, one should throw ConflictError
-expect([result1, result2].filter(r => r.status === "fulfilled")).toHaveLength(1);
+ const session = await createGameSession(validRequest, store, "user-1");
+ const question = session.questions[0]!;
+
+ // Submit two answers simultaneously
+ const [result1, result2] = await Promise.allSettled([
+ evaluateAnswer({ sessionId, questionId, selectedOptionId: 0 }, store, "user-1"),
+ evaluateAnswer({ sessionId, questionId, selectedOptionId: 1 }, store, "user-1"),
+ ]);
+
+ // Exactly one should succeed, one should throw ConflictError
+ expect([result1, result2].filter(r => r.status === "fulfilled")).toHaveLength(1);
});
// 2. TTL expiration test
it("deletes session after TTL expires", async () => {
-vi.useFakeTimers();
-const session = await createGameSession(validRequest, store, "user-1");
-
-vi.advanceTimersByTime(31 _ 60 _ 1000); // 31 minutes
-
-await expect(store.get(session.sessionId)).resolves.toBeNull();
+ vi.useFakeTimers();
+ const session = await createGameSession(validRequest, store, "user-1");
+
+ vi.advanceTimersByTime(31 * 60 * 1000); // 31 minutes
+
+ await expect(store.get(session.sessionId)).resolves.toBeNull();
});
// 3. Distractor fallback strategy test
it("uses fallback when <3 unique distractors available", async () => {
-mockGetDistractors.mockResolvedValue(["same", "same", "same", "same"]);
-// Should either: (a) fetch from broader pool, or (b) reduce rounds gracefully
+ mockGetDistractors.mockResolvedValue(["same", "same", "same", "same"]);
+ // Should either: (a) fetch from broader pool, or (b) reduce rounds gracefully
});
Priority: ๐ก HIGH โ Prevents regression on critical fixes
-๐งผ Code Quality Nitpicks 7. Magic Numbers
+๐งผ Code Quality Nitpicks
+7. Magic Numbers
// gameService.ts:52
-await store.create(sessionId, {...}, 30 _ 60 _ 1000); // What is this?
+await store.create(sessionId, {...}, 30 * 60 * 1000); // What is this?
// termModel.ts:65
.limit(count); // count=6, but why?
@@ -255,16 +267,16 @@ optionId: z.number().int().min(0).max(3), // Why 4 options?
Fix: Centralize in @lila/shared/constants.ts:
-export const GAME*SESSION_TTL_MS = 30 * 60 \_ 1000;
+export const GAME_SESSION_TTL_MS = 30 * 60 * 1000;
export const DISTRACTOR_FETCH_COUNT = 6;
export const GAME_OPTION_COUNT = 4;
export const MIN_UNIQUE_DISTRACTORS = 3;
8. Mutable Reference Leakage
- Location: InMemoryGameSessionStore.ts:get()
+Location: InMemoryGameSessionStore.ts:get()
get(sessionId: string): Promise {
-return Promise.resolve(entry.data); // ๐ฉ Returns mutable reference to internal state
+ return Promise.resolve(entry.data); // ๐ฉ Returns mutable reference to internal state
}
Risk: Any code that does session.answers.delete(...) mutates the store's internal Map directly.
@@ -279,35 +291,37 @@ return Promise.resolve(entry.data as Readonly);
// Option C: Use immutable data structures (overkill for now)
9. Zero Observability
- Problem: No logging, no metrics. You're flying blind in production.
- Minimal Fix (5 minutes):
+Problem: No logging, no metrics. You're flying blind in production.
+Minimal Fix (5 minutes):
+
+
// apps/api/src/lib/logger.ts
import pino from "pino";
-export const logger = pino({
-level: process.env.LOG_LEVEL || "info",
-transport: process.env.NODE_ENV === "production"
-? { target: "pino-pretty" }
-: undefined
+export const logger = pino({
+ level: process.env.LOG_LEVEL || "info",
+ transport: process.env.NODE_ENV === "production"
+ ? { target: "pino-pretty" }
+ : undefined
});
// In gameService.ts:
import { logger } from "../lib/logger.js";
logger.info(
-{ userId, sourceLang, targetLang, termCount: terms.length },
-"game_session_created"
+ { userId, sourceLang, targetLang, termCount: terms.length },
+ "game_session_created"
);
logger.debug(
-{ sessionId, questionId, isCorrect, responseTimeMs },
-"answer_evaluated"
+ { sessionId, questionId, isCorrect, responseTimeMs },
+ "answer_evaluated"
);
Bonus: Export a Prometheus histogram for game_service_duration_seconds.
10. ORDER BY RANDOM() Time Bomb
- Location: termModel.ts:getGameTerms() + getDistractors()
+Location: termModel.ts:getGameTerms() + getDistractors()
.orderBy(sql`RANDOM()`) // ๐ฉ Fine for 10k rows, slow for 1M
@@ -319,16 +333,16 @@ Reality Check: "Post-MVP" never comes without a ticket.
Fix Options:
-- Option A: Pre-computed random_seed column (updated nightly)
-WHERE ... AND random_seed >= random()
-ORDER BY random_seed
+WHERE ... AND random_seed >= random()
+ORDER BY random_seed
LIMIT $1
-- Option B: TABLESAMPLE for approximate sampling (Postgres 9.5+)
-FROM terms TABLESAMPLE SYSTEM(10)
-WHERE ...
+FROM terms TABLESAMPLE SYSTEM(10)
+WHERE ...
LIMIT $1
-- Option C: Random offset (simple, but still scans)
-OFFSET floor(random() _ (SELECT count(_) FROM terms WHERE ...))
+OFFSET floor(random() * (SELECT count(*) FROM terms WHERE ...))
-Action: Add a ticket to documentation/tickets/t00009.md now.
+Action: Add a ticket to documentation/tickets/t00009.md now.
\ No newline at end of file
diff --git a/documentation/tickets/t00006.md b/documentation/tickets/t00006.md
index edc5e25..8e777f0 100644
--- a/documentation/tickets/t00006.md
+++ b/documentation/tickets/t00006.md
@@ -84,10 +84,7 @@ Rejected because: for user-owned resources identified by opaque IDs, confirming
2. `GameSessionStore.ts` โ add `userId` to `GameSessionData`:
```ts
- export type GameSessionData = {
- answers: Map;
- userId: string;
- };
+ export type GameSessionData = { answers: Map; userId: string };
```
3. `gameService.ts` โ add `userId` to both function signatures:
@@ -103,11 +100,7 @@ Rejected because: for user-owned resources identified by opaque IDs, confirming
Store it on create:
```ts
- await store.create(
- sessionId,
- { answers: answerKey, userId },
- 30 * 60 * 1000,
- );
+ await store.create(sessionId, { answers: answerKey, userId }, 30 * 60 * 1000);
```
Assert on evaluate:
@@ -121,7 +114,7 @@ Rejected because: for user-owned resources identified by opaque IDs, confirming
4. `gameController.ts` โ extract from authenticated request:
```ts
- req.session.user.id;
+ req.session.user.id
```
5. `gameRouter.ts` โ cast at registration:
diff --git a/documentation/tickets/t00008.md b/documentation/tickets/t00008.md
index 670f610..b171abc 100644
--- a/documentation/tickets/t00008.md
+++ b/documentation/tickets/t00008.md
@@ -27,9 +27,7 @@ Not chosen for this ticket โ the database query is in `@lila/db` and is a sepa
- Filter distractors against the correct answer before building options:
```ts
- const uniqueDistractors = distractorTexts.filter(
- (t) => t !== term.targetText,
- );
+ const uniqueDistractors = distractorTexts.filter((t) => t !== term.targetText);
const optionTexts = [term.targetText, ...uniqueDistractors.slice(0, 3)];
```
diff --git a/eslint.config.mjs b/eslint.config.mjs
index a88b6f1..290fa14 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -10,6 +10,8 @@ export default defineConfig([
globalIgnores([
"**/dist/**",
"node_modules/",
+ "eslint.config.mjs",
+ "**/*.config.ts",
"routeTree.gen.ts",
"scripts/**",
"data-pipeline/**/*",
@@ -22,19 +24,12 @@ export default defineConfig([
{
languageOptions: {
parserOptions: {
- projectService: { allowDefaultProject: ["*.mjs", "*.ts"] },
+ projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
- {
- files: ["eslint.config.mjs"],
- rules: {
- "@typescript-eslint/no-unsafe-member-access": "off",
- "@typescript-eslint/no-unsafe-assignment": "off",
- "@typescript-eslint/no-unsafe-call": "off",
- },
- },
+
{
files: ["apps/web/**/*.{ts,tsx}"],
extends: [
@@ -48,9 +43,6 @@ export default defineConfig([
rules: {
"react-refresh/only-export-components": "off",
"@typescript-eslint/only-throw-error": "off",
- "@typescript-eslint/no-unsafe-assignment": "off",
- "@typescript-eslint/no-unsafe-member-access": "off",
- "@typescript-eslint/no-unsafe-call": "off",
},
},
{
diff --git a/package.json b/package.json
index 8e9a1f6..d900474 100644
--- a/package.json
+++ b/package.json
@@ -6,22 +6,11 @@
"scripts": {
"build": "pnpm --filter @lila/shared build && pnpm --filter @lila/db build && pnpm --filter @lila/api build",
"dev": "concurrently --names \"api,web\" -c \"magenta.bold,green.bold\" \"pnpm --filter @lila/api dev\" \"pnpm --filter @lila/web dev\"",
- "prepare": "husky || true",
"test": "vitest",
"test:run": "vitest run",
"lint": "eslint .",
"format": "prettier --write .",
- "format:check": "prettier --check .",
- "typecheck": "pnpm -r typecheck"
- },
- "lint-staged": {
- "**/*.{ts,tsx}": [
- "prettier --write",
- "eslint --fix"
- ],
- "**/*.{js,mjs,json,md,css,html}": [
- "prettier --write"
- ]
+ "format:check": "prettier --check ."
},
"packageManager": "pnpm@10.33.1",
"devDependencies": {
@@ -33,8 +22,6 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
- "husky": "^9.1.7",
- "lint-staged": "^16.4.0",
"prettier": "^3.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.1",
diff --git a/packages/db/package.json b/packages/db/package.json
index ee17556..914e989 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -6,8 +6,7 @@
"scripts": {
"build": "rm -rf dist && tsc",
"generate": "drizzle-kit generate",
- "migrate": "drizzle-kit migrate",
- "typecheck": "tsc --noEmit"
+ "migrate": "drizzle-kit migrate"
},
"dependencies": {
"@lila/shared": "workspace:*",
diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts
index 567a460..baa05e0 100644
--- a/packages/db/src/index.ts
+++ b/packages/db/src/index.ts
@@ -6,13 +6,10 @@ import { dirname } from "path";
import * as schema from "./db/schema.js";
config({
- path: resolve(dirname(fileURLToPath(import.meta.url)), "../../../../.env"),
+ path: resolve(dirname(fileURLToPath(import.meta.url)), "../../../.env"),
});
-export const db = drizzle(
- process.env["DATABASE_URL_LOCAL"] ?? process.env["DATABASE_URL"]!,
- { schema },
-);
+export const db = drizzle(process.env["DATABASE_URL"]!, { schema });
export * from "./models/termModel.js";
export * from "./models/lobbyModel.js";
diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json
index c8c1b3a..af1fba6 100644
--- a/packages/db/tsconfig.json
+++ b/packages/db/tsconfig.json
@@ -10,7 +10,6 @@
"include": [
"src",
"vitest.config.ts",
- "drizzle.config.ts",
"../../data-pipeline/archive/packages-db-src-old-seeding-scripts/data"
]
}
diff --git a/packages/shared/package.json b/packages/shared/package.json
index c46ab0a..bf6aa8a 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -4,8 +4,7 @@
"private": true,
"type": "module",
"scripts": {
- "build": "tsc",
- "typecheck": "tsc --noEmit"
+ "build": "tsc"
},
"exports": {
".": "./dist/src/index.js"
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 582394e..7dc79f5 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -1,4 +1,3 @@
export * from "./constants.js";
export * from "./schemas/game.js";
export * from "./schemas/lobby.js";
-export * from "./schemas/auth.js";
diff --git a/packages/shared/src/schemas/auth.ts b/packages/shared/src/schemas/auth.ts
deleted file mode 100644
index 6aaf35d..0000000
--- a/packages/shared/src/schemas/auth.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as z from "zod";
-
-export const ResetPasswordSearchSchema = z.object({
- token: z.string().catch(""),
-});
-
-export type ResetPasswordSearch = z.infer;
-
-export const AuthModalSearchSchema = z.object({
- modal: z.enum(["auth"]).optional().catch(undefined),
- redirect: z.string().optional().catch(undefined),
-});
-
-export type AuthModalSearch = z.infer;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4453586..15acc4d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,7 +16,7 @@ importers:
version: 1.161.6(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
'@vitest/coverage-v8':
specifier: ^4.1.0
- version: 4.1.0(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)))
+ version: 4.1.0(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)))
concurrently:
specifier: ^9.2.1
version: 9.2.1
@@ -32,12 +32,6 @@ importers:
eslint-plugin-react-refresh:
specifier: ^0.5.2
version: 0.5.2(eslint@10.0.3(jiti@2.6.1))
- husky:
- specifier: ^9.1.7
- version: 9.1.7
- lint-staged:
- specifier: ^16.4.0
- version: 16.4.0
prettier:
specifier: ^3.8.1
version: 3.8.1
@@ -49,7 +43,7 @@ importers:
version: 8.57.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)
vitest:
specifier: ^4.1.0
- version: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))
+ version: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
apps/api:
dependencies:
@@ -61,7 +55,7 @@ importers:
version: link:../../packages/shared
better-auth:
specifier: ^1.6.2
- version: 1.6.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)))
+ version: 1.6.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)))
cors:
specifier: ^2.8.6
version: 2.8.6
@@ -74,9 +68,6 @@ importers:
helmet:
specifier: ^8.1.0
version: 8.1.0
- resend:
- specifier: ^6.12.2
- version: 6.12.2
ws:
specifier: ^8.20.0
version: 8.20.0
@@ -107,7 +98,7 @@ importers:
version: link:../../packages/shared
'@tailwindcss/vite':
specifier: ^4.2.2
- version: 4.2.2(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))
+ version: 4.2.2(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
'@tanstack/react-router':
specifier: ^1.168.1
version: 1.168.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -116,23 +107,20 @@ importers:
version: 1.166.10(@tanstack/react-router@1.168.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.168.1)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
better-auth:
specifier: ^1.6.2
- version: 1.6.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)))
+ version: 1.6.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)))
react:
specifier: ^19.2.4
version: 19.2.4
react-dom:
specifier: ^19.2.4
version: 19.2.4(react@19.2.4)
- sonner:
- specifier: ^2.0.7
- version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
tailwindcss:
specifier: ^4.2.2
version: 4.2.2
devDependencies:
'@tanstack/router-plugin':
specifier: ^1.167.2
- version: 1.167.2(@tanstack/react-router@1.168.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))
+ version: 1.167.2(@tanstack/react-router@1.168.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
'@types/node':
specifier: ^24.12.0
version: 24.12.0
@@ -144,13 +132,13 @@ importers:
version: 19.2.3(@types/react@19.2.14)
'@vitejs/plugin-react':
specifier: ^6.0.1
- version: 6.0.1(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))
+ version: 6.0.1(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
jsdom:
specifier: ^29.0.1
version: 29.0.1(@noble/hashes@2.2.0)
vite:
specifier: ^8.0.1
- version: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)
+ version: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)
data-pipeline:
dependencies:
@@ -1096,9 +1084,6 @@ packages:
'@rolldown/pluginutils@1.0.0-rc.7':
resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==}
- '@stablelib/base64@1.0.1':
- resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
-
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -1497,26 +1482,14 @@ packages:
ajv@6.14.0:
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
- ansi-escapes@7.3.0:
- resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==}
- engines: {node: '>=18'}
-
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
- ansi-regex@6.2.2:
- resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
- engines: {node: '>=12'}
-
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
- ansi-styles@6.2.3:
- resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
- engines: {node: '>=12'}
-
ansis@4.2.0:
resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
engines: {node: '>=14'}
@@ -1701,14 +1674,6 @@ packages:
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
- cli-cursor@5.0.0:
- resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
- engines: {node: '>=18'}
-
- cli-truncate@5.2.0:
- resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
- engines: {node: '>=20'}
-
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -1728,17 +1693,10 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
- colorette@2.0.20:
- resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
-
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
- commander@14.0.3:
- resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
- engines: {node: '>=20'}
-
component-emitter@1.3.1:
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
@@ -1951,9 +1909,6 @@ packages:
electron-to-chromium@1.5.321:
resolution: {integrity: sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==}
- emoji-regex@10.6.0:
- resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
-
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1972,10 +1927,6 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
- environment@1.1.0:
- resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
- engines: {node: '>=18'}
-
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@@ -2092,9 +2043,6 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
- eventemitter3@5.0.4:
- resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
-
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
@@ -2125,9 +2073,6 @@ packages:
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
- fast-sha256@1.3.0:
- resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
-
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -2202,10 +2147,6 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
- get-east-asian-width@1.5.0:
- resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
- engines: {node: '>=18'}
-
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -2277,11 +2218,6 @@ packages:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
- husky@9.1.7:
- resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
- engines: {node: '>=18'}
- hasBin: true
-
iconv-lite@0.7.2:
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
engines: {node: '>=0.10.0'}
@@ -2327,10 +2263,6 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
- is-fullwidth-code-point@5.1.0:
- resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
- engines: {node: '>=18'}
-
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -2490,23 +2422,10 @@ packages:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
- lint-staged@16.4.0:
- resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==}
- engines: {node: '>=20.17'}
- hasBin: true
-
- listr2@9.0.5:
- resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
- engines: {node: '>=20.0.0'}
-
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
- log-update@6.1.0:
- resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
- engines: {node: '>=18'}
-
lru-cache@11.2.7:
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
engines: {node: 20 || >=22}
@@ -2564,10 +2483,6 @@ packages:
engines: {node: '>=4.0.0'}
hasBin: true
- mimic-function@5.0.1:
- resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
- engines: {node: '>=18'}
-
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@@ -2633,10 +2548,6 @@ packages:
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
- onetime@7.0.0:
- resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
- engines: {node: '>=18'}
-
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -2715,9 +2626,6 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
- postal-mime@2.7.4:
- resolution: {integrity: sha512-0WdnFQYUrPGGTFu1uOqD2s7omwua8xaeYGdO6rb88oD5yJ/4pPHDA4sdWqfD8wQVfCny563n/HQS7zTFft+f/g==}
-
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
@@ -2809,25 +2717,9 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
- resend@6.12.2:
- resolution: {integrity: sha512-xwgmU4b0OqoabJsIoK/x0Whk0Fcs3bpbK4i/DEWPiE5hYJHyHl0TbB6QbI3gIr+bLdLUJ1GYm/fe41aVFuHXgw==}
- engines: {node: '>=20'}
- peerDependencies:
- '@react-email/render': '*'
- peerDependenciesMeta:
- '@react-email/render':
- optional: true
-
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
- restore-cursor@5.1.0:
- resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
- engines: {node: '>=18'}
-
- rfdc@1.4.1:
- resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
-
rolldown@1.0.0-rc.10:
resolution: {integrity: sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -2920,30 +2812,12 @@ packages:
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
- signal-exit@4.1.0:
- resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
- engines: {node: '>=14'}
-
simple-concat@1.0.1:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
simple-get@4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
- slice-ansi@7.1.2:
- resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
- engines: {node: '>=18'}
-
- slice-ansi@8.0.0:
- resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
- engines: {node: '>=20'}
-
- sonner@2.0.7:
- resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
- peerDependencies:
- react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
- react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
-
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -2970,9 +2844,6 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
- standardwebhooks@1.0.0:
- resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==}
-
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
@@ -2980,22 +2851,10 @@ packages:
std-env@4.0.0:
resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
- string-argv@0.3.2:
- resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
- engines: {node: '>=0.6.19'}
-
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
- string-width@7.2.0:
- resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
- engines: {node: '>=18'}
-
- string-width@8.2.1:
- resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==}
- engines: {node: '>=20'}
-
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
@@ -3003,10 +2862,6 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
- strip-ansi@7.2.0:
- resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
- engines: {node: '>=12'}
-
strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
@@ -3027,9 +2882,6 @@ packages:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
- svix@1.90.0:
- resolution: {integrity: sha512-ljkZuyy2+IBEoESkIpn8sLM+sxJHQcPxlZFxU+nVDhltNfUMisMBzWX/UR8SjEnzoI28ZjCzMbmYAPwSTucoMw==}
-
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
@@ -3167,11 +3019,6 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
- uuid@10.0.0:
- resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
- deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
- hasBin: true
-
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
@@ -3299,10 +3146,6 @@ packages:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
- wrap-ansi@9.0.2:
- resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
- engines: {node: '>=18'}
-
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -3341,11 +3184,6 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
- yaml@2.8.3:
- resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
- engines: {node: '>= 14.6'}
- hasBin: true
-
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -3978,8 +3816,6 @@ snapshots:
'@rolldown/pluginutils@1.0.0-rc.7': {}
- '@stablelib/base64@1.0.1': {}
-
'@standard-schema/spec@1.1.0': {}
'@tailwindcss/node@4.2.2':
@@ -4043,12 +3879,12 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
'@tailwindcss/oxide-win32-x64-msvc': 4.2.2
- '@tailwindcss/vite@4.2.2(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))':
+ '@tailwindcss/vite@4.2.2(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))':
dependencies:
'@tailwindcss/node': 4.2.2
'@tailwindcss/oxide': 4.2.2
tailwindcss: 4.2.2
- vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)
'@tanstack/eslint-plugin-router@1.161.6(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
@@ -4120,7 +3956,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@tanstack/router-plugin@1.167.2(@tanstack/react-router@1.168.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))':
+ '@tanstack/router-plugin@1.167.2(@tanstack/react-router@1.168.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
@@ -4137,7 +3973,7 @@ snapshots:
zod: 3.25.76
optionalDependencies:
'@tanstack/react-router': 1.168.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
- vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)
transitivePeerDependencies:
- supports-color
@@ -4356,12 +4192,12 @@ snapshots:
'@typescript-eslint/types': 8.57.1
eslint-visitor-keys: 5.0.1
- '@vitejs/plugin-react@6.0.1(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))':
+ '@vitejs/plugin-react@6.0.1(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.7
- vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)
- '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)))':
+ '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)))':
dependencies:
'@bcoe/v8-coverage': 1.0.2
'@vitest/utils': 4.1.0
@@ -4373,7 +4209,7 @@ snapshots:
obug: 2.1.1
std-env: 4.0.0
tinyrainbow: 3.1.0
- vitest: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))
+ vitest: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
'@vitest/expect@4.1.0':
dependencies:
@@ -4384,22 +4220,22 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.1.0
- '@vitest/mocker@4.1.0(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))':
+ '@vitest/mocker@4.1.0(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))':
dependencies:
'@vitest/spy': 4.1.0
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)
optional: true
- '@vitest/mocker@4.1.0(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))':
+ '@vitest/mocker@4.1.0(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))':
dependencies:
'@vitest/spy': 4.1.0
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- vite: 8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite: 8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)
'@vitest/pretty-format@4.1.0':
dependencies:
@@ -4445,20 +4281,12 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
- ansi-escapes@7.3.0:
- dependencies:
- environment: 1.1.0
-
ansi-regex@5.0.1: {}
- ansi-regex@6.2.2: {}
-
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
- ansi-styles@6.2.3: {}
-
ansis@4.2.0: {}
anymatch@3.1.3:
@@ -4497,7 +4325,7 @@ snapshots:
baseline-browser-mapping@2.10.9: {}
- better-auth@1.6.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))):
+ better-auth@1.6.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))):
dependencies:
'@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0)
'@better-auth/drizzle-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))
@@ -4523,12 +4351,12 @@ snapshots:
pg: 8.20.0
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
- vitest: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))
+ vitest: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
transitivePeerDependencies:
- '@cloudflare/workers-types'
- '@opentelemetry/api'
- better-auth@1.6.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))):
+ better-auth@1.6.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.9.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))(pg@8.20.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))):
dependencies:
'@better-auth/core': 1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0)
'@better-auth/drizzle-adapter': 1.6.2(@better-auth/core@1.6.2(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.3.6))(jose@6.2.2)(kysely@0.28.16)(nanostores@1.2.0))(@better-auth/utils@0.4.0)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.9.0)(kysely@0.28.16)(pg@8.20.0))
@@ -4554,7 +4382,7 @@ snapshots:
pg: 8.20.0
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
- vitest: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))
+ vitest: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
transitivePeerDependencies:
- '@cloudflare/workers-types'
- '@opentelemetry/api'
@@ -4666,15 +4494,6 @@ snapshots:
chownr@1.1.4: {}
- cli-cursor@5.0.0:
- dependencies:
- restore-cursor: 5.1.0
-
- cli-truncate@5.2.0:
- dependencies:
- slice-ansi: 8.0.0
- string-width: 8.2.1
-
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -4691,14 +4510,10 @@ snapshots:
color-name@1.1.4: {}
- colorette@2.0.20: {}
-
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
- commander@14.0.3: {}
-
component-emitter@1.3.1: {}
concurrently@9.2.1:
@@ -4808,8 +4623,6 @@ snapshots:
electron-to-chromium@1.5.321: {}
- emoji-regex@10.6.0: {}
-
emoji-regex@8.0.0: {}
encodeurl@2.0.0: {}
@@ -4825,8 +4638,6 @@ snapshots:
entities@6.0.1: {}
- environment@1.1.0: {}
-
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@@ -5026,8 +4837,6 @@ snapshots:
etag@1.8.1: {}
- eventemitter3@5.0.4: {}
-
expand-template@2.0.3: {}
expect-type@1.3.0: {}
@@ -5078,8 +4887,6 @@ snapshots:
fast-safe-stringify@2.1.1: {}
- fast-sha256@1.3.0: {}
-
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
@@ -5148,8 +4955,6 @@ snapshots:
get-caller-file@2.0.5: {}
- get-east-asian-width@1.5.0: {}
-
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -5226,8 +5031,6 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
- husky@9.1.7: {}
-
iconv-lite@0.7.2:
dependencies:
safer-buffer: 2.1.2
@@ -5256,10 +5059,6 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
- is-fullwidth-code-point@5.1.0:
- dependencies:
- get-east-asian-width: 1.5.0
-
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
@@ -5391,36 +5190,10 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
- lint-staged@16.4.0:
- dependencies:
- commander: 14.0.3
- listr2: 9.0.5
- picomatch: 4.0.3
- string-argv: 0.3.2
- tinyexec: 1.0.4
- yaml: 2.8.3
-
- listr2@9.0.5:
- dependencies:
- cli-truncate: 5.2.0
- colorette: 2.0.20
- eventemitter3: 5.0.4
- log-update: 6.1.0
- rfdc: 1.4.1
- wrap-ansi: 9.0.2
-
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
- log-update@6.1.0:
- dependencies:
- ansi-escapes: 7.3.0
- cli-cursor: 5.0.0
- slice-ansi: 7.1.2
- strip-ansi: 7.2.0
- wrap-ansi: 9.0.2
-
lru-cache@11.2.7: {}
lru-cache@5.1.1:
@@ -5465,8 +5238,6 @@ snapshots:
mime@2.6.0: {}
- mimic-function@5.0.1: {}
-
mimic-response@3.1.0: {}
minimatch@10.2.4:
@@ -5511,10 +5282,6 @@ snapshots:
dependencies:
wrappy: 1.0.2
- onetime@7.0.0:
- dependencies:
- mimic-function: 5.0.1
-
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -5587,8 +5354,6 @@ snapshots:
picomatch@4.0.3: {}
- postal-mime@2.7.4: {}
-
postcss@8.5.8:
dependencies:
nanoid: 3.3.11
@@ -5685,20 +5450,8 @@ snapshots:
require-from-string@2.0.2: {}
- resend@6.12.2:
- dependencies:
- postal-mime: 2.7.4
- svix: 1.90.0
-
resolve-pkg-maps@1.0.0: {}
- restore-cursor@5.1.0:
- dependencies:
- onetime: 7.0.0
- signal-exit: 4.1.0
-
- rfdc@1.4.1: {}
-
rolldown@1.0.0-rc.10:
dependencies:
'@oxc-project/types': 0.120.0
@@ -5823,8 +5576,6 @@ snapshots:
siginfo@2.0.0: {}
- signal-exit@4.1.0: {}
-
simple-concat@1.0.1: {}
simple-get@4.0.1:
@@ -5833,21 +5584,6 @@ snapshots:
once: 1.4.0
simple-concat: 1.0.1
- slice-ansi@7.1.2:
- dependencies:
- ansi-styles: 6.2.3
- is-fullwidth-code-point: 5.1.0
-
- slice-ansi@8.0.0:
- dependencies:
- ansi-styles: 6.2.3
- is-fullwidth-code-point: 5.1.0
-
- sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
- dependencies:
- react: 19.2.4
- react-dom: 19.2.4(react@19.2.4)
-
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@@ -5867,34 +5603,16 @@ snapshots:
stackback@0.0.2: {}
- standardwebhooks@1.0.0:
- dependencies:
- '@stablelib/base64': 1.0.1
- fast-sha256: 1.3.0
-
statuses@2.0.2: {}
std-env@4.0.0: {}
- string-argv@0.3.2: {}
-
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
- string-width@7.2.0:
- dependencies:
- emoji-regex: 10.6.0
- get-east-asian-width: 1.5.0
- strip-ansi: 7.2.0
-
- string-width@8.2.1:
- dependencies:
- get-east-asian-width: 1.5.0
- strip-ansi: 7.2.0
-
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
@@ -5903,10 +5621,6 @@ snapshots:
dependencies:
ansi-regex: 5.0.1
- strip-ansi@7.2.0:
- dependencies:
- ansi-regex: 6.2.2
-
strip-json-comments@2.0.1: {}
superagent@10.3.0:
@@ -5939,11 +5653,6 @@ snapshots:
dependencies:
has-flag: 4.0.0
- svix@1.90.0:
- dependencies:
- standardwebhooks: 1.0.0
- uuid: 10.0.0
-
symbol-tree@3.2.4: {}
tailwindcss@4.2.2: {}
@@ -6074,11 +5783,9 @@ snapshots:
util-deprecate@1.0.2: {}
- uuid@10.0.0: {}
-
vary@1.1.2: {}
- vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3):
+ vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.3
@@ -6091,9 +5798,8 @@ snapshots:
fsevents: 2.3.3
jiti: 2.6.1
tsx: 4.21.0
- yaml: 2.8.3
- vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3):
+ vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.3
@@ -6106,12 +5812,11 @@ snapshots:
fsevents: 2.3.3
jiti: 2.6.1
tsx: 4.21.0
- yaml: 2.8.3
- vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)):
+ vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)):
dependencies:
'@vitest/expect': 4.1.0
- '@vitest/mocker': 4.1.0(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))
+ '@vitest/mocker': 4.1.0(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
'@vitest/pretty-format': 4.1.0
'@vitest/runner': 4.1.0
'@vitest/snapshot': 4.1.0
@@ -6128,7 +5833,7 @@ snapshots:
tinyexec: 1.0.4
tinyglobby: 0.2.15
tinyrainbow: 3.1.0
- vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@opentelemetry/api': 1.9.1
@@ -6138,10 +5843,10 @@ snapshots:
- msw
optional: true
- vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)):
+ vitest@4.1.0(@opentelemetry/api@1.9.1)(@types/node@25.5.0)(jsdom@29.0.1(@noble/hashes@2.2.0))(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)):
dependencies:
'@vitest/expect': 4.1.0
- '@vitest/mocker': 4.1.0(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))
+ '@vitest/mocker': 4.1.0(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0))
'@vitest/pretty-format': 4.1.0
'@vitest/runner': 4.1.0
'@vitest/snapshot': 4.1.0
@@ -6158,7 +5863,7 @@ snapshots:
tinyexec: 1.0.4
tinyglobby: 0.2.15
tinyrainbow: 3.1.0
- vite: 8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)
+ vite: 8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@opentelemetry/api': 1.9.1
@@ -6206,12 +5911,6 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
- wrap-ansi@9.0.2:
- dependencies:
- ansi-styles: 6.2.3
- string-width: 7.2.0
- strip-ansi: 7.2.0
-
wrappy@1.0.2: {}
ws@8.20.0: {}
@@ -6236,8 +5935,6 @@ snapshots:
yallist@3.1.1: {}
- yaml@2.8.3: {}
-
yargs-parser@21.1.1: {}
yargs@17.7.2: