kstulgys
kstulgys8mo ago

Ents is in maintenance mode. Should I use it?

"Ents is in maintenance mode. We're open to taking PRs, and will make sure it doesn't break. There will not be active feature development from the Convex team." Is it OK to use ents? Should I avoid using it if I started a new project?
25 Replies
Convex Bot
Convex Bot8mo ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
erquhart
erquhart8mo ago
No danger in using it, really comes down to preference. It can save boilerplate on relationship related code for sure. Just comes with the caveats you mentioned.
kstulgys
kstulgysOP8mo ago
Thanks for the response. What caveats?
ballingt
ballingt8mo ago
We're open to taking PRs, and will make sure it doesn't break. There will not be active feature development from the Convex team.
We like the stuff in Ents and are thinking about how to incorporate some of it into Convex core. We're not adding new feature to Ents.
Micha
Micha6mo ago
Does that mean if I go now with Ents, I can expect some relevant migration later if you ship your own version as part of the convex core?
ballingt
ballingt6mo ago
Migration as in a stack post comparing the two approaches and outlining the differences and how to update, yes. An automated codemod, probably not; the changes may be deep enough that it would be difficult automatically convert the code.
ian
ian6mo ago
You should check out the new helpers here: https://stack.convex.dev/merging-streams-of-convex-data and article on doing SQL with it here: https://stack.convex.dev/translate-sql-into-convex-queries I'd love to know if this is sufficient to skip using Ents, or if there was something else you really liked - e.g. edge definition & traversal sugar
Merging Streams of Convex data
New convex-helpers are available now for fetching streams of documents, merging them together, filtering them them out, and paginating the results. Wi...
Translate SQL into Convex Queries
Here’s a cheatsheet with examples of conversions between SQL queries and Convex queries. This article is geared towards developers (and LLMs) who have...
Micha
Micha6mo ago
Thanks for sharing—I will check it. The second article about SQL is probably not so relevant to me as I come from Fauna (their service is ending at the end of May—eventually, this is an opportunity for you to get more Fauna customers a new home 😉 ).
ian
ian5mo ago
I never actually used Fauna - what's an example query look like? It's their special graphql-style declarative syntax?
Micha
Micha5mo ago
They had GraphQL support, but they dropped it in favor of their more powerful FQL language, which is inspired by JavaScript, Python, and GraphQL. Collections (Table) Written in a language file called fsl (fauna schema language). I appreciated the co-location of collection constraints, making it easy to understand a collection and its associated rules.
collection User {
firstName: String
...
compute emailVerification: String? = doc => { getVerificationEmail(doc.id)} // A field that will be resolved while fetching

compute roles: Array<String> = (user => currentRoles())
activeOrganization: Ref<Organization>?
organizations: Array<Ref<Organization>>?

// A check constraint ensures the created/updated/replaced data comply a specific shape
check isActiveOrganizationPartOfOrganizations ((user) => {
if(user.activeOrganization != null) {
user.organizations?.includes(user.activeOrganization)
} else {
true
}
})

check uniqueEmails (user => user.emails.length == user.emails.distinct().length)
unique [mva(.emails)]
check uniqueAccounts (user => user.accounts.length == user.accounts.distinct().length)
unique [mva(.accounts)]
}
collection User {
firstName: String
...
compute emailVerification: String? = doc => { getVerificationEmail(doc.id)} // A field that will be resolved while fetching

compute roles: Array<String> = (user => currentRoles())
activeOrganization: Ref<Organization>?
organizations: Array<Ref<Organization>>?

// A check constraint ensures the created/updated/replaced data comply a specific shape
check isActiveOrganizationPartOfOrganizations ((user) => {
if(user.activeOrganization != null) {
user.organizations?.includes(user.activeOrganization)
} else {
true
}
})

check uniqueEmails (user => user.emails.length == user.emails.distinct().length)
unique [mva(.emails)]
check uniqueAccounts (user => user.accounts.length == user.accounts.distinct().length)
unique [mva(.accounts)]
}
CRUD (Written in FQL - Fauna query language) I liked the short, but powerful syntax.
// Read
User.byId("123")
User.all()
User.first()

// They make it amazingly easy to filter based on or retrieve related ents
User.all().where((user) => user.activeOrganization.plan == "Pro")
User.first().activeOrganization

// Create
User.create({
firstName: "John"
})

// Delete
User.byId("123").delete()

// Update & Replace
User.byId("123").update({})
User.byId("123").replace({})

