W
Wasp-langโ€ข4mo ago
wardbox

Typescript not validating included relation

This is likely due to my inexperience with typescript, but I've been trying to access a related property that I've included on a query, but for some reason typescript isn't validating that it's a valid property. I think it only has scalar properties available.
const { data: team, status } = useQuery(getTeamById, { id: props.match.params.id });
console.log(team.logo);
const { data: team, status } = useQuery(getTeamById, { id: props.match.params.id });
console.log(team.logo);
gives this error:
Property 'logo' does not exist on type 'GetResult<{ id: string; name: string; createdAt: Date; }, unknown> & {}'.ts(2339)
Property 'logo' does not exist on type 'GetResult<{ id: string; name: string; createdAt: Date; }, unknown> & {}'.ts(2339)
export const getTeamById: GetTeamById<{ id: string }, Team> = async ({ id }, context) => {
if (!context.user) {
throw new HttpError(401);
}

try {
const team = await context.entities.Team.findFirstOrThrow({
where: {
id: id
},
include: {
logo: true,
}
});
return team;
} catch (error: any) {
throw new HttpError(404, error.message);
}
}
export const getTeamById: GetTeamById<{ id: string }, Team> = async ({ id }, context) => {
if (!context.user) {
throw new HttpError(401);
}

try {
const team = await context.entities.Team.findFirstOrThrow({
where: {
id: id
},
include: {
logo: true,
}
});
return team;
} catch (error: any) {
throw new HttpError(404, error.message);
}
}
I'd expect prisma and/or wasp handles the types when i run wasp db migrate-dev , is that not correct?
16 Replies
kapa.ai
kapa.aiโ€ข4mo ago
Hi @wardbox. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- The issue you're experiencing is related to full-stack type safety in Wasp. When you annotate your function with a type, like GetTeamById<{ id: string }, Team>, it tells TypeScript that the function will return an object of type Team. However, the Team type only includes its scalar properties and not the relationships. In your case, logo is a related property, not a scalar property, so TypeScript doesn't recognize it as part of the Team type. This is why you're getting the error Property 'logo' does not exist on type 'GetResult<{ id: string; name: string; createdAt: Date; }, unknown> & {}. To resolve this issue, you need to create a new type that includes the logo property and use that type to annotate your function. Unfortunately, as of now, Wasp doesn't automatically generate types that include related properties when you run wasp db migrate-dev. This issue has been discussed in the Wasp community before. You can refer to this Discord thread (https://discord.com/channels/686873244791210014/1217160369789735062) and this Github issue (https://github.com/wasp-lang/wasp/issues/901) for more information. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @wardbox, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
wardbox
wardboxโ€ข4mo ago
This works on the component where useQuery is run, but when I pass that down to another component i have no way of importing the type with logo added :think_cry:
MEE6
MEE6โ€ข4mo ago
Wohooo @wardbox, you just became a Waspeteer level 1!
wardbox
wardboxโ€ข4mo ago
as a workaround, currently exporting the type at the level where usequery is run and importing it in subcomponents
import { type Team as OriginalTeam, File } from 'wasp/entities';
export type Team = OriginalTeam & {
logo: File;
};
import { type Team as OriginalTeam, File } from 'wasp/entities';
export type Team = OriginalTeam & {
logo: File;
};
miho
mihoโ€ข4mo ago
@sodic has a good insight with using satisfies that could help you
Filip
Filipโ€ข4mo ago
@miho Thanks for calling me in ๐Ÿ™‚ @wardbox, to avoid defining a new type for the Query's return value, use TypeScript's satisfies keyword. This tells TypeScript: "Make sure this function doesn't take any arguments and works with the proper context object, but infer the correct return type on your own." The satisfies keyword isn't Wasp-specific. It's a feature of TypeScript, so make sure to add it to your TS knowledge base and take it with you wherever you go ๐Ÿ˜„ In your case, the function should probably look like this:
export const getTeamById = (async ({ id }, context) => {
if (!context.user) {
throw new HttpError(401);
}

try {
const team = await context.entities.Team.findFirstOrThrow({
where: {
id: id
},
include: {
logo: true,
}
});
return team;
} catch (error: any) {
throw new HttpError(404, error.message);
}
}) satisfies GetTeamById<{ id: string }, Team>
export const getTeamById = (async ({ id }, context) => {
if (!context.user) {
throw new HttpError(401);
}

try {
const team = await context.entities.Team.findFirstOrThrow({
where: {
id: id
},
include: {
logo: true,
}
});
return team;
} catch (error: any) {
throw new HttpError(404, error.message);
}
}) satisfies GetTeamById<{ id: string }, Team>
miho
mihoโ€ข4mo ago
@sodic maybe we can remove the Team as return value or it will infer the correct shape even with the Team return value?
Filip
Filipโ€ข4mo ago
We can, but keeping it makes sure that you always return something that satisfies the Team constraint So, @wardbox, it's up to you to choose what you want to infer and what you want to constrain explicitly
Filip
Filipโ€ข4mo ago
Btw, our docs used to mention this, but we removed it thinking it was too much of an edge case. Looks like we'll be adding it back in after all (issue here) ๐Ÿ™‚ Here's the relevant section from the old docs:
GitHub
Issues ยท wasp-lang/wasp
The fastest way to develop full-stack web apps with React & Node.js. - Issues ยท wasp-lang/wasp
No description
wardbox
wardboxโ€ข4mo ago
That works wherever I call the useQuery function but is there anything special I need to do when I pass this down to another component? If i have something like this for example it isn't aware of the usequery return value team.tsx
<TeamProfile team={team as Team} />
<TeamProfile team={team as Team} />
teamprofile.tsx
import {
type Team
} from 'wasp/entities';

