在一个Nest.js控制器方法的多个部分使用一个类型



我有一个后端和前端之间的共享类型系统,在控制器中看起来像这样:

@Controller()
export class MyController {

@ApplyRoute('GET /foo')
async findAll(
@Query() { name }: Routes['GET /foo']['query'], // repeated route name
): Promise<Routes['GET /foo']['response']> { // repeated route name
return { foo: true }
}
}

注意'GET/foo'出现了3次:一次用来声明http方法/路径,一次用来获取查询参数的类型,还有一次用来强制返回类型。

看到操场

我正在寻找一种方法以某种方式删除重复,但我似乎想不出任何方法可以与装饰者玩得很好。现在有人可能使用一条路线的路径,然后使用另一条路线的queryresponse,这可能是非常错误的。

如何删除路由名的重复?


例如,做这个完全无效语法所暗示的事情:

@Controller()
export class MyController {

@ApplyRoute('GET /foo') {
async findAll(
@Query() { name }: CurrentRoute['query'],
): Promise<CurrentRoute['response']> {
return { foo: true }
}
}
}

对于额外的信息,管道工作如下:

有一个类型定义了所有路由,以及路由接受和返回的类型:

type Routes = {
'GET /foo': {
query: { name: string }
response: { foo: true }
}
'GET /bar': {
query: {},
response: { bar: true }
}
}

然后我们有一个小的辅助装饰器,它将路由名称解析为@RequestMapping()装饰器,以设置控制器方法的http方法和路径:

export function ApplyRoute(routeName: keyof Routes): MethodDecorator {
const [method, path] = routeName.split(' ') as ['GET' | 'POST', string]
return applyDecorators(
RequestMapping({ path, method: RequestMethod[method] }),
)
}

可以使用动态路由技术来删除代码重复。考虑这个例子:

// define a controller that will host all methods of `Routes`
// it's, of course, also possible to have many of these or even have them generated based on the `Routes` descriptor
@Controller()
export class MyController {}
function Action<T extends keyof Routes>(route: T, fn: (query: Routes[T]["query"]) => Routes[T]["response"]): void {
const [method, path] = route.split(" ") as ["GET" | "POST", string];
const key = path.split("/").join("_");
// assigning the function body to a method in the controller class
MyController.prototype[key] = fn;
// applying the `Query` directive to the first parameter
// this could also be configured through the `Routes` in case if you have e.g. post methods
Query()(MyController.prototype, key, 0);
// applying the `Get` decorator to the controller method
RequestMapping({ path: path, method: RequestMethod[method] })(
MyController,
key,
Object.getOwnPropertyDescriptor(MyController.prototype, key)
);
}
// now, register the methods in the global scope
Action("GET /foo", ({ name: string }) => ({ foo: true }));
Action("GET /bar", () => ({ bar: true }));

不幸的是,decorator不能改变一个类的定义,而且在Typescript的repo中有很多关于decorator不能修改类及其定义的问题。据我所知,这包括类型定义,所以没有直接的办法说,当一个方法有装饰符时,返回的应该是不同的类型。以您的人为示例为例,您可以得到的最接近的方法是将Routes['GET /foo']重命名为像type FooRoute = Routes['Get /foo']这样的类型别名,这样您现在只需要FooRoute['query']

// contrived example from original post, modified
import { applyDecorators, RequestMapping, RequestMethod, Controller, Query } from '@nestjs/common'
type Routes = {
'GET /foo': {
query: { name: string }
response: { foo: true }
}
'GET /bar': {
query: {},
response: { bar: true }
}
}
export function ApplyRoute(routeName: keyof Routes): MethodDecorator {
const [method, path] = routeName.split(' ') as ['GET' | 'POST', string]
return applyDecorators(
RequestMapping({ path, method: RequestMethod[method] }),
)
}
type FooRoute = Routes['GET /foo']
@Controller()
export class MyController {

@ApplyRoute('GET /foo')
async findAll(
@Query() { name }: FooRoute['query'], // repeated route name
): Promise<FooRoute['response']> { // repeated route name
return { foo: true }
}
}

所以现在有了更少的重复的代码,但最后它仍然是重复的。

最新更新