// Projection
User.first() {
firstName,
lastName,
primaryEmail
}
// Read
User.byId("123")
User.all()
User.first()

// They make it amazingly easy to filter based on or retrieve related ents
User.all().where((user) => user.activeOrganization.plan == "Pro")
User.first().activeOrganization

// Create
User.create({
firstName: "John"
})

// Delete
User.byId("123").delete()

// Update & Replace
User.byId("123").update({})
User.byId("123").replace({})

// Projection
User.first() {
firstName,
lastName,
primaryEmail
}
More queries: https://docs.fauna.com/fauna/current/reference/fql-api/cheat-sheet/ Security (ABAC) - written in FSL I love their simple yet powerful ABAC system - in my opinion, the best part of Fauna, because it makes security relatively easy.
role role_user {
privileges User {
read {
predicate (doc =>
doc == Query.identity()
)
}
write {
predicate ((oldDoc, newDoc) => {
oldDoc.emails == newDoc.emails &&
oldDoc.accounts == newDoc.accounts &&
oldDoc == Query.identity()
})
}
}

privileges createOrganization {
call {
//A user can only be part of 5 organizations to prevent organization abuse.
// Limit can be lifted if you have a specific use case.
predicate ((data) => {
Query.identity()!.organizations == null || Query.identity()!.organizations.length < 5
})
}
}


membership User {
predicate ( (doc) => isCalledWithAccessToken() )
}
}
role role_user {
privileges User {
read {
predicate (doc =>
doc == Query.identity()
)
}
write {
predicate ((oldDoc, newDoc) => {
oldDoc.emails == newDoc.emails &&
oldDoc.accounts == newDoc.accounts &&
oldDoc == Query.identity()
})
}
}

privileges createOrganization {
call {
//A user can only be part of 5 organizations to prevent organization abuse.
// Limit can be lifted if you have a specific use case.
predicate ((data) => {
Query.identity()!.organizations == null || Query.identity()!.organizations.length < 5
})
}
}


membership User {
predicate ( (doc) => isCalledWithAccessToken() )
}
}
ian
ian5mo ago
Thanks! Looks pretty nifty - similar to a few existing helpers in Convex. Best of luck with the migration!
joeeh
joeeh3mo ago
Glad I'm not the only one that struggled with this decision. I'm new to convex and fairly new to full stack development with some experience using Supabase. Ents seems to fit my use case fairly well given my business logic requires a lot of joins. Development sped up immensely using the Clerk Ents Starter moving from Supabase, and I didn't notice the maintenance mode notice until after getting my apps functionality working (I think the Ents maintenance mode notice should probably be placed in the Started Template description page). Now, I'm stuck in this limbo of choices between, moving forward with Ents or taking the time to try and create the same thing from scratch using vanilla convex. It sounds like the same functionality can be achieved with Helpers, but Ents do provide some efficiency over vanilla + helpers? I guess I'm just worried Ents may not work with other Convex functionality in the future (maybe already the case now? I'm unsure) but I would also like to avoid restarting the project again if possible... Apologies if this is a case of overthinking, but the more I learn in this world of development, its clear theres always more than one way to skin the cat, but each method will have tradeoffs. The tradeoffs are sort of Known Unknowns to me right now, and I'd love any direction or docs that might help make them Known Knowns, if you know what I mean. Haha. Many thanks in advance!
erquhart
erquhart3mo ago
One of the best things about the Ents docs are these dropdowns showing how to implement each Ents feature in plain Convex. So that helps from a migration standpoint. I haven't used Ents, but the one thing that made me seriously consider using it a while back is cascading deletes. It takes a decent amount of boilerplate to do well. Everything else it covers works well without a library, just a bit manual. But it's also beneficial to understand the Convex api's deeply if you're building on Convex, which was the real reason I didn't use Ents (and don't use many helpers either, thought I probably should). The team has also shared that the core api's are due for an upgrade, another data point to consider in your decision.
No description
ampp
ampp3mo ago
I've used ents a lot, i refactored our rather large code base to mostly deal with our own id's so i lost some of the benefits of edges in a otherwise extremely relational project. I've been programming in another convex codebase with a traditional db structure without ents and i do miss it more often then i expected.
Im still failing to see what other than major breaking changes to all convex projects would mess up ents, and i still think we could make the PR's. There hasn't been any updates to ents as it's basically stable. It would take a lot for me to consider migrating. And these days with tab complete i could rewrite most projects fairly quickly. Nothing will make ctx.db.get(id) not feel dirty 😛
erquhart
erquhart3mo ago
Using Convex api's without ents for many many months will lol (but seriously thanks for chiming in from an Ents user perspective)
ian
ian3mo ago
When we eventually rev the query syntax, we'll keep support for the old syntax for a long time, so ents will continue to work, and yeah any gaps hopefully could be patched relatively easily. (also - we're moving towards table("users").get(id) for better safety behavior) It'd be great to know which features are the most critical - I'd love to incorporate ents inspiration, while not pulling over things that are magical / have unclear underlying behavior. Unfortunately cascading deletes are tricky b/c there's no guarantee the dependency tree will all be operable in a transaction, and async behavior might be unexpected / create inconsistent views where a document is queried but its parent is missing. And to set expectations, we have our plate pretty full right now, so new query syntax isn't making progress in the next few weeks (at least)
ampp
ampp3mo ago
Yeah i haven't used cascade delete much and its of less value when you cant use edges. I just like the general syntax with edges the most when properly setup. I wish i could use edges on my own id's, that would be amazing, but all that would be solved if you could generate your own ids on the front-end. To read: get, getX, edge, edgeX, unique, uniqueX, first, firstX, take, etc. To write: insert, insertMany, patch, replace, delete is all so very nice. I think .table() with the above is #1 EntWriter design #2 Edge and edges and creating linking tables is #3 And less important: Rules RLS #4 Cascade Delete #5 Ive found that edges keep me from having misnamed id()s, i just seem to discover them more often by surprise in native convex undetected.
I also have never used .field much and have found more value in using validator objects in the schema so i keep everything in sync. For me the only conversion friction really is getting the functions file setup. Being able to specify unique in schema is nice, but that might count as Magic behavior?
ian
ian3mo ago
"my own id's" being non-_id fields? For misnamed id()s you mean like v.id("ussers") ? Have you seen the v = typedV(schema) helper? it makes a type-safe v.id as well as adds v.doc
zbeyens
zbeyens2mo ago
I'm migrating from tRPC + Prisma + tanstack query to Convex, and like most migrations, I don't want any feature regression but also no performance/scalability regression. Here is my feedback: - I've started from convex cursor rules, but I had to iterate on it since it's missing many features. - It's kind of a puzzle to setup everything between core, helpers, components and ents; and official docs vs stack. - I'd rewrite the docs examples to be scalable-first, instead of "optimize when you have thousands of records". I have a general rule to never fetch all docs, forbidding methods like collect(): this should be an exception instead of default choice. - Pagination + filters was challenging. A common mistake AI was doing is to filter after pagination, or over-fetch to fill page size. The solution was to use merged streams. - Wish there was an official state-of-the-art guide/example with most features Prisma, which is basically what I had to do by digging into the docs/stack
jamwt
jamwt2mo ago
this is very fair criticism. a big feature in the fall is a "convex book", wrapping up all the lessons we've learned over the last two years of going into production with teams and convex. it will take all the pieces of from helpers/stack etc and formalize a more broad set of recommendations to actual backend problems. it will live authoritatively on docs.convex.dev . would love to get your feedback on it when we have it!
ampp
ampp2mo ago
I'm not sure if you ever saw https://labs.convex.dev/convex-vs-prisma
Prisma vs Convex
Compare Prisma examples with Convex
ampp
ampp2mo ago
It would be nice to get a fully updated version of this with all the different solutions I'd like to think I'm at a expert level with convex but i honestly still get confused with patination and some setup things, even in vanilla. 😅 And i really don't want to leave the editor for some doc website. I think my mind is a bit corrupted as i have analyzed over 100 projects in good depth and i have seen so many variations, causing memory corruption 😛
zbeyens
zbeyens2mo ago
Very helpful, thanks! From there, we can see that pages, filter and aggregates need some love regarding scalability Convex Ents is the most similar to Prisma – removes so much boilerplate, hopefully we don't have 3 alternatives in the next major release 🙂 One thing I like from Prisma that is lacking in Ents: you can do almost all queries in a single promise. Note that having a similar API will help LLMs since it has a perfect knowledge about Prisma
No description
zbeyens
zbeyens2mo ago
For non-indexed filtered pages, merged streams work well, it lacks support of .withSearchIndex though Same while building the optimized cursor rules - that I'll share soon 🙂
ampp
ampp2mo ago
when i was converting some stuff from native to ents cursor tab would work amazingly well at times based on existing code.

Did you find this page helpful?