MaveriX89
MaveriX89
Explore posts from servers
DTDrizzle Team
Created by MaveriX89 on 2/16/2025 in #help
Recommended Docker Compose Workflow
Hey all, I'm new to making full stack applications and am working with Drizzle to make mine. I have a question on what the best practice is when using databases that are spun up using Docker Compose and how to initialize them with Drizzle tables. I have the following docker-compose.yaml
networks:
web-app:

services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: postgres
networks:
- web-app
ports:
- 5432:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s

app:
build: .
depends_on:
db:
condition: service_healthy
ports:
- 8000:8000
environment:
AUTH_SECRET: <secret>
AUTH_URL: http://localhost:3000
DB_USER: postgres
DB_PASSWORD: postgres
DB_URL: postgresql://postgres:postgres@localhost:5432/postgres
networks:
- web-app
networks:
web-app:

services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: postgres
networks:
- web-app
ports:
- 5432:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s

app:
build: .
depends_on:
db:
condition: service_healthy
ports:
- 8000:8000
environment:
AUTH_SECRET: <secret>
AUTH_URL: http://localhost:3000
DB_USER: postgres
DB_PASSWORD: postgres
DB_URL: postgresql://postgres:postgres@localhost:5432/postgres
networks:
- web-app
I am using a code-first approach with Drizzle where I want my codebase to be the source of truth for the database since there is tight coupling between the DB and my API server + SPA. The question I have is, when my app is being spun up for a production deployment, how can I ensure that when my PostgresDB from docker spins up, it also prepped with my Drizzle tables via running drizzle-kit push when it comes alive? Essentially, I want to make sure that the definition of my database container being "ready" means, the Postgres container is up, and I pushed my Drizzle tables to it (alongside any optional seed script I decide to run -- I actually have a seed_db.ts script that I typically invoke locally using bun ./scripts/seed_db.ts which I would like to run for local dev workflows)
2 replies
SSolidJS
Created by MaveriX89 on 2/10/2025 in #support
Component in test is not updating despite signal change
I am currently writing some tests for a library I'm building with Solid. I have the following test:
const AuthStateComponent = () => {
const { user, signIn, signOut } = useSSO();

createEffect(() => {
console.log("auth state user >>>>", user());
});

return (
<Show
when={user()}
fallback={
<>
<div>Not Authenticated</div>
<button onClick={signIn}>Sign In</button>
</>
}
>
<div>Authenticated</div>
<div>{user()!.profile.name}</div>
<button onClick={signOut}>Sign Out</button>
</Show>
);
};

test("handles successful sign in", async () => {
let userManagerInstance!: UserManager;

function TestWithUserManager() {
const { userManager } = useSSO();
userManagerInstance = userManager();
return <AuthStateComponent />;
}

const signinRedirectMock = vi.fn(async () => {
await userManagerInstance.events.load(mockUser, true);
});

vi.spyOn(UserManager.prototype, "signinRedirect").mockImplementationOnce(signinRedirectMock);

const { findByText } = render(() => (
<SSOProvider authority={TEST_AUTHORITY} client_id={TEST_CLIENT_ID}>
<TestWithUserManager />
</SSOProvider>
));

expect(userManagerInstance).not.toBeUndefined();

await userEvent.click(await findByText("Sign In"));

expect(signinRedirectMock).toHaveBeenCalled();

await waitFor(async () => {
expect(await findByText("Authenticated")).toBeInTheDocument();
});
});
const AuthStateComponent = () => {
const { user, signIn, signOut } = useSSO();

createEffect(() => {
console.log("auth state user >>>>", user());
});

return (
<Show
when={user()}
fallback={
<>
<div>Not Authenticated</div>
<button onClick={signIn}>Sign In</button>
</>
}
>
<div>Authenticated</div>
<div>{user()!.profile.name}</div>
<button onClick={signOut}>Sign Out</button>
</Show>
);
};

test("handles successful sign in", async () => {
let userManagerInstance!: UserManager;

function TestWithUserManager() {
const { userManager } = useSSO();
userManagerInstance = userManager();
return <AuthStateComponent />;
}

const signinRedirectMock = vi.fn(async () => {
await userManagerInstance.events.load(mockUser, true);
});

vi.spyOn(UserManager.prototype, "signinRedirect").mockImplementationOnce(signinRedirectMock);

const { findByText } = render(() => (
<SSOProvider authority={TEST_AUTHORITY} client_id={TEST_CLIENT_ID}>
<TestWithUserManager />
</SSOProvider>
));

expect(userManagerInstance).not.toBeUndefined();

await userEvent.click(await findByText("Sign In"));

expect(signinRedirectMock).toHaveBeenCalled();

await waitFor(async () => {
expect(await findByText("Authenticated")).toBeInTheDocument();
});
});
So the context summary is, I have two primary things under test: SSOProvider and the useSSO hook. As you can see above, I have an internal test component called AuthStateComponent that is using the useSSO to either show an authenticated or unauthenticated view. The issue is, when I run my test and trigger a user update, the test component does not re-render to show the authenticated view. It always remains in the unauthenticated despite the fact the user has been updated internally within useSSO (I verified this via console logging everything). So that little createEffect log that I have within the test component only ever runs once, outputting a null, and it does not re-run after the user signal updates internally. I'm currently stuck in testing this particular area and can't proceed. Not sure exactly what I'm doing wrong here.
2 replies
SSolidJS
Created by MaveriX89 on 2/5/2025 in #support
How to filter/remove elements from an array within a Store
If I have the following createStore invocation:
export type WeeklyTimesheetSubmission = {
readonly cards: TimesheetCard[];
};

function SomeComponent() {
const [submission, setSubmission] = createStore<WeeklyTimesheetSubmission>({
cards: [],
});
}
export type WeeklyTimesheetSubmission = {
readonly cards: TimesheetCard[];
};

function SomeComponent() {
const [submission, setSubmission] = createStore<WeeklyTimesheetSubmission>({
cards: [],
});
}
What is the proper way to filter/remove an element from that cards list? I have tried the following and I get yelled at by TypeScript saying it's incorrect.
setSubmission("cards", (c) => c.id !== card.id); // Attempt 1: Fail
setSubmission("cards", (c) => c.id !== card.id, undefined!) // Attempt 2: Fail
setSubmission("cards", (c) => c.id !== card.id); // Attempt 1: Fail
setSubmission("cards", (c) => c.id !== card.id, undefined!) // Attempt 2: Fail
Since the cards field is at the top level, is the only option to reference the previous value of the store?
setSubmission((prev) => ({ ...prev, cards: prev.cards.filter((c) => c.id !== card.id) }))
setSubmission((prev) => ({ ...prev, cards: prev.cards.filter((c) => c.id !== card.id) }))
6 replies
SSolidJS
Created by MaveriX89 on 1/22/2025 in #support
Exception thrown when invoking useNavigate within Router root layout
I am having a real hard time working through this problem. I feel like I've tried everything but nothing has been working and there's probably something I'm not seeing clearly anymore. The short of it is, when I attempt to invoke navigate("/login") from my root layout component, I am greeted with the following exception:
TypeError: Cannot read properties of undefined (reading 'path')
TypeError: Cannot read properties of undefined (reading 'path')
Here is the entirety of my App.tsx file:
import "./app.css";

import { attachDevtoolsOverlay } from "@solid-devtools/overlay";

import { createAsync, Route, Router, type RouteSectionProps, useNavigate } from "@solidjs/router";
import CircleUser from "lucide-solid/icons/circle-user";
import Clock from "lucide-solid/icons/clock-4";
import LogOut from "lucide-solid/icons/log-out";
import { createEffect, ErrorBoundary, lazy, type ParentProps, Show, Suspense } from "solid-js";

import { isMedium } from "./globalSignals";
import { authClient } from "./lib/auth-client";
import { getAuthState } from "./lib/queries";
import { cn, isNil } from "./lib/utils";
import { Navbar, NavbarLink } from "./Navbar";

const Login = lazy(() => import("./pages/Login"));
const Home = lazy(() => import("./pages/Dashboard"));

attachDevtoolsOverlay();

const MainContent = (props: ParentProps) => {
return <main class="flex flex-col h-full w-full flex-grow p-4">{props.children}</main>;
};

const RootLayout = (props: RouteSectionProps<unknown>) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

return (
<div id="app-screen" class="h-screen w-screen">
<Show when={!isNil(auth())} fallback={<MainContent {...props} />}>
<div
id="root-layout"
class={cn([
"h-full",
isMedium() ? "grid grid-rows-[1fr] grid-cols-[auto_1fr]" : "flex flex-col",
])}
>
<Navbar>
<NavbarLink href="/dashboard" icon={<CircleUser />}>
Dashboard
</NavbarLink>
<NavbarLink href="/timesheets" icon={<Clock />}>
Timesheets
</NavbarLink>
<hr class="my-3" />
<NavbarLink
href="#"
icon={<LogOut height={30} />}
onClick={() => {
authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate("/login");
},
},
});
}}
>
Sign Out
</NavbarLink>
</Navbar>
<MainContent {...props} />
</div>
</Show>
</div>
);
};

const ActiveUserSession = (props: ParentProps) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

createEffect(() => {
if (!auth()) {
navigate("/login");
}
});

return (
<Show when={!isNil(auth())}>
<Suspense>{props.children}</Suspense>
</Show>
);
};

export const App = () => {
return (
<ErrorBoundary fallback={<div>Oops! Something went wrong!</div>}>
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
</ErrorBoundary>
);
};
import "./app.css";

import { attachDevtoolsOverlay } from "@solid-devtools/overlay";

import { createAsync, Route, Router, type RouteSectionProps, useNavigate } from "@solidjs/router";
import CircleUser from "lucide-solid/icons/circle-user";
import Clock from "lucide-solid/icons/clock-4";
import LogOut from "lucide-solid/icons/log-out";
import { createEffect, ErrorBoundary, lazy, type ParentProps, Show, Suspense } from "solid-js";

import { isMedium } from "./globalSignals";
import { authClient } from "./lib/auth-client";
import { getAuthState } from "./lib/queries";
import { cn, isNil } from "./lib/utils";
import { Navbar, NavbarLink } from "./Navbar";

const Login = lazy(() => import("./pages/Login"));
const Home = lazy(() => import("./pages/Dashboard"));

attachDevtoolsOverlay();

const MainContent = (props: ParentProps) => {
return <main class="flex flex-col h-full w-full flex-grow p-4">{props.children}</main>;
};

const RootLayout = (props: RouteSectionProps<unknown>) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

return (
<div id="app-screen" class="h-screen w-screen">
<Show when={!isNil(auth())} fallback={<MainContent {...props} />}>
<div
id="root-layout"
class={cn([
"h-full",
isMedium() ? "grid grid-rows-[1fr] grid-cols-[auto_1fr]" : "flex flex-col",
])}
>
<Navbar>
<NavbarLink href="/dashboard" icon={<CircleUser />}>
Dashboard
</NavbarLink>
<NavbarLink href="/timesheets" icon={<Clock />}>
Timesheets
</NavbarLink>
<hr class="my-3" />
<NavbarLink
href="#"
icon={<LogOut height={30} />}
onClick={() => {
authClient.signOut({
fetchOptions: {
onSuccess: () => {
navigate("/login");
},
},
});
}}
>
Sign Out
</NavbarLink>
</Navbar>
<MainContent {...props} />
</div>
</Show>
</div>
);
};

const ActiveUserSession = (props: ParentProps) => {
const navigate = useNavigate();
const auth = createAsync(() => getAuthState());

createEffect(() => {
if (!auth()) {
navigate("/login");
}
});

return (
<Show when={!isNil(auth())}>
<Suspense>{props.children}</Suspense>
</Show>
);
};

