Matt Luo
Matt Luo5mo ago

Convex Ents: how to express optional reference in 1 to many relationship?

From this documentation https://labs.convex.dev/convex-ents/schema, I'm trying to figure out a common use case, how to express zero-or-many? Looking at the documentation's example, how can I express that a user can have zero or many messages?
defineEntSchema({
users: defineEnt({
name: v.string(),
}).edges("messages", { ref: true }),
messages: defineEnt({
text: v.string(),
}).edge("user"),
});
defineEntSchema({
users: defineEnt({
name: v.string(),
}).edges("messages", { ref: true }),
messages: defineEnt({
text: v.string(),
}).edge("user"),
});
14 Replies
Matt Luo
Matt LuoOP5mo ago
Adding , { optional: true } works for edge() but not edges(). Why? I think I'm mostly done with my migration to Ents, but I am stuck on this issue. This is the schema validation error message I get since I cannot figure out how to express a zero-or-many. ✖ Schema validation failed Document with ID "kn7591x3ngxfc7gyw8zgx9c1f96w4abc" in table "analyzeSessions" does not match the schema: Value does not match validator. Path: .userId Value: "" Validator: v.id("users") If I add a optional property,
typescript
analyzeSessions: defineEnt({

}).edge('user', { optional: true }),
typescript
analyzeSessions: defineEnt({

}).edge('user', { optional: true }),
Then I will get this error in npx convex dev.
400 Bad Request: Error: Hit an error while evaluating your schema:
Uncaught Error: The edge "user" in table "analyzeSessions" cannot be optional, as it must store the 1:many edge as a field. Check the its inverse edge "analyzeSessions" in table "users".
at defineEntSchema (../node_modules/convex-ents/src/schema.ts:157:14)
at <anonymous> (../convex/schema.ts:4:19)
400 Bad Request: Error: Hit an error while evaluating your schema:
Uncaught Error: The edge "user" in table "analyzeSessions" cannot be optional, as it must store the 1:many edge as a field. Check the its inverse edge "analyzeSessions" in table "users".
at defineEntSchema (../node_modules/convex-ents/src/schema.ts:157:14)
at <anonymous> (../convex/schema.ts:4:19)
users table has .edges('analyzeSessions', { ref: true }) I have found that although this error message appears in the npx convex dev terminal, there is no runtime error or log event when running the application and inserting documents. In other words, the document inserts successfully, with a "" value in userId, despite this npx convex dev error message:
✖ Schema validation failed
Document with ID "kn7591x3ngxfc7gyw8zgx9c1f96w4abc" in table "analyzeSessions" does not match the schema: Value does not match validator.
Path: .userId
Value: ""
Validator: v.id("users")
✖ Schema validation failed
Document with ID "kn7591x3ngxfc7gyw8zgx9c1f96w4abc" in table "analyzeSessions" does not match the schema: Value does not match validator.
Path: .userId
Value: ""
Validator: v.id("users")
ampp
ampp5mo ago
So this setup should work, its by default optional because the remote object stores the list of ids in this case the messages table has the list, so I dont ever specify optional And i just always clear the table after changing shape, i don't think ive been able to insert something with schema validation on that would cause a schema error.
Matt Luo
Matt LuoOP5mo ago
Thanks, but after I clear the table, and insert a new document, I get that error again It's a blocker because my npm run build will error I think the problem is that my insert() function is inserting a "" value. Is it possible to insert a null value at all? How about with with Convex Ents? If so, how about for a userId column that is supposed to be a foreign key to users table?
ampp
ampp5mo ago
I've had to be careful in designing it so that i only insert the downstream records after the upstream has been created. Inserting blank would be bad for sure. We spent at least a week working on schema and i still have refactored so many times not catching some of these things. Or wanting to use ents in more cases then i should.
Matt Luo
Matt LuoOP5mo ago
So, is it expected behavior that convex ents cannot store null? A zero-to-many relationship is a common need So if you have a zero to many relationship, are you forced to exempt the table out of Ents (and so use vanilla convex for that table)? By the way, I’ve tried to insert null by skipping the property in the ctx.table insert(), but “” will still be in the database
Michal Srb
Michal Srb5mo ago
Looking at the documentation's example, how can I express that a user can have zero or many messages?
This is already what the code does. Are you instead looking for "message can have an author or not"? In that case you'd be running into: https://github.com/xixixao/convex-ents/issues/5 And the workaround is to use a many:many edge, and enforce the author uniqueness in your code.
GitHub
1:many edges require single cardinality end to be required · Issue ...
This will require more configuration, especially for cascading deletes. We'll hold off on adding this until we get people asking for it.
Matt Luo
Matt LuoOP5mo ago
Yes, I think i am looking for "message can have an author or not". In other words, I'm looking for a zero-to-many relationship, or a optional one-to-many relationship. I think the many:many edge can be a workaround I can do for now. Or limit my app's functionality to require user sign-in on feature that insert to a table with a userId column. Thanks Michal, It's an unfortunate limitation to not have optional one-to-many, which is a very common relationship Another workaround I considered is to make two separate tables, one with a userId column and one without a userId column. That way, I can have a normal looking one-to-many edge. This approach can be pretty complicated since I want a non-logged-in user to be able to see a significant amount of my app before landing on a login wall
Michal Srb
Michal Srb5mo ago
This is what anonymous users are for, that way you already have a user ID. They have been built into Convex Auth but not entirely finished yet. null wouldnt' probably work at all for you, if that's the scenario? Can you describe in more detail what you are trying to implement? (from end-user perspective)
Matt Luo
Matt LuoOP5mo ago
Yeah, right now, an end user can go to https://www.claritytext.com/translate right now without hitting a login wall. When that translation form is submitted, Convex will essentially insert documents into a table with a userId column
ClarityText
Foreign language learning and content analysis.
Matt Luo
Matt LuoOP5mo ago
Your suggestion about an anonymous user account is interesting. Maybe in Clerk I can create a single "guest" user account that holds all this anonymous activity But anyway, I think I'm going to reach this zero-to-many limitation again and again. It's a very common pattern
Michal Srb
Michal Srb5mo ago
I haven't found to be very common, so far I know of one other user running into this.
Michal Srb
Michal Srb5mo ago
Looks like Clerk doesn't support Anonymous users yet: https://github.com/orgs/clerk/discussions/3542 😦
GitHub
Does it support anonymous login? · clerk · Discussion #3542
Hello. I'm wondering if there is support for anonymous login, similar to what is offered in Supabase, or if there are plans to support it in the future, or perhaps methods to implement it mysel...
Matt Luo
Matt LuoOP5mo ago
Hmm okay let me walk back that statement and think through how common it is. I suppose I would need a situation where a one-to-many is neccesary and the reference does not (yet) exist I suppose I didn't feel the pain of this in other situations in my career because I could always readily insert a null value
ampp
ampp5mo ago
I'm a bit confused, we've been building something with complexities that rival most platforms. We have users that can have many orgs, with many members within that org. Within the org the member has roles, those roles have many permissions and the member has many roles or zero roles. Each member can control what any other member can see. Plus we plan to allow a lot of outside visibility. I have yet to run into a inescapable many to zero situation. I guess since our users are many to many with members now, i could get away with linking to zero users. But in principle our members own control of records, through object that have many owner types. So i can create those objects without members needing to exist. But we designed it like this for many other reasons. its built so many members or other entities can own a object at a near infinite level of granularity, it just happens to fix this problem. but i deal with all the issues around having unions of ids and a table type identifier. which i could in theory go without? 😅