zx
zx•3mo ago

Zeron AI Chat App

https://www.zeron.sh/ Built with Convex, Clerk, and TanStack Start!
15 Replies
Nicolas
Nicolas•3mo ago
Really cool UI!
Hmza
Hmza•3mo ago
šŸ‘€
No description
zx
zxOP•3mo ago
This is fixed now
ampp
ampp•2mo ago
Nice, it's a very good example of a convex project, I just noticed that you are using ctx.runMutation and ctx.runQuery within other mutations/queries however this is better done as typescript functions as last i checked there is a performance penalty using these "subquery/mutation" calls and could cause more write conflicts as all mutations would be locked longer. So authenticate should look more like
export async function authenticateUser(
ctx: QueryCtx,
authId: string
): Promise<Doc<"users"> | null> {
const user = await ctx.db
.query("users")
.withIndex("by_auth_id", (q) => q.eq("authId", authId))
.first();

if (!user) {
return null;
}

return user;
}
export async function authenticateUser(
ctx: QueryCtx,
authId: string
): Promise<Doc<"users"> | null> {
const user = await ctx.db
.query("users")
.withIndex("by_auth_id", (q) => q.eq("authId", authId))
.first();

if (!user) {
return null;
}

return user;
}
id also remove from authenticate, components are like "subqueries".
const subscription = await polar.getCurrentSubscription(ctx, {
userId: user._id,
});
const subscription = await polar.getCurrentSubscription(ctx, {
userId: user._id,
});
from the authenticate as this would hurt performance on every query also, Id cache the subscription type in the users table. I was also curious about this, i'm not aware of any performance hits using clerk?
README
### Jun 18, 2025 Notes
Ended up removing the Clerk authentication check on the backend as that was slowing down the site significantly.
README
### Jun 18, 2025 Notes
Ended up removing the Clerk authentication check on the backend as that was slowing down the site significantly.
zx
zxOP•2mo ago
Hey thanks for the input For some reason when I had the clerk check in beforeLoad on tanstack start it created a considerable delay between page changes I decided to only check auth on frontend and everything loaded a lot faster As for the polar subquery, I see what you mean but I'm not sure how to optimize that since a lot of places I need the subscription data although I could do that in a separate util
ampp
ampp•2mo ago
yeah i try to always reduce the number of queries that always run from every function to 1 or 2
zx
zxOP•2mo ago
That makes sense I had little to no experience with convex going into this and I'm still not too knowledgeable about it so it's good feedback
ampp
ampp•2mo ago
its really difficult on a convex backend that is like 50k lines lol.. but id just update users when polar updates but i havent used polar yet
zx
zxOP•2mo ago
oh yeah I would like to denormalize data but I want to be confident nothing get's out of sync when denormalizing
ampp
ampp•2mo ago
For backend auth you are sorta doing it with authenticateUser with my clerk setup normally what i do is make a authWrapper that only checks that the user has a identity so it runs no user query.
async function authenticationRequired(ctx: QueryCtx | MutationCtx | ActionCtx) {
const identity = await ctx.auth.getUserIdentity()

if (identity === null) {
throw new ConvexError('This function requires authentication!')
}
return identity
}

export const authMutation = customMutation(
rawMutation,
customCtx(async (ctx) => {
await authenticationRequired(ctx)
return triggers.wrapDB(ctx)
})
)
async function authenticationRequired(ctx: QueryCtx | MutationCtx | ActionCtx) {
const identity = await ctx.auth.getUserIdentity()

if (identity === null) {
throw new ConvexError('This function requires authentication!')
}
return identity
}

export const authMutation = customMutation(
rawMutation,
customCtx(async (ctx) => {
await authenticationRequired(ctx)
return triggers.wrapDB(ctx)
})
)
on my more complex ents wrapped setup i have like 4 types No auth, identity only, identity + user, identity + specific session, user, identity
so on like a userMutation i could access ctx.user and have that be whatever i want. on the session ones i can pass in various types of sessionIds to build a context for anything or it tries to level up your access as much as it can given the data it has. So for the complex setup i insert a row into a Sessions Table that is like the authentication check that the backend knows about the user. and that caches all relevant data. Any db call to a document other than ctx.auth.getUserIdentity eats bandwidth so queries can really get out of control fast I am curious if you are planning to keep adding on features to this?
zx
zxOP•2mo ago
I am but I’m currently trying to fix a lot of things I rushed through for the hackathon
ampp
ampp•2mo ago
do you want to switch it over to the agent component? I'm possibly interested in helping,
zx
zxOP•2mo ago
Hmm I'm not sure if that's something I want to do, I chose not to use the agent component in order to have more flexibility on the shape of threads/messages I wasn't sure how to do that with the agent component and I was worried i would spend too much time figuring it out Do you think it would be better to switch to the agent component?
ampp
ampp•2mo ago
I'm still trying to determine it myself if i need some like tool only agent requests, i plan to go as deep as possible with some agent flows well beyond a chat app. Ian is open to changing the agent component but that requires putting forth a reasonable need.
Jumping Back
Jumping Back•2w ago
Any updates agent vs without agent comp

Did you find this page helpful?