export const App = () => {
return (
<ErrorBoundary fallback={<div>Oops! Something went wrong!</div>}>
<Router
root={(props) => (
<Suspense>
<RootLayout {...props} />
</Suspense>
)}
>
<Route path="/login" component={Login} />
<Route
path="/"
component={(props) => (
<Suspense>
<ActiveUserSession>{props.children}</ActiveUserSession>
</Suspense>
)}
>
<Route path="/dashboard" component={Home} />
</Route>
<Route path="*" component={() => <div>Not Found</div>} />
</Router>
</ErrorBoundary>
);
};
The area to focus on is the onClick for the Sign Out NavbarLink. In the callback, I am invoking the Better Auth client to sign out and on success, I navigate to /login. It's that invocation that causes the exception. I'm not exactly sure the proper way to debug this and could use all the help I can get
33 replies
BABetter Auth
Created by MaveriX89 on 1/21/2025 in #help
Better Auth + Solid Start
When using Better Auth within Solid Start -- after doing all the required configuration, do we simply use the Better Auth server client (instead of the client one) to invoke all auth related matters? So given a Solid Start action like below:
export const logIn = action((formData: FormData) => {
"use server"
const username = String(formData.get("username"));
const password = String(formData.get("password"));

let error = validateUsername(username) || validatePassword(password);

if (error) {
return new Error(error);
}

try {
// Better Auth Server client
const response = await auth.api.signInUsername({ body: { username, password } });
// other stuff..
} catch (err) {
return err as Error;
}

throw redirect("/");
}, "logIn");
export const logIn = action((formData: FormData) => {
"use server"
const username = String(formData.get("username"));
const password = String(formData.get("password"));

let error = validateUsername(username) || validatePassword(password);

if (error) {
return new Error(error);
}

try {
// Better Auth Server client
const response = await auth.api.signInUsername({ body: { username, password } });
// other stuff..
} catch (err) {
return err as Error;
}

throw redirect("/");
}, "logIn");
Is the above the expected way we interface with Better Auth on the server? Or is there some other behind the scenes stuff happening due to the below configuration?
// routes/api/*auth.ts
import { auth } from "~/lib/auth";
import { toSolidStartHandler } from "better-auth/solid-start";

export const { GET, POST } = toSolidStartHandler(auth);
// routes/api/*auth.ts
import { auth } from "~/lib/auth";
import { toSolidStartHandler } from "better-auth/solid-start";

export const { GET, POST } = toSolidStartHandler(auth);
Or lastly, should we still be using the Better Auth client on the frontend and do:
const response = await authClient.signIn.username(credentials));
const response = await authClient.signIn.username(credentials));
3 replies
SSolidJS
Created by MaveriX89 on 1/21/2025 in #support
SolidJS SPA with Better Auth
I am trying to build a SolidJS SPA that uses the new Better Auth library to manage all things related to authentication/authorization for my app. Post all the needed configuration for it, on the SPA side, I have to use the Better Auth client to invoke all the auth related methods on my UI. I have one example here that I'm wrestling with in trying to get a redirect to happen after the user signs in but the redirect is not happening on the Solid SPA side: My Better Auth Client configuration:
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
My SignIn component:
import { Button } from "@kobalte/core/button";
import { action, query, redirect } from "@solidjs/router";
import { createEffect } from "solid-js";

import Logo from "../assets/logo_login.svg";
import { TextInput } from "../components/TextInput";
import { authClient } from "../lib/auth-client";
import { convertToRecord } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

export const getUserSession = query(() => authClient.getSession(), "userSession");

const signIn = action(async (data: FormData) => {
authClient.signIn.username(convertToRecord<SignInForm>(data), {
onSuccess: () => {
console.log("ON SUCCESS >>>>") // this logs correctly
throw redirect("/home", { revalidate: getUserSession.key }); // <--- this does not redirect
},
onError: (ctx) => {
alert(ctx.error.message);
},
});
});

export default function SignIn() {
return (
<div class="h-full flex flex-col items-center justify-center gap-2">
<img src={Logo} width="200" />
<form action={signIn} method="post" class="flex flex-col gap-4">
<TextInput name="username" label="Username" />
<TextInput name="password" label="Password" type="password" />
<Button class="button" type="submit">
Login
</Button>
</form>
</div>
);
}
import { Button } from "@kobalte/core/button";
import { action, query, redirect } from "@solidjs/router";
import { createEffect } from "solid-js";

import Logo from "../assets/logo_login.svg";
import { TextInput } from "../components/TextInput";
import { authClient } from "../lib/auth-client";
import { convertToRecord } from "../lib/utils";

type SignInForm = {
readonly username: string;
readonly password: string;
};

export const getUserSession = query(() => authClient.getSession(), "userSession");

const signIn = action(async (data: FormData) => {
authClient.signIn.username(convertToRecord<SignInForm>(data), {
onSuccess: () => {
console.log("ON SUCCESS >>>>") // this logs correctly
throw redirect("/home", { revalidate: getUserSession.key }); // <--- this does not redirect
},
onError: (ctx) => {
alert(ctx.error.message);
},
});
});

export default function SignIn() {
return (
<div class="h-full flex flex-col items-center justify-center gap-2">
<img src={Logo} width="200" />
<form action={signIn} method="post" class="flex flex-col gap-4">
<TextInput name="username" label="Username" />
<TextInput name="password" label="Password" type="password" />
<Button class="button" type="submit">
Login
</Button>
</form>
</div>
);
}
I'm suspecting that perhaps, the issue has to do with the fact that I am doing the throw redirect inside of the onSuccess handler of the Better Auth client as opposed to the scope of the action itself? Wondering if others have attempted this yet.
5 replies
SSolidJS
Created by MaveriX89 on 1/21/2025 in #support
TanStack Query vs Solid Router
I'm looking to use Solidjs for a production app and have a question about a library I typically reach for (TanStack Query) for my SPAs and some of the built-in APIs for Solid Router: query + createAsync. Is there any reason to use TanStack when Solid Router comes with those built-in? Just wondering if there is something I'm missing because, the query + createAsync combination if I'm not mistaken provides a lot of the same benefits that TanStack does with request de-deduping and caching by key. Wondering if there are other gaps present that I am not privy to (e.g. easy request refetching/polling on interval or conditional fetching like the enabled field from TanStack Query). For the visually inclined -- the question is what is the recommendation between these two options and the pros/cons of each: Option 1:
import { createQuery } from "@tanstack/solid-query";

function SomeComponent() {
const query = createQuery(() => ({
queryKey: ["userSession"],
queryFn: async () => {
const response = await fetch(...);
if (!response.ok) throw new Error("Failed to fetch data");
return response.json()
},
enabled: true,
refetchInterval: 5000,
}));
}
import { createQuery } from "@tanstack/solid-query";

function SomeComponent() {
const query = createQuery(() => ({
queryKey: ["userSession"],
queryFn: async () => {
const response = await fetch(...);
if (!response.ok) throw new Error("Failed to fetch data");
return response.json()
},
enabled: true,
refetchInterval: 5000,
}));
}
VS Option 2:
import { createAsync, query } from "@solidjs/router";
const getUserSession = query(() => fetch(...), "userSession");

