Typing issue - findFirst/findMany `with` parameter with a dynamic value

I am having an issue using findFirst and findMany where including a dynamic value for the with parameter breaks the types for the output. Here's a simplified example of what I have going on:
export const findWorkById = async (id: number, scope: "basic" | "extended") => {
const work = await db.query.works.findFirst({
where: eq(works.id, id),
with: scope === "extended" ? { workDescription: true, workContact: true } : undefined,
});

return work;
};
export const findWorkById = async (id: number, scope: "basic" | "extended") => {
const work = await db.query.works.findFirst({
where: eq(works.id, id),
with: scope === "extended" ? { workDescription: true, workContact: true } : undefined,
});

return work;
};
When calling the findWorkById method with the scope being extended I would expect the type of the returned value to be:
const work: {
id: number;
companyId: number;
createdAt: Date;
updatedAt: Date;
workDescription: {
...;
};
workContact: {
...;
};
} | undefined
const work: {
id: number;
companyId: number;
createdAt: Date;
updatedAt: Date;
workDescription: {
...;
};
workContact: {
...;
};
} | undefined
But whatever I pass as the scope value the return type is always the 'base' type for the work schema which is:
const work: {
id: number;
companyId: number;
createdAt: Date;
updatedAt: Date;
} | undefined
const work: {
id: number;
companyId: number;
createdAt: Date;
updatedAt: Date;
} | undefined
When I provide the { workDescription: true, workContact: true } directly to the with parameter, without the ternary, the the returned object has the correct type. Has anyone faced this issue and have a good solution? Do I just have morning-brain and am missing something silly? Any help is appreciated!
1 Reply
cenuijza
cenuijza3mo ago
When dealing with union types where one type is a subtype of another, TypeScript simplifies the return type to the more general type. So findWorkById will just have a return type of
{
id: number;
companyId: number;
createdAt: Date;
updatedAt: Date;
}
{
id: number;
companyId: number;
createdAt: Date;
updatedAt: Date;
}
So, if you want it to return a union type, I guess you have to explicitly mark the return type of findWorkById. Or you would have to return some sort of discriminated union ... make the return types different enough so that one is not a subset of the other, then the inferred return type will be a union type. You could do some function 'overloading' for the different return types, something along the line of
function f(scope: "extended"): { a: number; extra: string };
function f(scope: "basic"): { a: number };

function f(scope: "extended" | "basic") {
return scope === "basic" ? { a: 1 } : { a: 1, extra: "extra" };
}

const a = f("basic");
const b = f("extended");
function f(scope: "extended"): { a: number; extra: string };
function f(scope: "basic"): { a: number };

function f(scope: "extended" | "basic") {
return scope === "basic" ? { a: 1 } : { a: 1, extra: "extra" };
}

const a = f("basic");
const b = f("extended");
Want results from more Discord servers?
Add your server