Luke
Luke•17mo ago

Best practices for joining data in query

I am attempting to join a project's notes to this query, I get no errors however the type inference insists that the property does not exist when I try to access it in the response. export const getProjectById = query({ args: { projectId: v.id("projects") }, handler: withUser(async ({ db, user }, { projectId }) => { const project = await db.get(projectId); if (!project) { throw new Error("Project not found"); } if (project.user !== user._id) { throw new Error("Unauthorized"); } const notes = await db .query("notes") .withIndex("by_project", (q) => q.eq("project", project._id)) .collect(); return { ...project, notes: notes, }; }), });
29 Replies
ballingt
ballingt•17mo ago
@Luke what's the schema for projects? Also where is the error in the above code? The property notes does not exist on the client?
Luke
LukeOP•17mo ago
The schema for projects is projects: defineTable({ title: v.string(), user: v.id("users"), isArchived: v.boolean(), parentProject: v.optional(v.id("projects")), icon: v.optional(v.string()), isFavorited: v.boolean(), }) .index("by_user", ["user"]) .index("by_user_parent", ["user", "parentProject"]), And yes, its that it doesn't exist However I see similar queries in this example (https://github.com/get-convex/convex-demos/blob/6fc30f0841274711c6cd2ca3d5df13dce3506e8e/users-and-auth/convex/messages.ts#L4) so not sure whats going wrong
ballingt
ballingt•17mo ago
@Luke could you show as example of this error? looks fine to me, notes should be a property that exists and is an array of (an unknown number of, maybe 0) note records.
Luke
LukeOP•17mo ago
Of course, so I query it like this const project = useQuery(api.projects.getById, { projectId: params.projectId, }); and then try to access notes via project?.notes and I get this error Property 'notes' does not exist on type '{ _id: Id<"projects">; _creationTime: number; parentProject?: Id<"projects"> | undefined; icon?: string | undefined; title: string; user: Id<"users">; isArchived: boolean; isFavorited: boolean; } | { ...; }'. Property 'notes' does not exist on type '{ _id: Id<"projects">; _creationTime: number; parentProject?: Id<"projects"> | undefined; icon?: string | undefined; title: string; user: Id<"users">; isArchived: boolean; isFavorited: boolean; }'.ts(2339) any
ballingt
ballingt•17mo ago
getById and getProjectById don't match, could that be it?
Luke
LukeOP•17mo ago
I just renamed the function in the meantime Apologies, should've mentioned
Luke
LukeOP•17mo ago
Intellisense also doesn't recognise the notes list
No description
Luke
LukeOP•17mo ago
I also tried removing the "withUser" wrapper, which also made no difference The wrapper was copied directly from the helpers repo
ballingt
ballingt•17mo ago
huh, I don't see the issue. I'm curious about that union, of the three union member s (the one we can see, the middle one that's collapsed here, and undefined,) do you know how the first and second differ?
Luke
LukeOP•17mo ago
I'm not explicitly defining a union type, so not sure where its getting it from...
Luke
LukeOP•17mo ago
No description
Luke
LukeOP•17mo ago
Which doesn't make sense, as the type of the "notes" value can be seem here
No description
ballingt
ballingt•17mo ago
Could you try setting "noErrorTruncation": true in your tsconfig.json not sure if it's the top level one or the convex/tsconfig.json that would impact this
Luke
LukeOP•17mo ago
I'll change both (property) getById: FunctionReference<"query", "public", { projectId: Id<"projects">; }, { _id: Id<"projects">; _creationTime: number; parentProject?: Id<"projects"> | undefined; icon?: string | undefined; title: string; user: Id<"users">; isArchived: boolean; isFavorited: boolean; } | { notes: { _id: Id<"notes">; _creationTime: number; project?: Id<"projects"> | undefined; content?: string | undefined; title: string; user: Id<"users">; isArchived: boolean; }[]; _id: Id<"projects">; _creationTime: number; parentProject?: Id<"projects"> | undefined; icon?: string | undefined; title: string; user: Id<"users">; isArchived: boolean; isFavorited: boolean; }>
ballingt
ballingt•17mo ago
maybe save it to a variable to see where this happens,
const x = {
...project,
notes,
};
return x;
const x = {
...project,
notes,
};
return x;
Luke
LukeOP•17mo ago
Same issue
ballingt
ballingt•17mo ago
if this is happening in the return value and you still see this without withUser then I'd love to get a repro, sounds like a bug or at least something pretty confusing
Luke
LukeOP•17mo ago
I'll try removing withUser and using the intermediate variable
ballingt
ballingt•17mo ago
curious too if hovering over the function definition shows this too or not
Luke
LukeOP•17mo ago
export const getById = query({ args: { projectId: v.id("projects") }, handler: async (ctx, args) => { const identity = await ctx.auth.getUserIdentity(); const project = await ctx.db.get(args.projectId); if (!project) { throw new Error("Not found"); } if (!project.isArchived) { return project; } if (!identity) { throw new Error("Not authenticated"); } const userId = identity.subject; if (project.user !== userId) { throw new Error("Unauthorized"); } const notes = await ctx.db .query("notes") .withIndex("by_project", (q) => q.eq("project", project._id)) .collect(); const x = { ...project, notes: notes, }; return x; }, }); So this is the raw query without any wrappers/anything special projects: defineTable({ title: v.string(), user: v.id("users"), isArchived: v.boolean(), parentProject: v.optional(v.id("projects")), icon: v.optional(v.string()), isFavorited: v.boolean(), }) .index("by_user", ["user"]) .index("by_user_parent", ["user", "parentProject"]), notes: defineTable({ title: v.string(), content: v.optional(v.string()), user: v.id("users"), project: v.optional(v.id("projects")), isArchived: v.boolean(), }) .index("by_user", ["user"]) .index("by_project", ["project"]), This is the relevant part of the schema
lee
lee•17mo ago
there's a return project; in there
Luke
LukeOP•17mo ago
Oh wow, very good spot! Thank you! Let me just verify this That explains the union Can confirm that sorted it
ballingt
ballingt•17mo ago
sweet
Luke
LukeOP•17mo ago
Glad it wasn't a bug
ballingt
ballingt•17mo ago
TypeScript being the hero for once
Luke
LukeOP•17mo ago
Love to see it
lee
lee•17mo ago
noice 🙂
Luke
LukeOP•17mo ago
PS, only been playing around with Convex for a few days but really enjoying it so far Coming from a background of several Firebase projects and most recently a Supabase project Thanks for the support
ballingt
ballingt•17mo ago
Good to hear! Let us know how things go, feedback is great

Did you find this page helpful?