function SomeComponent() {
const session = createAsync(() => getUserSession());
// do stuff ...
}
import { createAsync, query } from "@solidjs/router";
const getUserSession = query(() => fetch(...), "userSession");

function SomeComponent() {
const session = createAsync(() => getUserSession());
// do stuff ...
}
P.S. Just throwing it out there but, man, Option 2 look really good haha
17 replies
BABetter Auth
Created by MaveriX89 on 1/21/2025 in #help
Getting UNPROCESSABLE_ENTITY when attempting to sign up a new user
Hitting another issue with Better Auth where I get an error when attempting to do the following:
authClient.signUp.email({
name: "Test User",
password: "password1234",
username: "test"
})
authClient.signUp.email({
name: "Test User",
password: "password1234",
username: "test"
})
I receive this error:
BetterCallAPIError: API Error: UNPROCESSABLE_ENTITY Failed to create user
cause: {
"message": "Failed to create user",
"details": {
"length": 164,
"name": "error",
"severity": "ERROR",
"code": "22P02",
"where": "unnamed portal parameter $1 = '...'",
"originalLine": 156,
"originalColumn": 12,
"file": "uuid.c",
"routine": "string_to_uuid",
"stack": "error: invalid input syntax for type uuid: \"fc0vhdcGvNCF6VDIqXCvfSAxuLCAMxJc\"\n at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/time-tracker/node_modules/pg-pool/index.js:45:11)\n at processTicksAndRejections (native:7:39)"
},
"code": "FAILED_TO_CREATE_USER"
}
BetterCallAPIError: API Error: UNPROCESSABLE_ENTITY Failed to create user
cause: {
"message": "Failed to create user",
"details": {
"length": 164,
"name": "error",
"severity": "ERROR",
"code": "22P02",
"where": "unnamed portal parameter $1 = '...'",
"originalLine": 156,
"originalColumn": 12,
"file": "uuid.c",
"routine": "string_to_uuid",
"stack": "error: invalid input syntax for type uuid: \"fc0vhdcGvNCF6VDIqXCvfSAxuLCAMxJc\"\n at <anonymous> (/Users/dsanchez/Developer/github/dsanchez/time-tracker/node_modules/pg-pool/index.js:45:11)\n at processTicksAndRejections (native:7:39)"
},
"code": "FAILED_TO_CREATE_USER"
}
I'm wondering if this is because Better Auth does not like the fact that I modified the primary keys of the default Drizzle tables it generated for me to use uuid instead of integer?
import { pgSchema, uuid } from "drizzle-orm/pg-core";

export const authSchema = pgSchema("auth");

export const UserTable = authSchema.table("user", {
id: uuid("id").defaultRandom().primaryKey(), // <-- did this to all the default tables
});
import { pgSchema, uuid } from "drizzle-orm/pg-core";

export const authSchema = pgSchema("auth");

export const UserTable = authSchema.table("user", {
id: uuid("id").defaultRandom().primaryKey(), // <-- did this to all the default tables
});
Appreciate any and all help with this
3 replies
BABetter Auth
Created by MaveriX89 on 1/21/2025 in #help
Getting INVALID_USERNAME_OR_PASSWORD Error when using correct credentials
I am new to Better Auth and am using the username() plugin to enable username/password logins in my ElysiaJS backend. I have the following configurations for both the server and client for Better Auth:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { username } from "better-auth/plugins";

import { db } from "../drizzle/db";
import {
SessionTable,
UserAccountTable,
UserTable,
VerificationTable,
} from "../drizzle/schemas/auth";

export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema: {
user: UserTable,
account: UserAccountTable,
session: SessionTable,
verification: VerificationTable,
},
}),
plugins: [username()],
trustedOrigins: ["http://localhost:3000"],
session: {
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Cache duration in seconds
},
},
});
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { username } from "better-auth/plugins";

import { db } from "../drizzle/db";
import {
SessionTable,
UserAccountTable,
UserTable,
VerificationTable,
} from "../drizzle/schemas/auth";

export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema: {
user: UserTable,
account: UserAccountTable,
session: SessionTable,
verification: VerificationTable,
},
}),
plugins: [username()],
trustedOrigins: ["http://localhost:3000"],
session: {
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Cache duration in seconds
},
},
});
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
I made some changes to the default Drizzle tables that Better Auth generated in my project:
import { sql } from "drizzle-orm";
import { boolean, check, decimal, pgSchema, text, timestamp, uuid } from "drizzle-orm/pg-core";

export const authSchema = pgSchema("auth");
export const UserTable = authSchema.table("user", {...});
export const UserAccountTable = authSchema.table("user_account", {...});
export const SessionTable = authSchema.table("session", {...});
export const VerificationTable = authSchema.table("verification", {...});
import { sql } from "drizzle-orm";
import { boolean, check, decimal, pgSchema, text, timestamp, uuid } from "drizzle-orm/pg-core";

export const authSchema = pgSchema("auth");
export const UserTable = authSchema.table("user", {...});
export const UserAccountTable = authSchema.table("user_account", {...});
export const SessionTable = authSchema.table("session", {...});
export const VerificationTable = authSchema.table("verification", {...});
With the above said, when I am doing local development, I am spinning up a Postgres docker container and run a custom seed script that I have to populate the tables with fake data. I'm probably not doing something correctly there in that step because when I attempt to log in with the fake admin user I added to the UserTable, I keep getting INVALID_USERNAME_OR_PASSWORD back as a response. Here is a small snippet of what I am doing:
const passwordMap: Record<string, string> = {
admin: "administrator",
};

const users = [
{
name: "Super User",
username: "admin",
emailVerified: true,
},
];

