Shared functionality patterns and authz
being honest, it feels very not ideal,
as i'll need to be constantly thinking about it
e.g
I have a
setStatus
function to patch 1 field.
I need it to be it's own mutation so I can call it from an action (LLM calling) with runMutation()
, but I also want it to be a pure function so I can call it efficiently from another mutation.
If I were able to use runQuery()
efficiently (read as equal to calling the function directly) from other queries, or just calling it straight as I did with currentUser
above (even better!), I would not have to worry about splitting it into two functions ever. It would just work and actually force me to abstract properly (it happened a few times before I found out I should not be doing it π
).
it's at least a missed opportunity
------------------------------------
BTW thank you guys for being so open and attentive here, I hope I'm not being too much of a bother12 Replies
@igor9silva here works
When you say "I'll need to be constantly thinking about it", can you say more on that?
for sure
also "It would just work and actually force me to abstract properly"
disclaimer:
I have a bunch of use cases where I will call queries and mutations from actions AND from other mutations, maybe that's not common?
super common π
I have to step away for a bit so I'll share this pre-emptively:β¨β¨Once at scale and beyond fast early prototyping, the mindset switch I embraced was to write my backend as plain Typescript functions that accept a context and interact with the database. Those functions can then be exposed in the api via Convex functions, or more than one can be used in a function. The plain functions become composable building blocks. In those functions I also include shared logic to enforce authz.
I can say a lot about the patterns I'ver personally landed on but there are a number of ways folks have approached this. It helps to understand that Convex is primarily a reactive database core. The team is actively working on building up supporting features and ergonomics, but it's still early days in that regard. So it's all about patterns.β¨β¨Despite some of the work it takes to build up those patterns, the benefits of Convex have massively outweigh the presence of mind required, imo.
if able to just write stuff like that
and call
find()
from other queries/mutations + ctx.runQuery(api.tasks.find)
from actions it'd just work for any use case, only thing to wonder would be "public or internal?"
but since I have to call the handler fn directly from queries/mutations, for every new piece I'll have to wonder "do I need to call it from an action?" + "do I need to call it from a mutation/query?"
and not just for new stuff, because I'll eventually need it from a mutation where I previously did not
it also cascades into other minor concerns such as:
- naming conventioning, as I'll have up to 2 variables for the same thing
- typing, as just implementing the handler will perfectly infer every param type :chef_kiss:
that was an amazing comment
i'll go with that approach for now, always writing pure functions and then wrapping them as needed
but nevertheless it's great to hear that "the team has acknowledged it'd be ideal if queries and mutations could call one another without overhead", it would be awesome
It helps to understand that Convex is primarily a reactive database core. The team is actively working on building up supporting features and ergonomics, but it's still early days in that regard.that was also very nice to learn I have not been following Convex for long, and that's not clear or easily assumed I've been (deeply) prototyping with a bunch of different web stacks for the last few months (I've tried the same use case with MongoDB Atlas, Upstash, Redis, RabbitMQ, G Pub/Sub, NATS), with a bunch of different architectures, and I must say I'm very impressed by Convex so far it fills SO many boxes and, most importantly, fits my personal engineering vision so deeply that it's been hard to believe it's a real product I'm a long-time MongoDB (+Atlas) user and fan, but it's been painfully hard in the "ergonomics" aspect you mentioned (e.g. I may have spent a total of 20 hours at least fighting the
ObjectId
type on TS), and Convex feels like what I always wanted MongoDB to be
so even though you guys are "primarily a reactive database core", you've been kicking ass in the ergonomics area already! DX been amazing so far
the query/mutation/action abstraction is so beautiful - truly the building blocks I've been looking for
oh, and you guys being TanStack sponsors?!?! wildly amazing, I'm a monumental fan of Tanner and his work
(that also implies you'll be supporting their stuff very quickly, which already does look like with Start π)
and the energy here...
I've written maybe 3 times here in the last few days, and have been replied with deep and meaningful answers within minutes, even from the CEO
I'm honestly rooting for you guys, you're definitely on the right track!Just wanted to say I agree with @igor9silva for these exact reasons. The less I have to worry about naming and typing, the happier I'm.
If calling
find()
directly can cause problems, something else like find.run()
would already help. Better than having to split into a separate function.Note, the question of βdo I need to call it from an action/query/mutation is always answered by the typescript compiler
Also, just to clarify, Iβm just a community member like you. And I 100% feel the things you said above.
having deeply engaged community is also +1οΈβ£
I mean like my use case
"do I need it from an action?"
I also have to create Types now π’
I think I'll just ignore performance for a while
You can adopt these techniques incrementally. Focus on building your project first - it's the best way to learn IMO
Create your validators outside of the convex function,
import type { Infer } from 'convex/values'
- infer the type. Now you can share the type with your typescript functions, and the validator with other convex functions. You can even destructure the validator to select just the parts you want, if you need to.