Reputation: 371
I'll try my best in describing our case, as I have no idea how to title this.
We have a package, which handles requesting our services/cloud functions (if for example service A needs something done by service B, it uses this package). This package exposes a function to call each service (for example for service auth
, there would be a auth
function exposed. We have a definition of available endpoints for each service and as this grows, it's harder to maintain since we were unable to figure out a way to type the parameters for the requests.
What I wanted to do, is simply have another type which would contain the parameter types for each endpoint of each service.
For example we have endpoint called getEmail
, which would return email of a user and requires an id
parameter.
Type params type would look like this:
type Params = {
getEmail: {
id: number;
}
}
Very simplified version of the code:
type FunctionName = 'foo' | 'bar' | 'baz';
type FunctionParams = {
foo: {
myVar: number;
};
bar: {
myVar: string;
};
baz: Record<string, number>;
};
declare const sendRequest: (...args: any[]) => any;
const callFunction = (
fn: FunctionName,
params: FunctionParams[typeof fn],
// ^^^^^^^^^ What should I put here?
) => {
sendRequest(fn, params);
};
callFunction('foo', {
myVar: 'this should fail',
// This is allowed, as the type of the second parameter is:
// { myVar: number; } | { myVar: string; } | Record<string, number>) => void
// I want it to be { myVar: number; }
});
Upvotes: 19
Views: 15077
Reputation: 19006
May be, you will need other kind of implementation, like following:
/**
* API Functions Mapping.
*/
interface ApiFunctions {
foo: {
myVar: number;
};
bar: {
myVar: string;
};
baz: Record<string, number>;
}
/**
* Generic API Functions method.
*
* @param args
* @returns
*/
declare function sendRequest(...args: any[]): any;
/**
* Call {@link sendRequest} wrapper.
*
* @param fn
* @param params
*/
function callFunction<T extends keyof ApiFunctions>(
fn: T,
params: ApiFunctions[typeof fn]
) {
sendRequest(fn, params);
}
callFunction('foo', {
myVar: 'this should FAIL', // <<<--- FAIL!
});
callFunction('bar', {
myVar: 'this should WORK',
});
Probably the following article can help you: https://typeofnan.dev/how-to-make-one-function-argument-dependent-on-another-in-typescript/
Upvotes: 1
Reputation: 7584
You want to use generics! You can parameterize callFunction
to bind fn
to a specific string, and then use this to index FunctionParams
:
function callFunction<T extends FunctionName>(
fn: T,
params: FunctionParams[T],
) {
sendRequest(fn, params);
};
callFunction('foo', {
myVar: 'this should fail', //BOOM!
});
Also, don't waste energy maintaining types. FunctionName
can be as easy as
type FunctionName = keyof FunctionParams
with that, every time you add new params, FunctionName
gets updated.
See also this playground
Upvotes: 27