for (const user of users) {
const inserted = await db
.insert(UserTable)
.values(user)
.returning({ userId: UserTable.id, username: UserTable.username });

const insertedUser = inserted[0] as { userId: string; username: string };

await db.insert(UserAccountTable).values({
userId: insertedUser?.userId,
password: passwordMap[insertedUser.username] ?? faker.internet.password(),
});
const passwordMap: Record<string, string> = {
admin: "administrator",
};

const users = [
{
name: "Super User",
username: "admin",
emailVerified: true,
},
];

for (const user of users) {
const inserted = await db
.insert(UserTable)
.values(user)
.returning({ userId: UserTable.id, username: UserTable.username });

const insertedUser = inserted[0] as { userId: string; username: string };

await db.insert(UserAccountTable).values({
userId: insertedUser?.userId,
password: passwordMap[insertedUser.username] ?? faker.internet.password(),
});
Has anybody else run into this problem when using the username login?
1 replies
BABetter Auth
Created by MaveriX89 on 1/21/2025 in #help
Help in resolving CORS issue with Better Auth
This is my first time using Better Auth and I am pairing it with Elysia and a Solid SPA. The Elysia backend is a dedicated backend-for-frontend that will serve the frontend in production so they will share the same origin url. I am currently running into CORS issues when I attempt to sign in using the username plug-in. Below are my configurations: For my frontend SPA, here is my vite.config.ts. The thing to note about it is the server.proxy configuration I have defined. When my frontend makes a request to http://localhost:3000/api/* it'll route it to port 8000 instead as that is the port where my ElysiaJS backend runs on in a sidecar shell.
import { defineConfig as defineViteConfig, mergeConfig } from "vite";
import solidPlugin from "vite-plugin-solid";

const viteConfig = defineViteConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
proxy: {
// Proxy API requests to the backend port in development
"/api": "http://localhost:8000",
},
},
build: {
target: "esnext",
},
resolve: {
conditions: ["development", "browser"],
},
});

// ...other stuff
import { defineConfig as defineViteConfig, mergeConfig } from "vite";
import solidPlugin from "vite-plugin-solid";

const viteConfig = defineViteConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
proxy: {
// Proxy API requests to the backend port in development
"/api": "http://localhost:8000",
},
},
build: {
target: "esnext",
},
resolve: {
conditions: ["development", "browser"],
},
});

// ...other stuff
My frontend Better Auth client config is the following:
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [usernameClient()],
});
Moving on to the backend side of things, here is my Better Auth server config:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { username } from "better-auth/plugins";

import { db } from "../drizzle/db";

export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
}),
plugins: [username()],
session: {
cookieCache: {
enabled: true,
maxAge: 5 * 60 // Cache duration in seconds
}
},
});
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { username } from "better-auth/plugins";

import { db } from "../drizzle/db";

export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
}),
plugins: [username()],
session: {
cookieCache: {
enabled: true,
maxAge: 5 * 60 // Cache duration in seconds
}
},
});
And here is my Elysia setup for Better Auth:
import type { Session, User } from "better-auth";
import { type Context, Elysia } from "elysia";

import { auth } from "../lib/auth";

// user middleware (compute user and session and pass to routes)
const userMiddleware = async (request: Request) => {
const session = (await auth.api.getSession({ headers: request.headers })) || {
user: null,
session: null,
};

return {
user: session.user,
session: session.session,
};
};

// https://www.better-auth.com/docs/integrations/elysia
const betterAuthView = async (context: Context) => {
const BETTER_AUTH_ACCEPT_METHODS = ["POST", "GET"];

// validate request method
if (BETTER_AUTH_ACCEPT_METHODS.includes(context.request.method)) {
return auth.handler(context.request);
} else {
context.error(405);
}
};

// user info view
// type User can be export from `typeof auth.$Infer.Session.user`
// type Session can be export from `typeof auth.$Infer.Session.session`
const userInfo = (user: User | null, session: Session | null) => {
return {
user: user,
session: session,
};
};

const api = new Elysia({ prefix: "/api" })
.derive(({ request }) => userMiddleware(request))
.all("/auth/*", betterAuthView)
.get("/user", ({ user, session }) => userInfo(user, session))
.get("/hello", () => ({ message: "Hello World API!" }));

export default api;
import type { Session, User } from "better-auth";
import { type Context, Elysia } from "elysia";

import { auth } from "../lib/auth";

// user middleware (compute user and session and pass to routes)
const userMiddleware = async (request: Request) => {
const session = (await auth.api.getSession({ headers: request.headers })) || {
user: null,
session: null,
};

return {
user: session.user,
session: session.session,
};
};

// https://www.better-auth.com/docs/integrations/elysia
const betterAuthView = async (context: Context) => {
const BETTER_AUTH_ACCEPT_METHODS = ["POST", "GET"];

// validate request method
if (BETTER_AUTH_ACCEPT_METHODS.includes(context.request.method)) {
return auth.handler(context.request);
} else {
context.error(405);
}
};

// user info view
// type User can be export from `typeof auth.$Infer.Session.user`
// type Session can be export from `typeof auth.$Infer.Session.session`
const userInfo = (user: User | null, session: Session | null) => {
return {
user: user,
session: session,
};
};

const api = new Elysia({ prefix: "/api" })
.derive(({ request }) => userMiddleware(request))
.all("/auth/*", betterAuthView)
.get("/user", ({ user, session }) => userInfo(user, session))
.get("/hello", () => ({ message: "Hello World API!" }));

