Reputation: 2630
I'm trying to write a declaration file for a library we're using without any means to modify it.
The way it works is that you can send a configuration object, the return will be based on some keys inside of it
const args = {
// ...
resources: {
One: {
query: { url: 'value' }
}
}
}
library(args)
Invoking it returns an object with the deeply nested keys of resources
as functions
const ret = {
One: {
query: async (query) => <data>
}
}
// ret.One.query(args) => data
Ideally each <data>
would also be typed, I'm not sure if this is possible due to the dynamic keys? I've tried a couple of approaches using keyof
the parameters without any luck
Edit: Updated example
const config = {
// ...
resources: {
User: {
find: { url: 'http://example.com/userapi' }
},
Dashboard: {
update: { method: 'post', url: 'http://example.com/dashboardapi'}
}
}
}
const services = serviceCreator(config)
// service creator turns the supplied resources object into promise functions
await services.User.find({id: '123'}) // Promise<User>
await services.Dashboard.update({user: '123', action: 'save'}) // Promise<Dashboard>
Upvotes: 1
Views: 70
Reputation: 11577
I'm fairly new to TS & find this question really interesting. Here's my take on it, it's fairly verbose & require a weird type input. However it gets pretty good type suggestion. I would love feedback from more experienced users:
/**
* map dynamic resource type to method name,
* i.e 'User' -> 'find' | 'lookup'
*/
type MethodMap = Record<string, string>
interface ResourceParams {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
}
/**
* Each method in resource type has interface ResourceParams
*/
type Resources<T extends MethodMap> = {
[K in keyof T]: {
[S in T[K]]: ResourceParams;
}
}
interface Config<T extends MethodMap> {
resources: Resources<T>;
[key: string]: unknown;
}
type Result<T extends MethodMap> = {
[K in keyof T]: {
[S in T[K]]: () => Promise<any>
}
}
declare const library: <T extends MethodMap>(config: Config<T>) => Result<T>
// usage
const result = library<{ User: 'find' | 'lookup'; Dashboard: 'search' }>({
resources: {
User: {
find: { url: 'value' },
lookup: { url: 'asdads', method: 'GET' },
},
Dashboard: {
search: { url: 'value' }
},
}
})
result.User.find() // OK
result.User.lookup() // OK
result.Dashboard.search() // OK
result.User.search() // Not OK
result.Dashboard.find() // Not OK
result.Store.find() // Not OK
Upvotes: 1
Reputation: 2534
Without further information, I'm guessing that you're looking for something like this:
type Arg<T> = { resources: T; }
type Ret<T> = {
[K in keyof T]: {
query: (...query: any[]) => Promise<any>;
}
}
declare const library: <T>(arg: Arg<T>) => Ret<T>;
Let's test it to see if it works:
const args = {
resources: {
One: {
query: { url: 'value' }
}
}
}
var ret = library(args);
async function test() {
await ret.One.query(); // OK
}
You can try it in this playground.
Upvotes: 2