ansa
ansa•3y ago

Helper Functions

can we run queries within queries? there's some logic that we want to share between two different queries, but we're not sure what the best way to go about this is. basically we have an admin table, and before all the admin operations that require admin privileges, we want to query the admin table and check that the user is there. we would like to put this into one method we could call from all the "privileged" operations. how should we do this?
13 Replies
ian
ian•3y ago
One pattern to use here is to just write a normal function that both queries call. The function will likely need to take the db object as a parameter. Another pattern would be to make a function that wraps your query, like middleware. But that's a bit more complicated to write generically. I hope to do a blog post on that pattern soon
jamwt
jamwt•3y ago
@ansa as an example, a game built on convex called Fast5 does this with a file called common.ts. You can check it out here: https://github.com/get-convex/fast5/blob/main/convex/common.ts
GitHub
fast5/common.ts at main · get-convex/fast5
The fastest word game in the west. Contribute to get-convex/fast5 development by creating an account on GitHub.
jamwt
jamwt•3y ago
in particular the first function, getUser, is used by most of the convex queries and mutations
ansa
ansaOP•3y ago
if we write our own function, like in the example, there's no atomicity guarantees right? so we shouldn't create such a function which queries then edits the db? also, what's the best way to type these without typing them as "any"? we have one solution here:
export const getUser = async ({
db,
auth,
}: Parameters<
Parameters<typeof query>[0]
>[0]): Promise<Document<"users"> | null> => {
const identity = await auth.getUserIdentity();
if (!identity) {
throw new Error("Called getUser without authentication present");
}

return await db
.query("users")
.filter((q) => q.eq(q.field("tokenIdentifier"), identity.tokenIdentifier))
.first();
};
export const getUser = async ({
db,
auth,
}: Parameters<
Parameters<typeof query>[0]
>[0]): Promise<Document<"users"> | null> => {
const identity = await auth.getUserIdentity();
if (!identity) {
throw new Error("Called getUser without authentication present");
}

return await db
.query("users")
.filter((q) => q.eq(q.field("tokenIdentifier"), identity.tokenIdentifier))
.first();
};
jamwt
jamwt•3y ago
@ansa as long as those functions are called inside a convex mutation, they have the same atomicity guarantees basically, convex mutations ensure any javascript/typescript function anywhere in the call stack is also a participant in the consistent + atomic interaction with the underlying database re: the typing, you should be able to type db as DatabaseReader or DatabaseWriter depending on if they mutate the database or not. in this case, since this function is read only, you should be able to use DatabaseReader so the function can be used by both queries and mutations (Since DatabaseWriter extends DatabaseReader mutations can call this function too)
ansa
ansaOP•3y ago
i tried import { Auth } from "./_generated/server" but it didnt seem to work :/
jamwt
jamwt•3y ago
okay, hmm... I'll ping our resident type expert who knows all these things super well so when he's online he can help out! @alexcole did DatabaseReader work for the first argument?
ansa
ansaOP•3y ago
yes!
jamwt
jamwt•3y ago
great, one down 😄 how about using Auth from "convex/server" ? looks like Auth may not be part of codegen, it may just be in the static convex/server package
ansa
ansaOP•3y ago
awesome that works, thank you!!
alexcole
alexcole•3y ago
Yep, we only put things in ./_generated/server if they have a more specific type based on your schema. auth is the same for everyone so it lives in convex/server.

Did you find this page helpful?