export default api;
On the frontend, when I attempt to execute authClient.signIn.username(...) I get the following error:
ERROR [Better Auth]: Invalid origin: http://localhost:3000
INFO [Better Auth]: If it's a valid URL, please add http://localhost:3000 to trustedOrigins in your auth config
Current list of trustedOrigins: http://localhost:3000/api/auth,http://localhost:8000
ERROR [Better Auth]: Invalid origin: http://localhost:3000
INFO [Better Auth]: If it's a valid URL, please add http://localhost:3000 to trustedOrigins in your auth config
Current list of trustedOrigins: http://localhost:3000/api/auth,http://localhost:8000
Clearly, http://localhost:3000 origin is not on that list which is why I'm getting hit with CORS, but I guess I'm wondering the question why since http://localhost:3000/api/auth is there. And what is the recommended way to resolve this? Hoping to avoid explicit origins..
1 replies
SSolidJS
Created by MaveriX89 on 1/13/2025 in #support
Getting UnhandledPromiseRejection in Solid Start server function that stops dev server
I have an async function in my Solid Start app and am receiving the following error message whenever it returns an Error.
UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "#<_Response>".
at throwUnhandledRejectionsMode (node:internal/process/promises:392:7)
at processPromiseRejections (node:internal/process/promises:475:17)
at process.processTicksAndRejections (node:internal/process/task_queues:106:32) {
code: 'ERR_UNHANDLED_REJECTION'
}
UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "#<_Response>".
at throwUnhandledRejectionsMode (node:internal/process/promises:392:7)
at processPromiseRejections (node:internal/process/promises:475:17)
at process.processTicksAndRejections (node:internal/process/task_queues:106:32) {
code: 'ERR_UNHANDLED_REJECTION'
}
Here is the function:
export async function loginOrRegister(formData: FormData) {
const username = String(formData.get("username"));
const password = String(formData.get("password"));

let error = validateUsername(username) || validatePassword(password);

if (error) {
// Promise.reject(new Error(error)). // <--- does not cause rejection, but UI does not show error message
return new Error(error); // <--- UI shows error message but dev server stops due to UnhandledPromiseRejection
}

try {
const user = await login(username, password);
const session = await getSession();
await session.update((d) => {
d.userId = user.id;
});
} catch (err) {
return err as Error;
}

throw redirect("/");
}
export async function loginOrRegister(formData: FormData) {
const username = String(formData.get("username"));
const password = String(formData.get("password"));

let error = validateUsername(username) || validatePassword(password);

if (error) {
// Promise.reject(new Error(error)). // <--- does not cause rejection, but UI does not show error message
return new Error(error); // <--- UI shows error message but dev server stops due to UnhandledPromiseRejection
}

try {
const user = await login(username, password);
const session = await getSession();
await session.update((d) => {
d.userId = user.id;
});
} catch (err) {
return err as Error;
}

throw redirect("/");
}
Not sure exactly what's going on and hoping someone can perhaps provide insight.
33 replies
DTDrizzle Team
Created by MaveriX89 on 1/8/2025 in #help
drizzle-kit push not working with pgSchema
Hello, this is my first time using Drizzle and am running into some problems pushing my schema out. I have the following docker-compose.yaml that is spinning up a Postgres instance alongside Adminer.
services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: example
ports:
- 5432:5432

adminer:
image: adminer
restart: always
ports:
- 8080:8080
services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: example
ports:
- 5432:5432

adminer:
image: adminer
restart: always
ports:
- 8080:8080
I am using a codebase first approach with Drizzle and have the following schema defined under the following path <project root>/drizzle/schema/auth.ts
import { sql } from "drizzle-orm";
import { boolean, check, pgRole, pgSchema, text, timestamp, uuid } from "drizzle-orm/pg-core";

export const admin = pgRole("admin", { createRole: true, createDb: true, inherit: true });

export const authSchema = pgSchema("auth");

export const user = authSchema.table("user", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
username: text("username").notNull().unique(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").notNull(),
image: text("image"),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
});

export const userAccount = authSchema.table(
"useraccount",
{
id: uuid("id").defaultRandom().primaryKey(),
accountId: text("account_id"),
providerId: text("provider_id"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
scope: text("scope"),
password: text("password").notNull(),
accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true }),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
},
(table) => [
{
checkConstraint: check("password_check", sql`char_length(${table.password}) >= 8`),
},
],
);

export const session = authSchema.table("session", {
id: uuid("id").defaultRandom().primaryKey(),
token: text("token").notNull().unique(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
expiresAt: timestamp("expires_at", { withTimezone: true }).default(
sql`(now() + interval '7 days')`,
),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
});

export const verification = authSchema.table("verification", {
id: uuid("id").defaultRandom().primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at", { withTimezone: true }).default(
sql`(now() + interval '7 days')`,
),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
});
import { sql } from "drizzle-orm";
import { boolean, check, pgRole, pgSchema, text, timestamp, uuid } from "drizzle-orm/pg-core";

export const admin = pgRole("admin", { createRole: true, createDb: true, inherit: true });

export const authSchema = pgSchema("auth");

export const user = authSchema.table("user", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
username: text("username").notNull().unique(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").notNull(),
image: text("image"),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
});

export const userAccount = authSchema.table(
"useraccount",
{
id: uuid("id").defaultRandom().primaryKey(),
accountId: text("account_id"),
providerId: text("provider_id"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
scope: text("scope"),
password: text("password").notNull(),
accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true }),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
},
(table) => [
{
checkConstraint: check("password_check", sql`char_length(${table.password}) >= 8`),
},
],
);

export const session = authSchema.table("session", {
id: uuid("id").defaultRandom().primaryKey(),
token: text("token").notNull().unique(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
expiresAt: timestamp("expires_at", { withTimezone: true }).default(
sql`(now() + interval '7 days')`,
),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
});

export const verification = authSchema.table("verification", {
id: uuid("id").defaultRandom().primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at", { withTimezone: true }).default(
sql`(now() + interval '7 days')`,
),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
});
And this is my drizzle.config.ts:
import { defineConfig } from "drizzle-kit";