export default function TeamProfile({ team }: { team: Team }) {
import {
type Team
} from 'wasp/entities';

export default function TeamProfile({ team }: { team: Team }) {
team is still like this, i presume from importing the entity
(parameter) team: GetResult<{
id: string;
name: string;
createdAt: Date;
}, unknown> & {}
(parameter) team: GetResult<{
id: string;
name: string;
createdAt: Date;
}, unknown> & {}
i suppose i could just store in state and pass that around instead?
Filip
Filipโ€ข4mo ago
Again, this is not Wasp specific. When you declare something as type X, TypeScript will treat it as type X. You declared the team prop as being of type Team. Therefore, TypeScript thinks it's a Team (and nothing else). If you want to use the query's return type (that's more than just a Team) as the type of the prop, there's two ways to do it: - Option 1: Use the Query's return type as a source of truth and rely on type inference for typing the client stuff. - Option 2: Explicitly define the payload type in a single place and import/reuse it on both the client and the server. Option 1 Extract the inferred return type on the frontend and use it there:
type TeamProfileProps = {
team: Awaited<ReturnType<typeof getTeamById>>;
};

export default function TeamProfile({ team }: TeamProfileProps) {
// ...
}
type TeamProfileProps = {
team: Awaited<ReturnType<typeof getTeamById>>;
};

export default function TeamProfile({ team }: TeamProfileProps) {
// ...
}
This way, the Query's inferred return type becomes the source of truth for what you pass around in your components (and changes depending on what you do in the Query). Option 2 This is what you already did in this message: https://discord.com/channels/686873244791210014/1233163179140321362/1233181933496832135 It all comes down to what you prefer: - Option 1 is "let typescript infer all the types and do the checking" - Option 2 is "I want to explicitly state the types I'm working with" If you go with Option 2, you can ditch the whole satisfies story and just explicitly define your payload type (that's richer than just a Team)
wardbox
wardboxโ€ข4mo ago
thanks - option 1 sounds easier, first time using typing and it is so nice to have everything typed but i kinda dove in so figuring these details out as i go!
Filip
Filipโ€ข4mo ago
I prefer Option 1 as well. It looks a little wild, but once you set it up, you just code and forget about the types And the compiler warns you when you mess up ๐Ÿ™‚
wardbox
wardboxโ€ข4mo ago
Absolutely, all I was aiming for was avoid having to update types in multiple places, that achieves what I wanted!
Filip
Filipโ€ข4mo ago
Glad I could help! Let me know if you have trouble with setting this up, I can create a gist to help you out if necessary. I've added this message to the issue that talks about reincluding this into the docs: https://github.com/wasp-lang/wasp/issues/1920
wardbox
wardboxโ€ข4mo ago
got it working :ty2: