kstulgys
kstulgys5mo 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?
11 Replies
Convex Bot
Convex Bot5mo 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
erquhart5mo 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
kstulgysOP5mo ago
Thanks for the response. What caveats?
ballingt
ballingt5mo 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
Micha2mo 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
ballingt2mo 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
ian2mo 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
Micha2mo 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
ian2mo ago
I never actually used Fauna - what's an example query look like? It's their special graphql-style declarative syntax?
Micha
Micha2mo 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
ian2mo ago
Thanks! Looks pretty nifty - similar to a few existing helpers in Convex. Best of luck with the migration!

Did you find this page helpful?