export default defineConfig({
dialect: "postgresql",
schema: "./drizzle/schema/*",
out: "./drizzle/migrations/",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
import { defineConfig } from "drizzle-kit";

export default defineConfig({
dialect: "postgresql",
schema: "./drizzle/schema/*",
out: "./drizzle/migrations/",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
I was able to successfully do drizzle-kit push and I did this while my databse was running in a container. However, when I open up Adminer to inspect the database, I do not see my auth schema shown in the UI nor any of the tables I have defined. Hoping someone can assist me here. Thanks in advance!
8 replies
SSolidJS
Created by MaveriX89 on 1/7/2025 in #support
Containerized Solid Start Deployments
I'm new to using Solid Start and full-stack frameworks -- I usually create "vanilla" app architectures (e.g. a Solidjs SPA paired with an Elysia backend-for-frontend layer). With those vanilla setups, I could containerize the deployments using Dockerfiles and reason about them very easily. However, for Solid Start, I'm not sure how to reason about them fully because there are gaps in my knowledge. The intent I have is to create a containerized Solid Start deployment that I could run anywhere (Azure, AWS, GCP, etc.). In my app, I am using a database (SQLite + Drizzle ORM) and Better Auth for authentication/authorization (it adds tables to the database mentioned earlier). I would also like to use Bun for my runtime -- unsure if this is a wise-decision if Bun isn't supported fully across hosting providers. I had the following Dockerfile but unfortunately it does not work as the docker build chokes when attempting to install the better-sqlite3 dependency I have in my project:
FROM oven/bun:1.1.42-alpine

WORKDIR /app

COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY public ./public
COPY tsconfig.json ./
COPY app.config.ts ./
COPY drizzle ./drizzle
COPY src ./src

RUN bun run build

EXPOSE 3000

CMD ["bun", "start"]
FROM oven/bun:1.1.42-alpine

WORKDIR /app

COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY public ./public
COPY tsconfig.json ./
COPY app.config.ts ./
COPY drizzle ./drizzle
COPY src ./src

RUN bun run build

EXPOSE 3000

CMD ["bun", "start"]
I suspect it has something to do with the base Bun image I'm using, but I went and used the full oven/bun base image and that still did not resolve the install block on better-sqlite3. Hoping someone with more experience in this arena can assist or guide my thinking so that I can better understand how to work through these kinds of problems. Thanks for any help in advance! P.S. I feel like this is a great opportunity to perhaps update the Solid Start documentation to include a section on Deployments and what that looks like if people want to pursue the whole "build once and deploy anywhere" mantra. That may be a pipe dream because I'm sure there are a lot of fancy things going on under the hood that may make that hard to generalize, but just a thought that I had and wanted to share here. 🙂
2 replies
SSolidJS
Created by MaveriX89 on 4/30/2024 in #support
Vite 5.2.x / Vitest 1.5.x: How to Resolve Multiple Instances of Solid
I am nearing my wits end trying to resolve these error messages I receive when trying to run my SolidJS tests with @solidjs/testing-library and vitest
stderr | file:~/Developer/github/.../nm/node_modules/solid-js/dist/dev.js:1932:13
You appear to have multiple instances of Solid. This can lead to unexpected behavior.

stderr | src/__tests__/App.test.tsx > <App /> > it will render an text input and a button
computations created outside a `createRoot` or `render` will never be disposed
stderr | file:~/Developer/github/.../nm/node_modules/solid-js/dist/dev.js:1932:13
You appear to have multiple instances of Solid. This can lead to unexpected behavior.

stderr | src/__tests__/App.test.tsx > <App /> > it will render an text input and a button
computations created outside a `createRoot` or `render` will never be disposed
I do not know what else to try and need assistance if anyone can help. I have the following vite.config.ts
/// <reference types="vitest" />
/// <reference types="vite/client" />

import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
import { configDefaults } from "vitest/config";

export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
proxy: {
// Proxy API requests to the backend port in development
"/api": "http://localhost:8000",
},
},
build: {
target: "esnext",
copyPublicDir: false,
},
resolve: {
conditions: ["development", "browser"],
},
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./setupTests.ts"],
testTransformMode: { web: ["/.[jt]sx?$/"] },
server: {
deps: {
inline: [/solid-js/],
},
},
deps: {
optimizer: {
web: {
enabled: true,
include: ['solid-js'],
}
}
},
coverage: {
all: true,
provider: "istanbul",
thresholds: {
"100": true,
},
},
},
});
/// <reference types="vitest" />
/// <reference types="vite/client" />

import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";
import { configDefaults } from "vitest/config";

export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
proxy: {
// Proxy API requests to the backend port in development
"/api": "http://localhost:8000",
},
},
build: {
target: "esnext",
copyPublicDir: false,
},
resolve: {
conditions: ["development", "browser"],
},
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./setupTests.ts"],
testTransformMode: { web: ["/.[jt]sx?$/"] },
server: {
deps: {
inline: [/solid-js/],
},
},
deps: {
optimizer: {
web: {
enabled: true,
include: ['solid-js'],
}
}
},
coverage: {
all: true,
provider: "istanbul",
thresholds: {
"100": true,
},
},
},
});
In my package.json these are the dependencies I have:
{
"dependencies": {
"@elysiajs/static": "^1.0.3",
"elysia": "^1.0.15",
"solid-js": "^1.8.17"
},
"devDependencies": {
"@elysiajs/eden": "^1.0.12",
"@solidjs/testing-library": "^0.8.7",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/user-event": "^14.5.2",
"@types/bun": "^1.1.0",
"@types/node": "20.10.0",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"@vitest/coverage-istanbul": "^1.5.2",
"eslint": "^8.56.0",
"eslint-plugin-simple-import-sort": "^12.1.0",
"eslint-plugin-solid": "^0.14.0",
"jsdom": "^24.0.0",
"prettier": "^3.2.5",
"typescript": "5.4.5",
"vite": "^5.2.10",
"vite-plugin-solid": "^2.10.2",
"vitest": "^1.5.2"
},
}
{
"dependencies": {
"@elysiajs/static": "^1.0.3",
"elysia": "^1.0.15",
"solid-js": "^1.8.17"
},
"devDependencies": {
"@elysiajs/eden": "^1.0.12",
"@solidjs/testing-library": "^0.8.7",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/user-event": "^14.5.2",
"@types/bun": "^1.1.0",
"@types/node": "20.10.0",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"@vitest/coverage-istanbul": "^1.5.2",
"eslint": "^8.56.0",
"eslint-plugin-simple-import-sort": "^12.1.0",
"eslint-plugin-solid": "^0.14.0",
"jsdom": "^24.0.0",
"prettier": "^3.2.5",
"typescript": "5.4.5",
"vite": "^5.2.10",
"vite-plugin-solid": "^2.10.2",
"vitest": "^1.5.2"
},
}
6 replies
SSolidJS
Created by MaveriX89 on 2/4/2024 in #support
How to handle necessary async/await work inside of a createEffect?
I'm building a SolidJS hook library and I have some asynchronous work that needs to happen which is occurring in a createEffect. I'm currently trying to write integration tests against my hook library and running into problems because I'm currently not awaiting the Promise result which is conflicting with other things occurirng in test. The way I work around it for testing purposes is literally doing an await sleep(1000) in test to wait for that asynchronous work to finish. Is there a better way to write my createEffect in such a way where I do not have to do a sleep in test?
function createDocument<T extends DocRecord<T>>(initialDocFn: Accessor<Doc<T>>): CreateDocumentResult<T> {
const [doc, setDoc] = createSignal(initialDocFn());
const initialDocId = () => initialDocFn()._id;

const refreshDoc = async (db: Database, docId = "") => {
const storedDoc = await db.get<T>(docId).catch(initialDocFn);
setDoc(() => storedDoc);
};

createEffect(() => {
// This is the problematic instruction. How can I re-express this to properly await it?
void refreshDoc(database(), initialDocId());
});
}
function createDocument<T extends DocRecord<T>>(initialDocFn: Accessor<Doc<T>>): CreateDocumentResult<T> {
const [doc, setDoc] = createSignal(initialDocFn());
const initialDocId = () => initialDocFn()._id;

const refreshDoc = async (db: Database, docId = "") => {
const storedDoc = await db.get<T>(docId).catch(initialDocFn);
setDoc(() => storedDoc);
};

createEffect(() => {
// This is the problematic instruction. How can I re-express this to properly await it?
void refreshDoc(database(), initialDocId());
});
}
8 replies
SSolidJS
Created by MaveriX89 on 1/28/2024 in #support
Getting ReferenceError: React is not defined with Vitest + Solid Testing Library
I honestly have no idea how I am receiving this error but I am despite testing using @solidjs/testing-library. Moreover, I am using Vitest by itself (not Vite) and my vitest.config.ts is as follows:
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./setupTests.ts"],
deps: {
optimizer: {
web: { enabled: true },
ssr: { enabled: true },
},
},
isolate: false,
coverage: {
provider: "istanbul",
},
},
});
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./setupTests.ts"],
deps: {
optimizer: {
web: { enabled: true },
ssr: { enabled: true },
},
},
isolate: false,
coverage: {
provider: "istanbul",
},
},
});
The test that is causing the error to appear is a custom Solid hook test where I am attempting to pass a wrapper to my renderHook invocation.
import { renderHook } from "@solidjs/testing-library";
import { describe, expect, it } from "vitest";

