My function can take in different adapters along with their optional options.
// Query adapter type 1
type O1 = { opt: 1 }
const adapter1 = (key: string, options?: O1) => 1
// Query adapter type 2
type O2 = { opt: 2 }
const adapter2 = (key: string, options?: O2) => 2
// There can be numerous query adapters, but they all follow a similar format
As mentioned in the comments, there could be an infinite number of adapters (hence the need for a generic argument in the adapter consuming function). However, all adapters have their Parameters
in the common format of
[string, options?: unknown (generic)]
Below is the definition of the adapter consuming function:
// Type definition helper for the adapter
type Fn = <O, R>(key: string, options?: O) => R
// 'consumer' function which accepts these adapters and their options as an optional argument
const query = <F extends Fn, O extends Parameters<F>[1]>(
key: string,
adapter: F,
options?: O
): ReturnType<F> => adapter(key, options)
I expected TypeScript to correctly infer the options
based on the provided arguments, however, that is not the case:
// These should be valid (omitting optional configuration)
query('1', adapter1)
query('2', adapter2)
// These should also be valid (with configuration applied)
query('1config', adapter1, { opt: 1 })
query('2config', adapter2, { opt: 2 })
// These should throw an error due to configuration type mismatch
query('1error', adapter1, { foo: 'bar' })
query('2error', adapter2, { foo: 'bar' })
However, all of these examples are triggering a TypeScript error due to a mismatch in the adapter arguments provided. The error message reads as follows:
Argument of type '(key: string, options?: O1) => number' is not assignable to parameter of type 'Fn'.
Types of parameters 'options' and 'options' are incompatible.
Type 'O | undefined' is not assignable to type 'O1 | undefined'.
Type 'O' is not assignable to type 'O1 | undefined'.
In this scenario, one could resolve the issue by changing O
to O extends O1 | O2 | undefined
. However, since there can be theoretically unlimited variations in adapter options, I still need my consumer function to accurately infer the option object for enhanced type safety when users interact with the query
function and specify the options
object.
The actual problem is a bit more complex with callbacks, but this example nicely illustrates the issue. Please refrain from commenting on the use of
for direct invocation, as it is beyond the scope of the question.(key, options) => adapter(key, options)
Here is the TS Playground for you to experiment with