To model your userCards and userCollections tables using Convex Ents and edges, you would define your entities (users, cards, collections) and then use .edges() to declare the relationships between them. This is more expressive and concise than manually managing v.id() references and indexes in vanilla Convex.
Example: Vanilla Convex
With vanilla Convex, you might have something like:
users: defineTable({
name: v.string(),
}),
cards: defineTable({
title: v.string(),
}),
userCards: defineTable({
userId: v.id("users"),
cardId: v.id("cards"),
}),
collections: defineTable({
userId: v.id("users"),
name: v.string(),
}),
userCollections: defineTable({
userId: v.id("users"),
collectionId: v.id("collections"),
}),
You would then use queries and manual joins to relate users to cards and collections.
Example: Convex Ents
With Convex Ents, you can define these relationships more declaratively:
import { defineEnt } from "convex-ents";
import { v } from "convex/values";
const users = defineEnt({
name: v.string(),
})
.edges("cards")
.edges("collections");
const cards = defineEnt({
title: v.string(),
})
.edges("users");
const collections = defineEnt({
name: v.string(),
})
.edges("users");
.edges("cards") on users means a user can have many cards.
.edges("users") on cards means a card can belong to many users (many-to-many).
- Similarly for
collections and users.
This approach automatically sets up the join tables and indexes for you, and you can traverse relationships easily in your queries:
const userCards = await ctx.table("users").getX(userId).edge("cards");
const userCollections = await ctx.table("users").getX(userId).edge("collections");