TripleSpeeder
TripleSpeeder•15mo ago

Is there a way to get the object type from a db.get result?

I want to create a generic Like table like this:
like: defineTable({
userId: v.id("users"),
targetId: v.union(
v.id("bike"),
v.id("part"),
v.id("event"),
);,
})
like: defineTable({
userId: v.id("users"),
targetId: v.union(
v.id("bike"),
v.id("part"),
v.id("event"),
);,
})
So i can use the same table for storing likes to bikes, parts or events. This works fine, but I'm struggling with one aspect: I would like to create a page where a users sees all objects he has liked. I can get the actual target object (bike, part or event) of a like with this query:
const like = await ctx.db.get(likeId);
return await ctx.db.get(like.targetId);
const like = await ctx.db.get(likeId);
return await ctx.db.get(like.targetId);
Problem is I don't know the actual type of the target object. And I need this information to display accordingly in the frontend. Is there some generic/built-in way to get the actual type of the get response?
9 Replies
TripleSpeeder
TripleSpeederOP•15mo ago
On typescript level in the frontend the retrieved target has the type `{_id: Id<"bike">, ...} | {_id: Id<"event">, ...} | {_id<"part">, ...} | null | undefined. I'm thinking I can do this via the _id, as it should have encoded the table name in some way, but i did not find any clear docs on this.
Dima Utkin
Dima Utkin•15mo ago
you can make a union of v.object and add a record type field, next to id, that would be more explicit, imho
TripleSpeeder
TripleSpeederOP•15mo ago
Yes, this would work. But i would need to explicitly pass this type field from the frontend to the "like()" function everywhere I'm using it. Not really a big deal, but I still think there must be a generic solution? I want to be able to slap my LikeButton onto everything without needing to think what type of object I'm actually liking. My horrible workaround right now is to throw the retrieved target object through a sequence of zod.safeParse() calls with all possible Schemas until one is successfull 🤪 If there is no builtin/generic way I fill definitely follow @Dima Utkins idea and store the target type when creating the Like entry.
Dima Utkin
Dima Utkin•15mo ago
maybe have a look at normalizeId
TripleSpeeder
TripleSpeederOP•15mo ago
Hmm, I think what i'm looking for would be the reverse of normalizeId(): Throw in the targetId and get back the tablename 🙂
lee
lee•15mo ago
if you have the list of possible tablenames, you can use normalizeId. See WrapReader.tableName in this library (Object.keys(this.rules) are the possible table names)
lee
lee•15mo ago
TripleSpeeder
TripleSpeederOP•15mo ago
Thanks @lee that is working! When creating a like I'm looking up the table name based on TargetID and store this in the likes table together with the targetId. When querying for liked objects I'm now getting the targetId together with the target tablename and based on that can show appropriate html. Frontend code lookes something like this:
type LikeCardProps = {
like: Doc<"like">;
};
const LikeCard = ({ like }: LikeCardProps) => {
switch (like.targetTableName) {
case "event":
return <EventCard eventId={like.targetId as Id<"event">} />;
case "bike":
return <BikeCard bikeId={like.targetId as Id<"bike">} />;
case "part":
return <PartCard partId={like.targetId as Id<"part">}/>
default:
return <div>Unhandled like target :-(</div>;
}
};
type LikeCardProps = {
like: Doc<"like">;
};
const LikeCard = ({ like }: LikeCardProps) => {
switch (like.targetTableName) {
case "event":
return <EventCard eventId={like.targetId as Id<"event">} />;
case "bike":
return <BikeCard bikeId={like.targetId as Id<"bike">} />;
case "part":
return <PartCard partId={like.targetId as Id<"part">}/>
default:
return <div>Unhandled like target :-(</div>;
}
};
There are probably ways to make this more generic/automated based on the list of allowed likeable types, but i spent far too much time already on this 😅