It seems that the error you're encountering is valid from a technical perspective. The compiler is cautious because it cannot guarantee that the argument type of innerFn
will accept a broad "User" for its "user" property. An example illustrating the compiler's concern is provided below:
const innerFn = async (arg: {
user: { id: number, name: "a" | "b" }
}) => ({ a: 1, b: 2 }[arg.user.name]).toFixed(2);
In this case, innerFn
demands its argument to have the type
{ user: { id: number, name: "a" | "b" } }
, which is more specific than simply "User." Attempting to call
innerFn()
with an argument of type
User
could result in a runtime error if
arg.user.name
is neither
"a"
nor
"b"
, leading to a
TypeError
due to dereferencing
undefined
.
However, your typings for outerFn()
do accept innerFn
without any issues:
const returnFn = outerFn(innerFn); // no compiler error
The inferred type Args
is
{user: {id: number; name: "a" | "b"}}
, and
Omit<Args, "user"> & { user: { id: number; name: string; }; }
essentially equates to
User
. However, this alignment does not cater to what
innerFn
specifically requires. This conflict arises because:
// 'Omit<Args, "user"> & { user: { id: number; name: string; }; }' is assignable to the constraint of
// type 'Args', but 'Args' could be instantiated with a different subtype of constraint 'ArgsWithUser'.
Consequently, executing returnFn()
might lead to a runtime error without any prior warning from the compiler:
returnFn({}).catch(e => console.log(e)) // no compiler error, but:
// đŸ’¥ (intermediate value)[arg.user.name] is undefined
This situational inconsistency exists. While quite unlikely, you may consider performing an assertion (or what you referred to as a "cast") to swiftly move forward.
If you wish to refactor and circumvent this issue effectively, you can incorporate "User" into a type by supplementing it instead of excluding it:
type InnerFn<A, R> = (args: A & User) => Promise<R>
type ReturnFn<A, R> = (args: A) => Promise<R>
const outerFn = <A, R>(innerFn: InnerFn<A, R>): ReturnFn<A, R> => {
const user: User['user'] = {
id: 1,
name: 'Something',
};
return async (argsWithoutUser) => innerFn({ ...argsWithoutUser, user });
};
This solution entails no compilation errors and functions correctly with the examples presented. Its applicability to your specific scenario remains uncertain, but the compiler satisfaction stands nevertheless.
Playground link to code