Lets say I have a function like this:
type FooParams <Params extends unknown[], Result> = {
name: string,
request: (...params: Params) => Promise<Result>
}
const foo = <Params extends unknown[], Result>(params: FooParams<Params, Result>) => {
// do stuff
}
Lets also say I have a couple of requests and a "store" containing those requests:
interface Todo {
id: number;
title: string;
}
const getTodos: () => Promise<Todo[]> = () => Promise.resolve([{ id: 2, title: 'clean' }]);
const getTodo: (id: number) => Promise<Todo> = (id: number) => Promise.resolve({ id, title: 'clean' });
const requestStore = {
getTodo: {
name: 'getTodo',
request: getTodo,
},
getTodos: {
name: 'getTodos',
request: getTodos,
},
} as const;
I would now like to generate foo-functions for each request in the store.
Adding them manually with explicit key for each request in the store works:
// Works
foo(requestStore['getTodo'])
But adding them dynamically like this does not work:
// Does not work. Error message:
// Type '(() => Promise<Todo[]>) | ((id: number) => Promise<Todo>)' is not assignable to type '() => Promise<Todo[]>'.
// Type '(id: number) => Promise<Todo>' is not assignable to type '() => Promise<Todo[]>'.(2322)
const createFooFromStore = (requestName: keyof typeof requestStore) => () => {
const { name, request } = requestStore[requestName]
foo({ name, request })
}
Is there someway one could re-write this so that a foo-function could be created for each entry in the "requestStore"?
Here is a playground link with the example code:
In which the "request"-parameter at the very bottom shows an error message.
I cant post this playground link into the comments section... but this works playground
// The type constraint here does the trick.
// It allows every specified key in the Requeststore as an input for foo
// The
const foo = <T extends RequestStore[keyof RequestStore]>(params: T) => {
// return params to check type infering
return params
}
interface Todo {
id: number;
title: string;
}
const getTodos: () => Promise<Todo[]> = () => Promise.resolve([{ id: 2, title: 'clean' }]);
const getTodo: (id: number) => Promise<Todo> = (id: number) => Promise.resolve({ id, title: 'clean' });
// if you want to ensure typesafety here you can use the new satisfies keyword to prevent wrong request definitions but you don't need it
const requestStore = {
getTodo: {
name: 'getTodo',
request: getTodo,
},
getTodos: {
name: 'getTodos',
request: getTodos,
},
} as const satisfies Record<string, { name: string, request: (...args: any[]) => any }>;
type RequestStore = typeof requestStore
// Works
foo(requestStore['getTodo'])
foo(requestStore['getTodos'])
const createFooFromStore = <T extends keyof typeof requestStore>(requestName: T) => () => foo(requestStore[requestName])
const a = createFooFromStore("getTodo") // valid
const b = createFooFromStore("getTodos") // valid
const c = createFooFromStore("getTos") // invalid
const createFooFromStore2 = <T extends keyof typeof requestStore>(requestName: T) => () => { foo(requestStore[requestName]) }