Creating call signatures for the functions withCookies()
, withToken()
, and a combined function withProps()
is certainly possible. However, one challenge you may face is related to type inference in TypeScript. The compiler might struggle with contextually typing the ctx
parameter within your innermost function due to the generic nature of the functions involved. This can lead to difficulties in proper type parameter inference, as demonstrated in issues such as microsoft/TypeScript#33042 and microsoft/TypeScript#38872. To overcome this, manual specification of types at certain points in your code may be necessary for the compiler to correctly understand the logic.
To start, defining the interfaces Cookies
and Token
helps give clarity to the objects being referenced:
interface Cookies {
cookies: Record<string, string>;
}
interface Token {
token: string;
}
The withCookies()
function should be generic with an object type T
, taking a callback function of type (ctx: T & Cookies) => void
and returning a function of type (ctx: T) => void
:
/*
declare const withCookies: (
cb: (ctx: T & Cookies) => void
) => (ctx: T) => void
*/
Similarly, withToken()
follows a comparable structure but substitutes Cookies
with Token
:
/*
declare const withToken: (
cb: (ctx: T & Token) => void
) => (ctx: T) => void
*/
The similarity in signatures suggests the possibility of a utility function like withProps()
that generates these functions. Here's a sample implementation:
const withProps = <U extends object>(u: U) =>
<T extends object>(cb: (ctx: T & U) => void) =>
(ctx: T) => cb({ ...ctx, ...u });
By using withProps()
with Cookies
as a parameter, you create the withCookies
function:
const withCookies = withProps<Cookies>(
{ cookies: { a: "hello", b: "goodbye" } }
);
A similar approach applies to generate withToken
by calling withProps()
with Token
:
const withToken = withProps<Token>(
{ token: "token" }
);
You can validate that these functions align with the defined call signatures.
Attempting to use these functions uncovers contextual typing challenges:
const badResult = withCookies(
withToken(
(ctx) => {
console.log(ctx.cookies) // error! Property 'cookies' does not exist on type 'never'
console.log(ctx.token) // error! Property 'token' does not exist on type 'never'
}
)
);
// const badResult: (ctx: never) => void
The inferred types result in errors due to incorrect contextually typed parameters. To address this, explicit annotations or specifying generic type parameters can be used, ensuring correct behavior:
const result = withCookies(
withToken(
(ctx: Cookies & Token) => {
console.log(ctx.cookies)
console.log(ctx.token)
}
);
// const result: (ctx: object) => void
const alsoResult = withCookies(
withToken<Cookies>(
(ctx) => {
console.log(ctx.cookies)
console.log(ctx.token)
}
)
);
// const alsoResult: (ctx: object) => void
Both approaches lead to functional output, demonstrating the successful propagation of objects passed into withProps()
into subsequent callbacks.
Click here to access the code via Playground link