Reputation: 8096
I am working on a library called expresskit that lets you use decorators to define routes/params/etc for express. I'm in the middle of refactoring and am thinking I need to limit the types of responses a route can have. As an example, here is how routes are created now-
export default class UserRouter {
@Route('GET', '/user/:userId')
public static getUser(@Param('userId') userId: number): any {
return new User();
}
}
A Route is applied to a static method. The static method may return a value directly or a Promise
. I'd like to require promises going forward like this-
export default class UserRouter {
@Route('GET', '/user/:userId')
public static async getUser(@Param('userId') userId: number): Promise<User> {
return Promise.resolve(new User());
}
}
The reason being, the logic behind these routes are getting bloated and tangle to handle different types of responses. Since most routes will likely be asynchronous, I'd rather have cleaner core code by relying on async. My Route decorator function looks like this-
export default function Route(routeMethod: RouteMethod,
path: string) {
return function(object: any, method: string) {
let config: IRouteConfig = {
name: 'Route',
routeMethod: routeMethod,
path: path
};
DecoratorManager.registerMethodDecorator(object, method, config);
}
}
I've created a generic manager service to keep track of where decorators are registered. In the sample I can get the class as well as the method name. I can reference this later like this- object[method]
.
On my decorator, I would like to require that the class method is asynchronous. But since I only get the object and method name I don't know if I can do that. How can I require the class method returns Promise<any>
?
Upvotes: 3
Views: 1039
Reputation: 20179
You need to add some types to indicate that your decorator factory returns a decorator function that only accepts property descriptors with the expected function signature (...any[]) => Promise<any>
. I went ahead and made a generic type alias RouteFunction
for it:
type RouteMethod = 'GET' | 'POST'; // or whatever your library supports
// The function types the decorator accepts
// Note: if needed, you can restrict the argument types as well!
type RouteFunction<T> = (...args: any[]) => Promise<T>;
// The decorator type that the factory produces
type RouteDecorator<T> = (
object: Object,
method: string,
desc: TypedPropertyDescriptor<RouteFunction<T>> // <<< Magic!
) => TypedPropertyDescriptor<RouteFunction<T>>
// Decorator factory implementation
function Route<T>(routeMethod: RouteMethod, path: string) : RouteDecorator<T> {
return (object, method, desc) => {
// Actual route registration goes here
return desc;
}
}
Example usage to demonstrate type checking:
class RouteExample {
@Route('GET', 'test1') // works, return type is a Promise
test1(): Promise<number> {
return Promise.resolve(1);
}
@Route('GET', 'test2') // error, return type not a Promise
test2(): number {
return 2;
}
@Route('GET', 'test3') // error, property is a number rather than a function
get test3(): Promise<number> {
return Promise.resolve(3);
}
}
Upvotes: 5