import { createDbQuery } from "../createDbQuery";

function FakeComponent() {
return <div />
}

describe("HOOK: createDbQuery", () => {
it("can be used as expected", async () => {
const { result } = renderHook(() => createDbQuery((d) => d), { wrapper: FakeComponent });
const db = createDbQuery.database();

expect(result()).toEqual({ rows: [], docs: [] });
await db.put({ good: true });
expect(result().docs.length).toBe(1);
});
});
import { renderHook } from "@solidjs/testing-library";
import { describe, expect, it } from "vitest";

import { createDbQuery } from "../createDbQuery";

function FakeComponent() {
return <div />
}

describe("HOOK: createDbQuery", () => {
it("can be used as expected", async () => {
const { result } = renderHook(() => createDbQuery((d) => d), { wrapper: FakeComponent });
const db = createDbQuery.database();

expect(result()).toEqual({ rows: [], docs: [] });
await db.put({ good: true });
expect(result().docs.length).toBe(1);
});
});
ReferenceError: React is not defined
❯ Object.FakeComponent [as wrapper] src/__tests__/createDbQuery.test.tsx:7:3
5|
6| function FakeComponent() {
7| return <div />
| ^
8| }
9|
ReferenceError: React is not defined
❯ Object.FakeComponent [as wrapper] src/__tests__/createDbQuery.test.tsx:7:3
5|
6| function FakeComponent() {
7| return <div />
| ^
8| }
9|
Anybody seen that before?
26 replies
SSolidJS
Created by MaveriX89 on 1/27/2024 in #support
Creating custom SolidJS hook that can be used in a global scope
Need some help understanding how to build a custom SolidJS hook that can be used both locally inside a component and outside in the global scope (like createSignal can). Below is. a small snippet of a custom hook I am writing:
export function createDatabase(dbOrName?: string | Database, config: ConfigOpts = {}): CreateDatabase {

const database = createMemo(() => (isDatabase(dbOrName) ? dbOrName : genDb(dbOrName || "DefaultDB", config)));

return { database }
export function createDatabase(dbOrName?: string | Database, config: ConfigOpts = {}): CreateDatabase {

const database = createMemo(() => (isDatabase(dbOrName) ? dbOrName : genDb(dbOrName || "DefaultDB", config)));

return { database }
The question I have is around the floating createMemo that I use in the implementation. When I use the createDatabase hook globally, I get greeted with the console warning: computations created outside a 'createRoot' or 'render' will never be disposed I'm not familiar with how to properly address that warning and hoping the community can offer some guidance here.
17 replies
SSolidJS
Created by MaveriX89 on 1/1/2024 in #support
Need clarity on eslint(solid/reactivity) warning use-case
I need help understanding how to deal with this ESLint warning when I am building a custom SolidJS hook library that doesn't have any visual components. There is a portion of my custom hook code that gets called out by this warning and I don't know how I should properly resolve it (or if it is safe to ignore it). The code snippet where I receive the warning is the following:
import { Accessor, createSignal, onMount, onCleanup } from "solid-js";

type LiveQueryResult = {
readonly docs: Doc[];
readonly rows: any[];
};

// NOTE: database is an accessor defined outside of this custom hook (via createMemo). createLiveQuery is effectively the inner function of an outer closure.
const createLiveQuery = (key: string, query = {}, initialRows: any[] = []): Accessor<LiveQueryResult> => {
const [result, setResult] = createSignal({
rows: initialRows,
docs: initialRows.map((r) => r.doc),
});

const refreshRows = async () => {
const res = await database().query(key, query);
setResult({ ...res, docs: res.rows.map((r) => r.doc) });
};

onMount(() => {
// this is where I receive the ESLint warning. Specifically on the callback passed to subscribe as the `refreshRows` function has internal reactivity due to using database() under the hood.
const unsubscribe = database().subscribe(() => void refreshRows());

onCleanup(() => {
unsubscribe();
});
});

return result;
};
import { Accessor, createSignal, onMount, onCleanup } from "solid-js";

type LiveQueryResult = {
readonly docs: Doc[];
readonly rows: any[];
};

// NOTE: database is an accessor defined outside of this custom hook (via createMemo). createLiveQuery is effectively the inner function of an outer closure.
const createLiveQuery = (key: string, query = {}, initialRows: any[] = []): Accessor<LiveQueryResult> => {
const [result, setResult] = createSignal({
rows: initialRows,
docs: initialRows.map((r) => r.doc),
});

const refreshRows = async () => {
const res = await database().query(key, query);
setResult({ ...res, docs: res.rows.map((r) => r.doc) });
};

onMount(() => {
// this is where I receive the ESLint warning. Specifically on the callback passed to subscribe as the `refreshRows` function has internal reactivity due to using database() under the hood.
const unsubscribe = database().subscribe(() => void refreshRows());

onCleanup(() => {
unsubscribe();
});
});

return result;
};
What is the proper way to address the warning in this case? Or is this something I can safely ignore perhaps?
5 replies